github.com/quite/nomad@v0.8.6/client/fingerprint/env_gce.go (about) 1 package fingerprint 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "io/ioutil" 7 "log" 8 "net/http" 9 "net/url" 10 "os" 11 "regexp" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/hashicorp/go-cleanhttp" 17 cstructs "github.com/hashicorp/nomad/client/structs" 18 "github.com/hashicorp/nomad/helper/useragent" 19 "github.com/hashicorp/nomad/nomad/structs" 20 ) 21 22 const ( 23 // This is where the GCE metadata server normally resides. We hardcode the 24 // "instance" path as well since it's the only one we access here. 25 DEFAULT_GCE_URL = "http://169.254.169.254/computeMetadata/v1/instance/" 26 27 // GceMetadataTimeout is the timeout used when contacting the GCE metadata 28 // service 29 GceMetadataTimeout = 2 * time.Second 30 ) 31 32 type GCEMetadataNetworkInterface struct { 33 AccessConfigs []struct { 34 ExternalIp string 35 Type string 36 } 37 ForwardedIps []string 38 Ip string 39 Network string 40 } 41 42 type ReqError struct { 43 StatusCode int 44 } 45 46 func (e ReqError) Error() string { 47 return http.StatusText(e.StatusCode) 48 } 49 50 func lastToken(s string) string { 51 index := strings.LastIndex(s, "/") 52 return s[index+1:] 53 } 54 55 // EnvGCEFingerprint is used to fingerprint GCE metadata 56 type EnvGCEFingerprint struct { 57 StaticFingerprinter 58 client *http.Client 59 logger *log.Logger 60 metadataURL string 61 } 62 63 // NewEnvGCEFingerprint is used to create a fingerprint from GCE metadata 64 func NewEnvGCEFingerprint(logger *log.Logger) Fingerprint { 65 // Read the internal metadata URL from the environment, allowing test files to 66 // provide their own 67 metadataURL := os.Getenv("GCE_ENV_URL") 68 if metadataURL == "" { 69 metadataURL = DEFAULT_GCE_URL 70 } 71 72 // assume 2 seconds is enough time for inside GCE network 73 client := &http.Client{ 74 Timeout: GceMetadataTimeout, 75 Transport: cleanhttp.DefaultTransport(), 76 } 77 78 return &EnvGCEFingerprint{ 79 client: client, 80 logger: logger, 81 metadataURL: metadataURL, 82 } 83 } 84 85 func (f *EnvGCEFingerprint) Get(attribute string, recursive bool) (string, error) { 86 reqUrl := f.metadataURL + attribute 87 if recursive { 88 reqUrl = reqUrl + "?recursive=true" 89 } 90 91 parsedUrl, err := url.Parse(reqUrl) 92 if err != nil { 93 return "", err 94 } 95 96 req := &http.Request{ 97 Method: "GET", 98 URL: parsedUrl, 99 Header: http.Header{ 100 "Metadata-Flavor": []string{"Google"}, 101 "User-Agent": []string{useragent.String()}, 102 }, 103 } 104 105 res, err := f.client.Do(req) 106 if err != nil || res.StatusCode != http.StatusOK { 107 f.logger.Printf("[DEBUG] fingerprint.env_gce: Could not read value for attribute %q", attribute) 108 return "", err 109 } 110 111 resp, err := ioutil.ReadAll(res.Body) 112 res.Body.Close() 113 if err != nil { 114 f.logger.Printf("[ERR] fingerprint.env_gce: Error reading response body for GCE %s", attribute) 115 return "", err 116 } 117 118 if res.StatusCode >= 400 { 119 return "", ReqError{res.StatusCode} 120 } 121 122 return string(resp), nil 123 } 124 125 func checkError(err error, logger *log.Logger, desc string) error { 126 // If it's a URL error, assume we're not actually in an GCE environment. 127 // To the outer layers, this isn't an error so return nil. 128 if _, ok := err.(*url.Error); ok { 129 logger.Printf("[DEBUG] fingerprint.env_gce: Error querying GCE " + desc + ", skipping") 130 return nil 131 } 132 // Otherwise pass the error through. 133 return err 134 } 135 136 func (f *EnvGCEFingerprint) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { 137 cfg := req.Config 138 139 // Check if we should tighten the timeout 140 if cfg.ReadBoolDefault(TightenNetworkTimeoutsConfig, false) { 141 f.client.Timeout = 1 * time.Millisecond 142 } 143 144 if !f.isGCE() { 145 return nil 146 } 147 148 // Keys and whether they should be namespaced as unique. Any key whose value 149 // uniquely identifies a node, such as ip, should be marked as unique. When 150 // marked as unique, the key isn't included in the computed node class. 151 keys := map[string]bool{ 152 "hostname": true, 153 "id": true, 154 "cpu-platform": false, 155 "scheduling/automatic-restart": false, 156 "scheduling/on-host-maintenance": false, 157 } 158 159 for k, unique := range keys { 160 value, err := f.Get(k, false) 161 if err != nil { 162 return checkError(err, f.logger, k) 163 } 164 165 // assume we want blank entries 166 key := "platform.gce." + strings.Replace(k, "/", ".", -1) 167 if unique { 168 key = structs.UniqueNamespace(key) 169 } 170 resp.AddAttribute(key, strings.Trim(value, "\n")) 171 } 172 173 // These keys need everything before the final slash removed to be usable. 174 keys = map[string]bool{ 175 "machine-type": false, 176 "zone": false, 177 } 178 for k, unique := range keys { 179 value, err := f.Get(k, false) 180 if err != nil { 181 return checkError(err, f.logger, k) 182 } 183 184 key := "platform.gce." + k 185 if unique { 186 key = structs.UniqueNamespace(key) 187 } 188 resp.AddAttribute(key, strings.Trim(lastToken(value), "\n")) 189 } 190 191 // Get internal and external IPs (if they exist) 192 value, err := f.Get("network-interfaces/", true) 193 if err != nil { 194 f.logger.Printf("[WARN] fingerprint.env_gce: Error retrieving network interface information: %s", err) 195 } else { 196 197 var interfaces []GCEMetadataNetworkInterface 198 if err := json.Unmarshal([]byte(value), &interfaces); err != nil { 199 f.logger.Printf("[WARN] fingerprint.env_gce: Error decoding network interface information: %s", err.Error()) 200 } 201 202 for _, intf := range interfaces { 203 prefix := "platform.gce.network." + lastToken(intf.Network) 204 uniquePrefix := "unique." + prefix 205 resp.AddAttribute(prefix, "true") 206 resp.AddAttribute(uniquePrefix+".ip", strings.Trim(intf.Ip, "\n")) 207 for index, accessConfig := range intf.AccessConfigs { 208 resp.AddAttribute(uniquePrefix+".external-ip."+strconv.Itoa(index), accessConfig.ExternalIp) 209 } 210 } 211 } 212 213 var tagList []string 214 value, err = f.Get("tags", false) 215 if err != nil { 216 return checkError(err, f.logger, "tags") 217 } 218 if err := json.Unmarshal([]byte(value), &tagList); err != nil { 219 f.logger.Printf("[WARN] fingerprint.env_gce: Error decoding instance tags: %s", err.Error()) 220 } 221 for _, tag := range tagList { 222 attr := "platform.gce.tag." 223 var key string 224 225 // If the tag is namespaced as unique, we strip it from the tag and 226 // prepend to the whole attribute. 227 if structs.IsUniqueNamespace(tag) { 228 tag = strings.TrimPrefix(tag, structs.NodeUniqueNamespace) 229 key = fmt.Sprintf("%s%s%s", structs.NodeUniqueNamespace, attr, tag) 230 } else { 231 key = fmt.Sprintf("%s%s", attr, tag) 232 } 233 234 resp.AddAttribute(key, "true") 235 } 236 237 var attrDict map[string]string 238 value, err = f.Get("attributes/", true) 239 if err != nil { 240 return checkError(err, f.logger, "attributes/") 241 } 242 if err := json.Unmarshal([]byte(value), &attrDict); err != nil { 243 f.logger.Printf("[WARN] fingerprint.env_gce: Error decoding instance attributes: %s", err.Error()) 244 } 245 for k, v := range attrDict { 246 attr := "platform.gce.attr." 247 var key string 248 249 // If the key is namespaced as unique, we strip it from the 250 // key and prepend to the whole attribute. 251 if structs.IsUniqueNamespace(k) { 252 k = strings.TrimPrefix(k, structs.NodeUniqueNamespace) 253 key = fmt.Sprintf("%s%s%s", structs.NodeUniqueNamespace, attr, k) 254 } else { 255 key = fmt.Sprintf("%s%s", attr, k) 256 } 257 258 resp.AddAttribute(key, strings.Trim(v, "\n")) 259 } 260 261 // populate Links 262 if id, ok := resp.Attributes["unique.platform.gce.id"]; ok { 263 resp.AddLink("gce", id) 264 } 265 266 resp.Detected = true 267 268 return nil 269 } 270 271 func (f *EnvGCEFingerprint) isGCE() bool { 272 // TODO: better way to detect GCE? 273 274 // Query the metadata url for the machine type, to verify we're on GCE 275 machineType, err := f.Get("machine-type", false) 276 if err != nil { 277 if re, ok := err.(ReqError); !ok || re.StatusCode != 404 { 278 // If it wasn't a 404 error, print an error message. 279 f.logger.Printf("[DEBUG] fingerprint.env_gce: Error querying GCE Metadata URL, skipping") 280 } 281 return false 282 } 283 284 match, err := regexp.MatchString("projects/.+/machineTypes/.+", machineType) 285 if err != nil || !match { 286 return false 287 } 288 289 return true 290 }