diff --git a/providers/azure/aci.go b/providers/azure/aci.go index ddcf52553..2690cc625 100644 --- a/providers/azure/aci.go +++ b/providers/azure/aci.go @@ -564,24 +564,6 @@ func (p *ACIProvider) getContainers(pod *v1.Pod) ([]aci.Container, error) { }) } - // NOTE(robbiezhang): ACI CPU limit must be times of 10m - cpuLimit := 1.00 - if _, ok := container.Resources.Limits[v1.ResourceCPU]; ok { - cpuLimit = float64(container.Resources.Limits.Cpu().MilliValue()/10.00) / 100.00 - if cpuLimit < 0.01 { - cpuLimit = 0.01 - } - } - - // NOTE(robbiezhang): ACI Memory limit must be times of 0.1 GB - memoryLimit := 1.50 - if _, ok := container.Resources.Limits[v1.ResourceMemory]; ok { - memoryLimit = float64(container.Resources.Limits.Memory().Value()/100000000.00) / 10.00 - if memoryLimit < 0.10 { - memoryLimit = 0.10 - } - } - // NOTE(robbiezhang): ACI CPU request must be times of 10m cpuRequest := 1.00 if _, ok := container.Resources.Requests[v1.ResourceCPU]; ok { @@ -601,16 +583,29 @@ func (p *ACIProvider) getContainers(pod *v1.Pod) ([]aci.Container, error) { } c.Resources = aci.ResourceRequirements{ - Limits: aci.ResourceLimits{ - CPU: cpuLimit, - MemoryInGB: memoryLimit, - }, - Requests: aci.ResourceRequests{ + Requests: &aci.ResourceRequests{ CPU: cpuRequest, MemoryInGB: memoryRequest, }, } + if container.Resources.Limits != nil { + cpuLimit := cpuRequest + if _, ok := container.Resources.Limits[v1.ResourceCPU]; ok { + cpuLimit = float64(container.Resources.Limits.Cpu().MilliValue()) / 1000.00 + } + + memoryLimit := memoryRequest + if _, ok := container.Resources.Limits[v1.ResourceMemory]; ok { + memoryLimit = float64(container.Resources.Limits.Memory().Value()) / 1000000000.00 + } + + c.Resources.Limits = &aci.ResourceLimits{ + CPU: cpuLimit, + MemoryInGB: memoryLimit, + } + } + containers = append(containers, c) } return containers, nil diff --git a/providers/azure/aci_test.go b/providers/azure/aci_test.go index 139cef3d0..9ebbc66b3 100644 --- a/providers/azure/aci_test.go +++ b/providers/azure/aci_test.go @@ -55,8 +55,7 @@ func TestCreatePodWithoutResourceSpec(t *testing.T) { assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources.Requests, "Container resource requests should not be nil") assert.Equal(t, 1.0, cg.ContainerGroupProperties.Containers[0].Resources.Requests.CPU, "Request CPU is not expected") assert.Equal(t, 1.5, cg.ContainerGroupProperties.Containers[0].Resources.Requests.MemoryInGB, "Request Memory is not expected") - assert.Equal(t, 1.0, cg.ContainerGroupProperties.Containers[0].Resources.Limits.CPU, "Limit CPU is not expected") - assert.Equal(t, 1.5, cg.ContainerGroupProperties.Containers[0].Resources.Limits.MemoryInGB, "Limit Memory is not expected") + assert.Nil(t, cg.ContainerGroupProperties.Containers[0].Resources.Limits, "Limits should be nil") return http.StatusOK, cg } @@ -80,8 +79,8 @@ func TestCreatePodWithoutResourceSpec(t *testing.T) { } } -// Tests create pod without resource spec -func TestCreatePodWithResourceSpec(t *testing.T) { +// Tests create pod with resource request only +func TestCreatePodWithResourceRequestOnly(t *testing.T) { _, aciServerMocker, provider, err := prepareMocks() if err != nil { @@ -104,8 +103,7 @@ func TestCreatePodWithResourceSpec(t *testing.T) { assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources.Requests, "Container resource requests should not be nil") assert.Equal(t, 1.98, cg.ContainerGroupProperties.Containers[0].Resources.Requests.CPU, "Request CPU is not expected") assert.Equal(t, 3.4, cg.ContainerGroupProperties.Containers[0].Resources.Requests.MemoryInGB, "Request Memory is not expected") - assert.Equal(t, 3.99, cg.ContainerGroupProperties.Containers[0].Resources.Limits.CPU, "Limit CPU is not expected") - assert.Equal(t, 8.0, cg.ContainerGroupProperties.Containers[0].Resources.Limits.MemoryInGB, "Limit Memory is not expected") + assert.Nil(t, cg.ContainerGroupProperties.Containers[0].Resources.Limits, "Limits should be nil") return http.StatusOK, cg } @@ -121,8 +119,63 @@ func TestCreatePodWithResourceSpec(t *testing.T) { Name: "nginx", Resources: v1.ResourceRequirements{ Requests: v1.ResourceList{ - "cpu": resource.MustParse("1.98"), - "memory": resource.MustParse("3.4G"), + "cpu": resource.MustParse("1.981"), + "memory": resource.MustParse("3.49G"), + }, + }, + }, + }, + }, + } + + if err := provider.CreatePod(pod); err != nil { + t.Fatal("Failed to create pod", err) + } +} + +// Tests create pod with both resource request and limit. +func TestCreatePodWithResourceRequestAndLimit(t *testing.T) { + _, aciServerMocker, provider, err := prepareMocks() + + if err != nil { + t.Fatal("Unable to prepare the mocks", err) + } + + podName := "pod-" + uuid.New().String() + podNamespace := "ns-" + uuid.New().String() + + aciServerMocker.OnCreate = func(subscription, resourceGroup, containerGroup string, cg *aci.ContainerGroup) (int, interface{}) { + assert.Equal(t, fakeSubscription, subscription, "Subscription doesn't match") + assert.Equal(t, fakeResourceGroup, resourceGroup, "Resource group doesn't match") + assert.NotNil(t, cg, "Container group is nil") + assert.Equal(t, podNamespace + "-" + podName, containerGroup, "Container group name is not expected") + assert.NotNil(t, cg.ContainerGroupProperties, "Container group properties should not be nil") + assert.NotNil(t, cg.ContainerGroupProperties.Containers, "Containers should not be nil") + assert.Equal(t, 1, len(cg.ContainerGroupProperties.Containers), "1 Container is expected") + assert.Equal(t, "nginx", cg.ContainerGroupProperties.Containers[0].Name, "Container nginx is expected") + assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources, "Container resources should not be nil") + assert.NotNil(t, cg.ContainerGroupProperties.Containers[0].Resources.Requests, "Container resource requests should not be nil") + assert.Equal(t, 1.98, cg.ContainerGroupProperties.Containers[0].Resources.Requests.CPU, "Request CPU is not expected") + assert.Equal(t, 3.4, cg.ContainerGroupProperties.Containers[0].Resources.Requests.MemoryInGB, "Request Memory is not expected") + assert.Equal(t, 3.999, cg.ContainerGroupProperties.Containers[0].Resources.Limits.CPU, "Limit CPU is not expected") + assert.Equal(t, 8.01, cg.ContainerGroupProperties.Containers[0].Resources.Limits.MemoryInGB, "Limit Memory is not expected") + + return http.StatusOK, cg + } + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: podNamespace, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + v1.Container{ + Name: "nginx", + Resources: v1.ResourceRequirements{ + Requests: v1.ResourceList{ + "cpu": resource.MustParse("1.981"), + "memory": resource.MustParse("3.49G"), }, Limits: v1.ResourceList{ "cpu": resource.MustParse("3999m"), diff --git a/providers/azure/client/aci/client_test.go b/providers/azure/client/aci/client_test.go index dc51efd0d..b4b401792 100644 --- a/providers/azure/client/aci/client_test.go +++ b/providers/azure/client/aci/client_test.go @@ -107,11 +107,51 @@ func TestCreateContainerGroupFails(t *testing.T) { }, }) if err == nil { - t.Fatal("expected create container group to fail with ResourceSomeRequestsNotSpecified, but returned nil") + t.Fatal("expected create container group to fail with ResourceRequestsNotSpecified, but returned nil") } - if !strings.Contains(err.Error(), "ResourceSomeRequestsNotSpecified") { - t.Fatalf("expected ResourceSomeRequestsNotSpecified to be in the error message but got: %v", err) + if !strings.Contains(err.Error(), "ResourceRequestsNotSpecified") { + t.Fatalf("expected ResourceRequestsNotSpecified to be in the error message but got: %v", err) + } +} + +func TestCreateContainerGroupWithoutResourceLimit(t *testing.T) { + cg, err := client.CreateContainerGroup(resourceGroup, containerGroup, ContainerGroup{ + Location: location, + ContainerGroupProperties: ContainerGroupProperties{ + OsType: Linux, + Containers: []Container{ + { + Name: "nginx", + ContainerProperties: ContainerProperties{ + Image: "nginx", + Command: []string{"nginx", "-g", "daemon off;"}, + Ports: []ContainerPort{ + { + Protocol: ContainerNetworkProtocolTCP, + Port: 80, + }, + }, + Resources: ResourceRequirements{ + Requests: &ResourceRequests{ + CPU: 1, + MemoryInGB: 1, + }, + }, + }, + }, + }, + }, + }) + if err != nil { + t.Fatal(err) + } + if cg.Name != containerGroup { + t.Fatalf("resource group name is %s, expected %s", cg.Name, containerGroup) + } + + if err := client.DeleteContainerGroup(resourceGroup, containerGroup); err != nil { + t.Fatal(err) } } @@ -133,11 +173,11 @@ func TestCreateContainerGroup(t *testing.T) { }, }, Resources: ResourceRequirements{ - Requests: ResourceRequests{ + Requests: &ResourceRequests{ CPU: 1, MemoryInGB: 1, }, - Limits: ResourceLimits{ + Limits: &ResourceLimits{ CPU: 1, MemoryInGB: 1, }, diff --git a/providers/azure/client/aci/types.go b/providers/azure/client/aci/types.go index 9f7793431..a01723f87 100644 --- a/providers/azure/client/aci/types.go +++ b/providers/azure/client/aci/types.go @@ -236,8 +236,8 @@ type ResourceRequests struct { // ResourceRequirements is the resource requirements. type ResourceRequirements struct { - Requests ResourceRequests `json:"requests,omitempty"` - Limits ResourceLimits `json:"limits,omitempty"` + Requests *ResourceRequests `json:"requests,omitempty"` + Limits *ResourceLimits `json:"limits,omitempty"` } // Usage is a single usage result