libcloud-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From anthonys...@apache.org
Subject [47/50] libcloud git commit: Extended the Kubernetes driver to support deploying containers as single-container pods. also added destroying containers (pods) via the API.
Date Wed, 20 Jan 2016 03:44:35 GMT
Extended the Kubernetes driver to support deploying containers as single-container pods. also
added destroying containers (pods) via the API.


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/0b875888
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/0b875888
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/0b875888

Branch: refs/heads/trunk
Commit: 0b8758881942fbac1963f30363fd49cef9adfcb4
Parents: 936fe42
Author: anthony-shaw <anthony.p.shaw@gmail.com>
Authored: Wed Jan 13 16:15:56 2016 +1100
Committer: anthony-shaw <anthony.p.shaw@gmail.com>
Committed: Wed Jan 13 16:15:56 2016 +1100

----------------------------------------------------------------------
 libcloud/container/drivers/kubernetes.py        | 138 +++++++++++++++++--
 .../_api_v1_namespaces_default_pods_POST.json   |  47 +++++++
 .../fixtures/kubernetes/_api_v1_pods.json       |  94 +++++++++++++
 libcloud/test/container/test_kubernetes.py      |  41 ++++--
 4 files changed, 302 insertions(+), 18 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/0b875888/libcloud/container/drivers/kubernetes.py
----------------------------------------------------------------------
diff --git a/libcloud/container/drivers/kubernetes.py b/libcloud/container/drivers/kubernetes.py
index a947fd2..13f2a51 100644
--- a/libcloud/container/drivers/kubernetes.py
+++ b/libcloud/container/drivers/kubernetes.py
@@ -28,7 +28,7 @@ from libcloud.common.base import JsonResponse, ConnectionUserAndKey
 from libcloud.common.types import InvalidCredsError
 
 from libcloud.container.base import (Container, ContainerDriver,
-                                     ContainerCluster)
+                                     ContainerImage, ContainerCluster)
 
 from libcloud.container.providers import Provider
 from libcloud.container.types import ContainerState
@@ -85,6 +85,16 @@ class KubernetesConnection(ConnectionUserAndKey):
         return headers
 
 
+class KubernetesPod(object):
+    def __init__(self, name, containers, namespace):
+        """
+        A Kubernetes pod
+        """
+        self.name = name
+        self.containers = containers
+        self.namespace = namespace
+
+
 class KubernetesContainerDriver(ContainerDriver):
     type = Provider.KUBERNETES
     name = 'Kubernetes'
