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  }