blob: bc3c5f0fece7b956291363899455666cfbf9b96e [file] [log] [blame]
Lorenz Brun85ad26a2023-03-27 17:00:00 +02001package main
2
3import (
4 "context"
5 "encoding/base64"
6 "errors"
7 "fmt"
8 "io"
9 "net/http"
10 "os"
11 "strings"
12
13 "github.com/cenkalti/backoff/v4"
14 "google.golang.org/protobuf/proto"
15
Tim Windelschmidt3b5a9172024-05-23 13:33:52 +020016 apb "source.monogon.dev/metropolis/proto/api"
17
Lorenz Brun85ad26a2023-03-27 17:00:00 +020018 "source.monogon.dev/metropolis/node/core/localstorage"
Tim Windelschmidt9f21f532024-05-07 15:14:20 +020019 "source.monogon.dev/osbase/supervisor"
Lorenz Brun85ad26a2023-03-27 17:00:00 +020020)
21
22func nodeParamsFWCFG(ctx context.Context) (*apb.NodeParameters, error) {
23 bytes, err := os.ReadFile("/sys/firmware/qemu_fw_cfg/by_name/dev.monogon.metropolis/parameters.pb/raw")
24 if err != nil {
25 return nil, fmt.Errorf("could not read firmware enrolment file: %w", err)
26 }
27
Tim Windelschmidt3b5a9172024-05-23 13:33:52 +020028 var config apb.NodeParameters
Lorenz Brun85ad26a2023-03-27 17:00:00 +020029 err = proto.Unmarshal(bytes, &config)
30 if err != nil {
31 return nil, fmt.Errorf("could not unmarshal: %v", err)
32 }
33
34 return &config, nil
35}
36
37// nodeParamsGCPMetadata attempts to retrieve the node parameters from the
38// GCP metadata service. Returns nil if the metadata service is available,
39// but no node parameters are specified.
40func nodeParamsGCPMetadata(ctx context.Context) (*apb.NodeParameters, error) {
41 const metadataURL = "http://169.254.169.254/computeMetadata/v1/instance/attributes/metropolis-node-params"
42 req, err := http.NewRequestWithContext(ctx, "GET", metadataURL, nil)
43 if err != nil {
44 return nil, fmt.Errorf("could not create request: %w", err)
45 }
46 req.Header.Set("Metadata-Flavor", "Google")
47 resp, err := http.DefaultClient.Do(req)
48 if err != nil {
49 return nil, fmt.Errorf("HTTP request failed: %w", err)
50 }
51 defer resp.Body.Close()
52 if resp.StatusCode != http.StatusOK {
53 if resp.StatusCode == http.StatusNotFound {
54 return nil, nil
55 }
56 return nil, fmt.Errorf("non-200 status code: %d", resp.StatusCode)
57 }
58 decoded, err := io.ReadAll(base64.NewDecoder(base64.StdEncoding, resp.Body))
59 if err != nil {
60 return nil, fmt.Errorf("cannot decode base64: %w", err)
61 }
Tim Windelschmidt3b5a9172024-05-23 13:33:52 +020062 var config apb.NodeParameters
Lorenz Brun85ad26a2023-03-27 17:00:00 +020063 err = proto.Unmarshal(decoded, &config)
64 if err != nil {
65 return nil, fmt.Errorf("failed unmarshalling NodeParameters: %w", err)
66 }
67 return &config, nil
68}
69
70func getDMIBoardName() (string, error) {
71 b, err := os.ReadFile("/sys/devices/virtual/dmi/id/board_name")
72 if err != nil {
73 return "", fmt.Errorf("could not read board name: %w", err)
74 }
75 return strings.TrimRight(string(b), "\n"), nil
76}
77
78func isGCPInstance(boardName string) bool {
79 return boardName == "Google Compute Engine"
80}
81
82func getNodeParams(ctx context.Context, storage *localstorage.Root) (*apb.NodeParameters, error) {
83 boardName, err := getDMIBoardName()
84 if err != nil {
85 if errors.Is(err, os.ErrNotExist) {
86 supervisor.Logger(ctx).Infof("Board name: UNKNOWN")
87 } else {
88 supervisor.Logger(ctx).Warningf("Could not get board name, cannot detect platform: %v", err)
89 }
90 } else {
91 supervisor.Logger(ctx).Infof("Board name: %q", boardName)
92 }
93
94 // When running on GCP, attempt to retrieve the node parameters from the
95 // metadata server first. Retry until we get a response, since we need to
96 // wait for the network service to assign an IP address first.
97 if isGCPInstance(boardName) {
98 var params *apb.NodeParameters
99 op := func() error {
100 supervisor.Logger(ctx).Info("Running on GCP, attempting to retrieve node parameters from metadata server")
101 params, err = nodeParamsGCPMetadata(ctx)
102 return err
103 }
104 err := backoff.Retry(op, backoff.WithContext(backoff.NewExponentialBackOff(), ctx))
105 if err != nil {
106 supervisor.Logger(ctx).Errorf("Failed to retrieve node parameters: %v", err)
107 }
108 if params != nil {
109 supervisor.Logger(ctx).Info("Retrieved parameters from GCP metadata server")
110 return params, nil
111 }
112 supervisor.Logger(ctx).Infof("\"metropolis-node-params\" metadata not found")
113 }
114
115 // Retrieve node parameters from qemu's fwcfg interface or ESP.
116 // TODO(q3k): probably abstract this away and implement per platform/build/...
117 paramsFWCFG, err := nodeParamsFWCFG(ctx)
118 if err != nil {
119 if errors.Is(err, os.ErrNotExist) {
120 supervisor.Logger(ctx).Infof("No qemu fwcfg params.")
121 } else {
122 supervisor.Logger(ctx).Warningf("Could not retrieve node parameters from qemu fwcfg: %v", err)
123 }
124 paramsFWCFG = nil
125 } else {
126 supervisor.Logger(ctx).Infof("Retrieved node parameters from qemu fwcfg")
127 }
128 paramsESP, err := storage.ESP.Metropolis.NodeParameters.Unmarshal()
129 if err != nil {
130 if errors.Is(err, os.ErrNotExist) {
131 supervisor.Logger(ctx).Infof("No ESP node parameters.")
132 } else {
133 supervisor.Logger(ctx).Warningf("Could not retrieve node parameters from ESP: %v", err)
134 }
135 paramsESP = nil
136 } else {
137 supervisor.Logger(ctx).Infof("Retrieved node parameters from ESP")
138 }
139 if paramsFWCFG == nil && paramsESP == nil {
140 return nil, fmt.Errorf("could not find node parameters in ESP or qemu fwcfg")
141 }
142 if paramsFWCFG != nil && paramsESP != nil {
143 supervisor.Logger(ctx).Warningf("Node parameters found both in both ESP and qemu fwcfg, using the latter")
144 return paramsFWCFG, nil
145 } else if paramsFWCFG != nil {
146 return paramsFWCFG, nil
147 } else {
148 return paramsESP, nil
149 }
150}