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  }