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