mirror of
https://github.com/kemko/nomad.git
synced 2026-01-06 10:25:42 +03:00
node pools: implement CLI (#17388)
This commit is contained in:
@@ -15,6 +15,7 @@ const (
|
||||
Evals Context = "evals"
|
||||
Jobs Context = "jobs"
|
||||
Nodes Context = "nodes"
|
||||
NodePools Context = "node_pools"
|
||||
Namespaces Context = "namespaces"
|
||||
Quotas Context = "quotas"
|
||||
Recommendations Context = "recommendations"
|
||||
|
||||
@@ -38,9 +38,6 @@ const (
|
||||
// DefaultNamespace is the default namespace.
|
||||
DefaultNamespace = "default"
|
||||
|
||||
// NodePoolDefault is the default node pool.
|
||||
NodePoolDefault = "default"
|
||||
|
||||
// For Job configuration, GlobalRegion is a sentinel region value
|
||||
// that users may specify to indicate the job should be run on
|
||||
// the region of the node that the job was submitted to.
|
||||
|
||||
105
api/node_pools.go
Normal file
105
api/node_pools.go
Normal file
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
const (
|
||||
// NodePoolAll is the node pool that always includes all nodes.
|
||||
NodePoolAll = "all"
|
||||
|
||||
// NodePoolDefault is the default node pool.
|
||||
NodePoolDefault = "default"
|
||||
)
|
||||
|
||||
// NodePools is used to access node pools endpoints.
|
||||
type NodePools struct {
|
||||
client *Client
|
||||
}
|
||||
|
||||
// NodePools returns a handle on the node pools endpoints.
|
||||
func (c *Client) NodePools() *NodePools {
|
||||
return &NodePools{client: c}
|
||||
}
|
||||
|
||||
// List is used to list all node pools.
|
||||
func (n *NodePools) List(q *QueryOptions) ([]*NodePool, *QueryMeta, error) {
|
||||
var resp []*NodePool
|
||||
qm, err := n.client.query("/v1/node/pools", &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return resp, qm, nil
|
||||
}
|
||||
|
||||
// PrefixList is used to list node pools that match a given prefix.
|
||||
func (n *NodePools) PrefixList(prefix string, q *QueryOptions) ([]*NodePool, *QueryMeta, error) {
|
||||
if q == nil {
|
||||
q = &QueryOptions{}
|
||||
}
|
||||
q.Prefix = prefix
|
||||
return n.List(q)
|
||||
}
|
||||
|
||||
// Info is used to fetch details of a specific node pool.
|
||||
func (n *NodePools) Info(name string, q *QueryOptions) (*NodePool, *QueryMeta, error) {
|
||||
if name == "" {
|
||||
return nil, nil, errors.New("missing node pool name")
|
||||
}
|
||||
|
||||
var resp NodePool
|
||||
qm, err := n.client.query("/v1/node/pool/"+url.PathEscape(name), &resp, q)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return &resp, qm, nil
|
||||
}
|
||||
|
||||
// Register is used to create or update a node pool.
|
||||
func (n *NodePools) Register(pool *NodePool, w *WriteOptions) (*WriteMeta, error) {
|
||||
if pool == nil {
|
||||
return nil, errors.New("missing node pool")
|
||||
}
|
||||
if pool.Name == "" {
|
||||
return nil, errors.New("missing node pool name")
|
||||
}
|
||||
|
||||
wm, err := n.client.put("/v1/node/pools", pool, nil, w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// Delete is used to delete a node pool.
|
||||
func (n *NodePools) Delete(name string, w *WriteOptions) (*WriteMeta, error) {
|
||||
if name == "" {
|
||||
return nil, errors.New("missing node pool name")
|
||||
}
|
||||
|
||||
wm, err := n.client.delete("/v1/node/pool/"+url.PathEscape(name), nil, nil, w)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return wm, nil
|
||||
}
|
||||
|
||||
// NodePool is used to serialize a node pool.
|
||||
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"`
|
||||
CreateIndex uint64
|
||||
ModifyIndex uint64
|
||||
}
|
||||
|
||||
// NodePoolSchedulerConfiguration is used to serialize the scheduler
|
||||
// configuration of a node pool.
|
||||
type NodePoolSchedulerConfiguration struct {
|
||||
SchedulerAlgorithm SchedulerAlgorithm `hcl:"scheduler_algorithm,optional"`
|
||||
}
|
||||
210
api/node_pools_test.go
Normal file
210
api/node_pools_test.go
Normal file
@@ -0,0 +1,210 @@
|
||||
// Copyright (c) HashiCorp, Inc.
|
||||
// SPDX-License-Identifier: MPL-2.0
|
||||
|
||||
package api
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/nomad/api/internal/testutil"
|
||||
"github.com/shoenig/test/must"
|
||||
)
|
||||
|
||||
func TestNodePools_List(t *testing.T) {
|
||||
testutil.Parallel(t)
|
||||
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodePools := c.NodePools()
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
q *QueryOptions
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "list all",
|
||||
q: nil,
|
||||
expected: []string{
|
||||
NodePoolAll,
|
||||
NodePoolDefault,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with query param",
|
||||
q: &QueryOptions{
|
||||
PerPage: 1,
|
||||
},
|
||||
expected: []string{NodePoolAll},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
resp, _, err := nodePools.List(tc.q)
|
||||
must.NoError(t, err)
|
||||
|
||||
got := make([]string, len(resp))
|
||||
for i, pool := range resp {
|
||||
got[i] = pool.Name
|
||||
}
|
||||
must.SliceContainsAll(t, got, tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodePools_PrefixList(t *testing.T) {
|
||||
testutil.Parallel(t)
|
||||
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodePools := c.NodePools()
|
||||
|
||||
// Create test node pool.
|
||||
dev1 := &NodePool{Name: "dev-1"}
|
||||
_, err := nodePools.Register(dev1, nil)
|
||||
must.NoError(t, err)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
prefix string
|
||||
q *QueryOptions
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "prefix",
|
||||
prefix: "d",
|
||||
q: nil,
|
||||
expected: []string{
|
||||
NodePoolDefault,
|
||||
dev1.Name,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "with query param",
|
||||
prefix: "d",
|
||||
q: &QueryOptions{
|
||||
PerPage: 1,
|
||||
},
|
||||
expected: []string{NodePoolDefault},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
resp, _, err := nodePools.PrefixList(tc.prefix, tc.q)
|
||||
must.NoError(t, err)
|
||||
|
||||
got := make([]string, len(resp))
|
||||
for i, pool := range resp {
|
||||
got[i] = pool.Name
|
||||
}
|
||||
must.SliceContainsAll(t, got, tc.expected)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNodePools_Info(t *testing.T) {
|
||||
testutil.Parallel(t)
|
||||
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodePools := c.NodePools()
|
||||
|
||||
t.Run("default node pool", func(t *testing.T) {
|
||||
pool, _, err := nodePools.Info(NodePoolDefault, nil)
|
||||
must.NoError(t, err)
|
||||
must.Eq(t, NodePoolDefault, pool.Name)
|
||||
})
|
||||
|
||||
t.Run("missing node pool name", func(t *testing.T) {
|
||||
pool, _, err := nodePools.Info("", nil)
|
||||
must.ErrorContains(t, err, "missing node pool name")
|
||||
must.Nil(t, pool)
|
||||
})
|
||||
|
||||
t.Run("node pool name with special charaters", func(t *testing.T) {
|
||||
pool, _, err := nodePools.Info("node/pool", nil)
|
||||
must.ErrorContains(t, err, "not found")
|
||||
must.Nil(t, pool)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNodePools_Register(t *testing.T) {
|
||||
testutil.Parallel(t)
|
||||
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodePools := c.NodePools()
|
||||
|
||||
// Create test node pool.
|
||||
t.Run("create and update node pool", func(t *testing.T) {
|
||||
dev1 := &NodePool{Name: "dev-1"}
|
||||
_, err := nodePools.Register(dev1, nil)
|
||||
must.NoError(t, err)
|
||||
|
||||
// Verify node pool was persisted.
|
||||
got, _, err := nodePools.Info(dev1.Name, nil)
|
||||
must.NoError(t, err)
|
||||
must.Eq(t, dev1.Name, got.Name)
|
||||
|
||||
// Update test node pool.
|
||||
dev1.Description = "test"
|
||||
_, err = nodePools.Register(dev1, nil)
|
||||
must.NoError(t, err)
|
||||
|
||||
// Verify node pool was updated.
|
||||
got, _, err = nodePools.Info(dev1.Name, nil)
|
||||
must.NoError(t, err)
|
||||
must.Eq(t, dev1.Name, got.Name)
|
||||
must.Eq(t, dev1.Description, got.Description)
|
||||
})
|
||||
|
||||
t.Run("missing node pool", func(t *testing.T) {
|
||||
_, err := nodePools.Register(nil, nil)
|
||||
must.ErrorContains(t, err, "missing node pool")
|
||||
})
|
||||
|
||||
t.Run("missing node pool name", func(t *testing.T) {
|
||||
_, err := nodePools.Register(&NodePool{}, nil)
|
||||
must.ErrorContains(t, err, "missing node pool name")
|
||||
})
|
||||
}
|
||||
|
||||
func TestNodePools_Delete(t *testing.T) {
|
||||
testutil.Parallel(t)
|
||||
|
||||
c, s := makeClient(t, nil, nil)
|
||||
defer s.Stop()
|
||||
nodePools := c.NodePools()
|
||||
|
||||
// Create test node pool.
|
||||
t.Run("delete node pool", func(t *testing.T) {
|
||||
dev1 := &NodePool{Name: "dev-1"}
|
||||
_, err := nodePools.Register(dev1, nil)
|
||||
must.NoError(t, err)
|
||||
|
||||
// Verify node pool was persisted.
|
||||
got, _, err := nodePools.Info(dev1.Name, nil)
|
||||
must.NoError(t, err)
|
||||
must.Eq(t, dev1.Name, got.Name)
|
||||
|
||||
// Delete test node pool.
|
||||
_, err = nodePools.Delete(dev1.Name, nil)
|
||||
must.NoError(t, err)
|
||||
|
||||
// Verify node pool is gone.
|
||||
got, _, err = nodePools.Info(dev1.Name, nil)
|
||||
must.ErrorContains(t, err, "not found")
|
||||
})
|
||||
|
||||
t.Run("missing node pool name", func(t *testing.T) {
|
||||
_, err := nodePools.Delete("", nil)
|
||||
must.ErrorContains(t, err, "missing node pool name")
|
||||
})
|
||||
|
||||
t.Run("node pool name with special charaters", func(t *testing.T) {
|
||||
_, err := nodePools.Delete("node/pool", nil)
|
||||
must.ErrorContains(t, err, "not found")
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user