Further implementation details to the ECS driver

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

Branch: refs/heads/trunk
Commit: a1adaae2df83ec7731a64eccbbcdbebd163a875e
Parents: f4c1e2e
Author: anthony-shaw <[email protected]>
Authored: Tue Dec 29 21:57:57 2015 +1100
Committer: anthony-shaw <[email protected]>
Committed: Tue Dec 29 21:57:57 2015 +1100

----------------------------------------------------------------------
 libcloud/container/base.py                      |   1 +
 libcloud/container/drivers/ecs.py               | 157 +++++++++++++++----
 .../test/container/fixtures/ecs/listtasks.json  |   8 +
 .../fixtures/ecs/registertaskdefinition.json    |  26 +++
 .../test/container/fixtures/ecs/runtask.json    |  37 +++++
 .../test/container/fixtures/ecs/stoptask.json   |  42 +++++
 libcloud/test/container/test_ecs.py             |  79 +++++++++-
 7 files changed, 317 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/a1adaae2/libcloud/container/base.py
----------------------------------------------------------------------
diff --git a/libcloud/container/base.py b/libcloud/container/base.py
index b9750d4..48ee573 100644
--- a/libcloud/container/base.py
+++ b/libcloud/container/base.py
@@ -22,6 +22,7 @@ __all__ = [
     'Container',
     'ContainerImage',
     'ContainerCluster',
+    'ClusterLocation',
     'ContainerDriver'
 ]
 

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a1adaae2/libcloud/container/drivers/ecs.py
----------------------------------------------------------------------
diff --git a/libcloud/container/drivers/ecs.py 
b/libcloud/container/drivers/ecs.py
index e3a356f..9aa98d3 100644
--- a/libcloud/container/drivers/ecs.py
+++ b/libcloud/container/drivers/ecs.py
@@ -71,7 +71,6 @@ class ElasticContainerDriver(ContainerDriver):
         params = {'Action': 'DescribeClusters'}
         data = self.connection.request(
             ROOT,
-            params=params,
             headers=self._get_headers(params['Action'])
         ).object
         return self._to_clusters(data)
@@ -92,7 +91,6 @@ class ElasticContainerDriver(ContainerDriver):
         request = {'clusterName': name}
         response = self.connection.request(
             ROOT,
-            params=params,
             data=request,
             headers=self._get_headers(params['Action'])
         ).object
@@ -109,7 +107,6 @@ class ElasticContainerDriver(ContainerDriver):
         request = {'cluster': cluster.id}
         data = self.connection.request(
             ROOT,
-            params=params,
             data=request,
             headers=self._get_headers(params['Action'])
         ).object
@@ -129,7 +126,8 @@ class ElasticContainerDriver(ContainerDriver):
 
     def list_images(self):
         """
-        List the installed container images
+        List the installed container images, in ECS these are
+        equivalent to the containers within task definitions.
 
         :rtype: ``list`` of :class:`ContainerImage`
         """
@@ -148,25 +146,26 @@ class ElasticContainerDriver(ContainerDriver):
 
         :rtype: ``list`` of :class:`Container`
         """
-        params = {'Action': 'DescribeTasks'}
         request = None
         if cluster is not None:
             request = {'cluster': cluster.id}
-        response = self.connection.request(
+        if image is not None:
+            request['family'] = image.name
+        list_response = self.connection.request(
             ROOT,
-            params=params,
             data=request,
-            headers=self._get_headers(params['Action'])
+            headers=self._get_headers('ListTasks')
         ).object
-        containers = []
-        for task in response['tasks']:
-            containers.extend(self._to_containers(task))
+        containers = self.ex_list_containers_for_task(
+            list_response['taskArns'])
         return containers
 
     def deploy_container(self, name, image, cluster=None,
-                         parameters=None, start=True):
+                         parameters=None, start=True, ex_cpu=10, ex_memory=500,
+                         ex_container_port=None, ex_host_port=None):
         """
-        Deploy an installed container image
+        Creates a task definition from a container image that can be run
+        in a cluster.
 
         :param name: The name of the new container
         :type  name: ``str``
