Have server store unique attrs in glob format

This commit is contained in:
Alex Dadgar 2016-01-21 11:25:14 -08:00
parent d646903a47
commit ccb932a9f0
3 changed files with 54 additions and 84 deletions

View File

@ -4,13 +4,14 @@ import (
"fmt"
"strings"
"github.com/gobwas/glob"
"github.com/mitchellh/hashstructure"
)
const (
// A suffix that can be appended to node meta keys to mark them for
// exclusion in computed node class.
NodeMetaUnique = "_unique"
// NodeUniqueSuffix is a suffix that can be appended to node meta or
// attribute keys to mark them for exclusion in computed node class.
NodeUniqueSuffix = "_unique"
)
// ComputeClass computes a derived class for the node based on its attributes.
@ -18,7 +19,6 @@ const (
// attributes and capabilities. Thus, when calculating a node's computed class
// we avoid including any uniquely identifing fields.
func (n *Node) ComputeClass() error {
// TODO: Bucket node resources such as DiskMB/IOPS/etc.
hash, err := hashstructure.Hash(n, nil)
if err != nil {
return err
@ -40,13 +40,45 @@ func (n Node) HashInclude(field string, v interface{}) (bool, error) {
return false, nil
case "CreateIndex", "ModifyIndex": // Raft indexes
return false, nil
case "Reserved": // Doesn't effect placement capability
case "Resources", "Reserved": // Doesn't effect placement capability
return false, nil
default:
return true, nil
}
}
var (
// GlobalUniqueAttrs is a set of attributes that uniquely identify all
// nodes. It is stored once by the server, rather than by each node to
// reduce storage costs.
GlobalUniqueAttrs = []glob.Glob{
glob.MustCompile("consul.name"),
glob.MustCompile("platform.gce.hostname"),
glob.MustCompile("platform.gce.id"),
glob.MustCompile("platform.gce.network.*.ip"),
glob.MustCompile("platform.gce.network.*.external-ip"),
glob.MustCompile("platform.aws.ami-id"),
glob.MustCompile("platform.aws.hostname"),
glob.MustCompile("platform.aws.instance-id"),
glob.MustCompile("platform.aws.local*"),
glob.MustCompile("platform.aws.public*"),
glob.MustCompile("network.ip-address"),
glob.MustCompile("storage.*"), // Ignore all storage
}
)
// excludeAttr returns whether the key should be excluded when calculating
// computed node class.
func excludeAttr(key string) bool {
for _, g := range GlobalUniqueAttrs {
if g.Match(key) {
return true
}
}
return false
}
// HashIncludeMap is used to blacklist uniquely identifying node map keys from being
// included in the computed node class.
func (n Node) HashIncludeMap(field string, k, v interface{}) (bool, error) {
@ -55,28 +87,15 @@ func (n Node) HashIncludeMap(field string, k, v interface{}) (bool, error) {
return false, fmt.Errorf("map key %v not a string")
}
// Check if the user marked the key as unique.
isUnique := strings.HasSuffix(key, NodeUniqueSuffix)
switch field {
case "Attributes":
// Check if the key is marked as unique by the fingerprinters.
_, unique := n.UniqueAttributes[key]
return !unique, nil
return !excludeAttr(key) && !isUnique, nil
case "Meta":
// Check if the user marked the key as unique.
return !strings.HasSuffix(key, NodeMetaUnique), nil
return !isUnique, nil
default:
return false, fmt.Errorf("unexpected map field: %v", field)
}
}
// HashInclude is used to blacklist uniquely identifying network fields from being
// included in the computed node class.
func (n NetworkResource) HashInclude(field string, v interface{}) (bool, error) {
switch field {
case "IP", "CIDR": // Uniquely identifying
return false, nil
case "ReservedPorts", "DynamicPorts": // Doesn't effect placement capability
return false, nil
default:
return true, nil
}
}

View File

@ -1,6 +1,7 @@
package structs
import (
"fmt"
"testing"
)
@ -15,7 +16,6 @@ func testNode() *Node {
"version": "0.1.0",
"driver.exec": "1",
},
UniqueAttributes: make(map[string]struct{}),
Resources: &Resources{
CPU: 4000,
MemoryMB: 8192,
@ -29,19 +29,6 @@ func testNode() *Node {
},
},
},
Reserved: &Resources{
CPU: 100,
MemoryMB: 256,
DiskMB: 4 * 1024,
Networks: []*NetworkResource{
&NetworkResource{
Device: "eth0",
IP: "192.168.0.100",
ReservedPorts: []Port{{Label: "main", Value: 22}},
MBits: 1,
},
},
},
Links: map[string]string{
"consul": "foobar.dc1",
},
@ -111,46 +98,6 @@ func TestNode_ComputedClass_Ignore(t *testing.T) {
}
}
func TestNode_ComputedClass_NetworkResources(t *testing.T) {
// Create a node with a few network resources and gets it computed class
nr1 := &NetworkResource{
Device: "eth0",
CIDR: "192.168.0.100/32",
MBits: 1000,
}
nr2 := &NetworkResource{
Device: "eth1",
CIDR: "192.168.0.100/32",
MBits: 500,
}
n := &Node{
Resources: &Resources{
Networks: []*NetworkResource{nr1, nr2},
},
}
if err := n.ComputeClass(); err != nil {
t.Fatalf("ComputeClass() failed: %v", err)
}
if n.ComputedClass == 0 {
t.Fatal("ComputeClass() didn't set computed class")
}
old := n.ComputedClass
// Change the order of the network resources and compute the class again.
n.Resources.Networks = []*NetworkResource{nr2, nr1}
if err := n.ComputeClass(); err != nil {
t.Fatalf("ComputeClass() failed: %v", err)
}
if n.ComputedClass == 0 {
t.Fatal("ComputeClass() didn't set computed class")
}
if old != n.ComputedClass {
t.Fatal("ComputeClass() didn't ignore NetworkResource order")
}
}
func TestNode_ComputedClass_Attr(t *testing.T) {
// Create a node and gets it computed class
n := testNode()
@ -162,6 +109,15 @@ func TestNode_ComputedClass_Attr(t *testing.T) {
}
old := n.ComputedClass
// Add a unique addr and compute the class again
n.Attributes[fmt.Sprintf("%s%s", "foo", NodeUniqueSuffix)] = "bar"
if err := n.ComputeClass(); err != nil {
t.Fatalf("ComputeClass() failed: %v", err)
}
if old != n.ComputedClass {
t.Fatal("ComputeClass() didn't ignore unique attr suffix")
}
// Modify an attribute and compute the class again.
n.Attributes["version"] = "New Version"
if err := n.ComputeClass(); err != nil {
@ -176,9 +132,7 @@ func TestNode_ComputedClass_Attr(t *testing.T) {
old = n.ComputedClass
// Add an ignored attribute and compute the class again.
key := "ignore"
n.Attributes[key] = "hello world"
n.UniqueAttributes[key] = struct{}{}
n.Attributes["network.ip-address"] = "hello world"
if err := n.ComputeClass(); err != nil {
t.Fatalf("ComputeClass() failed: %v", err)
}

View File

@ -476,9 +476,6 @@ type Node struct {
// "docker.runtime=1.8.3"
Attributes map[string]string
// UniqueAttributes are attributes that uniquely identify a node.
UniqueAttributes map[string]struct{}
// Resources is the available resources on the client.
// For example 'cpu=2' 'memory=2048'
Resources *Resources
@ -570,7 +567,7 @@ type Resources struct {
MemoryMB int `mapstructure:"memory"`
DiskMB int `mapstructure:"disk"`
IOPS int
Networks []*NetworkResource `hash:"set"`
Networks []*NetworkResource
}
// Copy returns a deep copy of the resources