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 }