Repository: docker/docker-py

Issue:
Add support for MemorySwappiness to create_host_config



Discussion:
Thanks!

The tests are failing because you have trailing whitespace and an indentation error in your patch. You can run `make flake8` in the project's root to detect those locally.

Apart from the code style nitpicking:
- It seems the option was added in API version 1.20 - can we add a client-side check for that? Just like we do for group_add [here](https://github.com/docker/docker-py/blob/master/docker/utils/utils.py#L541-L544)
- The parameter should be added to the HostConfig documentation (`docs/host_config.md`).
- It might also be valuable to add an integration test that verifies the value we pass is reflected in the `inspect_container` dict.


Resolving patch:
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -469,16 +469,17 @@ def parse_bytes(s):
     return s
 
 
-def create_host_config(
-    binds=None, port_bindings=None, lxc_conf=None,
-    publish_all_ports=False, links=None, privileged=False,
-    dns=None, dns_search=None, volumes_from=None, network_mode=None,
-    restart_policy=None, cap_add=None, cap_drop=None, devices=None,
-    extra_hosts=None, read_only=None, pid_mode=None, ipc_mode=None,
-    security_opt=None, ulimits=None, log_config=None, mem_limit=None,
-    memswap_limit=None, cgroup_parent=None, group_add=None, cpu_quota=None,
-    cpu_period=None, version=None
-):
+def create_host_config(binds=None, port_bindings=None, lxc_conf=None,
+                       publish_all_ports=False, links=None, privileged=False,
+                       dns=None, dns_search=None, volumes_from=None,
+                       network_mode=None, restart_policy=None, cap_add=None,
+                       cap_drop=None, devices=None, extra_hosts=None,
+                       read_only=None, pid_mode=None, ipc_mode=None,
+                       security_opt=None, ulimits=None, log_config=None,
+                       mem_limit=None, memswap_limit=None, mem_swappiness=None,
+                       cgroup_parent=None, group_add=None, cpu_quota=None,
+                       cpu_period=None, version=None):
+
     host_config = {}
 
     if not version:
@@ -491,6 +492,7 @@ def create_host_config(
     if mem_limit is not None:
         if isinstance(mem_limit, six.string_types):
             mem_limit = parse_bytes(mem_limit)
+
         host_config['Memory'] = mem_limit
 
     if memswap_limit is not None:
@@ -498,6 +500,18 @@ def create_host_config(
             memswap_limit = parse_bytes(memswap_limit)
         host_config['MemorySwap'] = memswap_limit
 
+    if mem_swappiness is not None:
+        if version_lt(version, '1.20'):
+            raise errors.InvalidVersion(
+                'mem_swappiness param not supported for API version < 1.20'
+            )
+        if not isinstance(mem_swappiness, int):
+            raise TypeError(
+                'Invalid type for mem_swappiness param: expected int but'
+                ' found {0}'.format(type(mem_swappiness))
+            )
+        host_config['MemorySwappiness'] = mem_swappiness
+
     if pid_mode not in (None, 'host'):
         raise errors.DockerException(
             'Invalid value for pid param: {0}'.format(pid_mode)


Test patch:
diff --git a/tests/integration/container_test.py b/tests/integration/container_test.py
--- a/tests/integration/container_test.py
+++ b/tests/integration/container_test.py
@@ -337,6 +337,38 @@ def test_valid_no_config_specified(self):
         self.assertEqual(container_log_config['Type'], "json-file")
         self.assertEqual(container_log_config['Config'], {})
 
+    def test_create_with_memory_constraints_with_str(self):
+        ctnr = self.client.create_container(
+            BUSYBOX, 'true',
+            host_config=self.client.create_host_config(
+                memswap_limit='1G',
+                mem_limit='700M'
+            )
+        )
+        self.assertIn('Id', ctnr)
+        self.tmp_containers.append(ctnr['Id'])
+        self.client.start(ctnr)
+        inspect = self.client.inspect_container(ctnr)
+
+        self.assertIn('HostConfig', inspect)
+        host_config = inspect['HostConfig']
+        for limit in ['Memory', 'MemorySwap']:
+            self.assertIn(limit, host_config)
+
+    def test_create_with_memory_constraints_with_int(self):
+        ctnr = self.client.create_container(
+            BUSYBOX, 'true',
+            host_config=self.client.create_host_config(mem_swappiness=40)
+        )
+        self.assertIn('Id', ctnr)
+        self.tmp_containers.append(ctnr['Id'])
+        self.client.start(ctnr)
+        inspect = self.client.inspect_container(ctnr)
+
+        self.assertIn('HostConfig', inspect)
+        host_config = inspect['HostConfig']
+        self.assertIn('MemorySwappiness', host_config)
+
 
 class VolumeBindTest(api_test.BaseTestCase):
     def setUp(self):