github.com/adityamillind98/nomad@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 }