mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 17:09:03 +00:00
8cdb9ea54f
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.
506 lines
16 KiB
Python
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
|
|
)
|
|
)
|