-
Notifications
You must be signed in to change notification settings - Fork 17
/
metadata.go
146 lines (119 loc) · 3.64 KB
/
metadata.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package nexagent
import (
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
agentapi "github.com/synadia-io/nex/internal/agent-api"
)
// MmdsAddress is the address used by the agent to query firecracker MMDS
// (see https://github.com/firecracker-microvm/firecracker/blob/main/docs/mmds/mmds-user-guide.md#version-2)
const MmdsAddress = "169.254.169.254"
const nexEnvSandbox = "NEX_SANDBOX"
const nexEnvWorkloadID = "NEX_WORKLOADID"
const nexEnvNodeNatsHost = "NEX_NODE_NATS_HOST"
const nexEnvNodeNatsPort = "NEX_NODE_NATS_PORT"
const nexEnvNodeNatsSeed = "NEX_NODE_NATS_NKEY_SEED"
const nexEnvAgentPluginPath = "NEX_AGENT_PLUGIN_PATH"
const metadataClientTimeoutMillis = 50
const metadataPollingTimeoutMillis = 5000
// GetMachineMetadata attempts to retrieve metadata from firecracker's MMDS.
// Version of 2 this service requires the acuisition of a token and the use
// of that token for all requests. Note that metadata is PUT into a running
// machine AFTER it starts. So if we have things that auto start (like this
// agent), then we need to ensure we avoid the race condition of reading
// metadata before it exists.
func GetMachineMetadata() (*agentapi.MachineMetadata, error) {
token, err := acquireToken()
if err != nil {
return nil, err
}
url := fmt.Sprintf("http://%s/", MmdsAddress)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
return nil, err
}
req.Header.Set("Accept", "application/json")
req.Header.Set("X-metadata-token", token)
client := &http.Client{
Timeout: metadataClientTimeoutMillis * time.Millisecond,
}
timeoutAt := time.Now().UTC().Add(metadataPollingTimeoutMillis * time.Millisecond)
for {
metadata, err := performMetadataQuery(req, client)
if err != nil {
if time.Now().UTC().After(timeoutAt) {
break
}
continue
}
return metadata, nil
}
return nil, fmt.Errorf("failed to obtain metadata after %dms", metadataPollingTimeoutMillis)
}
func GetMachineMetadataFromEnv() (*agentapi.MachineMetadata, error) {
vmid := os.Getenv(nexEnvWorkloadID)
host := os.Getenv(nexEnvNodeNatsHost)
port := os.Getenv(nexEnvNodeNatsPort)
seed := os.Getenv(nexEnvNodeNatsSeed)
pluginPath := os.Getenv(nexEnvAgentPluginPath)
msg := "Metadata obtained from no-sandbox environment"
p, err := strconv.Atoi(port)
if err != nil {
fmt.Println("Bad port number for internat NATS server")
return nil, err
}
return &agentapi.MachineMetadata{
VmID: &vmid,
NodeNatsHost: &host,
NodeNatsPort: &p,
NodeNatsNkeySeed: &seed,
Message: &msg,
PluginPath: &pluginPath,
}, nil
}
func performMetadataQuery(req *http.Request, client *http.Client) (*agentapi.MachineMetadata, error) {
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, errors.New("metadata not found")
}
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return nil, err
}
var metadata agentapi.MachineMetadata
err = json.Unmarshal(bodyBytes, &metadata)
if err != nil {
return nil, fmt.Errorf("deserialization failure: %s: body: '%s'", err, string(bodyBytes))
}
return &metadata, nil
}
func acquireToken() (string, error) {
url := fmt.Sprintf("http://%s/latest/api/token", MmdsAddress)
req, err := http.NewRequest(http.MethodPut, url, nil)
if err != nil {
return "", err
}
req.Header.Set("X-metadata-token-ttl-seconds", "60")
client := &http.Client{
Timeout: 1 * time.Second,
}
resp, err := client.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
bodyBytes, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
return string(bodyBytes), nil
}