github.com/huiliang/nomad@v0.2.1-0.20151124023127-7a8b664699ff/client/fingerprint/env_aws.go (about)

     1  package fingerprint
     2  
     3  import (
     4  	"io/ioutil"
     5  	"log"
     6  	"net/http"
     7  	"net/url"
     8  	"os"
     9  	"regexp"
    10  	"strings"
    11  	"time"
    12  
    13  	"github.com/hashicorp/go-cleanhttp"
    14  	"github.com/hashicorp/nomad/client/config"
    15  	"github.com/hashicorp/nomad/nomad/structs"
    16  )
    17  
    18  // This is where the AWS metadata server normally resides. We hardcode the
    19  // "instance" path as well since it's the only one we access here.
    20  const DEFAULT_AWS_URL = "http://169.254.169.254/latest/meta-data/"
    21  
    22  // map of instance type to approximate speed, in Mbits/s
    23  // http://serverfault.com/questions/324883/aws-bandwidth-and-content-delivery/326797#326797
    24  // which itself cites these sources:
    25  // - http://blog.rightscale.com/2007/10/28/network-performance-within-amazon-ec2-and-to-amazon-s3/
    26  // - http://www.soc.napier.ac.uk/~bill/chris_p.pdf
    27  //
    28  // This data is meant for a loose approximation
    29  var ec2InstanceSpeedMap = map[string]int{
    30  	"m4.large":    80,
    31  	"m3.medium":   80,
    32  	"m3.large":    80,
    33  	"c4.large":    80,
    34  	"c3.large":    80,
    35  	"c3.xlarge":   80,
    36  	"r3.large":    80,
    37  	"r3.xlarge":   80,
    38  	"i2.xlarge":   80,
    39  	"d2.xlarge":   80,
    40  	"t2.micro":    16,
    41  	"t2.small":    16,
    42  	"t2.medium":   16,
    43  	"t2.large":    16,
    44  	"m4.xlarge":   760,
    45  	"m4.2xlarge":  760,
    46  	"m4.4xlarge":  760,
    47  	"m3.xlarge":   760,
    48  	"m3.2xlarge":  760,
    49  	"c4.xlarge":   760,
    50  	"c4.2xlarge":  760,
    51  	"c4.4xlarge":  760,
    52  	"c3.2xlarge":  760,
    53  	"c3.4xlarge":  760,
    54  	"g2.2xlarge":  760,
    55  	"r3.2xlarge":  760,
    56  	"r3.4xlarge":  760,
    57  	"i2.2xlarge":  760,
    58  	"i2.4xlarge":  760,
    59  	"d2.2xlarge":  760,
    60  	"d2.4xlarge":  760,
    61  	"m4.10xlarge": 10000,
    62  	"c4.8xlarge":  10000,
    63  	"c3.8xlarge":  10000,
    64  	"g2.8xlarge":  10000,
    65  	"r3.8xlarge":  10000,
    66  	"i2.8xlarge":  10000,
    67  	"d2.8xlarge":  10000,
    68  }
    69  
    70  // EnvAWSFingerprint is used to fingerprint AWS metadata
    71  type EnvAWSFingerprint struct {
    72  	StaticFingerprinter
    73  	logger *log.Logger
    74  }
    75  
    76  // NewEnvAWSFingerprint is used to create a fingerprint from AWS metadata
    77  func NewEnvAWSFingerprint(logger *log.Logger) Fingerprint {
    78  	f := &EnvAWSFingerprint{logger: logger}
    79  	return f
    80  }
    81  
    82  func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) {
    83  	if !isAWS() {
    84  		return false, nil
    85  	}
    86  
    87  	// newNetwork is populated and addded to the Nodes resources
    88  	newNetwork := &structs.NetworkResource{
    89  		Device: "eth0",
    90  	}
    91  
    92  	if node.Links == nil {
    93  		node.Links = make(map[string]string)
    94  	}
    95  	metadataURL := os.Getenv("AWS_ENV_URL")
    96  	if metadataURL == "" {
    97  		metadataURL = DEFAULT_AWS_URL
    98  	}
    99  
   100  	// assume 2 seconds is enough time for inside AWS network
   101  	client := &http.Client{
   102  		Timeout:   2 * time.Second,
   103  		Transport: cleanhttp.DefaultTransport(),
   104  	}
   105  
   106  	keys := []string{
   107  		"ami-id",
   108  		"hostname",
   109  		"instance-id",
   110  		"instance-type",
   111  		"local-hostname",
   112  		"local-ipv4",
   113  		"public-hostname",
   114  		"public-ipv4",
   115  		"placement/availability-zone",
   116  	}
   117  	for _, k := range keys {
   118  		res, err := client.Get(metadataURL + k)
   119  		if err != nil {
   120  			// if it's a URL error, assume we're not in an AWS environment
   121  			// TODO: better way to detect AWS? Check xen virtualization?
   122  			if _, ok := err.(*url.Error); ok {
   123  				return false, nil
   124  			}
   125  			// not sure what other errors it would return
   126  			return false, err
   127  		}
   128  		resp, err := ioutil.ReadAll(res.Body)
   129  		res.Body.Close()
   130  		if err != nil {
   131  			f.logger.Printf("[ERR]: fingerprint.env_aws: Error reading response body for AWS %s", k)
   132  		}
   133  
   134  		// assume we want blank entries
   135  		key := strings.Replace(k, "/", ".", -1)
   136  		node.Attributes["platform.aws."+key] = strings.Trim(string(resp), "\n")
   137  	}
   138  
   139  	// copy over network specific information
   140  	if node.Attributes["platform.aws.local-ipv4"] != "" {
   141  		node.Attributes["network.ip-address"] = node.Attributes["platform.aws.local-ipv4"]
   142  		newNetwork.IP = node.Attributes["platform.aws.local-ipv4"]
   143  		newNetwork.CIDR = newNetwork.IP + "/32"
   144  	}
   145  
   146  	// find LinkSpeed from lookup
   147  	if throughput := f.linkSpeed(); throughput > 0 {
   148  		newNetwork.MBits = throughput
   149  	}
   150  
   151  	if node.Resources == nil {
   152  		node.Resources = &structs.Resources{}
   153  	}
   154  	node.Resources.Networks = append(node.Resources.Networks, newNetwork)
   155  
   156  	// populate Node Network Resources
   157  
   158  	// populate Links
   159  	node.Links["aws.ec2"] = node.Attributes["platform.aws.placement.availability-zone"] + "." + node.Attributes["platform.aws.instance-id"]
   160  
   161  	return true, nil
   162  }
   163  
   164  func isAWS() bool {
   165  	// Read the internal metadata URL from the environment, allowing test files to
   166  	// provide their own
   167  	metadataURL := os.Getenv("AWS_ENV_URL")
   168  	if metadataURL == "" {
   169  		metadataURL = DEFAULT_AWS_URL
   170  	}
   171  
   172  	// assume 2 seconds is enough time for inside AWS network
   173  	client := &http.Client{
   174  		Timeout:   2 * time.Second,
   175  		Transport: cleanhttp.DefaultTransport(),
   176  	}
   177  
   178  	// Query the metadata url for the ami-id, to veryify we're on AWS
   179  	resp, err := client.Get(metadataURL + "ami-id")
   180  
   181  	if err != nil {
   182  		log.Printf("[ERR] fingerprint.env_aws: Error querying AWS Metadata URL, skipping")
   183  		return false
   184  	}
   185  	defer resp.Body.Close()
   186  
   187  	if resp.StatusCode >= 400 {
   188  		// URL not found, which indicates that this isn't AWS
   189  		return false
   190  	}
   191  
   192  	instanceID, err := ioutil.ReadAll(resp.Body)
   193  	if err != nil {
   194  		log.Printf("[ERR] fingerprint.env_aws: Error reading AWS Instance ID, skipping")
   195  		return false
   196  	}
   197  
   198  	match, err := regexp.MatchString("ami-*", string(instanceID))
   199  	if !match {
   200  		return false
   201  	}
   202  
   203  	return true
   204  }
   205  
   206  // EnvAWSFingerprint uses lookup table to approximate network speeds
   207  func (f *EnvAWSFingerprint) linkSpeed() int {
   208  
   209  	// Query the API for the instance type, and use the table above to approximate
   210  	// the network speed
   211  	metadataURL := os.Getenv("AWS_ENV_URL")
   212  	if metadataURL == "" {
   213  		metadataURL = DEFAULT_AWS_URL
   214  	}
   215  
   216  	// assume 2 seconds is enough time for inside AWS network
   217  	client := &http.Client{
   218  		Timeout:   2 * time.Second,
   219  		Transport: cleanhttp.DefaultTransport(),
   220  	}
   221  
   222  	res, err := client.Get(metadataURL + "instance-type")
   223  	body, err := ioutil.ReadAll(res.Body)
   224  	res.Body.Close()
   225  	if err != nil {
   226  		f.logger.Printf("[ERR]: fingerprint.env_aws: Error reading response body for instance-type")
   227  		return 0
   228  	}
   229  
   230  	key := strings.Trim(string(body), "\n")
   231  	v, ok := ec2InstanceSpeedMap[key]
   232  	if !ok {
   233  		return 0
   234  	}
   235  
   236  	return v
   237  }