1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180 |
- # -*- coding: utf-8 -*-
- '''
- Integration tests for the docker_container states
- '''
- # Import Python Libs
- from __future__ import absolute_import, print_function, unicode_literals
- import errno
- import functools
- import logging
- import os
- import subprocess
- import tempfile
- # Import Salt Testing Libs
- from tests.support.unit import skipIf
- from tests.support.case import ModuleCase
- from tests.support.docker import with_network, random_name
- from tests.support.paths import FILES, TMP
- from tests.support.helpers import destructiveTest, with_tempdir
- from tests.support.mixins import SaltReturnAssertsMixin
- # Import Salt Libs
- import salt.utils.files
- import salt.utils.network
- import salt.utils.path
- from salt.exceptions import CommandExecutionError
- # Import 3rd-party libs
- from salt.ext import six
- log = logging.getLogger(__name__)
- IPV6_ENABLED = bool(salt.utils.network.ip_addrs6(include_loopback=True))
- def container_name(func):
- '''
- Generate a randomized name for a container and clean it up afterward
- '''
- @functools.wraps(func)
- def wrapper(self, *args, **kwargs):
- name = random_name(prefix='salt_test_')
- try:
- return func(self, name, *args, **kwargs)
- finally:
- try:
- self.run_function('docker.rm', [name], force=True)
- except CommandExecutionError as exc:
- if 'No such container' not in exc.__str__():
- raise
- return wrapper
- @destructiveTest
- @skipIf(not salt.utils.path.which('busybox'), 'Busybox not installed')
- @skipIf(not salt.utils.path.which('dockerd'), 'Docker not installed')
- class DockerContainerTestCase(ModuleCase, SaltReturnAssertsMixin):
- '''
- Test docker_container states
- '''
- @classmethod
- def setUpClass(cls):
- '''
- '''
- # Create temp dir
- cls.image_build_rootdir = tempfile.mkdtemp(dir=TMP)
- # Generate image name
- cls.image = random_name(prefix='salt_busybox_')
- script_path = \
- os.path.join(FILES, 'file/base/mkimage-busybox-static')
- cmd = [script_path, cls.image_build_rootdir, cls.image]
- log.debug('Running \'%s\' to build busybox image', ' '.join(cmd))
- process = subprocess.Popen(
- cmd,
- close_fds=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- output = process.communicate()[0]
- log.debug('Output from mkimge-busybox-static:\n%s', output)
- if process.returncode != 0:
- raise Exception('Failed to build image')
- try:
- salt.utils.files.rm_rf(cls.image_build_rootdir)
- except OSError as exc:
- if exc.errno != errno.ENOENT:
- raise
- @classmethod
- def tearDownClass(cls):
- cmd = ['docker', 'rmi', '--force', cls.image]
- log.debug('Running \'%s\' to destroy busybox image', ' '.join(cmd))
- process = subprocess.Popen(
- cmd,
- close_fds=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT)
- output = process.communicate()[0]
- log.debug('Output from %s:\n%s', ' '.join(cmd), output)
- if process.returncode != 0:
- raise Exception('Failed to destroy image')
- def run_state(self, function, **kwargs):
- ret = super(DockerContainerTestCase, self).run_state(function, **kwargs)
- log.debug('ret = %s', ret)
- return ret
- @with_tempdir()
- @container_name
- def test_running_with_no_predefined_volume(self, name, bind_dir_host):
- '''
- This tests that a container created using the docker_container.running
- state, with binds defined, will also create the corresponding volumes
- if they aren't pre-defined in the image.
- '''
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- binds=bind_dir_host + ':/foo',
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Now check to ensure that the container has volumes to match the
- # binds that we used when creating it.
- ret = self.run_function('docker.inspect_container', [name])
- self.assertTrue('/foo' in ret['Config']['Volumes'])
- @container_name
- def test_running_with_no_predefined_ports(self, name):
- '''
- This tests that a container created using the docker_container.running
- state, with port_bindings defined, will also configure the
- corresponding ports if they aren't pre-defined in the image.
- '''
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- port_bindings='14505-14506:24505-24506,2123:2123/udp,8080',
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Now check to ensure that the container has ports to match the
- # port_bindings that we used when creating it.
- expected_ports = (4505, 4506, 8080, '2123/udp')
- ret = self.run_function('docker.inspect_container', [name])
- self.assertTrue(x in ret['NetworkSettings']['Ports']
- for x in expected_ports)
- @container_name
- def test_running_updated_image_id(self, name):
- '''
- This tests the case of an image being changed after the container is
- created. The next time the state is run, the container should be
- replaced because the image ID is now different.
- '''
- # Create and start a container
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Get the container's info
- c_info = self.run_function('docker.inspect_container', [name])
- c_name, c_id = (c_info[x] for x in ('Name', 'Id'))
- # Alter the filesystem inside the container
- self.assertEqual(
- self.run_function('docker.retcode', [name, 'touch /.salttest']),
- 0
- )
- # Commit the changes and overwrite the test class' image
- self.run_function('docker.commit', [c_id, self.image])
- # Re-run the state
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check to make sure that the container was replaced
- self.assertTrue('container_id' in ret['changes'])
- # Check to make sure that the image is in the changes dict, since
- # it should have changed
- self.assertTrue('image' in ret['changes'])
- # Check that the comment in the state return states that
- # container's image has changed
- self.assertTrue('Container has a new image' in ret['comment'])
- @container_name
- def test_running_start_false_without_replace(self, name):
- '''
- Test that we do not start a container which is stopped, when it is not
- being replaced.
- '''
- # Create a container
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Stop the container
- self.run_function('docker.stop', [name], force=True)
- # Re-run the state with start=False
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- start=False,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check to make sure that the container was not replaced
- self.assertTrue('container_id' not in ret['changes'])
- # Check to make sure that the state is not the changes dict, since
- # it should not have changed
- self.assertTrue('state' not in ret['changes'])
- @container_name
- def test_running_start_false_with_replace(self, name):
- '''
- Test that we do start a container which was previously stopped, even
- though start=False, because the container was replaced.
- '''
- # Create a container
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Stop the container
- self.run_function('docker.stop', [name], force=True)
- # Re-run the state with start=False but also change the command to
- # trigger the container being replaced.
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- command='sleep 600',
- start=False,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check to make sure that the container was not replaced
- self.assertTrue('container_id' in ret['changes'])
- # Check to make sure that the state is not the changes dict, since
- # it should not have changed
- self.assertTrue('state' not in ret['changes'])
- @container_name
- def test_running_start_true(self, name):
- '''
- This tests that we *do* start a container that is stopped, when the
- "start" argument is set to True.
- '''
- # Create a container
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Stop the container
- self.run_function('docker.stop', [name], force=True)
- # Re-run the state with start=True
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- start=True,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check to make sure that the container was not replaced
- self.assertTrue('container_id' not in ret['changes'])
- # Check to make sure that the state is in the changes dict, since
- # it should have changed
- self.assertTrue('state' in ret['changes'])
- # Check that the comment in the state return states that
- # container's state has changed
- self.assertTrue(
- "State changed from 'stopped' to 'running'" in ret['comment'])
- @container_name
- def test_running_with_invalid_input(self, name):
- '''
- This tests that the input tranlation code identifies invalid input and
- includes information about that invalid argument in the state return.
- '''
- # Try to create a container with invalid input
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- ulimits='nofile:2048',
- shutdown_timeout=1,
- )
- self.assertSaltFalseReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check to make sure that the container was not created
- self.assertTrue('container_id' not in ret['changes'])
- # Check that the error message about the invalid argument is
- # included in the comment for the state
- self.assertTrue(
- 'Ulimit definition \'nofile:2048\' is not in the format '
- 'type=soft_limit[:hard_limit]' in ret['comment']
- )
- @container_name
- def test_running_with_argument_collision(self, name):
- '''
- this tests that the input tranlation code identifies an argument
- collision (API args and their aliases being simultaneously used) and
- includes information about them in the state return.
- '''
- # try to create a container with invalid input
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- ulimits='nofile=2048',
- ulimit='nofile=1024:2048',
- shutdown_timeout=1,
- )
- self.assertSaltFalseReturn(ret)
- # Ciscard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check to make sure that the container was not created
- self.assertTrue('container_id' not in ret['changes'])
- # Check that the error message about the collision is included in
- # the comment for the state
- self.assertTrue(
- '\'ulimit\' is an alias for \'ulimits\'' in ret['comment'])
- @container_name
- def test_running_with_ignore_collisions(self, name):
- '''
- This tests that the input tranlation code identifies an argument
- collision (API args and their aliases being simultaneously used)
- includes information about them in the state return.
- '''
- # try to create a container with invalid input
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- ignore_collisions=True,
- ulimits='nofile=2048',
- ulimit='nofile=1024:2048',
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check to make sure that the container was created
- self.assertTrue('container_id' in ret['changes'])
- # Check that the value from the API argument was one that was used
- # to create the container
- c_info = self.run_function('docker.inspect_container', [name])
- actual = c_info['HostConfig']['Ulimits']
- expected = [{'Name': 'nofile', 'Soft': 2048, 'Hard': 2048}]
- self.assertEqual(actual, expected)
- @container_name
- def test_running_with_removed_argument(self, name):
- '''
- This tests that removing an argument from a created container will
- be detected and result in the container being replaced.
- It also tests that we revert back to the value from the image. This
- way, when the "command" argument is removed, we confirm that we are
- reverting back to the image's command.
- '''
- # Create the container
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- command='sleep 600',
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Run the state again with the "command" argument removed
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Now check to ensure that the changes include the command
- # reverting back to the image's command.
- image_info = self.run_function('docker.inspect_image', [self.image])
- self.assertEqual(
- ret['changes']['container']['Config']['Cmd']['new'],
- image_info['Config']['Cmd']
- )
- @container_name
- def test_running_with_port_bindings(self, name):
- '''
- This tests that the ports which are being bound are also exposed, even
- when not explicitly configured. This test will create a container with
- only some of the ports exposed, including some which aren't even bound.
- The resulting containers exposed ports should contain all of the ports
- defined in the "ports" argument, as well as each of the ports which are
- being bound.
- '''
- # Create the container
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- command='sleep 600',
- shutdown_timeout=1,
- port_bindings=[1234, '1235-1236', '2234/udp', '2235-2236/udp'],
- ports=[1235, '2235/udp', 9999],
- )
- self.assertSaltTrueReturn(ret)
- # Check the created container's port bindings and exposed ports. The
- # port bindings should only contain the ports defined in the
- # port_bindings argument, while the exposed ports should also contain
- # the extra port (9999/tcp) which was included in the ports argument.
- cinfo = self.run_function('docker.inspect_container', [name])
- ports = ['1234/tcp', '1235/tcp', '1236/tcp',
- '2234/udp', '2235/udp', '2236/udp']
- self.assertEqual(
- sorted(cinfo['HostConfig']['PortBindings']),
- ports
- )
- self.assertEqual(
- sorted(cinfo['Config']['ExposedPorts']),
- ports + ['9999/tcp']
- )
- @container_name
- def test_absent_with_stopped_container(self, name):
- '''
- This tests the docker_container.absent state on a stopped container
- '''
- # Create the container
- self.run_function('docker.create', [self.image], name=name)
- # Remove the container
- ret = self.run_state(
- 'docker_container.absent',
- name=name,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check that we have a removed container ID in the changes dict
- self.assertTrue('removed' in ret['changes'])
- # Run the state again to confirm it changes nothing
- ret = self.run_state(
- 'docker_container.absent',
- name=name,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Nothing should have changed
- self.assertEqual(ret['changes'], {})
- # Ensure that the comment field says the container does not exist
- self.assertEqual(
- ret['comment'],
- 'Container \'{0}\' does not exist'.format(name)
- )
- @container_name
- def test_absent_with_running_container(self, name):
- '''
- This tests the docker_container.absent state and
- '''
- # Create the container
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- command='sleep 600',
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- # Try to remove the container. This should fail because force=True
- # is needed to remove a container that is running.
- ret = self.run_state(
- 'docker_container.absent',
- name=name,
- )
- self.assertSaltFalseReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Nothing should have changed
- self.assertEqual(ret['changes'], {})
- # Ensure that the comment states that force=True is required
- self.assertEqual(
- ret['comment'],
- 'Container is running, set force to True to forcibly remove it'
- )
- # Try again with force=True. This should succeed.
- ret = self.run_state('docker_container.absent',
- name=name,
- force=True,
- )
- self.assertSaltTrueReturn(ret)
- # Discard the outer dict with the state compiler data to make below
- # asserts easier to read/write
- ret = ret[next(iter(ret))]
- # Check that we have a removed container ID in the changes dict
- self.assertTrue('removed' in ret['changes'])
- # The comment should mention that the container was removed
- self.assertEqual(
- ret['comment'],
- 'Forcibly removed container \'{0}\''.format(name)
- )
- @container_name
- def test_running_image_name(self, name):
- '''
- Ensure that we create the container using the image name instead of ID
- '''
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- ret = self.run_function('docker.inspect_container', [name])
- self.assertEqual(ret['Config']['Image'], self.image)
- @container_name
- def test_env_with_running_container(self, name):
- '''
- docker_container.running environnment part. Testing issue 39838.
- '''
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- env='VAR1=value1,VAR2=value2,VAR3=value3',
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- ret = self.run_function('docker.inspect_container', [name])
- self.assertTrue('VAR1=value1' in ret['Config']['Env'])
- self.assertTrue('VAR2=value2' in ret['Config']['Env'])
- self.assertTrue('VAR3=value3' in ret['Config']['Env'])
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image,
- env='VAR1=value1,VAR2=value2',
- shutdown_timeout=1,
- )
- self.assertSaltTrueReturn(ret)
- ret = self.run_function('docker.inspect_container', [name])
- self.assertTrue('VAR1=value1' in ret['Config']['Env'])
- self.assertTrue('VAR2=value2' in ret['Config']['Env'])
- self.assertTrue('VAR3=value3' not in ret['Config']['Env'])
- @with_network(subnet='10.247.197.96/27', create=True)
- @container_name
- def test_static_ip_one_network(self, container_name, net):
- '''
- Ensure that if a network is created and specified as network_mode, that is the only network, and
- the static IP is applied.
- '''
- requested_ip = '10.247.197.100'
- kwargs = {
- 'name': container_name,
- 'image': self.image,
- 'network_mode': net.name,
- 'networks': [{net.name: [{'ipv4_address': requested_ip}]}],
- 'shutdown_timeout': 1,
- }
- # Create a container
- ret = self.run_state('docker_container.running', **kwargs)
- self.assertSaltTrueReturn(ret)
- inspect_result = self.run_function('docker.inspect_container',
- [container_name])
- connected_networks = inspect_result['NetworkSettings']['Networks']
- self.assertEqual(list(connected_networks.keys()), [net.name])
- self.assertEqual(inspect_result['HostConfig']['NetworkMode'], net.name)
- self.assertEqual(connected_networks[net.name]['IPAMConfig']['IPv4Address'], requested_ip)
- def _test_running(self, container_name, *nets):
- '''
- DRY function for testing static IPs
- '''
- networks = []
- for net in nets:
- net_def = {
- net.name: [
- {net.ip_arg: net[0]}
- ]
- }
- networks.append(net_def)
- kwargs = {
- 'name': container_name,
- 'image': self.image,
- 'networks': networks,
- 'shutdown_timeout': 1,
- }
- # Create a container
- ret = self.run_state('docker_container.running', **kwargs)
- self.assertSaltTrueReturn(ret)
- inspect_result = self.run_function('docker.inspect_container',
- [container_name])
- connected_networks = inspect_result['NetworkSettings']['Networks']
- # Check that the correct IP was set
- try:
- for net in nets:
- self.assertEqual(
- connected_networks[net.name]['IPAMConfig'][net.arg_map(net.ip_arg)],
- net[0]
- )
- except KeyError:
- # Fail with a meaningful error
- msg = (
- 'Container does not have the expected network config for '
- 'network {0}'.format(net.name)
- )
- log.error(msg)
- log.error('Connected networks: %s', connected_networks)
- self.fail('{0}. See log for more information.'.format(msg))
- # Check that container continued running and didn't immediately exit
- self.assertTrue(inspect_result['State']['Running'])
- # Update the SLS configuration to use the second random IP so that we
- # can test updating a container's network configuration without
- # replacing the container.
- for idx, net in enumerate(nets):
- kwargs['networks'][idx][net.name][0][net.ip_arg] = net[1]
- ret = self.run_state('docker_container.running', **kwargs)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- expected = {'container': {'Networks': {}}}
- for net in nets:
- expected['container']['Networks'][net.name] = {
- 'IPAMConfig': {
- 'old': {net.arg_map(net.ip_arg): net[0]},
- 'new': {net.arg_map(net.ip_arg): net[1]},
- }
- }
- self.assertEqual(ret['changes'], expected)
- expected = [
- "Container '{0}' is already configured as specified.".format(
- container_name
- )
- ]
- expected.extend([
- "Reconnected to network '{0}' with updated configuration.".format(
- x.name
- )
- for x in sorted(nets, key=lambda y: y.name)
- ])
- expected = ' '.join(expected)
- self.assertEqual(ret['comment'], expected)
- # Update the SLS configuration to remove the last network
- kwargs['networks'].pop(-1)
- ret = self.run_state('docker_container.running', **kwargs)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- expected = {
- 'container': {
- 'Networks': {
- nets[-1].name: {
- 'IPAMConfig': {
- 'old': {
- nets[-1].arg_map(nets[-1].ip_arg): nets[-1][1]
- },
- 'new': None,
- }
- }
- }
- }
- }
- self.assertEqual(ret['changes'], expected)
- expected = (
- "Container '{0}' is already configured as specified. Disconnected "
- "from network '{1}'.".format(container_name, nets[-1].name)
- )
- self.assertEqual(ret['comment'], expected)
- # Update the SLS configuration to add back the last network, only use
- # an automatic IP instead of static IP.
- kwargs['networks'].append(nets[-1].name)
- ret = self.run_state('docker_container.running', **kwargs)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- # Get the automatic IP by inspecting the container, and use it to build
- # the expected changes.
- container_netinfo = self.run_function(
- 'docker.inspect_container',
- [container_name]).get('NetworkSettings', {}).get('Networks', {})[nets[-1].name]
- autoip_keys = self.minion_opts['docker.compare_container_networks']['automatic']
- autoip_config = {
- x: y for x, y in six.iteritems(container_netinfo)
- if x in autoip_keys and y
- }
- expected = {'container': {'Networks': {nets[-1].name: {}}}}
- for key, val in six.iteritems(autoip_config):
- expected['container']['Networks'][nets[-1].name][key] = {
- 'old': None, 'new': val
- }
- self.assertEqual(ret['changes'], expected)
- expected = (
- "Container '{0}' is already configured as specified. Connected "
- "to network '{1}'.".format(container_name, nets[-1].name)
- )
- self.assertEqual(ret['comment'], expected)
- # Update the SLS configuration to remove the last network
- kwargs['networks'].pop(-1)
- ret = self.run_state('docker_container.running', **kwargs)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- expected = {'container': {'Networks': {nets[-1].name: {}}}}
- for key, val in six.iteritems(autoip_config):
- expected['container']['Networks'][nets[-1].name][key] = {
- 'old': val, 'new': None
- }
- self.assertEqual(ret['changes'], expected)
- expected = (
- "Container '{0}' is already configured as specified. Disconnected "
- "from network '{1}'.".format(container_name, nets[-1].name)
- )
- self.assertEqual(ret['comment'], expected)
- @with_network(subnet='10.247.197.96/27', create=True)
- @container_name
- def test_running_ipv4(self, container_name, *nets):
- self._test_running(container_name, *nets)
- @with_network(subnet='10.247.197.128/27', create=True)
- @with_network(subnet='10.247.197.96/27', create=True)
- @container_name
- def test_running_dual_ipv4(self, container_name, *nets):
- self._test_running(container_name, *nets)
- @with_network(subnet='fe3f:2180:26:1::/123', create=True)
- @container_name
- @skipIf(not IPV6_ENABLED, 'IPv6 not enabled')
- def test_running_ipv6(self, container_name, *nets):
- self._test_running(container_name, *nets)
- @with_network(subnet='fe3f:2180:26:1::20/123', create=True)
- @with_network(subnet='fe3f:2180:26:1::/123', create=True)
- @container_name
- @skipIf(not IPV6_ENABLED, 'IPv6 not enabled')
- def test_running_dual_ipv6(self, container_name, *nets):
- self._test_running(container_name, *nets)
- @with_network(subnet='fe3f:2180:26:1::/123', create=True)
- @with_network(subnet='10.247.197.96/27', create=True)
- @container_name
- @skipIf(not IPV6_ENABLED, 'IPv6 not enabled')
- def test_running_mixed_ipv4_and_ipv6(self, container_name, *nets):
- self._test_running(container_name, *nets)
- @with_network(subnet='10.247.197.96/27', create=True)
- @container_name
- def test_running_explicit_networks(self, container_name, net):
- '''
- Ensure that if we use an explicit network configuration, we remove any
- default networks not specified (e.g. the default "bridge" network).
- '''
- # Create a container with no specific network configuration. The only
- # networks connected will be the default ones.
- ret = self.run_state(
- 'docker_container.running',
- name=container_name,
- image=self.image,
- shutdown_timeout=1)
- self.assertSaltTrueReturn(ret)
- inspect_result = self.run_function('docker.inspect_container',
- [container_name])
- # Get the default network names
- default_networks = list(inspect_result['NetworkSettings']['Networks'])
- # Re-run the state with an explicit network configuration. All of the
- # default networks should be disconnected.
- ret = self.run_state(
- 'docker_container.running',
- name=container_name,
- image=self.image,
- networks=[net.name],
- shutdown_timeout=1)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- net_changes = ret['changes']['container']['Networks']
- self.assertIn(
- "Container '{0}' is already configured as specified.".format(
- container_name
- ),
- ret['comment']
- )
- updated_networks = self.run_function(
- 'docker.inspect_container',
- [container_name])['NetworkSettings']['Networks']
- for default_network in default_networks:
- self.assertIn(
- "Disconnected from network '{0}'.".format(default_network),
- ret['comment']
- )
- self.assertIn(default_network, net_changes)
- # We've tested that the state return is correct, but let's be extra
- # paranoid and check the actual connected networks.
- self.assertNotIn(default_network, updated_networks)
- self.assertIn(
- "Connected to network '{0}'.".format(net.name),
- ret['comment']
- )
- @container_name
- def test_run_with_onlyif(self, name):
- '''
- Test docker_container.run with onlyif. The container should not run
- (and the state should return a True result) if the onlyif has a nonzero
- return code, but if the onlyif has a zero return code the container
- should run.
- '''
- for cmd in ('/bin/false', ['/bin/true', '/bin/false']):
- log.debug('Trying %s', cmd)
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- onlyif=cmd)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertFalse(ret['changes'])
- self.assertTrue(
- ret['comment'].startswith(
- 'onlyif command /bin/false returned exit code of'
- )
- )
- self.run_function('docker.rm', [name], force=True)
- for cmd in ('/bin/true', ['/bin/true', 'ls /']):
- log.debug('Trying %s', cmd)
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- onlyif=cmd)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes']['Logs'], 'root\n')
- self.assertEqual(
- ret['comment'],
- 'Container ran and exited with a return code of 0'
- )
- self.run_function('docker.rm', [name], force=True)
- @container_name
- def test_run_with_unless(self, name):
- '''
- Test docker_container.run with unless. The container should not run
- (and the state should return a True result) if the unless has a zero
- return code, but if the unless has a nonzero return code the container
- should run.
- '''
- for cmd in ('/bin/true', ['/bin/false', '/bin/true']):
- log.debug('Trying %s', cmd)
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- unless=cmd)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertFalse(ret['changes'])
- self.assertEqual(
- ret['comment'],
- 'unless command /bin/true returned exit code of 0'
- )
- self.run_function('docker.rm', [name], force=True)
- for cmd in ('/bin/false', ['/bin/false', 'ls /paththatdoesnotexist']):
- log.debug('Trying %s', cmd)
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- unless=cmd)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes']['Logs'], 'root\n')
- self.assertEqual(
- ret['comment'],
- 'Container ran and exited with a return code of 0'
- )
- self.run_function('docker.rm', [name], force=True)
- @container_name
- def test_run_with_creates(self, name):
- '''
- Test docker_container.run with creates. The container should not run
- (and the state should return a True result) if all of the files exist,
- but if if any of the files do not exist the container should run.
- '''
- def _mkstemp():
- fd, ret = tempfile.mkstemp()
- try:
- os.close(fd)
- except OSError as exc:
- if exc.errno != errno.EBADF:
- raise exc
- else:
- self.addCleanup(os.remove, ret)
- return ret
- bad_file = '/tmp/filethatdoesnotexist'
- good_file1 = _mkstemp()
- good_file2 = _mkstemp()
- for path in (good_file1, [good_file1, good_file2]):
- log.debug('Trying %s', path)
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- creates=path)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertFalse(ret['changes'])
- self.assertEqual(
- ret['comment'],
- 'All specified paths in \'creates\' argument exist'
- )
- self.run_function('docker.rm', [name], force=True)
- for path in (bad_file, [good_file1, bad_file]):
- log.debug('Trying %s', path)
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- creates=path)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes']['Logs'], 'root\n')
- self.assertEqual(
- ret['comment'],
- 'Container ran and exited with a return code of 0'
- )
- self.run_function('docker.rm', [name], force=True)
- @container_name
- def test_run_replace(self, name):
- '''
- Test the replace and force arguments to make sure they work properly
- '''
- # Run once to create the container
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami')
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes']['Logs'], 'root\n')
- self.assertEqual(
- ret['comment'],
- 'Container ran and exited with a return code of 0'
- )
- # Run again with replace=False, this should fail
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- replace=False)
- self.assertSaltFalseReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertFalse(ret['changes'])
- self.assertEqual(
- ret['comment'],
- 'Encountered error running container: Container \'{0}\' exists. '
- 'Run with replace=True to remove the existing container'.format(name)
- )
- # Run again with replace=True, this should proceed and there should be
- # a "Replaces" key in the changes dict to show that a container was
- # replaced.
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- replace=True)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes']['Logs'], 'root\n')
- self.assertTrue('Replaces' in ret['changes'])
- self.assertEqual(
- ret['comment'],
- 'Container ran and exited with a return code of 0'
- )
- @container_name
- def test_run_force(self, name):
- '''
- Test the replace and force arguments to make sure they work properly
- '''
- # Start up a container that will stay running
- ret = self.run_state(
- 'docker_container.running',
- name=name,
- image=self.image)
- self.assertSaltTrueReturn(ret)
- # Run again with replace=True, this should fail because the container
- # is still running
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- replace=True,
- force=False)
- self.assertSaltFalseReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertFalse(ret['changes'])
- self.assertEqual(
- ret['comment'],
- 'Encountered error running container: Container \'{0}\' exists '
- 'and is running. Run with replace=True and force=True to force '
- 'removal of the existing container.'.format(name)
- )
- # Run again with replace=True and force=True, this should proceed and
- # there should be a "Replaces" key in the changes dict to show that a
- # container was replaced.
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='whoami',
- replace=True,
- force=True)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes']['Logs'], 'root\n')
- self.assertTrue('Replaces' in ret['changes'])
- self.assertEqual(
- ret['comment'],
- 'Container ran and exited with a return code of 0'
- )
- @container_name
- def test_run_failhard(self, name):
- '''
- Test to make sure that we fail a state when the container exits with
- nonzero status if failhard is set to True, and that we don't when it is
- set to False.
- '''
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='/bin/false',
- failhard=True)
- self.assertSaltFalseReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes']['Logs'], '')
- self.assertTrue(
- ret['comment'].startswith(
- 'Container ran and exited with a return code of'
- )
- )
- self.run_function('docker.rm', [name], force=True)
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='/bin/false',
- failhard=False)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes']['Logs'], '')
- self.assertTrue(
- ret['comment'].startswith(
- 'Container ran and exited with a return code of'
- )
- )
- self.run_function('docker.rm', [name], force=True)
- @container_name
- def test_run_bg(self, name):
- '''
- Test to make sure that if the container is run in the background, we do
- not include an ExitCode or Logs key in the return. Then check the logs
- for the container to ensure that it ran as expected.
- '''
- ret = self.run_state(
- 'docker_container.run',
- name=name,
- image=self.image,
- command='sh -c "sleep 5 && whoami"',
- bg=True)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertTrue('Logs' not in ret['changes'])
- self.assertTrue('ExitCode' not in ret['changes'])
- self.assertEqual(ret['comment'], 'Container was run in the background')
- # Now check the logs. The expectation is that the above asserts
- # completed during the 5-second sleep.
- self.assertEqual(
- self.run_function('docker.logs', [name], follow=True),
- 'root\n'
- )
|