cloud: split shepherd up
Change-Id: I8e386d9eaaf17543743e1e8a37a8d71426910d59
Reviewed-on: https://review.monogon.dev/c/monogon/+/2213
Reviewed-by: Serge Bazanski <serge@monogon.tech>
Tested-by: Jenkins CI
diff --git a/cloud/shepherd/mini/main.go b/cloud/shepherd/mini/main.go
new file mode 100644
index 0000000..67231c0
--- /dev/null
+++ b/cloud/shepherd/mini/main.go
@@ -0,0 +1,191 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "io"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+
+ "k8s.io/klog/v2"
+
+ "source.monogon.dev/cloud/bmaas/bmdb"
+ "source.monogon.dev/cloud/bmaas/bmdb/model"
+ "source.monogon.dev/cloud/bmaas/bmdb/webug"
+ "source.monogon.dev/cloud/lib/component"
+ "source.monogon.dev/cloud/shepherd"
+ "source.monogon.dev/cloud/shepherd/manager"
+ clicontext "source.monogon.dev/metropolis/cli/pkg/context"
+)
+
+type Config struct {
+ Component component.ComponentConfig
+ BMDB bmdb.BMDB
+ WebugConfig webug.Config
+
+ InitializerConfig manager.InitializerConfig
+ ProvisionerConfig manager.ProvisionerConfig
+ RecovererConfig manager.RecovererConfig
+
+ SSHConfig sshConfig
+ DeviceListSource string
+ ProviderType model.Provider
+}
+
+// TODO(q3k): factor this out to BMDB library?
+func runtimeInfo() string {
+ hostname, _ := os.Hostname()
+ if hostname == "" {
+ hostname = "UNKNOWN"
+ }
+ return fmt.Sprintf("host %s", hostname)
+}
+
+func (c *Config) RegisterFlags() {
+ c.Component.RegisterFlags("shepherd")
+ c.BMDB.ComponentName = "shepherd-mini"
+ c.BMDB.RuntimeInfo = runtimeInfo()
+ c.BMDB.Database.RegisterFlags("bmdb")
+ c.WebugConfig.RegisterFlags()
+
+ c.InitializerConfig.RegisterFlags()
+ c.ProvisionerConfig.RegisterFlags()
+ c.RecovererConfig.RegisterFlags()
+
+ c.SSHConfig.RegisterFlags()
+ flag.StringVar(&c.DeviceListSource, "mini_device_list_url", "", "The url from where to fetch the device list. For local paths use file:// as scheme")
+ flag.Func("mini_provider", "The provider this mini shepherd should emulate. Supported values are: lumen,equinix", func(s string) error {
+ switch s {
+ case strings.ToLower(string(model.ProviderEquinix)):
+ c.ProviderType = model.ProviderEquinix
+ case strings.ToLower(string(model.ProviderLumen)):
+ c.ProviderType = model.ProviderLumen
+ default:
+ return fmt.Errorf("invalid provider name")
+ }
+ return nil
+ })
+}
+
+type deviceList []machine
+
+func (dl deviceList) asMap() map[shepherd.ProviderID]machine {
+ mm := make(map[shepherd.ProviderID]machine)
+ for _, m := range dl {
+ mm[m.ProviderID] = m
+ }
+ return mm
+}
+
+func fetchDeviceList(s string) (deviceList, error) {
+ var r io.Reader
+ u, err := url.Parse(s)
+ if err != nil {
+ return nil, fmt.Errorf("failed parsing device list url: %v", err)
+ }
+
+ if u.Scheme != "file" {
+ resp, err := http.Get(u.String())
+ if err != nil {
+ return nil, err
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ return nil, fmt.Errorf("invalid status code: %d != %v", http.StatusOK, resp.StatusCode)
+ }
+ r = resp.Body
+ } else {
+ f, err := os.Open(u.Path)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+ r = f
+ }
+
+ var d deviceList
+ dec := json.NewDecoder(r)
+ dec.DisallowUnknownFields()
+ if err := dec.Decode(&d); err != nil {
+ return nil, err
+ }
+
+ klog.Infof("Fetched device list with %d entries", len(d))
+
+ return d, nil
+}
+
+func main() {
+ var c Config
+ c.RegisterFlags()
+
+ flag.Parse()
+ if flag.NArg() > 0 {
+ klog.Exitf("unexpected positional arguments: %v", flag.Args())
+ }
+
+ registry := c.Component.PrometheusRegistry()
+ c.BMDB.EnableMetrics(registry)
+
+ ctx := clicontext.WithInterrupt(context.Background())
+ c.Component.StartPrometheus(ctx)
+
+ conn, err := c.BMDB.Open(true)
+ if err != nil {
+ klog.Exitf("Failed to open BMDB connection: %v", err)
+ }
+
+ sshClient, err := c.SSHConfig.NewClient()
+ if err != nil {
+ klog.Exitf("Failed to create SSH client: %v", err)
+ }
+
+ if c.DeviceListSource == "" {
+ klog.Exitf("-mini_device_list_source must be set")
+ }
+
+ list, err := fetchDeviceList(c.DeviceListSource)
+ if err != nil {
+ klog.Exitf("Failed to fetch device list: %v", err)
+ }
+
+ mini := &provider{
+ providerType: c.ProviderType,
+ machines: list.asMap(),
+ }
+
+ provisioner, err := manager.NewProvisioner(mini, c.ProvisionerConfig)
+ if err != nil {
+ klog.Exitf("%v", err)
+ }
+
+ initializer, err := manager.NewInitializer(mini, sshClient, c.InitializerConfig)
+ if err != nil {
+ klog.Exitf("%v", err)
+ }
+
+ go func() {
+ err = provisioner.Run(ctx, conn)
+ if err != nil {
+ klog.Exit(err)
+ }
+ }()
+ go func() {
+ err = manager.RunControlLoop(ctx, conn, initializer)
+ if err != nil {
+ klog.Exit(err)
+ }
+ }()
+ go func() {
+ if err := c.WebugConfig.Start(ctx, conn); err != nil && err != ctx.Err() {
+ klog.Exitf("Failed to start webug: %v", err)
+ }
+ }()
+
+ <-ctx.Done()
+}