Merge pull request #29300 from ticosax/dockerng-volumes

[dockerng] Add support for volume management in dockerng
This commit is contained in:
Mike Place 2015-12-02 10:48:53 -07:00
commit 5ec7947595
4 changed files with 396 additions and 6 deletions

View file

@ -1421,10 +1421,14 @@ def _validate_input(kwargs,
raise SaltInvocationError(err)
if not os.path.isabs(host_path):
raise SaltInvocationError(
'Host path {0} in bind {1} is not absolute'
.format(host_path, bind)
)
if os.path.sep in host_path:
raise SaltInvocationError(
'Host path {0} in bind {1} is not absolute'
.format(container_path, bind)
)
log.warn('Host path {0} in bind {1} is not absolute,'
' assuming it is a docker volume.'.format(host_path,
bind))
if not os.path.isabs(container_path):
raise SaltInvocationError(
'Container path {0} in bind {1} is not absolute'
@ -4304,6 +4308,7 @@ def networks(names=None, ids=None):
@_api_version(1.21)
@_client_version('1.5.0')
def create_network(name, driver=None):
'''
Create a new network
@ -4419,6 +4424,107 @@ def disconnect_container_from_network(container, network_id):
# Only non-error return case is a True return, so just return the response
return response
# Volume Management
@_api_version(1.21)
@_client_version('1.5.0')
def volumes(filters=None):
'''
List existing volumes
.. versionadded:: 2015.8.4
filters
There is one available filter: dangling=true
CLI Example:
.. code-block:: bash
salt myminion dockerng.volumes filters="{'dangling': True}"
'''
response = _client_wrapper('volumes', filters=filters)
_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 create_volume(name, driver=None, driver_opts=None):
'''
Create a new volume
.. versionadded:: 2015.8.4
name
name of volume
driver
Driver of the volume
driver_opts
Options for the driver volume
CLI Example:
.. code-block:: bash
salt myminion dockerng.create_volume my_volume driver=local
'''
response = _client_wrapper('create_volume', name, driver=driver,
driver_opts=driver_opts)
_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_volume(name):
'''
Remove a volume
.. versionadded:: 2015.8.4
name
Name of volume
CLI Example:
.. code-block:: bash
salt myminion dockerng.remove_volume my_volume
'''
response = _client_wrapper('remove_volume', name)
_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_volume(name):
'''
Inspect Volume
.. versionadded:: 2015.8.4
name
Name of volume
CLI Example:
.. code-block:: bash
salt myminion dockerng.inspect_volume my_volume
'''
response = _client_wrapper('inspect_volume', name)
_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

View file

@ -1969,7 +1969,7 @@ def network_present(name, driver=None, containers=None):
Ensure that a network is present.
name
Name of the netwotk
Name of the network
driver
Type of driver for that network.
@ -2045,7 +2045,7 @@ def network_absent(name, driver=None):
Ensure that a network is absent.
name
Name of the netwotk
Name of the network
Usage Examples:
@ -2081,6 +2081,132 @@ def network_absent(name, driver=None):
return ret
def volume_present(name, driver=None, driver_opts=None):
'''
Ensure that a volume is present.
.. versionadded:: 2015.8.4
name
Name of the volume
driver
Type of driver for that volume.
driver_opts
Option for tha volume driver
Usage Examples:
.. code-block:: yaml
volume_foo:
dockerng.volume_present
.. code-block:: yaml
volume_bar:
dockerng.volume_present
- name: bar
- driver: local
- driver_opts:
foo: bar
.. code-block:: yaml
volume_bar:
dockerng.volume_present
- name: bar
- driver: local
- driver_opts:
- foo: bar
- option: value
'''
ret = {'name': name,
'changes': {},
'result': False,
'comment': ''}
if salt.utils.is_dictlist(driver_opts):
driver_opts = salt.utils.repack_dictlist(driver_opts)
volumes = [v for v in __salt__['dockerng.volumes']()['Volumes'] if v['Name'] == name]
if not volumes:
try:
ret['changes']['created'] = __salt__['dockerng.create_volume'](
name, driver=driver, driver_opts=driver_opts)
except Exception as exc:
ret['comment'] = ('Failed to create volume \'{0}\': {1}'
.format(name, exc))
return ret
else:
result = True
ret['result'] = result
return ret
# volume exits, check if driver is the same.
volume = volumes[0]
if volume['Driver'] != driver:
try:
ret['changes']['removed'] = __salt__['dockerng.remove_volume'](name)
except Exception as exc:
ret['comment'] = ('Failed to remove volume \'{0}\': {1}'
.format(name, exc))
return ret
else:
try:
ret['changes']['created'] = __salt__['dockerng.create_volume'](
name, driver=driver, driver_opts=driver_opts)
except Exception as exc:
ret['comment'] = ('Failed to create volume \'{0}\': {1}'
.format(name, exc))
return ret
else:
result = True
ret['result'] = result
return ret
ret['result'] = True
ret['comment'] = 'Volume \'{0}\' already exists.'.format(name)
return ret
def volume_absent(name, driver=None):
'''
Ensure that a volume is absent.
.. versionadded:: 2015.8.4
name
Name of the volume
Usage Examples:
.. code-block:: yaml
volume_foo:
dockerng.volume_absent
'''
ret = {'name': name,
'changes': {},
'result': False,
'comment': ''}
volumes = [v for v in __salt__['dockerng.volumes']()['Volumes'] if v['Name'] == name]
if not volumes:
ret['result'] = True
ret['comment'] = 'Volume \'{0}\' already absent'.format(name)
return ret
try:
ret['changes']['removed'] = __salt__['dockerng.remove_volume'](name)
ret['result'] = True
except Exception as exc:
ret['comment'] = ('Failed to remove volume \'{0}\': {1}'
.format(name, exc))
return ret
def mod_watch(name, sfun=None, **kwargs):
if sfun == 'running':
watch_kwargs = copy.deepcopy(kwargs)

View file

@ -421,6 +421,94 @@ class DockerngTestCase(TestCase):
client.disconnect_container_from_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_list_volumes(self, *args):
'''
test list volumes.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
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.volumes(
filters={'dangling': [True]},
)
client.volumes.assert_called_once_with(
filters={'dangling': [True]},
)
@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_volume(self, *args):
'''
test create volume.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
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_volume(
'foo',
driver='bridge',
driver_opts={},
)
client.create_volume.assert_called_once_with(
'foo',
driver='bridge',
driver_opts={},
)
@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_volume(self, *args):
'''
test remove volume.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
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_volume('foo')
client.remove_volume.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_volume(self, *args):
'''
test inspect volume.
'''
__salt__ = {
'config.get': Mock(),
'mine.send': Mock(),
}
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_volume('foo')
client.inspect_volume.assert_called_once_with('foo')
if __name__ == '__main__':
from integration import run_tests

View file

@ -502,6 +502,76 @@ class DockerngTestCase(TestCase):
'removed': 'removed'},
'result': True})
def test_volume_present(self):
'''
Test dockerng.volume_present
'''
dockerng_create_volume = Mock(return_value='created')
__salt__ = {'dockerng.create_volume': dockerng_create_volume,
'dockerng.volumes': Mock(return_value={'Volumes': []}),
}
with patch.dict(dockerng_state.__dict__,
{'__salt__': __salt__}):
ret = dockerng_state.volume_present(
'volume_foo',
)
dockerng_create_volume.assert_called_with('volume_foo',
driver=None,
driver_opts=None)
self.assertEqual(ret, {'name': 'volume_foo',
'comment': '',
'changes': {'created': 'created'},
'result': True})
def test_volume_present_with_another_driver(self):
'''
Test dockerng.volume_present
'''
dockerng_create_volume = Mock(return_value='created')
dockerng_remove_volume = Mock(return_value='removed')
__salt__ = {'dockerng.create_volume': dockerng_create_volume,
'dockerng.remove_volume': dockerng_remove_volume,
'dockerng.volumes': Mock(return_value={
'Volumes': [{'Name': 'volume_foo',
'Driver': 'foo'}]}),
}
with patch.dict(dockerng_state.__dict__,
{'__salt__': __salt__}):
ret = dockerng_state.volume_present(
'volume_foo',
driver='bar'
)
dockerng_remove_volume.assert_called_with('volume_foo')
dockerng_create_volume.assert_called_with('volume_foo',
driver='bar',
driver_opts=None)
self.assertEqual(ret, {'name': 'volume_foo',
'comment': '',
'changes': {'created': 'created',
'removed': 'removed'},
'result': True})
def test_volume_absent(self):
'''
Test dockerng.volume_absent
'''
dockerng_remove_volume = Mock(return_value='removed')
__salt__ = {'dockerng.remove_volume': dockerng_remove_volume,
'dockerng.volumes': Mock(return_value={
'Volumes': [{'Name': 'volume_foo'}]}),
}
with patch.dict(dockerng_state.__dict__,
{'__salt__': __salt__}):
ret = dockerng_state.volume_absent(
'volume_foo',
)
dockerng_remove_volume.assert_called_with('volume_foo')
self.assertEqual(ret, {'name': 'volume_foo',
'comment': '',
'changes': {'removed': 'removed'},
'result': True})
if __name__ == '__main__':
from integration import run_tests
run_tests(DockerngTestCase, needs_daemon=False)