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