gopkg.in/hashicorp/nomad.v0@v0.11.8/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  	memdb "github.com/hashicorp/go-memdb"
    12  	version "github.com/hashicorp/go-version"
    13  	"github.com/hashicorp/nomad/nomad/state"
    14  	"github.com/hashicorp/nomad/nomad/structs"
    15  	"github.com/hashicorp/serf/serf"
    16  )
    17  
    18  // MinVersionPlanNormalization is the minimum version to support the
    19  // normalization of Plan in SubmitPlan, and the denormalization raft log entry committed
    20  // in ApplyPlanResultsRequest
    21  var MinVersionPlanNormalization = version.Must(version.NewVersion("0.9.2"))
    22  
    23  // ensurePath is used to make sure a path exists
    24  func ensurePath(path string, dir bool) error {
    25  	if !dir {
    26  		path = filepath.Dir(path)
    27  	}
    28  	return os.MkdirAll(path, 0755)
    29  }
    30  
    31  // serverParts is used to return the parts of a server role
    32  type serverParts struct {
    33  	Name         string
    34  	ID           string
    35  	Region       string
    36  	Datacenter   string
    37  	Port         int
    38  	Bootstrap    bool
    39  	Expect       int
    40  	MajorVersion int
    41  	MinorVersion int
    42  	Build        version.Version
    43  	RaftVersion  int
    44  	Addr         net.Addr
    45  	RPCAddr      net.Addr
    46  	Status       serf.MemberStatus
    47  	NonVoter     bool
    48  }
    49  
    50  func (s *serverParts) String() string {
    51  	return fmt.Sprintf("%s (Addr: %s) (DC: %s)",
    52  		s.Name, s.Addr, s.Datacenter)
    53  }
    54  
    55  func (s *serverParts) Copy() *serverParts {
    56  	ns := new(serverParts)
    57  	*ns = *s
    58  	return ns
    59  }
    60  
    61  // Returns if a member is a Nomad server. Returns a boolean,
    62  // and a struct with the various important components
    63  func isNomadServer(m serf.Member) (bool, *serverParts) {
    64  	if m.Tags["role"] != "nomad" {
    65  		return false, nil
    66  	}
    67  
    68  	id := "unknown"
    69  	if v, ok := m.Tags["id"]; ok {
    70  		id = v
    71  	}
    72  	region := m.Tags["region"]
    73  	datacenter := m.Tags["dc"]
    74  	_, bootstrap := m.Tags["bootstrap"]
    75  
    76  	expect := 0
    77  	expectStr, ok := m.Tags["expect"]
    78  	var err error
    79  	if ok {
    80  		expect, err = strconv.Atoi(expectStr)
    81  		if err != nil {
    82  			return false, nil
    83  		}
    84  	}
    85  
    86  	// If the server is missing the rpc_addr tag, default to the serf advertise addr
    87  	rpcIP := net.ParseIP(m.Tags["rpc_addr"])
    88  	if rpcIP == nil {
    89  		rpcIP = m.Addr
    90  	}
    91  
    92  	portStr := m.Tags["port"]
    93  	port, err := strconv.Atoi(portStr)
    94  	if err != nil {
    95  		return false, nil
    96  	}
    97  
    98  	buildVersion, err := version.NewVersion(m.Tags["build"])
    99  	if err != nil {
   100  		return false, nil
   101  	}
   102  
   103  	// The "vsn" tag was Version, which is now the MajorVersion number.
   104  	majorVersionStr := m.Tags["vsn"]
   105  	majorVersion, err := strconv.Atoi(majorVersionStr)
   106  	if err != nil {
   107  		return false, nil
   108  	}
   109  
   110  	// To keep some semblance of convention, "mvn" is now the "Minor
   111  	// Version Number."
   112  	minorVersionStr := m.Tags["mvn"]
   113  	minorVersion, err := strconv.Atoi(minorVersionStr)
   114  	if err != nil {
   115  		minorVersion = 0
   116  	}
   117  
   118  	raftVsn := 0
   119  	raftVsnString, ok := m.Tags["raft_vsn"]
   120  	if ok {
   121  		raftVsn, err = strconv.Atoi(raftVsnString)
   122  		if err != nil {
   123  			return false, nil
   124  		}
   125  	}
   126  
   127  	// Check if the server is a non voter
   128  	_, nonVoter := m.Tags["nonvoter"]
   129  
   130  	addr := &net.TCPAddr{IP: m.Addr, Port: port}
   131  	rpcAddr := &net.TCPAddr{IP: rpcIP, Port: port}
   132  	parts := &serverParts{
   133  		Name:         m.Name,
   134  		ID:           id,
   135  		Region:       region,
   136  		Datacenter:   datacenter,
   137  		Port:         port,
   138  		Bootstrap:    bootstrap,
   139  		Expect:       expect,
   140  		Addr:         addr,
   141  		RPCAddr:      rpcAddr,
   142  		MajorVersion: majorVersion,
   143  		MinorVersion: minorVersion,
   144  		Build:        *buildVersion,
   145  		RaftVersion:  raftVsn,
   146  		Status:       m.Status,
   147  		NonVoter:     nonVoter,
   148  	}
   149  	return true, parts
   150  }
   151  
   152  // ServersMeetMinimumVersion returns whether the Nomad servers are at least on the
   153  // given Nomad version. The checkFailedServers parameter specifies whether version
   154  // for the failed servers should be verified.
   155  func ServersMeetMinimumVersion(members []serf.Member, minVersion *version.Version, checkFailedServers bool) bool {
   156  	for _, member := range members {
   157  		if valid, parts := isNomadServer(member); valid && (parts.Status == serf.StatusAlive || (checkFailedServers && parts.Status == serf.StatusFailed)) {
   158  			// Check if the versions match - version.LessThan will return true for
   159  			// 0.8.0-rc1 < 0.8.0, so we want to ignore the metadata
   160  			versionsMatch := slicesMatch(minVersion.Segments(), parts.Build.Segments())
   161  			if parts.Build.LessThan(minVersion) && !versionsMatch {
   162  				return false
   163  			}
   164  		}
   165  	}
   166  
   167  	return true
   168  }
   169  
   170  func slicesMatch(a, b []int) bool {
   171  	if a == nil && b == nil {
   172  		return true
   173  	}
   174  
   175  	if a == nil || b == nil {
   176  		return false
   177  	}
   178  
   179  	if len(a) != len(b) {
   180  		return false
   181  	}
   182  
   183  	for i := range a {
   184  		if a[i] != b[i] {
   185  			return false
   186  		}
   187  	}
   188  
   189  	return true
   190  }
   191  
   192  // shuffleStrings randomly shuffles the list of strings
   193  func shuffleStrings(list []string) {
   194  	for i := range list {
   195  		j := rand.Intn(i + 1)
   196  		list[i], list[j] = list[j], list[i]
   197  	}
   198  }
   199  
   200  // partitionAll splits a slice of strings into a slice of slices of strings, each with a max
   201  // size of `size`. All entries from the original slice are preserved. The last slice may be
   202  // smaller than `size`. The input slice is unmodified
   203  func partitionAll(size int, xs []string) [][]string {
   204  	if size < 1 {
   205  		return [][]string{xs}
   206  	}
   207  
   208  	out := [][]string{}
   209  
   210  	for i := 0; i < len(xs); i += size {
   211  		j := i + size
   212  		if j > len(xs) {
   213  			j = len(xs)
   214  		}
   215  		out = append(out, xs[i:j])
   216  	}
   217  
   218  	return out
   219  }
   220  
   221  // maxUint64 returns the maximum value
   222  func maxUint64(inputs ...uint64) uint64 {
   223  	l := len(inputs)
   224  	if l == 0 {
   225  		return 0
   226  	} else if l == 1 {
   227  		return inputs[0]
   228  	}
   229  
   230  	max := inputs[0]
   231  	for i := 1; i < l; i++ {
   232  		cur := inputs[i]
   233  		if cur > max {
   234  			max = cur
   235  		}
   236  	}
   237  	return max
   238  }
   239  
   240  // getNodeForRpc returns a Node struct if the Node supports Node RPC. Otherwise
   241  // an error is returned.
   242  func getNodeForRpc(snap *state.StateSnapshot, nodeID string) (*structs.Node, error) {
   243  	node, err := snap.NodeByID(nil, nodeID)
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	if node == nil {
   249  		return nil, fmt.Errorf("Unknown node %q", nodeID)
   250  	}
   251  
   252  	if err := nodeSupportsRpc(node); err != nil {
   253  		return nil, err
   254  	}
   255  
   256  	return node, nil
   257  }
   258  
   259  var minNodeVersionSupportingRPC = version.Must(version.NewVersion("0.8.0-rc1"))
   260  
   261  // nodeSupportsRpc returns a non-nil error if a Node does not support RPC.
   262  func nodeSupportsRpc(node *structs.Node) error {
   263  	rawNodeVer, ok := node.Attributes["nomad.version"]
   264  	if !ok {
   265  		return structs.ErrUnknownNomadVersion
   266  	}
   267  
   268  	nodeVer, err := version.NewVersion(rawNodeVer)
   269  	if err != nil {
   270  		return structs.ErrUnknownNomadVersion
   271  	}
   272  
   273  	if nodeVer.LessThan(minNodeVersionSupportingRPC) {
   274  		return structs.ErrNodeLacksRpc
   275  	}
   276  
   277  	return nil
   278  }
   279  
   280  // AllocGetter is an interface for retrieving allocations by ID. It is
   281  // satisfied by *state.StateStore and *state.StateSnapshot.
   282  type AllocGetter interface {
   283  	AllocByID(ws memdb.WatchSet, id string) (*structs.Allocation, error)
   284  }
   285  
   286  // getAlloc retrieves an allocation by ID and namespace. If the allocation is
   287  // nil, an error is returned.
   288  func getAlloc(state AllocGetter, allocID string) (*structs.Allocation, error) {
   289  	if allocID == "" {
   290  		return nil, structs.ErrMissingAllocID
   291  	}
   292  
   293  	alloc, err := state.AllocByID(nil, allocID)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  
   298  	if alloc == nil {
   299  		return nil, structs.NewErrUnknownAllocation(allocID)
   300  	}
   301  
   302  	return alloc, nil
   303  }