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  }