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