123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506 |
- # -*- coding: utf-8 -*-
- '''
- Integration tests for the docker_network 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 pytest
- # Import Salt Testing Libs
- from tests.support.runtime import RUNTIME_VARS
- from tests.support.unit import skipIf
- from tests.support.case import ModuleCase
- from tests.support.docker import with_network, random_name
- from tests.support.helpers import requires_system_grains
- 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
- log = logging.getLogger(__name__)
- IMAGE_NAME = random_name(prefix='salt_busybox_')
- IPV6_ENABLED = bool(salt.utils.network.ip_addrs6(include_loopback=True))
- def network_name(func):
- '''
- Generate a randomized name for a network and clean it up afterward
- '''
- @functools.wraps(func)
- def wrapper(self, *args, **kwargs):
- name = random_name(prefix='salt_net_')
- try:
- return func(self, name, *args, **kwargs)
- finally:
- self.run_function(
- 'docker.disconnect_all_containers_from_network', [name])
- try:
- self.run_function('docker.remove_network', [name])
- except CommandExecutionError as exc:
- if 'No such network' not in exc.__str__():
- raise
- return wrapper
- def container_name(func):
- '''
- Generate a randomized name for a container and clean it up afterward
- '''
- def build_image():
- # Create temp dir
- image_build_rootdir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
- script_path = os.path.join(RUNTIME_VARS.BASE_FILES, 'mkimage-busybox-static')
- cmd = [script_path, image_build_rootdir, IMAGE_NAME]
- 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(image_build_rootdir)
- except OSError as exc:
- if exc.errno != errno.ENOENT:
- raise
- @functools.wraps(func)
- def wrapper(self, *args, **kwargs):
- try:
- self.run_function('docker.inspect_image', [IMAGE_NAME])
- except CommandExecutionError:
- pass
- else:
- build_image()
- name = random_name(prefix='salt_test_')
- self.run_function(
- 'docker.create',
- name=name,
- image=IMAGE_NAME,
- command='sleep 600',
- start=True)
- 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
- @pytest.mark.destructive_test
- @skipIf(not salt.utils.path.which('dockerd'), 'Docker not installed')
- class DockerNetworkTestCase(ModuleCase, SaltReturnAssertsMixin):
- '''
- Test docker_network states
- '''
- @classmethod
- def tearDownClass(cls):
- '''
- Remove test image if present. Note that this will run a docker rmi even
- if no test which required the image was run.
- '''
- cmd = ['docker', 'rmi', '--force', IMAGE_NAME]
- 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 and 'No such image' not in output:
- raise Exception('Failed to destroy image')
- def run_state(self, function, **kwargs):
- ret = super(DockerNetworkTestCase, self).run_state(function, **kwargs)
- log.debug('ret = %s', ret)
- return ret
- @with_network(create=False)
- def test_absent(self, net):
- self.assertSaltTrueReturn(
- self.run_state('docker_network.present', name=net.name))
- ret = self.run_state('docker_network.absent', name=net.name)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes'], {'removed': True})
- self.assertEqual(
- ret['comment'],
- 'Removed network \'{0}\''.format(net.name)
- )
- @container_name
- @with_network(create=False)
- def test_absent_with_disconnected_container(self, net, container_name):
- self.assertSaltTrueReturn(
- self.run_state('docker_network.present',
- name=net.name,
- containers=[container_name])
- )
- ret = self.run_state('docker_network.absent', name=net.name)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(
- ret['changes'],
- {
- 'removed': True,
- 'disconnected': [container_name],
- }
- )
- self.assertEqual(
- ret['comment'],
- 'Removed network \'{0}\''.format(net.name)
- )
- @with_network(create=False)
- def test_absent_when_not_present(self, net):
- ret = self.run_state('docker_network.absent', name=net.name)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes'], {})
- self.assertEqual(
- ret['comment'],
- 'Network \'{0}\' already absent'.format(net.name)
- )
- @with_network(create=False)
- def test_present(self, net):
- ret = self.run_state('docker_network.present', name=net.name)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- # Make sure the state return is what we expect
- self.assertEqual(ret['changes'], {'created': True})
- self.assertEqual(
- ret['comment'],
- 'Network \'{0}\' created'.format(net.name)
- )
- # Now check to see that the network actually exists. If it doesn't,
- # this next function call will raise an exception.
- self.run_function('docker.inspect_network', [net.name])
- @container_name
- @with_network(create=False)
- def test_present_with_containers(self, net, container_name):
- ret = self.run_state(
- 'docker_network.present',
- name=net.name,
- containers=[container_name])
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(
- ret['changes'],
- {
- 'created': True,
- 'connected': [container_name],
- }
- )
- self.assertEqual(
- ret['comment'],
- 'Network \'{0}\' created'.format(net.name)
- )
- # Now check to see that the network actually exists. If it doesn't,
- # this next function call will raise an exception.
- self.run_function('docker.inspect_network', [net.name])
- def _test_present_reconnect(self, net, container_name, reconnect=True):
- ret = self.run_state(
- 'docker_network.present',
- name=net.name,
- driver='bridge')
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(ret['changes'], {'created': True})
- self.assertEqual(
- ret['comment'],
- 'Network \'{0}\' created'.format(net.name)
- )
- # Connect the container
- self.run_function(
- 'docker.connect_container_to_network',
- [container_name, net.name]
- )
- # Change the driver to force the network to be replaced
- ret = self.run_state(
- 'docker_network.present',
- name=net.name,
- driver='macvlan',
- reconnect=reconnect)
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- self.assertEqual(
- ret['changes'],
- {
- 'recreated': True,
- 'reconnected' if reconnect else 'disconnected': [container_name],
- net.name: {
- 'Driver': {
- 'old': 'bridge',
- 'new': 'macvlan',
- }
- }
- }
- )
- self.assertEqual(
- ret['comment'],
- 'Network \'{0}\' was replaced with updated config'.format(net.name)
- )
- @container_name
- @with_network(create=False)
- def test_present_with_reconnect(self, net, container_name):
- '''
- Test reconnecting with containers not passed to state
- '''
- self._test_present_reconnect(net, container_name, reconnect=True)
- @container_name
- @with_network(create=False)
- def test_present_with_no_reconnect(self, net, container_name):
- '''
- Test reconnecting with containers not passed to state
- '''
- self._test_present_reconnect(net, container_name, reconnect=False)
- @with_network()
- def test_present_internal(self, net):
- self.assertSaltTrueReturn(
- self.run_state(
- 'docker_network.present',
- name=net.name,
- internal=True,
- )
- )
- net_info = self.run_function('docker.inspect_network', [net.name])
- self.assertIs(net_info['Internal'], True)
- @with_network()
- def test_present_labels(self, net):
- # Test a mix of different ways of specifying labels
- self.assertSaltTrueReturn(
- self.run_state(
- 'docker_network.present',
- name=net.name,
- labels=[
- 'foo',
- 'bar=baz',
- {'hello': 'world'},
- ],
- )
- )
- net_info = self.run_function('docker.inspect_network', [net.name])
- self.assertEqual(
- net_info['Labels'],
- {'foo': '',
- 'bar': 'baz',
- 'hello': 'world'},
- )
- @with_network(subnet='fe3f:2180:26:1::/123')
- @with_network(subnet='10.247.197.96/27')
- @skipIf(not IPV6_ENABLED, 'IPv6 not enabled')
- def test_present_enable_ipv6(self, net1, net2):
- self.assertSaltTrueReturn(
- self.run_state(
- 'docker_network.present',
- name=net1.name,
- enable_ipv6=True,
- ipam_pools=[
- {'subnet': net1.subnet},
- {'subnet': net2.subnet},
- ],
- )
- )
- net_info = self.run_function('docker.inspect_network', [net1.name])
- self.assertIs(net_info['EnableIPv6'], True)
- @requires_system_grains
- @with_network()
- def test_present_attachable(self, net, grains):
- if grains['os_family'] == 'RedHat' \
- and grains.get('osmajorrelease', 0) <= 7:
- self.skipTest('Cannot reliably manage attachable on RHEL <= 7')
- self.assertSaltTrueReturn(
- self.run_state(
- 'docker_network.present',
- name=net.name,
- attachable=True,
- )
- )
- net_info = self.run_function('docker.inspect_network', [net.name])
- self.assertIs(net_info['Attachable'], True)
- @skipIf(True, 'Skip until we can set up docker swarm testing')
- @with_network()
- def test_present_scope(self, net):
- self.assertSaltTrueReturn(
- self.run_state(
- 'docker_network.present',
- name=net.name,
- scope='global',
- )
- )
- net_info = self.run_function('docker.inspect_network', [net.name])
- self.assertIs(net_info['Scope'], 'global')
- @skipIf(True, 'Skip until we can set up docker swarm testing')
- @with_network()
- def test_present_ingress(self, net):
- self.assertSaltTrueReturn(
- self.run_state(
- 'docker_network.present',
- name=net.name,
- ingress=True,
- )
- )
- net_info = self.run_function('docker.inspect_network', [net.name])
- self.assertIs(net_info['Ingress'], True)
- @with_network(subnet='10.247.197.128/27')
- @with_network(subnet='10.247.197.96/27')
- def test_present_with_custom_ipv4(self, net1, net2):
- # First run will test passing the IPAM arguments individually
- self.assertSaltTrueReturn(
- self.run_state(
- 'docker_network.present',
- name=net1.name,
- subnet=net1.subnet,
- gateway=net1.gateway,
- )
- )
- # Second run will pass them in the ipam_pools argument
- ret = self.run_state(
- 'docker_network.present',
- name=net1.name, # We want to keep the same network name
- ipam_pools=[
- {'subnet': net2.subnet,
- 'gateway': net2.gateway},
- ],
- )
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- # Docker requires there to be IPv4, even when only an IPv6 subnet was
- # provided. So, there will be both an IPv4 and IPv6 pool in the
- # configuration.
- expected = {
- 'recreated': True,
- net1.name: {
- 'IPAM': {
- 'Config': {
- 'old': [
- {'Subnet': net1.subnet,
- 'Gateway': net1.gateway},
- ],
- 'new': [
- {'Subnet': net2.subnet,
- 'Gateway': net2.gateway},
- ],
- }
- }
- }
- }
- self.assertEqual(ret['changes'], expected)
- self.assertEqual(
- ret['comment'],
- 'Network \'{0}\' was replaced with updated config'.format(
- net1.name
- )
- )
- @with_network(subnet='fe3f:2180:26:1::20/123')
- @with_network(subnet='fe3f:2180:26:1::/123')
- @with_network(subnet='10.247.197.96/27')
- @skipIf(not IPV6_ENABLED, 'IPv6 not enabled')
- def test_present_with_custom_ipv6(self, ipv4_net, ipv6_net1, ipv6_net2):
- self.assertSaltTrueReturn(
- self.run_state(
- 'docker_network.present',
- name=ipv4_net.name,
- enable_ipv6=True,
- ipam_pools=[
- {'subnet': ipv4_net.subnet,
- 'gateway': ipv4_net.gateway},
- {'subnet': ipv6_net1.subnet,
- 'gateway': ipv6_net1.gateway}
- ],
- )
- )
- ret = self.run_state(
- 'docker_network.present',
- name=ipv4_net.name, # We want to keep the same network name
- enable_ipv6=True,
- ipam_pools=[
- {'subnet': ipv4_net.subnet,
- 'gateway': ipv4_net.gateway},
- {'subnet': ipv6_net2.subnet,
- 'gateway': ipv6_net2.gateway}
- ],
- )
- self.assertSaltTrueReturn(ret)
- ret = ret[next(iter(ret))]
- # Docker requires there to be IPv4, even when only an IPv6 subnet was
- # provided. So, there will be both an IPv4 and IPv6 pool in the
- # configuration.
- expected = {
- 'recreated': True,
- ipv4_net.name: {
- 'IPAM': {
- 'Config': {
- 'old': [
- {'Subnet': ipv4_net.subnet,
- 'Gateway': ipv4_net.gateway},
- {'Subnet': ipv6_net1.subnet,
- 'Gateway': ipv6_net1.gateway}
- ],
- 'new': [
- {'Subnet': ipv4_net.subnet,
- 'Gateway': ipv4_net.gateway},
- {'Subnet': ipv6_net2.subnet,
- 'Gateway': ipv6_net2.gateway}
- ],
- }
- }
- }
- }
- self.assertEqual(ret['changes'], expected)
- self.assertEqual(
- ret['comment'],
- 'Network \'{0}\' was replaced with updated config'.format(
- ipv4_net.name
- )
- )
|