-
Notifications
You must be signed in to change notification settings - Fork 4.6k
/
metadata.go
205 lines (169 loc) · 6.42 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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
/*
Copyright 2023 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package openstack
import (
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"path"
"strings"
"k8s.io/mount-utils"
utilexec "k8s.io/utils/exec"
)
const (
// MetadataLatestPath is the path to the metadata on the config drive
MetadataLatestPath string = "openstack/latest/meta_data.json"
// MetadataID is the identifier for the metadata service
MetadataID string = "metadataService"
// MetadataLastestServiceURL points to the latest metadata of the metadata service
MetadataLatestServiceURL string = "http://169.254.169.254/" + MetadataLatestPath
// ConfigDriveID is the identifier for the config drive containing metadata
ConfigDriveID string = "configDrive"
// ConfigDriveLabel identifies the config drive by label on the OS
ConfigDriveLabel string = "config-2"
// DefaultMetadataSearchOrder defines the default order in which the metadata services are queried
DefaultMetadataSearchOrder string = ConfigDriveID + ", " + MetadataID
DiskByLabelPath string = "/dev/disk/by-label/"
)
type Metadata struct {
// Matches openstack.TagClusterName
ClusterName string `json:"KubernetesCluster"`
}
type InstanceMetadata struct {
Name string `json:"name"`
UserMeta *Metadata `json:"meta"`
ProjectID string `json:"project_id"`
AvailabilityZone string `json:"availability_zone"`
Hostname string `json:"hostname"`
ServerID string `json:"uuid"`
}
type MetadataService struct {
serviceURL string
configDrivePath string
mounter *mount.SafeFormatAndMount
mountTarget string
searchOrder string
}
func newMetadataService(serviceURL string, configDrivePath string, mounter *mount.SafeFormatAndMount, mountTarget string, searchOrder string) *MetadataService {
return &MetadataService{
serviceURL: serviceURL,
configDrivePath: configDrivePath,
mounter: mounter,
mountTarget: mountTarget,
searchOrder: searchOrder,
}
}
// GetLocalMetadata returns a local metadata for the server
func GetLocalMetadata() (*InstanceMetadata, error) {
mountTarget, err := os.MkdirTemp("", "configdrive")
if err != nil {
return nil, err
}
defer os.Remove(mountTarget)
return newMetadataService(MetadataLatestServiceURL, MetadataLatestPath, getDefaultMounter(), mountTarget, DefaultMetadataSearchOrder).getMetadata()
}
// getFromConfigDrive tries to get metadata by mounting a config drive and returns it as InstanceMetadata
// It will return an error if there is no disk labelled as ConfigDriveLabel or other errors while mounting the disk, or reading the file occur.
func (mds MetadataService) getFromConfigDrive() (*InstanceMetadata, error) {
dev := path.Join(DiskByLabelPath, ConfigDriveLabel)
if _, err := os.Stat(dev); os.IsNotExist(err) {
out, err := mds.mounter.Exec.Command(
"blkid", "-l",
"-t", fmt.Sprintf("LABEL=%s", ConfigDriveLabel),
"-o", "device",
).CombinedOutput()
if err != nil {
return nil, fmt.Errorf("unable to run blkid: %v", err)
}
dev = strings.TrimSpace(string(out))
}
err := mds.mounter.Mount(dev, mds.mountTarget, "iso9660", []string{"ro"})
if err != nil {
err = mds.mounter.Mount(dev, mds.mountTarget, "vfat", []string{"ro"})
}
if err != nil {
return nil, fmt.Errorf("error mounting configdrive '%s': %v", dev, err)
}
defer mds.mounter.Unmount(mds.mountTarget)
f, err := os.Open(
path.Join(mds.mountTarget, mds.configDrivePath))
if err != nil {
return nil, fmt.Errorf("error reading '%s' on config drive: %v", mds.configDrivePath, err)
}
defer f.Close()
return mds.parseMetadata(f)
}
// getFromMetadataService tries to get metadata from a metadata service endpoint and returns it as InstanceMetadata.
// If the service endpoint cannot be contacted or reports a different status than StatusOK it will return an error.
func (mds MetadataService) getFromMetadataService() (*InstanceMetadata, error) {
var client http.Client
resp, err := client.Get(mds.serviceURL)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode == http.StatusOK {
return mds.parseMetadata(resp.Body)
}
err = fmt.Errorf("fetching metadata from '%s' returned status code '%d'", mds.serviceURL, resp.StatusCode)
return nil, err
}
// parseMetadata reads JSON data from a Reader and returns it as InstanceMetadata.
func (mds MetadataService) parseMetadata(r io.Reader) (*InstanceMetadata, error) {
var meta InstanceMetadata
data, err := io.ReadAll(r)
if err != nil {
return nil, err
}
err = json.Unmarshal(data, &meta)
if err != nil {
return nil, err
}
return &meta, nil
}
// getMetadata tries to get metadata for the instance by mounting the config drive and/or querying the metadata service endpoint.
// Depending on the searchOrder it will return data from the first source which successfully returns.
// If all the sources in searchOrder are erroneous it will propagate the last error to its caller.
func (mds MetadataService) getMetadata() (*InstanceMetadata, error) {
// Note(ederst): I used and modified code for getting the config drive metadata to work from here:
// * https://github.com/kubernetes/cloud-provider-openstack/blob/27b6fc483451b6df2112a6a4a40a34ffc9093635/pkg/util/metadata/metadata.go
var meta *InstanceMetadata
var err error
ids := strings.Split(mds.searchOrder, ",")
for _, id := range ids {
id = strings.TrimSpace(id)
switch id {
case ConfigDriveID:
meta, err = mds.getFromConfigDrive()
case MetadataID:
meta, err = mds.getFromMetadataService()
default:
err = fmt.Errorf("%s is not a valid metadata search order option. Supported options are %s and %s", id, ConfigDriveID, MetadataID)
}
if err == nil {
break
}
}
return meta, err
}
// getDefaultMounter returns a mount and executor interface to use for getting metadata from a config drive
func getDefaultMounter() *mount.SafeFormatAndMount {
mounter := mount.New("")
exec := utilexec.New()
return &mount.SafeFormatAndMount{
Interface: mounter,
Exec: exec,
}
}