mirror of
https://github.com/valitydev/salt.git
synced 2024-11-07 17:09:03 +00:00
665de2d1f9
Resolves #42538
1832 lines
58 KiB
Python
1832 lines
58 KiB
Python
# -*- coding: utf-8 -*-
|
|
'''
|
|
tests.unit.utils.test_docker
|
|
============================
|
|
|
|
Test the funcs in salt.utils.docker and salt.utils.docker.translate
|
|
'''
|
|
# Import Python Libs
|
|
from __future__ import absolute_import
|
|
import functools
|
|
import logging
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
# Import Salt Testing Libs
|
|
from tests.support.unit import TestCase
|
|
|
|
# Import salt libs
|
|
import salt.config
|
|
import salt.loader
|
|
import salt.utils.docker as docker_utils
|
|
import salt.utils.docker.translate as translate_funcs
|
|
|
|
# Import 3rd-party libs
|
|
from salt.ext import six
|
|
|
|
|
|
def __test_stringlist(testcase, name):
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
# Using file paths here because "volumes" must be passed through this
|
|
# set of assertions and it requires absolute paths.
|
|
if salt.utils.is_windows():
|
|
data = [r'c:\foo', r'c:\bar', r'c:\baz']
|
|
else:
|
|
data = ['/foo', '/bar', '/baz']
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(**{item: ','.join(data)}),
|
|
({name: data}, {}, [])
|
|
)
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(**{item: data}),
|
|
({name: data}, {}, [])
|
|
)
|
|
if name != 'volumes':
|
|
# Test coercing to string
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(**{item: ['one', 2]}),
|
|
({name: ['one', '2']}, {}, [])
|
|
)
|
|
if alias is not None:
|
|
# Test collision
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(**{name: data, alias: sorted(data)}),
|
|
({name: data}, {}, [name])
|
|
)
|
|
|
|
|
|
def __test_key_value(testcase, name, delimiter):
|
|
'''
|
|
Common logic for key/value pair testing
|
|
'''
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
expected = {'foo': 'bar', 'baz': 'qux'}
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: 'foo{0}bar,baz{0}qux'.format(delimiter)}),
|
|
({name: expected}, {}, [])
|
|
)
|
|
# This two are contrived examples, but they will test bool-ifying a
|
|
# non-bool value to ensure proper input format.
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: ['foo{0}bar'.format(delimiter),
|
|
'baz{0}qux'.format(delimiter)]}
|
|
),
|
|
({name: expected}, {}, [])
|
|
)
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(**{item: expected}),
|
|
({name: expected}, {}, [])
|
|
)
|
|
# "Dictlist" input from states
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: [{'foo': 'bar'}, {'baz': 'qux'}]}
|
|
),
|
|
({name: expected}, {}, [])
|
|
)
|
|
# Passing a non-string should be converted to a string
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(labels=1.0),
|
|
({'labels': ['1.0']}, {}, [])
|
|
)
|
|
if alias is not None:
|
|
# Test collision
|
|
testcase.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{name: 'foo{0}bar,baz{0}qux'.format(delimiter),
|
|
alias: 'hello{0}world'.format(delimiter)}),
|
|
({name: expected}, {}, [name])
|
|
)
|
|
|
|
|
|
def assert_bool(func):
|
|
'''
|
|
Test a boolean value
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: True}),
|
|
({name: True}, {}, [])
|
|
)
|
|
# This two are contrived examples, but they will test bool-ifying a
|
|
# non-bool value to ensure proper input format.
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 'foo'}),
|
|
({name: True}, {}, [])
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 0}),
|
|
({name: False}, {}, [])
|
|
)
|
|
if alias is not None:
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{name: True, alias: False}),
|
|
({name: True}, {}, [name])
|
|
)
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_int(func):
|
|
'''
|
|
Test an integer value
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 100}),
|
|
({name: 100}, {}, [])
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: '200'}),
|
|
({name: 200}, {}, [])
|
|
)
|
|
# Error case: non-numeric value passed
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 'foo'}),
|
|
({}, {item: '\'foo\' is not an integer'}, [])
|
|
)
|
|
if alias is not None:
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{name: 100, alias: 200}),
|
|
({name: 100}, {}, [name])
|
|
)
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_string(func):
|
|
'''
|
|
Test that item is a string or is converted to one
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
# Using file paths here because "working_dir" must be passed through
|
|
# this set of assertions and it requires absolute paths.
|
|
if salt.utils.is_windows():
|
|
data = r'c:\foo'
|
|
else:
|
|
data = '/foo'
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: data}),
|
|
({name: data}, {}, [])
|
|
)
|
|
if name != 'working_dir':
|
|
# Test coercing to string
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 123}),
|
|
({name: '123'}, {}, [])
|
|
)
|
|
if alias is not None:
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{name: data, alias: data}),
|
|
({name: data}, {}, [name])
|
|
)
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_int_or_string(func):
|
|
'''
|
|
Test an integer or string value
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 100}),
|
|
({name: 100}, {}, [])
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: '100M'}),
|
|
({name: '100M'}, {}, [])
|
|
)
|
|
if alias is not None:
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{name: 100, alias: '100M'}),
|
|
({name: 100}, {}, [name])
|
|
)
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_stringlist(func):
|
|
'''
|
|
Test a comma-separated or Python list of strings
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
__test_stringlist(self, name)
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_dict(func):
|
|
'''
|
|
Dictionaries should be untouched, dictlists should be repacked and end up
|
|
as a single dictionary.
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
expected = {'foo': 'bar', 'baz': 'qux'}
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: expected}),
|
|
({name: expected}, {}, [])
|
|
)
|
|
# "Dictlist" input from states
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: [{x: y} for x, y in six.iteritems(expected)]}
|
|
),
|
|
({name: expected}, {}, [])
|
|
)
|
|
# Error case: non-dictionary input
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 'foo'}),
|
|
({}, {item: '\'foo\' is not a dictionary'}, [])
|
|
)
|
|
if alias is not None:
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{name: 'foo', alias: 'bar'}),
|
|
({name: 'foo'}, {}, [name])
|
|
)
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_cmd(func):
|
|
'''
|
|
Test for a string, or a comma-separated or Python list of strings. This is
|
|
different from a stringlist in that we do not do any splitting. This
|
|
decorator is used both by the "command" and "entrypoint" arguments.
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 'foo bar'}),
|
|
({name: 'foo bar'}, {}, [])
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: ['foo', 'bar']}),
|
|
({name: ['foo', 'bar']}, {}, [])
|
|
)
|
|
# Test coercing to string
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 123}),
|
|
({name: '123'}, {}, [])
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: ['one', 2]}),
|
|
({name: ['one', '2']}, {}, [])
|
|
)
|
|
if alias is not None:
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{name: 'foo', alias: 'bar'}),
|
|
({name: 'foo'}, {}, [name])
|
|
)
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_key_colon_value(func):
|
|
'''
|
|
Test a key/value pair with parameters passed as key:value pairs
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
__test_key_value(self, name, ':')
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_key_equals_value(func):
|
|
'''
|
|
Test a key/value pair with parameters passed as key=value pairs
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
__test_key_value(self, name, '=')
|
|
if name == 'labels':
|
|
__test_stringlist(self, name)
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
def assert_device_rates(func):
|
|
'''
|
|
Tests for device_{read,write}_{bps,iops}. The bps values have a "Rate"
|
|
value expressed in bytes/kb/mb/gb, while the iops values have a "Rate"
|
|
expressed as a simple integer.
|
|
'''
|
|
@functools.wraps(func)
|
|
def wrapper(self, *args, **kwargs):
|
|
# Strip off the "test_" from the function name
|
|
name = func.__name__[5:]
|
|
alias = salt.utils.docker.ALIASES_REVMAP.get(name)
|
|
for item in (name, alias):
|
|
if item is None:
|
|
continue
|
|
|
|
# Error case: Not an absolute path
|
|
if salt.utils.is_windows():
|
|
path = r'foo\bar\baz'
|
|
else:
|
|
path = 'foo/bar/baz'
|
|
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: '{0}:1048576'.format(path)}
|
|
),
|
|
(
|
|
{},
|
|
{item: 'Path \'{0}\' is not absolute'.format(path)},
|
|
[]
|
|
)
|
|
)
|
|
|
|
if name.endswith('_bps'):
|
|
# Both integer bytes and a string providing a shorthand for kb,
|
|
# mb, or gb can be used, so we need to test for both.
|
|
expected = (
|
|
{name: [{'Path': '/dev/sda', 'Rate': 1048576},
|
|
{'Path': '/dev/sdb', 'Rate': 1048576}]},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: '/dev/sda:1048576,/dev/sdb:1048576'}
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: ['/dev/sda:1048576', '/dev/sdb:1048576']}
|
|
),
|
|
expected
|
|
)
|
|
expected = (
|
|
{name: [{'Path': '/dev/sda', 'Rate': '1mb'},
|
|
{'Path': '/dev/sdb', 'Rate': '5mb'}]},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: '/dev/sda:1mb,/dev/sdb:5mb'}
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: ['/dev/sda:1mb', '/dev/sdb:5mb']}
|
|
),
|
|
expected
|
|
)
|
|
|
|
if alias is not None:
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{name: '/dev/sda:1048576,/dev/sdb:1048576',
|
|
alias: '/dev/sda:1mb,/dev/sdb:5mb'}
|
|
),
|
|
(
|
|
{name: [{'Path': '/dev/sda', 'Rate': 1048576},
|
|
{'Path': '/dev/sdb', 'Rate': 1048576}]},
|
|
{}, [name]
|
|
)
|
|
)
|
|
else:
|
|
# The "Rate" value must be an integer
|
|
expected = (
|
|
{name: [{'Path': '/dev/sda', 'Rate': 1000},
|
|
{'Path': '/dev/sdb', 'Rate': 500}]},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: '/dev/sda:1000,/dev/sdb:500'}
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: ['/dev/sda:1000', '/dev/sdb:500']}
|
|
),
|
|
expected
|
|
)
|
|
# Test non-integer input
|
|
expected = (
|
|
{},
|
|
{item: 'Rate \'5mb\' for path \'/dev/sdb\' is non-numeric'},
|
|
[]
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: '/dev/sda:1000,/dev/sdb:5mb'}
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{item: ['/dev/sda:1000', '/dev/sdb:5mb']}
|
|
),
|
|
expected
|
|
)
|
|
|
|
if alias is not None:
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{name: '/dev/sda:1000,/dev/sdb:500',
|
|
alias: '/dev/sda:888,/dev/sdb:999'}
|
|
),
|
|
(
|
|
{name: [{'Path': '/dev/sda', 'Rate': 1000},
|
|
{'Path': '/dev/sdb', 'Rate': 500}]},
|
|
{}, [name]
|
|
)
|
|
)
|
|
|
|
return func(self, *args, **kwargs)
|
|
return wrapper
|
|
|
|
|
|
class TranslateInputTestCase(TestCase):
|
|
'''
|
|
Tests for salt.utils.docker.translate_input(). This function returns a
|
|
3-tuple consisting of:
|
|
|
|
1) A translated copy of the kwargs
|
|
2) A dictionary mapping any invalid arguments to error messages describing
|
|
why they are invalid
|
|
3) A list of "collisions" (API arguments for which their alias was also
|
|
provided)
|
|
'''
|
|
maxDiff = None
|
|
|
|
def tearDown(self):
|
|
'''
|
|
Test skip_translate kwarg
|
|
'''
|
|
name = self.id().split('.')[-1][5:]
|
|
# The below is not valid input for the Docker API, but these
|
|
# assertions confirm that we successfully skipped translation.
|
|
expected = ({name: 'foo'}, {}, [])
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{name: 'foo', 'skip_translate': True}
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
**{name: 'foo', 'skip_translate': [name]}
|
|
),
|
|
expected
|
|
)
|
|
|
|
@assert_bool
|
|
def test_auto_remove(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
def test_binds(self):
|
|
'''
|
|
Test the "binds" kwarg. Any volumes not defined in the "volumes" kwarg
|
|
should be added to the results.
|
|
'''
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
binds='/srv/www:/var/www:ro',
|
|
volumes='/testing'),
|
|
(
|
|
{'binds': ['/srv/www:/var/www:ro'],
|
|
'volumes': ['/testing', '/var/www']},
|
|
{},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
binds=['/srv/www:/var/www:ro'],
|
|
volumes='/testing'),
|
|
(
|
|
{'binds': ['/srv/www:/var/www:ro'],
|
|
'volumes': ['/testing', '/var/www']},
|
|
{},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
binds={'/srv/www': {'bind': '/var/www', 'mode': 'ro'}},
|
|
volumes='/testing'),
|
|
(
|
|
{'binds': {'/srv/www': {'bind': '/var/www', 'mode': 'ro'}},
|
|
'volumes': ['/testing', '/var/www']},
|
|
{},
|
|
[]
|
|
)
|
|
)
|
|
|
|
@assert_int
|
|
def test_blkio_weight(self):
|
|
'''
|
|
Should be an int or converted to one
|
|
'''
|
|
pass
|
|
|
|
def test_blkio_weight_device(self):
|
|
'''
|
|
Should translate a list of PATH:WEIGHT pairs to a list of dictionaries
|
|
with the following format: {'Path': PATH, 'Weight': WEIGHT}
|
|
'''
|
|
expected = (
|
|
{'blkio_weight_device': [{'Path': '/dev/sda', 'Weight': 100},
|
|
{'Path': '/dev/sdb', 'Weight': 200}]},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
blkio_weight_device='/dev/sda:100,/dev/sdb:200'
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
blkio_weight_device=['/dev/sda:100', '/dev/sdb:200']
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(blkio_weight_device='foo'),
|
|
(
|
|
{},
|
|
{'blkio_weight_device': '\'foo\' contains 1 value(s) '
|
|
'(expected 2)'},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(blkio_weight_device='foo:bar:baz'),
|
|
(
|
|
{},
|
|
{'blkio_weight_device': '\'foo:bar:baz\' contains 3 value(s) '
|
|
'(expected 2)'},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
blkio_weight_device=['/dev/sda:100', '/dev/sdb:foo']
|
|
),
|
|
(
|
|
{},
|
|
{'blkio_weight_device': 'Weight \'foo\' for path \'/dev/sdb\' '
|
|
'is not an integer'},
|
|
[]
|
|
)
|
|
)
|
|
|
|
@assert_stringlist
|
|
def test_cap_add(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_stringlist
|
|
def test_cap_drop(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_cmd
|
|
def test_command(self):
|
|
'''
|
|
Can either be a string or a comma-separated or Python list of strings.
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_cpuset_cpus(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_cpuset_mems(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int
|
|
def test_cpu_group(self):
|
|
'''
|
|
Should be an int or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int
|
|
def test_cpu_period(self):
|
|
'''
|
|
Should be an int or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int
|
|
def test_cpu_shares(self):
|
|
'''
|
|
Should be an int or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_bool
|
|
def test_detach(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_device_rates
|
|
def test_device_read_bps(self):
|
|
'''
|
|
CLI input is a list of PATH:RATE pairs, but the API expects a list of
|
|
dictionaries in the format [{'Path': path, 'Rate': rate}]
|
|
'''
|
|
pass
|
|
|
|
@assert_device_rates
|
|
def test_device_read_iops(self):
|
|
'''
|
|
CLI input is a list of PATH:RATE pairs, but the API expects a list of
|
|
dictionaries in the format [{'Path': path, 'Rate': rate}]
|
|
'''
|
|
pass
|
|
|
|
@assert_device_rates
|
|
def test_device_write_bps(self):
|
|
'''
|
|
CLI input is a list of PATH:RATE pairs, but the API expects a list of
|
|
dictionaries in the format [{'Path': path, 'Rate': rate}]
|
|
'''
|
|
pass
|
|
|
|
@assert_device_rates
|
|
def test_device_write_iops(self):
|
|
'''
|
|
CLI input is a list of PATH:RATE pairs, but the API expects a list of
|
|
dictionaries in the format [{'Path': path, 'Rate': rate}]
|
|
'''
|
|
pass
|
|
|
|
@assert_stringlist
|
|
def test_devices(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_stringlist
|
|
def test_dns_opt(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_stringlist
|
|
def test_dns_search(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
def test_dns(self):
|
|
'''
|
|
While this is a stringlist, it also supports IP address validation, so
|
|
it can't use the test_stringlist decorator because we need to test both
|
|
with and without validation, and it isn't necessary to make all other
|
|
stringlist tests also do that same kind of testing.
|
|
'''
|
|
expected = ({'dns': ['8.8.8.8', '8.8.4.4']}, {}, [])
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
dns='8.8.8.8,8.8.4.4',
|
|
validate_ip_addrs=True,
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
dns=['8.8.8.8', '8.8.4.4'],
|
|
validate_ip_addrs=True,
|
|
),
|
|
expected
|
|
)
|
|
|
|
# Error case: invalid IP address caught by validaton
|
|
expected = ({}, {'dns': '\'8.8.8.888\' is not a valid IP address'}, [])
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
dns='8.8.8.888,8.8.4.4',
|
|
validate_ip_addrs=True,
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
dns=['8.8.8.888', '8.8.4.4'],
|
|
validate_ip_addrs=True,
|
|
),
|
|
expected
|
|
)
|
|
|
|
# This is not valid input but it will test whether or not IP address
|
|
# validation happened.
|
|
expected = ({'dns': ['foo', 'bar']}, {}, [])
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
dns='foo,bar',
|
|
validate_ip_addrs=False,
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
dns=['foo', 'bar'],
|
|
validate_ip_addrs=False,
|
|
),
|
|
expected
|
|
)
|
|
|
|
@assert_string
|
|
def test_domainname(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_cmd
|
|
def test_entrypoint(self):
|
|
'''
|
|
Can either be a string or a comma-separated or Python list of strings.
|
|
'''
|
|
pass
|
|
|
|
@assert_key_equals_value
|
|
def test_environment(self):
|
|
'''
|
|
Can be passed in several formats but must end up as a dictionary
|
|
mapping keys to values
|
|
'''
|
|
pass
|
|
|
|
def test_extra_hosts(self):
|
|
'''
|
|
Can be passed as a list of key:value pairs but can't be simply tested
|
|
using @assert_key_colon_value since we need to test both with and without
|
|
IP address validation.
|
|
'''
|
|
expected = (
|
|
{'extra_hosts': {'web1': '10.9.8.7', 'web2': '10.9.8.8'}},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
extra_hosts='web1:10.9.8.7,web2:10.9.8.8',
|
|
validate_ip_addrs=True,
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
extra_hosts=['web1:10.9.8.7', 'web2:10.9.8.8'],
|
|
validate_ip_addrs=True,
|
|
),
|
|
expected
|
|
)
|
|
|
|
expected = (
|
|
{},
|
|
{'extra_hosts': '\'10.9.8.299\' is not a valid IP address'},
|
|
[]
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
extra_hosts='web1:10.9.8.299,web2:10.9.8.8',
|
|
validate_ip_addrs=True,
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
extra_hosts=['web1:10.9.8.299', 'web2:10.9.8.8'],
|
|
validate_ip_addrs=True,
|
|
),
|
|
expected
|
|
)
|
|
|
|
# This is not valid input but it will test whether or not IP address
|
|
# validation happened.
|
|
expected = ({'extra_hosts': {'foo': 'bar', 'baz': 'qux'}}, {}, [])
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
extra_hosts='foo:bar,baz:qux',
|
|
validate_ip_addrs=False,
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
extra_hosts=['foo:bar', 'baz:qux'],
|
|
validate_ip_addrs=False,
|
|
),
|
|
expected
|
|
)
|
|
|
|
@assert_stringlist
|
|
def test_group_add(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_hostname(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_ipc_mode(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_isolation(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_key_equals_value
|
|
def test_labels(self):
|
|
'''
|
|
Can be passed as a list of key=value pairs or a dictionary, and must
|
|
ultimately end up as a dictionary.
|
|
'''
|
|
pass
|
|
|
|
@assert_key_colon_value
|
|
def test_links(self):
|
|
'''
|
|
Can be passed as a list of key:value pairs or a dictionary, and must
|
|
ultimately end up as a dictionary.
|
|
'''
|
|
pass
|
|
|
|
def test_log_config(self):
|
|
'''
|
|
This is a mixture of log_driver and log_opt, which get combined into a
|
|
dictionary.
|
|
|
|
log_driver is a simple string, but log_opt can be passed in several
|
|
ways, so we need to test them all.
|
|
'''
|
|
expected = (
|
|
{'log_config': {'Type': 'foo',
|
|
'Config': {'foo': 'bar', 'baz': 'qux'}}},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
log_driver='foo',
|
|
log_opt='foo=bar,baz=qux'
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
log_driver='foo',
|
|
log_opt=['foo=bar', 'baz=qux']
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
log_driver='foo',
|
|
log_opt={'foo': 'bar', 'baz': 'qux'}
|
|
),
|
|
expected
|
|
)
|
|
|
|
# Ensure passing either `log_driver` or `log_opt` works
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
log_driver='foo'
|
|
),
|
|
(
|
|
{'log_config': {'Type': 'foo',
|
|
'Config': {}}},
|
|
{}, []
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
log_opt={'foo': 'bar', 'baz': 'qux'}
|
|
),
|
|
(
|
|
{'log_config': {'Type': 'none',
|
|
'Config': {'foo': 'bar', 'baz': 'qux'}}},
|
|
{}, []
|
|
)
|
|
)
|
|
|
|
@assert_key_equals_value
|
|
def test_lxc_conf(self):
|
|
'''
|
|
Can be passed as a list of key=value pairs or a dictionary, and must
|
|
ultimately end up as a dictionary.
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_mac_address(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int_or_string
|
|
def test_mem_limit(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int
|
|
def test_mem_swappiness(self):
|
|
'''
|
|
Should be an int or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int_or_string
|
|
def test_memswap_limit(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_name(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_bool
|
|
def test_network_disabled(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_network_mode(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_bool
|
|
def test_oom_kill_disable(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int
|
|
def test_oom_score_adj(self):
|
|
'''
|
|
Should be an int or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_pid_mode(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int
|
|
def test_pids_limit(self):
|
|
'''
|
|
Should be an int or converted to one
|
|
'''
|
|
pass
|
|
|
|
def test_port_bindings(self):
|
|
'''
|
|
This has several potential formats and can include port ranges. It
|
|
needs its own test.
|
|
'''
|
|
# ip:hostPort:containerPort - Bind a specific IP and port on the host
|
|
# to a specific port within the container.
|
|
expected = (
|
|
{'port_bindings': {
|
|
80: [('10.1.2.3', 8080), ('10.1.2.3', 8888)],
|
|
3333: ('10.4.5.6', 3333),
|
|
4505: ('10.7.8.9', 14505),
|
|
4506: ('10.7.8.9', 14506),
|
|
'81/udp': [('10.1.2.3', 8080), ('10.1.2.3', 8888)],
|
|
'3334/udp': ('10.4.5.6', 3334),
|
|
'5505/udp': ('10.7.8.9', 15505),
|
|
'5506/udp': ('10.7.8.9', 15506)},
|
|
'ports': [80, '81/udp', 3333, '3334/udp', 4505, 4506, '5505/udp', '5506/udp'],
|
|
},
|
|
{}, []
|
|
)
|
|
translated_input = docker_utils.translate_input(
|
|
port_bindings='10.1.2.3:8080:80,10.1.2.3:8888:80,10.4.5.6:3333:3333,'
|
|
'10.7.8.9:14505-14506:4505-4506,10.1.2.3:8080:81/udp,'
|
|
'10.1.2.3:8888:81/udp,10.4.5.6:3334:3334/udp,'
|
|
'10.7.8.9:15505-15506:5505-5506/udp',
|
|
)
|
|
self.assertEqual(translated_input, expected)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings=[
|
|
'10.1.2.3:8080:80',
|
|
'10.1.2.3:8888:80',
|
|
'10.4.5.6:3333:3333',
|
|
'10.7.8.9:14505-14506:4505-4506',
|
|
'10.1.2.3:8080:81/udp',
|
|
'10.1.2.3:8888:81/udp',
|
|
'10.4.5.6:3334:3334/udp',
|
|
'10.7.8.9:15505-15506:5505-5506/udp']
|
|
),
|
|
expected
|
|
)
|
|
|
|
# ip::containerPort - Bind a specific IP and an ephemeral port to a
|
|
# specific port within the container.
|
|
expected = (
|
|
{'port_bindings': {
|
|
80: [('10.1.2.3',), ('10.1.2.3',)],
|
|
3333: ('10.4.5.6',),
|
|
4505: ('10.7.8.9',),
|
|
4506: ('10.7.8.9',),
|
|
'81/udp': [('10.1.2.3',), ('10.1.2.3',)],
|
|
'3334/udp': ('10.4.5.6',),
|
|
'5505/udp': ('10.7.8.9',),
|
|
'5506/udp': ('10.7.8.9',)},
|
|
'ports': [80, '81/udp', 3333, '3334/udp', 4505, 4506, '5505/udp', '5506/udp'],
|
|
},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='10.1.2.3::80,10.1.2.3::80,10.4.5.6::3333,10.7.8.9::4505-4506,10.1.2.3::81/udp,10.1.2.3::81/udp,10.4.5.6::3334/udp,10.7.8.9::5505-5506/udp',
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings=[
|
|
'10.1.2.3::80',
|
|
'10.1.2.3::80',
|
|
'10.4.5.6::3333',
|
|
'10.7.8.9::4505-4506',
|
|
'10.1.2.3::81/udp',
|
|
'10.1.2.3::81/udp',
|
|
'10.4.5.6::3334/udp',
|
|
'10.7.8.9::5505-5506/udp']
|
|
),
|
|
expected
|
|
)
|
|
|
|
# hostPort:containerPort - Bind a specific port on all of the host's
|
|
# interfaces to a specific port within the container.
|
|
expected = (
|
|
{'port_bindings': {80: [8080, 8888],
|
|
3333: 3333,
|
|
4505: 14505,
|
|
4506: 14506,
|
|
'81/udp': [8080, 8888],
|
|
'3334/udp': 3334,
|
|
'5505/udp': 15505,
|
|
'5506/udp': 15506},
|
|
'ports': [80, '81/udp', 3333, '3334/udp', 4505, 4506, '5505/udp', '5506/udp'],
|
|
},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='8080:80,8888:80,3333:3333,14505-14506:4505-4506,8080:81/udp,8888:81/udp,3334:3334/udp,15505-15506:5505-5506/udp',
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings=['8080:80',
|
|
'8888:80',
|
|
'3333:3333',
|
|
'14505-14506:4505-4506',
|
|
'8080:81/udp',
|
|
'8888:81/udp',
|
|
'3334:3334/udp',
|
|
'15505-15506:5505-5506/udp']
|
|
),
|
|
expected
|
|
)
|
|
|
|
# containerPort - Bind an ephemeral port on all of the host's
|
|
# interfaces to a specific port within the container.
|
|
expected = (
|
|
{'port_bindings': {80: None,
|
|
3333: None,
|
|
4505: None,
|
|
4506: None,
|
|
'81/udp': None,
|
|
'3334/udp': None,
|
|
'5505/udp': None,
|
|
'5506/udp': None},
|
|
'ports': [80, '81/udp', 3333, '3334/udp', 4505, 4506, '5505/udp', '5506/udp'],
|
|
},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='80,3333,4505-4506,81/udp,3334/udp,5505-5506/udp',
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings=['80', '3333', '4505-4506',
|
|
'81/udp', '3334/udp', '5505-5506/udp']
|
|
),
|
|
expected
|
|
)
|
|
|
|
# Test a mixture of different types of input
|
|
expected = (
|
|
{'port_bindings': {80: ('10.1.2.3', 8080),
|
|
3333: ('10.4.5.6',),
|
|
4505: 14505,
|
|
4506: 14506,
|
|
9999: None,
|
|
10000: None,
|
|
10001: None,
|
|
'81/udp': ('10.1.2.3', 8080),
|
|
'3334/udp': ('10.4.5.6',),
|
|
'5505/udp': 15505,
|
|
'5506/udp': 15506,
|
|
'19999/udp': None,
|
|
'20000/udp': None,
|
|
'20001/udp': None},
|
|
'ports': [80, '81/udp', 3333, '3334/udp', 4505, 4506, '5505/udp', '5506/udp',
|
|
9999, 10000, 10001, '19999/udp', '20000/udp', '20001/udp']
|
|
},
|
|
{}, []
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='10.1.2.3:8080:80,10.4.5.6::3333,14505-14506:4505-4506,9999-10001,10.1.2.3:8080:81/udp,10.4.5.6::3334/udp,15505-15506:5505-5506/udp,19999-20001/udp',
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings=[
|
|
'10.1.2.3:8080:80',
|
|
'10.4.5.6::3333',
|
|
'14505-14506:4505-4506',
|
|
'9999-10001',
|
|
'10.1.2.3:8080:81/udp',
|
|
'10.4.5.6::3334/udp',
|
|
'15505-15506:5505-5506/udp',
|
|
'19999-20001/udp']
|
|
),
|
|
expected
|
|
)
|
|
|
|
# Error case: too many items (max 3)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='10.1.2.3:8080:80:123'),
|
|
(
|
|
{},
|
|
{'port_bindings': '\'10.1.2.3:8080:80:123\' is an invalid '
|
|
'port binding definition (at most 3 '
|
|
'components are allowed, found 4)'},
|
|
[]
|
|
)
|
|
)
|
|
|
|
# Error case: port range start is greater than end
|
|
expected = (
|
|
{},
|
|
{'port_bindings': 'Start of port range (5555) cannot be greater '
|
|
'than end of port range (5554)'},
|
|
[]
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='10.1.2.3:5555-5554:1111-1112'
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='10.1.2.3:1111-1112:5555-5554'
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='10.1.2.3::5555-5554'),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='5555-5554:1111-1112'),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='1111-1112:5555-5554'),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='5555-5554'),
|
|
expected
|
|
)
|
|
|
|
# Error case: non-numeric port range
|
|
expected = (
|
|
{},
|
|
{'port_bindings': '\'foo\' is non-numeric or an invalid port range'},
|
|
[]
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='10.1.2.3:foo:1111-1112'
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='10.1.2.3:1111-1112:foo'
|
|
),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='10.1.2.3::foo'),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='foo:1111-1112'),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='1111-1112:foo'),
|
|
expected
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='foo'),
|
|
expected
|
|
)
|
|
|
|
# Error case: misatched port range
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='10.1.2.3:1111-1113:1111-1112'
|
|
),
|
|
(
|
|
{},
|
|
{'port_bindings': 'Host port range (1111-1113) does not have '
|
|
'the same number of ports as the container '
|
|
'port range (1111-1112)'},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
port_bindings='10.1.2.3:1111-1112:1111-1113'
|
|
),
|
|
(
|
|
{},
|
|
{'port_bindings': 'Host port range (1111-1112) does not have '
|
|
'the same number of ports as the container '
|
|
'port range (1111-1113)'},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='1111-1113:1111-1112'),
|
|
(
|
|
{},
|
|
{'port_bindings': 'Host port range (1111-1113) does not have '
|
|
'the same number of ports as the container '
|
|
'port range (1111-1112)'},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='1111-1112:1111-1113'),
|
|
(
|
|
{},
|
|
{'port_bindings': 'Host port range (1111-1112) does not have '
|
|
'the same number of ports as the container '
|
|
'port range (1111-1113)'},
|
|
[]
|
|
)
|
|
)
|
|
|
|
# Error case: empty host port or container port
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings=':1111'),
|
|
(
|
|
{},
|
|
{'port_bindings': 'Empty host port in port binding definition '
|
|
'\':1111\''},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings='1111:'),
|
|
(
|
|
{},
|
|
{'port_bindings': 'Empty container port in port binding '
|
|
'definition \'1111:\''},
|
|
[]
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(port_bindings=''),
|
|
({}, {'port_bindings': 'Empty port binding definition found'}, [])
|
|
)
|
|
|
|
def test_ports(self):
|
|
'''
|
|
Ports can be passed as a comma-separated or Python list of port
|
|
numbers, with '/tcp' being optional for TCP ports. They must ultimately
|
|
be a list of port definitions, in which an integer denotes a TCP port,
|
|
and a tuple in the format (port_num, 'udp') denotes a UDP port. Also,
|
|
the port numbers must end up as integers. None of the decorators will
|
|
suffice so this one must be tested specially.
|
|
'''
|
|
expected = ({'ports': [1111, 2222, (3333, 'udp'), 4505, 4506]}, {}, [])
|
|
# Comma-separated list
|
|
self.assertEqual(
|
|
docker_utils.translate_input(ports='1111,2222/tcp,3333/udp,4505-4506'),
|
|
expected
|
|
)
|
|
# Python list
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
ports=[1111, '2222/tcp', '3333/udp', '4505-4506']
|
|
),
|
|
expected
|
|
)
|
|
# Same as above but with the first port as a string (it should be
|
|
# converted to an integer).
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
ports=['1111', '2222/tcp', '3333/udp', '4505-4506']
|
|
),
|
|
expected
|
|
)
|
|
# Error case: argument passed as a list, but with a non-integer and
|
|
# non/string value
|
|
self.assertEqual(
|
|
docker_utils.translate_input(ports=1.0),
|
|
({}, {'ports': '\'1.0\' is not a valid port definition'}, [])
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(ports=[1.0]),
|
|
({}, {'ports': '\'1.0\' is not a valid port definition'}, [])
|
|
)
|
|
# Error case: port range start is greater than end
|
|
self.assertEqual(
|
|
docker_utils.translate_input(ports='5555-5554'),
|
|
(
|
|
{},
|
|
{'ports': 'Start of port range (5555) cannot be greater than '
|
|
'end of port range (5554)'},
|
|
[]
|
|
)
|
|
)
|
|
|
|
@assert_bool
|
|
def test_privileged(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_bool
|
|
def test_publish_all_ports(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_bool
|
|
def test_read_only(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
def test_restart_policy(self):
|
|
'''
|
|
Input is in the format "name[:retry_count]", but the API wants it
|
|
in the format {'Name': name, 'MaximumRetryCount': retry_count}
|
|
'''
|
|
for item in ('restart_policy', 'restart'):
|
|
# Test with retry count
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 'on-failure:5'}),
|
|
(
|
|
{'restart_policy': {'Name': 'on-failure',
|
|
'MaximumRetryCount': 5}},
|
|
{}, []
|
|
)
|
|
)
|
|
# Test without retry count
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 'on-failure'}),
|
|
(
|
|
{'restart_policy': {'Name': 'on-failure',
|
|
'MaximumRetryCount': 0}},
|
|
{}, []
|
|
)
|
|
)
|
|
# Test collision
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
restart_policy='on-failure:5',
|
|
restart='always'
|
|
),
|
|
(
|
|
{'restart_policy': {'Name': 'on-failure',
|
|
'MaximumRetryCount': 5}},
|
|
{},
|
|
['restart_policy']
|
|
)
|
|
)
|
|
# Error case: more than one policy passed
|
|
self.assertEqual(
|
|
docker_utils.translate_input(**{item: 'on-failure,always'}),
|
|
({}, {item: 'Only one policy is permitted'}, [])
|
|
)
|
|
|
|
@assert_stringlist
|
|
def test_security_opt(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int_or_string
|
|
def test_shm_size(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_bool
|
|
def test_stdin_open(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_stop_signal(self):
|
|
'''
|
|
Should be a string or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_int
|
|
def test_stop_timeout(self):
|
|
'''
|
|
Should be an int or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_key_equals_value
|
|
def test_storage_opt(self):
|
|
'''
|
|
Can be passed in several formats but must end up as a dictionary
|
|
mapping keys to values
|
|
'''
|
|
pass
|
|
|
|
@assert_key_equals_value
|
|
def test_sysctls(self):
|
|
'''
|
|
Can be passed in several formats but must end up as a dictionary
|
|
mapping keys to values
|
|
'''
|
|
pass
|
|
|
|
@assert_dict
|
|
def test_tmpfs(self):
|
|
'''
|
|
Can be passed in several formats but must end up as a dictionary
|
|
mapping keys to values
|
|
'''
|
|
pass
|
|
|
|
@assert_bool
|
|
def test_tty(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
def test_ulimits(self):
|
|
'''
|
|
Input is in the format "name=soft_limit[:hard_limit]", but the API wants it
|
|
in the format {'Name': name, 'Soft': soft_limit, 'Hard': hard_limit}
|
|
'''
|
|
# Test with and without hard limit
|
|
self.assertEqual(
|
|
docker_utils.translate_input(ulimits='nofile=1024:2048,nproc=50'),
|
|
(
|
|
{'ulimits': [{'Name': 'nofile', 'Soft': 1024, 'Hard': 2048},
|
|
{'Name': 'nproc', 'Soft': 50, 'Hard': 50}]},
|
|
{}, []
|
|
)
|
|
)
|
|
self.assertEqual(
|
|
docker_utils.translate_input(
|
|
ulimits=['nofile=1024:2048', 'nproc=50:50']
|
|
),
|
|
(
|
|
{'ulimits': [{'Name': 'nofile', 'Soft': 1024, 'Hard': 2048},
|
|
{'Name': 'nproc', 'Soft': 50, 'Hard': 50}]},
|
|
{}, []
|
|
)
|
|
)
|
|
|
|
# Error case: Invalid format
|
|
self.assertEqual(
|
|
docker_utils.translate_input(ulimits='nofile:1024:2048'),
|
|
(
|
|
{},
|
|
{'ulimits': 'Ulimit definition \'nofile:1024:2048\' is not '
|
|
'in the format type=soft_limit[:hard_limit]'},
|
|
[]
|
|
)
|
|
)
|
|
|
|
# Error case: Invalid format
|
|
self.assertEqual(
|
|
docker_utils.translate_input(ulimits='nofile=foo:2048'),
|
|
(
|
|
{},
|
|
{'ulimits': 'Limit \'nofile=foo:2048\' contains non-numeric '
|
|
'value(s)'},
|
|
[]
|
|
)
|
|
)
|
|
|
|
def test_user(self):
|
|
'''
|
|
Must be either username (string) or uid (int). An int passed as a
|
|
string (e.g. '0') should be converted to an int.
|
|
'''
|
|
# Username passed as string
|
|
self.assertEqual(
|
|
docker_utils.translate_input(user='foo'),
|
|
({'user': 'foo'}, {}, [])
|
|
)
|
|
# Username passed as int
|
|
self.assertEqual(
|
|
docker_utils.translate_input(user=0),
|
|
({'user': 0}, {}, [])
|
|
)
|
|
# Username passed as stringified int
|
|
self.assertEqual(
|
|
docker_utils.translate_input(user='0'),
|
|
({'user': 0}, {}, [])
|
|
)
|
|
# Error case: non string/int passed
|
|
self.assertEqual(
|
|
docker_utils.translate_input(user=['foo']),
|
|
({}, {'user': 'Value must be a username or uid'}, [])
|
|
)
|
|
# Error case: negative int passed
|
|
self.assertEqual(
|
|
docker_utils.translate_input(user=-1),
|
|
({}, {'user': '\'-1\' is an invalid uid'}, [])
|
|
)
|
|
|
|
@assert_string
|
|
def test_userns_mode(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_volume_driver(self):
|
|
'''
|
|
Should be a bool or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_stringlist
|
|
def test_volumes(self):
|
|
'''
|
|
Should be a list of absolute paths
|
|
'''
|
|
# Error case: Not an absolute path
|
|
if salt.utils.is_windows():
|
|
path = r'foo\bar\baz'
|
|
else:
|
|
path = 'foo/bar/baz'
|
|
self.assertEqual(
|
|
docker_utils.translate_input(volumes=path),
|
|
(
|
|
{},
|
|
{'volumes': '\'{0}\' is not an absolute path'.format(path)},
|
|
[]
|
|
)
|
|
)
|
|
|
|
@assert_stringlist
|
|
def test_volumes_from(self):
|
|
'''
|
|
Should be a list of strings or converted to one
|
|
'''
|
|
pass
|
|
|
|
@assert_string
|
|
def test_working_dir(self):
|
|
'''
|
|
Should be a single absolute path
|
|
'''
|
|
# Error case: Not an absolute path
|
|
if salt.utils.is_windows():
|
|
path = r'foo\bar\baz'
|
|
else:
|
|
path = 'foo/bar/baz'
|
|
self.assertEqual(
|
|
docker_utils.translate_input(volumes=path),
|
|
(
|
|
{},
|
|
{'volumes': '\'{0}\' is not an absolute path'.format(path)},
|
|
[]
|
|
)
|
|
)
|
|
|
|
|
|
class DockerUtilsTestCase(TestCase):
|
|
'''
|
|
Tests for functions other than translate_input() in salt.utils.docker
|
|
'''
|
|
def test_get_repo_tag(self):
|
|
# Pass image name without tag (take the default_tag value from 2nd arg)
|
|
self.assertEqual(
|
|
docker_utils.get_repo_tag('foo', 'bar'),
|
|
('foo', 'bar')
|
|
)
|
|
# Pass image name with tag (ignore the default_tag value from 2nd arg)
|
|
self.assertEqual(
|
|
docker_utils.get_repo_tag('foo:1.0', 'bar'),
|
|
('foo', '1.0')
|
|
)
|
|
# Pass numeric image (should be converted to string and assume the
|
|
# default_tag value)
|
|
self.assertEqual(
|
|
docker_utils.get_repo_tag(123, 'bar'),
|
|
('123', 'bar')
|
|
)
|
|
# Edge case where someone passes an image name ending with a colon but
|
|
# with no tag (should assume the default_tag value)
|
|
self.assertEqual(
|
|
docker_utils.get_repo_tag('foo:', 'bar'),
|
|
('foo', 'bar')
|
|
)
|
|
|
|
|
|
class DockerTranslateHelperTestCase(TestCase):
|
|
'''
|
|
Tests for a couple helper functions in salt.utils.docker.translate
|
|
'''
|
|
def test_get_port_def(self):
|
|
'''
|
|
Test translation of port definition (1234, '1234/tcp', '1234/udp',
|
|
etc.) into the format which docker-py uses (integer for TCP ports,
|
|
'port_num/udp' for UDP ports).
|
|
'''
|
|
# Test TCP port (passed as int, no protocol passed)
|
|
self.assertEqual(translate_funcs._get_port_def(2222), 2222)
|
|
# Test TCP port (passed as str, no protocol passed)
|
|
self.assertEqual(translate_funcs._get_port_def('2222'), 2222)
|
|
# Test TCP port (passed as str, with protocol passed)
|
|
self.assertEqual(translate_funcs._get_port_def('2222', 'tcp'), 2222)
|
|
# Test TCP port (proto passed in port_num, with passed proto ignored).
|
|
# This is a contrived example as we would never invoke the function in
|
|
# this way, but it tests that we are taking the port number from the
|
|
# port_num argument and ignoring the passed protocol.
|
|
self.assertEqual(translate_funcs._get_port_def('2222/tcp', 'udp'), 2222)
|
|
|
|
# Test UDP port (passed as int)
|
|
self.assertEqual(translate_funcs._get_port_def(2222, 'udp'), (2222, 'udp'))
|
|
# Test UDP port (passed as string)
|
|
self.assertEqual(translate_funcs._get_port_def('2222', 'udp'), (2222, 'udp'))
|
|
# Test UDP port (proto passed in port_num
|
|
self.assertEqual(translate_funcs._get_port_def('2222/udp'), (2222, 'udp'))
|
|
|
|
def test_get_port_range(self):
|
|
'''
|
|
Test extracting the start and end of a port range from a port range
|
|
expression (e.g. 4505-4506)
|
|
'''
|
|
# Passing a single int should return the start and end as the same value
|
|
self.assertEqual(translate_funcs._get_port_range(2222), (2222, 2222))
|
|
# Same as above but with port number passed as a string
|
|
self.assertEqual(translate_funcs._get_port_range('2222'), (2222, 2222))
|
|
# Passing a port range
|
|
self.assertEqual(translate_funcs._get_port_range('2222-2223'), (2222, 2223))
|
|
# Error case: port range start is greater than end
|
|
with self.assertRaisesRegex(
|
|
ValueError,
|
|
r'Start of port range \(2222\) cannot be greater than end of '
|
|
r'port range \(2221\)'):
|
|
translate_funcs._get_port_range('2222-2221')
|
|
# Error case: non-numeric input
|
|
with self.assertRaisesRegex(
|
|
ValueError,
|
|
'\'2222-bar\' is non-numeric or an invalid port range'):
|
|
translate_funcs._get_port_range('2222-bar')
|