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