@@ -169,7 +179,7 @@ class KubernetesContainerDriver(ContainerDriver):
         """
         try:
             result = self.connection.request(
-                ROOT_URL + "v1/nodes/").object
+                ROOT_URL + "v1/pods").object
         except Exception as exc:
             if hasattr(exc, 'errno') and exc.errno == 111:
                 raise KubernetesException(
@@ -178,7 +188,10 @@ class KubernetesContainerDriver(ContainerDriver):
                     'and the API port is correct')
             raise
 
-        containers = [self._to_container(value) for value in result['items']]
+        pods = [self._to_pod(value) for value in result['items']]
+        containers = []
+        for pod in pods:
+            containers.extend(pod.containers)
         return containers
 
     def get_container(self, id):
@@ -265,19 +278,124 @@ class KubernetesContainerDriver(ContainerDriver):
                                          data=json.dumps(request)).object
         return self._to_cluster(result)
 
-    def _to_container(self, data):
+    def deploy_container(self, name, image, cluster=None,
+                         parameters=None, start=True):
+        """
+        Deploy an installed container image.
+        In kubernetes this deploys a single container Pod.
+        https://cloud.google.com/container-engine/docs/pods/single-container
+
+        :param name: The name of the new container
+        :type  name: ``str``
+
+        :param image: The container image to deploy
+        :type  image: :class:`.ContainerImage`
+
+        :param cluster: The cluster to deploy to, None is default
+        :type  cluster: :class:`.ContainerCluster`
+
+        :param parameters: Container Image parameters
+        :type  parameters: ``str``
+
+        :param start: Start the container on deployment
+        :type  start: ``bool``
+
+        :rtype: :class:`.Container`
+        """
+        if cluster is None:
+            namespace = 'default'
+        else:
+            namespace = cluster.id
+        request = {
+            "metadata": {
+                "name": name
+            },
+            "spec": {
+                "containers": [
+                    {
+                        "name": name,
+                        "image": image.name
+                    }
+                ]
+            }
+        }
+        result = self.connection.request(ROOT_URL + "v1/namespaces/%s/pods"
+                                         % namespace,
+                                         method='POST',
+                                         data=json.dumps(request)).object
+        return self._to_cluster(result)
+
+    def destroy_container(self, container):
+        """
+        Destroy a deployed container. Because the containers are single
+        container pods, this will delete the pod.
+
+        :param container: The container to destroy
+        :type  container: :class:`.Container`
+
+        :rtype: ``bool``
+        """
+        return self.ex_delete_pod(container.extra['namespace'],
+                                  container.extra['pod'])
+
+    def ex_list_pods(self):
+        """
+        List available Pods
+
+        :rtype: ``list`` of :class:`.KubernetesPod`
+        """
+        result = self.connection.request(ROOT_URL + "v1/pods").object
+        return [self._to_pod(value) for value in result['items']]
+
+    def ex_destroy_pod(self, namespace, pod_name):
+        """
+        Delete a pod and the containers within it.
+        """
+        self.connection.request(
+            ROOT_URL + "v1/namespaces/%s/pods/%s" % (
+                namespace, pod_name),
+            method='DELETE').object
+        return True
+
+    def _to_pod(self, data):
+        """
+        Convert an API response to a Pod object
+        """
+        container_statuses = data['status']['containerStatuses']
+        containers = []
+        # response contains the status of the containers in a separate field
+        for container in data['spec']['containers']:
+            spec = list(filter(lambda i: i['name'] == container['name'],
+                               container_statuses))[0]
+            containers.append(
+                self._to_container(container, spec, data)
+            )
+        return KubernetesPod(
+            name=data['metadata']['name'],
+            namespace=data['metadata']['namespace'],
+            containers=containers)
+
+    def _to_container(self, data, container_status, pod_data):
         """
         Convert container in Container instances
         """
-        metadata = data['metadata']
         return Container(
-            id=data['spec']['externalID'],
-            name=metadata['name'],
-            image=None,
-            ip_addresses="ips",
+            id=container_status['containerID'],
+            name=data['name'],
+            image=ContainerImage(
+                id=container_status['imageID'],
+                name=data['image'],
+                path=None,
+                version=None,
+                driver=self.connection.driver
+                ),
+            ip_addresses=None,
             state=ContainerState.RUNNING,
             driver=self.connection.driver,
-            extra=None)
+            extra={
+                'pod': pod_data['metadata']['name'],
+                'namespace': pod_data['metadata']['namespace']
+            })
 
     def _to_cluster(self, data):
         """

