diff --git a/azure/aci.go b/azure/aci.go index 6f2a19c05..e8fefcd7f 100644 --- a/azure/aci.go +++ b/azure/aci.go @@ -20,9 +20,7 @@ import ( "context" "fmt" "io" - "io/ioutil" "net/http" - "strings" "time" "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance" @@ -103,10 +101,6 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex return err } - containerGroup, err := future.Result(containerGroupsClient) - if err != nil { - return err - } for _, c := range *groupDefinition.Containers { w.Event(progress.Event{ ID: *c.Name, @@ -115,30 +109,6 @@ func createOrUpdateACIContainers(ctx context.Context, aciContext store.AciContex }) } - if len(*containerGroup.Containers) > 1 { - var commands []string - for _, container := range *containerGroup.Containers { - commands = append(commands, fmt.Sprintf("echo 127.0.0.1 %s >> /etc/hosts", *container.Name)) - } - commands = append(commands, "exit") - - containers := *containerGroup.Containers - container := containers[0] - response, err := execACIContainer(ctx, aciContext, "/bin/sh", *containerGroup.Name, *container.Name) - if err != nil { - return err - } - - if err = execCommands( - ctx, - *response.WebSocketURI, - *response.Password, - commands, - ); err != nil { - return err - } - } - return err } @@ -188,37 +158,6 @@ func getTermSize() (*int32, *int32) { return to.Int32Ptr(int32(rows)), to.Int32Ptr(int32(cols)) } -type commandSender struct { - commands string -} - -func (cs *commandSender) Read(p []byte) (int, error) { - if len(cs.commands) == 0 { - return 0, io.EOF - } - - var command string - if len(p) >= len(cs.commands) { - command = cs.commands - cs.commands = "" - } else { - command = cs.commands[:len(p)] - cs.commands = cs.commands[len(p):] - } - - copy(p, command) - - return len(command), nil -} - -func execCommands(ctx context.Context, address string, password string, commands []string) error { - writer := ioutil.Discard - reader := &commandSender{ - commands: strings.Join(commands, "\n"), - } - return exec(ctx, address, password, reader, writer) -} - func exec(ctx context.Context, address string, password string, reader io.Reader, writer io.Writer) error { conn, _, _, err := ws.DefaultDialer.Dial(ctx, address) if err != nil { diff --git a/azure/backend.go b/azure/backend.go index 76168d21c..9d4b71e18 100644 --- a/azure/backend.go +++ b/azure/backend.go @@ -129,6 +129,10 @@ func (cs *aciContainerService) List(ctx context.Context, _ bool) ([]containers.C for _, container := range *group.Containers { var containerID string + // don't list sidecar container + if *container.Name == convert.ComposeDNSSidecarName { + continue + } if *container.Name == singleContainerName { containerID = *containerGroup.Name } else { diff --git a/azure/backend_test.go b/azure/backend_test.go index 286938d07..6f50678df 100644 --- a/azure/backend_test.go +++ b/azure/backend_test.go @@ -19,14 +19,9 @@ package azure import ( "testing" - "github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance" - "github.com/Azure/go-autorest/autorest/to" "github.com/stretchr/testify/suite" . "github.com/onsi/gomega" - - "github.com/docker/api/azure/convert" - "github.com/docker/api/containers" ) type BackendSuiteTest struct { @@ -51,56 +46,3 @@ func TestBackendSuite(t *testing.T) { RegisterTestingT(t) suite.Run(t, new(BackendSuiteTest)) } - -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"), - }, - }, - } - 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{ - MemoryInGB: to.Float64Ptr(9), - }, - }, - }, - } - - var expectedContainer = containers.Container{ - ID: "myContainerID", - Status: "Running", - Image: "sha256:666", - Command: "mycommand", - MemoryLimit: 9, - Ports: []containers.Port{{ - HostPort: uint32(80), - ContainerPort: uint32(80), - Protocol: "tcp", - HostIP: "42.42.42.42", - }}, - } - - container, err := convert.ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer) - Expect(err).To(BeNil()) - Expect(container).To(Equal(expectedContainer)) -} diff --git a/azure/convert/convert.go b/azure/convert/convert.go index 3dcc856d9..672df83a4 100644 --- a/azure/convert/convert.go +++ b/azure/convert/convert.go @@ -33,6 +33,10 @@ import ( ) const ( + // ComposeDNSSidecarName name of the dns sidecar container + ComposeDNSSidecarName = "aci--dns--sidecar" + dnsSidecarImage = "busybox:1.31.1" + azureFileDriverName = "azure_file" volumeDriveroptsShareNameKey = "share_name" volumeDriveroptsAccountNameKey = "storage_account_name" @@ -110,10 +114,44 @@ func ToContainerGroup(aciContext store.AciContext, p compose.Project) (container containers = append(containers, containerDefinition) } + if len(containers) > 1 { + dnsSideCar := getDNSSidecar(containers) + containers = append(containers, dnsSideCar) + } groupDefinition.ContainerGroupProperties.Containers = &containers + return groupDefinition, nil } +func getDNSSidecar(containers []containerinstance.Container) containerinstance.Container { + var commands []string + for _, container := range containers { + commands = append(commands, fmt.Sprintf("echo 127.0.0.1 %s >> /etc/hosts", *container.Name)) + } + // ACI restart policy is currently at container group level, cannot let the sidecar terminate quietly once /etc/hosts has been edited + // Pricing is done at the container group level so letting the sidecar container "sleep" should not impact the price for the whole group + commands = append(commands, "sleep infinity") + alpineCmd := []string{"sh", "-c", strings.Join(commands, ";")} + dnsSideCar := containerinstance.Container{ + Name: to.StringPtr(ComposeDNSSidecarName), + ContainerProperties: &containerinstance.ContainerProperties{ + Image: to.StringPtr(dnsSidecarImage), + Command: &alpineCmd, + Resources: &containerinstance.ResourceRequirements{ + Limits: &containerinstance.ResourceLimits{ + MemoryInGB: to.Float64Ptr(0.1), // "The memory requirement should be in incrememts of 0.1 GB." + CPU: to.Float64Ptr(0.01), // "The CPU requirement should be in incrememts of 0.01." + }, + Requests: &containerinstance.ResourceRequests{ + MemoryInGB: to.Float64Ptr(0.1), + CPU: to.Float64Ptr(0.01), + }, + }, + }, + } + return dnsSideCar +} + type projectAciHelper compose.Project func (p projectAciHelper) getAciSecretVolumes() ([]containerinstance.Volume, error) { diff --git a/azure/convert/convert_test.go b/azure/convert/convert_test.go index e7eb1f27f..ba583fce8 100644 --- a/azure/convert/convert_test.go +++ b/azure/convert/convert_test.go @@ -19,44 +19,148 @@ package convert import ( "testing" + "github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance" + "github.com/Azure/go-autorest/autorest/to" + "github.com/compose-spec/compose-go/types" + "github.com/docker/api/compose" + "github.com/docker/api/containers" "github.com/docker/api/context/store" + . "github.com/onsi/gomega" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" ) -const ( - projectName = "TEST" - expectedProjectName = "test" -) - type ConvertTestSuite struct { suite.Suite - ctx store.AciContext - project compose.Project + ctx store.AciContext } func (suite *ConvertTestSuite) BeforeTest(suiteName, testName string) { - ctx := store.AciContext{ + suite.ctx = store.AciContext{ SubscriptionID: "subID", ResourceGroup: "rg", Location: "eu", } - project := compose.Project{ - Name: projectName, - } - - suite.ctx = ctx - suite.project = project } func (suite *ConvertTestSuite) TestProjectName() { - containerGroup, err := ToContainerGroup(suite.ctx, suite.project) + project := compose.Project{ + Name: "TEST", + } + containerGroup, err := ToContainerGroup(suite.ctx, project) require.NoError(suite.T(), err) - require.Equal(suite.T(), *containerGroup.Name, expectedProjectName) + require.Equal(suite.T(), *containerGroup.Name, "test") +} + +func (suite *ConvertTestSuite) TestContainerGroupToContainer() { + 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{ + 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{ + MemoryInGB: to.Float64Ptr(9), + }, + }, + }, + } + + var expectedContainer = containers.Container{ + ID: "myContainerID", + Status: "Running", + Image: "sha256:666", + Command: "mycommand", + MemoryLimit: 9, + Ports: []containers.Port{{ + HostPort: uint32(80), + ContainerPort: uint32(80), + Protocol: "tcp", + HostIP: "42.42.42.42", + }}, + } + + container, err := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer) + Expect(err).To(BeNil()) + Expect(container).To(Equal(expectedContainer)) +} + +func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerWithDnsSideCarSide() { + project := compose.Project{ + Name: "", + Config: types.Config{ + Services: []types.ServiceConfig{ + { + Name: "service1", + Image: "image1", + }, + { + Name: "service2", + Image: "image2", + }, + }, + }, + } + + group, err := ToContainerGroup(suite.ctx, project) + Expect(err).To(BeNil()) + Expect(len(*group.Containers)).To(Equal(3)) + + Expect(*(*group.Containers)[0].Name).To(Equal("service1")) + Expect(*(*group.Containers)[1].Name).To(Equal("service2")) + Expect(*(*group.Containers)[2].Name).To(Equal(ComposeDNSSidecarName)) + + Expect(*(*group.Containers)[2].Command).To(Equal([]string{"sh", "-c", "echo 127.0.0.1 service1 >> /etc/hosts;echo 127.0.0.1 service2 >> /etc/hosts;sleep infinity"})) + + Expect(*(*group.Containers)[0].Image).To(Equal("image1")) + Expect(*(*group.Containers)[1].Image).To(Equal("image2")) + Expect(*(*group.Containers)[2].Image).To(Equal(dnsSidecarImage)) +} + +func (suite *ConvertTestSuite) TestComposeSingleContainerGroupToContainerNoDnsSideCarSide() { + project := compose.Project{ + Name: "", + Config: types.Config{ + Services: []types.ServiceConfig{ + { + Name: "service1", + Image: "image1", + }, + }, + }, + } + + group, err := ToContainerGroup(suite.ctx, project) + Expect(err).To(BeNil()) + + Expect(len(*group.Containers)).To(Equal(1)) + Expect(*(*group.Containers)[0].Name).To(Equal("service1")) + Expect(*(*group.Containers)[0].Image).To(Equal("image1")) } func TestConvertTestSuite(t *testing.T) { + RegisterTestingT(t) suite.Run(t, new(ConvertTestSuite)) } diff --git a/tests/aci-e2e/e2e-aci_test.go b/tests/aci-e2e/e2e-aci_test.go index da97b91f3..0a38a06d3 100644 --- a/tests/aci-e2e/e2e-aci_test.go +++ b/tests/aci-e2e/e2e-aci_test.go @@ -115,7 +115,7 @@ func (s *E2eACISuite) TestACIBackend() { testStorageAccountName, firstKey, testShareName, mountTarget), "-p", "80:80", "--name", testContainerName).ExecOrDie() - Expect(output).To(Equal(testContainerName + "\n")) + Expect(output).To(ContainSubstring(testContainerName)) output = s.NewDockerCommand("ps").ExecOrDie() lines := Lines(output) Expect(len(lines)).To(Equal(2))