diff options
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | docker/constants.py | 2 | ||||
-rw-r--r-- | docker/utils/utils.py | 60 | ||||
-rw-r--r-- | docs/hostconfig.md | 4 | ||||
-rw-r--r-- | docs/tmpfs.md | 33 | ||||
-rw-r--r-- | mkdocs.yml | 1 | ||||
-rw-r--r-- | tests/integration/api_test.py | 2 | ||||
-rw-r--r-- | tests/integration/container_test.py | 16 | ||||
-rw-r--r-- | tests/integration/network_test.py | 56 | ||||
-rw-r--r-- | tests/unit/container_test.py | 58 | ||||
-rw-r--r-- | tests/unit/utils_test.py | 31 |
11 files changed, 231 insertions, 36 deletions
@@ -32,14 +32,14 @@ integration-test-py3: build-py3 integration-dind: build build-py3 docker rm -vf dpy-dind || : - docker run -d --name dpy-dind --env="DOCKER_HOST=tcp://localhost:2375" --privileged dockerswarm/dind:1.9.0 docker -d -H tcp://0.0.0.0:2375 + docker run -d --name dpy-dind --env="DOCKER_HOST=tcp://localhost:2375" --privileged dockerswarm/dind:1.10.3 docker daemon -H tcp://0.0.0.0:2375 docker run --env="DOCKER_HOST=tcp://docker:2375" --link=dpy-dind:docker docker-py py.test tests/integration docker run --env="DOCKER_HOST=tcp://docker:2375" --link=dpy-dind:docker docker-py3 py.test tests/integration docker rm -vf dpy-dind integration-dind-ssl: build-dind-certs build build-py3 docker run -d --name dpy-dind-certs dpy-dind-certs - docker run -d --env="DOCKER_HOST=tcp://localhost:2375" --env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --volumes-from dpy-dind-certs --name dpy-dind-ssl -v /tmp --privileged dockerswarm/dind:1.9.0 docker daemon --tlsverify --tlscacert=/certs/ca.pem --tlscert=/certs/server-cert.pem --tlskey=/certs/server-key.pem -H tcp://0.0.0.0:2375 + docker run -d --env="DOCKER_HOST=tcp://localhost:2375" --env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --volumes-from dpy-dind-certs --name dpy-dind-ssl -v /tmp --privileged dockerswarm/dind:1.10.3 docker daemon --tlsverify --tlscacert=/certs/ca.pem --tlscert=/certs/server-cert.pem --tlskey=/certs/server-key.pem -H tcp://0.0.0.0:2375 docker run --volumes-from dpy-dind-ssl --env="DOCKER_HOST=tcp://docker:2375" --env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --link=dpy-dind-ssl:docker docker-py py.test tests/integration docker run --volumes-from dpy-dind-ssl --env="DOCKER_HOST=tcp://docker:2375" --env="DOCKER_TLS_VERIFY=1" --env="DOCKER_CERT_PATH=/certs" --link=dpy-dind-ssl:docker docker-py3 py.test tests/integration docker rm -vf dpy-dind-ssl dpy-dind-certs diff --git a/docker/constants.py b/docker/constants.py index 0627ba0..6c381de 100644 --- a/docker/constants.py +++ b/docker/constants.py @@ -1,4 +1,4 @@ -DEFAULT_DOCKER_API_VERSION = '1.21' +DEFAULT_DOCKER_API_VERSION = '1.22' DEFAULT_TIMEOUT_SECONDS = 60 STREAM_HEADER_SIZE_BYTES = 8 CONTAINER_LIMITS_KEYS = [ diff --git a/docker/utils/utils.py b/docker/utils/utils.py index d4393d5..ee1d1b0 100644 --- a/docker/utils/utils.py +++ b/docker/utils/utils.py @@ -337,6 +337,35 @@ def convert_volume_binds(binds): return result +def convert_tmpfs_mounts(tmpfs): + if isinstance(tmpfs, dict): + return tmpfs + + if not isinstance(tmpfs, list): + raise ValueError( + 'Expected tmpfs value to be either a list or a dict, found: {}' + .format(type(tmpfs).__name__) + ) + + result = {} + for mount in tmpfs: + if isinstance(mount, six.string_types): + if ":" in mount: + name, options = mount.split(":", 1) + else: + name = mount + options = "" + + else: + raise ValueError( + "Expected item in tmpfs list to be a string, found: {}" + .format(type(mount).__name__) + ) + + result[name] = options + return result + + def parse_repository_tag(repo_name): parts = repo_name.rsplit('@', 1) if len(parts) == 2: @@ -371,11 +400,12 @@ def parse_host(addr, platform=None, tls=False): if addr == 'tcp://': raise errors.DockerException( - "Invalid bind address format: {0}".format(addr)) + "Invalid bind address format: {0}".format(addr) + ) elif addr.startswith('unix://'): addr = addr[7:] elif addr.startswith('tcp://'): - proto = "http" + proto = 'http{0}'.format('s' if tls else '') addr = addr[6:] elif addr.startswith('https://'): proto = "https" @@ -449,15 +479,17 @@ def parse_devices(devices): return device_list -def kwargs_from_env(ssl_version=None, assert_hostname=None): - host = os.environ.get('DOCKER_HOST') +def kwargs_from_env(ssl_version=None, assert_hostname=None, environment=None): + if not environment: + environment = os.environ + host = environment.get('DOCKER_HOST') # empty string for cert path is the same as unset. - cert_path = os.environ.get('DOCKER_CERT_PATH') or None + cert_path = environment.get('DOCKER_CERT_PATH') or None # empty string for tls verify counts as "false". # Any value or 'unset' counts as true. - tls_verify = os.environ.get('DOCKER_TLS_VERIFY') + tls_verify = environment.get('DOCKER_TLS_VERIFY') if tls_verify == '': tls_verify = False else: @@ -584,7 +616,7 @@ def create_host_config(binds=None, port_bindings=None, lxc_conf=None, mem_limit=None, memswap_limit=None, mem_swappiness=None, cgroup_parent=None, group_add=None, cpu_quota=None, cpu_period=None, oom_kill_disable=False, shm_size=None, - version=None): + version=None, tmpfs=None, oom_score_adj=None): host_config = {} @@ -634,6 +666,15 @@ def create_host_config(binds=None, port_bindings=None, lxc_conf=None, host_config['OomKillDisable'] = oom_kill_disable + if oom_score_adj: + if version_lt(version, '1.22'): + raise host_config_version_error('oom_score_adj', '1.22') + if not isinstance(oom_score_adj, int): + raise host_config_type_error( + 'oom_score_adj', oom_score_adj, 'int' + ) + host_config['OomScoreAdj'] = oom_score_adj + if publish_all_ports: host_config['PublishAllPorts'] = publish_all_ports @@ -751,6 +792,11 @@ def create_host_config(binds=None, port_bindings=None, lxc_conf=None, host_config['CpuPeriod'] = cpu_period + if tmpfs: + if version_lt(version, '1.22'): + raise host_config_version_error('tmpfs', '1.22') + host_config["Tmpfs"] = convert_tmpfs_mounts(tmpfs) + return host_config diff --git a/docs/hostconfig.md b/docs/hostconfig.md index 4b841d5..cd96433 100644 --- a/docs/hostconfig.md +++ b/docs/hostconfig.md @@ -73,6 +73,8 @@ for example: for more information. * lxc_conf (dict): LXC config * oom_kill_disable (bool): Whether to disable OOM killer +* oom_score_adj (int): An integer value containing the score given to the + container in order to tune OOM killer preferences * publish_all_ports (bool): Whether to publish all ports to the host * links (dict or list of tuples): either as a dictionary mapping name to alias or as a list of `(name, alias)` tuples @@ -111,6 +113,8 @@ for example: container process will run as. * devices (list): Host device bindings. See [host devices](host-devices.md) for more information. +* tmpfs: Temporary filesystems to mouunt. See [Using tmpfs](tmpfs.md) for more + information. **Returns** (dict) HostConfig dictionary diff --git a/docs/tmpfs.md b/docs/tmpfs.md new file mode 100644 index 0000000..d8be9b6 --- /dev/null +++ b/docs/tmpfs.md @@ -0,0 +1,33 @@ +# Using tmpfs + +When creating a container, you can specify paths to be mounted with tmpfs using +the `tmpfs` argument to `create_host_config`, similarly to the `--tmpfs` +argument to `docker run`. + +This capability is supported in Docker Engine 1.10 and up. + +`tmpfs` can be either a list or a dictionary. If it's a list, each item is a +string specifying the path and (optionally) any configuration for the mount: + +```python +client.create_container( + 'busybox', 'ls', + host_config=client.create_host_config(tmpfs=[ + '/mnt/vol2', + '/mnt/vol1:size=3G,uid=1000' + ]) +) +``` + +Alternatively, if it's a dictionary, each key is a path and each value contains +the mount options: + +```python +client.create_container( + 'busybox', 'ls', + host_config=client.create_host_config(tmpfs={ + '/mnt/vol2': '', + '/mnt/vol1': 'size=3G,uid=1000' + }) +) +``` @@ -13,6 +13,7 @@ pages: - Host devices: host-devices.md - Host configuration: hostconfig.md - Network configuration: networks.md +- Using tmpfs: tmpfs.md - Using with boot2docker: boot2docker.md - Change Log: change_log.md - Contributing: contributing.md diff --git a/tests/integration/api_test.py b/tests/integration/api_test.py index e120c84..67ed068 100644 --- a/tests/integration/api_test.py +++ b/tests/integration/api_test.py @@ -49,7 +49,7 @@ class LinkTest(helpers.BaseTestCase): container2 = self.client.create_container( helpers.BUSYBOX, 'cat', host_config=self.client.create_host_config( - links={link_path: link_alias}, network_mode='none' + links={link_path: link_alias} ) ) container2_id = container2['Id'] diff --git a/tests/integration/container_test.py b/tests/integration/container_test.py index a1498f9..940e5b8 100644 --- a/tests/integration/container_test.py +++ b/tests/integration/container_test.py @@ -382,6 +382,22 @@ class CreateContainerTest(helpers.BaseTestCase): sorted(['Foo', 'Other=one', 'Blank=']) ) + @requires_api_version('1.22') + def test_create_with_tmpfs(self): + tmpfs = { + '/tmp1': 'size=3M' + } + + container = self.client.create_container( + BUSYBOX, + ['echo'], + host_config=self.client.create_host_config( + tmpfs=tmpfs)) + + self.tmp_containers.append(container['Id']) + config = self.client.inspect_container(container) + assert config['HostConfig']['Tmpfs'] == tmpfs + class VolumeBindTest(helpers.BaseTestCase): def setUp(self): diff --git a/tests/integration/network_test.py b/tests/integration/network_test.py index a744617..053e4ae 100644 --- a/tests/integration/network_test.py +++ b/tests/integration/network_test.py @@ -238,48 +238,56 @@ class TestNetworks(helpers.BaseTestCase): @requires_api_version('1.22') def test_connect_with_ipv4_address(self): - net_name, net_id = self.create_network() + net_name, net_id = self.create_network( + ipam=create_ipam_config( + driver='default', + pool_configs=[ + create_ipam_pool( + subnet="172.28.0.0/16", iprange="172.28.5.0/24", + gateway="172.28.5.254" + ) + ] + ) + ) container = self.create_and_start( host_config=self.client.create_host_config(network_mode=net_name)) self.client.disconnect_container_from_network(container, net_name) self.client.connect_container_to_network( - container, net_name, - ipv4_address='192.168.0.1') + container, net_name, ipv4_address='172.28.5.24' + ) container_data = self.client.inspect_container(container) + net_data = container_data['NetworkSettings']['Networks'][net_name] self.assertEqual( - container_data['NetworkSettings']['Networks'][net_name] - ['IPAMConfig']['IPv4Address'], - '192.168.0.1') - - self.create_and_start( - name='docker-py-test-upstream', - host_config=self.client.create_host_config(network_mode=net_name)) - - self.execute(container, ['nslookup', 'bar']) + net_data['IPAMConfig']['IPv4Address'], '172.28.5.24' + ) @requires_api_version('1.22') def test_connect_with_ipv6_address(self): - net_name, net_id = self.create_network() + net_name, net_id = self.create_network( + ipam=create_ipam_config( + driver='default', + pool_configs=[ + create_ipam_pool( + subnet="2001:389::1/64", iprange="2001:389::0/96", + gateway="2001:389::ffff" + ) + ] + ) + ) container = self.create_and_start( host_config=self.client.create_host_config(network_mode=net_name)) self.client.disconnect_container_from_network(container, net_name) self.client.connect_container_to_network( - container, net_name, - ipv6_address='2001:389::1') + container, net_name, ipv6_address='2001:389::f00d' + ) container_data = self.client.inspect_container(container) + net_data = container_data['NetworkSettings']['Networks'][net_name] self.assertEqual( - container_data['NetworkSettings']['Networks'][net_name] - ['IPAMConfig']['IPv6Address'], - '2001:389::1') - - self.create_and_start( - name='docker-py-test-upstream', - host_config=self.client.create_host_config(network_mode=net_name)) - - self.execute(container, ['nslookup', 'bar']) + net_data['IPAMConfig']['IPv6Address'], '2001:389::f00d' + ) diff --git a/tests/unit/container_test.py b/tests/unit/container_test.py index 6e23f89..2a72c17 100644 --- a/tests/unit/container_test.py +++ b/tests/unit/container_test.py @@ -1016,6 +1016,64 @@ class CreateContainerTest(DockerClientTest): } }}''')) + @requires_api_version('1.22') + def test_create_container_with_tmpfs_list(self): + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + tmpfs=[ + "/tmp", + "/mnt:size=3G,uid=100" + ] + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Tmpfs'] = { + "/tmp": "", + "/mnt": "size=3G,uid=100" + } + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + + @requires_api_version('1.22') + def test_create_container_with_tmpfs_dict(self): + + self.client.create_container( + 'busybox', 'true', host_config=self.client.create_host_config( + tmpfs={ + "/tmp": "", + "/mnt": "size=3G,uid=100" + } + ) + ) + + args = fake_request.call_args + self.assertEqual(args[0][1], url_prefix + + 'containers/create') + expected_payload = self.base_create_payload() + expected_payload['HostConfig'] = self.client.create_host_config() + expected_payload['HostConfig']['Tmpfs'] = { + "/tmp": "", + "/mnt": "size=3G,uid=100" + } + self.assertEqual(json.loads(args[1]['data']), expected_payload) + self.assertEqual(args[1]['headers'], + {'Content-Type': 'application/json'}) + self.assertEqual( + args[1]['timeout'], + DEFAULT_TIMEOUT_SECONDS + ) + class ContainerTest(DockerClientTest): def test_list_containers(self): diff --git a/tests/unit/utils_test.py b/tests/unit/utils_test.py index 65b7cf8..eb952b2 100644 --- a/tests/unit/utils_test.py +++ b/tests/unit/utils_test.py @@ -87,6 +87,16 @@ class HostConfigTest(base.BaseTestCase): InvalidVersion, lambda: create_host_config(version='1.18.3', oom_kill_disable=True)) + def test_create_host_config_with_oom_score_adj(self): + config = create_host_config(version='1.22', oom_score_adj=100) + self.assertEqual(config.get('OomScoreAdj'), 100) + self.assertRaises( + InvalidVersion, lambda: create_host_config(version='1.21', + oom_score_adj=100)) + self.assertRaises( + TypeError, lambda: create_host_config(version='1.22', + oom_score_adj='100')) + def test_create_endpoint_config_with_aliases(self): config = create_endpoint_config(version='1.22', aliases=['foo', 'bar']) assert config == {'Aliases': ['foo', 'bar']} @@ -249,6 +259,20 @@ class KwargsFromEnvTest(base.BaseTestCase): if temp_dir: shutil.rmtree(temp_dir) + def test_kwargs_from_env_alternate_env(self): + # Values in os.environ are entirely ignored if an alternate is + # provided + os.environ.update( + DOCKER_HOST='tcp://192.168.59.103:2376', + DOCKER_CERT_PATH=TEST_CERT_DIR, + DOCKER_TLS_VERIFY='' + ) + kwargs = kwargs_from_env(environment={ + 'DOCKER_HOST': 'http://docker.gensokyo.jp:2581', + }) + assert 'http://docker.gensokyo.jp:2581' == kwargs['base_url'] + assert 'tls' not in kwargs + class ConverVolumeBindsTest(base.BaseTestCase): def test_convert_volume_binds_empty(self): @@ -412,7 +436,12 @@ class ParseHostTest(base.BaseTestCase): def test_parse_host_tls(self): host_value = 'myhost.docker.net:3348' expected_result = 'https://myhost.docker.net:3348' - self.assertEqual(parse_host(host_value, None, True), expected_result) + assert parse_host(host_value, tls=True) == expected_result + + def test_parse_host_tls_tcp_proto(self): + host_value = 'tcp://myhost.docker.net:3348' + expected_result = 'https://myhost.docker.net:3348' + assert parse_host(host_value, tls=True) == expected_result class ParseRepositoryTagTest(base.BaseTestCase): |