2023-05-15 14:49:08 +00:00
|
|
|
// Copyright (c) HashiCorp, Inc.
|
|
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
|
|
|
package structs
|
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"regexp"
|
2023-06-12 17:24:24 +00:00
|
|
|
"sort"
|
2023-05-15 14:49:08 +00:00
|
|
|
|
|
|
|
"github.com/hashicorp/go-multierror"
|
2023-06-19 15:41:46 +00:00
|
|
|
"github.com/hashicorp/nomad/helper/pointer"
|
2023-06-12 17:24:24 +00:00
|
|
|
"golang.org/x/crypto/blake2b"
|
2023-05-15 14:49:08 +00:00
|
|
|
"golang.org/x/exp/maps"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
// NodePoolAll is a built-in node pool that always includes all nodes in
|
|
|
|
// the cluster.
|
|
|
|
NodePoolAll = "all"
|
|
|
|
NodePoolAllDescription = "Node pool with all nodes in the cluster."
|
|
|
|
|
|
|
|
// NodePoolDefault is a built-in node pool for nodes that don't specify a
|
|
|
|
// node pool in their configuration.
|
|
|
|
NodePoolDefault = "default"
|
|
|
|
NodePoolDefaultDescription = "Default node pool."
|
|
|
|
|
|
|
|
// maxNodePoolDescriptionLength is the maximum length allowed for a node
|
|
|
|
// pool description.
|
|
|
|
maxNodePoolDescriptionLength = 256
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
// validNodePoolName is the rule used to validate a node pool name.
|
|
|
|
validNodePoolName = regexp.MustCompile("^[a-zA-Z0-9-_]{1,128}$")
|
|
|
|
)
|
|
|
|
|
2023-07-24 07:28:28 +00:00
|
|
|
// ValidateNodePoolName returns an error if a node pool name is invalid.
|
2023-06-02 21:50:50 +00:00
|
|
|
func ValidateNodePoolName(pool string) error {
|
|
|
|
if !validNodePoolName.MatchString(pool) {
|
|
|
|
return fmt.Errorf("invalid name %q, must match regex %s", pool, validNodePoolName)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-05-15 14:49:08 +00:00
|
|
|
// NodePool allows partioning infrastructure
|
|
|
|
type NodePool struct {
|
|
|
|
// Name is the node pool name. It must be unique.
|
|
|
|
Name string
|
|
|
|
|
|
|
|
// Description is the human-friendly description of the node pool.
|
|
|
|
Description string
|
|
|
|
|
|
|
|
// Meta is a set of user-provided metadata for the node pool.
|
|
|
|
Meta map[string]string
|
|
|
|
|
|
|
|
// SchedulerConfiguration is the scheduler configuration specific to the
|
|
|
|
// node pool.
|
|
|
|
SchedulerConfiguration *NodePoolSchedulerConfiguration
|
|
|
|
|
2023-06-12 17:24:24 +00:00
|
|
|
// Hash is the hash of the node pool which is used to efficiently diff when
|
|
|
|
// we replicate pools across regions.
|
|
|
|
Hash []byte
|
|
|
|
|
2023-05-15 14:49:08 +00:00
|
|
|
// Raft indexes.
|
|
|
|
CreateIndex uint64
|
|
|
|
ModifyIndex uint64
|
|
|
|
}
|
|
|
|
|
2023-06-01 19:55:49 +00:00
|
|
|
// GetID implements the IDGetter interface required for pagination.
|
|
|
|
func (n *NodePool) GetID() string {
|
|
|
|
return n.Name
|
|
|
|
}
|
|
|
|
|
2023-05-15 14:49:08 +00:00
|
|
|
// Validate returns an error if the node pool is invalid.
|
|
|
|
func (n *NodePool) Validate() error {
|
|
|
|
var mErr *multierror.Error
|
|
|
|
|
2023-06-02 21:50:50 +00:00
|
|
|
mErr = multierror.Append(mErr, ValidateNodePoolName(n.Name))
|
|
|
|
|
2023-05-15 14:49:08 +00:00
|
|
|
if len(n.Description) > maxNodePoolDescriptionLength {
|
|
|
|
mErr = multierror.Append(mErr, fmt.Errorf("description longer than %d", maxNodePoolDescriptionLength))
|
|
|
|
}
|
|
|
|
|
|
|
|
mErr = multierror.Append(mErr, n.SchedulerConfiguration.Validate())
|
|
|
|
|
|
|
|
return mErr.ErrorOrNil()
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy returns a deep copy of the node pool.
|
|
|
|
func (n *NodePool) Copy() *NodePool {
|
|
|
|
if n == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
nc := new(NodePool)
|
|
|
|
*nc = *n
|
|
|
|
nc.Meta = maps.Clone(nc.Meta)
|
|
|
|
nc.SchedulerConfiguration = nc.SchedulerConfiguration.Copy()
|
|
|
|
|
2023-06-12 17:24:24 +00:00
|
|
|
nc.Hash = make([]byte, len(n.Hash))
|
|
|
|
copy(nc.Hash, n.Hash)
|
|
|
|
|
2023-05-15 14:49:08 +00:00
|
|
|
return nc
|
|
|
|
}
|
|
|
|
|
|
|
|
// IsBuiltIn returns true if the node pool is one of the built-in pools.
|
|
|
|
//
|
|
|
|
// Built-in node pools are created automatically by Nomad and can never be
|
|
|
|
// deleted or modified so they are always present in the cluster..
|
|
|
|
func (n *NodePool) IsBuiltIn() bool {
|
|
|
|
switch n.Name {
|
|
|
|
case NodePoolAll, NodePoolDefault:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-22 00:31:50 +00:00
|
|
|
// MemoryOversubscriptionEnabled returns true if memory oversubscription is
|
|
|
|
// enabled in the node pool or in the global cluster configuration.
|
|
|
|
func (n *NodePool) MemoryOversubscriptionEnabled(global *SchedulerConfiguration) bool {
|
|
|
|
|
|
|
|
// Default to the global scheduler config.
|
|
|
|
memOversubEnabled := global != nil && global.MemoryOversubscriptionEnabled
|
|
|
|
|
|
|
|
// But overwrite it if the node pool also has it configured.
|
|
|
|
poolHasMemOversub := n != nil &&
|
|
|
|
n.SchedulerConfiguration != nil &&
|
|
|
|
n.SchedulerConfiguration.MemoryOversubscriptionEnabled != nil
|
|
|
|
if poolHasMemOversub {
|
|
|
|
memOversubEnabled = *n.SchedulerConfiguration.MemoryOversubscriptionEnabled
|
|
|
|
}
|
|
|
|
|
|
|
|
return memOversubEnabled
|
|
|
|
}
|
|
|
|
|
2023-06-12 17:24:24 +00:00
|
|
|
// SetHash is used to compute and set the hash of node pool
|
|
|
|
func (n *NodePool) SetHash() []byte {
|
|
|
|
// Initialize a 256bit Blake2 hash (32 bytes)
|
|
|
|
hash, err := blake2b.New256(nil)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Write all the user set fields
|
|
|
|
_, _ = hash.Write([]byte(n.Name))
|
|
|
|
_, _ = hash.Write([]byte(n.Description))
|
|
|
|
if n.SchedulerConfiguration != nil {
|
|
|
|
_, _ = hash.Write([]byte(n.SchedulerConfiguration.SchedulerAlgorithm))
|
2023-06-19 15:41:46 +00:00
|
|
|
|
|
|
|
memSub := n.SchedulerConfiguration.MemoryOversubscriptionEnabled
|
|
|
|
if memSub != nil {
|
|
|
|
if *memSub {
|
|
|
|
_, _ = hash.Write([]byte("memory_oversubscription_enabled"))
|
|
|
|
} else {
|
|
|
|
_, _ = hash.Write([]byte("memory_oversubscription_disabled"))
|
|
|
|
}
|
|
|
|
}
|
2023-06-12 17:24:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// sort keys to ensure hash stability when meta is stored later
|
|
|
|
var keys []string
|
|
|
|
for k := range n.Meta {
|
|
|
|
keys = append(keys, k)
|
|
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
|
|
|
|
|
|
for _, k := range keys {
|
|
|
|
_, _ = hash.Write([]byte(k))
|
|
|
|
_, _ = hash.Write([]byte(n.Meta[k]))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finalize the hash
|
|
|
|
hashVal := hash.Sum(nil)
|
|
|
|
|
|
|
|
// Set and return the hash
|
|
|
|
n.Hash = hashVal
|
|
|
|
return hashVal
|
|
|
|
}
|
|
|
|
|
2023-05-15 14:49:08 +00:00
|
|
|
// NodePoolSchedulerConfiguration is the scheduler confinguration applied to a
|
|
|
|
// node pool.
|
2023-06-22 00:31:50 +00:00
|
|
|
//
|
|
|
|
// When adding new values that should override global scheduler configuration,
|
|
|
|
// verify the scheduler handles the node pool configuration as well.
|
2023-05-15 14:49:08 +00:00
|
|
|
type NodePoolSchedulerConfiguration struct {
|
|
|
|
|
|
|
|
// SchedulerAlgorithm is the scheduling algorithm to use for the pool.
|
|
|
|
// If not defined, the global cluster scheduling algorithm is used.
|
|
|
|
SchedulerAlgorithm SchedulerAlgorithm `hcl:"scheduler_algorithm"`
|
2023-06-19 15:41:46 +00:00
|
|
|
|
|
|
|
// MemoryOversubscriptionEnabled specifies whether memory oversubscription
|
|
|
|
// is enabled. If not defined, the global cluster configuration is used.
|
|
|
|
MemoryOversubscriptionEnabled *bool `hcl:"memory_oversubscription_enabled"`
|
2023-05-15 14:49:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Copy returns a deep copy of the node pool scheduler configuration.
|
|
|
|
func (n *NodePoolSchedulerConfiguration) Copy() *NodePoolSchedulerConfiguration {
|
|
|
|
if n == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
nc := new(NodePoolSchedulerConfiguration)
|
|
|
|
*nc = *n
|
|
|
|
|
2023-06-19 15:41:46 +00:00
|
|
|
if n.MemoryOversubscriptionEnabled != nil {
|
|
|
|
nc.MemoryOversubscriptionEnabled = pointer.Of(*n.MemoryOversubscriptionEnabled)
|
2023-05-15 14:49:08 +00:00
|
|
|
}
|
|
|
|
|
2023-06-19 15:41:46 +00:00
|
|
|
return nc
|
2023-05-15 14:49:08 +00:00
|
|
|
}
|
2023-06-01 19:55:49 +00:00
|
|
|
|
|
|
|
// NodePoolListRequest is used to list node pools.
|
|
|
|
type NodePoolListRequest struct {
|
|
|
|
QueryOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodePoolListResponse is the response to node pools list request.
|
|
|
|
type NodePoolListResponse struct {
|
|
|
|
NodePools []*NodePool
|
|
|
|
QueryMeta
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodePoolSpecificRequest is used to make a request for a specific node pool.
|
|
|
|
type NodePoolSpecificRequest struct {
|
|
|
|
Name string
|
|
|
|
QueryOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
// SingleNodePoolResponse is the response to a specific node pool request.
|
|
|
|
type SingleNodePoolResponse struct {
|
|
|
|
NodePool *NodePool
|
|
|
|
QueryMeta
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodePoolUpsertRequest is used to make a request to insert or update a node
|
|
|
|
// pool.
|
|
|
|
type NodePoolUpsertRequest struct {
|
|
|
|
NodePools []*NodePool
|
|
|
|
WriteRequest
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodePoolDeleteRequest is used to make a request to delete a node pool.
|
|
|
|
type NodePoolDeleteRequest struct {
|
|
|
|
Names []string
|
|
|
|
WriteRequest
|
|
|
|
}
|
2023-06-05 19:36:52 +00:00
|
|
|
|
2023-06-06 14:43:43 +00:00
|
|
|
// NodePoolNodesRequest is used to list all nodes that are part of a node pool.
|
|
|
|
type NodePoolNodesRequest struct {
|
|
|
|
Name string
|
|
|
|
Fields *NodeStubFields
|
|
|
|
QueryOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodePoolNodesResponse is used to return a list nodes in the node pool.
|
|
|
|
type NodePoolNodesResponse struct {
|
|
|
|
Nodes []*NodeListStub
|
|
|
|
QueryMeta
|
|
|
|
}
|
|
|
|
|
2023-06-05 19:36:52 +00:00
|
|
|
// NodePoolJobsRequest is used to make a request for the jobs in a specific node pool.
|
|
|
|
type NodePoolJobsRequest struct {
|
|
|
|
Name string
|
|
|
|
Fields *JobStubFields
|
|
|
|
QueryOptions
|
|
|
|
}
|
|
|
|
|
|
|
|
// NodePoolJobsResponse returns a list of jobs in a specific node pool.
|
|
|
|
type NodePoolJobsResponse struct {
|
|
|
|
Jobs []*JobListStub
|
|
|
|
QueryMeta
|
|
|
|
}
|