github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/structs/node.go (about) 1 package structs 2 3 import ( 4 "reflect" 5 "time" 6 7 "golang.org/x/exp/maps" 8 ) 9 10 // CSITopology is a map of topological domains to topological segments. 11 // A topological domain is a sub-division of a cluster, like "region", 12 // "zone", "rack", etc. 13 // 14 // According to CSI, there are a few requirements for the keys within this map: 15 // - Valid keys have two segments: an OPTIONAL prefix and name, separated 16 // by a slash (/), for example: "com.company.example/zone". 17 // - The key name segment is REQUIRED. The prefix is OPTIONAL. 18 // - The key name MUST be 63 characters or less, begin and end with an 19 // alphanumeric character ([a-z0-9A-Z]), and contain only dashes (-), 20 // underscores (_), dots (.), or alphanumerics in between, for example 21 // "zone". 22 // - The key prefix MUST be 63 characters or less, begin and end with a 23 // lower-case alphanumeric character ([a-z0-9]), contain only 24 // dashes (-), dots (.), or lower-case alphanumerics in between, and 25 // follow domain name notation format 26 // (https://tools.ietf.org/html/rfc1035#section-2.3.1). 27 // - The key prefix SHOULD include the plugin's host company name and/or 28 // the plugin name, to minimize the possibility of collisions with keys 29 // from other plugins. 30 // - If a key prefix is specified, it MUST be identical across all 31 // topology keys returned by the SP (across all RPCs). 32 // - Keys MUST be case-insensitive. Meaning the keys "Zone" and "zone" 33 // MUST not both exist. 34 // - Each value (topological segment) MUST contain 1 or more strings. 35 // - Each string MUST be 63 characters or less and begin and end with an 36 // alphanumeric character with '-', '_', '.', or alphanumerics in 37 // between. 38 // 39 // However, Nomad applies lighter restrictions to these, as they are already 40 // only referenced by plugin within the scheduler and as such collisions and 41 // related concerns are less of an issue. We may implement these restrictions 42 // in the future. 43 type CSITopology struct { 44 Segments map[string]string 45 } 46 47 func (t *CSITopology) Copy() *CSITopology { 48 if t == nil { 49 return nil 50 } 51 52 return &CSITopology{ 53 Segments: maps.Clone(t.Segments), 54 } 55 } 56 57 func (t *CSITopology) Equal(o *CSITopology) bool { 58 if t == nil || o == nil { 59 return t == o 60 } 61 return maps.Equal(t.Segments, o.Segments) 62 } 63 64 func (t *CSITopology) MatchFound(o []*CSITopology) bool { 65 if t == nil || o == nil || len(o) == 0 { 66 return false 67 } 68 69 for _, other := range o { 70 if t.Equal(other) { 71 return true 72 } 73 } 74 return false 75 } 76 77 // CSITopologyRequest are the topologies submitted as options to the 78 // storage provider at the time the volume was created. The storage 79 // provider will return a single topology. 80 type CSITopologyRequest struct { 81 Required []*CSITopology 82 Preferred []*CSITopology 83 } 84 85 func (tr *CSITopologyRequest) Equal(o *CSITopologyRequest) bool { 86 if tr == nil && o == nil { 87 return true 88 } 89 if tr == nil && o != nil || tr != nil && o == nil { 90 return false 91 } 92 if len(tr.Required) != len(o.Required) || len(tr.Preferred) != len(o.Preferred) { 93 return false 94 } 95 for i, topo := range tr.Required { 96 if !topo.Equal(o.Required[i]) { 97 return false 98 } 99 } 100 for i, topo := range tr.Preferred { 101 if !topo.Equal(o.Preferred[i]) { 102 return false 103 } 104 } 105 return true 106 } 107 108 // CSINodeInfo is the fingerprinted data from a CSI Plugin that is specific to 109 // the Node API. 110 type CSINodeInfo struct { 111 // ID is the identity of a given nomad client as observed by the storage 112 // provider. 113 ID string 114 115 // MaxVolumes is the maximum number of volumes that can be attached to the 116 // current host via this provider. 117 // If 0 then unlimited volumes may be attached. 118 MaxVolumes int64 119 120 // AccessibleTopology specifies where (regions, zones, racks, etc.) the node is 121 // accessible from within the storage provider. 122 // 123 // A plugin that returns this field MUST also set the `RequiresTopologies` 124 // property. 125 // 126 // This field is OPTIONAL. If it is not specified, then we assume that the 127 // the node is not subject to any topological constraint, and MAY 128 // schedule workloads that reference any volume V, such that there are 129 // no topological constraints declared for V. 130 // 131 // Example 1: 132 // accessible_topology = 133 // {"region": "R1", "zone": "Z2"} 134 // Indicates the node exists within the "region" "R1" and the "zone" 135 // "Z2" within the storage provider. 136 AccessibleTopology *CSITopology 137 138 // RequiresNodeStageVolume indicates whether the client should Stage/Unstage 139 // volumes on this node. 140 RequiresNodeStageVolume bool 141 142 // SupportsStats indicates plugin support for GET_VOLUME_STATS 143 SupportsStats bool 144 145 // SupportsExpand indicates plugin support for EXPAND_VOLUME 146 SupportsExpand bool 147 148 // SupportsCondition indicates plugin support for VOLUME_CONDITION 149 SupportsCondition bool 150 } 151 152 func (n *CSINodeInfo) Copy() *CSINodeInfo { 153 if n == nil { 154 return nil 155 } 156 157 nc := new(CSINodeInfo) 158 *nc = *n 159 nc.AccessibleTopology = n.AccessibleTopology.Copy() 160 161 return nc 162 } 163 164 // CSIControllerInfo is the fingerprinted data from a CSI Plugin that is specific to 165 // the Controller API. 166 type CSIControllerInfo struct { 167 168 // SupportsCreateDelete indicates plugin support for CREATE_DELETE_VOLUME 169 SupportsCreateDelete bool 170 171 // SupportsPublishVolume is true when the controller implements the 172 // methods required to attach and detach volumes. If this is false Nomad 173 // should skip the controller attachment flow. 174 SupportsAttachDetach bool 175 176 // SupportsListVolumes is true when the controller implements the 177 // ListVolumes RPC. NOTE: This does not guarantee that attached nodes will 178 // be returned unless SupportsListVolumesAttachedNodes is also true. 179 SupportsListVolumes bool 180 181 // SupportsGetCapacity indicates plugin support for GET_CAPACITY 182 SupportsGetCapacity bool 183 184 // SupportsCreateDeleteSnapshot indicates plugin support for 185 // CREATE_DELETE_SNAPSHOT 186 SupportsCreateDeleteSnapshot bool 187 188 // SupportsListSnapshots indicates plugin support for LIST_SNAPSHOTS 189 SupportsListSnapshots bool 190 191 // SupportsClone indicates plugin support for CLONE_VOLUME 192 SupportsClone bool 193 194 // SupportsReadOnlyAttach is set to true when the controller returns the 195 // ATTACH_READONLY capability. 196 SupportsReadOnlyAttach bool 197 198 // SupportsExpand indicates plugin support for EXPAND_VOLUME 199 SupportsExpand bool 200 201 // SupportsListVolumesAttachedNodes indicates whether the plugin will 202 // return attached nodes data when making ListVolume RPCs (plugin support 203 // for LIST_VOLUMES_PUBLISHED_NODES) 204 SupportsListVolumesAttachedNodes bool 205 206 // SupportsCondition indicates plugin support for VOLUME_CONDITION 207 SupportsCondition bool 208 209 // SupportsGet indicates plugin support for GET_VOLUME 210 SupportsGet bool 211 } 212 213 func (c *CSIControllerInfo) Copy() *CSIControllerInfo { 214 if c == nil { 215 return nil 216 } 217 218 nc := new(CSIControllerInfo) 219 *nc = *c 220 221 return nc 222 } 223 224 // CSIInfo is the current state of a single CSI Plugin. This is updated regularly 225 // as plugin health changes on the node. 226 type CSIInfo struct { 227 PluginID string 228 AllocID string 229 Healthy bool 230 HealthDescription string 231 UpdateTime time.Time 232 233 Provider string // vendor name from CSI GetPluginInfoResponse 234 ProviderVersion string // vendor version from CSI GetPluginInfoResponse 235 236 // RequiresControllerPlugin is set when the CSI Plugin returns the 237 // CONTROLLER_SERVICE capability. When this is true, the volumes should not be 238 // scheduled on this client until a matching controller plugin is available. 239 RequiresControllerPlugin bool 240 241 // RequiresTopologies is set when the CSI Plugin returns the 242 // VOLUME_ACCESSIBLE_CONSTRAINTS capability. When this is true, we must 243 // respect the Volume and Node Topology information. 244 RequiresTopologies bool 245 246 // CSI Specific metadata 247 ControllerInfo *CSIControllerInfo `json:",omitempty"` 248 NodeInfo *CSINodeInfo `json:",omitempty"` 249 } 250 251 func (c *CSIInfo) Copy() *CSIInfo { 252 if c == nil { 253 return nil 254 } 255 256 nc := new(CSIInfo) 257 *nc = *c 258 nc.ControllerInfo = c.ControllerInfo.Copy() 259 nc.NodeInfo = c.NodeInfo.Copy() 260 261 return nc 262 } 263 264 func (c *CSIInfo) SetHealthy(hs bool) { 265 c.Healthy = hs 266 if hs { 267 c.HealthDescription = "healthy" 268 } else { 269 c.HealthDescription = "unhealthy" 270 } 271 } 272 273 func (c *CSIInfo) Equal(o *CSIInfo) bool { 274 if c == nil && o == nil { 275 return c == o 276 } 277 278 nc := *c 279 nc.UpdateTime = time.Time{} 280 no := *o 281 no.UpdateTime = time.Time{} 282 283 return reflect.DeepEqual(nc, no) 284 } 285 286 func (c *CSIInfo) IsController() bool { 287 if c == nil || c.ControllerInfo == nil { 288 return false 289 } 290 return true 291 } 292 293 func (c *CSIInfo) IsNode() bool { 294 if c == nil || c.NodeInfo == nil { 295 return false 296 } 297 return true 298 } 299 300 // DriverInfo is the current state of a single driver. This is updated 301 // regularly as driver health changes on the node. 302 type DriverInfo struct { 303 Attributes map[string]string 304 Detected bool 305 Healthy bool 306 HealthDescription string 307 UpdateTime time.Time 308 } 309 310 func (di *DriverInfo) Copy() *DriverInfo { 311 if di == nil { 312 return nil 313 } 314 315 cdi := new(DriverInfo) 316 *cdi = *di 317 cdi.Attributes = maps.Clone(di.Attributes) 318 return cdi 319 } 320 321 // MergeHealthCheck merges information from a health check for a drier into a 322 // node's driver info 323 func (di *DriverInfo) MergeHealthCheck(other *DriverInfo) { 324 di.Healthy = other.Healthy 325 di.HealthDescription = other.HealthDescription 326 di.UpdateTime = other.UpdateTime 327 } 328 329 // MergeFingerprintInfo merges information from fingerprinting a node for a 330 // driver into a node's driver info for that driver. 331 func (di *DriverInfo) MergeFingerprintInfo(other *DriverInfo) { 332 di.Detected = other.Detected 333 di.Attributes = other.Attributes 334 } 335 336 // HealthCheckEquals determines if two driver info objects are equal. As this 337 // is used in the process of health checking, we only check the fields that are 338 // computed by the health checker. In the future, this will be merged. 339 func (di *DriverInfo) HealthCheckEquals(other *DriverInfo) bool { 340 if di == nil && other == nil { 341 return true 342 } 343 344 if di.Healthy != other.Healthy { 345 return false 346 } 347 348 if di.HealthDescription != other.HealthDescription { 349 return false 350 } 351 352 return true 353 }