mirror of
https://github.com/saltstack/salt.git
synced 2025-04-17 10:10:20 +00:00
Merge pull request #29118 from ticosax/dockerng-network
[dockerng] Add networking capabilities
This commit is contained in:
commit
95689ee1a4
4 changed files with 491 additions and 4 deletions
|
@ -167,6 +167,18 @@ Functions
|
|||
- :py:func:`dockerng.rmi <salt.modules.dockerng.rmi>`
|
||||
- :py:func:`dockerng.save <salt.modules.dockerng.save>`
|
||||
- :py:func:`dockerng.tag <salt.modules.dockerng.tag>`
|
||||
- Network Management
|
||||
- :py:func:`dockerng.networks <salt.modules.dockerng.networks>`
|
||||
- :py:func:`dockerng.create_network <salt.modules.dockerng.create_network>`
|
||||
- :py:func:`dockerng.remove_network <salt.modules.dockerng.remove_network>`
|
||||
- :py:func:`dockerng.inspect_network
|
||||
<salt.modules.dockerng.inspect_network>`
|
||||
- :py:func:`dockerng.connect_container_to_network
|
||||
<salt.modules.dockerng.connect_container_to_network>`
|
||||
- :py:func:`dockerng.disconnect_container_from_network
|
||||
<salt.modules.dockerng.disconnect_container_from_network>`
|
||||
|
||||
|
||||
|
||||
.. _docker-execution-driver:
|
||||
|
||||
|
@ -550,6 +562,30 @@ class _api_version(object):
|
|||
return _mimic_signature(func, wrapper)
|
||||
|
||||
|
||||
class _client_version(object):
|
||||
'''
|
||||
Enforce a specific Docker client version
|
||||
'''
|
||||
def __init__(self, version):
|
||||
self.version = distutils.version.StrictVersion(version)
|
||||
|
||||
def __call__(self, func):
|
||||
def wrapper(*args, **kwargs):
|
||||
'''
|
||||
Get the current client version and check it against the one passed
|
||||
'''
|
||||
_get_client()
|
||||
current_version = '.'.join(map(str, _get_docker_py_versioninfo()))
|
||||
if distutils.version.StrictVersion(current_version) < self.version:
|
||||
raise CommandExecutionError(
|
||||
'This function requires a Docker Client version of at least '
|
||||
'{0}. Version in use is {1}.'
|
||||
.format(self.version, current_version)
|
||||
)
|
||||
return func(*args, **salt.utils.clean_kwargs(**kwargs))
|
||||
return _mimic_signature(func, wrapper)
|
||||
|
||||
|
||||
def _docker_client(wrapped):
|
||||
'''
|
||||
Decorator to run a function that requires the use of a docker.Client()
|
||||
|
@ -1555,11 +1591,16 @@ def _validate_input(kwargs,
|
|||
# Ensure that the user didn't just pass 'container:', because
|
||||
# that would be invalid.
|
||||
return
|
||||
raise SaltInvocationError()
|
||||
else:
|
||||
# just a name assume it is a network
|
||||
log.info(
|
||||
'Assuming network_mode \'{0}\' is a network.'.format(
|
||||
kwargs['network_mode'])
|
||||
)
|
||||
except SaltInvocationError:
|
||||
raise SaltInvocationError(
|
||||
'network_mode must be one of \'bridge\', \'host\', or '
|
||||
'\'container:<id or name>\''
|
||||
'network_mode must be one of \'bridge\', \'host\', '
|
||||
'\'container:<id or name>\' or a name of a network.'
|
||||
)
|
||||
|
||||
def _valid_restart_policy(): # pylint: disable=unused-variable
|
||||
|
@ -4231,6 +4272,153 @@ def tag_(name, image, force=False):
|
|||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
# Network Management
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def networks(names=None, ids=None):
|
||||
'''
|
||||
List existing networks
|
||||
|
||||
names
|
||||
Filter by name
|
||||
|
||||
ids
|
||||
Filter by id
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.networks names="['network-web']"
|
||||
salt myminion dockerng.networks ids="['1f9d2454d0872b68dd9e8744c6e7a4c66b86f10abaccc21e14f7f014f729b2bc']"
|
||||
'''
|
||||
response = _client_wrapper('networks',
|
||||
names=names,
|
||||
ids=ids,
|
||||
)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
def create_network(name, driver=None):
|
||||
'''
|
||||
Create a new network
|
||||
|
||||
network_id
|
||||
ID of network
|
||||
|
||||
driver
|
||||
Driver of the network
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.create_network web_network driver=bridge
|
||||
'''
|
||||
response = _client_wrapper('create_network', name, driver=driver)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def remove_network(network_id):
|
||||
'''
|
||||
Remove a network
|
||||
|
||||
network_id
|
||||
ID of network
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.remove_network 1f9d2454d0872b68dd9e8744c6e7a4c66b86f10abaccc21e14f7f014f729b2bc
|
||||
'''
|
||||
response = _client_wrapper('remove_network', network_id)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def inspect_network(network_id):
|
||||
'''
|
||||
Inspect Network
|
||||
|
||||
network_id
|
||||
ID of network
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.inspect_network 1f9d2454d0872b68dd9e8744c6e7a4c66b86f10abaccc21e14f7f014f729b2bc
|
||||
'''
|
||||
response = _client_wrapper('inspect_network', network_id)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def connect_container_to_network(container, network_id):
|
||||
'''
|
||||
Connect container to network.
|
||||
|
||||
container
|
||||
Container name or ID
|
||||
|
||||
network_id
|
||||
ID of network
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.connect_container_from_network web-1 1f9d2454d0872b68dd9e8744c6e7a4c66b86f10abaccc21e14f7f014f729b2bc
|
||||
'''
|
||||
response = _client_wrapper('connect_container_to_network',
|
||||
container,
|
||||
network_id)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
@_api_version(1.21)
|
||||
@_client_version('1.5.0')
|
||||
def disconnect_container_from_network(container, network_id):
|
||||
'''
|
||||
Disconnect container from network.
|
||||
|
||||
container
|
||||
Container name or ID
|
||||
|
||||
network_id
|
||||
ID of network
|
||||
|
||||
CLI Example:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
salt myminion dockerng.disconnect_container_from_network web-1 1f9d2454d0872b68dd9e8744c6e7a4c66b86f10abaccc21e14f7f014f729b2bc
|
||||
'''
|
||||
response = _client_wrapper('disconnect_container_from_network',
|
||||
container,
|
||||
network_id)
|
||||
_clear_context()
|
||||
# Only non-error return case is a True return, so just return the response
|
||||
return response
|
||||
|
||||
|
||||
# Functions to manage container state
|
||||
@_refresh_mine_cache
|
||||
|
|
|
@ -344,7 +344,7 @@ def _compare(actual, create_kwargs):
|
|||
)
|
||||
if actual_hosts != desired_hosts:
|
||||
ret.update({item: {'old': actual_hosts,
|
||||
'new': desired_hosts}})
|
||||
'new': desired_hosts}})
|
||||
continue
|
||||
|
||||
elif isinstance(data, list):
|
||||
|
@ -1962,6 +1962,123 @@ def absent(name, force=False):
|
|||
return ret
|
||||
|
||||
|
||||
def network_present(name, driver=None, containers=None):
|
||||
'''
|
||||
Ensure that a network is present.
|
||||
|
||||
name
|
||||
Name of the netwotk
|
||||
|
||||
driver
|
||||
Type of driver for that network.
|
||||
|
||||
containers:
|
||||
List of container names that should be part of this network
|
||||
Usage Examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
network_foo:
|
||||
dockerng.network_present
|
||||
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
network_bar:
|
||||
dockerng.network_present
|
||||
- name: bar
|
||||
- containers:
|
||||
- cont1
|
||||
- cont2
|
||||
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': ''}
|
||||
if containers is None:
|
||||
containers = []
|
||||
networks = __salt__['dockerng.networks'](names=[name])
|
||||
if networks:
|
||||
network = networks[0] # we expect network's name to be unique
|
||||
if all(c in network['Containers'] for c in containers):
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Network \'{0}\' already exists.'.format(name)
|
||||
return ret
|
||||
result = True
|
||||
for container in containers:
|
||||
if container not in network['Containers']:
|
||||
try:
|
||||
ret['changes']['connected'] = __salt__['dockerng.connect_container_to_network'](
|
||||
container, name)
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to connect container \'{0}\' to network \'{1}\' {2}'.format(
|
||||
container, name, exc))
|
||||
result = False
|
||||
ret['result'] = result
|
||||
|
||||
else:
|
||||
try:
|
||||
ret['changes']['created'] = __salt__['dockerng.create_network'](
|
||||
name, driver=driver)
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to create network \'{0}\': {1}'
|
||||
.format(name, exc))
|
||||
else:
|
||||
result = True
|
||||
for container in containers:
|
||||
try:
|
||||
ret['changes']['connected'] = __salt__['dockerng.connect_container_to_network'](
|
||||
container, name)
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to connect container \'{0}\' to network \'{1}\' {2}'.format(
|
||||
container, name, exc))
|
||||
result = False
|
||||
ret['result'] = result
|
||||
return ret
|
||||
|
||||
|
||||
def network_absent(name, driver=None):
|
||||
'''
|
||||
Ensure that a network is absent.
|
||||
|
||||
name
|
||||
Name of the netwotk
|
||||
|
||||
Usage Examples:
|
||||
|
||||
.. code-block:: yaml
|
||||
|
||||
network_foo:
|
||||
dockerng.network_absent
|
||||
|
||||
'''
|
||||
ret = {'name': name,
|
||||
'changes': {},
|
||||
'result': False,
|
||||
'comment': ''}
|
||||
|
||||
networks = __salt__['dockerng.networks'](names=[name])
|
||||
if not networks:
|
||||
ret['result'] = True
|
||||
ret['comment'] = 'Network \'{0}\' already absent'.format(name)
|
||||
return ret
|
||||
|
||||
for container in networks[0]['Containers']:
|
||||
try:
|
||||
ret['changes']['disconnected'] = __salt__['dockerng.disconnect_container_from_network'](container, name)
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to disconnect container \'{0}\' to network \'{1}\' {2}'.format(
|
||||
container, name, exc))
|
||||
try:
|
||||
ret['changes']['removed'] = __salt__['dockerng.remove_network'](name)
|
||||
ret['result'] = True
|
||||
except Exception as exc:
|
||||
ret['comment'] = ('Failed to remove network \'{0}\': {1}'
|
||||
.format(name, exc))
|
||||
return ret
|
||||
|
||||
|
||||
def mod_watch(name, sfun=None, **kwargs):
|
||||
if sfun == 'running':
|
||||
watch_kwargs = copy.deepcopy(kwargs)
|
||||
|
|
|
@ -287,6 +287,140 @@ class DockerngTestCase(TestCase):
|
|||
name='ctn',
|
||||
)
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_list_networks(self, *args):
|
||||
'''
|
||||
test list networks.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.networks(
|
||||
names=['foo'],
|
||||
ids=['01234'],
|
||||
)
|
||||
client.networks.assert_called_once_with(
|
||||
names=['foo'],
|
||||
ids=['01234'],
|
||||
)
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_create_network(self, *args):
|
||||
'''
|
||||
test create network.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.create_network(
|
||||
'foo',
|
||||
driver='bridge',
|
||||
)
|
||||
client.create_network.assert_called_once_with(
|
||||
'foo',
|
||||
driver='bridge',
|
||||
)
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_remove_network(self, *args):
|
||||
'''
|
||||
test remove network.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.remove_network('foo')
|
||||
client.remove_network.assert_called_once_with('foo')
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_inspect_network(self, *args):
|
||||
'''
|
||||
test inspect network.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.inspect_network('foo')
|
||||
client.inspect_network.assert_called_once_with('foo')
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_connect_container_to_network(self, *args):
|
||||
'''
|
||||
test inspect network.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.connect_container_to_network('container', 'foo')
|
||||
client.connect_container_to_network.assert_called_once_with(
|
||||
'container', 'foo')
|
||||
|
||||
@skipIf(_docker_py_version() < (1, 5, 0),
|
||||
'docker module must be installed to run this test or is too old. >=1.5.0')
|
||||
def test_disconnect_container_from_network(self, *args):
|
||||
'''
|
||||
test inspect network.
|
||||
'''
|
||||
__salt__ = {
|
||||
'config.get': Mock(),
|
||||
'mine.send': Mock(),
|
||||
}
|
||||
host_config = {}
|
||||
client = Mock()
|
||||
client.api_version = '1.21'
|
||||
with patch.dict(dockerng_mod.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
with patch.dict(dockerng_mod.__context__,
|
||||
{'docker.client': client}):
|
||||
dockerng_mod.disconnect_container_from_network('container', 'foo')
|
||||
client.disconnect_container_from_network.assert_called_once_with(
|
||||
'container', 'foo')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
|
|
|
@ -453,6 +453,54 @@ class DockerngTestCase(TestCase):
|
|||
labels=['LABEL1', 'LABEL2'],
|
||||
client_timeout=60)
|
||||
|
||||
def test_network_present(self):
|
||||
'''
|
||||
Test dockerng.network_present
|
||||
'''
|
||||
dockerng_create_network = Mock(return_value='created')
|
||||
dockerng_connect_container_to_network = Mock(return_value='connected')
|
||||
__salt__ = {'dockerng.create_network': dockerng_create_network,
|
||||
'dockerng.connect_container_to_network': dockerng_connect_container_to_network,
|
||||
'dockerng.networks': Mock(return_value=[]),
|
||||
}
|
||||
with patch.dict(dockerng_state.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
ret = dockerng_state.network_present(
|
||||
'network_foo',
|
||||
containers=['container'],
|
||||
)
|
||||
dockerng_create_network.assert_called_with('network_foo', driver=None)
|
||||
dockerng_connect_container_to_network.assert_called_with('container',
|
||||
'network_foo')
|
||||
self.assertEqual(ret, {'name': 'network_foo',
|
||||
'comment': '',
|
||||
'changes': {'connected': 'connected',
|
||||
'created': 'created'},
|
||||
'result': True})
|
||||
|
||||
def test_network_absent(self):
|
||||
'''
|
||||
Test dockerng.network_absent
|
||||
'''
|
||||
dockerng_remove_network = Mock(return_value='removed')
|
||||
dockerng_disconnect_container_from_network = Mock(return_value='disconnected')
|
||||
__salt__ = {'dockerng.remove_network': dockerng_remove_network,
|
||||
'dockerng.disconnect_container_from_network': dockerng_disconnect_container_from_network,
|
||||
'dockerng.networks': Mock(return_value=[{'Containers': {'container': {}}}]),
|
||||
}
|
||||
with patch.dict(dockerng_state.__dict__,
|
||||
{'__salt__': __salt__}):
|
||||
ret = dockerng_state.network_absent(
|
||||
'network_foo',
|
||||
)
|
||||
dockerng_disconnect_container_from_network.assert_called_with('container',
|
||||
'network_foo')
|
||||
dockerng_remove_network.assert_called_with('network_foo')
|
||||
self.assertEqual(ret, {'name': 'network_foo',
|
||||
'comment': '',
|
||||
'changes': {'disconnected': 'disconnected',
|
||||
'removed': 'removed'},
|
||||
'result': True})
|
||||
|
||||
if __name__ == '__main__':
|
||||
from integration import run_tests
|
||||
|
|
Loading…
Add table
Reference in a new issue