Ensure unique container names
When a container already exists with the desired name, the container name has a random suffix attached to it so that it can still be run. This ensures containers are always created regardless of other running containers. Since the name may not be as expected, the exec action needs an extra lookup to attempt to discover the actual name, falling back to the requested name if the lookup fails. Since there is a container_name label set with the desired name, the next patch in this series modifies 50-heat-config-docker-cmd to rename containers to their desired name when possible. Change-Id: Ibd97f52811f653295559d000487d2c50a7c67ece
This commit is contained in:
parent
b6dfdf8e99
commit
4d34592f4a
|
@ -15,6 +15,8 @@
|
|||
import json
|
||||
import logging
|
||||
import os
|
||||
import random
|
||||
import string
|
||||
import subprocess
|
||||
import sys
|
||||
import yaml
|
||||
|
@ -74,6 +76,54 @@ def label_arguments(cmd, container, cid, iv):
|
|||
])
|
||||
|
||||
|
||||
def inspect(container, format=None):
|
||||
cmd = [DOCKER_CMD, 'inspect']
|
||||
if format:
|
||||
cmd.append('--format')
|
||||
cmd.append(format)
|
||||
cmd.append(container)
|
||||
(cmd_stdout, cmd_stderr, returncode) = execute(cmd)
|
||||
if returncode != 0:
|
||||
return
|
||||
try:
|
||||
if format:
|
||||
return cmd_stdout
|
||||
else:
|
||||
return json.loads(cmd_stdout)[0]
|
||||
except Exception as e:
|
||||
log.error('Problem parsing docker inspect: %s' % e)
|
||||
|
||||
|
||||
def unique_container_name(container):
|
||||
container_name = container
|
||||
while inspect(container_name, format='exists'):
|
||||
suffix = ''.join(random.choice(
|
||||
string.ascii_lowercase + string.digits) for i in range(8))
|
||||
container_name = '%s-%s' % (container, suffix)
|
||||
return container_name
|
||||
|
||||
|
||||
def discover_container_name(container, cid):
|
||||
cmd = [
|
||||
DOCKER_CMD,
|
||||
'ps',
|
||||
'-a',
|
||||
'--filter',
|
||||
'label=container_name=%s' % container,
|
||||
'--filter',
|
||||
'label=config_id=%s' % cid,
|
||||
'--format',
|
||||
'{{.Names}}'
|
||||
]
|
||||
(cmd_stdout, cmd_stderr, returncode) = execute(cmd)
|
||||
if returncode != 0:
|
||||
return container
|
||||
names = cmd_stdout.split()
|
||||
if names:
|
||||
return names[0]
|
||||
return container
|
||||
|
||||
|
||||
def main(argv=sys.argv):
|
||||
global log
|
||||
log = logging.getLogger('heat-config')
|
||||
|
@ -119,7 +169,7 @@ def main(argv=sys.argv):
|
|||
DOCKER_CMD,
|
||||
'run',
|
||||
'--name',
|
||||
container
|
||||
unique_container_name(container)
|
||||
]
|
||||
label_arguments(cmd, container, c.get('id'), input_values)
|
||||
if config[container].get('detach', True):
|
||||
|
@ -148,7 +198,14 @@ def main(argv=sys.argv):
|
|||
cmd.append(image_name)
|
||||
|
||||
if 'command' in config[container]:
|
||||
cmd.extend(config[container].get('command'))
|
||||
command = config[container].get('command')
|
||||
|
||||
if action == 'exec':
|
||||
# for exec, the first argument is the container name,
|
||||
# make sure the correct one is used
|
||||
command[0] = discover_container_name(command[0], c.get('id'))
|
||||
|
||||
cmd.extend(command)
|
||||
|
||||
(cmd_stdout, cmd_stderr, returncode) = execute(cmd)
|
||||
if cmd_stdout:
|
||||
|
|
|
@ -69,6 +69,7 @@ class HookDockerCmdTest(common.RunScriptTest):
|
|||
data_exit_code = {
|
||||
"name": "abcdef001",
|
||||
"group": "docker-cmd",
|
||||
"id": "abc123",
|
||||
"config": {
|
||||
"web-ls": {
|
||||
"action": "exec",
|
||||
|
@ -109,11 +110,293 @@ class HookDockerCmdTest(common.RunScriptTest):
|
|||
|
||||
self.env.update({
|
||||
'TEST_RESPONSE': json.dumps([{
|
||||
'stderr': 'Error: No such image, container or task: db',
|
||||
'returncode': 1
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'Creating db...'
|
||||
}, {
|
||||
'stderr': 'Error: No such image, container or task: web',
|
||||
'returncode': 1
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'Creating web...'
|
||||
}, {
|
||||
'stdout': 'web',
|
||||
}, {
|
||||
|
||||
'stdout': '',
|
||||
'stderr': 'one.txt\ntwo.txt\nthree.txt'
|
||||
}])
|
||||
})
|
||||
returncode, stdout, stderr = self.run_cmd(
|
||||
[self.hook_path], self.env, json.dumps(self.data))
|
||||
|
||||
self.assertEqual(0, returncode, stderr)
|
||||
|
||||
self.assertEqual({
|
||||
'deploy_stdout': '',
|
||||
'deploy_stderr': 'Creating db...\n'
|
||||
'Creating web...\n'
|
||||
'one.txt\ntwo.txt\nthree.txt',
|
||||
'deploy_status_code': 0
|
||||
}, json.loads(stdout))
|
||||
|
||||
state = list(self.json_from_files(self.test_state_path, 6))
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'inspect',
|
||||
'--format',
|
||||
'exists',
|
||||
'db',
|
||||
], state[0]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'run',
|
||||
'--name',
|
||||
'db',
|
||||
'--label',
|
||||
'deploy_stack_id=the_stack',
|
||||
'--label',
|
||||
'deploy_resource_name=the_deployment',
|
||||
'--label',
|
||||
'config_id=abc123',
|
||||
'--label',
|
||||
'container_name=db',
|
||||
'--label',
|
||||
'managed_by=docker-cmd',
|
||||
'--detach=true',
|
||||
'--privileged=false',
|
||||
'xxx'
|
||||
], state[1]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'inspect',
|
||||
'--format',
|
||||
'exists',
|
||||
'web',
|
||||
], state[2]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'run',
|
||||
'--name',
|
||||
'web',
|
||||
'--label',
|
||||
'deploy_stack_id=the_stack',
|
||||
'--label',
|
||||
'deploy_resource_name=the_deployment',
|
||||
'--label',
|
||||
'config_id=abc123',
|
||||
'--label',
|
||||
'container_name=web',
|
||||
'--label',
|
||||
'managed_by=docker-cmd',
|
||||
'--detach=true',
|
||||
'--env=KOLLA_CONFIG_STRATEGY=COPY_ALWAYS',
|
||||
'--env=FOO=BAR',
|
||||
'--net=host',
|
||||
'--privileged=true',
|
||||
'--restart=always',
|
||||
'--user=root',
|
||||
'--volume=/run:/run',
|
||||
'--volume=db:/var/lib/db',
|
||||
'xxx'
|
||||
], state[3]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'ps',
|
||||
'-a',
|
||||
'--filter',
|
||||
'label=container_name=web',
|
||||
'--filter',
|
||||
'label=config_id=abc123',
|
||||
'--format',
|
||||
'{{.Names}}',
|
||||
], state[4]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'exec',
|
||||
'web',
|
||||
'/bin/ls',
|
||||
'-l'
|
||||
], state[5]['args'])
|
||||
|
||||
def test_hook_exit_codes(self):
|
||||
|
||||
self.env.update({
|
||||
'TEST_RESPONSE': json.dumps([{
|
||||
'stdout': 'web',
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'Warning: custom exit code',
|
||||
'returncode': 1
|
||||
}])
|
||||
})
|
||||
returncode, stdout, stderr = self.run_cmd(
|
||||
[self.hook_path], self.env, json.dumps(self.data_exit_code))
|
||||
|
||||
self.assertEqual({
|
||||
'deploy_stdout': '',
|
||||
'deploy_stderr': 'Warning: custom exit code',
|
||||
'deploy_status_code': 0
|
||||
}, json.loads(stdout))
|
||||
|
||||
state = list(self.json_from_files(self.test_state_path, 2))
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'ps',
|
||||
'-a',
|
||||
'--filter',
|
||||
'label=container_name=web',
|
||||
'--filter',
|
||||
'label=config_id=abc123',
|
||||
'--format',
|
||||
'{{.Names}}',
|
||||
], state[0]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'exec',
|
||||
'web',
|
||||
'/bin/ls',
|
||||
'-l'
|
||||
], state[1]['args'])
|
||||
|
||||
def test_hook_failed(self):
|
||||
|
||||
self.env.update({
|
||||
'TEST_RESPONSE': json.dumps([{
|
||||
'stderr': 'Error: No such image, container or task: db',
|
||||
'returncode': 1
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'Creating db...'
|
||||
}, {
|
||||
'stderr': 'Error: No such image, container or task: web',
|
||||
'returncode': 1
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'Creating web...'
|
||||
}, {
|
||||
'stdout': 'web',
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'No such file or directory',
|
||||
'returncode': 2
|
||||
}])
|
||||
})
|
||||
returncode, stdout, stderr = self.run_cmd(
|
||||
[self.hook_path], self.env, json.dumps(self.data))
|
||||
|
||||
self.assertEqual({
|
||||
'deploy_stdout': '',
|
||||
'deploy_stderr': 'Creating db...\n'
|
||||
'Creating web...\n'
|
||||
'No such file or directory',
|
||||
'deploy_status_code': 2
|
||||
}, json.loads(stdout))
|
||||
|
||||
state = list(self.json_from_files(self.test_state_path, 6))
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'inspect',
|
||||
'--format',
|
||||
'exists',
|
||||
'db',
|
||||
], state[0]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'run',
|
||||
'--name',
|
||||
'db',
|
||||
'--label',
|
||||
'deploy_stack_id=the_stack',
|
||||
'--label',
|
||||
'deploy_resource_name=the_deployment',
|
||||
'--label',
|
||||
'config_id=abc123',
|
||||
'--label',
|
||||
'container_name=db',
|
||||
'--label',
|
||||
'managed_by=docker-cmd',
|
||||
'--detach=true',
|
||||
'--privileged=false',
|
||||
'xxx'
|
||||
], state[1]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'inspect',
|
||||
'--format',
|
||||
'exists',
|
||||
'web',
|
||||
], state[2]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'run',
|
||||
'--name',
|
||||
'web',
|
||||
'--label',
|
||||
'deploy_stack_id=the_stack',
|
||||
'--label',
|
||||
'deploy_resource_name=the_deployment',
|
||||
'--label',
|
||||
'config_id=abc123',
|
||||
'--label',
|
||||
'container_name=web',
|
||||
'--label',
|
||||
'managed_by=docker-cmd',
|
||||
'--detach=true',
|
||||
'--env=KOLLA_CONFIG_STRATEGY=COPY_ALWAYS',
|
||||
'--env=FOO=BAR',
|
||||
'--net=host',
|
||||
'--privileged=true',
|
||||
'--restart=always',
|
||||
'--user=root',
|
||||
'--volume=/run:/run',
|
||||
'--volume=db:/var/lib/db',
|
||||
'xxx'
|
||||
], state[3]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'ps',
|
||||
'-a',
|
||||
'--filter',
|
||||
'label=container_name=web',
|
||||
'--filter',
|
||||
'label=config_id=abc123',
|
||||
'--format',
|
||||
'{{.Names}}',
|
||||
], state[4]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'exec',
|
||||
'web',
|
||||
'/bin/ls',
|
||||
'-l'
|
||||
], state[5]['args'])
|
||||
|
||||
def test_hook_unique_names(self):
|
||||
|
||||
self.env.update({
|
||||
'TEST_RESPONSE': json.dumps([{
|
||||
'stdout': 'exists\n',
|
||||
'returncode': 0
|
||||
}, {
|
||||
'stderr': 'Error: No such image, container or task: db-blah',
|
||||
'returncode': 1
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'Creating db...'
|
||||
}, {
|
||||
'stdout': 'exists\n',
|
||||
'returncode': 0
|
||||
}, {
|
||||
'stderr': 'Error: No such image, container or task: web-blah',
|
||||
'returncode': 1
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'Creating web...'
|
||||
}, {
|
||||
'stdout': 'web-asdf1234',
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'one.txt\ntwo.txt\nthree.txt'
|
||||
|
@ -132,12 +415,30 @@ class HookDockerCmdTest(common.RunScriptTest):
|
|||
'deploy_status_code': 0
|
||||
}, json.loads(stdout))
|
||||
|
||||
state = list(self.json_from_files(self.test_state_path, 3))
|
||||
state = list(self.json_from_files(self.test_state_path, 8))
|
||||
db_container_name = state[1]['args'][4]
|
||||
web_container_name = state[4]['args'][4]
|
||||
self.assertRegex(db_container_name, 'db-[0-9a-z]{8}')
|
||||
self.assertRegex(web_container_name, 'web-[0-9a-z]{8}')
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'inspect',
|
||||
'--format',
|
||||
'exists',
|
||||
'db',
|
||||
], state[0]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'inspect',
|
||||
'--format',
|
||||
'exists',
|
||||
db_container_name,
|
||||
], state[1]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'run',
|
||||
'--name',
|
||||
'db',
|
||||
db_container_name,
|
||||
'--label',
|
||||
'deploy_stack_id=the_stack',
|
||||
'--label',
|
||||
|
@ -151,12 +452,26 @@ class HookDockerCmdTest(common.RunScriptTest):
|
|||
'--detach=true',
|
||||
'--privileged=false',
|
||||
'xxx'
|
||||
], state[0]['args'])
|
||||
], state[2]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'inspect',
|
||||
'--format',
|
||||
'exists',
|
||||
'web',
|
||||
], state[3]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'inspect',
|
||||
'--format',
|
||||
'exists',
|
||||
web_container_name,
|
||||
], state[4]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'run',
|
||||
'--name',
|
||||
'web',
|
||||
web_container_name,
|
||||
'--label',
|
||||
'deploy_stack_id=the_stack',
|
||||
'--label',
|
||||
|
@ -177,121 +492,25 @@ class HookDockerCmdTest(common.RunScriptTest):
|
|||
'--volume=/run:/run',
|
||||
'--volume=db:/var/lib/db',
|
||||
'xxx'
|
||||
], state[1]['args'])
|
||||
], state[5]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'ps',
|
||||
'-a',
|
||||
'--filter',
|
||||
'label=container_name=web',
|
||||
'--filter',
|
||||
'label=config_id=abc123',
|
||||
'--format',
|
||||
'{{.Names}}',
|
||||
], state[6]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'exec',
|
||||
'web',
|
||||
'web-asdf1234',
|
||||
'/bin/ls',
|
||||
'-l'
|
||||
], state[2]['args'])
|
||||
|
||||
def test_hook_exit_codes(self):
|
||||
|
||||
self.env.update({
|
||||
'TEST_RESPONSE': json.dumps({
|
||||
'stdout': '',
|
||||
'stderr': 'Warning: custom exit code',
|
||||
'returncode': 1
|
||||
})
|
||||
})
|
||||
returncode, stdout, stderr = self.run_cmd(
|
||||
[self.hook_path], self.env, json.dumps(self.data_exit_code))
|
||||
|
||||
self.assertEqual({
|
||||
'deploy_stdout': '',
|
||||
'deploy_stderr': 'Warning: custom exit code',
|
||||
'deploy_status_code': 0
|
||||
}, json.loads(stdout))
|
||||
|
||||
state = list(self.json_from_files(self.test_state_path, 1))
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'exec',
|
||||
'web',
|
||||
'/bin/ls',
|
||||
'-l'
|
||||
], state[0]['args'])
|
||||
|
||||
def test_hook_failed(self):
|
||||
|
||||
self.env.update({
|
||||
'TEST_RESPONSE': json.dumps([{
|
||||
'stdout': '',
|
||||
'stderr': 'Creating db...'
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'Creating web...'
|
||||
}, {
|
||||
'stdout': '',
|
||||
'stderr': 'No such file or directory',
|
||||
'returncode': 2
|
||||
}])
|
||||
})
|
||||
returncode, stdout, stderr = self.run_cmd(
|
||||
[self.hook_path], self.env, json.dumps(self.data))
|
||||
|
||||
self.assertEqual({
|
||||
'deploy_stdout': '',
|
||||
'deploy_stderr': 'Creating db...\n'
|
||||
'Creating web...\n'
|
||||
'No such file or directory',
|
||||
'deploy_status_code': 2
|
||||
}, json.loads(stdout))
|
||||
|
||||
state = list(self.json_from_files(self.test_state_path, 3))
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'run',
|
||||
'--name',
|
||||
'db',
|
||||
'--label',
|
||||
'deploy_stack_id=the_stack',
|
||||
'--label',
|
||||
'deploy_resource_name=the_deployment',
|
||||
'--label',
|
||||
'config_id=abc123',
|
||||
'--label',
|
||||
'container_name=db',
|
||||
'--label',
|
||||
'managed_by=docker-cmd',
|
||||
'--detach=true',
|
||||
'--privileged=false',
|
||||
'xxx'
|
||||
], state[0]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'run',
|
||||
'--name',
|
||||
'web',
|
||||
'--label',
|
||||
'deploy_stack_id=the_stack',
|
||||
'--label',
|
||||
'deploy_resource_name=the_deployment',
|
||||
'--label',
|
||||
'config_id=abc123',
|
||||
'--label',
|
||||
'container_name=web',
|
||||
'--label',
|
||||
'managed_by=docker-cmd',
|
||||
'--detach=true',
|
||||
'--env=KOLLA_CONFIG_STRATEGY=COPY_ALWAYS',
|
||||
'--env=FOO=BAR',
|
||||
'--net=host',
|
||||
'--privileged=true',
|
||||
'--restart=always',
|
||||
'--user=root',
|
||||
'--volume=/run:/run',
|
||||
'--volume=db:/var/lib/db',
|
||||
'xxx'
|
||||
], state[1]['args'])
|
||||
self.assertEqual([
|
||||
self.fake_tool_path,
|
||||
'exec',
|
||||
'web',
|
||||
'/bin/ls',
|
||||
'-l'
|
||||
], state[2]['args'])
|
||||
], state[7]['args'])
|
||||
|
||||
def test_cleanup_deleted(self):
|
||||
self.env.update({
|
||||
|
|
Loading…
Reference in New Issue