diff --git a/api/node_pools.go b/api/node_pools.go index f2fa67b49..d3f18bd87 100644 --- a/api/node_pools.go +++ b/api/node_pools.go @@ -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"` } diff --git a/command/agent/node_pool_endpoint_test.go b/command/agent/node_pool_endpoint_test.go index 1e744fcb6..74e2770ff 100644 --- a/command/agent/node_pool_endpoint_test.go +++ b/command/agent/node_pool_endpoint_test.go @@ -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) diff --git a/command/asset/pool.nomad.hcl b/command/asset/pool.nomad.hcl index e4c3ca228..b9a111359 100644 --- a/command/asset/pool.nomad.hcl +++ b/command/asset/pool.nomad.hcl @@ -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 # } } diff --git a/command/asset/pool.nomad.json b/command/asset/pool.nomad.json index 02622902a..16d205f26 100644 --- a/command/asset/pool.nomad.json +++ b/command/asset/pool.nomad.json @@ -6,6 +6,7 @@ "owner": "sre" }, "SchedulerConfiguration": { - "SchedulerAlgorithm": "spread" + "SchedulerAlgorithm": "spread", + "MemoryOversubscriptionEnabled": true } } diff --git a/command/node_pool_info.go b/command/node_pool_info.go index 34766e8e2..4a93e4471 100644 --- a/command/node_pool_info.go +++ b/command/node_pool_info.go @@ -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") } diff --git a/command/node_pool_info_test.go b/command/node_pool_info_test.go index b8607ab9d..ef002f312 100644 --- a/command/node_pool_info_test.go +++ b/command/node_pool_info_test.go @@ -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. diff --git a/nomad/mock/mock.go b/nomad/mock/mock.go index 45154e4c6..728a931aa 100644 --- a/nomad/mock/mock.go +++ b/nomad/mock/mock.go @@ -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 diff --git a/nomad/structs/node_pool.go b/nomad/structs/node_pool.go index 39fc72046..e706b4ff2 100644 --- a/nomad/structs/node_pool.go +++ b/nomad/structs/node_pool.go @@ -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 diff --git a/nomad/structs/node_pool_oss.go b/nomad/structs/node_pool_oss.go new file mode 100644 index 000000000..769c7aa49 --- /dev/null +++ b/nomad/structs/node_pool_oss.go @@ -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 +} diff --git a/nomad/structs/node_pool_oss_test.go b/nomad/structs/node_pool_oss_test.go new file mode 100644 index 000000000..8180411ba --- /dev/null +++ b/nomad/structs/node_pool_oss_test.go @@ -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) + } + }) + } +} diff --git a/nomad/structs/node_pool_test.go b/nomad/structs/node_pool_test.go index 54c2b1e1d..f261c49c6 100644 --- a/nomad/structs/node_pool_test.go +++ b/nomad/structs/node_pool_test.go @@ -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 { diff --git a/website/content/docs/commands/node-pool/apply.mdx b/website/content/docs/commands/node-pool/apply.mdx index 7c462f7ff..c185e960a 100644 --- a/website/content/docs/commands/node-pool/apply.mdx +++ b/website/content/docs/commands/node-pool/apply.mdx @@ -45,7 +45,7 @@ node_pool "prod" { } # Available only in Nomad Enterprise. - scheduler_configuration { + scheduler_config { scheduler_algorithm = "spread" } } diff --git a/website/content/docs/other-specifications/node-pool.mdx b/website/content/docs/other-specifications/node-pool.mdx index 75833cbff..ebc4b78c2 100644 --- a/website/content/docs/other-specifications/node-pool.mdx +++ b/website/content/docs/other-specifications/node-pool.mdx @@ -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` ([SchedulerConfig][sched-config]: - nil) - 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` ([SchedulerConfig][sched-config]: nil) - + Sets scheduler configuration options specific to the node pool. If not + defined, the global scheduler configurations are used. -### Scheduler Configuration Parameters +### `scheduler_config` Parameters - `scheduler_algorithm` `(string: )` - The [scheduler algorithm][] used for this node pool. Must be one of `binpack` or `spread`. +- `memory_oversubscription_enabled` `(bool: )` - 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