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:
parent
3c0d4f51d9
commit
c19a46bb57
136
paunch/runner.py
136
paunch/runner.py
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
Loading…
Reference in New Issue