Repository: docker/docker-py

Issue:
memory_limit and memswap_limit support in host_config
Unable to set memory_limit and memswap_limit in host_config, both are missing from supported parameters in utils.py.

Containers created using python client,

```
[{
        "Config":  {
        ...
        "Memory": 2147483648,
        "MemorySwap": -1,
       ...
       "HostConfig": {
        ...
        "Memory": 0,
        "MemorySwap": 0,
        ...
```

Containers created using commandline with --memory=2048MB --memory_swap=-1

```
[{
        "Config":  {
        ...
        "Memory": 2147483648,
        "MemorySwap": -1,
       ...
       "HostConfig": {
        ...
        "Memory": 2147483648,
        "MemorySwap": -1,
        ...
```

As results, limits are only configured in Config and not Host_Config and therefore make no effect.



Discussion:
@shin- I think this is fallout from https://github.com/docker/docker/pull/10298.  docker-py currently accepts mem_limit as a direct argument to `create_container`, but it looks like it should be moved and/or forwarded to create_host_config?

(I'm seeing this issue as well by the way)

It's a 1.7 change. I'll take care of it today.
On Jun 2, 2015 9:37 AM, "Scott Sanderson" notifications@github.com wrote:

> (I'm seeing this issue as well by the way)
> 
> —
> Reply to this email directly or view it on GitHub
> https://github.com/docker/docker-py/issues/623#issuecomment-108008785.

https://github.com/docker/docker/issues/13663


Resolving patch:
diff --git a/docker/client.py b/docker/client.py
--- a/docker/client.py
+++ b/docker/client.py
@@ -464,11 +464,11 @@ def copy(self, container, resource):
 
     def create_container(self, image, command=None, hostname=None, user=None,
                          detach=False, stdin_open=False, tty=False,
-                         mem_limit=0, ports=None, environment=None, dns=None,
-                         volumes=None, volumes_from=None,
+                         mem_limit=None, ports=None, environment=None,
+                         dns=None, volumes=None, volumes_from=None,
                          network_disabled=False, name=None, entrypoint=None,
                          cpu_shares=None, working_dir=None, domainname=None,
-                         memswap_limit=0, cpuset=None, host_config=None,
+                         memswap_limit=None, cpuset=None, host_config=None,
                          mac_address=None, labels=None, volume_driver=None):
 
         if isinstance(volumes, six.string_types):
diff --git a/docker/utils/utils.py b/docker/utils/utils.py
--- a/docker/utils/utils.py
+++ b/docker/utils/utils.py
@@ -378,10 +378,21 @@ def create_host_config(
     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
+    security_opt=None, ulimits=None, log_config=None, mem_limit=None,
+    memswap_limit=None
 ):
     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:
+        if isinstance(memswap_limit, six.string_types):
+            memswap_limit = parse_bytes(memswap_limit)
+        host_config['MemorySwap'] = memswap_limit
+
     if pid_mode not in (None, 'host'):
         raise errors.DockerException(
             'Invalid value for pid param: {0}'.format(pid_mode)
@@ -498,10 +509,10 @@ def create_host_config(
 
 def create_container_config(
     version, image, command, hostname=None, user=None, detach=False,
-    stdin_open=False, tty=False, mem_limit=0, ports=None, environment=None,
+    stdin_open=False, tty=False, mem_limit=None, ports=None, environment=None,
     dns=None, volumes=None, volumes_from=None, network_disabled=False,
     entrypoint=None, cpu_shares=None, working_dir=None, domainname=None,
-    memswap_limit=0, cpuset=None, host_config=None, mac_address=None,
+    memswap_limit=None, cpuset=None, host_config=None, mac_address=None,
     labels=None, volume_driver=None
 ):
     if isinstance(command, six.string_types):
@@ -517,10 +528,24 @@ def create_container_config(
             'labels were only introduced in API version 1.18'
         )
 
-    if volume_driver is not None and compare_version('1.19', version) < 0:
-        raise errors.InvalidVersion(
-            'Volume drivers were only introduced in API version 1.19'
-        )
+    if compare_version('1.19', version) < 0:
+        if volume_driver is not None:
+            raise errors.InvalidVersion(
+                'Volume drivers were only introduced in API version 1.19'
+            )
+        mem_limit = mem_limit if mem_limit is not None else 0
+        memswap_limit = memswap_limit if memswap_limit is not None else 0
+    else:
+        if mem_limit is not None:
+            raise errors.InvalidVersion(
+                'mem_limit has been moved to host_config in API version 1.19'
+            )
+
+        if memswap_limit is not None:
+            raise errors.InvalidVersion(
+                'memswap_limit has been moved to host_config in API '
+                'version 1.19'
+            )
 
     if isinstance(labels, list):
         labels = dict((lbl, six.text_type('')) for lbl in labels)


Test patch:
diff --git a/tests/test.py b/tests/test.py
--- a/tests/test.py
+++ b/tests/test.py
@@ -124,11 +124,10 @@ def base_create_payload(self, img='busybox', cmd=None):
         if not cmd:
             cmd = ['true']
         return {"Tty": False, "Image": img, "Cmd": cmd,
-                "AttachStdin": False, "Memory": 0,
+                "AttachStdin": False,
                 "AttachStderr": True, "AttachStdout": True,
                 "StdinOnce": False,
                 "OpenStdin": False, "NetworkDisabled": False,
-                "MemorySwap": 0
                 }
 
     def test_ctor(self):
@@ -337,11 +336,10 @@ def test_create_container(self):
         self.assertEqual(json.loads(args[1]['data']),
                          json.loads('''
                             {"Tty": false, "Image": "busybox", "Cmd": ["true"],
-                             "AttachStdin": false, "Memory": 0,
+                             "AttachStdin": false,
                              "AttachStderr": true, "AttachStdout": true,
                              "StdinOnce": false,
-                             "OpenStdin": false, "NetworkDisabled": false,
-                             "MemorySwap": 0}'''))
+                             "OpenStdin": false, "NetworkDisabled": false}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -361,12 +359,11 @@ def test_create_container_with_binds(self):
                          json.loads('''
                             {"Tty": false, "Image": "busybox",
                              "Cmd": ["ls", "/mnt"], "AttachStdin": false,
-                             "Volumes": {"/mnt": {}}, "Memory": 0,
+                             "Volumes": {"/mnt": {}},
                              "AttachStderr": true,
                              "AttachStdout": true, "OpenStdin": false,
                              "StdinOnce": false,
-                             "NetworkDisabled": false,
-                             "MemorySwap": 0}'''))
+                             "NetworkDisabled": false}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -386,12 +383,11 @@ def test_create_container_with_volume_string(self):
                          json.loads('''
                             {"Tty": false, "Image": "busybox",
                              "Cmd": ["ls", "/mnt"], "AttachStdin": false,
-                             "Volumes": {"/mnt": {}}, "Memory": 0,
+                             "Volumes": {"/mnt": {}},
                              "AttachStderr": true,
                              "AttachStdout": true, "OpenStdin": false,
                              "StdinOnce": false,
-                             "NetworkDisabled": false,
-                             "MemorySwap": 0}'''))
+                             "NetworkDisabled": false}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -409,7 +405,7 @@ def test_create_container_with_ports(self):
                          json.loads('''
                             {"Tty": false, "Image": "busybox",
                              "Cmd": ["ls"], "AttachStdin": false,
-                             "Memory": 0, "ExposedPorts": {
+                             "ExposedPorts": {
                                 "1111/tcp": {},
                                 "2222/udp": {},
                                 "3333/tcp": {}
@@ -417,8 +413,7 @@ def test_create_container_with_ports(self):
                              "AttachStderr": true,
                              "AttachStdout": true, "OpenStdin": false,
                              "StdinOnce": false,
-                             "NetworkDisabled": false,
-                             "MemorySwap": 0}'''))
+                             "NetworkDisabled": false}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -436,13 +431,11 @@ def test_create_container_with_entrypoint(self):
                          json.loads('''
                             {"Tty": false, "Image": "busybox",
                              "Cmd": ["hello"], "AttachStdin": false,
-                             "Memory": 0,
                              "AttachStderr": true,
                              "AttachStdout": true, "OpenStdin": false,
                              "StdinOnce": false,
                              "NetworkDisabled": false,
-                             "Entrypoint": "cowsay",
-                             "MemorySwap": 0}'''))
+                             "Entrypoint": "cowsay"}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -460,13 +453,11 @@ def test_create_container_with_cpu_shares(self):
                          json.loads('''
                             {"Tty": false, "Image": "busybox",
                              "Cmd": ["ls"], "AttachStdin": false,
-                             "Memory": 0,
                              "AttachStderr": true,
                              "AttachStdout": true, "OpenStdin": false,
                              "StdinOnce": false,
                              "NetworkDisabled": false,
-                             "CpuShares": 5,
-                             "MemorySwap": 0}'''))
+                             "CpuShares": 5}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -484,14 +475,12 @@ def test_create_container_with_cpuset(self):
                          json.loads('''
                             {"Tty": false, "Image": "busybox",
                              "Cmd": ["ls"], "AttachStdin": false,
-                             "Memory": 0,
                              "AttachStderr": true,
                              "AttachStdout": true, "OpenStdin": false,
                              "StdinOnce": false,
                              "NetworkDisabled": false,
                              "Cpuset": "0,1",
-                             "CpusetCpus": "0,1",
-                             "MemorySwap": 0}'''))
+                             "CpusetCpus": "0,1"}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -509,13 +498,11 @@ def test_create_container_with_working_dir(self):
                          json.loads('''
                             {"Tty": false, "Image": "busybox",
                              "Cmd": ["ls"], "AttachStdin": false,
-                             "Memory": 0,
                              "AttachStderr": true,
                              "AttachStdout": true, "OpenStdin": false,
                              "StdinOnce": false,
                              "NetworkDisabled": false,
-                             "WorkingDir": "/root",
-                             "MemorySwap": 0}'''))
+                             "WorkingDir": "/root"}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -531,11 +518,10 @@ def test_create_container_with_stdin_open(self):
         self.assertEqual(json.loads(args[1]['data']),
                          json.loads('''
                             {"Tty": false, "Image": "busybox", "Cmd": ["true"],
-                             "AttachStdin": true, "Memory": 0,
+                             "AttachStdin": true,
                              "AttachStderr": true, "AttachStdout": true,
                              "StdinOnce": true,
-                             "OpenStdin": true, "NetworkDisabled": false,
-                             "MemorySwap": 0}'''))
+                             "OpenStdin": true, "NetworkDisabled": false}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
 
