From d740b3cfc53b5ceb7a509520b4486f76cb6e04c2 Mon Sep 17 00:00:00 2001 From: helgi Date: Fri, 16 Sep 2016 09:29:31 -0700 Subject: [PATCH] ref(scheduler): add more Pod tests in scheduler and add create() to Pod resource --- rootfs/api/models/app.py | 4 +- rootfs/scheduler/__init__.py | 21 +---- rootfs/scheduler/resources/pod.py | 29 +++++- rootfs/scheduler/tests/test_deployments.py | 17 +++- rootfs/scheduler/tests/test_pods.py | 93 +++++++++++++++++++ .../tests/test_replicationcontrollers.py | 11 ++- 6 files changed, 143 insertions(+), 32 deletions(-) diff --git a/rootfs/api/models/app.py b/rootfs/api/models/app.py index aa4a9dc7b..7ec39b306 100644 --- a/rootfs/api/models/app.py +++ b/rootfs/api/models/app.py @@ -681,7 +681,6 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits): return ''.join(random.choice(chars) for _ in range(size)) """Run a one-off command in an ephemeral app container.""" - scale_type = 'run' release = self.release_set.latest() if release.build is None: raise DeisException('No build associated with this release to run this command') @@ -690,11 +689,12 @@ def pod_name(size=5, chars=string.ascii_lowercase + string.digits): # use slugrunner image for app if buildpack app otherwise use normal image image = settings.SLUGRUNNER_IMAGE if release.build.type == 'buildpack' else release.image - data = self._gather_app_settings(release, app_settings, 'run', 1) + data = self._gather_app_settings(release, app_settings, process_type='run', replicas=1) # create application config and build the pod manifest self.set_application_config(release) + scale_type = 'run' name = self._get_job_id(scale_type) + '-' + pod_name() self.log("{} on {} runs '{}'".format(user.username, name, command)) diff --git a/rootfs/scheduler/__init__.py b/rootfs/scheduler/__init__.py index 4772bbeae..010aade4a 100644 --- a/rootfs/scheduler/__init__.py +++ b/rootfs/scheduler/__init__.py @@ -304,23 +304,7 @@ def run(self, namespace, name, image, entrypoint, command, **kwargs): kwargs['command'] = entrypoint kwargs['args'] = command - manifest = self.pod.manifest(namespace, name, image, **kwargs) - - url = self.pods.api("/namespaces/{}/pods", namespace) - response = self.http_post(url, json=manifest) - if self.unhealthy(response.status_code): - raise KubeHTTPException(response, 'create Pod in Namespace "{}"', namespace) - - # wait for run pod to start - use the same function as scale - labels = manifest['metadata']['labels'] - containers = manifest['spec']['containers'] - self.pods.wait_until_ready( - namespace, - containers, - labels, - desired=1, - timeout=kwargs.get('deploy_timeout') - ) + self.pod.create(namespace, name, image, **kwargs) try: # give pod 20 minutes to execute (after it got into ready state) @@ -331,8 +315,7 @@ def run(self, namespace, name, image, entrypoint, command, **kwargs): waited = 0 timeout = 1200 # 20 minutes while (state == 'up' and waited < timeout): - response = self.pod.get(namespace, name) - pod = response.json() + pod = self.pod.get(namespace, name).json() state = str(self.pod.state(pod)) # default data exit_code = 0 diff --git a/rootfs/scheduler/resources/pod.py b/rootfs/scheduler/resources/pod.py index 08c7852fe..2933312dd 100644 --- a/rootfs/scheduler/resources/pod.py +++ b/rootfs/scheduler/resources/pod.py @@ -32,6 +32,27 @@ def get(self, namespace, name=None, **kwargs): return response + def create(self, namespace, name, image, **kwargs): + manifest = self.manifest(namespace, name, image, **kwargs) + + url = self.api('/namespaces/{}/pods', namespace) + response = self.http_post(url, json=manifest) + if self.unhealthy(response.status_code): + raise KubeHTTPException(response, 'create Pod in Namespace "{}"', namespace) + + # wait for all pods to start - use the same function as scale + labels = manifest['metadata']['labels'] + containers = manifest['spec']['containers'] + self.pods.wait_until_ready( + namespace, + containers, + labels, + desired=kwargs.get('replicas'), + timeout=kwargs.get('deploy_timeout') + ) + + return response + def state(self, pod): """ Resolve Pod state to an internally understandable format and returns a @@ -315,9 +336,9 @@ def delete(self, namespace, name): # delete pod url = self.api("/namespaces/{}/pods/{}", namespace, name) - resp = self.http_delete(url) - if self.unhealthy(resp.status_code): - raise KubeHTTPException(resp, 'delete Pod "{}" in Namespace "{}"', name, namespace) + response = self.http_delete(url) + if self.unhealthy(response.status_code): + raise KubeHTTPException(response, 'delete Pod "{}" in Namespace "{}"', name, namespace) # Verify the pod has been deleted # Only wait as long as the grace period is - k8s will eventually GC @@ -333,6 +354,8 @@ def delete(self, namespace, name): time.sleep(1) + return response + def logs(self, namespace, name): url = self.api("/namespaces/{}/pods/{}/log", namespace, name) response = self.http_get(url) diff --git a/rootfs/scheduler/tests/test_deployments.py b/rootfs/scheduler/tests/test_deployments.py index e89c0d32d..8bd0a0e1f 100644 --- a/rootfs/scheduler/tests/test_deployments.py +++ b/rootfs/scheduler/tests/test_deployments.py @@ -22,10 +22,12 @@ def create(self, namespace=None, name=generate_random_name(), **kwargs): 'version': kwargs.get('version', 'v99'), 'replicas': kwargs.get('replicas', 4), 'pod_termination_grace_period_seconds': 2, + 'image': 'quay.io/fake/image', + 'entrypoint': 'sh', + 'command': 'start', } - deployment = self.scheduler.deployment.create(namespace, name, 'quay.io/fake/image', - 'sh', 'start', **kwargs) + deployment = self.scheduler.deployment.create(namespace, name, **kwargs) self.assertEqual(deployment.status_code, 201, deployment.json()) return name @@ -40,10 +42,12 @@ def update(self, namespace=None, name=generate_random_name(), **kwargs): 'version': kwargs.get('version', 'v99'), 'replicas': kwargs.get('replicas', 4), 'pod_termination_grace_period_seconds': 2, + 'image': 'quay.io/fake/image', + 'entrypoint': 'sh', + 'command': 'start', } - deployment = self.scheduler.deployment.update(namespace, name, 'quay.io/fake/image', - 'sh', 'start', **kwargs) + deployment = self.scheduler.deployment.update(namespace, name, **kwargs) data = deployment.json() self.assertEqual(deployment.status_code, 200, data) return name @@ -59,9 +63,12 @@ def scale(self, namespace=None, name=generate_random_name(), **kwargs): 'version': kwargs.get('version', 'v99'), 'replicas': kwargs.get('replicas', 4), 'pod_termination_grace_period_seconds': 2, + 'image': 'quay.io/fake/image', + 'entrypoint': 'sh', + 'command': 'start', } - self.scheduler.scale(namespace, name, 'quay.io/fake/image', 'sh', 'start', **kwargs) + self.scheduler.scale(namespace, name, **kwargs) return name def test_create_failure(self): diff --git a/rootfs/scheduler/tests/test_pods.py b/rootfs/scheduler/tests/test_pods.py index e69de29bb..236144d2f 100644 --- a/rootfs/scheduler/tests/test_pods.py +++ b/rootfs/scheduler/tests/test_pods.py @@ -0,0 +1,93 @@ +""" +Unit tests for the Deis scheduler module. + +Run the tests with './manage.py test scheduler' +""" +from scheduler import KubeHTTPException +from scheduler.tests import TestCase +from scheduler.utils import generate_random_name + + +class PodsTest(TestCase): + """Tests scheduler pod calls""" + + def create(self, namespace=None, name=generate_random_name(), **kwargs): + """ + Helper function to create and verify a pod on the namespace + """ + namespace = self.namespace if namespace is None else namespace + # these are all required even if it is kwargs... + kwargs = { + 'app_type': kwargs.get('app_type', 'web'), + 'version': kwargs.get('version', 'v99'), + 'replicas': kwargs.get('replicas', 4), + 'pod_termination_grace_period_seconds': 2, + 'image': 'quay.io/fake/image', + 'entrypoint': 'sh', + 'command': 'start', + 'deploy_timeout': 10, + } + + pod = self.scheduler.pod.create(namespace, name, **kwargs) + self.assertEqual(pod.status_code, 201, pod.json()) + return name + + def test_create_failure(self): + with self.assertRaises( + KubeHTTPException, + msg='failed to create Pod doesnotexist in Namespace {}: 404 Not Found'.format(self.namespace) # noqa + ): + self.create('doesnotexist', 'doesnotexist') + + def test_create(self): + self.create() + + def test_delete_failure(self): + # test failure + with self.assertRaises( + KubeHTTPException, + msg='failed to delete Pod foo in Namespace {}: 404 Not Found'.format(self.namespace) # noqa + ): + self.scheduler.pod.delete(self.namespace, 'foo') + + def test_delete(self): + # test success + name = self.create() + response = self.scheduler.pod.delete(self.namespace, name) + data = response.json() + self.assertEqual(response.status_code, 200, data) + + def test_get_pods(self): + # test success + name = self.create() + response = self.scheduler.pod.get(self.namespace) + data = response.json() + self.assertEqual(response.status_code, 200, data) + self.assertIn('items', data) + self.assertEqual(1, len(data['items']), data['items']) + # simple verify of data + self.assertEqual(data['items'][0]['metadata']['name'], name, data) + + def test_get_pod_failure(self): + # test failure + with self.assertRaises( + KubeHTTPException, + msg='failed to get Pod doesnotexist in Namespace {}: 404 Not Found'.format(self.namespace) # noqa + ): + self.scheduler.pod.get(self.namespace, 'doesnotexist') + + def test_get_pod(self): + # test success + name = self.create() + response = self.scheduler.pod.get(self.namespace, name) + data = response.json() + self.assertEqual(response.status_code, 200, data) + self.assertEqual(data['kind'], 'Pod') + self.assertEqual(data['metadata']['name'], name) + self.assertDictContainsSubset( + { + 'app': self.namespace, + 'heritage': 'deis' + }, + data['metadata']['labels'] + ) diff --git a/rootfs/scheduler/tests/test_replicationcontrollers.py b/rootfs/scheduler/tests/test_replicationcontrollers.py index d018bb01d..39c9125c5 100644 --- a/rootfs/scheduler/tests/test_replicationcontrollers.py +++ b/rootfs/scheduler/tests/test_replicationcontrollers.py @@ -22,10 +22,12 @@ def create(self, namespace=None, name=generate_random_name(), **kwargs): 'version': kwargs.get('version', 'v99'), 'replicas': kwargs.get('replicas', 4), 'pod_termination_grace_period_seconds': 2, + 'image': 'quay.io/fake/image', + 'entrypoint': 'sh', + 'command': 'start', } - rc = self.scheduler.rc.create(namespace, name, 'quay.io/fake/image', - 'sh', 'start', **kwargs) + rc = self.scheduler.rc.create(namespace, name, **kwargs) data = rc.json() self.assertEqual(rc.status_code, 201, data) return name @@ -42,9 +44,12 @@ def scale_rc(self, namespace=None, name=generate_random_name(), **kwargs): 'replicas': kwargs.get('replicas', 4), 'deploy_timeout': 120, 'pod_termination_grace_period_seconds': 2, + 'image': 'quay.io/fake/image', + 'entrypoint': 'sh', + 'command': 'start', } - self.scheduler.scale_rc(namespace, name, 'quay.io/fake/image', 'sh', 'start', **kwargs) + self.scheduler.scale_rc(namespace, name, **kwargs) return name def test_create_failure(self):