@@ -185,8 +184,47 @@ class ElasticContainerDriver(ContainerDriver):
 
         :rtype: :class:`Container`
         """
-        raise NotImplementedError(
-            'deploy_container not implemented for this driver')
+        data = {}
+        data['containerDefinitions'] = [
+            {
+                "mountPoints": [],
+                "name": name,
+                "image": image.name,
+                "cpu": ex_cpu,
+                "environment": [],
+                "memory": ex_memory,
+                "portMappings": [
+                    {
+                        "containerPort": ex_container_port,
+                        "hostPort": ex_host_port
+                    }
+                ],
+                "essential": True,
+                "volumesFrom": []
+            }
+        ]
+        data['family'] = name
+        response = self.connection.request(
+            ROOT,
+            data=data,
+            headers=self._get_headers('RegisterTaskDefinition')
+        ).object
+        if start:
+            return self.ex_start_task(
+                response['taskDefinition']['taskDefinitionArn'])[0]
+        else:
+            return Container(
+                id=None,
+                name=name,
+                image=image,
+                state=ContainerState.RUNNING,
+                ip_addresses=[],
+                extra={
+                    'taskDefinitionArn':
+                        response['taskDefinition']['taskDefinitionArn']
+                },
+                driver=self.connection.driver
+            )
 
     def get_container(self, id):
         """
@@ -197,20 +235,22 @@ class ElasticContainerDriver(ContainerDriver):
 
         :rtype: :class:`Container`
         """
-        raise NotImplementedError(
-            'get_container not implemented for this driver')
+        containers = self.ex_list_containers_for_task([id])
+        return containers[0]
 
-    def start_container(self, container):
+    def start_container(self, container, count=1):
         """
-        Start a deployed container
+        Start a deployed task
 
         :param container: The container to start
         :type  container: :class:`Container`
 
+        :param count: Number of containers to start
+        :type  count: ``int``
+
         :rtype: :class:`Container`
         """
-        raise NotImplementedError(
-            'start_container not implemented for this driver')
+        return self.ex_start_task(container.extra['taskDefinitionArn'], count)
 
     def stop_container(self, container):
         """
@@ -221,8 +261,17 @@ class ElasticContainerDriver(ContainerDriver):
 
         :rtype: :class:`Container`
         """
-        raise NotImplementedError(
-            'stop_container not implemented for this driver')
+        request = {'task': container.extra['taskArn']}
+        response = self.connection.request(
+            ROOT,
+            data=request,
+            headers=self._get_headers('StopTask')
+        ).object
+        containers = []
+        containers.extend(self._to_containers(
+            response['task'],
+            container.extra['taskDefinitionArn']))
+        return containers
 
     def restart_container(self, container):
         """
@@ -233,8 +282,8 @@ class ElasticContainerDriver(ContainerDriver):
 
         :rtype: :class:`Container`
         """
-        raise NotImplementedError(
-            'restart_container not implemented for this driver')
+        self.stop_container(container)
+        return self.start_container(container)
 
     def destroy_container(self, container):
         """