@@ -581,78 +567,95 @@ def test_create_named_container(self):
         self.assertEqual(json.loads(args[1]['data']),
                          json.loads('''
                             {"Tty": false, "Image": "busybox", "Cmd": ["true"],
-                             "AttachStdin": false, "Memory": 0,
+                             "AttachStdin": false,
                              "AttachStderr": true, "AttachStdout": true,
                              "StdinOnce": false,
-                             "OpenStdin": false, "NetworkDisabled": false,
-                             "MemorySwap": 0}'''))
+                             "OpenStdin": false, "NetworkDisabled": false}'''))
         self.assertEqual(args[1]['headers'],
                          {'Content-Type': 'application/json'})
         self.assertEqual(args[1]['params'], {'name': 'marisa-kirisame'})
 
     def test_create_container_with_mem_limit_as_int(self):
         try:
-            self.client.create_container('busybox', 'true',
-                                         mem_limit=128.0)
+            self.client.create_container(
+                'busybox', 'true', host_config=create_host_config(
+                    mem_limit=128.0
+                )
+            )
         except Exception as e:
             self.fail('Command should not raise exception: {0}'.format(e))
 
         args = fake_request.call_args
         data = json.loads(args[1]['data'])
-        self.assertEqual(data['Memory'], 128.0)
+        self.assertEqual(data['HostConfig']['Memory'], 128.0)
 
     def test_create_container_with_mem_limit_as_string(self):
         try:
-            self.client.create_container('busybox', 'true',
-                                         mem_limit='128')
+            self.client.create_container(
+                'busybox', 'true', host_config=create_host_config(
+                    mem_limit='128'
+                )
+            )
         except Exception as e:
             self.fail('Command should not raise exception: {0}'.format(e))
 
         args = fake_request.call_args
         data = json.loads(args[1]['data'])
-        self.assertEqual(data['Memory'], 128.0)
+        self.assertEqual(data['HostConfig']['Memory'], 128.0)
 
     def test_create_container_with_mem_limit_as_string_with_k_unit(self):
         try:
-            self.client.create_container('busybox', 'true',
-                                         mem_limit='128k')
+            self.client.create_container(
+                'busybox', 'true', host_config=create_host_config(
+                    mem_limit='128k'
+                )
+            )
         except Exception as e:
             self.fail('Command should not raise exception: {0}'.format(e))
 
         args = fake_request.call_args
         data = json.loads(args[1]['data'])
-        self.assertEqual(data['Memory'], 128.0 * 1024)
+        self.assertEqual(data['HostConfig']['Memory'], 128.0 * 1024)
 
     def test_create_container_with_mem_limit_as_string_with_m_unit(self):
         try:
-            self.client.create_container('busybox', 'true',
-                                         mem_limit='128m')
+            self.client.create_container(
+                'busybox', 'true', host_config=create_host_config(
+                    mem_limit='128m'
+                )
+            )
+
         except Exception as e:
             self.fail('Command should not raise exception: {0}'.format(e))
 
         args = fake_request.call_args
         data = json.loads(args[1]['data'])
-        self.assertEqual(data['Memory'], 128.0 * 1024 * 1024)
+        self.assertEqual(data['HostConfig']['Memory'], 128.0 * 1024 * 1024)
 
     def test_create_container_with_mem_limit_as_string_with_g_unit(self):
         try:
-            self.client.create_container('busybox', 'true',
-                                         mem_limit='128g')
+            self.client.create_container(
+                'busybox', 'true', host_config=create_host_config(
+                    mem_limit='128g'
+                )
+            )
         except Exception as e:
             self.fail('Command should not raise exception: {0}'.format(e))
 
         args = fake_request.call_args
         data = json.loads(args[1]['data'])
-        self.assertEqual(data['Memory'], 128.0 * 1024 * 1024 * 1024)
+        self.assertEqual(
+            data['HostConfig']['Memory'], 128.0 * 1024 * 1024 * 1024
+        )
 
     def test_create_container_with_mem_limit_as_string_with_wrong_value(self):
-        self.assertRaises(docker.errors.DockerException,
-                          self.client.create_container,
-                          'busybox', 'true', mem_limit='128p')
+        self.assertRaises(
+            docker.errors.DockerException, create_host_config, mem_limit='128p'
+        )
 
-        self.assertRaises(docker.errors.DockerException,
-                          self.client.create_container,
-                          'busybox', 'true', mem_limit='1f28')
+        self.assertRaises(
+            docker.errors.DockerException, create_host_config, mem_limit='1f28'
+        )
 
     def test_start_container(self):
         try: