mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 18:35:44 +03:00
np: scheduler configuration updates (#17575)
* jobspec: rename node pool scheduler_configuration In HCL specifications we usually call configuration blocks `config` instead of `configuration`. * np: add memory oversubscription config * np: make scheduler config ENT
This commit is contained in:
@@ -118,7 +118,7 @@ type NodePool struct {
|
||||
Name string `hcl:"name,label"`
|
||||
Description string `hcl:"description,optional"`
|
||||
Meta map[string]string `hcl:"meta,block"`
|
||||
SchedulerConfiguration *NodePoolSchedulerConfiguration `hcl:"scheduler_configuration,block"`
|
||||
SchedulerConfiguration *NodePoolSchedulerConfiguration `hcl:"scheduler_config,block"`
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
@@ -126,5 +126,6 @@ type NodePool struct {
|
||||
// NodePoolSchedulerConfiguration is used to serialize the scheduler
|
||||
// configuration of a node pool.
|
||||
type NodePoolSchedulerConfiguration struct {
|
||||
SchedulerAlgorithm SchedulerAlgorithm `hcl:"scheduler_algorithm,optional"`
|
||||
SchedulerAlgorithm SchedulerAlgorithm `hcl:"scheduler_algorithm,optional"`
|
||||
MemoryOversubscriptionEnabled *bool `hcl:"memory_oversubscription_enabled,optional"`
|
||||
}
|
||||
|
||||
@@ -172,9 +172,6 @@ func TestHTTP_NodePool_Update(t *testing.T) {
|
||||
updated.Meta = map[string]string{
|
||||
"updated": "true",
|
||||
}
|
||||
updated.SchedulerConfiguration = &structs.NodePoolSchedulerConfiguration{
|
||||
SchedulerAlgorithm: structs.SchedulerAlgorithmBinpack,
|
||||
}
|
||||
|
||||
buf := encodeReq(updated)
|
||||
req, err := http.NewRequest("PUT", fmt.Sprintf("/v1/node/pool/%s", updated.Name), buf)
|
||||
@@ -219,9 +216,6 @@ func TestHTTP_NodePool_Update(t *testing.T) {
|
||||
updated.Meta = map[string]string{
|
||||
"updated": "true",
|
||||
}
|
||||
updated.SchedulerConfiguration = &structs.NodePoolSchedulerConfiguration{
|
||||
SchedulerAlgorithm: structs.SchedulerAlgorithmBinpack,
|
||||
}
|
||||
|
||||
buf := encodeReq(updated)
|
||||
req, err := http.NewRequest("PUT", "/v1/node/pool/", buf)
|
||||
@@ -264,9 +258,6 @@ func TestHTTP_NodePool_Update(t *testing.T) {
|
||||
updated.Meta = map[string]string{
|
||||
"updated": "true",
|
||||
}
|
||||
updated.SchedulerConfiguration = &structs.NodePoolSchedulerConfiguration{
|
||||
SchedulerAlgorithm: structs.SchedulerAlgorithmBinpack,
|
||||
}
|
||||
|
||||
// Make request with the wrong path.
|
||||
buf := encodeReq(updated)
|
||||
|
||||
@@ -17,9 +17,13 @@ node_pool "example" {
|
||||
# * scheduler_algorithm is the scheduling algorithm to use for the pool.
|
||||
# If not defined, the global cluster scheduling algorithm is used.
|
||||
#
|
||||
# * memory_oversubscription_enabled specifies whether memory oversubscription
|
||||
# is enabled. If not defined, the global cluster configuration is used.
|
||||
#
|
||||
# Available only in Nomad Enterprise.
|
||||
|
||||
# scheduler_configuration {
|
||||
# scheduler_algorithm = "spread"
|
||||
# scheduler_config {
|
||||
# scheduler_algorithm = "spread"
|
||||
# memory_oversubscription_enabled = true
|
||||
# }
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
"owner": "sre"
|
||||
},
|
||||
"SchedulerConfiguration": {
|
||||
"SchedulerAlgorithm": "spread"
|
||||
"SchedulerAlgorithm": "spread",
|
||||
"MemoryOversubscriptionEnabled": true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,11 +130,16 @@ func (c *NodePoolInfoCommand) Run(args []string) int {
|
||||
}
|
||||
|
||||
c.Ui.Output(c.Colorize().Color("\n[bold]Scheduler Configuration[reset]"))
|
||||
if pool.SchedulerConfiguration != nil {
|
||||
schedConfig := []string{
|
||||
fmt.Sprintf("Scheduler Algorithm|%s", pool.SchedulerConfiguration.SchedulerAlgorithm),
|
||||
if schedConfig := pool.SchedulerConfiguration; schedConfig != nil {
|
||||
schedConfigOut := []string{
|
||||
fmt.Sprintf("Scheduler Algorithm|%s", schedConfig.SchedulerAlgorithm),
|
||||
}
|
||||
c.Ui.Output(formatKV(schedConfig))
|
||||
if schedConfig.MemoryOversubscriptionEnabled != nil {
|
||||
schedConfigOut = append(schedConfigOut,
|
||||
fmt.Sprintf("Memory Oversubscription Enabled|%v", *schedConfig.MemoryOversubscriptionEnabled),
|
||||
)
|
||||
}
|
||||
c.Ui.Output(formatKV(schedConfigOut))
|
||||
} else {
|
||||
c.Ui.Output("No scheduler configuration")
|
||||
}
|
||||
|
||||
@@ -35,9 +35,6 @@ func TestNodePoolInfoCommand_Run(t *testing.T) {
|
||||
Meta: map[string]string{
|
||||
"env": "test",
|
||||
},
|
||||
SchedulerConfiguration: &api.NodePoolSchedulerConfiguration{
|
||||
SchedulerAlgorithm: api.SchedulerAlgorithmSpread,
|
||||
},
|
||||
}
|
||||
_, err := client.NodePools().Register(dev1, nil)
|
||||
must.NoError(t, err)
|
||||
@@ -50,7 +47,7 @@ Metadata
|
||||
env = test
|
||||
|
||||
Scheduler Configuration
|
||||
Scheduler Algorithm = spread`
|
||||
No scheduler configuration`
|
||||
|
||||
dev1JsonOutput := `
|
||||
{
|
||||
@@ -59,9 +56,7 @@ Scheduler Algorithm = spread`
|
||||
"env": "test"
|
||||
},
|
||||
"Name": "dev-1",
|
||||
"SchedulerConfiguration": {
|
||||
"SchedulerAlgorithm": "spread"
|
||||
}
|
||||
"SchedulerConfiguration": null
|
||||
}`
|
||||
|
||||
// These two node pools are used to test exact prefix match.
|
||||
|
||||
@@ -257,9 +257,6 @@ func NodePool() *structs.NodePool {
|
||||
Name: fmt.Sprintf("pool-%s", uuid.Short()),
|
||||
Description: "test node pool",
|
||||
Meta: map[string]string{"team": "test"},
|
||||
SchedulerConfiguration: &structs.NodePoolSchedulerConfiguration{
|
||||
SchedulerAlgorithm: structs.SchedulerAlgorithmSpread,
|
||||
},
|
||||
}
|
||||
pool.SetHash()
|
||||
return pool
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"sort"
|
||||
|
||||
"github.com/hashicorp/go-multierror"
|
||||
"github.com/hashicorp/nomad/helper/pointer"
|
||||
"golang.org/x/crypto/blake2b"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
@@ -129,6 +130,15 @@ func (n *NodePool) SetHash() []byte {
|
||||
_, _ = hash.Write([]byte(n.Description))
|
||||
if n.SchedulerConfiguration != nil {
|
||||
_, _ = hash.Write([]byte(n.SchedulerConfiguration.SchedulerAlgorithm))
|
||||
|
||||
memSub := n.SchedulerConfiguration.MemoryOversubscriptionEnabled
|
||||
if memSub != nil {
|
||||
if *memSub {
|
||||
_, _ = hash.Write([]byte("memory_oversubscription_enabled"))
|
||||
} else {
|
||||
_, _ = hash.Write([]byte("memory_oversubscription_disabled"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sort keys to ensure hash stability when meta is stored later
|
||||
@@ -158,6 +168,10 @@ 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"`
|
||||
|
||||
// MemoryOversubscriptionEnabled specifies whether memory oversubscription
|
||||
// is enabled. If not defined, the global cluster configuration is used.
|
||||
MemoryOversubscriptionEnabled *bool `hcl:"memory_oversubscription_enabled"`
|
||||
}
|
||||
|
||||
// Copy returns a deep copy of the node pool scheduler configuration.
|
||||
@@ -169,27 +183,13 @@ func (n *NodePoolSchedulerConfiguration) Copy() *NodePoolSchedulerConfiguration
|
||||
nc := new(NodePoolSchedulerConfiguration)
|
||||
*nc = *n
|
||||
|
||||
if n.MemoryOversubscriptionEnabled != nil {
|
||||
nc.MemoryOversubscriptionEnabled = pointer.Of(*n.MemoryOversubscriptionEnabled)
|
||||
}
|
||||
|
||||
return nc
|
||||
}
|
||||
|
||||
// Validate returns an error if the node pool scheduler confinguration is
|
||||
// invalid.
|
||||
func (n *NodePoolSchedulerConfiguration) Validate() error {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var mErr *multierror.Error
|
||||
|
||||
switch n.SchedulerAlgorithm {
|
||||
case "", SchedulerAlgorithmBinpack, SchedulerAlgorithmSpread:
|
||||
default:
|
||||
mErr = multierror.Append(mErr, fmt.Errorf("invalid scheduler algorithm %q", n.SchedulerAlgorithm))
|
||||
}
|
||||
|
||||
return mErr.ErrorOrNil()
|
||||
}
|
||||
|
||||
// NodePoolListRequest is used to list node pools.
|
||||
type NodePoolListRequest struct {
|
||||
QueryOptions
|
||||
|
||||
18
nomad/structs/node_pool_oss.go
Normal file
18
nomad/structs/node_pool_oss.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// copyright (c) hashicorp, inc.
|
||||
// spdx-license-identifier: mpl-2.0
|
||||
|
||||
//go:build !ent
|
||||
// +build !ent
|
||||
|
||||
package structs
|
||||
|
||||
import "errors"
|
||||
|
||||
// Validate returns an error if the node pool scheduler confinguration is
|
||||
// invalid.
|
||||
func (n *NodePoolSchedulerConfiguration) Validate() error {
|
||||
if n != nil {
|
||||
return errors.New("Node Pools Governance is unlicensed.")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
47
nomad/structs/node_pool_oss_test.go
Normal file
47
nomad/structs/node_pool_oss_test.go
Normal file
@@ -0,0 +1,47 @@
|
||||
// copyright (c) hashicorp, inc.
|
||||
// spdx-license-identifier: mpl-2.0
|
||||
|
||||
//go:build !ent
|
||||
// +build !ent
|
||||
|
||||
package structs
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestNodePool_Validate_OSS(t *testing.T) {
|
||||
ci.Parallel(t)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
pool *NodePool
|
||||
expectedErr string
|
||||
}{
|
||||
{
|
||||
name: "invalid scheduling algorithm",
|
||||
pool: &NodePool{
|
||||
Name: "valid",
|
||||
SchedulerConfiguration: &NodePoolSchedulerConfiguration{
|
||||
SchedulerAlgorithm: SchedulerAlgorithmBinpack,
|
||||
},
|
||||
},
|
||||
expectedErr: "unlicensed",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
err := tc.pool.Validate()
|
||||
|
||||
if tc.expectedErr != "" {
|
||||
must.ErrorContains(t, err, tc.expectedErr)
|
||||
} else {
|
||||
must.NoError(t, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/ci"
|
||||
"github.com/hashicorp/nomad/helper/pointer"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
@@ -19,7 +20,8 @@ func TestNodePool_Copy(t *testing.T) {
|
||||
Description: "original node pool",
|
||||
Meta: map[string]string{"original": "true"},
|
||||
SchedulerConfiguration: &NodePoolSchedulerConfiguration{
|
||||
SchedulerAlgorithm: SchedulerAlgorithmSpread,
|
||||
SchedulerAlgorithm: SchedulerAlgorithmSpread,
|
||||
MemoryOversubscriptionEnabled: pointer.Of(false),
|
||||
},
|
||||
}
|
||||
poolCopy := pool.Copy()
|
||||
@@ -28,6 +30,7 @@ func TestNodePool_Copy(t *testing.T) {
|
||||
poolCopy.Meta["original"] = "false"
|
||||
poolCopy.Meta["new_key"] = "true"
|
||||
poolCopy.SchedulerConfiguration.SchedulerAlgorithm = SchedulerAlgorithmBinpack
|
||||
poolCopy.SchedulerConfiguration.MemoryOversubscriptionEnabled = pointer.Of(true)
|
||||
|
||||
must.NotEq(t, pool, poolCopy)
|
||||
must.NotEq(t, pool.Meta, poolCopy.Meta)
|
||||
@@ -71,16 +74,6 @@ func TestNodePool_Validate(t *testing.T) {
|
||||
},
|
||||
expectedErr: "description longer",
|
||||
},
|
||||
{
|
||||
name: "invalid scheduling algorithm",
|
||||
pool: &NodePool{
|
||||
Name: "valid",
|
||||
SchedulerConfiguration: &NodePoolSchedulerConfiguration{
|
||||
SchedulerAlgorithm: "invalid",
|
||||
},
|
||||
},
|
||||
expectedErr: "invalid scheduler algorithm",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
@@ -45,7 +45,7 @@ node_pool "prod" {
|
||||
}
|
||||
|
||||
# Available only in Nomad Enterprise.
|
||||
scheduler_configuration {
|
||||
scheduler_config {
|
||||
scheduler_algorithm = "spread"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ node_pool "example" {
|
||||
#
|
||||
# Available only in Nomad Enterprise.
|
||||
|
||||
# scheduler_configuration {
|
||||
# scheduler_config {
|
||||
# scheduler_algorithm = "spread"
|
||||
# }
|
||||
}
|
||||
@@ -59,18 +59,21 @@ Successfully applied node pool "example"!
|
||||
pool, defined as key-value pairs. The scheduler does not use node pool metadat
|
||||
as part of scheduling.
|
||||
|
||||
- `scheduler_configuration` <code>([SchedulerConfig][sched-config]:
|
||||
nil)</code> - Sets scheduler configuration options specific to the node
|
||||
pool. If not defined, the global cluster scheduling algorithm is
|
||||
used. Available only in Nomad Enterprise.
|
||||
- `scheduler_config` <code>([SchedulerConfig][sched-config]: nil)</code> <EnterpriseAlert inline /> -
|
||||
Sets scheduler configuration options specific to the node pool. If not
|
||||
defined, the global scheduler configurations are used.
|
||||
|
||||
### Scheduler Configuration Parameters
|
||||
### `scheduler_config` Parameters <EnterpriseAlert inline />
|
||||
|
||||
- `scheduler_algorithm` `(string: <optional>)` - The [scheduler algorithm][]
|
||||
used for this node pool. Must be one of `binpack` or `spread`.
|
||||
|
||||
- `memory_oversubscription_enabled` `(bool: <optional>)` - The [memory
|
||||
oversubscription][] setting to use for this node pool.
|
||||
|
||||
[pool-apply]: /nomad/docs/commands/node-pool/apply
|
||||
[jobspecs]: /nomad/docs/job-specification
|
||||
[pool-init]: /nomad/docs/commands/node-pool/init
|
||||
[sched-config]: #scheduler-configuration-parameters
|
||||
[sched-config]: #scheduler_config-parameters
|
||||
[scheduler algorithm]: /nomad/api-docs/operator/scheduler#scheduleralgorithm-1
|
||||
[memory oversubscription]: /nomad/api-docs/operator/scheduler#memoryoversubscriptionenabled-1
|
||||
|
||||
Reference in New Issue
Block a user