blob: 67231c06bda3135109eb505b42415222e15d7675 [file] [log] [blame]
Tim Windelschmidtb6308cd2023-10-10 21:19:03 +02001package main
2
3import (
4 "context"
5 "encoding/json"
6 "flag"
7 "fmt"
8 "io"
9 "net/http"
10 "net/url"
11 "os"
12 "strings"
13
14 "k8s.io/klog/v2"
15
16 "source.monogon.dev/cloud/bmaas/bmdb"
17 "source.monogon.dev/cloud/bmaas/bmdb/model"
18 "source.monogon.dev/cloud/bmaas/bmdb/webug"
19 "source.monogon.dev/cloud/lib/component"
20 "source.monogon.dev/cloud/shepherd"
21 "source.monogon.dev/cloud/shepherd/manager"
22 clicontext "source.monogon.dev/metropolis/cli/pkg/context"
23)
24
25type Config struct {
26 Component component.ComponentConfig
27 BMDB bmdb.BMDB
28 WebugConfig webug.Config
29
30 InitializerConfig manager.InitializerConfig
31 ProvisionerConfig manager.ProvisionerConfig
32 RecovererConfig manager.RecovererConfig
33
34 SSHConfig sshConfig
35 DeviceListSource string
36 ProviderType model.Provider
37}
38
39// TODO(q3k): factor this out to BMDB library?
40func runtimeInfo() string {
41 hostname, _ := os.Hostname()
42 if hostname == "" {
43 hostname = "UNKNOWN"
44 }
45 return fmt.Sprintf("host %s", hostname)
46}
47
48func (c *Config) RegisterFlags() {
49 c.Component.RegisterFlags("shepherd")
50 c.BMDB.ComponentName = "shepherd-mini"
51 c.BMDB.RuntimeInfo = runtimeInfo()
52 c.BMDB.Database.RegisterFlags("bmdb")
53 c.WebugConfig.RegisterFlags()
54
55 c.InitializerConfig.RegisterFlags()
56 c.ProvisionerConfig.RegisterFlags()
57 c.RecovererConfig.RegisterFlags()
58
59 c.SSHConfig.RegisterFlags()
60 flag.StringVar(&c.DeviceListSource, "mini_device_list_url", "", "The url from where to fetch the device list. For local paths use file:// as scheme")
61 flag.Func("mini_provider", "The provider this mini shepherd should emulate. Supported values are: lumen,equinix", func(s string) error {
62 switch s {
63 case strings.ToLower(string(model.ProviderEquinix)):
64 c.ProviderType = model.ProviderEquinix
65 case strings.ToLower(string(model.ProviderLumen)):
66 c.ProviderType = model.ProviderLumen
67 default:
68 return fmt.Errorf("invalid provider name")
69 }
70 return nil
71 })
72}
73
74type deviceList []machine
75
76func (dl deviceList) asMap() map[shepherd.ProviderID]machine {
77 mm := make(map[shepherd.ProviderID]machine)
78 for _, m := range dl {
79 mm[m.ProviderID] = m
80 }
81 return mm
82}
83
84func fetchDeviceList(s string) (deviceList, error) {
85 var r io.Reader
86 u, err := url.Parse(s)
87 if err != nil {
88 return nil, fmt.Errorf("failed parsing device list url: %v", err)
89 }
90
91 if u.Scheme != "file" {
92 resp, err := http.Get(u.String())
93 if err != nil {
94 return nil, err
95 }
96 defer resp.Body.Close()
97
98 if resp.StatusCode != http.StatusOK {
99 return nil, fmt.Errorf("invalid status code: %d != %v", http.StatusOK, resp.StatusCode)
100 }
101 r = resp.Body
102 } else {
103 f, err := os.Open(u.Path)
104 if err != nil {
105 return nil, err
106 }
107 defer f.Close()
108 r = f
109 }
110
111 var d deviceList
112 dec := json.NewDecoder(r)
113 dec.DisallowUnknownFields()
114 if err := dec.Decode(&d); err != nil {
115 return nil, err
116 }
117
118 klog.Infof("Fetched device list with %d entries", len(d))
119
120 return d, nil
121}
122
123func main() {
124 var c Config
125 c.RegisterFlags()
126
127 flag.Parse()
128 if flag.NArg() > 0 {
129 klog.Exitf("unexpected positional arguments: %v", flag.Args())
130 }
131
132 registry := c.Component.PrometheusRegistry()
133 c.BMDB.EnableMetrics(registry)
134
135 ctx := clicontext.WithInterrupt(context.Background())
136 c.Component.StartPrometheus(ctx)
137
138 conn, err := c.BMDB.Open(true)
139 if err != nil {
140 klog.Exitf("Failed to open BMDB connection: %v", err)
141 }
142
143 sshClient, err := c.SSHConfig.NewClient()
144 if err != nil {
145 klog.Exitf("Failed to create SSH client: %v", err)
146 }
147
148 if c.DeviceListSource == "" {
149 klog.Exitf("-mini_device_list_source must be set")
150 }
151
152 list, err := fetchDeviceList(c.DeviceListSource)
153 if err != nil {
154 klog.Exitf("Failed to fetch device list: %v", err)
155 }
156
157 mini := &provider{
158 providerType: c.ProviderType,
159 machines: list.asMap(),
160 }
161
162 provisioner, err := manager.NewProvisioner(mini, c.ProvisionerConfig)
163 if err != nil {
164 klog.Exitf("%v", err)
165 }
166
167 initializer, err := manager.NewInitializer(mini, sshClient, c.InitializerConfig)
168 if err != nil {
169 klog.Exitf("%v", err)
170 }
171
172 go func() {
173 err = provisioner.Run(ctx, conn)
174 if err != nil {
175 klog.Exit(err)
176 }
177 }()
178 go func() {
179 err = manager.RunControlLoop(ctx, conn, initializer)
180 if err != nil {
181 klog.Exit(err)
182 }
183 }()
184 go func() {
185 if err := c.WebugConfig.Start(ctx, conn); err != nil && err != ctx.Err() {
186 klog.Exitf("Failed to start webug: %v", err)
187 }
188 }()
189
190 <-ctx.Done()
191}