http://git-wip-us.apache.org/repos/asf/libcloud/blob/0b875888/libcloud/test/container/fixtures/kubernetes/_api_v1_namespaces_default_pods_POST.json
----------------------------------------------------------------------
diff --git a/libcloud/test/container/fixtures/kubernetes/_api_v1_namespaces_default_pods_POST.json
b/libcloud/test/container/fixtures/kubernetes/_api_v1_namespaces_default_pods_POST.json
new file mode 100644
index 0000000..49387d1
--- /dev/null
+++ b/libcloud/test/container/fixtures/kubernetes/_api_v1_namespaces_default_pods_POST.json
@@ -0,0 +1,47 @@
+{
+  "kind": "Pod",
+  "apiVersion": "v1",
+  "metadata": {
+    "name": "hello-world",
+    "namespace": "default",
+    "selfLink": "/api/v1/namespaces/default/pods/hello-world",
+    "uid": "1fad5411-b9af-11e5-8701-0050568157ec",
+    "resourceVersion": "32",
+    "creationTimestamp": "2016-01-13T04:35:50Z"
+  },
+  "spec": {
+    "volumes": [
+      {
+        "name": "default-token-dpyh0",
+        "secret": {
+          "secretName": "default-token-dpyh0"
+        }
+      }
+    ],
+    "containers": [
+      {
+        "name": "hello-world",
+        "image": "ubuntu:14.04",
+        "resources": {},
+        "volumeMounts": [
+          {
+            "name": "default-token-dpyh0",
+            "readOnly": true,
+            "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+          }
+        ],
+        "terminationMessagePath": "/dev/termination-log",
+        "imagePullPolicy": "IfNotPresent"
+      }
+    ],
+    "restartPolicy": "Always",
+    "terminationGracePeriodSeconds": 30,
+    "dnsPolicy": "ClusterFirst",
+    "serviceAccountName": "default",
+    "serviceAccount": "default",
+    "securityContext": {}
+  },
+  "status": {
+    "phase": "Pending"
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/0b875888/libcloud/test/container/fixtures/kubernetes/_api_v1_pods.json
----------------------------------------------------------------------
diff --git a/libcloud/test/container/fixtures/kubernetes/_api_v1_pods.json b/libcloud/test/container/fixtures/kubernetes/_api_v1_pods.json
new file mode 100644
index 0000000..f29ffdc
--- /dev/null
+++ b/libcloud/test/container/fixtures/kubernetes/_api_v1_pods.json
@@ -0,0 +1,94 @@
+{
+  "kind": "PodList",
+  "apiVersion": "v1",
+  "metadata": {
+    "selfLink": "/api/v1/pods",
+    "resourceVersion": "63"
+  },
+  "items": [
+    {
+      "metadata": {
+        "name": "hello-world",
+        "namespace": "default",
+        "selfLink": "/api/v1/namespaces/default/pods/hello-world",
+        "uid": "1fad5411-b9af-11e5-8701-0050568157ec",
+        "resourceVersion": "62",
+        "creationTimestamp": "2016-01-13T04:35:50Z"
+      },
+      "spec": {
+        "volumes": [
+          {
+            "name": "default-token-dpyh0",
+            "secret": {
+              "secretName": "default-token-dpyh0"
+            }
+          }
+        ],
+        "containers": [
+          {
+            "name": "hello-world",
+            "image": "ubuntu:14.04",
+            "resources": {},
+            "volumeMounts": [
+              {
+                "name": "default-token-dpyh0",
+                "readOnly": true,
+                "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount"
+              }
+            ],
+            "terminationMessagePath": "/dev/termination-log",
+            "imagePullPolicy": "IfNotPresent"
+          }
+        ],
+        "restartPolicy": "Always",
+        "terminationGracePeriodSeconds": 30,
+        "dnsPolicy": "ClusterFirst",
+        "serviceAccountName": "default",
+        "serviceAccount": "default",
+        "nodeName": "127.0.0.1",
+        "securityContext": {}
+      },
+      "status": {
+        "phase": "Running",
+        "conditions": [
+          {
+            "type": "Ready",
+            "status": "False",
+            "lastProbeTime": null,
+            "lastTransitionTime": "2016-01-13T04:37:09Z",
+            "reason": "ContainersNotReady",
+            "message": "containers with unready status: [hello-world]"
+          }
+        ],
+        "hostIP": "127.0.0.1",
+        "podIP": "172.17.0.2",
+        "startTime": "2016-01-13T04:35:50Z",
+        "containerStatuses": [
+          {
+            "name": "hello-world",
+            "state": {
+              "waiting": {
+                "reason": "CrashLoopBackOff",
+                "message": "Back-off 20s restarting failed container=hello-world pod=hello-world_default(1fad5411-b9af-11e5-8701-0050568157ec)"
+              }
+            },
+            "lastState": {
+              "terminated": {
+                "exitCode": 0,
+                "reason": "Completed",
+                "startedAt": "2016-01-13T04:37:07Z",
+                "finishedAt": "2016-01-13T04:37:07Z",
+                "containerID": "docker://3c48b5cda79bce4c8866f02a3b96a024edb8f660d10e7d1755e9ced49ef47b36"
+              }
+            },
+            "ready": false,
+            "restartCount": 2,
+            "image": "ubuntu:14.04",
+            "imageID": "docker://c4bea91afef3764163fd506f5c1090be1d34a9b63ece81867cb863455937048e",
+            "containerID": "docker://3c48b5cda79bce4c8866f02a3b96a024edb8f660d10e7d1755e9ced49ef47b36"
+          }
+        ]
+      }
+    }
+  ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/0b875888/libcloud/test/container/test_kubernetes.py
----------------------------------------------------------------------
diff --git a/libcloud/test/container/test_kubernetes.py b/libcloud/test/container/test_kubernetes.py
index 2e8da4e..b60a648 100644
--- a/libcloud/test/container/test_kubernetes.py
+++ b/libcloud/test/container/test_kubernetes.py
@@ -17,6 +17,8 @@ import sys
 
 from libcloud.test import unittest
 
+from libcloud.container.base import ContainerImage
+
 from libcloud.container.drivers.kubernetes import KubernetesContainerDriver
 
 from libcloud.utils.py3 import httplib
@@ -38,14 +40,8 @@ class KubernetesContainerDriverTestCase(unittest.TestCase):
         containers = self.driver.list_containers()
         self.assertEqual(len(containers), 1)
         self.assertEqual(containers[0].id,
-                         '127.0.0.1')
-        self.assertEqual(containers[0].name, '127.0.0.1')
-
-    def test_get_container(self):
-        container = self.driver.get_container('127.0.0.1')
-        self.assertEqual(container.id,
-                         '127.0.0.1')
-        self.assertEqual(container.name, '127.0.0.1')
+                         'docker://3c48b5cda79bce4c8866f02a3b96a024edb8f660d10e7d1755e9ced49ef47b36')
+        self.assertEqual(containers[0].name, 'hello-world')
 
     def test_list_clusters(self):
         clusters = self.driver.list_clusters()
@@ -71,6 +67,17 @@ class KubernetesContainerDriverTestCase(unittest.TestCase):
         result = self.driver.destroy_cluster(cluster)
         self.assertTrue(result)
 
+    def test_deploy_container(self):
+        image = ContainerImage(
+            id=None,
+            name='hello-world',
+            path=None,
+            driver=self.driver,
+            version=None
+        )
+        container = self.driver.deploy_container('hello-world', image=image)
+        self.assertEqual(container.name, 'hello-world')
+
 
 class KubernetesMockHttp(MockHttp):
     fixtures = ContainerFileFixtures('kubernetes')
@@ -83,6 +90,14 @@ class KubernetesMockHttp(MockHttp):
             raise AssertionError('Unsupported method')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _api_v1_pods(
+            self, method, url, body, headers):
+        if method == 'GET':
+            body = self.fixtures.load('_api_v1_pods.json')
+        else:
+            raise AssertionError('Unsupported method')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
     def _api_v1_nodes(
             self, method, url, body, headers):
         if method == 'GET':
@@ -121,5 +136,15 @@ class KubernetesMockHttp(MockHttp):
             raise AssertionError('Unsupported method')
         return (httplib.OK, body, {}, httplib.responses[httplib.OK])
 
+    def _api_v1_namespaces_default_pods(
+            self, method, url, body, headers):
+        if method == 'GET':
+            body = self.fixtures.load('_api_v1_namespaces_default_pods.json')
+        elif method == 'POST':
+            body = self.fixtures.load('_api_v1_namespaces_default_pods_POST.json')
+        else:
+            raise AssertionError('Unsupported method')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
 if __name__ == '__main__':
     sys.exit(unittest.main())


Mime
View raw message