github.com/zoomfoo/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/nomad/util.go (about)

     1  package nomad
     2  
     3  import (
     4  	"fmt"
     5  	"math/rand"
     6  	"net"
     7  	"os"
     8  	"path/filepath"
     9  	"strconv"
    10  
    11  	version "github.com/hashicorp/go-version"
    12  	"github.com/hashicorp/nomad/nomad/state"
    13  	"github.com/hashicorp/nomad/nomad/structs"
    14  	"github.com/hashicorp/serf/serf"
    15  )
    16  
    17  // ensurePath is used to make sure a path exists
    18  func ensurePath(path string, dir bool) error {
    19  	if !dir {
    20  		path = filepath.Dir(path)
    21  	}
    22  	return os.MkdirAll(path, 0755)
    23  }
    24  
    25  // serverParts is used to return the parts of a server role
    26  type serverParts struct {
    27  	Name         string
    28  	ID           string
    29  	Region       string
    30  	Datacenter   string
    31  	Port         int
    32  	Bootstrap    bool
    33  	Expect       int
    34  	MajorVersion int
    35  	MinorVersion int
    36  	Build        version.Version
    37  	RaftVersion  int
    38  	Addr         net.Addr
    39  	RPCAddr      net.Addr
    40  	Status       serf.MemberStatus
    41  }
    42  
    43  func (s *serverParts) String() string {
    44  	return fmt.Sprintf("%s (Addr: %s) (DC: %s)",
    45  		s.Name, s.Addr, s.Datacenter)
    46  }
    47  
    48  func (s *serverParts) Copy() *serverParts {
    49  	ns := new(serverParts)
    50  	*ns = *s
    51  	return ns
    52  }
    53  
    54  // Returns if a member is a Nomad server. Returns a boolean,
    55  // and a struct with the various important components
    56  func isNomadServer(m serf.Member) (bool, *serverParts) {
    57  	if m.Tags["role"] != "nomad" {
    58  		return false, nil
    59  	}
    60  
    61  	id := "unknown"
    62  	if v, ok := m.Tags["id"]; ok {
    63  		id = v
    64  	}
    65  	region := m.Tags["region"]
    66  	datacenter := m.Tags["dc"]
    67  	_, bootstrap := m.Tags["bootstrap"]
    68  
    69  	expect := 0
    70  	expectStr, ok := m.Tags["expect"]
    71  	var err error
    72  	if ok {
    73  		expect, err = strconv.Atoi(expectStr)
    74  		if err != nil {
    75  			return false, nil
    76  		}
    77  	}
    78  
    79  	// If the server is missing the rpc_addr tag, default to the serf advertise addr
    80  	rpcIP := net.ParseIP(m.Tags["rpc_addr"])
    81  	if rpcIP == nil {
    82  		rpcIP = m.Addr
    83  	}
    84  
    85  	portStr := m.Tags["port"]
    86  	port, err := strconv.Atoi(portStr)
    87  	if err != nil {
    88  		return false, nil
    89  	}
    90  
    91  	buildVersion, err := version.NewVersion(m.Tags["build"])
    92  	if err != nil {
    93  		return false, nil
    94  	}
    95  
    96  	// The "vsn" tag was Version, which is now the MajorVersion number.
    97  	majorVersionStr := m.Tags["vsn"]
    98  	majorVersion, err := strconv.Atoi(majorVersionStr)
    99  	if err != nil {
   100  		return false, nil
   101  	}
   102  
   103  	// To keep some semblance of convention, "mvn" is now the "Minor
   104  	// Version Number."
   105  	minorVersionStr := m.Tags["mvn"]
   106  	minorVersion, err := strconv.Atoi(minorVersionStr)
   107  	if err != nil {
   108  		minorVersion = 0
   109  	}
   110  
   111  	raftVsn := 0
   112  	raftVsnString, ok := m.Tags["raft_vsn"]
   113  	if ok {
   114  		raftVsn, err = strconv.Atoi(raftVsnString)
   115  		if err != nil {
   116  			return false, nil
   117  		}
   118  	}
   119  
   120  	addr := &net.TCPAddr{IP: m.Addr, Port: port}
   121  	rpcAddr := &net.TCPAddr{IP: rpcIP, Port: port}
   122  	parts := &serverParts{
   123  		Name:         m.Name,
   124  		ID:           id,
   125  		Region:       region,
   126  		Datacenter:   datacenter,
   127  		Port:         port,
   128  		Bootstrap:    bootstrap,
   129  		Expect:       expect,
   130  		Addr:         addr,
   131  		RPCAddr:      rpcAddr,
   132  		MajorVersion: majorVersion,
   133  		MinorVersion: minorVersion,
   134  		Build:        *buildVersion,
   135  		RaftVersion:  raftVsn,
   136  		Status:       m.Status,
   137  	}
   138  	return true, parts
   139  }
   140  
   141  // ServersMeetMinimumVersion returns whether the given alive servers are at least on the
   142  // given Nomad version
   143  func ServersMeetMinimumVersion(members []serf.Member, minVersion *version.Version) bool {
   144  	for _, member := range members {
   145  		if valid, parts := isNomadServer(member); valid && parts.Status == serf.StatusAlive {
   146  			// Check if the versions match - version.LessThan will return true for
   147  			// 0.8.0-rc1 < 0.8.0, so we want to ignore the metadata
   148  			versionsMatch := slicesMatch(minVersion.Segments(), parts.Build.Segments())
   149  			if parts.Build.LessThan(minVersion) && !versionsMatch {
   150  				return false
   151  			}
   152  		}
   153  	}
   154  
   155  	return true
   156  }
   157  
   158  func slicesMatch(a, b []int) bool {
   159  	if a == nil && b == nil {
   160  		return true
   161  	}
   162  
   163  	if a == nil || b == nil {
   164  		return false
   165  	}
   166  
   167  	if len(a) != len(b) {
   168  		return false
   169  	}
   170  
   171  	for i := range a {
   172  		if a[i] != b[i] {
   173  			return false
   174  		}
   175  	}
   176  
   177  	return true
   178  }
   179  
   180  // shuffleStrings randomly shuffles the list of strings
   181  func shuffleStrings(list []string) {
   182  	for i := range list {
   183  		j := rand.Intn(i + 1)
   184  		list[i], list[j] = list[j], list[i]
   185  	}
   186  }
   187  
   188  // maxUint64 returns the maximum value
   189  func maxUint64(inputs ...uint64) uint64 {
   190  	l := len(inputs)
   191  	if l == 0 {
   192  		return 0
   193  	} else if l == 1 {
   194  		return inputs[0]
   195  	}
   196  
   197  	max := inputs[0]
   198  	for i := 1; i < l; i++ {
   199  		cur := inputs[i]
   200  		if cur > max {
   201  			max = cur
   202  		}
   203  	}
   204  	return max
   205  }
   206  
   207  // getNodeForRpc returns a Node struct if the Node supports Node RPC. Otherwise
   208  // an error is returned.
   209  func getNodeForRpc(snap *state.StateSnapshot, nodeID string) (*structs.Node, error) {
   210  	node, err := snap.NodeByID(nil, nodeID)
   211  	if err != nil {
   212  		return nil, err
   213  	}
   214  
   215  	if node == nil {
   216  		return nil, fmt.Errorf("Unknown node %q", nodeID)
   217  	}
   218  
   219  	if err := nodeSupportsRpc(node); err != nil {
   220  		return nil, err
   221  	}
   222  
   223  	return node, nil
   224  }
   225  
   226  var minNodeVersionSupportingRPC = version.Must(version.NewVersion("0.8.0-rc1"))
   227  
   228  // nodeSupportsRpc returns a non-nil error if a Node does not support RPC.
   229  func nodeSupportsRpc(node *structs.Node) error {
   230  	rawNodeVer, ok := node.Attributes["nomad.version"]
   231  	if !ok {
   232  		return structs.ErrUnknownNomadVersion
   233  	}
   234  
   235  	nodeVer, err := version.NewVersion(rawNodeVer)
   236  	if err != nil {
   237  		return structs.ErrUnknownNomadVersion
   238  	}
   239  
   240  	if nodeVer.LessThan(minNodeVersionSupportingRPC) {
   241  		return structs.ErrNodeLacksRpc
   242  	}
   243  
   244  	return nil
   245  }