Skip to content

Commit 1ded1d5

Browse files
committed
Set MaxAllocations in client config
Add NodeAllocationTracker struct to Node struct Evaluate MaxAllocations in AllocsFit function
1 parent bee2400 commit 1ded1d5

File tree

5 files changed

+106
-2
lines changed

5 files changed

+106
-2
lines changed

client/client.go

+8-1
Original file line numberDiff line numberDiff line change
@@ -1628,7 +1628,14 @@ func (c *Client) setupNode() error {
16281628
if _, ok := node.Meta[envoy.DefaultTransparentProxyOutboundPortParam]; !ok {
16291629
node.Meta[envoy.DefaultTransparentProxyOutboundPortParam] = envoy.DefaultTransparentProxyOutboundPort
16301630
}
1631-
1631+
// Set NodeMaxAllocs before dynamic configuration is set
1632+
if node.NodeAllocationTracker == nil {
1633+
if newConfig.NodeMaxAllocs >= 1 {
1634+
node.NodeAllocationTracker = &structs.NodeAllocationTracker{
1635+
NodeMaxAllocs: newConfig.NodeMaxAllocs,
1636+
}
1637+
}
1638+
}
16321639
// Since node.Meta will get dynamic metadata merged in, save static metadata
16331640
// here.
16341641
c.metaStatic = maps.Clone(node.Meta)

client/config/config.go

+4
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,10 @@ type Config struct {
375375

376376
// ExtraAllocHooks are run with other allocation hooks, mainly for testing.
377377
ExtraAllocHooks []interfaces.RunnerHook
378+
379+
// NodeMaxAllocs is an optional field that sets the maximum number of
380+
// allocations a node can be assigned. Defaults to 0 and ignored if unset.
381+
NodeMaxAllocs int
378382
}
379383

380384
type APIListenerRegistrar interface {

nomad/structs/funcs.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,11 @@ func (a TerminalByNodeByName) Get(nodeID, name string) (*Allocation, bool) {
141141
func AllocsFit(node *Node, allocs []*Allocation, netIdx *NetworkIndex, checkDevices bool) (bool, string, *ComparableResources, error) {
142142
// Compute the allocs' utilization from zero
143143
used := new(ComparableResources)
144-
144+
if node.NodeAllocationTracker != nil {
145+
if node.NodeAllocationTracker.NodeMaxAllocs <= len(allocs) {
146+
return false, "max allocation exceeded", used, fmt.Errorf("plan exceeds max allocation")
147+
}
148+
}
145149
reservedCores := map[uint16]struct{}{}
146150
var coreOverlap bool
147151

nomad/structs/funcs_test.go

+76
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,82 @@ func TestScoreFitBinPack(t *testing.T) {
716716
}
717717
}
718718

719+
func TestAllocsFit_MaxNodeAllocs(t *testing.T) {
720+
ci.Parallel(t)
721+
baseAlloc := &Allocation{
722+
AllocatedResources: &AllocatedResources{
723+
Tasks: map[string]*AllocatedTaskResources{
724+
"web": {
725+
Cpu: AllocatedCpuResources{
726+
CpuShares: 1000,
727+
ReservedCores: []uint16{},
728+
},
729+
Memory: AllocatedMemoryResources{
730+
MemoryMB: 1024,
731+
},
732+
},
733+
},
734+
Shared: AllocatedSharedResources{
735+
DiskMB: 5000,
736+
Networks: Networks{
737+
{
738+
Mode: "host",
739+
IP: "10.0.0.1",
740+
ReservedPorts: []Port{{Label: "main", Value: 8000}},
741+
},
742+
},
743+
Ports: AllocatedPorts{
744+
{
745+
Label: "main",
746+
Value: 8000,
747+
HostIP: "10.0.0.1",
748+
},
749+
},
750+
},
751+
},
752+
}
753+
754+
testCases := []struct {
755+
name string
756+
allocations []*Allocation
757+
expectErr bool
758+
maxAllocs int
759+
}{
760+
{
761+
name: "happy_path",
762+
allocations: []*Allocation{baseAlloc},
763+
expectErr: false,
764+
maxAllocs: 2,
765+
},
766+
{
767+
name: "too many allocs",
768+
allocations: []*Allocation{baseAlloc, baseAlloc, baseAlloc},
769+
expectErr: true,
770+
maxAllocs: 2,
771+
},
772+
}
773+
774+
for _, tc := range testCases {
775+
t.Run(tc.name, func(t *testing.T) {
776+
n := node2k()
777+
n.NodeAllocationTracker = &NodeAllocationTracker{false, tc.maxAllocs}
778+
fit, dim, used, err := AllocsFit(n, tc.allocations, nil, false)
779+
if !tc.expectErr {
780+
must.NoError(t, err)
781+
must.True(t, fit)
782+
must.Eq(t, 1000, used.Flattened.Cpu.CpuShares)
783+
must.Eq(t, 1024, used.Flattened.Memory.MemoryMB)
784+
} else {
785+
must.Error(t, err)
786+
must.False(t, fit)
787+
must.StrContains(t, dim, "max allocation exceeded")
788+
must.StrContains(t, err.Error(), "plan exceeds max allocation")
789+
must.Eq(t, 0, used.Flattened.Cpu.CpuShares)
790+
must.Eq(t, 0, used.Flattened.Memory.MemoryMB)
791+
}
792+
})
793+
}
794+
}
719795
func TestACLPolicyListHash(t *testing.T) {
720796
ci.Parallel(t)
721797

nomad/structs/structs.go

+13
Original file line numberDiff line numberDiff line change
@@ -2174,6 +2174,11 @@ type Node struct {
21742174
// Raft Indexes
21752175
CreateIndex uint64
21762176
ModifyIndex uint64
2177+
2178+
// NodeAllocationTracker holds NodeMaxAllocs value, if configured,
2179+
// and CurrentNodeAllocations to help the scheduler to block excess
2180+
// allocations.
2181+
NodeAllocationTracker *NodeAllocationTracker
21772182
}
21782183

21792184
// GetID is a helper for getting the ID when the object may be nil and is
@@ -2394,6 +2399,14 @@ type NodeStubFields struct {
23942399
OS bool
23952400
}
23962401

2402+
// NodeAllocationTracker retains awareness of a client's NodeMaxAllocs
2403+
// value, the current number of Allocations and whether the node can
2404+
// accept new Allocations
2405+
type NodeAllocationTracker struct {
2406+
atMax bool
2407+
NodeMaxAllocs int
2408+
}
2409+
23972410
// Resources is used to define the resources available
23982411
// on a client
23992412
type Resources struct {

0 commit comments

Comments
 (0)