refactor: cpu and memory under Resources struct

This commit is contained in:
Pavel Sviderski
2025-04-24 16:40:26 +10:00
parent badb8ecd3c
commit 7db8bba6ea
4 changed files with 148 additions and 161 deletions
+7 -10
View File
@@ -1,14 +1,11 @@
package api package api
type CPUResources struct { type ContainerResources struct {
// Limit is the maximum amount of CPU nanocores (1000000000 = 1 CPU core) the container can use. // CPU is the maximum amount of CPU nanocores (1000000000 = 1 CPU core) the container can use.
Limit int64 CPU int64
} // Memory is the maximum amount of memory (in bytes) the container can use.
Memory int64
type MemoryResources struct { // MemoryReservation is the minimum amount of memory (in bytes) the container needs to run efficiently.
// Limit is the maximum amount of memory (in bytes) the container can use.
Limit int64
// Reservation is the minimum amount of memory (in bytes) the container needs to run efficiently.
// TODO: implement a placement constraint that checks available memory on machines. // TODO: implement a placement constraint that checks available memory on machines.
Reservation int64 MemoryReservation int64
} }
+2 -4
View File
@@ -162,8 +162,6 @@ func (s *ServiceSpec) Clone() ServiceSpec {
type ContainerSpec struct { type ContainerSpec struct {
// Command overrides the default CMD of the image to be executed when running a container. // Command overrides the default CMD of the image to be executed when running a container.
Command []string Command []string
// CPU resource allocation for the container.
CPU CPUResources
// Entrypoint overrides the default ENTRYPOINT of the image. // Entrypoint overrides the default ENTRYPOINT of the image.
Entrypoint []string Entrypoint []string
// Env defines the environment variables to set inside the container. // Env defines the environment variables to set inside the container.
@@ -173,13 +171,13 @@ type ContainerSpec struct {
Init *bool Init *bool
// LogDriver overrides the default logging driver for the container. Each Docker daemon can have its own default. // LogDriver overrides the default logging driver for the container. Each Docker daemon can have its own default.
LogDriver *LogDriver LogDriver *LogDriver
// Memory resource allocation for the container.
Memory MemoryResources
// Privileged gives extended privileges to the container. This is a security risk and should be used with caution. // Privileged gives extended privileges to the container. This is a security risk and should be used with caution.
Privileged bool Privileged bool
// PullPolicy determines when to pull the image from the registry or use the image already available in the cluster. // PullPolicy determines when to pull the image from the registry or use the image already available in the cluster.
// Default is PullPolicyMissing if empty. // Default is PullPolicyMissing if empty.
PullPolicy string PullPolicy string
// Resource allocation for the container.
Resources ContainerResources
// User overrides the default user of the image used to run the container. Format: user|UID[:group|GID]. // User overrides the default user of the image used to run the container. Format: user|UID[:group|GID].
User string User string
// VolumeMounts specifies how volumes are mounted into the container filesystem. // VolumeMounts specifies how volumes are mounted into the container filesystem.
+5 -7
View File
@@ -29,12 +29,10 @@ func EvalContainerSpecChange(current api.ServiceSpec, new api.ServiceSpec) Conta
// Pull policy doesn't affect the container configuration. // Pull policy doesn't affect the container configuration.
new.Container.PullPolicy = current.Container.PullPolicy new.Container.PullPolicy = current.Container.PullPolicy
// Save mutable container properties that can be updated without recreation. // Save mutable container resources that can be updated without recreation.
newCPU := new.Container.CPU newResources := new.Container.Resources
newMemory := new.Container.Memory // Temporarily set mutable container resources to current values to check if other properties changed.
// Temporarily set mutable container properties to current values to check if other properties changed. new.Container.Resources = current.Container.Resources
new.Container.CPU = current.Container.CPU
new.Container.Memory = current.Container.Memory
// Check if immutable container properties changed. // Check if immutable container properties changed.
if !current.Container.Equals(new.Container) { if !current.Container.Equals(new.Container) {
@@ -69,7 +67,7 @@ func EvalContainerSpecChange(current api.ServiceSpec, new api.ServiceSpec) Conta
} }
// Check if any mutable properties changed. // Check if any mutable properties changed.
if !reflect.DeepEqual(current.Container.CPU, newCPU) || !reflect.DeepEqual(current.Container.Memory, newMemory) { if !reflect.DeepEqual(current.Container.Resources, newResources) {
return ContainerNeedsUpdate return ContainerNeedsUpdate
} }
+134 -140
View File
@@ -8,39 +8,145 @@ import (
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestEvalContainerSpecChange_ContainerCPU(t *testing.T) { func TestEvalContainerSpecChange_ContainerResources(t *testing.T) {
t.Parallel() t.Parallel()
tests := []struct { tests := []struct {
name string name string
current api.CPUResources current api.ContainerResources
new api.CPUResources new api.ContainerResources
want ContainerSpecStatus want ContainerSpecStatus
}{ }{
{ {
name: "set limit", name: "empty",
current: api.CPUResources{}, current: api.ContainerResources{},
new: api.CPUResources{ new: api.ContainerResources{},
Limit: 1000000000, want: ContainerUpToDate,
},
// CPU
{
name: "set CPU",
current: api.ContainerResources{},
new: api.ContainerResources{
CPU: 1000000000,
}, },
want: ContainerNeedsUpdate, want: ContainerNeedsUpdate,
}, },
{ {
name: "change limit", name: "change CPU",
current: api.CPUResources{ current: api.ContainerResources{
Limit: 1000000000, CPU: 1000000000,
}, },
new: api.CPUResources{ new: api.ContainerResources{
Limit: 2000000000, CPU: 2000000000,
}, },
want: ContainerNeedsUpdate, want: ContainerNeedsUpdate,
}, },
{ {
name: "unset limit", name: "unset CPU",
current: api.CPUResources{ current: api.ContainerResources{
Limit: 1000000000, CPU: 1000000000,
},
new: api.ContainerResources{},
want: ContainerNeedsUpdate,
},
// Memory
{
name: "equal memory",
current: api.ContainerResources{Memory: 100 * 1024 * 1024},
new: api.ContainerResources{Memory: 100 * 1024 * 1024},
want: ContainerUpToDate,
},
{
name: "equal reservation",
current: api.ContainerResources{MemoryReservation: 50 * 1024 * 1024},
new: api.ContainerResources{MemoryReservation: 50 * 1024 * 1024},
want: ContainerUpToDate,
},
{
name: "equal memory and reservation",
current: api.ContainerResources{Memory: 100 * 1024 * 1024, MemoryReservation: 50 * 1024 * 1024},
new: api.ContainerResources{Memory: 100 * 1024 * 1024, MemoryReservation: 50 * 1024 * 1024},
want: ContainerUpToDate,
},
{
name: "set memory",
current: api.ContainerResources{},
new: api.ContainerResources{Memory: 100 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "set reservation",
current: api.ContainerResources{},
new: api.ContainerResources{MemoryReservation: 50 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "set memory and reservation",
current: api.ContainerResources{},
new: api.ContainerResources{Memory: 100 * 1024 * 1024, MemoryReservation: 50 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "change memory",
current: api.ContainerResources{Memory: 100 * 1024 * 1024},
new: api.ContainerResources{Memory: 200 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "change reservation",
current: api.ContainerResources{MemoryReservation: 50 * 1024 * 1024},
new: api.ContainerResources{MemoryReservation: 100 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "change memory and reservation",
current: api.ContainerResources{Memory: 100 * 1024 * 1024, MemoryReservation: 50 * 1024 * 1024},
new: api.ContainerResources{Memory: 200 * 1024 * 1024, MemoryReservation: 100 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "unset memory",
current: api.ContainerResources{Memory: 100 * 1024 * 1024, MemoryReservation: 50 * 1024 * 1024},
new: api.ContainerResources{MemoryReservation: 50 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "unset reservation",
current: api.ContainerResources{Memory: 100 * 1024 * 1024, MemoryReservation: 50 * 1024 * 1024},
new: api.ContainerResources{Memory: 100 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "unset memory and reservation",
current: api.ContainerResources{Memory: 100 * 1024 * 1024, MemoryReservation: 50 * 1024 * 1024},
new: api.ContainerResources{},
want: ContainerNeedsUpdate,
},
// Memory and CPU
{
name: "set CPU and memory",
current: api.ContainerResources{},
new: api.ContainerResources{
CPU: 1000000000,
Memory: 100 * 1024 * 1024,
},
want: ContainerNeedsUpdate,
},
{
name: "update CPU and memory",
current: api.ContainerResources{
CPU: 1000000000,
Memory: 100 * 1024 * 1024,
},
new: api.ContainerResources{
CPU: 2000000000,
Memory: 200 * 1024 * 1024,
MemoryReservation: 100 * 1024 * 1024,
}, },
new: api.CPUResources{},
want: ContainerNeedsUpdate, want: ContainerNeedsUpdate,
}, },
} }
@@ -49,14 +155,14 @@ func TestEvalContainerSpecChange_ContainerCPU(t *testing.T) {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
currentSpec := api.ServiceSpec{ currentSpec := api.ServiceSpec{
Container: api.ContainerSpec{ Container: api.ContainerSpec{
Image: "nginx:latest", Image: "nginx:latest",
CPU: tt.current, Resources: tt.current,
}, },
} }
newSpec := api.ServiceSpec{ newSpec := api.ServiceSpec{
Container: api.ContainerSpec{ Container: api.ContainerSpec{
Image: "nginx:latest", Image: "nginx:latest",
CPU: tt.new, Resources: tt.new,
}, },
} }
@@ -199,116 +305,6 @@ func TestEvalContainerSpecChange_ContainerLogDriver(t *testing.T) {
} }
} }
func TestEvalContainerSpecChange_ContainerMemory(t *testing.T) {
t.Parallel()
tests := []struct {
name string
current api.MemoryResources
new api.MemoryResources
want ContainerSpecStatus
}{
{
name: "empty",
current: api.MemoryResources{},
new: api.MemoryResources{},
want: ContainerUpToDate,
},
{
name: "equal limit",
current: api.MemoryResources{Limit: 100 * 1024 * 1024},
new: api.MemoryResources{Limit: 100 * 1024 * 1024},
want: ContainerUpToDate,
},
{
name: "equal reservation",
current: api.MemoryResources{Reservation: 50 * 1024 * 1024},
new: api.MemoryResources{Reservation: 50 * 1024 * 1024},
want: ContainerUpToDate,
},
{
name: "equal limit and reservation",
current: api.MemoryResources{Limit: 100 * 1024 * 1024, Reservation: 50 * 1024 * 1024},
new: api.MemoryResources{Limit: 100 * 1024 * 1024, Reservation: 50 * 1024 * 1024},
want: ContainerUpToDate,
},
{
name: "set limit",
current: api.MemoryResources{},
new: api.MemoryResources{Limit: 100 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "set reservation",
current: api.MemoryResources{},
new: api.MemoryResources{Reservation: 50 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "set limit and reservation",
current: api.MemoryResources{},
new: api.MemoryResources{Limit: 100 * 1024 * 1024, Reservation: 50 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "change limit",
current: api.MemoryResources{Limit: 100 * 1024 * 1024},
new: api.MemoryResources{Limit: 200 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "change reservation",
current: api.MemoryResources{Reservation: 50 * 1024 * 1024},
new: api.MemoryResources{Reservation: 100 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "change limit and reservation",
current: api.MemoryResources{Limit: 100 * 1024 * 1024, Reservation: 50 * 1024 * 1024},
new: api.MemoryResources{Limit: 200 * 1024 * 1024, Reservation: 100 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "unset limit",
current: api.MemoryResources{Limit: 100 * 1024 * 1024, Reservation: 50 * 1024 * 1024},
new: api.MemoryResources{Reservation: 50 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "unset reservation",
current: api.MemoryResources{Limit: 100 * 1024 * 1024, Reservation: 50 * 1024 * 1024},
new: api.MemoryResources{Limit: 100 * 1024 * 1024},
want: ContainerNeedsUpdate,
},
{
name: "unset limit and reservation",
current: api.MemoryResources{Limit: 100 * 1024 * 1024, Reservation: 50 * 1024 * 1024},
new: api.MemoryResources{},
want: ContainerNeedsUpdate,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
currentSpec := api.ServiceSpec{
Container: api.ContainerSpec{
Image: "nginx:latest",
Memory: tt.current,
},
}
newSpec := api.ServiceSpec{
Container: api.ContainerSpec{
Image: "nginx:latest",
Memory: tt.new,
},
}
result := EvalContainerSpecChange(currentSpec, newSpec)
assert.Equal(t, tt.want, result)
})
}
}
func TestEvalContainerSpecChange_ContainerPrivileged(t *testing.T) { func TestEvalContainerSpecChange_ContainerPrivileged(t *testing.T) {
t.Parallel() t.Parallel()
@@ -1321,20 +1317,18 @@ func TestEvalContainerSpecChange_Mixed(t *testing.T) {
current: api.ServiceSpec{ current: api.ServiceSpec{
Container: api.ContainerSpec{ Container: api.ContainerSpec{
Image: "nginx:latest", Image: "nginx:latest",
Memory: api.MemoryResources{ Resources: api.ContainerResources{
Limit: 100 * 1024 * 1024, Memory: 100 * 1024 * 1024,
}, },
}, },
}, },
new: api.ServiceSpec{ new: api.ServiceSpec{
Container: api.ContainerSpec{ Container: api.ContainerSpec{
Image: "nginx:latest", Image: "nginx:latest",
CPU: api.CPUResources{ Resources: api.ContainerResources{
Limit: 1000000000, CPU: 1000000000,
}, Memory: 200 * 1024 * 1024,
Memory: api.MemoryResources{ MemoryReservation: 100 * 1024 * 1024,
Limit: 200 * 1024 * 1024,
Reservation: 100 * 1024 * 1024,
}, },
}, },
}, },
@@ -1350,8 +1344,8 @@ func TestEvalContainerSpecChange_Mixed(t *testing.T) {
new: api.ServiceSpec{ new: api.ServiceSpec{
Container: api.ContainerSpec{ Container: api.ContainerSpec{
Image: "nginx:latest", Image: "nginx:latest",
CPU: api.CPUResources{ Resources: api.ContainerResources{
Limit: 1000000000, CPU: 1000000000,
}, },
User: "root", User: "root",
}, },