Add Restart Policy support when running single container

This commit is contained in:
Ulysses Souza 2020-07-16 11:05:31 +02:00 committed by Guillaume Tardif
parent 2c4be4f7e3
commit a2c2d6aa5d
12 changed files with 189 additions and 48 deletions

View File

@ -24,6 +24,11 @@ func ContainerToComposeProject(r containers.ContainerConfig) (types.Project, err
return types.Project{}, err return types.Project{}, err
} }
composeRestartPolicyCondition := r.RestartPolicyCondition
if composeRestartPolicyCondition == "no" {
composeRestartPolicyCondition = "none"
}
project := types.Project{ project := types.Project{
Name: r.ID, Name: r.ID,
Services: []types.ServiceConfig{ Services: []types.ServiceConfig{
@ -41,6 +46,9 @@ func ContainerToComposeProject(r containers.ContainerConfig) (types.Project, err
MemoryBytes: types.UnitBytes(r.MemLimit.Value()), MemoryBytes: types.UnitBytes(r.MemLimit.Value()),
}, },
}, },
RestartPolicy: &types.RestartPolicy{
Condition: composeRestartPolicyCondition,
},
}, },
}, },
}, },

View File

@ -48,6 +48,18 @@ func (suite *ContainerConvertTestSuite) TestConvertContainerEnvironment() {
})) }))
} }
func (suite *ContainerConvertTestSuite) TestConvertRestartPolicy() {
container := containers.ContainerConfig{
ID: "container1",
RestartPolicyCondition: "no",
}
project, err := ContainerToComposeProject(container)
Expect(err).To(BeNil())
service1 := project.Services[0]
Expect(service1.Name).To(Equal(container.ID))
Expect(service1.Deploy.RestartPolicy.Condition).To(Equal("none"))
}
func TestContainerConvertTestSuite(t *testing.T) { func TestContainerConvertTestSuite(t *testing.T) {
RegisterTestingT(t) RegisterTestingT(t)
suite.Run(t, new(ContainerConvertTestSuite)) suite.Run(t, new(ContainerConvertTestSuite))

View File

@ -26,7 +26,7 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/Azure/azure-sdk-for-go/profiles/latest/containerinstance/mgmt/containerinstance" "github.com/Azure/azure-sdk-for-go/services/containerinstance/mgmt/2018-10-01/containerinstance"
"github.com/Azure/go-autorest/autorest/to" "github.com/Azure/go-autorest/autorest/to"
"github.com/compose-spec/compose-go/types" "github.com/compose-spec/compose-go/types"
@ -71,6 +71,15 @@ func ToContainerGroup(aciContext store.AciContext, p types.Project) (containerin
return containerinstance.ContainerGroup{}, err return containerinstance.ContainerGroup{}, err
} }
var restartPolicyCondition containerinstance.ContainerGroupRestartPolicy
if len(p.Services) == 1 &&
p.Services[0].Deploy != nil &&
p.Services[0].Deploy.RestartPolicy != nil {
restartPolicyCondition = toAciRestartPolicy(p.Services[0].Deploy.RestartPolicy.Condition)
} else {
restartPolicyCondition = containerinstance.Always
}
var containers []containerinstance.Container var containers []containerinstance.Container
groupDefinition := containerinstance.ContainerGroup{ groupDefinition := containerinstance.ContainerGroup{
Name: &containerGroupName, Name: &containerGroupName,
@ -80,6 +89,7 @@ func ToContainerGroup(aciContext store.AciContext, p types.Project) (containerin
Containers: &containers, Containers: &containers,
Volumes: volumes, Volumes: volumes,
ImageRegistryCredentials: &registryCreds, ImageRegistryCredentials: &registryCreds,
RestartPolicy: restartPolicyCondition,
}, },
} }
@ -125,6 +135,32 @@ func ToContainerGroup(aciContext store.AciContext, p types.Project) (containerin
return groupDefinition, nil return groupDefinition, nil
} }
func toAciRestartPolicy(restartPolicy string) containerinstance.ContainerGroupRestartPolicy {
switch restartPolicy {
case containers.RestartPolicyNone:
return containerinstance.Never
case containers.RestartPolicyAny:
return containerinstance.Always
case containers.RestartPolicyOnFailure:
return containerinstance.OnFailure
default:
return containerinstance.Always
}
}
func toContainerRestartPolicy(aciRestartPolicy containerinstance.ContainerGroupRestartPolicy) string {
switch aciRestartPolicy {
case containerinstance.Never:
return containers.RestartPolicyNone
case containerinstance.Always:
return containers.RestartPolicyAny
case containerinstance.OnFailure:
return containers.RestartPolicyOnFailure
default:
return containers.RestartPolicyAny
}
}
func getDNSSidecar(containers []containerinstance.Container) containerinstance.Container { func getDNSSidecar(containers []containerinstance.Container) containerinstance.Container {
var commands []string var commands []string
for _, container := range containers { for _, container := range containers {
@ -348,19 +384,20 @@ func ContainerGroupToContainer(containerID string, cg containerinstance.Containe
platform := string(cg.OsType) platform := string(cg.OsType)
c := containers.Container{ c := containers.Container{
ID: containerID, ID: containerID,
Status: status, Status: status,
Image: to.String(cc.Image), Image: to.String(cc.Image),
Command: command, Command: command,
CPUTime: 0, CPUTime: 0,
CPULimit: cpuLimit, CPULimit: cpuLimit,
MemoryUsage: 0, MemoryUsage: 0,
MemoryLimit: uint64(memLimits), MemoryLimit: uint64(memLimits),
PidsCurrent: 0, PidsCurrent: 0,
PidsLimit: 0, PidsLimit: 0,
Labels: nil, Labels: nil,
Ports: ToPorts(cg.IPAddress, *cc.Ports), Ports: ToPorts(cg.IPAddress, *cc.Ports),
Platform: platform, Platform: platform,
RestartPolicyCondition: toContainerRestartPolicy(cg.RestartPolicy),
} }
return c, nil return c, nil

View File

@ -104,6 +104,7 @@ func (suite *ConvertTestSuite) TestContainerGroupToContainer() {
Protocol: "tcp", Protocol: "tcp",
HostIP: "42.42.42.42", HostIP: "42.42.42.42",
}}, }},
RestartPolicyCondition: "any",
} }
container, err := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer) container, err := ContainerGroupToContainer("myContainerID", myContainerGroup, myContainer)
@ -158,6 +159,47 @@ func (suite *ConvertTestSuite) TestComposeSingleContainerGroupToContainerNoDnsSi
Expect(*(*group.Containers)[0].Image).To(Equal("image1")) Expect(*(*group.Containers)[0].Image).To(Equal("image1"))
} }
func (suite *ConvertTestSuite) TestComposeSingleContainerGroupToContainerSpecificRestartPolicy() {
project := types.Project{
Services: []types.ServiceConfig{
{
Name: "service1",
Image: "image1",
Deploy: &types.DeployConfig{
RestartPolicy: &types.RestartPolicy{
Condition: "on-failure",
},
},
},
},
}
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.RestartPolicy).To(Equal(containerinstance.OnFailure))
}
func (suite *ConvertTestSuite) TestComposeSingleContainerGroupToContainerDefaultRestartPolicy() {
project := types.Project{
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.RestartPolicy).To(Equal(containerinstance.Always))
}
func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerMultiplePorts() { func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerMultiplePorts() {
project := types.Project{ project := types.Project{
Services: []types.ServiceConfig{ Services: []types.ServiceConfig{
@ -287,6 +329,20 @@ func (suite *ConvertTestSuite) TestComposeContainerGroupToContainerenvVar() {
Expect(envVars).To(ContainElement(containerinstance.EnvironmentVariable{Name: to.StringPtr("key2"), Value: to.StringPtr("value2")})) Expect(envVars).To(ContainElement(containerinstance.EnvironmentVariable{Name: to.StringPtr("key2"), Value: to.StringPtr("value2")}))
} }
func (suite *ConvertTestSuite) TestConvertToAciRestartPolicyCondition() {
Expect(toAciRestartPolicy("none")).To(Equal(containerinstance.Never))
Expect(toAciRestartPolicy("always")).To(Equal(containerinstance.Always))
Expect(toAciRestartPolicy("on-failure")).To(Equal(containerinstance.OnFailure))
Expect(toAciRestartPolicy("on-failure:5")).To(Equal(containerinstance.Always))
}
func (suite *ConvertTestSuite) TestConvertToDockerRestartPolicyCondition() {
Expect(toContainerRestartPolicy(containerinstance.Never)).To(Equal("none"))
Expect(toContainerRestartPolicy(containerinstance.Always)).To(Equal("any"))
Expect(toContainerRestartPolicy(containerinstance.OnFailure)).To(Equal("on-failure"))
Expect(toContainerRestartPolicy("")).To(Equal("any"))
}
func TestConvertTestSuite(t *testing.T) { func TestConvertTestSuite(t *testing.T) {
RegisterTestingT(t) RegisterTestingT(t)
suite.Run(t, new(ConvertTestSuite)) suite.Run(t, new(ConvertTestSuite))

View File

@ -52,6 +52,7 @@ func Command() *cobra.Command {
cmd.Flags().Float64Var(&opts.Cpus, "cpus", 1., "Number of CPUs") cmd.Flags().Float64Var(&opts.Cpus, "cpus", 1., "Number of CPUs")
cmd.Flags().VarP(&opts.Memory, "memory", "m", "Memory limit") cmd.Flags().VarP(&opts.Memory, "memory", "m", "Memory limit")
cmd.Flags().StringArrayVarP(&opts.Environment, "env", "e", []string{}, "Set environment variables") cmd.Flags().StringArrayVarP(&opts.Environment, "env", "e", []string{}, "Set environment variables")
cmd.Flags().StringVarP(&opts.RestartPolicyCondition, "restart", "", "no", "Restart policy to apply when a container exits")
return cmd return cmd
} }

View File

@ -11,4 +11,5 @@ Flags:
-m, --memory bytes Memory limit -m, --memory bytes Memory limit
--name string Assign a name to the container --name string Assign a name to the container
-p, --publish stringArray Publish a container's port(s). [HOST_PORT:]CONTAINER_PORT -p, --publish stringArray Publish a container's port(s). [HOST_PORT:]CONTAINER_PORT
--restart string Restart policy to apply when a container exits (default "no")
-v, --volume stringArray Volume. Ex: user:key@my_share:/absolute/path/to/target -v, --volume stringArray Volume. Ex: user:key@my_share:/absolute/path/to/target

View File

@ -11,5 +11,6 @@
"PidsLimit": 0, "PidsLimit": 0,
"Labels": null, "Labels": null,
"Ports": null, "Ports": null,
"Platform": "Linux" "Platform": "Linux",
"RestartPolicyCondition": ""
} }

View File

@ -30,14 +30,15 @@ import (
// Opts contain run command options // Opts contain run command options
type Opts struct { type Opts struct {
Name string Name string
Publish []string Publish []string
Labels []string Labels []string
Volumes []string Volumes []string
Cpus float64 Cpus float64
Memory formatter.MemBytes Memory formatter.MemBytes
Detach bool Detach bool
Environment []string Environment []string
RestartPolicyCondition string
} }
// ToContainerConfig convert run options to a container configuration // ToContainerConfig convert run options to a container configuration
@ -57,14 +58,15 @@ func (r *Opts) ToContainerConfig(image string) (containers.ContainerConfig, erro
} }
return containers.ContainerConfig{ return containers.ContainerConfig{
ID: r.Name, ID: r.Name,
Image: image, Image: image,
Ports: publish, Ports: publish,
Labels: labels, Labels: labels,
Volumes: r.Volumes, Volumes: r.Volumes,
MemLimit: r.Memory, MemLimit: r.Memory,
CPULimit: r.Cpus, CPULimit: r.Cpus,
Environment: r.Environment, Environment: r.Environment,
RestartPolicyCondition: r.RestartPolicyCondition,
}, nil }, nil
} }

View File

@ -25,19 +25,20 @@ import (
// Container represents a created container // Container represents a created container
type Container struct { type Container struct {
ID string ID string
Status string Status string
Image string Image string
Command string Command string
CPUTime uint64 CPUTime uint64
CPULimit float64 CPULimit float64
MemoryUsage uint64 MemoryUsage uint64
MemoryLimit uint64 MemoryLimit uint64
PidsCurrent uint64 PidsCurrent uint64
PidsLimit uint64 PidsLimit uint64
Labels []string Labels []string
Ports []Port Ports []Port
Platform string Platform string
RestartPolicyCondition string
} }
// Port represents a published port of a container // Port represents a published port of a container
@ -70,6 +71,8 @@ type ContainerConfig struct {
CPULimit float64 CPULimit float64
// Environment variables // Environment variables
Environment []string Environment []string
// Restart policy condition
RestartPolicyCondition string
} }
// ExecRequest contaiens configuration about an exec request // ExecRequest contaiens configuration about an exec request

11
containers/types.go Normal file
View File

@ -0,0 +1,11 @@
package containers
const (
// RestartPolicyAny Always restarts
RestartPolicyAny = "any"
// RestartPolicyNone Never restarts
// "no" is the value for docker run, "none" is the value in compose file (and default differ
RestartPolicyNone = "none"
// RestartPolicyOnFailure Restarts only on failure
RestartPolicyOnFailure = "on-failure"
)

View File

@ -18,6 +18,8 @@ package ecs
import ( import (
"context" "context"
ecsplugin "github.com/docker/ecs-plugin/pkg/amazon/backend"
"github.com/docker/api/backend" "github.com/docker/api/backend"
"github.com/docker/api/compose" "github.com/docker/api/compose"
"github.com/docker/api/containers" "github.com/docker/api/containers"
@ -25,7 +27,6 @@ import (
"github.com/docker/api/context/cloud" "github.com/docker/api/context/cloud"
"github.com/docker/api/context/store" "github.com/docker/api/context/store"
"github.com/docker/api/errdefs" "github.com/docker/api/errdefs"
ecsplugin "github.com/docker/ecs-plugin/pkg/amazon/backend"
) )
const backendType = store.EcsContextType const backendType = store.EcsContextType

View File

@ -86,6 +86,7 @@ func (s *E2eACISuite) TestACIRunSingleContainer() {
defer deleteResourceGroup(resourceGroupName) defer deleteResourceGroup(resourceGroupName)
var nginxExposedURL string var nginxExposedURL string
var containerID string
s.Step("runs nginx on port 80", func() { s.Step("runs nginx on port 80", func() {
aciContext := store.AciContext{ aciContext := store.AciContext{
SubscriptionID: subscriptionID, SubscriptionID: subscriptionID,
@ -118,7 +119,7 @@ func (s *E2eACISuite) TestACIRunSingleContainer() {
Expect(containerFields[1]).To(Equal("nginx")) Expect(containerFields[1]).To(Equal("nginx"))
Expect(containerFields[2]).To(Equal("Running")) Expect(containerFields[2]).To(Equal("Running"))
exposedIP := containerFields[3] exposedIP := containerFields[3]
containerID := containerFields[0] containerID = containerFields[0]
Expect(exposedIP).To(ContainSubstring(":80->80/tcp")) Expect(exposedIP).To(ContainSubstring(":80->80/tcp"))
nginxExposedURL = strings.ReplaceAll(exposedIP, "->80/tcp", "") nginxExposedURL = strings.ReplaceAll(exposedIP, "->80/tcp", "")
@ -129,6 +130,13 @@ func (s *E2eACISuite) TestACIRunSingleContainer() {
Expect(output).To(ContainSubstring("GET")) Expect(output).To(ContainSubstring("GET"))
}) })
s.Step("inspect command", func() {
inspect := s.NewDockerCommand("inspect", containerID).ExecOrDie()
Expect(inspect).To(ContainSubstring("\"Platform\": \"Linux\""))
Expect(inspect).To(ContainSubstring("\"CPULimit\": 1"))
Expect(inspect).To(ContainSubstring("\"RestartPolicyCondition\": \"none\""))
})
s.Step("exec command", func() { s.Step("exec command", func() {
output := s.NewDockerCommand("exec", containerName, "pwd").ExecOrDie() output := s.NewDockerCommand("exec", containerName, "pwd").ExecOrDie()
Expect(output).To(ContainSubstring("/")) Expect(output).To(ContainSubstring("/"))
@ -174,13 +182,12 @@ func (s *E2eACISuite) TestACIRunSingleContainer() {
shutdown := make(chan time.Time) shutdown := make(chan time.Time)
errs := make(chan error) errs := make(chan error)
outChan := make(chan string) outChan := make(chan string)
cmd := s.NewDockerCommand("run", "nginx", "--memory", "0.1G", "--cpus", "0.1", "-p", "80:80", "--name", testContainerName).WithTimeout(shutdown) cmd := s.NewDockerCommand("run", "nginx", "--restart", "on-failure", "--memory", "0.1G", "--cpus", "0.1", "-p", "80:80", "--name", testContainerName).WithTimeout(shutdown)
go func() { go func() {
output, err := cmd.Exec() output, err := cmd.Exec()
outChan <- output outChan <- output
errs <- err errs <- err
}() }()
var containerID string
err := WaitFor(time.Second, 100*time.Second, errs, func() bool { err := WaitFor(time.Second, 100*time.Second, errs, func() bool {
output := s.NewDockerCommand("ps").ExecOrDie() output := s.NewDockerCommand("ps").ExecOrDie()
lines := Lines(output) lines := Lines(output)
@ -201,6 +208,7 @@ func (s *E2eACISuite) TestACIRunSingleContainer() {
inspect := s.NewDockerCommand("inspect", containerID).ExecOrDie() inspect := s.NewDockerCommand("inspect", containerID).ExecOrDie()
Expect(inspect).To(ContainSubstring("\"CPULimit\": 0.1")) Expect(inspect).To(ContainSubstring("\"CPULimit\": 0.1"))
Expect(inspect).To(ContainSubstring("\"MemoryLimit\": 107374182")) Expect(inspect).To(ContainSubstring("\"MemoryLimit\": 107374182"))
Expect(inspect).To(ContainSubstring("\"RestartPolicyCondition\": \"on-failure\""))
// Give a little time to get logs of the curl call // Give a little time to get logs of the curl call
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)