diff --git a/providers/azure/aci.go b/providers/azure/aci.go index 58b533979..1884f94fc 100644 --- a/providers/azure/aci.go +++ b/providers/azure/aci.go @@ -676,10 +676,8 @@ func (p *ACIProvider) getContainers(pod *v1.Pod) ([]aci.Container, error) { c.EnvironmentVariables = make([]aci.EnvironmentVariable, 0, len(container.Env)) for _, e := range container.Env { - c.EnvironmentVariables = append(c.EnvironmentVariables, aci.EnvironmentVariable{ - Name: e.Name, - Value: e.Value, - }) + envVar := getACIEnvVar(e) + c.EnvironmentVariables = append(c.EnvironmentVariables, envVar) } // NOTE(robbiezhang): ACI CPU request must be times of 10m @@ -724,11 +722,67 @@ func (p *ACIProvider) getContainers(pod *v1.Pod) ([]aci.Container, error) { } } + if container.LivenessProbe != nil { + probe, err := getProbe(container.LivenessProbe) + if err != nil { + return nil, err + } + c.LivenessProbe = probe + } + + if container.ReadinessProbe != nil { + probe, err := getProbe(container.ReadinessProbe) + if err != nil { + return nil, err + } + c.ReadinessProbe = probe + } + containers = append(containers, c) } return containers, nil } +func getProbe(probe *v1.Probe) (*aci.ContainerProbe, error) { + + if probe.Handler.Exec != nil && probe.Handler.HTTPGet != nil { + return nil, fmt.Errorf("probe may not specify more than one of \"exec\" and \"httpGet\"") + } + + if probe.Handler.Exec == nil && probe.Handler.HTTPGet == nil { + return nil, fmt.Errorf("probe must specify one of \"exec\" and \"httpGet\"") + } + + // Probes have can have a Exec or HTTP Get Handler. + // Create those if they exist, then add to the + // ContainerProbe struct + var exec *aci.ContainerExecProbe + if probe.Handler.Exec != nil { + exec = &aci.ContainerExecProbe{ + Command: probe.Handler.Exec.Command, + } + } + + var httpGET *aci.ContainerHTTPGetProbe + if probe.Handler.HTTPGet != nil { + httpGET = &aci.ContainerHTTPGetProbe{ + Port: probe.Handler.HTTPGet.Port.IntValue(), + Path: probe.Handler.HTTPGet.Path, + Scheme: string(probe.Handler.HTTPGet.Scheme), + } + } + + return &aci.ContainerProbe{ + Exec: exec, + HTTPGet: httpGET, + InitialDelaySeconds: probe.InitialDelaySeconds, + Period: probe.PeriodSeconds, + FailureThreshold: probe.FailureThreshold, + SuccessThreshold: probe.SuccessThreshold, + TimeoutSeconds: probe.TimeoutSeconds, + }, nil +} + func (p *ACIProvider) getVolumes(pod *v1.Pod) ([]aci.Volume, error) { volumes := make([]aci.Volume, 0, len(pod.Spec.Volumes)) for _, v := range pod.Spec.Volumes { @@ -1058,3 +1112,20 @@ func filterServiceAccountSecretVolume(osType string, containerGroup *aci.Contain containerGroup.ContainerGroupProperties.Volumes = volumes } } + +func getACIEnvVar(e v1.EnvVar) aci.EnvironmentVariable { + var envVar aci.EnvironmentVariable + // If the variable is a secret, use SecureValue + if e.ValueFrom.SecretKeyRef != nil { + envVar = aci.EnvironmentVariable{ + Name: e.Name, + SecureValue: e.Value, + } + } else { + envVar = aci.EnvironmentVariable{ + Name: e.Name, + Value: e.Value, + } + } + return envVar +} diff --git a/providers/azure/aci_test.go b/providers/azure/aci_test.go index 81d7e3722..6a030c219 100644 --- a/providers/azure/aci_test.go +++ b/providers/azure/aci_test.go @@ -20,6 +20,7 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/kubernetes/fake" ) @@ -363,6 +364,58 @@ func TestGetPodWithoutResourceRequestsLimits(t *testing.T) { "Containers[0].Resources.Requests.Memory doesn't match") } +func TestPodToACISecretEnvVar(t *testing.T) { + + testKey := "testVar" + testVal := "testVal" + + e := v1.EnvVar{ + Name: testKey, + Value: testVal, + ValueFrom: &v1.EnvVarSource{ + SecretKeyRef: &v1.SecretKeySelector{}, + }, + } + aciEnvVar := getACIEnvVar(e) + + if aciEnvVar.Value != "" { + t.Fatalf("ACI Env Variable Value should be empty for a secret") + } + + if aciEnvVar.Name != testKey { + t.Fatalf("ACI Env Variable Name does not match expected Name") + } + + if aciEnvVar.SecureValue != testVal { + t.Fatalf("ACI Env Variable Secure Value does not match expected value") + } +} + +func TestPodToACIEnvVar(t *testing.T) { + + testKey := "testVar" + testVal := "testVal" + + e := v1.EnvVar{ + Name: testKey, + Value: testVal, + ValueFrom: &v1.EnvVarSource{}, + } + aciEnvVar := getACIEnvVar(e) + + if aciEnvVar.SecureValue != "" { + t.Fatalf("ACI Env Variable Secure Value should be empty for non-secret variables") + } + + if aciEnvVar.Name != testKey { + t.Fatalf("ACI Env Variable Name does not match expected Name") + } + + if aciEnvVar.Value != testVal { + t.Fatalf("ACI Env Variable Value does not match expected value") + } +} + func prepareMocks() (*AADMock, *ACIMock, *ACIProvider, error) { aadServerMocker := NewAADMock() aciServerMocker := NewACIMock() @@ -408,3 +461,127 @@ func prepareMocks() (*AADMock, *ACIMock, *ACIProvider, error) { func ptrQuantity(q resource.Quantity) *resource.Quantity { return &q } + +func TestCreatePodWithLivenessProbe(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.Containers[0].LivenessProbe, "Liveness probe expected") + assert.Equal(t, cg.Containers[0].LivenessProbe.InitialDelaySeconds, 10, "Initial Probe Delay doesn't match") + assert.Equal(t, cg.Containers[0].LivenessProbe.Period, 5, "Probe Period doesn't match") + assert.Equal(t, cg.Containers[0].LivenessProbe.TimeoutSeconds, 60, "Probe Timeout doesn't match") + assert.Equal(t, cg.Containers[0].LivenessProbe.SuccessThreshold, 3, "Probe Success Threshold doesn't match") + assert.Equal(t, cg.Containers[0].LivenessProbe.FailureThreshold, 5, "Probe Failure Threshold doesn't match") + assert.NotNil(t, cg.Containers[0].LivenessProbe.HTTPGet, "Expected an HTTP Get Probe") + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: podNamespace, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + v1.Container{ + Name: "nginx", + LivenessProbe: &v1.Probe{ + Handler: v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Port: intstr.FromString("8080"), + Path: "/", + }, + }, + InitialDelaySeconds: 10, + PeriodSeconds: 5, + TimeoutSeconds: 60, + SuccessThreshold: 3, + FailureThreshold: 5, + }, + }, + }, + }, + } + + if err := provider.CreatePod(pod); err != nil { + t.Fatal("Failed to create pod", err) + } + + return http.StatusOK, cg + } +} + +func TestCreatePodWithReadinessProbe(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.Containers[0].ReadinessProbe, "Readiness probe expected") + assert.Equal(t, cg.Containers[0].ReadinessProbe.InitialDelaySeconds, 10, "Initial Probe Delay doesn't match") + assert.Equal(t, cg.Containers[0].ReadinessProbe.Period, 5, "Probe Period doesn't match") + assert.Equal(t, cg.Containers[0].ReadinessProbe.TimeoutSeconds, 60, "Probe Timeout doesn't match") + assert.Equal(t, cg.Containers[0].ReadinessProbe.SuccessThreshold, 3, "Probe Success Threshold doesn't match") + assert.Equal(t, cg.Containers[0].ReadinessProbe.FailureThreshold, 5, "Probe Failure Threshold doesn't match") + assert.NotNil(t, cg.Containers[0].ReadinessProbe.HTTPGet, "Expected an HTTP Get Probe") + + pod := &v1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: podName, + Namespace: podNamespace, + }, + Spec: v1.PodSpec{ + Containers: []v1.Container{ + v1.Container{ + Name: "nginx", + ReadinessProbe: &v1.Probe{ + Handler: v1.Handler{ + HTTPGet: &v1.HTTPGetAction{ + Port: intstr.FromString("8080"), + Path: "/", + }, + }, + InitialDelaySeconds: 10, + PeriodSeconds: 5, + TimeoutSeconds: 60, + SuccessThreshold: 3, + FailureThreshold: 5, + }, + }, + }, + }, + } + + if err := provider.CreatePod(pod); err != nil { + t.Fatal("Failed to create pod", err) + } + + return http.StatusOK, cg + } +}