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:
Luiz Aoqui
2023-06-19 11:41:46 -04:00
committed by GitHub
parent e29ad68c58
commit 6c64847e1b
13 changed files with 120 additions and 65 deletions

View File

@@ -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"`
}

View File

@@ -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)

View File

@@ -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
# }
}

View File

@@ -6,6 +6,7 @@
"owner": "sre"
},
"SchedulerConfiguration": {
"SchedulerAlgorithm": "spread"
"SchedulerAlgorithm": "spread",
"MemoryOversubscriptionEnabled": true
}
}

View File

@@ -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")
}

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View 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
}

View 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)
}
})
}
}

View File

@@ -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 {

View File

@@ -45,7 +45,7 @@ node_pool "prod" {
}
# Available only in Nomad Enterprise.
scheduler_configuration {
scheduler_config {
scheduler_algorithm = "spread"
}
}

View File

@@ -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