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