github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/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 "github.com/hashicorp/nomad/client/config" 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(cfg *config.Config, node *structs.Node) (bool, error) { 135 // Check if we should tighten the timeout 136 if cfg.ReadBoolDefault(TightenNetworkTimeoutsConfig, false) { 137 f.client.Timeout = 1 * time.Millisecond 138 } 139 140 if !f.isGCE() { 141 return false, nil 142 } 143 144 if node.Links == nil { 145 node.Links = make(map[string]string) 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 false, 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 node.Attributes[key] = strings.Trim(string(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 false, checkError(err, f.logger, k) 182 } 183 184 key := "platform.gce." + k 185 if unique { 186 key = structs.UniqueNamespace(key) 187 } 188 node.Attributes[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 var interfaces []GCEMetadataNetworkInterface 194 if err := json.Unmarshal([]byte(value), &interfaces); err != nil { 195 f.logger.Printf("[WARN] fingerprint.env_gce: Error decoding network interface information: %s", err.Error()) 196 } 197 198 for _, intf := range interfaces { 199 prefix := "platform.gce.network." + lastToken(intf.Network) 200 uniquePrefix := "unique." + prefix 201 node.Attributes[prefix] = "true" 202 node.Attributes[uniquePrefix+".ip"] = strings.Trim(intf.Ip, "\n") 203 for index, accessConfig := range intf.AccessConfigs { 204 node.Attributes[uniquePrefix+".external-ip."+strconv.Itoa(index)] = accessConfig.ExternalIp 205 } 206 } 207 208 var tagList []string 209 value, err = f.Get("tags", false) 210 if err != nil { 211 return false, checkError(err, f.logger, "tags") 212 } 213 if err := json.Unmarshal([]byte(value), &tagList); err != nil { 214 f.logger.Printf("[WARN] fingerprint.env_gce: Error decoding instance tags: %s", err.Error()) 215 } 216 for _, tag := range tagList { 217 attr := "platform.gce.tag." 218 var key string 219 220 // If the tag is namespaced as unique, we strip it from the tag and 221 // prepend to the whole attribute. 222 if structs.IsUniqueNamespace(tag) { 223 tag = strings.TrimPrefix(tag, structs.NodeUniqueNamespace) 224 key = fmt.Sprintf("%s%s%s", structs.NodeUniqueNamespace, attr, tag) 225 } else { 226 key = fmt.Sprintf("%s%s", attr, tag) 227 } 228 229 node.Attributes[key] = "true" 230 } 231 232 var attrDict map[string]string 233 value, err = f.Get("attributes/", true) 234 if err != nil { 235 return false, checkError(err, f.logger, "attributes/") 236 } 237 if err := json.Unmarshal([]byte(value), &attrDict); err != nil { 238 f.logger.Printf("[WARN] fingerprint.env_gce: Error decoding instance attributes: %s", err.Error()) 239 } 240 for k, v := range attrDict { 241 attr := "platform.gce.attr." 242 var key string 243 244 // If the key is namespaced as unique, we strip it from the 245 // key and prepend to the whole attribute. 246 if structs.IsUniqueNamespace(k) { 247 k = strings.TrimPrefix(k, structs.NodeUniqueNamespace) 248 key = fmt.Sprintf("%s%s%s", structs.NodeUniqueNamespace, attr, k) 249 } else { 250 key = fmt.Sprintf("%s%s", attr, k) 251 } 252 253 node.Attributes[key] = strings.Trim(v, "\n") 254 } 255 256 // populate Links 257 node.Links["gce"] = node.Attributes["unique.platform.gce.id"] 258 259 return true, nil 260 } 261 262 func (f *EnvGCEFingerprint) isGCE() bool { 263 // TODO: better way to detect GCE? 264 265 // Query the metadata url for the machine type, to verify we're on GCE 266 machineType, err := f.Get("machine-type", false) 267 if err != nil { 268 if re, ok := err.(ReqError); !ok || re.StatusCode != 404 { 269 // If it wasn't a 404 error, print an error message. 270 f.logger.Printf("[DEBUG] fingerprint.env_gce: Error querying GCE Metadata URL, skipping") 271 } 272 return false 273 } 274 275 match, err := regexp.MatchString("projects/.+/machineTypes/.+", machineType) 276 if err != nil || !match { 277 return false 278 } 279 280 return true 281 }