salt/tests/integration/states/test_docker_network.py
Erik Johnson 8cdb9ea54f
[PY3] Add print_function import to files with unicode_literals already added
This makes the 2.x usage invalid syntax and forces the use of print as a
function. This adds the import to the files which I've updated in the
last couple of days but forgot to add it.
2017-12-15 12:14:18 -06:00

506 lines
16 KiB
Python

# -*- 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 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, 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=TMP)
script_path = \
os.path.join(FILES, 'file/base/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
@destructiveTest
@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
)
)