Refactor the runner to support more than docker

Refactor the runner.py to support more than docker, like we did for
compose1.py.

Change-Id: I9ca29573570b14f204d464598408f316bb71401c
This commit is contained in:
Emilien Macchi 2018-08-08 14:52:15 -04:00
parent 3c0d4f51d9
commit c19a46bb57
2 changed files with 173 additions and 165 deletions

View File

@ -22,11 +22,10 @@ import subprocess
LOG = logging.getLogger(__name__)
class DockerRunner(object):
def __init__(self, managed_by, docker_cmd=None):
class BaseRunner(object):
def __init__(self, managed_by, docker_cmd):
self.managed_by = managed_by
self.docker_cmd = docker_cmd or 'docker'
self.docker_cmd = docker_cmd
@staticmethod
def execute(cmd):
@ -69,6 +68,74 @@ class DockerRunner(object):
return [c for c in cmd_stdout.split()]
def inspect(self, name, format=None, type='container'):
cmd = [self.docker_cmd, 'inspect', '--type', type]
if format:
cmd.append('--format')
cmd.append(format)
cmd.append(name)
(cmd_stdout, cmd_stderr, returncode) = self.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(self, container):
container_name = container
while self.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(self, container, cid):
cmd = [
self.docker_cmd,
'ps',
'-a',
'--filter',
'label=container_name=%s' % container,
'--filter',
'label=config_id=%s' % cid,
'--format',
'{{.Names}}'
]
(cmd_stdout, cmd_stderr, returncode) = self.execute(cmd)
if returncode != 0:
return container
names = cmd_stdout.split()
if names:
return names[0]
return container
def delete_missing_configs(self, config_ids):
if not config_ids:
config_ids = []
for conf_id in self.current_config_ids():
if conf_id not in config_ids:
LOG.debug('%s no longer exists, deleting containers' % conf_id)
self.remove_containers(conf_id)
def list_configs(self):
configs = collections.defaultdict(list)
for conf_id in self.current_config_ids():
for container in self.containers_in_config(conf_id):
configs[conf_id].append(self.inspect(container))
return configs
class DockerRunner(BaseRunner):
def __init__(self, managed_by, docker_cmd='docker'):
docker_cmd = docker_cmd or 'docker'
super(DockerRunner, self).__init__(managed_by, docker_cmd)
def remove_containers(self, conf_id):
for container in self.containers_in_config(conf_id):
self.remove_container(container)
@ -131,64 +198,3 @@ class DockerRunner(object):
if returncode != 0:
LOG.error('Error renaming container: %s' % container)
LOG.error(cmd_stderr)
def inspect(self, name, format=None, type='container'):
cmd = [self.docker_cmd, 'inspect', '--type', type]
if format:
cmd.append('--format')
cmd.append(format)
cmd.append(name)
(cmd_stdout, cmd_stderr, returncode) = self.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(self, container):
container_name = container
while self.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(self, container, cid):
cmd = [
self.docker_cmd,
'ps',
'-a',
'--filter',
'label=container_name=%s' % container,
'--filter',
'label=config_id=%s' % cid,
'--format',
'{{.Names}}'
]
(cmd_stdout, cmd_stderr, returncode) = self.execute(cmd)
if returncode != 0:
return container
names = cmd_stdout.split()
if names:
return names[0]
return container
def delete_missing_configs(self, config_ids):
if not config_ids:
config_ids = []
for conf_id in self.current_config_ids():
if conf_id not in config_ids:
LOG.debug('%s no longer exists, deleting containers' % conf_id)
self.remove_containers(conf_id)
def list_configs(self):
configs = collections.defaultdict(list)
for conf_id in self.current_config_ids():
for container in self.containers_in_config(conf_id):
configs[conf_id].append(self.inspect(container))
return configs

View File

