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