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