From bd446f7bd3e122e251aebeae730aa7224c0e1af7 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Mon, 21 Sep 2015 14:20:51 -0500 Subject: [PATCH 01/21] start java docs --- website/source/docs/drivers/java.html.md | 41 +++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/website/source/docs/drivers/java.html.md b/website/source/docs/drivers/java.html.md index 130fc999b..d430a6563 100644 --- a/website/source/docs/drivers/java.html.md +++ b/website/source/docs/drivers/java.html.md @@ -10,5 +10,44 @@ description: |- Name: `java` -TODO +The `Java` driver is used to execute Java applications packaged into a Java Jar +file. The driver currently requires the Jar file be accessbile via +HTTP from the Nomad client. + +## Task Configuration + +The `java` driver supports the following configuration in the job spec: + +* `jar_source` - **(Required)** The hosted location of the source Jar file. Must be accessible +from the Nomad client, via HTTP + +* `args` - (Optional) The argument list for the `java` command, space seperated. + +## Client Requirements + +The `java` driver requires Java to be installed and in your systems `$PATH`. +The `jar_source` must be accessible by the node running Nomad. This can be an +internal source, private to your cluster, but it must be reachable by the client +over HTTP. + +The resource isolation primitives vary by OS. + +## Client Attributes + +The `java` driver will set the following client attributes: + +* `driver.java` - This will always be set to "1", indicating the + driver is available. + +## Resource Isolation + +The resource isolation provided varies by the operating system of +the client and the configuration. + +On Linux, Nomad will attempt to use cgroups, namespaces, and chroot +to isolate the resources of a process. If the Nomad agent is not +running as root many of these mechanisms cannot be used. + +As a baseline, the task driver will just execute the command +with no additional resource isolation if none are available. From fdbcf08c77d098d9cedd8f7653b74b522ee214d4 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Fri, 18 Sep 2015 16:56:27 -0500 Subject: [PATCH 02/21] Basic network fingerprinting for Unix type, AWS systems --- client/fingerprint/network.go | 71 +++++++++++++ client/fingerprint/network_aws.go | 150 ++++++++++++++++++++++++++++ client/fingerprint/network_test.go | 76 ++++++++++++++ client/fingerprint/network_unix.go | 153 +++++++++++++++++++++++++++++ 4 files changed, 450 insertions(+) create mode 100644 client/fingerprint/network.go create mode 100644 client/fingerprint/network_aws.go create mode 100644 client/fingerprint/network_test.go create mode 100644 client/fingerprint/network_unix.go diff --git a/client/fingerprint/network.go b/client/fingerprint/network.go new file mode 100644 index 000000000..5e3351d45 --- /dev/null +++ b/client/fingerprint/network.go @@ -0,0 +1,71 @@ +package fingerprint + +import ( + "io/ioutil" + "log" + "net/http" + "os" + "regexp" + "time" + + "github.com/hashicorp/nomad/client/config" + "github.com/hashicorp/nomad/nomad/structs" +) + +type NetworkFingerPrinter interface { + // Fingerprint collects information about the nodes network configuration + Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) + + // Interfaces returns a slice of connected interface devices for the node + Interfaces() []string + + // LinkSpeed queries a given interface device and returns speed information, + // in MB/s + LinkSpeed(device string) string +} + +func NetworkDefault(logger *log.Logger) NetworkFingerPrinter { + if isAWS() { + return NewAWSNetworkFingerprinter(logger) + } + return NewNetworkFingerprinter(logger) +} + +// isAWS queries the internal AWS Instance Metadata url, and determines if the +// node is running on AWS or not. +// TODO: Generalize this and use in other AWS related Fingerprinters +func isAWS() bool { + // Read the internal metadata URL from the environment, allowing test files to + // provide their own + metadataURL := os.Getenv("AWS_ENV_URL") + if metadataURL == "" { + metadataURL = "http://169.254.169.254/latest/meta-data/" + } + + // assume 2 seconds is enough time for inside AWS network + client := &http.Client{ + Timeout: 2 * time.Second, + } + + // Query the metadata url for the ami-id, to veryify we're on AWS + resp, err := client.Get(metadataURL + "ami-id") + + if err != nil { + log.Printf("[Err] Error querying AWS Metadata URL, skipping") + return false + } + defer resp.Body.Close() + + instanceID, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("[Err] Error reading AWS Instance ID, skipping") + return false + } + + match, err := regexp.MatchString("ami-*", string(instanceID)) + if !match { + return false + } + + return true +} diff --git a/client/fingerprint/network_aws.go b/client/fingerprint/network_aws.go new file mode 100644 index 000000000..b1aee175b --- /dev/null +++ b/client/fingerprint/network_aws.go @@ -0,0 +1,150 @@ +// +build linux,darwin +package fingerprint + +import ( + "io/ioutil" + "log" + "net/http" + "net/url" + "os" + "strings" + "time" + + "github.com/hashicorp/nomad/client/config" + "github.com/hashicorp/nomad/nomad/structs" +) + +// AWSNetworkFingerprint is used to fingerprint the Network capabilities of a node +type AWSNetworkFingerprint struct { + logger *log.Logger +} + +// AWSNetworkFingerprint is used to create a new AWS Network Fingerprinter +func NewAWSNetworkFingerprinter(logger *log.Logger) NetworkFingerPrinter { + f := &AWSNetworkFingerprint{logger: logger} + return f +} + +func (f *AWSNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { + metadataURL := os.Getenv("AWS_ENV_URL") + if metadataURL == "" { + metadataURL = "http://169.254.169.254/latest/meta-data/" + } + + // assume 2 seconds is enough time for inside AWS network + client := &http.Client{ + Timeout: 2 * time.Second, + } + + keys := make(map[string]string) + keys["ip-address"] = "public-hostname" + keys["internal-ip"] = "local-ipv4" + + for name, key := range keys { + res, err := client.Get(metadataURL + key) + if err != nil { + // if it's a URL error, assume we're not in an AWS environment + if _, ok := err.(*url.Error); ok { + return false, nil + } + // not sure what other errors it would return + return false, err + } + body, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + log.Fatal(err) + return false, err + } + + // assume we want blank entries + node.Attributes["network."+name] = strings.Trim(string(body), "\n") + } + + if throughput := f.LinkSpeed(""); throughput != "" { + node.Attributes["network.throughput"] = throughput + } + + return true, nil +} + +func (f *AWSNetworkFingerprint) Interfaces() []string { + // NO OP for now + return nil +} + +func (f *AWSNetworkFingerprint) LinkSpeed(device string) string { + // This table is an approximation of network speeds based on + // http://serverfault.com/questions/324883/aws-bandwidth-and-content-delivery/326797#326797 + // which itself cites these sources: + // - http://blog.rightscale.com/2007/10/28/network-performance-within-amazon-ec2-and-to-amazon-s3/ + // - http://www.soc.napier.ac.uk/~bill/chris_p.pdf + // + // This data is meant for a loose approximation + net := make(map[string]string) + net["m4.large"] = "10MB/s" + net["m3.medium"] = "10MB/s" + net["m3.large"] = "10MB/s" + net["c4.large"] = "10MB/s" + net["c3.large"] = "10MB/s" + net["c3.xlarge"] = "10MB/s" + net["r3.large"] = "10MB/s" + net["r3.xlarge"] = "10MB/s" + net["i2.xlarge"] = "10MB/s" + net["d2.xlarge"] = "10MB/s" + net["t2.micro"] = "2MB/s" + net["t2.small"] = "2MB/s" + net["t2.medium"] = "2MB/s" + net["t2.large"] = "2MB/s" + net["m4.xlarge"] = "95MB/s" + net["m4.2xlarge"] = "95MB/s" + net["m4.4xlarge"] = "95MB/s" + net["m3.xlarge"] = "95MB/s" + net["m3.2xlarge"] = "95MB/s" + net["c4.xlarge"] = "95MB/s" + net["c4.2xlarge"] = "95MB/s" + net["c4.4xlarge"] = "95MB/s" + net["c3.2xlarge"] = "95MB/s" + net["c3.4xlarge"] = "95MB/s" + net["g2.2xlarge"] = "95MB/s" + net["r3.2xlarge"] = "95MB/s" + net["r3.4xlarge"] = "95MB/s" + net["i2.2xlarge"] = "95MB/s" + net["i2.4xlarge"] = "95MB/s" + net["d2.2xlarge"] = "95MB/s" + net["d2.4xlarge"] = "95MB/s" + net["m4.10xlarge"] = "10Gbp/s" + net["c4.8xlarge"] = "10Gbp/s" + net["c3.8xlarge"] = "10Gbp/s" + net["g2.8xlarge"] = "10Gbp/s" + net["r3.8xlarge"] = "10Gbp/s" + net["i2.8xlarge"] = "10Gbp/s" + net["d2.8xlarge"] = "10Gbp/s" + + // Query the API for the instance type, and use the table above to approximate + // the network speed + metadataURL := os.Getenv("AWS_ENV_URL") + if metadataURL == "" { + metadataURL = "http://169.254.169.254/latest/meta-data/" + } + + // assume 2 seconds is enough time for inside AWS network + client := &http.Client{ + Timeout: 2 * time.Second, + } + + res, err := client.Get(metadataURL + "instance-type") + body, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + log.Fatal(err) + return "" + } + + key := strings.Trim(string(body), "\n") + if v, ok := net[key]; ok { + return v + } + + return "" +} diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go new file mode 100644 index 000000000..85981998f --- /dev/null +++ b/client/fingerprint/network_test.go @@ -0,0 +1,76 @@ +package fingerprint + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "os" + "runtime" + "testing" + + "github.com/hashicorp/nomad/client/config" + "github.com/hashicorp/nomad/nomad/structs" +) + +func TestNetworkFingerprint_basic(t *testing.T) { + f := NetworkDefault(testLogger()) + node := &structs.Node{ + Attributes: make(map[string]string), + } + + ok, err := f.Fingerprint(&config.Config{}, node) + if err != nil { + t.Fatalf("err: %v", err) + } + if !ok { + t.Fatalf("should apply") + } + if _, ok := f.(*UnixNetworkFingerprint); !ok { + t.Fatalf("Expected a Unix type Network Fingerprinter") + } + + // Darwin uses en0 for the default device, and does not have a standard + // location for the linkspeed file, so we skip these + if "darwin" != runtime.GOOS { + assertNodeAttributeContains(t, node, "network.throughput") + assertNodeAttributeContains(t, node, "network.ip-address") + } +} + +func TestNetworkFingerprint_AWS(t *testing.T) { + // configure mock server with fixture routes, data + // TODO: Refator with the AWS ENV test + routes := routes{} + if err := json.Unmarshal([]byte(aws_routes), &routes); err != nil { + t.Fatalf("Failed to unmarshal JSON in AWS ENV test: %s", err) + } + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for _, e := range routes.Endpoints { + if r.RequestURI == e.Uri { + w.Header().Set("Content-Type", e.ContentType) + fmt.Fprintln(w, e.Body) + } + } + })) + + defer ts.Close() + os.Setenv("AWS_ENV_URL", ts.URL+"/latest/meta-data/") + + f := NetworkDefault(testLogger()) + node := &structs.Node{ + Attributes: make(map[string]string), + } + + ok, err := f.Fingerprint(&config.Config{}, node) + if err != nil { + t.Fatalf("err: %v", err) + } + if !ok { + t.Fatalf("should apply") + } + + assertNodeAttributeContains(t, node, "network.throughput") + assertNodeAttributeContains(t, node, "network.ip-address") + assertNodeAttributeContains(t, node, "network.internal-ip") +} diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go new file mode 100644 index 000000000..280e2b6e4 --- /dev/null +++ b/client/fingerprint/network_unix.go @@ -0,0 +1,153 @@ +// +build linux darwin +package fingerprint + +import ( + "fmt" + "io/ioutil" + "log" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + + "github.com/hashicorp/nomad/client/config" + "github.com/hashicorp/nomad/nomad/structs" +) + +// UnixNetworkFingerprint is used to fingerprint the Network capabilities of a node +type UnixNetworkFingerprint struct { + logger *log.Logger +} + +// NewNetworkFingerprint is used to create a CPU fingerprint +func NewNetworkFingerprinter(logger *log.Logger) NetworkFingerPrinter { + f := &UnixNetworkFingerprint{logger: logger} + return f +} + +func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { + if ip := ifConfig("eth0"); ip != "" { + node.Attributes["network.ip-address"] = ip + } + + if s := f.LinkSpeed("eth0"); s != "" { + node.Attributes["network.throughput"] = s + } + + // return true, because we have a network connection + return true, nil +} + +func (f *UnixNetworkFingerprint) Interfaces() []string { + // No OP for now + return nil +} + +// LinkSpeed attempts to determine link speed, first by checking if any tools +// exist that can return the speed (ethtool for now). If no tools are found, +// fall back to /sys/class/net speed file, if it exists. +// +// The return value is in the format of "MB/s" +// +// LinkSpeed returns an empty string if no tools or sys file are found +func (f *UnixNetworkFingerprint) LinkSpeed(device string) string { + // Use LookPath to find the ethtool in the systems $PATH + // If it's not found or otherwise errors, LookPath returns and empty string + // and an error we can ignore for our purposes + ethtoolPath, _ := exec.LookPath("ethtool") + if ethtoolPath != "" { + speed := linkSpeedEthtool(ethtoolPath, device) + if speed != "" { + return speed + } + } + fmt.Println("[WARN] Ethtool not found, checking /sys/net speed file") + + // Fall back on checking a system file for link speed. + return linkSpeedSys(device) +} + +// linkSpeedSys parses the information stored in the sys diretory for the +// default device. This method retuns an empty string if the file is not found +// or cannot be read +func linkSpeedSys(device string) string { + path := fmt.Sprintf("/sys/class/net/%s/speed", device) + _, err := os.Stat(path) + if err != nil { + log.Printf("[WARN] Error getting information about net speed") + return "" + } + + // Read contents of the device/speed file + content, err := ioutil.ReadFile(path) + if err == nil { + lines := strings.Split(string(content), "\n") + // convert to MB/s + mbs, err := strconv.Atoi(lines[0]) + if err != nil { + log.Println("[WARN] Unable to parse ethtool output") + return "" + } + mbs = mbs / 8 + + return fmt.Sprintf("%dMB/s", mbs) + } + return "" +} + +// linkSpeedEthtool uses the ethtool installed on the node to gather link speed +// information. It executes the command on the device specified and parses +// out the speed. The expected format is Mbps and converted to MB/s +// Returns an empty string there is an error in parsing or executing ethtool +func linkSpeedEthtool(path, device string) string { + outBytes, err := exec.Command(path, device).Output() + if err == nil { + output := strings.TrimSpace(string(outBytes)) + re := regexp.MustCompile("Speed: [0-9]+[a-zA-Z]+/s") + m := re.FindString(output) + if m == "" { + // no matches found, output may be in a different format + log.Println("[WARN] Ethtool output did not match regex") + return "" + } + + // Split and trim the Mb/s unit from the string output + args := strings.Split(m, ": ") + raw := strings.TrimSuffix(args[1], "Mb/s") + + // convert to MB/s + mbs, err := strconv.Atoi(raw) + if err != nil { + log.Println("[WARN] Unable to parse ethtool output") + return "" + } + mbs = mbs / 8 + + return fmt.Sprintf("%dMB/s", mbs) + } + log.Printf("error calling ethtool (%s): %s", path, err) + return "" +} + +// ifConfig returns the IP Address for this node according to ifConfig, for the +// specified device. +func ifConfig(device string) string { + ifConfigPath, _ := exec.LookPath("ifconfig") + if ifConfigPath != "" { + outBytes, err := exec.Command(ifConfigPath, device).Output() + if err == nil { + output := strings.TrimSpace(string(outBytes)) + re := regexp.MustCompile("inet addr:[0-9].+") + m := re.FindString(output) + args := strings.Split(m, "inet addr:") + + return args[1] + } + log.Printf("[Err] Error calling ifconfig (%s): %s", ifConfigPath, err) + return "" + } + + log.Println("[WARN] Ethtool not found") + return "" +} From 66226d7510d5c478d605726b692269b4cfbc9324 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 14:23:48 -0500 Subject: [PATCH 03/21] Rework client/fingerprint/fingerprint.go to use a slice and enforce ordering --- client/fingerprint/fingerprint.go | 32 ++++++++++++++++++++++--------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/client/fingerprint/fingerprint.go b/client/fingerprint/fingerprint.go index 3a4587105..b87c0d9db 100644 --- a/client/fingerprint/fingerprint.go +++ b/client/fingerprint/fingerprint.go @@ -8,21 +8,35 @@ import ( "github.com/hashicorp/nomad/nomad/structs" ) -// BuiltinFingerprints contains the built in registered fingerprints -// which are available -var BuiltinFingerprints = map[string]Factory{ - "arch": NewArchFingerprint, - "cpu": NewCPUFingerprint, - "host": NewHostFingerprint, - "memory": NewMemoryFingerprint, - "storage": NewStorageFingerprint, +// BuiltinFingerprints is a slice containing the key names of all regestered +// fingerprints available, to provided an ordered iteration +var BuiltinFingerprints = []string{ + "arch", + "cpu", + "host", + "memory", + "storage", + "unix_network", + "aws_network", +} + +// builtinFingerprintMap contains the built in registered fingerprints +// which are available, corresponding to a key found in BuiltinFingerprints +var builtinFingerprintMap = map[string]Factory{ + "arch": NewArchFingerprint, + "cpu": NewCPUFingerprint, + "host": NewHostFingerprint, + "memory": NewMemoryFingerprint, + "storage": NewStorageFingerprint, + "unix_network": NewUnixNetworkFingerprinter, + "aws_network": NewAWSNetworkFingerprinter, } // NewFingerprint is used to instantiate and return a new fingerprint // given the name and a logger func NewFingerprint(name string, logger *log.Logger) (Fingerprint, error) { // Lookup the factory function - factory, ok := BuiltinFingerprints[name] + factory, ok := builtinFingerprintMap[name] if !ok { return nil, fmt.Errorf("unknown fingerprint '%s'", name) } From 7ca269381a53f9fe0e82d3f3e30ea03b58a7b6a4 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 15:59:00 -0500 Subject: [PATCH 04/21] Refactor the Network Fingerprinters --- client/fingerprint/network.go | 22 ------------------ client/fingerprint/network_aws.go | 30 +++++++++++++++--------- client/fingerprint/network_test.go | 7 ++---- client/fingerprint/network_unix.go | 37 +++++++++++++----------------- 4 files changed, 37 insertions(+), 59 deletions(-) diff --git a/client/fingerprint/network.go b/client/fingerprint/network.go index 5e3351d45..d59a5ff24 100644 --- a/client/fingerprint/network.go +++ b/client/fingerprint/network.go @@ -7,30 +7,8 @@ import ( "os" "regexp" "time" - - "github.com/hashicorp/nomad/client/config" - "github.com/hashicorp/nomad/nomad/structs" ) -type NetworkFingerPrinter interface { - // Fingerprint collects information about the nodes network configuration - Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) - - // Interfaces returns a slice of connected interface devices for the node - Interfaces() []string - - // LinkSpeed queries a given interface device and returns speed information, - // in MB/s - LinkSpeed(device string) string -} - -func NetworkDefault(logger *log.Logger) NetworkFingerPrinter { - if isAWS() { - return NewAWSNetworkFingerprinter(logger) - } - return NewNetworkFingerprinter(logger) -} - // isAWS queries the internal AWS Instance Metadata url, and determines if the // node is running on AWS or not. // TODO: Generalize this and use in other AWS related Fingerprinters diff --git a/client/fingerprint/network_aws.go b/client/fingerprint/network_aws.go index b1aee175b..9045f0e3e 100644 --- a/client/fingerprint/network_aws.go +++ b/client/fingerprint/network_aws.go @@ -2,11 +2,13 @@ package fingerprint import ( + "fmt" "io/ioutil" "log" "net/http" "net/url" "os" + "strconv" "strings" "time" @@ -20,7 +22,7 @@ type AWSNetworkFingerprint struct { } // AWSNetworkFingerprint is used to create a new AWS Network Fingerprinter -func NewAWSNetworkFingerprinter(logger *log.Logger) NetworkFingerPrinter { +func NewAWSNetworkFingerprinter(logger *log.Logger) Fingerprint { f := &AWSNetworkFingerprint{logger: logger} return f } @@ -61,19 +63,14 @@ func (f *AWSNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.No node.Attributes["network."+name] = strings.Trim(string(body), "\n") } - if throughput := f.LinkSpeed(""); throughput != "" { + if throughput := f.linkSpeed(); throughput != "" { node.Attributes["network.throughput"] = throughput } return true, nil } -func (f *AWSNetworkFingerprint) Interfaces() []string { - // NO OP for now - return nil -} - -func (f *AWSNetworkFingerprint) LinkSpeed(device string) string { +func (f *AWSNetworkFingerprint) linkSpeed() string { // This table is an approximation of network speeds based on // http://serverfault.com/questions/324883/aws-bandwidth-and-content-delivery/326797#326797 // which itself cites these sources: @@ -142,9 +139,20 @@ func (f *AWSNetworkFingerprint) LinkSpeed(device string) string { } key := strings.Trim(string(body), "\n") - if v, ok := net[key]; ok { - return v + v, ok := net[key] + if !ok { + return "" } - return "" + // convert to Mbps + if strings.Contains(v, "Gbp/s") { + i, err := strconv.Atoi(strings.TrimSuffix(v, "Gbp/s")) + if err != nil { + f.logger.Printf("[Err] Error converting lookup value") + return "" + } + v = fmt.Sprintf("%dMB/s", i*125) + } + + return v } diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index 85981998f..32155795d 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -14,7 +14,7 @@ import ( ) func TestNetworkFingerprint_basic(t *testing.T) { - f := NetworkDefault(testLogger()) + f := NewUnixNetworkFingerprinter(testLogger()) node := &structs.Node{ Attributes: make(map[string]string), } @@ -26,9 +26,6 @@ func TestNetworkFingerprint_basic(t *testing.T) { if !ok { t.Fatalf("should apply") } - if _, ok := f.(*UnixNetworkFingerprint); !ok { - t.Fatalf("Expected a Unix type Network Fingerprinter") - } // Darwin uses en0 for the default device, and does not have a standard // location for the linkspeed file, so we skip these @@ -57,7 +54,7 @@ func TestNetworkFingerprint_AWS(t *testing.T) { defer ts.Close() os.Setenv("AWS_ENV_URL", ts.URL+"/latest/meta-data/") - f := NetworkDefault(testLogger()) + f := NewAWSNetworkFingerprinter(testLogger()) node := &structs.Node{ Attributes: make(map[string]string), } diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index 280e2b6e4..929ef5efb 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -21,17 +21,17 @@ type UnixNetworkFingerprint struct { } // NewNetworkFingerprint is used to create a CPU fingerprint -func NewNetworkFingerprinter(logger *log.Logger) NetworkFingerPrinter { +func NewUnixNetworkFingerprinter(logger *log.Logger) Fingerprint { f := &UnixNetworkFingerprint{logger: logger} return f } func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { - if ip := ifConfig("eth0"); ip != "" { + if ip := f.ifConfig("eth0"); ip != "" { node.Attributes["network.ip-address"] = ip } - if s := f.LinkSpeed("eth0"); s != "" { + if s := f.linkSpeed("eth0"); s != "" { node.Attributes["network.throughput"] = s } @@ -39,11 +39,6 @@ func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.N return true, nil } -func (f *UnixNetworkFingerprint) Interfaces() []string { - // No OP for now - return nil -} - // LinkSpeed attempts to determine link speed, first by checking if any tools // exist that can return the speed (ethtool for now). If no tools are found, // fall back to /sys/class/net speed file, if it exists. @@ -51,27 +46,27 @@ func (f *UnixNetworkFingerprint) Interfaces() []string { // The return value is in the format of "MB/s" // // LinkSpeed returns an empty string if no tools or sys file are found -func (f *UnixNetworkFingerprint) LinkSpeed(device string) string { +func (f *UnixNetworkFingerprint) linkSpeed(device string) string { // Use LookPath to find the ethtool in the systems $PATH // If it's not found or otherwise errors, LookPath returns and empty string // and an error we can ignore for our purposes ethtoolPath, _ := exec.LookPath("ethtool") if ethtoolPath != "" { - speed := linkSpeedEthtool(ethtoolPath, device) + speed := f.linkSpeedEthtool(ethtoolPath, device) if speed != "" { return speed } } - fmt.Println("[WARN] Ethtool not found, checking /sys/net speed file") + f.logger.Printf("[WARN] Ethtool not found, checking /sys/net speed file") // Fall back on checking a system file for link speed. - return linkSpeedSys(device) + return f.linkSpeedSys(device) } // linkSpeedSys parses the information stored in the sys diretory for the // default device. This method retuns an empty string if the file is not found // or cannot be read -func linkSpeedSys(device string) string { +func (f *UnixNetworkFingerprint) linkSpeedSys(device string) string { path := fmt.Sprintf("/sys/class/net/%s/speed", device) _, err := os.Stat(path) if err != nil { @@ -86,7 +81,7 @@ func linkSpeedSys(device string) string { // convert to MB/s mbs, err := strconv.Atoi(lines[0]) if err != nil { - log.Println("[WARN] Unable to parse ethtool output") + f.logger.Println("[WARN] Unable to parse ethtool output") return "" } mbs = mbs / 8 @@ -100,7 +95,7 @@ func linkSpeedSys(device string) string { // information. It executes the command on the device specified and parses // out the speed. The expected format is Mbps and converted to MB/s // Returns an empty string there is an error in parsing or executing ethtool -func linkSpeedEthtool(path, device string) string { +func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) string { outBytes, err := exec.Command(path, device).Output() if err == nil { output := strings.TrimSpace(string(outBytes)) @@ -108,7 +103,7 @@ func linkSpeedEthtool(path, device string) string { m := re.FindString(output) if m == "" { // no matches found, output may be in a different format - log.Println("[WARN] Ethtool output did not match regex") + f.logger.Println("[WARN] Ethtool output did not match regex") return "" } @@ -119,20 +114,20 @@ func linkSpeedEthtool(path, device string) string { // convert to MB/s mbs, err := strconv.Atoi(raw) if err != nil { - log.Println("[WARN] Unable to parse ethtool output") + f.logger.Println("[WARN] Unable to parse ethtool output") return "" } mbs = mbs / 8 return fmt.Sprintf("%dMB/s", mbs) } - log.Printf("error calling ethtool (%s): %s", path, err) + f.logger.Printf("error calling ethtool (%s): %s", path, err) return "" } // ifConfig returns the IP Address for this node according to ifConfig, for the // specified device. -func ifConfig(device string) string { +func (f *UnixNetworkFingerprint) ifConfig(device string) string { ifConfigPath, _ := exec.LookPath("ifconfig") if ifConfigPath != "" { outBytes, err := exec.Command(ifConfigPath, device).Output() @@ -144,10 +139,10 @@ func ifConfig(device string) string { return args[1] } - log.Printf("[Err] Error calling ifconfig (%s): %s", ifConfigPath, err) + f.logger.Printf("[Err] Error calling ifconfig (%s): %s", ifConfigPath, err) return "" } - log.Println("[WARN] Ethtool not found") + f.logger.Println("[WARN] Ethtool not found") return "" } From 7fd8123b98ba0e2af6a0bfa8829b65c7c96d9748 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 16:01:29 -0500 Subject: [PATCH 05/21] change the naming --- client/fingerprint/fingerprint.go | 8 ++++---- client/fingerprint/network_aws.go | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/client/fingerprint/fingerprint.go b/client/fingerprint/fingerprint.go index b87c0d9db..e3363031e 100644 --- a/client/fingerprint/fingerprint.go +++ b/client/fingerprint/fingerprint.go @@ -16,8 +16,8 @@ var BuiltinFingerprints = []string{ "host", "memory", "storage", - "unix_network", - "aws_network", + "network_unix", + "network_aws", } // builtinFingerprintMap contains the built in registered fingerprints @@ -28,8 +28,8 @@ var builtinFingerprintMap = map[string]Factory{ "host": NewHostFingerprint, "memory": NewMemoryFingerprint, "storage": NewStorageFingerprint, - "unix_network": NewUnixNetworkFingerprinter, - "aws_network": NewAWSNetworkFingerprinter, + "network_unix": NewUnixNetworkFingerprinter, + "network_aws": NewAWSNetworkFingerprinter, } // NewFingerprint is used to instantiate and return a new fingerprint diff --git a/client/fingerprint/network_aws.go b/client/fingerprint/network_aws.go index 9045f0e3e..ae77585e1 100644 --- a/client/fingerprint/network_aws.go +++ b/client/fingerprint/network_aws.go @@ -1,4 +1,3 @@ -// +build linux,darwin package fingerprint import ( From 2942212ee3cbb81eb1ade3ad2b1d97da50de2be8 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 16:31:57 -0500 Subject: [PATCH 06/21] Refactor some AWS things, removing fingerprint/network.go - move isAWS to env, use in other places - fingerprint/network.go is no empty; removed --- client/fingerprint/env_aws.go | 40 ++++++++++++++++++++++++ client/fingerprint/network.go | 49 ------------------------------ client/fingerprint/network_aws.go | 3 ++ client/fingerprint/network_test.go | 15 +++++++++ 4 files changed, 58 insertions(+), 49 deletions(-) delete mode 100644 client/fingerprint/network.go diff --git a/client/fingerprint/env_aws.go b/client/fingerprint/env_aws.go index c52c7e85d..21c3c8410 100644 --- a/client/fingerprint/env_aws.go +++ b/client/fingerprint/env_aws.go @@ -6,6 +6,7 @@ import ( "net/http" "net/url" "os" + "regexp" "strings" "time" @@ -25,6 +26,9 @@ func NewEnvAWSFingerprint(logger *log.Logger) Fingerprint { } func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { + if !isAWS() { + return false, nil + } if node.Links == nil { node.Links = make(map[string]string) } @@ -76,3 +80,39 @@ func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) return true, nil } + +func isAWS() bool { + // Read the internal metadata URL from the environment, allowing test files to + // provide their own + metadataURL := os.Getenv("AWS_ENV_URL") + if metadataURL == "" { + metadataURL = "http://169.254.169.254/latest/meta-data/" + } + + // assume 2 seconds is enough time for inside AWS network + client := &http.Client{ + Timeout: 2 * time.Second, + } + + // Query the metadata url for the ami-id, to veryify we're on AWS + resp, err := client.Get(metadataURL + "ami-id") + + if err != nil { + log.Printf("[Err] Error querying AWS Metadata URL, skipping") + return false + } + defer resp.Body.Close() + + instanceID, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Printf("[Err] Error reading AWS Instance ID, skipping") + return false + } + + match, err := regexp.MatchString("ami-*", string(instanceID)) + if !match { + return false + } + + return true +} diff --git a/client/fingerprint/network.go b/client/fingerprint/network.go deleted file mode 100644 index d59a5ff24..000000000 --- a/client/fingerprint/network.go +++ /dev/null @@ -1,49 +0,0 @@ -package fingerprint - -import ( - "io/ioutil" - "log" - "net/http" - "os" - "regexp" - "time" -) - -// isAWS queries the internal AWS Instance Metadata url, and determines if the -// node is running on AWS or not. -// TODO: Generalize this and use in other AWS related Fingerprinters -func isAWS() bool { - // Read the internal metadata URL from the environment, allowing test files to - // provide their own - metadataURL := os.Getenv("AWS_ENV_URL") - if metadataURL == "" { - metadataURL = "http://169.254.169.254/latest/meta-data/" - } - - // assume 2 seconds is enough time for inside AWS network - client := &http.Client{ - Timeout: 2 * time.Second, - } - - // Query the metadata url for the ami-id, to veryify we're on AWS - resp, err := client.Get(metadataURL + "ami-id") - - if err != nil { - log.Printf("[Err] Error querying AWS Metadata URL, skipping") - return false - } - defer resp.Body.Close() - - instanceID, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Printf("[Err] Error reading AWS Instance ID, skipping") - return false - } - - match, err := regexp.MatchString("ami-*", string(instanceID)) - if !match { - return false - } - - return true -} diff --git a/client/fingerprint/network_aws.go b/client/fingerprint/network_aws.go index ae77585e1..6cd3da872 100644 --- a/client/fingerprint/network_aws.go +++ b/client/fingerprint/network_aws.go @@ -27,6 +27,9 @@ func NewAWSNetworkFingerprinter(logger *log.Logger) Fingerprint { } func (f *AWSNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { + if !isAWS() { + return false, nil + } metadataURL := os.Getenv("AWS_ENV_URL") if metadataURL == "" { metadataURL = "http://169.254.169.254/latest/meta-data/" diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index 32155795d..37a749ed9 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -71,3 +71,18 @@ func TestNetworkFingerprint_AWS(t *testing.T) { assertNodeAttributeContains(t, node, "network.ip-address") assertNodeAttributeContains(t, node, "network.internal-ip") } + +func TestNetworkFingerprint_notAWS(t *testing.T) { + f := NewAWSNetworkFingerprinter(testLogger()) + node := &structs.Node{ + Attributes: make(map[string]string), + } + + ok, err := f.Fingerprint(&config.Config{}, node) + if err != nil { + t.Fatalf("err: %v", err) + } + if ok { + t.Fatalf("Should not apply") + } +} From a2162041110349d1432d38d75d32bf0d4235f764 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 16:56:04 -0500 Subject: [PATCH 07/21] Consolidate the AWS fingerprinters --- client/fingerprint/env_aws.go | 104 +++++++++++++++++++ client/fingerprint/env_aws_test.go | 55 ++++++++++ client/fingerprint/fingerprint.go | 4 +- client/fingerprint/network_aws.go | 160 ----------------------------- client/fingerprint/network_test.go | 57 ---------- 5 files changed, 161 insertions(+), 219 deletions(-) delete mode 100644 client/fingerprint/network_aws.go diff --git a/client/fingerprint/env_aws.go b/client/fingerprint/env_aws.go index 21c3c8410..1871ee665 100644 --- a/client/fingerprint/env_aws.go +++ b/client/fingerprint/env_aws.go @@ -1,12 +1,14 @@ package fingerprint import ( + "fmt" "io/ioutil" "log" "net/http" "net/url" "os" "regexp" + "strconv" "strings" "time" @@ -75,6 +77,21 @@ func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) node.Attributes["platform.aws."+key] = strings.Trim(string(resp), "\n") } + // copy over network specific items + networkKeys := make(map[string]string) + networkKeys["public-hostname"] = "ip-address" + networkKeys["local-ipv4"] = "internal-ip" + for key, name := range networkKeys { + if node.Attributes["platform.aws."+key] != "" { + node.Attributes["network."+name] = node.Attributes["platform.aws."+key] + } + } + + // find LinkSpeed from lookup + if throughput := f.linkSpeed(); throughput != "" { + node.Attributes["network.throughput"] = throughput + } + // populate links node.Links["aws.ec2"] = node.Attributes["platform.aws.placement.availability-zone"] + "." + node.Attributes["platform.aws.instance-id"] @@ -116,3 +133,90 @@ func isAWS() bool { return true } + +// EnvAWSFingerprint uses lookup table to approximate network speeds based on +// http://serverfault.com/questions/324883/aws-bandwidth-and-content-delivery/326797#326797 +// which itself cites these sources: +// - http://blog.rightscale.com/2007/10/28/network-performance-within-amazon-ec2-and-to-amazon-s3/ +// - http://www.soc.napier.ac.uk/~bill/chris_p.pdf +// +// This data is meant for a loose approximation +func (f *EnvAWSFingerprint) linkSpeed() string { + net := make(map[string]string) + net["m4.large"] = "10MB/s" + net["m3.medium"] = "10MB/s" + net["m3.large"] = "10MB/s" + net["c4.large"] = "10MB/s" + net["c3.large"] = "10MB/s" + net["c3.xlarge"] = "10MB/s" + net["r3.large"] = "10MB/s" + net["r3.xlarge"] = "10MB/s" + net["i2.xlarge"] = "10MB/s" + net["d2.xlarge"] = "10MB/s" + net["t2.micro"] = "2MB/s" + net["t2.small"] = "2MB/s" + net["t2.medium"] = "2MB/s" + net["t2.large"] = "2MB/s" + net["m4.xlarge"] = "95MB/s" + net["m4.2xlarge"] = "95MB/s" + net["m4.4xlarge"] = "95MB/s" + net["m3.xlarge"] = "95MB/s" + net["m3.2xlarge"] = "95MB/s" + net["c4.xlarge"] = "95MB/s" + net["c4.2xlarge"] = "95MB/s" + net["c4.4xlarge"] = "95MB/s" + net["c3.2xlarge"] = "95MB/s" + net["c3.4xlarge"] = "95MB/s" + net["g2.2xlarge"] = "95MB/s" + net["r3.2xlarge"] = "95MB/s" + net["r3.4xlarge"] = "95MB/s" + net["i2.2xlarge"] = "95MB/s" + net["i2.4xlarge"] = "95MB/s" + net["d2.2xlarge"] = "95MB/s" + net["d2.4xlarge"] = "95MB/s" + net["m4.10xlarge"] = "10Gbp/s" + net["c4.8xlarge"] = "10Gbp/s" + net["c3.8xlarge"] = "10Gbp/s" + net["g2.8xlarge"] = "10Gbp/s" + net["r3.8xlarge"] = "10Gbp/s" + net["i2.8xlarge"] = "10Gbp/s" + net["d2.8xlarge"] = "10Gbp/s" + + // Query the API for the instance type, and use the table above to approximate + // the network speed + metadataURL := os.Getenv("AWS_ENV_URL") + if metadataURL == "" { + metadataURL = "http://169.254.169.254/latest/meta-data/" + } + + // assume 2 seconds is enough time for inside AWS network + client := &http.Client{ + Timeout: 2 * time.Second, + } + + res, err := client.Get(metadataURL + "instance-type") + body, err := ioutil.ReadAll(res.Body) + res.Body.Close() + if err != nil { + log.Fatal(err) + return "" + } + + key := strings.Trim(string(body), "\n") + v, ok := net[key] + if !ok { + return "" + } + + // convert to Mbps + if strings.Contains(v, "Gbp/s") { + i, err := strconv.Atoi(strings.TrimSuffix(v, "Gbp/s")) + if err != nil { + f.logger.Printf("[Err] Error converting lookup value") + return "" + } + v = fmt.Sprintf("%dMB/s", i*125) + } + + return v +} diff --git a/client/fingerprint/env_aws_test.go b/client/fingerprint/env_aws_test.go index 291b93835..c0c7ce097 100644 --- a/client/fingerprint/env_aws_test.go +++ b/client/fingerprint/env_aws_test.go @@ -69,6 +69,9 @@ func TestEnvAWSFingerprint_aws(t *testing.T) { "platform.aws.public-hostname", "platform.aws.public-ipv4", "platform.aws.placement.availability-zone", + "network.throughput", + "network.ip-address", + "network.internal-ip", } for _, k := range keys { @@ -145,3 +148,55 @@ const aws_routes = ` ] } ` + +func TestNetworkFingerprint_AWS(t *testing.T) { + // configure mock server with fixture routes, data + // TODO: Refator with the AWS ENV test + routes := routes{} + if err := json.Unmarshal([]byte(aws_routes), &routes); err != nil { + t.Fatalf("Failed to unmarshal JSON in AWS ENV test: %s", err) + } + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + for _, e := range routes.Endpoints { + if r.RequestURI == e.Uri { + w.Header().Set("Content-Type", e.ContentType) + fmt.Fprintln(w, e.Body) + } + } + })) + + defer ts.Close() + os.Setenv("AWS_ENV_URL", ts.URL+"/latest/meta-data/") + + f := NewEnvAWSFingerprint(testLogger()) + node := &structs.Node{ + Attributes: make(map[string]string), + } + + ok, err := f.Fingerprint(&config.Config{}, node) + if err != nil { + t.Fatalf("err: %v", err) + } + if !ok { + t.Fatalf("should apply") + } + + assertNodeAttributeContains(t, node, "network.throughput") + assertNodeAttributeContains(t, node, "network.ip-address") + assertNodeAttributeContains(t, node, "network.internal-ip") +} + +func TestNetworkFingerprint_notAWS(t *testing.T) { + f := NewEnvAWSFingerprint(testLogger()) + node := &structs.Node{ + Attributes: make(map[string]string), + } + + ok, err := f.Fingerprint(&config.Config{}, node) + if err != nil { + t.Fatalf("err: %v", err) + } + if ok { + t.Fatalf("Should not apply") + } +} diff --git a/client/fingerprint/fingerprint.go b/client/fingerprint/fingerprint.go index e3363031e..3e8d91c97 100644 --- a/client/fingerprint/fingerprint.go +++ b/client/fingerprint/fingerprint.go @@ -17,7 +17,7 @@ var BuiltinFingerprints = []string{ "memory", "storage", "network_unix", - "network_aws", + "env_aws", } // builtinFingerprintMap contains the built in registered fingerprints @@ -29,7 +29,7 @@ var builtinFingerprintMap = map[string]Factory{ "memory": NewMemoryFingerprint, "storage": NewStorageFingerprint, "network_unix": NewUnixNetworkFingerprinter, - "network_aws": NewAWSNetworkFingerprinter, + "env_aws": NewEnvAWSFingerprint, } // NewFingerprint is used to instantiate and return a new fingerprint diff --git a/client/fingerprint/network_aws.go b/client/fingerprint/network_aws.go deleted file mode 100644 index 6cd3da872..000000000 --- a/client/fingerprint/network_aws.go +++ /dev/null @@ -1,160 +0,0 @@ -package fingerprint - -import ( - "fmt" - "io/ioutil" - "log" - "net/http" - "net/url" - "os" - "strconv" - "strings" - "time" - - "github.com/hashicorp/nomad/client/config" - "github.com/hashicorp/nomad/nomad/structs" -) - -// AWSNetworkFingerprint is used to fingerprint the Network capabilities of a node -type AWSNetworkFingerprint struct { - logger *log.Logger -} - -// AWSNetworkFingerprint is used to create a new AWS Network Fingerprinter -func NewAWSNetworkFingerprinter(logger *log.Logger) Fingerprint { - f := &AWSNetworkFingerprint{logger: logger} - return f -} - -func (f *AWSNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { - if !isAWS() { - return false, nil - } - metadataURL := os.Getenv("AWS_ENV_URL") - if metadataURL == "" { - metadataURL = "http://169.254.169.254/latest/meta-data/" - } - - // assume 2 seconds is enough time for inside AWS network - client := &http.Client{ - Timeout: 2 * time.Second, - } - - keys := make(map[string]string) - keys["ip-address"] = "public-hostname" - keys["internal-ip"] = "local-ipv4" - - for name, key := range keys { - res, err := client.Get(metadataURL + key) - if err != nil { - // if it's a URL error, assume we're not in an AWS environment - if _, ok := err.(*url.Error); ok { - return false, nil - } - // not sure what other errors it would return - return false, err - } - body, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - log.Fatal(err) - return false, err - } - - // assume we want blank entries - node.Attributes["network."+name] = strings.Trim(string(body), "\n") - } - - if throughput := f.linkSpeed(); throughput != "" { - node.Attributes["network.throughput"] = throughput - } - - return true, nil -} - -func (f *AWSNetworkFingerprint) linkSpeed() string { - // This table is an approximation of network speeds based on - // http://serverfault.com/questions/324883/aws-bandwidth-and-content-delivery/326797#326797 - // which itself cites these sources: - // - http://blog.rightscale.com/2007/10/28/network-performance-within-amazon-ec2-and-to-amazon-s3/ - // - http://www.soc.napier.ac.uk/~bill/chris_p.pdf - // - // This data is meant for a loose approximation - net := make(map[string]string) - net["m4.large"] = "10MB/s" - net["m3.medium"] = "10MB/s" - net["m3.large"] = "10MB/s" - net["c4.large"] = "10MB/s" - net["c3.large"] = "10MB/s" - net["c3.xlarge"] = "10MB/s" - net["r3.large"] = "10MB/s" - net["r3.xlarge"] = "10MB/s" - net["i2.xlarge"] = "10MB/s" - net["d2.xlarge"] = "10MB/s" - net["t2.micro"] = "2MB/s" - net["t2.small"] = "2MB/s" - net["t2.medium"] = "2MB/s" - net["t2.large"] = "2MB/s" - net["m4.xlarge"] = "95MB/s" - net["m4.2xlarge"] = "95MB/s" - net["m4.4xlarge"] = "95MB/s" - net["m3.xlarge"] = "95MB/s" - net["m3.2xlarge"] = "95MB/s" - net["c4.xlarge"] = "95MB/s" - net["c4.2xlarge"] = "95MB/s" - net["c4.4xlarge"] = "95MB/s" - net["c3.2xlarge"] = "95MB/s" - net["c3.4xlarge"] = "95MB/s" - net["g2.2xlarge"] = "95MB/s" - net["r3.2xlarge"] = "95MB/s" - net["r3.4xlarge"] = "95MB/s" - net["i2.2xlarge"] = "95MB/s" - net["i2.4xlarge"] = "95MB/s" - net["d2.2xlarge"] = "95MB/s" - net["d2.4xlarge"] = "95MB/s" - net["m4.10xlarge"] = "10Gbp/s" - net["c4.8xlarge"] = "10Gbp/s" - net["c3.8xlarge"] = "10Gbp/s" - net["g2.8xlarge"] = "10Gbp/s" - net["r3.8xlarge"] = "10Gbp/s" - net["i2.8xlarge"] = "10Gbp/s" - net["d2.8xlarge"] = "10Gbp/s" - - // Query the API for the instance type, and use the table above to approximate - // the network speed - metadataURL := os.Getenv("AWS_ENV_URL") - if metadataURL == "" { - metadataURL = "http://169.254.169.254/latest/meta-data/" - } - - // assume 2 seconds is enough time for inside AWS network - client := &http.Client{ - Timeout: 2 * time.Second, - } - - res, err := client.Get(metadataURL + "instance-type") - body, err := ioutil.ReadAll(res.Body) - res.Body.Close() - if err != nil { - log.Fatal(err) - return "" - } - - key := strings.Trim(string(body), "\n") - v, ok := net[key] - if !ok { - return "" - } - - // convert to Mbps - if strings.Contains(v, "Gbp/s") { - i, err := strconv.Atoi(strings.TrimSuffix(v, "Gbp/s")) - if err != nil { - f.logger.Printf("[Err] Error converting lookup value") - return "" - } - v = fmt.Sprintf("%dMB/s", i*125) - } - - return v -} diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index 37a749ed9..b77b9fc5e 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -1,11 +1,6 @@ package fingerprint import ( - "encoding/json" - "fmt" - "net/http" - "net/http/httptest" - "os" "runtime" "testing" @@ -34,55 +29,3 @@ func TestNetworkFingerprint_basic(t *testing.T) { assertNodeAttributeContains(t, node, "network.ip-address") } } - -func TestNetworkFingerprint_AWS(t *testing.T) { - // configure mock server with fixture routes, data - // TODO: Refator with the AWS ENV test - routes := routes{} - if err := json.Unmarshal([]byte(aws_routes), &routes); err != nil { - t.Fatalf("Failed to unmarshal JSON in AWS ENV test: %s", err) - } - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - for _, e := range routes.Endpoints { - if r.RequestURI == e.Uri { - w.Header().Set("Content-Type", e.ContentType) - fmt.Fprintln(w, e.Body) - } - } - })) - - defer ts.Close() - os.Setenv("AWS_ENV_URL", ts.URL+"/latest/meta-data/") - - f := NewAWSNetworkFingerprinter(testLogger()) - node := &structs.Node{ - Attributes: make(map[string]string), - } - - ok, err := f.Fingerprint(&config.Config{}, node) - if err != nil { - t.Fatalf("err: %v", err) - } - if !ok { - t.Fatalf("should apply") - } - - assertNodeAttributeContains(t, node, "network.throughput") - assertNodeAttributeContains(t, node, "network.ip-address") - assertNodeAttributeContains(t, node, "network.internal-ip") -} - -func TestNetworkFingerprint_notAWS(t *testing.T) { - f := NewAWSNetworkFingerprinter(testLogger()) - node := &structs.Node{ - Attributes: make(map[string]string), - } - - ok, err := f.Fingerprint(&config.Config{}, node) - if err != nil { - t.Fatalf("err: %v", err) - } - if ok { - t.Fatalf("Should not apply") - } -} From ea7cc84b94fc5e11508c036224cd76c138c1c807 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 22:04:20 -0500 Subject: [PATCH 08/21] Move ec2InstanceSpeed to a package variable, convert to string:int map --- client/fingerprint/env_aws.go | 103 +++++++++++++---------------- client/fingerprint/network_unix.go | 37 ++++++----- 2 files changed, 67 insertions(+), 73 deletions(-) diff --git a/client/fingerprint/env_aws.go b/client/fingerprint/env_aws.go index 1871ee665..6c5947f58 100644 --- a/client/fingerprint/env_aws.go +++ b/client/fingerprint/env_aws.go @@ -8,7 +8,6 @@ import ( "net/url" "os" "regexp" - "strconv" "strings" "time" @@ -16,6 +15,47 @@ import ( "github.com/hashicorp/nomad/nomad/structs" ) +var ec2InstanceSpeedMap = map[string]int{ + "m4.large": 10, + "m3.medium": 10, + "m3.large": 10, + "c4.large": 10, + "c3.large": 10, + "c3.xlarge": 10, + "r3.large": 10, + "r3.xlarge": 10, + "i2.xlarge": 10, + "d2.xlarge": 10, + "t2.micro": 2, + "t2.small": 2, + "t2.medium": 2, + "t2.large": 2, + "m4.xlarge": 95, + "m4.2xlarge": 95, + "m4.4xlarge": 95, + "m3.xlarge": 95, + "m3.2xlarge": 95, + "c4.xlarge": 95, + "c4.2xlarge": 95, + "c4.4xlarge": 95, + "c3.2xlarge": 95, + "c3.4xlarge": 95, + "g2.2xlarge": 95, + "r3.2xlarge": 95, + "r3.4xlarge": 95, + "i2.2xlarge": 95, + "i2.4xlarge": 95, + "d2.2xlarge": 95, + "d2.4xlarge": 95, + "m4.10xlarge": 1250, + "c4.8xlarge": 1250, + "c3.8xlarge": 1250, + "g2.8xlarge": 1250, + "r3.8xlarge": 1250, + "i2.8xlarge": 1250, + "d2.8xlarge": 1250, +} + // EnvAWSFingerprint is used to fingerprint the CPU type EnvAWSFingerprint struct { logger *log.Logger @@ -88,8 +128,8 @@ func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) } // find LinkSpeed from lookup - if throughput := f.linkSpeed(); throughput != "" { - node.Attributes["network.throughput"] = throughput + if throughput := f.linkSpeed(); throughput > 0 { + node.Attributes["network.throughput"] = fmt.Sprintf("%dMB/s", throughput) } // populate links @@ -141,46 +181,7 @@ func isAWS() bool { // - http://www.soc.napier.ac.uk/~bill/chris_p.pdf // // This data is meant for a loose approximation -func (f *EnvAWSFingerprint) linkSpeed() string { - net := make(map[string]string) - net["m4.large"] = "10MB/s" - net["m3.medium"] = "10MB/s" - net["m3.large"] = "10MB/s" - net["c4.large"] = "10MB/s" - net["c3.large"] = "10MB/s" - net["c3.xlarge"] = "10MB/s" - net["r3.large"] = "10MB/s" - net["r3.xlarge"] = "10MB/s" - net["i2.xlarge"] = "10MB/s" - net["d2.xlarge"] = "10MB/s" - net["t2.micro"] = "2MB/s" - net["t2.small"] = "2MB/s" - net["t2.medium"] = "2MB/s" - net["t2.large"] = "2MB/s" - net["m4.xlarge"] = "95MB/s" - net["m4.2xlarge"] = "95MB/s" - net["m4.4xlarge"] = "95MB/s" - net["m3.xlarge"] = "95MB/s" - net["m3.2xlarge"] = "95MB/s" - net["c4.xlarge"] = "95MB/s" - net["c4.2xlarge"] = "95MB/s" - net["c4.4xlarge"] = "95MB/s" - net["c3.2xlarge"] = "95MB/s" - net["c3.4xlarge"] = "95MB/s" - net["g2.2xlarge"] = "95MB/s" - net["r3.2xlarge"] = "95MB/s" - net["r3.4xlarge"] = "95MB/s" - net["i2.2xlarge"] = "95MB/s" - net["i2.4xlarge"] = "95MB/s" - net["d2.2xlarge"] = "95MB/s" - net["d2.4xlarge"] = "95MB/s" - net["m4.10xlarge"] = "10Gbp/s" - net["c4.8xlarge"] = "10Gbp/s" - net["c3.8xlarge"] = "10Gbp/s" - net["g2.8xlarge"] = "10Gbp/s" - net["r3.8xlarge"] = "10Gbp/s" - net["i2.8xlarge"] = "10Gbp/s" - net["d2.8xlarge"] = "10Gbp/s" +func (f *EnvAWSFingerprint) linkSpeed() int { // Query the API for the instance type, and use the table above to approximate // the network speed @@ -199,23 +200,13 @@ func (f *EnvAWSFingerprint) linkSpeed() string { res.Body.Close() if err != nil { log.Fatal(err) - return "" + return 0 } key := strings.Trim(string(body), "\n") - v, ok := net[key] + v, ok := ec2InstanceSpeedMap[key] if !ok { - return "" - } - - // convert to Mbps - if strings.Contains(v, "Gbp/s") { - i, err := strconv.Atoi(strings.TrimSuffix(v, "Gbp/s")) - if err != nil { - f.logger.Printf("[Err] Error converting lookup value") - return "" - } - v = fmt.Sprintf("%dMB/s", i*125) + return 0 } return v diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index 929ef5efb..da4872115 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -31,8 +31,8 @@ func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.N node.Attributes["network.ip-address"] = ip } - if s := f.linkSpeed("eth0"); s != "" { - node.Attributes["network.throughput"] = s + if throughput := f.linkSpeed("eth0"); throughput > 0 { + node.Attributes["network.throughput"] = fmt.Sprintf("%dMB/s", throughput) } // return true, because we have a network connection @@ -46,14 +46,13 @@ func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.N // The return value is in the format of "MB/s" // // LinkSpeed returns an empty string if no tools or sys file are found -func (f *UnixNetworkFingerprint) linkSpeed(device string) string { +func (f *UnixNetworkFingerprint) linkSpeed(device string) int { // Use LookPath to find the ethtool in the systems $PATH // If it's not found or otherwise errors, LookPath returns and empty string // and an error we can ignore for our purposes ethtoolPath, _ := exec.LookPath("ethtool") if ethtoolPath != "" { - speed := f.linkSpeedEthtool(ethtoolPath, device) - if speed != "" { + if speed := f.linkSpeedEthtool(ethtoolPath, device); speed > 0 { return speed } } @@ -66,12 +65,12 @@ func (f *UnixNetworkFingerprint) linkSpeed(device string) string { // linkSpeedSys parses the information stored in the sys diretory for the // default device. This method retuns an empty string if the file is not found // or cannot be read -func (f *UnixNetworkFingerprint) linkSpeedSys(device string) string { +func (f *UnixNetworkFingerprint) linkSpeedSys(device string) int { path := fmt.Sprintf("/sys/class/net/%s/speed", device) _, err := os.Stat(path) if err != nil { log.Printf("[WARN] Error getting information about net speed") - return "" + return 0 } // Read contents of the device/speed file @@ -82,20 +81,22 @@ func (f *UnixNetworkFingerprint) linkSpeedSys(device string) string { mbs, err := strconv.Atoi(lines[0]) if err != nil { f.logger.Println("[WARN] Unable to parse ethtool output") - return "" + return 0 } - mbs = mbs / 8 - return fmt.Sprintf("%dMB/s", mbs) + // Convert to MB/s + if mbs > 0 { + return mbs / 8 + } } - return "" + return 0 } // linkSpeedEthtool uses the ethtool installed on the node to gather link speed // information. It executes the command on the device specified and parses // out the speed. The expected format is Mbps and converted to MB/s // Returns an empty string there is an error in parsing or executing ethtool -func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) string { +func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) int { outBytes, err := exec.Command(path, device).Output() if err == nil { output := strings.TrimSpace(string(outBytes)) @@ -104,7 +105,7 @@ func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) string { if m == "" { // no matches found, output may be in a different format f.logger.Println("[WARN] Ethtool output did not match regex") - return "" + return 0 } // Split and trim the Mb/s unit from the string output @@ -115,14 +116,16 @@ func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) string { mbs, err := strconv.Atoi(raw) if err != nil { f.logger.Println("[WARN] Unable to parse ethtool output") - return "" + return 0 } - mbs = mbs / 8 - return fmt.Sprintf("%dMB/s", mbs) + // Convert to MB/s + if mbs > 0 { + return mbs / 8 + } } f.logger.Printf("error calling ethtool (%s): %s", path, err) - return "" + return 0 } // ifConfig returns the IP Address for this node according to ifConfig, for the From 3882fd7afcf3ca020f1d9fcc19e3c3b112999168 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 22:56:31 -0500 Subject: [PATCH 09/21] Update code for parsing IP address --- client/fingerprint/network_test.go | 9 ++++++- client/fingerprint/network_unix.go | 38 ++++++++++++++++++++++++++---- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index b77b9fc5e..6d49e3025 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -1,6 +1,7 @@ package fingerprint import ( + "net" "runtime" "testing" @@ -26,6 +27,12 @@ func TestNetworkFingerprint_basic(t *testing.T) { // location for the linkspeed file, so we skip these if "darwin" != runtime.GOOS { assertNodeAttributeContains(t, node, "network.throughput") - assertNodeAttributeContains(t, node, "network.ip-address") + } + assertNodeAttributeContains(t, node, "network.ip-address") + + ip := node.Attributes["network.ip-address"] + match := net.ParseIP(ip) + if match == nil { + t.Fatalf("Bad IP match: %s", ip) } } diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index da4872115..a07b4c0f6 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -5,9 +5,11 @@ import ( "fmt" "io/ioutil" "log" + "net" "os" "os/exec" "regexp" + "runtime" "strconv" "strings" @@ -27,7 +29,12 @@ func NewUnixNetworkFingerprinter(logger *log.Logger) Fingerprint { } func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { - if ip := f.ifConfig("eth0"); ip != "" { + // eth0 is the default device for Linux, and en0 is default for OS X + defaultDevice := "eth0" + if "darwin" == runtime.GOOS { + defaultDevice = "en0" + } + if ip := f.ifConfig(defaultDevice); ip != "" { node.Attributes["network.ip-address"] = ip } @@ -135,12 +142,33 @@ func (f *UnixNetworkFingerprint) ifConfig(device string) string { if ifConfigPath != "" { outBytes, err := exec.Command(ifConfigPath, device).Output() if err == nil { + // Parse out the IP address returned from ifconfig for this device + // Tested on Ubuntu, the matching part of ifconfig output for eth0 is like + // so: + // inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 + // For OS X and en0, we have: + // inet 192.168.0.7 netmask 0xffffff00 broadcast 192.168.0.255 output := strings.TrimSpace(string(outBytes)) - re := regexp.MustCompile("inet addr:[0-9].+") - m := re.FindString(output) - args := strings.Split(m, "inet addr:") - return args[1] + // re is a regular expression, which can vary based on the OS + var re *regexp.Regexp + + if "darwin" == runtime.GOOS { + re = regexp.MustCompile("inet [0-9].+") + } else { + re = regexp.MustCompile("inet addr:[0-9].+") + } + args := strings.Split(re.FindString(output), " ") + + var ip string + if len(args) > 1 { + ip = strings.TrimPrefix(args[1], "addr:") + } + + // validate what we've sliced out is a valid IP + if net.ParseIP(ip) != nil { + return ip + } } f.logger.Printf("[Err] Error calling ifconfig (%s): %s", ifConfigPath, err) return "" From a90109c7b84e3e4e4c0554aeb77e509816af50ac Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 23:22:23 -0500 Subject: [PATCH 10/21] populate node network resource --- client/fingerprint/env_aws.go | 26 +++++++++++++++++--------- client/fingerprint/env_aws_test.go | 11 ++++++++++- client/fingerprint/network_test.go | 10 ++++++++++ client/fingerprint/network_unix.go | 11 +++++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) diff --git a/client/fingerprint/env_aws.go b/client/fingerprint/env_aws.go index 6c5947f58..c537d8b2f 100644 --- a/client/fingerprint/env_aws.go +++ b/client/fingerprint/env_aws.go @@ -71,6 +71,10 @@ func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) if !isAWS() { return false, nil } + + // newNetwork is populated and addded to the Nodes resources + newNetwork := &structs.NetworkResource{} + if node.Links == nil { node.Links = make(map[string]string) } @@ -117,22 +121,26 @@ func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) node.Attributes["platform.aws."+key] = strings.Trim(string(resp), "\n") } - // copy over network specific items - networkKeys := make(map[string]string) - networkKeys["public-hostname"] = "ip-address" - networkKeys["local-ipv4"] = "internal-ip" - for key, name := range networkKeys { - if node.Attributes["platform.aws."+key] != "" { - node.Attributes["network."+name] = node.Attributes["platform.aws."+key] - } + // copy over network specific information + if node.Attributes["platform.aws.local-ipv4"] != "" { + node.Attributes["network.ip-address"] = node.Attributes["platform.aws.local-ipv4"] + newNetwork.IP = node.Attributes["platform.aws.local-ipv4"] } // find LinkSpeed from lookup if throughput := f.linkSpeed(); throughput > 0 { node.Attributes["network.throughput"] = fmt.Sprintf("%dMB/s", throughput) + newNetwork.MBits = throughput } - // populate links + if node.Resources == nil { + node.Resources = &structs.Resources{} + } + node.Resources.Networks = append(node.Resources.Networks, newNetwork) + + // populate Node Network Resources + + // populate Links node.Links["aws.ec2"] = node.Attributes["platform.aws.placement.availability-zone"] + "." + node.Attributes["platform.aws.instance-id"] return true, nil diff --git a/client/fingerprint/env_aws_test.go b/client/fingerprint/env_aws_test.go index c0c7ce097..6c858b7ab 100644 --- a/client/fingerprint/env_aws_test.go +++ b/client/fingerprint/env_aws_test.go @@ -183,7 +183,16 @@ func TestNetworkFingerprint_AWS(t *testing.T) { assertNodeAttributeContains(t, node, "network.throughput") assertNodeAttributeContains(t, node, "network.ip-address") - assertNodeAttributeContains(t, node, "network.internal-ip") + + if node.Resources == nil || len(node.Resources.Networks) == 0 { + t.Fatal("Expected to find Network Resources") + } + + // Test at least the first Network Resource + net := node.Resources.Networks[0] + if net.IP == "" { + t.Fatal("Expected Network Resource to not be empty") + } } func TestNetworkFingerprint_notAWS(t *testing.T) { diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index 6d49e3025..dae94e2fc 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -35,4 +35,14 @@ func TestNetworkFingerprint_basic(t *testing.T) { if match == nil { t.Fatalf("Bad IP match: %s", ip) } + + if node.Resources == nil || len(node.Resources.Networks) == 0 { + t.Fatal("Expected to find Network Resources") + } + + // Test at least the first Network Resource + net := node.Resources.Networks[0] + if net.IP == "" { + t.Fatal("Expected Network Resource to not be empty") + } } diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index a07b4c0f6..d6646cb39 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -29,6 +29,9 @@ func NewUnixNetworkFingerprinter(logger *log.Logger) Fingerprint { } func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { + // newNetwork is populated and addded to the Nodes resources + newNetwork := &structs.NetworkResource{} + // eth0 is the default device for Linux, and en0 is default for OS X defaultDevice := "eth0" if "darwin" == runtime.GOOS { @@ -36,12 +39,20 @@ func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.N } if ip := f.ifConfig(defaultDevice); ip != "" { node.Attributes["network.ip-address"] = ip + newNetwork.IP = ip } if throughput := f.linkSpeed("eth0"); throughput > 0 { node.Attributes["network.throughput"] = fmt.Sprintf("%dMB/s", throughput) + newNetwork.MBits = throughput } + if node.Resources == nil { + node.Resources = &structs.Resources{} + } + + node.Resources.Networks = append(node.Resources.Networks, newNetwork) + // return true, because we have a network connection return true, nil } From c9dab7e0c72baa860d728162b030655445eec063 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 23:25:12 -0500 Subject: [PATCH 11/21] fix range error --- client/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/client.go b/client/client.go index da6ad20bf..f36e24c43 100644 --- a/client/client.go +++ b/client/client.go @@ -344,7 +344,7 @@ func (c *Client) setupNode() error { // fingerprint is used to fingerprint the client and setup the node func (c *Client) fingerprint() error { var applied []string - for name := range fingerprint.BuiltinFingerprints { + for _, name := range fingerprint.BuiltinFingerprints { f, err := fingerprint.NewFingerprint(name, c.logger) if err != nil { return err From c799477f83f56264ede70567fcaeac907cdf859a Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 23:51:56 -0500 Subject: [PATCH 12/21] refactoring, docs --- client/fingerprint/env_aws.go | 98 +++++++++++++++--------------- client/fingerprint/env_aws_test.go | 10 ++- client/fingerprint/network_test.go | 12 ++-- client/fingerprint/network_unix.go | 7 ++- 4 files changed, 68 insertions(+), 59 deletions(-) diff --git a/client/fingerprint/env_aws.go b/client/fingerprint/env_aws.go index c537d8b2f..35704688a 100644 --- a/client/fingerprint/env_aws.go +++ b/client/fingerprint/env_aws.go @@ -1,7 +1,6 @@ package fingerprint import ( - "fmt" "io/ioutil" "log" "net/http" @@ -15,45 +14,52 @@ import ( "github.com/hashicorp/nomad/nomad/structs" ) +// map of instance type to approximate speed, in Mbits/s +// http://serverfault.com/questions/324883/aws-bandwidth-and-content-delivery/326797#326797 +// which itself cites these sources: +// - http://blog.rightscale.com/2007/10/28/network-performance-within-amazon-ec2-and-to-amazon-s3/ +// - http://www.soc.napier.ac.uk/~bill/chris_p.pdf +// +// This data is meant for a loose approximation var ec2InstanceSpeedMap = map[string]int{ - "m4.large": 10, - "m3.medium": 10, - "m3.large": 10, - "c4.large": 10, - "c3.large": 10, - "c3.xlarge": 10, - "r3.large": 10, - "r3.xlarge": 10, - "i2.xlarge": 10, - "d2.xlarge": 10, - "t2.micro": 2, - "t2.small": 2, - "t2.medium": 2, - "t2.large": 2, - "m4.xlarge": 95, - "m4.2xlarge": 95, - "m4.4xlarge": 95, - "m3.xlarge": 95, - "m3.2xlarge": 95, - "c4.xlarge": 95, - "c4.2xlarge": 95, - "c4.4xlarge": 95, - "c3.2xlarge": 95, - "c3.4xlarge": 95, - "g2.2xlarge": 95, - "r3.2xlarge": 95, - "r3.4xlarge": 95, - "i2.2xlarge": 95, - "i2.4xlarge": 95, - "d2.2xlarge": 95, - "d2.4xlarge": 95, - "m4.10xlarge": 1250, - "c4.8xlarge": 1250, - "c3.8xlarge": 1250, - "g2.8xlarge": 1250, - "r3.8xlarge": 1250, - "i2.8xlarge": 1250, - "d2.8xlarge": 1250, + "m4.large": 80, + "m3.medium": 80, + "m3.large": 80, + "c4.large": 80, + "c3.large": 80, + "c3.xlarge": 80, + "r3.large": 80, + "r3.xlarge": 80, + "i2.xlarge": 80, + "d2.xlarge": 80, + "t2.micro": 16, + "t2.small": 16, + "t2.medium": 16, + "t2.large": 16, + "m4.xlarge": 760, + "m4.2xlarge": 760, + "m4.4xlarge": 760, + "m3.xlarge": 760, + "m3.2xlarge": 760, + "c4.xlarge": 760, + "c4.2xlarge": 760, + "c4.4xlarge": 760, + "c3.2xlarge": 760, + "c3.4xlarge": 760, + "g2.2xlarge": 760, + "r3.2xlarge": 760, + "r3.4xlarge": 760, + "i2.2xlarge": 760, + "i2.4xlarge": 760, + "d2.2xlarge": 760, + "d2.4xlarge": 760, + "m4.10xlarge": 10000, + "c4.8xlarge": 10000, + "c3.8xlarge": 10000, + "g2.8xlarge": 10000, + "r3.8xlarge": 10000, + "i2.8xlarge": 10000, + "d2.8xlarge": 10000, } // EnvAWSFingerprint is used to fingerprint the CPU @@ -73,7 +79,9 @@ func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) } // newNetwork is populated and addded to the Nodes resources - newNetwork := &structs.NetworkResource{} + newNetwork := &structs.NetworkResource{ + Device: "eth0", + } if node.Links == nil { node.Links = make(map[string]string) @@ -125,11 +133,11 @@ func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) if node.Attributes["platform.aws.local-ipv4"] != "" { node.Attributes["network.ip-address"] = node.Attributes["platform.aws.local-ipv4"] newNetwork.IP = node.Attributes["platform.aws.local-ipv4"] + newNetwork.CIDR = newNetwork.IP + "/32" } // find LinkSpeed from lookup if throughput := f.linkSpeed(); throughput > 0 { - node.Attributes["network.throughput"] = fmt.Sprintf("%dMB/s", throughput) newNetwork.MBits = throughput } @@ -182,13 +190,7 @@ func isAWS() bool { return true } -// EnvAWSFingerprint uses lookup table to approximate network speeds based on -// http://serverfault.com/questions/324883/aws-bandwidth-and-content-delivery/326797#326797 -// which itself cites these sources: -// - http://blog.rightscale.com/2007/10/28/network-performance-within-amazon-ec2-and-to-amazon-s3/ -// - http://www.soc.napier.ac.uk/~bill/chris_p.pdf -// -// This data is meant for a loose approximation +// EnvAWSFingerprint uses lookup table to approximate network speeds func (f *EnvAWSFingerprint) linkSpeed() int { // Query the API for the instance type, and use the table above to approximate diff --git a/client/fingerprint/env_aws_test.go b/client/fingerprint/env_aws_test.go index 6c858b7ab..1ea002062 100644 --- a/client/fingerprint/env_aws_test.go +++ b/client/fingerprint/env_aws_test.go @@ -69,7 +69,6 @@ func TestEnvAWSFingerprint_aws(t *testing.T) { "platform.aws.public-hostname", "platform.aws.public-ipv4", "platform.aws.placement.availability-zone", - "network.throughput", "network.ip-address", "network.internal-ip", } @@ -181,7 +180,6 @@ func TestNetworkFingerprint_AWS(t *testing.T) { t.Fatalf("should apply") } - assertNodeAttributeContains(t, node, "network.throughput") assertNodeAttributeContains(t, node, "network.ip-address") if node.Resources == nil || len(node.Resources.Networks) == 0 { @@ -191,7 +189,13 @@ func TestNetworkFingerprint_AWS(t *testing.T) { // Test at least the first Network Resource net := node.Resources.Networks[0] if net.IP == "" { - t.Fatal("Expected Network Resource to not be empty") + t.Fatal("Expected Network Resource to have an IP") + } + if net.CIDR == "" { + t.Fatal("Expected Network Resource to have a CIDR") + } + if net.Device == "" { + t.Fatal("Expected Network Resource to have a Device Name") } } diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index dae94e2fc..b928dde64 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -2,7 +2,6 @@ package fingerprint import ( "net" - "runtime" "testing" "github.com/hashicorp/nomad/client/config" @@ -23,11 +22,6 @@ func TestNetworkFingerprint_basic(t *testing.T) { t.Fatalf("should apply") } - // Darwin uses en0 for the default device, and does not have a standard - // location for the linkspeed file, so we skip these - if "darwin" != runtime.GOOS { - assertNodeAttributeContains(t, node, "network.throughput") - } assertNodeAttributeContains(t, node, "network.ip-address") ip := node.Attributes["network.ip-address"] @@ -45,4 +39,10 @@ func TestNetworkFingerprint_basic(t *testing.T) { if net.IP == "" { t.Fatal("Expected Network Resource to not be empty") } + if net.CIDR == "" { + t.Fatal("Expected Network Resource to have a CIDR") + } + if net.Device == "" { + t.Fatal("Expected Network Resource to have a Device Name") + } } diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index d6646cb39..e803149e5 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -37,13 +37,16 @@ func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.N if "darwin" == runtime.GOOS { defaultDevice = "en0" } + + newNetwork.Device = defaultDevice + if ip := f.ifConfig(defaultDevice); ip != "" { node.Attributes["network.ip-address"] = ip newNetwork.IP = ip + newNetwork.CIDR = newNetwork.IP + "/32" } if throughput := f.linkSpeed("eth0"); throughput > 0 { - node.Attributes["network.throughput"] = fmt.Sprintf("%dMB/s", throughput) newNetwork.MBits = throughput } @@ -104,7 +107,7 @@ func (f *UnixNetworkFingerprint) linkSpeedSys(device string) int { // Convert to MB/s if mbs > 0 { - return mbs / 8 + return mbs } } return 0 From bbe4ee2f6c9e24b127cabe516679d60a84061fef Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Tue, 22 Sep 2015 23:57:24 -0500 Subject: [PATCH 13/21] don't re-convert mbits --- client/fingerprint/network_unix.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index e803149e5..f4a97db00 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -105,7 +105,6 @@ func (f *UnixNetworkFingerprint) linkSpeedSys(device string) int { return 0 } - // Convert to MB/s if mbs > 0 { return mbs } @@ -140,9 +139,8 @@ func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) int { return 0 } - // Convert to MB/s if mbs > 0 { - return mbs / 8 + return mbs } } f.logger.Printf("error calling ethtool (%s): %s", path, err) From dc8ce6d0ffd73471e1c3c8bc115c3f4a17d659a9 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 23 Sep 2015 10:00:06 -0500 Subject: [PATCH 14/21] fix casing for ERR logs --- client/fingerprint/env_aws.go | 4 ++-- client/fingerprint/network_unix.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/client/fingerprint/env_aws.go b/client/fingerprint/env_aws.go index 35704688a..dc8a00de9 100644 --- a/client/fingerprint/env_aws.go +++ b/client/fingerprint/env_aws.go @@ -171,14 +171,14 @@ func isAWS() bool { resp, err := client.Get(metadataURL + "ami-id") if err != nil { - log.Printf("[Err] Error querying AWS Metadata URL, skipping") + log.Printf("[ERR] Error querying AWS Metadata URL, skipping") return false } defer resp.Body.Close() instanceID, err := ioutil.ReadAll(resp.Body) if err != nil { - log.Printf("[Err] Error reading AWS Instance ID, skipping") + log.Printf("[ERR] Error reading AWS Instance ID, skipping") return false } diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index f4a97db00..81bbdf499 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -182,7 +182,7 @@ func (f *UnixNetworkFingerprint) ifConfig(device string) string { return ip } } - f.logger.Printf("[Err] Error calling ifconfig (%s): %s", ifConfigPath, err) + f.logger.Printf("[ERR] Error calling ifconfig (%s): %s", ifConfigPath, err) return "" } From 2897780800b4257ff3610daefdc2113648867794 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 23 Sep 2015 10:03:35 -0500 Subject: [PATCH 15/21] prefix warn/error messages --- client/fingerprint/env_aws.go | 4 ++-- client/fingerprint/network_unix.go | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/fingerprint/env_aws.go b/client/fingerprint/env_aws.go index dc8a00de9..5c8f4426b 100644 --- a/client/fingerprint/env_aws.go +++ b/client/fingerprint/env_aws.go @@ -171,14 +171,14 @@ func isAWS() bool { resp, err := client.Get(metadataURL + "ami-id") if err != nil { - log.Printf("[ERR] Error querying AWS Metadata URL, skipping") + log.Printf("[ERR] fingerprint.env_aws: Error querying AWS Metadata URL, skipping") return false } defer resp.Body.Close() instanceID, err := ioutil.ReadAll(resp.Body) if err != nil { - log.Printf("[ERR] Error reading AWS Instance ID, skipping") + log.Printf("[ERR] fingerprint.env_aws: Error reading AWS Instance ID, skipping") return false } diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index 81bbdf499..c1cf95e46 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -77,7 +77,7 @@ func (f *UnixNetworkFingerprint) linkSpeed(device string) int { return speed } } - f.logger.Printf("[WARN] Ethtool not found, checking /sys/net speed file") + f.logger.Printf("[WARN] fingerprint.network_aws: Ethtool not found, checking /sys/net speed file") // Fall back on checking a system file for link speed. return f.linkSpeedSys(device) @@ -101,7 +101,7 @@ func (f *UnixNetworkFingerprint) linkSpeedSys(device string) int { // convert to MB/s mbs, err := strconv.Atoi(lines[0]) if err != nil { - f.logger.Println("[WARN] Unable to parse ethtool output") + f.logger.Println("[WARN] fingerprint.network_aws: Enable to parse ethtool output") return 0 } @@ -124,7 +124,7 @@ func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) int { m := re.FindString(output) if m == "" { // no matches found, output may be in a different format - f.logger.Println("[WARN] Ethtool output did not match regex") + f.logger.Println("[WARN] fingerprint.network_aws: Ethtool output did not match regex") return 0 } @@ -135,7 +135,7 @@ func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) int { // convert to MB/s mbs, err := strconv.Atoi(raw) if err != nil { - f.logger.Println("[WARN] Unable to parse ethtool output") + f.logger.Println("[WARN] fingerprint.network_aws: Unable to parse ethtool output") return 0 } @@ -143,7 +143,7 @@ func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) int { return mbs } } - f.logger.Printf("error calling ethtool (%s): %s", path, err) + f.logger.Printf("[ERR] fingerprint.network_aws: Error calling ethtool (%s): %s", path, err) return 0 } @@ -182,10 +182,10 @@ func (f *UnixNetworkFingerprint) ifConfig(device string) string { return ip } } - f.logger.Printf("[ERR] Error calling ifconfig (%s): %s", ifConfigPath, err) + f.logger.Printf("[ERR] fingerprint.network_aws: Error calling ifconfig (%s): %s", ifConfigPath, err) return "" } - f.logger.Println("[WARN] Ethtool not found") + f.logger.Println("[WARN] fingerprint.network_aws: Ethtool not found") return "" } From 0a334a267da405ec55e0b16303efbc7998905c14 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 23 Sep 2015 10:12:56 -0500 Subject: [PATCH 16/21] Refactor network fingerprinting to be generic, use build flags --- client/fingerprint/fingerprint.go | 16 ++++++++-------- client/fingerprint/network_test.go | 2 +- client/fingerprint/network_unix.go | 21 +++++++++++---------- client/fingerprint/network_windows.go | 26 ++++++++++++++++++++++++++ 4 files changed, 46 insertions(+), 19 deletions(-) create mode 100644 client/fingerprint/network_windows.go diff --git a/client/fingerprint/fingerprint.go b/client/fingerprint/fingerprint.go index 3e8d91c97..ce69a8ac9 100644 --- a/client/fingerprint/fingerprint.go +++ b/client/fingerprint/fingerprint.go @@ -16,20 +16,20 @@ var BuiltinFingerprints = []string{ "host", "memory", "storage", - "network_unix", + "network", "env_aws", } // builtinFingerprintMap contains the built in registered fingerprints // which are available, corresponding to a key found in BuiltinFingerprints var builtinFingerprintMap = map[string]Factory{ - "arch": NewArchFingerprint, - "cpu": NewCPUFingerprint, - "host": NewHostFingerprint, - "memory": NewMemoryFingerprint, - "storage": NewStorageFingerprint, - "network_unix": NewUnixNetworkFingerprinter, - "env_aws": NewEnvAWSFingerprint, + "arch": NewArchFingerprint, + "cpu": NewCPUFingerprint, + "host": NewHostFingerprint, + "memory": NewMemoryFingerprint, + "storage": NewStorageFingerprint, + "network": NewNetworkFingerprinter, + "env_aws": NewEnvAWSFingerprint, } // NewFingerprint is used to instantiate and return a new fingerprint diff --git a/client/fingerprint/network_test.go b/client/fingerprint/network_test.go index b928dde64..142ad13ee 100644 --- a/client/fingerprint/network_test.go +++ b/client/fingerprint/network_test.go @@ -9,7 +9,7 @@ import ( ) func TestNetworkFingerprint_basic(t *testing.T) { - f := NewUnixNetworkFingerprinter(testLogger()) + f := NewNetworkFingerprinter(testLogger()) node := &structs.Node{ Attributes: make(map[string]string), } diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index c1cf95e46..c6e84f598 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -17,18 +17,19 @@ import ( "github.com/hashicorp/nomad/nomad/structs" ) -// UnixNetworkFingerprint is used to fingerprint the Network capabilities of a node -type UnixNetworkFingerprint struct { +// NetworkFingerprint is used to fingerprint the Network capabilities of a node +type NetworkFingerprint struct { logger *log.Logger } -// NewNetworkFingerprint is used to create a CPU fingerprint -func NewUnixNetworkFingerprinter(logger *log.Logger) Fingerprint { - f := &UnixNetworkFingerprint{logger: logger} +// NewNetworkFingerprint returns a new NetworkFingerprinter with the given +// logger +func NewNetworkFingerprinter(logger *log.Logger) Fingerprint { + f := &NetworkFingerprint{logger: logger} return f } -func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { +func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { // newNetwork is populated and addded to the Nodes resources newNetwork := &structs.NetworkResource{} @@ -67,7 +68,7 @@ func (f *UnixNetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.N // The return value is in the format of "MB/s" // // LinkSpeed returns an empty string if no tools or sys file are found -func (f *UnixNetworkFingerprint) linkSpeed(device string) int { +func (f *NetworkFingerprint) linkSpeed(device string) int { // Use LookPath to find the ethtool in the systems $PATH // If it's not found or otherwise errors, LookPath returns and empty string // and an error we can ignore for our purposes @@ -86,7 +87,7 @@ func (f *UnixNetworkFingerprint) linkSpeed(device string) int { // linkSpeedSys parses the information stored in the sys diretory for the // default device. This method retuns an empty string if the file is not found // or cannot be read -func (f *UnixNetworkFingerprint) linkSpeedSys(device string) int { +func (f *NetworkFingerprint) linkSpeedSys(device string) int { path := fmt.Sprintf("/sys/class/net/%s/speed", device) _, err := os.Stat(path) if err != nil { @@ -116,7 +117,7 @@ func (f *UnixNetworkFingerprint) linkSpeedSys(device string) int { // information. It executes the command on the device specified and parses // out the speed. The expected format is Mbps and converted to MB/s // Returns an empty string there is an error in parsing or executing ethtool -func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) int { +func (f *NetworkFingerprint) linkSpeedEthtool(path, device string) int { outBytes, err := exec.Command(path, device).Output() if err == nil { output := strings.TrimSpace(string(outBytes)) @@ -149,7 +150,7 @@ func (f *UnixNetworkFingerprint) linkSpeedEthtool(path, device string) int { // ifConfig returns the IP Address for this node according to ifConfig, for the // specified device. -func (f *UnixNetworkFingerprint) ifConfig(device string) string { +func (f *NetworkFingerprint) ifConfig(device string) string { ifConfigPath, _ := exec.LookPath("ifconfig") if ifConfigPath != "" { outBytes, err := exec.Command(ifConfigPath, device).Output() diff --git a/client/fingerprint/network_windows.go b/client/fingerprint/network_windows.go new file mode 100644 index 000000000..866fc102a --- /dev/null +++ b/client/fingerprint/network_windows.go @@ -0,0 +1,26 @@ +// +build windows +package fingerprint + +import ( + "log" + + "github.com/hashicorp/nomad/client/config" + "github.com/hashicorp/nomad/nomad/structs" +) + +// NetworkFingerprint is used to fingerprint the Network capabilities of a node +type NetworkFingerprint struct { + logger *log.Logger +} + +// NewNetworkFingerprint returns a new NetworkFingerprinter with the given +// logger +func NewNetworkFingerprinter(logger *log.Logger) Fingerprint { + f := &NetworkFingerprint{logger: logger} + return f +} + +func (f *NetworkFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) (bool, error) { + // return false, because we don't yet support Windows + return false, nil +} From 237ab9e747f4b9db2841813b2372ab990886f77b Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 23 Sep 2015 11:44:17 -0500 Subject: [PATCH 17/21] some Java driver docs --- website/source/docs/drivers/java.html.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/website/source/docs/drivers/java.html.md b/website/source/docs/drivers/java.html.md index d430a6563..e10b000fd 100644 --- a/website/source/docs/drivers/java.html.md +++ b/website/source/docs/drivers/java.html.md @@ -11,7 +11,7 @@ description: |- Name: `java` The `Java` driver is used to execute Java applications packaged into a Java Jar -file. The driver currently requires the Jar file be accessbile via +file. The driver currently requires the Jar file be accessible via HTTP from the Nomad client. ## Task Configuration @@ -21,7 +21,7 @@ The `java` driver supports the following configuration in the job spec: * `jar_source` - **(Required)** The hosted location of the source Jar file. Must be accessible from the Nomad client, via HTTP -* `args` - (Optional) The argument list for the `java` command, space seperated. +* `args` - (Optional) The argument list for the `java` command, space separated. ## Client Requirements @@ -30,14 +30,15 @@ The `jar_source` must be accessible by the node running Nomad. This can be an internal source, private to your cluster, but it must be reachable by the client over HTTP. -The resource isolation primitives vary by OS. - ## Client Attributes The `java` driver will set the following client attributes: * `driver.java` - This will always be set to "1", indicating the driver is available. +* `driver.java.version` - Version of Java, ex: `1.6.0_65` +* `driver.java.runtime` - Runtime version, ex: `Java(TM) SE Runtime Environment (build 1.6.0_65-b14-466.1-11M4716)` +* `driver.java.vm` - Virtual Machine information, ex: `Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-466.1, mixed mode)` ## Resource Isolation @@ -48,6 +49,6 @@ On Linux, Nomad will attempt to use cgroups, namespaces, and chroot to isolate the resources of a process. If the Nomad agent is not running as root many of these mechanisms cannot be used. -As a baseline, the task driver will just execute the command -with no additional resource isolation if none are available. +As a baseline, the Java jars will be ran inside a Java Virtual Machine, +providing a minimum amount of isolation. From 92bfe163da48bf46669b3b5ab569bb6ae54aae6c Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 23 Sep 2015 13:58:42 -0500 Subject: [PATCH 18/21] start the Qemu docs --- client/driver/qemu_test.go | 2 +- website/source/docs/drivers/qemu.html.md | 55 +++++++++++++++++++++++- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/client/driver/qemu_test.go b/client/driver/qemu_test.go index 23a5cf206..a86475be3 100644 --- a/client/driver/qemu_test.go +++ b/client/driver/qemu_test.go @@ -53,10 +53,10 @@ func TestQemuDriver_Start(t *testing.T) { task := &structs.Task{ Config: map[string]string{ "image_source": "https://dl.dropboxusercontent.com/u/47675/jar_thing/linux-0.2.img", + "checksum": "a5e836985934c3392cbbd9b26db55a7d35a8d7ae1deb7ca559dd9c0159572544", "accelerator": "tcg", "host_port": "8080", "guest_port": "8081", - "checksum": "a5e836985934c3392cbbd9b26db55a7d35a8d7ae1deb7ca559dd9c0159572544", // ssh u/p would be here }, } diff --git a/website/source/docs/drivers/qemu.html.md b/website/source/docs/drivers/qemu.html.md index f617179f5..a34b09b31 100644 --- a/website/source/docs/drivers/qemu.html.md +++ b/website/source/docs/drivers/qemu.html.md @@ -10,5 +10,58 @@ description: |- Name: `qemu` -TODO +The `Qemu` driver provides a generic virtual machine runner. Qemu can utilize +the KVM kernel module to utilize hardware virtualization features and provide +great performance. Currently the `Qemu` driver can map a set of ports from the +host machine to the guest virtual machine, and provides configuration for +resource allocation. + +The `Qemu` driver can execute any regular `qemu` image (e.g. `qcow`, `img`, +`iso`), and is currently invoked with `qemu-system-x86_64`. + +## Task Configuration + +The `Qemu` driver supports the following configuration in the job spec: + +* `image_source` - **(Required)** The hosted location of the source Qemu image. Must be accessible +from the Nomad client, via HTTP. +* `checksum` - **(Required)** The MD5 checksum of the `qemu` image. If the +checksums do not match, the `Qemu` diver will fail to start the image +* `accelerator` - (Optional) The type of accelerator to use in the invocation. +Default is `tcg`. If the host machine has `Qemu` installed with KVM support, +users can specify `kvm` for the `accelerator` +* `host_port` - **(Required)** The hosted location of the source Jar file. Must be accessible +from the Nomad client, via HTTP +* `guest_port` - **(Required)** The hosted location of the source Jar file. Must be accessible +from the Nomad client, via HTTP + +## Client Requirements + +The `java` driver requires Java to be installed and in your systems `$PATH`. +The `jar_source` must be accessible by the node running Nomad. This can be an +internal source, private to your cluster, but it must be reachable by the client +over HTTP. + +## Client Attributes + +The `java` driver will set the following client attributes: + +* `driver.java` - This will always be set to "1", indicating the + driver is available. +* `driver.java.version` - Version of Java, ex: `1.6.0_65` +* `driver.java.runtime` - Runtime version, ex: `Java(TM) SE Runtime Environment (build 1.6.0_65-b14-466.1-11M4716)` +* `driver.java.vm` - Virtual Machine information, ex: `Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-466.1, mixed mode)` + +## Resource Isolation + +The resource isolation provided varies by the operating system of +the client and the configuration. + +On Linux, Nomad will attempt to use cgroups, namespaces, and chroot +to isolate the resources of a process. If the Nomad agent is not +running as root many of these mechanisms cannot be used. + +As a baseline, the Java jars will be ran inside a Java Virtual Machine, +providing a minimum amount of isolation. + From 3624ebdcd074e2ac8ecc0fde33d717543cb1d6a4 Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 23 Sep 2015 14:09:55 -0500 Subject: [PATCH 19/21] clean up some log formatting --- client/fingerprint/env_aws.go | 4 ++-- client/fingerprint/network_unix.go | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/fingerprint/env_aws.go b/client/fingerprint/env_aws.go index 5c8f4426b..0bf92410c 100644 --- a/client/fingerprint/env_aws.go +++ b/client/fingerprint/env_aws.go @@ -121,7 +121,7 @@ func (f *EnvAWSFingerprint) Fingerprint(cfg *config.Config, node *structs.Node) resp, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { - log.Fatal(err) + f.logger.Printf("[ERR]: fingerprint.env_aws: Error reading response body for AWS %s", k) } // assume we want blank entries @@ -209,7 +209,7 @@ func (f *EnvAWSFingerprint) linkSpeed() int { body, err := ioutil.ReadAll(res.Body) res.Body.Close() if err != nil { - log.Fatal(err) + f.logger.Printf("[ERR]: fingerprint.env_aws: Error reading response body for instance-type") return 0 } diff --git a/client/fingerprint/network_unix.go b/client/fingerprint/network_unix.go index c6e84f598..f2b17d3c0 100644 --- a/client/fingerprint/network_unix.go +++ b/client/fingerprint/network_unix.go @@ -78,7 +78,7 @@ func (f *NetworkFingerprint) linkSpeed(device string) int { return speed } } - f.logger.Printf("[WARN] fingerprint.network_aws: Ethtool not found, checking /sys/net speed file") + f.logger.Printf("[WARN] fingerprint.network: Ethtool not found, checking /sys/net speed file") // Fall back on checking a system file for link speed. return f.linkSpeedSys(device) @@ -91,7 +91,7 @@ func (f *NetworkFingerprint) linkSpeedSys(device string) int { path := fmt.Sprintf("/sys/class/net/%s/speed", device) _, err := os.Stat(path) if err != nil { - log.Printf("[WARN] Error getting information about net speed") + f.logger.Printf("[WARN] fingerprint.network: Error getting information about net speed") return 0 } @@ -102,7 +102,7 @@ func (f *NetworkFingerprint) linkSpeedSys(device string) int { // convert to MB/s mbs, err := strconv.Atoi(lines[0]) if err != nil { - f.logger.Println("[WARN] fingerprint.network_aws: Enable to parse ethtool output") + f.logger.Println("[WARN] fingerprint.network: Enable to parse ethtool output") return 0 } @@ -125,7 +125,7 @@ func (f *NetworkFingerprint) linkSpeedEthtool(path, device string) int { m := re.FindString(output) if m == "" { // no matches found, output may be in a different format - f.logger.Println("[WARN] fingerprint.network_aws: Ethtool output did not match regex") + f.logger.Println("[WARN] fingerprint.network: Ethtool output did not match regex") return 0 } @@ -136,7 +136,7 @@ func (f *NetworkFingerprint) linkSpeedEthtool(path, device string) int { // convert to MB/s mbs, err := strconv.Atoi(raw) if err != nil { - f.logger.Println("[WARN] fingerprint.network_aws: Unable to parse ethtool output") + f.logger.Println("[WARN] fingerprint.network: Unable to parse ethtool output") return 0 } @@ -144,7 +144,7 @@ func (f *NetworkFingerprint) linkSpeedEthtool(path, device string) int { return mbs } } - f.logger.Printf("[ERR] fingerprint.network_aws: Error calling ethtool (%s): %s", path, err) + f.logger.Printf("[ERR] fingerprint.network: Error calling ethtool (%s): %s", path, err) return 0 } @@ -183,10 +183,10 @@ func (f *NetworkFingerprint) ifConfig(device string) string { return ip } } - f.logger.Printf("[ERR] fingerprint.network_aws: Error calling ifconfig (%s): %s", ifConfigPath, err) + f.logger.Printf("[ERR] fingerprint.network: Error calling ifconfig (%s): %s", ifConfigPath, err) return "" } - f.logger.Println("[WARN] fingerprint.network_aws: Ethtool not found") + f.logger.Println("[WARN] fingerprint.network: Ethtool not found") return "" } From 9fb704e6862a508fd05b27bd6a159e56fdb193ef Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 23 Sep 2015 14:16:43 -0500 Subject: [PATCH 20/21] Update Java docs on how Java is detected --- client/driver/java.go | 1 - website/source/docs/drivers/java.html.md | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/client/driver/java.go b/client/driver/java.go index 31960512e..bd0667a77 100644 --- a/client/driver/java.go +++ b/client/driver/java.go @@ -77,7 +77,6 @@ func (d *JavaDriver) Fingerprint(cfg *config.Config, node *structs.Node) (bool, // OpenJDK Runtime Environment (IcedTea6 1.13.8) (6b36-1.13.8-0ubuntu1~12.04) // OpenJDK 64-Bit Server VM (build 23.25-b01, mixed mode) // Each line is terminated by \n - info := strings.Split(infoString, "\n") versionString := info[0] versionString = strings.TrimPrefix(versionString, "java version ") diff --git a/website/source/docs/drivers/java.html.md b/website/source/docs/drivers/java.html.md index e10b000fd..11018965e 100644 --- a/website/source/docs/drivers/java.html.md +++ b/website/source/docs/drivers/java.html.md @@ -34,8 +34,8 @@ over HTTP. The `java` driver will set the following client attributes: -* `driver.java` - This will always be set to "1", indicating the - driver is available. +* `driver.java` - Set to `1` if Java is found on the host node. Nomad determines +this by executing `java -version` on the host and parsing the output * `driver.java.version` - Version of Java, ex: `1.6.0_65` * `driver.java.runtime` - Runtime version, ex: `Java(TM) SE Runtime Environment (build 1.6.0_65-b14-466.1-11M4716)` * `driver.java.vm` - Virtual Machine information, ex: `Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-466.1, mixed mode)` From 7580a0d3a03ddae709234bd455efde81987de18c Mon Sep 17 00:00:00 2001 From: Clint Shryock Date: Wed, 23 Sep 2015 14:44:49 -0500 Subject: [PATCH 21/21] Basic Qemu docs --- client/driver/qemu_test.go | 1 - website/source/docs/drivers/qemu.html.md | 30 ++++++++++-------------- 2 files changed, 13 insertions(+), 18 deletions(-) diff --git a/client/driver/qemu_test.go b/client/driver/qemu_test.go index a86475be3..0acde6a39 100644 --- a/client/driver/qemu_test.go +++ b/client/driver/qemu_test.go @@ -57,7 +57,6 @@ func TestQemuDriver_Start(t *testing.T) { "accelerator": "tcg", "host_port": "8080", "guest_port": "8081", - // ssh u/p would be here }, } diff --git a/website/source/docs/drivers/qemu.html.md b/website/source/docs/drivers/qemu.html.md index a34b09b31..4fbb1eb97 100644 --- a/website/source/docs/drivers/qemu.html.md +++ b/website/source/docs/drivers/qemu.html.md @@ -28,29 +28,26 @@ from the Nomad client, via HTTP. * `checksum` - **(Required)** The MD5 checksum of the `qemu` image. If the checksums do not match, the `Qemu` diver will fail to start the image * `accelerator` - (Optional) The type of accelerator to use in the invocation. -Default is `tcg`. If the host machine has `Qemu` installed with KVM support, -users can specify `kvm` for the `accelerator` -* `host_port` - **(Required)** The hosted location of the source Jar file. Must be accessible -from the Nomad client, via HTTP -* `guest_port` - **(Required)** The hosted location of the source Jar file. Must be accessible -from the Nomad client, via HTTP + If the host machine has `Qemu` installed with KVM support, users can specify `kvm` for the `accelerator`. Default is `tcg` +* `host_port` - **(Required)** Port on the host machine to forward to the guest +VM +* `guest_port` - **(Required)** Port on the guest machine that is listening for +traffic from the host ## Client Requirements -The `java` driver requires Java to be installed and in your systems `$PATH`. -The `jar_source` must be accessible by the node running Nomad. This can be an +The `Qemu` driver requires Qemu to be installed and in your systems `$PATH`. +The `image_source` must be accessible by the node running Nomad. This can be an internal source, private to your cluster, but it must be reachable by the client over HTTP. ## Client Attributes -The `java` driver will set the following client attributes: +The `Qemu` driver will set the following client attributes: -* `driver.java` - This will always be set to "1", indicating the - driver is available. -* `driver.java.version` - Version of Java, ex: `1.6.0_65` -* `driver.java.runtime` - Runtime version, ex: `Java(TM) SE Runtime Environment (build 1.6.0_65-b14-466.1-11M4716)` -* `driver.java.vm` - Virtual Machine information, ex: `Java HotSpot(TM) 64-Bit Server VM (build 20.65-b04-466.1, mixed mode)` +* `driver.qemu` - Set to `1` if Qemu is found on the host node. Nomad determines +this by executing `qemu-system-x86_64 -version` on the host and parsing the output +* `driver.qemu.version` - Version of `qemu-system-x86_64, ex: `2.4.0` ## Resource Isolation @@ -61,7 +58,6 @@ On Linux, Nomad will attempt to use cgroups, namespaces, and chroot to isolate the resources of a process. If the Nomad agent is not running as root many of these mechanisms cannot be used. -As a baseline, the Java jars will be ran inside a Java Virtual Machine, -providing a minimum amount of isolation. - +As a baseline, the Qemu images will be ran inside a virtual machine operated by +Qemu, providing a minimum amount of isolation.