@ -19,10 +19,9 @@ from paunch import runner
from paunch.tests import base
class TestDockerRunner(base.TestCase):
class TestBaseRunner(base.TestCase):
def setUp(self):
super(TestDockerRunner, self).setUp()
super(TestBaseRunner, self).setUp()
self.runner = runner.DockerRunner('tester')
def mock_execute(self, popen, stdout, stderr, returncode):
@ -73,103 +72,6 @@ class TestDockerRunner(base.TestCase):
)
self.assertEqual(['one', 'two', 'three'], result)
@mock.patch('subprocess.Popen')
def test_remove_containers(self, popen):
self.mock_execute(popen, 'one\ntwo\nthree', '', 0)
self.runner.remove_container = mock.Mock()
self.runner.remove_containers('foo')
self.assert_execute(
popen, ['docker', 'ps', '-q', '-a',
'--filter', 'label=managed_by=tester',
'--filter', 'label=config_id=foo']
)
self.runner.remove_container.assert_has_calls([
mock.call('one'), mock.call('two'), mock.call('three')
])
@mock.patch('subprocess.Popen')
def test_remove_container(self, popen):
self.mock_execute(popen, '', '', 0)
self.runner.remove_container('one')
self.assert_execute(
popen, ['docker', 'rm', '-f', 'one']
)
@mock.patch('subprocess.Popen')
def test_container_names(self, popen):
ps_result = '''one one
two-12345678 two
two two
three-12345678 three
four-12345678 four
'''
self.mock_execute(popen, ps_result, '', 0)
names = list(self.runner.container_names())
self.assert_execute(
popen, ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester',
'--format', '{{.Names}} {{.Label "container_name"}}']
)
self.assertEqual([
['one', 'one'],
['two-12345678', 'two'],
['two', 'two'],
['three-12345678', 'three'],
['four-12345678', 'four']
], names)
@mock.patch('subprocess.Popen')
def test_container_names_by_conf_id(self, popen):
ps_result = '''one one
two-12345678 two
'''
self.mock_execute(popen, ps_result, '', 0)
names = list(self.runner.container_names('abc'))
self.assert_execute(
popen, ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester',
'--filter', 'label=config_id=abc',
'--format', '{{.Names}} {{.Label "container_name"}}']
)
self.assertEqual([
['one', 'one'],
['two-12345678', 'two']
], names)
@mock.patch('subprocess.Popen')
def test_rename_containers(self, popen):
ps_result = '''one one
two-12345678 two
two two
three-12345678 three
four-12345678 four
'''
self.mock_execute(popen, ps_result, '', 0)
self.runner.rename_container = mock.Mock()
self.runner.rename_containers()
self.assert_execute(
popen, ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester',
'--format', '{{.Names}} {{.Label "container_name"}}']
)
# only containers three-12345678 and four-12345678 four will be renamed
self.runner.rename_container.assert_has_calls([
mock.call('three-12345678', 'three'),
mock.call('four-12345678', 'four')
], any_order=True)
@mock.patch('subprocess.Popen')
def test_inspect(self, popen):
self.mock_execute(popen, '[{"foo": "bar"}]', '', 0)
@ -284,3 +186,103 @@ four-12345678 four
'two': [{'e': 'f'}, {'e': 'f'}, {'e': 'f'}],
'three': [{'e': 'f'}, {'e': 'f'}, {'e': 'f'}]
}, result)
class TestDockerRunner(TestBaseRunner):
@mock.patch('subprocess.Popen')
def test_remove_containers(self, popen):
self.mock_execute(popen, 'one\ntwo\nthree', '', 0)
self.runner.remove_container = mock.Mock()
self.runner.remove_containers('foo')
self.assert_execute(
popen, ['docker', 'ps', '-q', '-a',
'--filter', 'label=managed_by=tester',
'--filter', 'label=config_id=foo']
)
self.runner.remove_container.assert_has_calls([
mock.call('one'), mock.call('two'), mock.call('three')
])
@mock.patch('subprocess.Popen')
def test_remove_container(self, popen):
self.mock_execute(popen, '', '', 0)
self.runner.remove_container('one')
self.assert_execute(
popen, ['docker', 'rm', '-f', 'one']
)
@mock.patch('subprocess.Popen')
def test_container_names(self, popen):
ps_result = '''one one
two-12345678 two
two two
three-12345678 three
four-12345678 four
'''
self.mock_execute(popen, ps_result, '', 0)
names = list(self.runner.container_names())
self.assert_execute(
popen, ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester',
'--format', '{{.Names}} {{.Label "container_name"}}']
)
self.assertEqual([
['one', 'one'],
['two-12345678', 'two'],
['two', 'two'],
['three-12345678', 'three'],
['four-12345678', 'four']
], names)
@mock.patch('subprocess.Popen')
def test_container_names_by_conf_id(self, popen):
ps_result = '''one one
two-12345678 two
'''
self.mock_execute(popen, ps_result, '', 0)
names = list(self.runner.container_names('abc'))
self.assert_execute(
popen, ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester',
'--filter', 'label=config_id=abc',
'--format', '{{.Names}} {{.Label "container_name"}}']
)
self.assertEqual([
['one', 'one'],
['two-12345678', 'two']
], names)
@mock.patch('subprocess.Popen')
def test_rename_containers(self, popen):
ps_result = '''one one
two-12345678 two
two two
three-12345678 three
four-12345678 four
'''
self.mock_execute(popen, ps_result, '', 0)
self.runner.rename_container = mock.Mock()
self.runner.rename_containers()
self.assert_execute(
popen, ['docker', 'ps', '-a',
'--filter', 'label=managed_by=tester',
'--format', '{{.Names}} {{.Label "container_name"}}']
)
# only containers three-12345678 and four-12345678 four will be renamed
self.runner.rename_container.assert_has_calls([
mock.call('three-12345678', 'three'),
mock.call('four-12345678', 'four')
], any_order=True)