Skip to content

Commit

Permalink
Added support for restart_policy parameter
Browse files Browse the repository at this point in the history
This patch adds the restart_policy parameter to services. The parameter
is passed to the Docker API when starting containers. Its recognized values
are the same as in the Docker CLI ("always" and "on-failure[:max-retry]").

For future compatibility, the policy can also be specified as a
dictionary, which is passed directly to the Docker API (after converting
the keys to CamelCase).

This feature seems to have been requested at least in issue docker#478.

Signed-off-by: Kenneth Falck <kennu@iki.fi>
  • Loading branch information
kennu committed Dec 7, 2014
1 parent 65ae22e commit 323b38c
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 3 deletions.
11 changes: 11 additions & 0 deletions docs/yml.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,14 @@ domainname: foo.com
mem_limit: 1000000000
privileged: true
```

### restart_policy

Specify the container restart policy using the same syntax as the Docker CLI.
Supported values are "no", "always" and "on-failure[:max-retry]". The default
is to have no restart policy.

```
restart_policy: "always"
restart_policy: "on-failure:3"
```
37 changes: 34 additions & 3 deletions fig/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
log = logging.getLogger(__name__)


DOCKER_CONFIG_KEYS = ['image', 'command', 'hostname', 'domainname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'entrypoint', 'privileged', 'volumes_from', 'net', 'working_dir']
DOCKER_CONFIG_KEYS = ['image', 'command', 'hostname', 'domainname', 'user', 'detach', 'stdin_open', 'tty', 'mem_limit', 'ports', 'environment', 'dns', 'volumes', 'entrypoint', 'privileged', 'volumes_from', 'net', 'working_dir', 'restart_policy']
DOCKER_CONFIG_HINTS = {
'link' : 'links',
'port' : 'ports',
Expand All @@ -24,6 +24,7 @@
'privilige' : 'privileged',
'volume' : 'volumes',
'workdir' : 'working_dir',
'restart' : 'restart_policy',
}

VALID_NAME_CHARS = '[a-zA-Z0-9]'
Expand Down Expand Up @@ -262,7 +263,7 @@ def start_container(self, container=None, intermediate_container=None, **overrid
net = options.get('net', 'bridge')
dns = options.get('dns', None)

container.start(
start_options = dict(
links=self._get_links(link_to_self=options.get('one_off', False)),
port_bindings=port_bindings,
binds=volume_bindings,
Expand All @@ -271,6 +272,10 @@ def start_container(self, container=None, intermediate_container=None, **overrid
network_mode=net,
dns=dns,
)
restart_policy = parse_restart_policy(options.get('restart_policy', None))
if restart_policy:
start_options['restart_policy'] = restart_policy
container.start(**start_options)
return container

def start_or_create_containers(self, insecure_registry=False):
Expand Down Expand Up @@ -376,7 +381,7 @@ def _get_container_create_options(self, override_options, one_off=False):
container_options['image'] = self._build_tag_name()

# Delete options which are only used when starting
for key in ['privileged', 'net', 'dns']:
for key in ['privileged', 'net', 'dns', 'restart_policy']:
if key in container_options:
del container_options[key]

Expand Down Expand Up @@ -534,3 +539,29 @@ def resolve_env(key, val):
return key, os.environ[key]
else:
return key, ''


def parse_restart_policy(restart_policy):
"""
Convert CLI/YML restart policy to a Docker API compatible dict.
"""
# Support a few different alternative syntaxes for restart policy
if not restart_policy or restart_policy == 'no':
# No policy
return None
elif isinstance(restart_policy, dict):
# Convert specified dict keys to CamelCase so
# {'name':'on-failure', 'maximum_retry_count':3} becomes
# {'Name':'on-failure', 'MaximumRetryCount':3} for Docker API
return dict([(re.sub(r'(?:^|_)([a-zA-Z])', lambda m: m.group(1).upper(), k), v) for (k, v) in restart_policy.items()])
elif re.match(r'^on-failure:\s*', restart_policy):
# Convert "on-failure:<max-retry>" into proper Docker API syntax
return {
'Name': 'on-failure',
'MaximumRetryCount': int(re.sub(r'^on-failure:\s*', '', restart_policy)),
}
else:
# Use specified string as the Name parameter
return {
'Name': restart_policy,
}
4 changes: 4 additions & 0 deletions tests/fixtures/restart-policy-figfile/fig.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
service:
image: busybox:latest
command: sleep 5
restart_policy: on-failure:3
10 changes: 10 additions & 0 deletions tests/integration/cli_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,16 @@ def test_run_service_with_environement_overridden(self, _):
# make sure a value with a = don't crash out
self.assertEqual('moto=bobo', container.environment['allo'])

@patch('dockerpty.start')
def test_run_service_with_restart_policy(self, __):
self.command.base_dir = 'tests/fixtures/restart-policy-figfile'
self.command.dispatch(['up', '-d', 'service'], None)
service = self.project.get_service('service')
containers = service.containers(stopped=True)
container = service.containers(stopped=True)[0].inspect()
self.assertEqual(container['HostConfig']['RestartPolicy']['Name'], 'on-failure')
self.assertEqual(container['HostConfig']['RestartPolicy']['MaximumRetryCount'], 3)

def test_rm(self):
service = self.project.get_service('simple')
service.create_container()
Expand Down
22 changes: 22 additions & 0 deletions tests/integration/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -389,3 +389,25 @@ def test_resolve_env(self):
del os.environ['FILE_DEF']
del os.environ['FILE_DEF_EMPTY']
del os.environ['ENV_DEF']

def test_restart_policy_default(self):
service = self.create_service('container')
container = service.start_container().inspect()
self.assertEqual(container['HostConfig']['RestartPolicy']['Name'], '')

def test_restart_policy_always(self):
service = self.create_service('container')
container = service.start_container(restart_policy='always').inspect()
self.assertEqual(container['HostConfig']['RestartPolicy']['Name'], 'always')

def test_restart_policy_on_failure(self):
service = self.create_service('container')
container = service.start_container(restart_policy='on-failure').inspect()
self.assertEqual(container['HostConfig']['RestartPolicy']['Name'], 'on-failure')
self.assertEqual(container['HostConfig']['RestartPolicy']['MaximumRetryCount'], 0)

def test_restart_policy_on_failure_retry_count(self):
service = self.create_service('container')
container = service.start_container(restart_policy='on-failure:3').inspect()
self.assertEqual(container['HostConfig']['RestartPolicy']['Name'], 'on-failure')
self.assertEqual(container['HostConfig']['RestartPolicy']['MaximumRetryCount'], 3)
13 changes: 13 additions & 0 deletions tests/unit/service_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
parse_volume_spec,
build_volume_binding,
APIError,
parse_restart_policy,
)


Expand Down Expand Up @@ -205,6 +206,18 @@ def test_create_container_from_insecure_registry(self, mock_log):
self.mock_client.pull.assert_called_once_with('someimage:sometag', insecure_registry=True, stream=True)
mock_log.info.assert_called_once_with('Pulling image someimage:sometag...')

def test_parse_restart_policy(self):
test_args = {
None: None,
'': None,
'no': None,
'always': {'Name':'always'},
'on-failure': {'Name':'on-failure'},
'on-failure:3': {'Name':'on-failure', 'MaximumRetryCount':3},
}
for k, v in test_args.items():
self.assertEqual(parse_restart_policy(k), v)


class ServiceVolumesTest(unittest.TestCase):

Expand Down

0 comments on commit 323b38c

Please sign in to comment.