Repository: apache/libcloud

Issue:
Fix a missing / in requests made by the DockerContainerDriver
## Fix a missing / in requests made by the DockerContainerDriver
### Description

Following the examples provided by the documentation would result in a "400 Bad Request" error due to the fact that the requests didn't begin with a '/' for example:

```
GET v1.24/images/json HTTP/1.1
```

would result in a 400 Bad Request error, but:

```
GET /v1.24/images/json HTTP/1.1
```

does not.
### Status

done, ready for review
### Checklist (tick everything that applies)
- [ ] [Code linting](http://libcloud.readthedocs.org/en/latest/development.html#code-style-guide) (required, can be done after the PR checks)
- [ ] Documentation
- [ ] [Tests](http://libcloud.readthedocs.org/en/latest/testing.html)
- [ ] [ICLA](http://libcloud.readthedocs.org/en/latest/development.html#contributing-bigger-changes) (required for bigger changes)



Discussion:

Resolving patch:
diff --git a/libcloud/__init__.py b/libcloud/__init__.py
--- a/libcloud/__init__.py
+++ b/libcloud/__init__.py
@@ -26,7 +26,6 @@
 from libcloud.base import get_driver  # NOQA
 
 
-
 try:
     import paramiko
     have_paramiko = True
diff --git a/libcloud/common/base.py b/libcloud/common/base.py
--- a/libcloud/common/base.py
+++ b/libcloud/common/base.py
@@ -867,6 +867,8 @@ def request(self, action, params=None, data=None, headers=None,
         return response
 
     def morph_action_hook(self, action):
+        if not action.startswith("/"):
+            action = "/" + action
         return self.request_path + action
 
     def add_default_params(self, params):
diff --git a/libcloud/container/drivers/docker.py b/libcloud/container/drivers/docker.py
--- a/libcloud/container/drivers/docker.py
+++ b/libcloud/container/drivers/docker.py
@@ -205,7 +205,7 @@ def install_image(self, path):
         }
         data = json.dumps(payload)
 
-        result = self.connection.request('v%s/images/create?fromImage=%s' %
+        result = self.connection.request('/v%s/images/create?fromImage=%s' %
                                          (self.version, path), data=data,
                                          method='POST')
         if "errorDetail" in result.body:
@@ -235,7 +235,7 @@ def list_images(self):
 
         :rtype: ``list`` of :class:`libcloud.container.base.ContainerImage`
         """
-        result = self.connection.request('v%s/images/json' %
+        result = self.connection.request('/v%s/images/json' %
                                          (self.version)).object
         images = []
         for image in result:
@@ -276,7 +276,7 @@ def list_containers(self, image=None, all=True):
             ex = ''
         try:
             result = self.connection.request(
-                "v%s/containers/json%s" % (self.version, ex)).object
+                "/v%s/containers/json%s" % (self.version, ex)).object
         except Exception as exc:
             errno = getattr(exc, 'errno', None)
             if errno == 111:
@@ -355,7 +355,7 @@ def deploy_container(self, name, image, parameters=None, start=True,
 
         data = json.dumps(payload)
         try:
-            result = self.connection.request('v%s/containers/create'
+            result = self.connection.request('/v%s/containers/create'
                                              % (self.version),
                                              data=data,
                                              params=params, method='POST')
@@ -377,7 +377,7 @@ def deploy_container(self, name, image, parameters=None, start=True,
         data = json.dumps(payload)
         if start:
             result = self.connection.request(
-                'v%s/containers/%s/start' %
+                '/v%s/containers/%s/start' %
                 (self.version, id_), data=data,
                 method='POST')
 
@@ -392,7 +392,7 @@ def get_container(self, id):
 
         :rtype: :class:`libcloud.container.base.Container`
         """
-        result = self.connection.request("v%s/containers/%s/json" %
+        result = self.connection.request("/v%s/containers/%s/json" %
                                          (self.version, id)).object
 
         return self._to_container(result)
@@ -413,7 +413,7 @@ def start_container(self, container):
         }
         data = json.dumps(payload)
         result = self.connection.request(
-            'v%s/containers/%s/start' %
+            '/v%s/containers/%s/start' %
             (self.version, container.id),
             method='POST', data=data)
         if result.status in VALID_RESPONSE_CODES:
@@ -432,7 +432,7 @@ def stop_container(self, container):
         :return: The container refreshed with current data
         :rtype: :class:`libcloud.container.base.Container`
         """
-        result = self.connection.request('v%s/containers/%s/stop' %
+        result = self.connection.request('/v%s/containers/%s/stop' %
                                          (self.version, container.id),
                                          method='POST')
         if result.status in VALID_RESPONSE_CODES:
@@ -453,7 +453,7 @@ def restart_container(self, container):
         """
         data = json.dumps({'t': 10})
         # number of seconds to wait before killing the container
-        result = self.connection.request('v%s/containers/%s/restart' %
+        result = self.connection.request('/v%s/containers/%s/restart' %
                                          (self.version, container.id),
                                          data=data, method='POST')
         if result.status in VALID_RESPONSE_CODES:
@@ -472,8 +472,8 @@ def destroy_container(self, container):
         :return: True if the destroy was successful, False otherwise.
         :rtype: ``bool``
         """
-        result = self.connection.request('v%s/containers/%s' % (self.version,
-                                                                container.id),
+        result = self.connection.request('/v%s/containers/%s' % (self.version,
+                                                                 container.id),
                                          method='DELETE')
         return result.status in VALID_RESPONSE_CODES
 
@@ -486,7 +486,7 @@ def ex_list_processes(self, container):
 
         :rtype: ``str``
         """
-        result = self.connection.request("v%s/containers/%s/top" %
+        result = self.connection.request("/v%s/containers/%s/top" %
                                          (self.version, container.id)).object
 
         return result
@@ -503,7 +503,7 @@ def ex_rename_container(self, container, name):
 
         :rtype: :class:`libcloud.container.base.Container`
         """
-        result = self.connection.request('v%s/containers/%s/rename?name=%s'
+        result = self.connection.request('/v%s/containers/%s/rename?name=%s'
                                          % (self.version, container.id, name),
                                          method='POST')
         if result.status in VALID_RESPONSE_CODES:
@@ -530,13 +530,13 @@ def ex_get_logs(self, container, stream=False):
 
         if float(self._get_api_version()) > 1.10:
             result = self.connection.request(
-                "v%s/containers/%s/logs?follow=%s&stdout=1&stderr=1" %
+                "/v%s/containers/%s/logs?follow=%s&stdout=1&stderr=1" %
                 (self.version, container.id, str(stream))).object
             logs = result
         else:
             result = self.connection.request(
-                "v%s/containers/%s/attach?logs=1&stream=%s&stdout=1&stderr=1" %
-                (self.version, container.id, str(stream)),
+                "/v%s/containers/%s/attach?logs=1&stream=%s&stdout=1&stderr=1"
+                % (self.version, container.id, str(stream)),
                 method='POST',
                 data=data)
             logs = result.body
@@ -560,7 +560,7 @@ def ex_search_images(self, term):
         """
 
         term = term.replace(' ', '+')
-        result = self.connection.request('v%s/images/search?term=%s' %
+        result = self.connection.request('/v%s/images/search?term=%s' %
                                          (self.version, term)).object
         images = []
         for image in result:
@@ -591,8 +591,8 @@ def ex_delete_image(self, image):
 
         :rtype: ``bool``
         """
-        result = self.connection.request('v%s/images/%s' % (self.version,
-                                                            image.name),
+        result = self.connection.request('/v%s/images/%s' % (self.version,
+                                                             image.name),
                                          method='DELETE')
         return result.status in VALID_RESPONSE_CODES
 


Test patch:
diff --git a/libcloud/test/container/test_docker.py b/libcloud/test/container/test_docker.py
--- a/libcloud/test/container/test_docker.py
+++ b/libcloud/test/container/test_docker.py
@@ -129,101 +129,101 @@ def _version(
             raise AssertionError('Unsupported method')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_images_search(
+    def _vlinux_121_images_search(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/search.json'), {}, httplib.responses[httplib.OK])
 
-    def vmac_124_images_search(
+    def _vmac_124_images_search(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('mac_124/search.json'), {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_images_json(
+    def _vlinux_121_images_json(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/images.json'), {}, httplib.responses[httplib.OK])
 
-    def vmac_124_images_json(
+    def _vmac_124_images_json(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/images.json'), {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_images_create(
+    def _vlinux_121_images_create(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/create_image.json'), {'Content-Type': 'application/json'},
                 httplib.responses[httplib.OK])
 
-    def vmac_124_images_create(
+    def _vmac_124_images_create(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('mac_124/create_image.json'), {'Content-Type': 'application/json'},
                 httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_json(
+    def _vlinux_121_containers_json(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/containers.json'), {}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_json(
+    def _vmac_124_containers_json(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('mac_124/containers.json'), {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_create(
+    def _vlinux_121_containers_create(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/create_container.json'), {}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_create(
+    def _vmac_124_containers_create(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('mac_124/create_container.json'), {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303(
+    def _vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303(
+    def _vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_start(
+    def _vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_start(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_start(
+    def _vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_start(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_restart(
+    def _vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_restart(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_restart(
+    def _vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_restart(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_rename(
+    def _vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_rename(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_rename(
+    def _vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_rename(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_stop(
+    def _vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_stop(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_stop(
+    def _vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_stop(
             self, method, url, body, headers):
         return (httplib.NO_CONTENT, '', {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_json(
+    def _vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_json(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/container_a68.json'), {}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_json(
+    def _vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_json(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/container_a68.json'), {}, httplib.responses[httplib.OK])
 
-    def vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_logs(
+    def _vlinux_121_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_logs(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/logs.txt'), {'content-type': 'text/plain'}, httplib.responses[httplib.OK])
 
-    def vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_logs(
+    def _vmac_124_containers_a68c1872c74630522c7aa74b85558b06824c5e672cee334296c50fb209825303_logs(
             self, method, url, body, headers):
         return (httplib.OK, self.fixtures.load('linux_121/logs.txt'), {'content-type': 'text/plain'}, httplib.responses[httplib.OK])