| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563 |
- /*
- Copyright 2020 Docker Compose CLI authors
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
- http://www.apache.org/licenses/LICENSE-2.0
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- */
- package convert
- import (
- "context"
- "os"
- "testing"
- "time"
- "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
- "github.com/Azure/go-autorest/autorest/to"
- "github.com/compose-spec/compose-go/types"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- "github.com/docker/compose-cli/api/compose"
- "github.com/docker/compose-cli/api/containers"
- "github.com/docker/compose-cli/context/store"
- )
- var (
- convertCtx = store.AciContext{
- SubscriptionID: "subID",
- ResourceGroup: "rg",
- Location: "eu",
- }
- mockStorageHelper = &mockStorageLogin{}
- )
- func TestProjectName(t *testing.T) {
- project := types.Project{
- Name: "TEST",
- }
- containerGroup, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- assert.Equal(t, *containerGroup.Name, "test")
- }
- func TestContainerGroupToContainer(t *testing.T) {
- myContainerGroup := containerinstance.ContainerGroup{
- ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
- IPAddress: &containerinstance.IPAddress{
- Ports: &[]containerinstance.Port{{
- Port: to.Int32Ptr(80),
- }},
- IP: to.StringPtr("42.42.42.42"),
- DNSNameLabel: to.StringPtr("myapp"),
- },
- OsType: "Linux",
- },
- }
- myContainer := containerinstance.Container{
- Name: to.StringPtr("myContainerID"),
- ContainerProperties: &containerinstance.ContainerProperties{
- Image: to.StringPtr("sha256:666"),
- Command: to.StringSlicePtr([]string{"mycommand"}),
- Ports: &[]containerinstance.ContainerPort{{
- Port: to.Int32Ptr(80),
- }},
- EnvironmentVariables: nil,
- InstanceView: &containerinstance.ContainerPropertiesInstanceView{
- RestartCount: nil,
- CurrentState: &containerinstance.ContainerState{
- State: to.StringPtr("Running"),
- },
- },
- Resources: &containerinstance.ResourceRequirements{
- Limits: &containerinstance.ResourceLimits{
- CPU: to.Float64Ptr(3),
- MemoryInGB: to.Float64Ptr(0.2),
- },
- Requests: &containerinstance.ResourceRequests{
- CPU: to.Float64Ptr(2),
- MemoryInGB: to.Float64Ptr(0.1),
- },
- },
- LivenessProbe: &containerinstance.ContainerProbe{
- Exec: &containerinstance.ContainerExec{
- Command: to.StringSlicePtr([]string{
- "my",
- "command",
- "--option",
- }),
- },
- PeriodSeconds: to.Int32Ptr(10),
- SuccessThreshold: to.Int32Ptr(3),
- InitialDelaySeconds: to.Int32Ptr(2),
- TimeoutSeconds: to.Int32Ptr(1),
- },
- },
- }
- var expectedContainer = containers.Container{
- ID: "myContainerID",
- Status: "Running",
- Image: "sha256:666",
- Command: "mycommand",
- Platform: "Linux",
- Ports: []containers.Port{{
- HostPort: uint32(80),
- ContainerPort: uint32(80),
- Protocol: "tcp",
- HostIP: "42.42.42.42",
- }},
- Config: &containers.RuntimeConfig{
- FQDN: "myapp.eastus.azurecontainer.io",
- },
- HostConfig: &containers.HostConfig{
- CPULimit: 3,
- CPUReservation: 2,
- MemoryLimit: gbToBytes(0.2),
- MemoryReservation: gbToBytes(0.1),
- RestartPolicy: "any",
- },
- Healthcheck: containers.Healthcheck{
- Disable: false,
- Test: []string{
- "my",
- "command",
- "--option",
- },
- Interval: types.Duration(10 * time.Second),
- Retries: 3,
- StartPeriod: types.Duration(2 * time.Second),
- Timeout: types.Duration(time.Second),
- },
- }
- container := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer, "eastus")
- assert.DeepEqual(t, container, expectedContainer)
- }
- func TestHealthcheckTranslation(t *testing.T) {
- test := []string{
- "my",
- "command",
- "--option",
- }
- interval := types.Duration(10 * time.Second)
- retries := uint64(42)
- startPeriod := types.Duration(2 * time.Second)
- timeout := types.Duration(3 * time.Second)
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- HealthCheck: &types.HealthCheckConfig{
- Test: test,
- Timeout: &timeout,
- Interval: &interval,
- Retries: &retries,
- StartPeriod: &startPeriod,
- Disable: false,
- },
- },
- },
- }
- testHealthcheckTestPrefixRemoval := func(test []string, shellPreffix ...string) {
- project.Services[0].HealthCheck.Test = append(shellPreffix, test...)
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- assert.DeepEqual(t, (*group.Containers)[0].LivenessProbe.Exec.Command, to.StringSlicePtr(test))
- assert.Equal(t, *(*group.Containers)[0].LivenessProbe.PeriodSeconds, int32(10))
- assert.Equal(t, *(*group.Containers)[0].LivenessProbe.SuccessThreshold, int32(42))
- assert.Equal(t, *(*group.Containers)[0].LivenessProbe.FailureThreshold, int32(42))
- assert.Equal(t, *(*group.Containers)[0].LivenessProbe.InitialDelaySeconds, int32(2))
- assert.Equal(t, *(*group.Containers)[0].LivenessProbe.TimeoutSeconds, int32(3))
- }
- testHealthcheckTestPrefixRemoval(test)
- testHealthcheckTestPrefixRemoval(test, "NONE")
- testHealthcheckTestPrefixRemoval(test, "CMD")
- testHealthcheckTestPrefixRemoval(test, "CMD-SHELL")
- project.Services[0].HealthCheck.Disable = true
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- assert.Assert(t, (*group.Containers)[0].LivenessProbe == nil)
- }
- func TestContainerGroupToServiceStatus(t *testing.T) {
- myContainerGroup := containerinstance.ContainerGroup{
- ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
- IPAddress: &containerinstance.IPAddress{
- Ports: &[]containerinstance.Port{{
- Port: to.Int32Ptr(80),
- }},
- IP: to.StringPtr("42.42.42.42"),
- },
- },
- }
- myContainer := containerinstance.Container{
- Name: to.StringPtr("myContainerID"),
- ContainerProperties: &containerinstance.ContainerProperties{
- Ports: &[]containerinstance.ContainerPort{{
- Port: to.Int32Ptr(80),
- }},
- InstanceView: &containerinstance.ContainerPropertiesInstanceView{
- RestartCount: nil,
- CurrentState: &containerinstance.ContainerState{
- State: to.StringPtr("Running"),
- },
- },
- },
- }
- var expectedService = compose.ServiceStatus{
- ID: "myContainerID",
- Name: "myContainerID",
- Ports: []string{"42.42.42.42:80->80/tcp"},
- Replicas: 1,
- Desired: 1,
- }
- container := ContainerGroupToServiceStatus("myContainerID", myContainerGroup, myContainer, "eastus")
- assert.DeepEqual(t, container, expectedService)
- }
- func TestComposeContainerGroupToContainerWithDnsSideCarSide(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- },
- {
- Name: "service2",
- Image: "image2",
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- assert.Assert(t, is.Len(*group.Containers, 3))
- assert.Equal(t, *(*group.Containers)[0].Name, "service1")
- assert.Equal(t, *(*group.Containers)[1].Name, "service2")
- assert.Equal(t, *(*group.Containers)[2].Name, ComposeDNSSidecarName)
- assert.DeepEqual(t, *(*group.Containers)[2].Command, []string{"/hosts", "service1", "service2"})
- assert.Equal(t, *(*group.Containers)[0].Image, "image1")
- assert.Equal(t, *(*group.Containers)[1].Image, "image2")
- assert.Equal(t, *(*group.Containers)[2].Image, dnsSidecarImage)
- }
- func TestComposeSingleContainerGroupToContainerNoDnsSideCarSide(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- assert.Assert(t, is.Len(*group.Containers, 1))
- assert.Equal(t, *(*group.Containers)[0].Name, "service1")
- assert.Equal(t, *(*group.Containers)[0].Image, "image1")
- }
- func TestLabelsErrorMessage(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- Labels: map[string]string{
- "label1": "value1",
- },
- },
- },
- }
- _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.Error(t, err, "ACI integration does not support labels in compose applications")
- }
- func TestComposeContainerGroupToContainerWithDomainName(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- Ports: []types.ServicePortConfig{
- {
- Published: 80,
- Target: 80,
- },
- },
- DomainName: "myApp",
- },
- {
- Name: "service2",
- Image: "image2",
- Ports: []types.ServicePortConfig{
- {
- Published: 8080,
- Target: 8080,
- },
- },
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- assert.Assert(t, is.Len(*group.Containers, 3))
- groupPorts := *group.IPAddress.Ports
- assert.Assert(t, is.Len(groupPorts, 2))
- assert.Equal(t, *groupPorts[0].Port, int32(80))
- assert.Equal(t, *groupPorts[1].Port, int32(8080))
- assert.Equal(t, *group.IPAddress.DNSNameLabel, "myApp")
- }
- func TestComposeContainerGroupToContainerErrorWhenSeveralDomainNames(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- DomainName: "myApp",
- },
- {
- Name: "service2",
- Image: "image2",
- DomainName: "myApp2",
- },
- },
- }
- _, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.Error(t, err, "ACI integration does not support specifying different domain names on services in the same compose application")
- }
- // ACI fails if group definition IPAddress has no ports
- func TestComposeContainerGroupToContainerIgnoreDomainNameWithoutPorts(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- DomainName: "myApp",
- },
- {
- Name: "service2",
- Image: "image2",
- DomainName: "myApp",
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- assert.Assert(t, is.Len(*group.Containers, 3))
- assert.Assert(t, group.IPAddress == nil)
- }
- var _0_1Gb = gbToBytes(0.1)
- func TestComposeContainerGroupToContainerResourceRequests(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- Deploy: &types.DeployConfig{
- Resources: types.Resources{
- Reservations: &types.Resource{
- NanoCPUs: "0.1",
- MemoryBytes: types.UnitBytes(_0_1Gb),
- },
- },
- },
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- request := *((*group.Containers)[0]).Resources.Requests
- assert.Equal(t, *request.CPU, float64(0.1))
- assert.Equal(t, *request.MemoryInGB, float64(0.1))
- limits := *((*group.Containers)[0]).Resources.Limits
- assert.Equal(t, *limits.CPU, float64(0.1))
- assert.Equal(t, *limits.MemoryInGB, float64(0.1))
- }
- func TestComposeContainerGroupToContainerResourceRequestsAndLimits(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- Deploy: &types.DeployConfig{
- Resources: types.Resources{
- Reservations: &types.Resource{
- NanoCPUs: "0.1",
- MemoryBytes: types.UnitBytes(_0_1Gb),
- },
- Limits: &types.Resource{
- NanoCPUs: "0.3",
- MemoryBytes: types.UnitBytes(2 * _0_1Gb),
- },
- },
- },
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- request := *((*group.Containers)[0]).Resources.Requests
- assert.Equal(t, *request.CPU, float64(0.1))
- assert.Equal(t, *request.MemoryInGB, float64(0.1))
- limits := *((*group.Containers)[0]).Resources.Limits
- assert.Equal(t, *limits.CPU, float64(0.3))
- assert.Equal(t, *limits.MemoryInGB, float64(0.2))
- }
- func TestComposeContainerGroupToContainerResourceLimitsOnly(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- Deploy: &types.DeployConfig{
- Resources: types.Resources{
- Limits: &types.Resource{
- NanoCPUs: "0.3",
- MemoryBytes: types.UnitBytes(2 * _0_1Gb),
- },
- },
- },
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- request := *((*group.Containers)[0]).Resources.Requests
- assert.Equal(t, *request.CPU, float64(0.3))
- assert.Equal(t, *request.MemoryInGB, float64(0.2))
- limits := *((*group.Containers)[0]).Resources.Limits
- assert.Equal(t, *limits.CPU, float64(0.3))
- assert.Equal(t, *limits.MemoryInGB, float64(0.2))
- }
- func TestComposeContainerGroupToContainerResourceRequestsDefaults(t *testing.T) {
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- Deploy: &types.DeployConfig{
- Resources: types.Resources{
- Reservations: &types.Resource{
- NanoCPUs: "",
- MemoryBytes: 0,
- },
- },
- },
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- request := *((*group.Containers)[0]).Resources.Requests
- assert.Equal(t, *request.CPU, float64(1))
- assert.Equal(t, *request.MemoryInGB, float64(1))
- }
- func TestComposeContainerGroupToContainerenvVar(t *testing.T) {
- err := os.Setenv("key2", "value2")
- assert.NilError(t, err)
- project := types.Project{
- Services: []types.ServiceConfig{
- {
- Name: "service1",
- Image: "image1",
- Environment: types.MappingWithEquals{
- "key1": to.StringPtr("value1"),
- "key2": nil,
- },
- },
- },
- }
- group, err := ToContainerGroup(context.TODO(), convertCtx, project, mockStorageHelper)
- assert.NilError(t, err)
- envVars := *((*group.Containers)[0]).EnvironmentVariables
- assert.Assert(t, is.Len(envVars, 2))
- assert.Assert(t, is.Contains(envVars, containerinstance.EnvironmentVariable{Name: to.StringPtr("key1"), Value: to.StringPtr("value1")}))
- assert.Assert(t, is.Contains(envVars, containerinstance.EnvironmentVariable{Name: to.StringPtr("key2"), Value: to.StringPtr("value2")}))
- }
- func TestConvertContainerGroupStatus(t *testing.T) {
- assert.Equal(t, "Running", GetStatus(container(to.StringPtr("Running")), group(to.StringPtr("Started"))))
- assert.Equal(t, "Terminated", GetStatus(container(to.StringPtr("Terminated")), group(to.StringPtr("Stopped"))))
- assert.Equal(t, "Node Stopped", GetStatus(container(nil), group(to.StringPtr("Stopped"))))
- assert.Equal(t, "Node Started", GetStatus(container(nil), group(to.StringPtr("Started"))))
- assert.Equal(t, "Running", GetStatus(container(to.StringPtr("Running")), group(nil)))
- assert.Equal(t, "Unknown", GetStatus(container(nil), group(nil)))
- }
- func container(status *string) containerinstance.Container {
- var state *containerinstance.ContainerState = nil
- if status != nil {
- state = &containerinstance.ContainerState{
- State: status,
- }
- }
- return containerinstance.Container{
- ContainerProperties: &containerinstance.ContainerProperties{
- InstanceView: &containerinstance.ContainerPropertiesInstanceView{
- CurrentState: state,
- },
- },
- }
- }
- func group(status *string) containerinstance.ContainerGroup {
- var view *containerinstance.ContainerGroupPropertiesInstanceView = nil
- if status != nil {
- view = &containerinstance.ContainerGroupPropertiesInstanceView{
- State: status,
- }
- }
- return containerinstance.ContainerGroup{
- ContainerGroupProperties: &containerinstance.ContainerGroupProperties{
- InstanceView: view,
- },
- }
- }
|