@@ -245,8 +294,53 @@ class ElasticContainerDriver(ContainerDriver):
 
         :rtype: :class:`Container`
         """
-        raise NotImplementedError(
-            'destroy_container not implemented for this driver')
+        return self.stop_container(container)
+
+    def ex_start_task(self, task_arn, count=1):
+        """
+        Run a task definition and get the containers
+
+        :param task_arn: The task ARN to Run
+        :type  task_arn: ``str``
+
+        :param count: The number of containers to start
+        :type  count: ``int``
+
+        :rtype: ``list`` of :class:`Container`
+        """
+        request = None
+        request = {'count': count,
+                   'taskDefinition': task_arn}
+        response = self.connection.request(
+            ROOT,
+            data=request,
+            headers=self._get_headers('RunTask')
+        ).object
+        containers = []
+        for task in response['tasks']:
+            containers.extend(self._to_containers(task, task_arn))
+        return containers
+
+    def ex_list_containers_for_task(self, task_arns):
+        """
+        Get a list of containers by ID collection (ARN)
+
+        :param task_arns: The list of ARNs
+        :type  task_arns: ``list`` of ``str``
+
+        :rtype: ``list`` of :class:`Container`
+        """
+        describe_request = {'tasks': task_arns}
+        descripe_response = self.connection.request(
+            ROOT,
+            data=describe_request,
+            headers=self._get_headers('DescribeTasks')
+        ).object
+        containers = []
+        for task in descripe_response['tasks']:
+            containers.extend(self._to_containers(
+                task, task['taskDefinitionArn']))
+        return containers
 
     def _get_headers(self, action):
         return {'x-amz-target': '%s.%s' %
@@ -265,13 +359,13 @@ class ElasticContainerDriver(ContainerDriver):
             driver=self.connection.driver
         )
 
-    def _to_containers(self, data):
+    def _to_containers(self, data, task_definition_arn):
         clusters = []
         for cluster in data['containers']:
-            clusters.append(self._to_container(cluster))
+            clusters.append(self._to_container(cluster, task_definition_arn))
         return clusters
 
-    def _to_container(self, data):
+    def _to_container(self, data, task_definition_arn):
         return Container(
             id=data['containerArn'],
             name=data['name'],
@@ -285,7 +379,8 @@ class ElasticContainerDriver(ContainerDriver):
             ip_addresses=None,
             state=self.status_map.get(data['lastStatus'], None),
             extra={
-                'taskArn': data['taskArn']
+                'taskArn': data['taskArn'],
+                'taskDefinitionArn': task_definition_arn
             },
             driver=self.connection.driver
         )

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a1adaae2/libcloud/test/container/fixtures/ecs/listtasks.json
----------------------------------------------------------------------
diff --git a/libcloud/test/container/fixtures/ecs/listtasks.json 
b/libcloud/test/container/fixtures/ecs/listtasks.json
new file mode 100644
index 0000000..644ff73
--- /dev/null
+++ b/libcloud/test/container/fixtures/ecs/listtasks.json
@@ -0,0 +1,8 @@
+{
+  "taskArns": [
+    
"arn:aws:ecs:us-east-1:012345678910:task/0b69d5c0-d655-4695-98cd-5d2d526d9d5a",
+    
"arn:aws:ecs:us-east-1:012345678910:task/51a01bdf-d00e-487e-ab14-7645330b6207",
+    
"arn:aws:ecs:us-east-1:012345678910:task/b0b28bb8-2be3-4810-b52b-88df129d893c",
+    
"arn:aws:ecs:us-east-1:012345678910:task/c09f0188-7f87-4b0f-bfc3-16296622b6fe"
+  ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a1adaae2/libcloud/test/container/fixtures/ecs/registertaskdefinition.json
----------------------------------------------------------------------
diff --git a/libcloud/test/container/fixtures/ecs/registertaskdefinition.json 
b/libcloud/test/container/fixtures/ecs/registertaskdefinition.json
new file mode 100644
index 0000000..163061d
--- /dev/null
+++ b/libcloud/test/container/fixtures/ecs/registertaskdefinition.json
@@ -0,0 +1,26 @@
+{
+  "taskDefinition": {
+    "containerDefinitions": [
+      {
+        "cpu": 10,
+        "environment": [
+          {
+            "name": "MYSQL_ROOT_PASSWORD",
+            "value": "password"
+          }
+        ],
+        "essential": true,
+        "image": "mysql",
+        "memory": 500,
+        "mountPoints": [],
+        "name": "mysql",
+        "portMappings": [],
+        "volumesFrom": []
+      }
+    ],
+    "family": "jim",
+    "revision": 11,
+    "taskDefinitionArn": 
"arn:aws:ecs:us-east-1:012345678910:task-definition/hello_world:11",
+    "volumes": []
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a1adaae2/libcloud/test/container/fixtures/ecs/runtask.json
----------------------------------------------------------------------
diff --git a/libcloud/test/container/fixtures/ecs/runtask.json 
b/libcloud/test/container/fixtures/ecs/runtask.json
new file mode 100644
index 0000000..70896e5
--- /dev/null
+++ b/libcloud/test/container/fixtures/ecs/runtask.json
@@ -0,0 +1,37 @@
+{
+  "failures": [],
+  "tasks": [
+    {
+      "clusterArn": "arn:aws:ecs:us-east-1:012345678910:cluster/default",
+      "containerInstanceArn": 
"arn:aws:ecs:us-east-1:012345678910:container-instance/cf447635-790d-477d-be24-58a9cb819d45",
+      "containers": [
+        {
+          "containerArn": 
"arn:aws:ecs:us-east-1:012345678910:container/e1ed7aac-d9b2-4315-8726-d2432bf11868",
+          "lastStatus": "PENDING",
+          "name": "wordpress",
+          "taskArn": 
"arn:aws:ecs:us-east-1:012345678910:task/d8c67b3c-ac87-4ffe-a847-4785bc3a8b55"
+        },
+        {
+          "containerArn": 
"arn:aws:ecs:us-east-1:012345678910:container/4b69fabd-991d-4781-bbf9-7efa03c754aa",
+          "lastStatus": "PENDING",
+          "name": "mysql",
+          "taskArn": 
"arn:aws:ecs:us-east-1:012345678910:task/d8c67b3c-ac87-4ffe-a847-4785bc3a8b55"
+        }
+      ],
+      "desiredStatus": "RUNNING",
+      "lastStatus": "PENDING",
+      "overrides": {
+        "containerOverrides": [
+          {
+            "name": "wordpress"
+          },
+          {
+            "name": "mysql"
+          }
+        ]
+      },
+      "taskArn": 
"arn:aws:ecs:us-east-1:012345678910:task/d8c67b3c-ac87-4ffe-a847-4785bc3a8b55",
+      "taskDefinitionArn": 
"arn:aws:ecs:us-east-1:012345678910:task-definition/hello_world:11"
+    }
+  ]
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a1adaae2/libcloud/test/container/fixtures/ecs/stoptask.json
----------------------------------------------------------------------
diff --git a/libcloud/test/container/fixtures/ecs/stoptask.json 
b/libcloud/test/container/fixtures/ecs/stoptask.json
new file mode 100644
index 0000000..72b5ebb
--- /dev/null
+++ b/libcloud/test/container/fixtures/ecs/stoptask.json
@@ -0,0 +1,42 @@
+{
+  "task": {
+    "clusterArn": "arn:aws:ecs:us-east-1:012345678910:cluster/default",
+    "containerInstanceArn": 
"arn:aws:ecs:us-east-1:012345678910:container-instance/8db248d6-16a7-42b5-b9f9-43d3b1ad9430",
+    "containers": [
+      {
+        "containerArn": 
"arn:aws:ecs:us-east-1:012345678910:container/05a5528c-77f6-4e5b-8f9a-2b0a1928a926",
+        "lastStatus": "RUNNING",
+        "name": "mysql",
+        "networkBindings": [],
+        "taskArn": 
"arn:aws:ecs:us-east-1:012345678910:task/a126249b-b7e4-4b06-9d8f-1b56e75a99b5"
+      },
+      {
+        "containerArn": 
"arn:aws:ecs:us-east-1:012345678910:container/37234a82-77f6-41d7-b54b-591f1e278093",
+        "lastStatus": "RUNNING",
+        "name": "wordpress",
+        "networkBindings": [
+          {
+            "bindIP": "0.0.0.0",
+            "containerPort": 80,
+            "hostPort": 80
+          }
+        ],
+        "taskArn": 
"arn:aws:ecs:us-east-1:012345678910:task/a126249b-b7e4-4b06-9d8f-1b56e75a99b5"
+      }
+    ],
+    "desiredStatus": "STOPPED",
+    "lastStatus": "RUNNING",
+    "overrides": { 
+      "containerOverrides": [
+        {
+          "name": "mysql"
+        },
+        {
+          "name": "wordpress"
+        }
+      ]
+    },
+    "taskArn": 
"arn:aws:ecs:us-east-1:012345678910:task/a126249b-b7e4-4b06-9d8f-1b56e75a99b5",
+    "taskDefinitionArn": 
"arn:aws:ecs:us-east-1:012345678910:task-definition/hello_world:11"
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a1adaae2/libcloud/test/container/test_ecs.py
----------------------------------------------------------------------
diff --git a/libcloud/test/container/test_ecs.py 
b/libcloud/test/container/test_ecs.py
index 81bdf09..1a4af3d 100644
--- a/libcloud/test/container/test_ecs.py
+++ b/libcloud/test/container/test_ecs.py
@@ -17,7 +17,7 @@ import sys
 
 from libcloud.test import unittest
 
-from libcloud.container.base import ContainerCluster
+from libcloud.container.base import ContainerCluster, ContainerImage, Container
 from libcloud.container.drivers.ecs import ElasticContainerDriver
 
 from libcloud.utils.py3 import httplib
@@ -62,6 +62,77 @@ class ElasticContainerDriverTestCase(unittest.TestCase):
         containers = self.driver.list_containers(cluster=cluster)
         self.assertEqual(len(containers), 2)
 
+    def test_deploy_container(self):
+        container = self.driver.deploy_container(
+            name='jim',
+            image=ContainerImage(
+                id=None,
+                name='mysql',
+                path='mysql',
+                version=None,
+                driver=self.driver
+            )
+        )
+        self.assertEqual(container.id, 
'arn:aws:ecs:us-east-1:012345678910:container/e1ed7aac-d9b2-4315-8726-d2432bf11868')
+
+    def test_get_container(self):
+        container = self.driver.get_container(
+            
'arn:aws:ecs:us-east-1:012345678910:container/76c980a8-2454-4a9c-acc4-9eb103117273'
+        )
+        self.assertEqual(container.id, 
'arn:aws:ecs:us-east-1:012345678910:container/76c980a8-2454-4a9c-acc4-9eb103117273')
+        self.assertEqual(container.name, 'mysql')
+        self.assertEqual(container.image.name, 'mysql')
+
+    def test_start_container(self):
+        container = self.driver.start_container(
+            Container(
+                id=None,
+                name=None,
+                image=None,
+                state=None,
+                ip_addresses=None,
+                driver=self.driver,
+                extra={
+                    'taskDefinitionArn': ''
+                }
+            )
+        )
+        self.assertFalse(container is None)
+
+    def test_stop_container(self):
+        container = self.driver.stop_container(
+            Container(
+                id=None,
+                name=None,
+                image=None,
+                state=None,
+                ip_addresses=None,
+                driver=self.driver,
+                extra={
+                    'taskArn': '12345',
+                    'taskDefinitionArn': '123556'
+                }
+            )
+        )
+        self.assertFalse(container is None)
+
+    def test_restart_container(self):
+        container = self.driver.restart_container(
+            Container(
+                id=None,
+                name=None,
+                image=None,
+                state=None,
+                ip_addresses=None,
+                driver=self.driver,
+                extra={
+                    'taskArn': '12345',
+                    'taskDefinitionArn': '123556'
+                }
+            )
+        )
+        self.assertFalse(container is None)
+
 
 class ECSMockHttp(MockHttp):
     fixtures = ContainerFileFixtures('ecs')
@@ -69,7 +140,11 @@ class ECSMockHttp(MockHttp):
         'DescribeClusters': 'describeclusters.json',
         'CreateCluster': 'createcluster.json',
         'DeleteCluster': 'deletecluster.json',
-        'DescribeTasks': 'describetasks.json'
+        'DescribeTasks': 'describetasks.json',
+        'ListTasks': 'listtasks.json',
+        'RegisterTaskDefinition': 'registertaskdefinition.json',
+        'RunTask': 'runtask.json',
+        'StopTask': 'stoptask.json'
     }
 
     def _2014_11_13(

Reply via email to