From b310a54aa6ff5ddedc561e81c4b300ef3d9df886 Mon Sep 17 00:00:00 2001 From: Alex Dadgar Date: Sat, 29 Sep 2018 17:23:41 -0700 Subject: [PATCH] Node resources on client --- api/nodes.go | 20 ++++ client/client.go | 9 ++ client/fingerprint/cpu.go | 7 ++ client/fingerprint/cpu_test.go | 9 ++ client/fingerprint/memory.go | 8 ++ client/fingerprint/memory_test.go | 7 ++ client/fingerprint/network.go | 6 + client/fingerprint/storage.go | 6 + client/fingerprint/storage_test.go | 5 + client/structs/structs.go | 7 +- nomad/structs/structs.go | 184 +++++++++++++++++++++++++---- 11 files changed, 245 insertions(+), 23 deletions(-) diff --git a/api/nodes.go b/api/nodes.go index 6184f6cd7..b7f8fa953 100644 --- a/api/nodes.go +++ b/api/nodes.go @@ -446,6 +446,7 @@ type Node struct { Attributes map[string]string Resources *Resources Reserved *Resources + NodeResources *NodeResources Links map[string]string Meta map[string]string NodeClass string @@ -461,6 +462,25 @@ type Node struct { ModifyIndex uint64 } +type NodeResources struct { + Cpu NodeCpuResources + Memory NodeMemoryResources + Disk NodeDiskResources + Networks []*NetworkResource +} + +type NodeCpuResources struct { + TotalShares uint64 +} + +type NodeMemoryResources struct { + MemoryMB uint64 +} + +type NodeDiskResources struct { + DiskMB uint64 +} + // DrainStrategy describes a Node's drain behavior. type DrainStrategy struct { // DrainSpec is the user declared drain specification diff --git a/client/client.go b/client/client.go index 79f50ac01..5f974fd3b 100644 --- a/client/client.go +++ b/client/client.go @@ -938,6 +938,9 @@ func (c *Client) setupNode() error { if node.Meta == nil { node.Meta = make(map[string]string) } + if node.NodeResources == nil { + node.NodeResources = &structs.NodeResources{} + } if node.Resources == nil { node.Resources = &structs.Resources{} } @@ -1042,11 +1045,17 @@ func (c *Client) updateNodeFromFingerprint(response *cstructs.FingerprintRespons } } + // COMPAT(0.10): Remove in 0.10 if response.Resources != nil && !resourcesAreEqual(c.config.Node.Resources, response.Resources) { nodeHasChanged = true c.config.Node.Resources.Merge(response.Resources) } + if response.NodeResources != nil && !c.config.Node.NodeResources.Equals(response.NodeResources) { + nodeHasChanged = true + c.config.Node.NodeResources.Merge(response.NodeResources) + } + if nodeHasChanged { c.updateNodeLocked() } diff --git a/client/fingerprint/cpu.go b/client/fingerprint/cpu.go index 434eb5844..3a87572e5 100644 --- a/client/fingerprint/cpu.go +++ b/client/fingerprint/cpu.go @@ -24,9 +24,16 @@ func NewCPUFingerprint(logger *log.Logger) Fingerprint { func (f *CPUFingerprint) Fingerprint(req *cstructs.FingerprintRequest, resp *cstructs.FingerprintResponse) error { cfg := req.Config setResourcesCPU := func(totalCompute int) { + // COMPAT(0.10): Remove in 0.10 resp.Resources = &structs.Resources{ CPU: totalCompute, } + + resp.NodeResources = &structs.NodeResources{ + Cpu: structs.NodeCpuResources{ + TotalShares: uint64(totalCompute), + }, + } } if err := stats.Init(); err != nil { diff --git a/client/fingerprint/cpu_test.go b/client/fingerprint/cpu_test.go index 07dd64d3d..94f32b491 100644 --- a/client/fingerprint/cpu_test.go +++ b/client/fingerprint/cpu_test.go @@ -45,9 +45,14 @@ func TestCPUFingerprint(t *testing.T) { t.Fatalf("Missing CPU Total Compute") } + // COMPAT(0.10): Remove in 0.10 if response.Resources == nil || response.Resources.CPU == 0 { t.Fatalf("Expected to find CPU Resources") } + + if response.NodeResources == nil || response.NodeResources.Cpu.TotalShares == 0 { + t.Fatalf("Expected to find CPU Resources") + } } // TestCPUFingerprint_OverrideCompute asserts that setting cpu_total_compute in @@ -91,8 +96,12 @@ func TestCPUFingerprint_OverrideCompute(t *testing.T) { t.Fatalf("err: %v", err) } + // COMPAT(0.10): Remove in 0.10 if response.Resources.CPU != cfg.CpuCompute { t.Fatalf("expected override cpu of %d but found %d", cfg.CpuCompute, response.Resources.CPU) } + if response.NodeResources.Cpu.TotalShares != uint64(cfg.CpuCompute) { + t.Fatalf("expected override cpu of %d but found %d", cfg.CpuCompute, response.NodeResources.Cpu.TotalShares) + } } } diff --git a/client/fingerprint/memory.go b/client/fingerprint/memory.go index 7f2bff2f4..d90725ce6 100644 --- a/client/fingerprint/memory.go +++ b/client/fingerprint/memory.go @@ -43,9 +43,17 @@ func (f *MemoryFingerprint) Fingerprint(req *cstructs.FingerprintRequest, resp * if totalMemory > 0 { resp.AddAttribute("memory.totalbytes", fmt.Sprintf("%d", totalMemory)) + + // COMPAT(0.10): Remove in 0.10 resp.Resources = &structs.Resources{ MemoryMB: totalMemory / bytesInMB, } + + resp.NodeResources = &structs.NodeResources{ + Memory: structs.NodeMemoryResources{ + MemoryMB: uint64(totalMemory / bytesInMB), + }, + } } return nil diff --git a/client/fingerprint/memory_test.go b/client/fingerprint/memory_test.go index 24365cebc..e01502205 100644 --- a/client/fingerprint/memory_test.go +++ b/client/fingerprint/memory_test.go @@ -29,9 +29,15 @@ func TestMemoryFingerprint(t *testing.T) { if response.Resources == nil { t.Fatalf("response resources should not be nil") } + + // COMPAT(0.10): Remove in 0.10 if response.Resources.MemoryMB == 0 { t.Fatalf("Expected node.Resources.MemoryMB to be non-zero") } + + if response.NodeResources.Memory.MemoryMB == 0 { + t.Fatalf("Expected node.Resources.MemoryMB to be non-zero") + } } func TestMemoryFingerprint_Override(t *testing.T) { @@ -52,4 +58,5 @@ func TestMemoryFingerprint_Override(t *testing.T) { require := require.New(t) require.NotNil(response.Resources) require.Equal(response.Resources.MemoryMB, memoryMB) + require.EqualValues(response.NodeResources.Memory.MemoryMB, memoryMB) } diff --git a/client/fingerprint/network.go b/client/fingerprint/network.go index 9634a7969..b83599b9c 100644 --- a/client/fingerprint/network.go +++ b/client/fingerprint/network.go @@ -95,9 +95,15 @@ func (f *NetworkFingerprint) Fingerprint(req *cstructs.FingerprintRequest, resp return err } + // COMPAT(0.10): Remove in 0.10 resp.Resources = &structs.Resources{ Networks: nwResources, } + + resp.NodeResources = &structs.NodeResources{ + Networks: nwResources, + } + for _, nwResource := range nwResources { f.logger.Printf("[DEBUG] fingerprint.network: Detected interface %v with IP: %v", intf.Name, nwResource.IP) } diff --git a/client/fingerprint/storage.go b/client/fingerprint/storage.go index 6dc72fb6d..38eddbd35 100644 --- a/client/fingerprint/storage.go +++ b/client/fingerprint/storage.go @@ -47,9 +47,15 @@ func (f *StorageFingerprint) Fingerprint(req *cstructs.FingerprintRequest, resp resp.AddAttribute("unique.storage.bytesfree", strconv.FormatUint(free, 10)) // set the disk size for the response + // COMPAT(0.10): Remove in 0.10 resp.Resources = &structs.Resources{ DiskMB: int(free / bytesPerMegabyte), } + resp.NodeResources = &structs.NodeResources{ + Disk: structs.NodeDiskResources{ + DiskMB: uint64(free / bytesPerMegabyte), + }, + } resp.Detected = true return nil diff --git a/client/fingerprint/storage_test.go b/client/fingerprint/storage_test.go index 509521bb3..0b7a47d60 100644 --- a/client/fingerprint/storage_test.go +++ b/client/fingerprint/storage_test.go @@ -37,10 +37,15 @@ func TestStorageFingerprint(t *testing.T) { t.Fatalf("unique.storage.bytesfree %d is larger than unique.storage.bytestotal %d", free, total) } + // COMPAT(0.10): Remove in 0.10 if response.Resources == nil { t.Fatalf("Node Resources was nil") } if response.Resources.DiskMB == 0 { t.Errorf("Expected node.Resources.DiskMB to be non-zero") } + + if response.NodeResources == nil || response.NodeResources.Disk.DiskMB == 0 { + t.Errorf("Expected node.Resources.DiskMB to be non-zero") + } } diff --git a/client/structs/structs.go b/client/structs/structs.go index af28b55ca..fcdcac225 100644 --- a/client/structs/structs.go +++ b/client/structs/structs.go @@ -354,9 +354,10 @@ type FingerprintRequest struct { // FingerprintResponse is the response which a fingerprinter annotates with the // results of the fingerprint method type FingerprintResponse struct { - Attributes map[string]string - Links map[string]string - Resources *structs.Resources + Attributes map[string]string + Links map[string]string + Resources *structs.Resources + NodeResources *structs.NodeResources // Detected is a boolean indicating whether the fingerprinter detected // if the resource was available diff --git a/nomad/structs/structs.go b/nomad/structs/structs.go index e4980edd2..376650d16 100644 --- a/nomad/structs/structs.go +++ b/nomad/structs/structs.go @@ -1425,6 +1425,9 @@ type Node struct { // "docker.runtime=1.8.3" Attributes map[string]string + // NodeResources captures the available resources on the client. + NodeResources *NodeResources + // Resources is the available resources on the client. // For example 'cpu=2' 'memory=2048' Resources *Resources @@ -1609,26 +1612,6 @@ type NodeListStub struct { ModifyIndex uint64 } -// Networks defined for a task on the Resources struct. -type Networks []*NetworkResource - -// Port assignment and IP for the given label or empty values. -func (ns Networks) Port(label string) (string, int) { - for _, n := range ns { - for _, p := range n.ReservedPorts { - if p.Label == label { - return n.IP, p.Value - } - } - for _, p := range n.DynamicPorts { - if p.Label == label { - return n.IP, p.Value - } - } - } - return "", 0 -} - // Resources is used to define the resources available // on a client type Resources struct { @@ -1927,6 +1910,167 @@ func (n *NetworkResource) PortLabels() map[string]int { return labelValues } +// Networks defined for a task on the Resources struct. +type Networks []*NetworkResource + +// Port assignment and IP for the given label or empty values. +func (ns Networks) Port(label string) (string, int) { + for _, n := range ns { + for _, p := range n.ReservedPorts { + if p.Label == label { + return n.IP, p.Value + } + } + for _, p := range n.DynamicPorts { + if p.Label == label { + return n.IP, p.Value + } + } + } + return "", 0 +} + +type NodeResources struct { + Cpu NodeCpuResources + Memory NodeMemoryResources + Disk NodeDiskResources + Networks Networks +} + +func (n *NodeResources) Merge(o *NodeResources) { + if o == nil { + return + } + + n.Cpu.Merge(&o.Cpu) + n.Memory.Merge(&o.Memory) + n.Disk.Merge(&o.Disk) + + if len(o.Networks) != 0 { + n.Networks = o.Networks + } +} + +func (n *NodeResources) Equals(o *NodeResources) bool { + if o == nil && n == nil { + return true + } else if o == nil { + return false + } else if n == nil { + return false + } + + if !n.Cpu.Equals(&o.Cpu) { + return false + } + if !n.Memory.Equals(&o.Memory) { + return false + } + if !n.Disk.Equals(&o.Disk) { + return false + } + + if len(n.Networks) != len(o.Networks) { + return false + } + for i, n := range n.Networks { + if !n.Equals(o.Networks[i]) { + return false + } + } + + return true +} + +type NodeCpuResources struct { + TotalShares uint64 +} + +func (n *NodeCpuResources) Merge(o *NodeCpuResources) { + if o == nil { + return + } + + if o.TotalShares != 0 { + n.TotalShares = o.TotalShares + } +} + +func (n *NodeCpuResources) Equals(o *NodeCpuResources) bool { + if o == nil && n == nil { + return true + } else if o == nil { + return false + } else if n == nil { + return false + } + + if n.TotalShares != o.TotalShares { + return false + } + + return true +} + +type NodeMemoryResources struct { + MemoryMB uint64 +} + +func (n *NodeMemoryResources) Merge(o *NodeMemoryResources) { + if o == nil { + return + } + + if o.MemoryMB != 0 { + n.MemoryMB = o.MemoryMB + } +} + +func (n *NodeMemoryResources) Equals(o *NodeMemoryResources) bool { + if o == nil && n == nil { + return true + } else if o == nil { + return false + } else if n == nil { + return false + } + + if n.MemoryMB != o.MemoryMB { + return false + } + + return true +} + +type NodeDiskResources struct { + DiskMB uint64 +} + +func (n *NodeDiskResources) Merge(o *NodeDiskResources) { + if o == nil { + return + } + if o.DiskMB != 0 { + n.DiskMB = o.DiskMB + } +} + +func (n *NodeDiskResources) Equals(o *NodeDiskResources) bool { + if o == nil && n == nil { + return true + } else if o == nil { + return false + } else if n == nil { + return false + } + + if n.DiskMB != o.DiskMB { + return false + } + + return true +} + const ( // JobTypeNomad is reserved for internal system tasks and is // always handled by the CoreScheduler.