From a16351d3524aa6754bf9570e9f53350764617076 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vincent=20Fran=C3=A7oise?= Date: Fri, 15 Jan 2016 15:40:59 +0100 Subject: [PATCH] API Tempest tests on Action plans This patchset adds CRUD tests on Action Plans via the API. Partially Implements: blueprint tempest-basic-set-up Change-Id: I8ff3c3f0dbf7d301be2e3f401edf24bca44914bd --- .../services/infra_optim/v1/json/client.py | 49 +++++- .../tests/api/admin/base.py | 43 ++++- .../tests/api/admin/test_action_plan.py | 165 ++++++++++++++++++ 3 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 watcher_tempest_plugin/tests/api/admin/test_action_plan.py diff --git a/watcher_tempest_plugin/services/infra_optim/v1/json/client.py b/watcher_tempest_plugin/services/infra_optim/v1/json/client.py index b7bfc9cc1..2e00d3dce 100644 --- a/watcher_tempest_plugin/services/infra_optim/v1/json/client.py +++ b/watcher_tempest_plugin/services/infra_optim/v1/json/client.py @@ -185,4 +185,51 @@ class InfraOptimClientJSON(base.BaseInfraOptimClient): :return: Tuple with the server response and the updated audit """ - return self._patch_request('audits', uuid, patch) + return self._patch_request('audits', audit_uuid, patch) + + # ### ACTION PLANS ### # + + @base.handle_errors + def list_action_plans(self, **kwargs): + """List all existing action plan""" + return self._list_request('action_plans', **kwargs) + + @base.handle_errors + def list_action_plans_detail(self, **kwargs): + """Lists details of all existing action plan""" + return self._list_request('/action_plans/detail', **kwargs) + + @base.handle_errors + def list_action_plan_by_audit(self, audit_uuid): + """Lists all action plans associated with an audit""" + return self._list_request('/action_plans', audit_uuid=audit_uuid) + + @base.handle_errors + def show_action_plan(self, action_plan_uuid): + """Gets a specific action plan + + :param action_plan_uuid: Unique identifier of the action plan + :return: Serialized action plan as a dictionary + """ + return self._show_request('/action_plans', action_plan_uuid) + + @base.handle_errors + def delete_action_plan(self, action_plan_uuid): + """Deletes an action_plan having the specified UUID + + :param action_plan_uuid: The unique identifier of the action_plan + :return: A tuple with the server response and the response body + """ + + return self._delete_request('/action_plans', action_plan_uuid) + + @base.handle_errors + def update_action_plan(self, action_plan_uuid, patch): + """Update the specified action plan + + :param action_plan_uuid: The unique identifier of the action_plan + :param patch: List of dicts representing json patches. + :return: Tuple with the server response and the updated action_plan + """ + + return self._patch_request('/action_plans', action_plan_uuid, patch) diff --git a/watcher_tempest_plugin/tests/api/admin/base.py b/watcher_tempest_plugin/tests/api/admin/base.py index 085220824..878f122b3 100644 --- a/watcher_tempest_plugin/tests/api/admin/base.py +++ b/watcher_tempest_plugin/tests/api/admin/base.py @@ -24,7 +24,7 @@ from watcher_tempest_plugin import infra_optim_clients as clients # Resources must be deleted in a specific order, this list # defines the resource types to clean up, and the correct order. -RESOURCE_TYPES = ['audit_template', 'audit'] +RESOURCE_TYPES = ['audit_template', 'audit', 'action_plan'] # RESOURCE_TYPES = ['action', 'action_plan', 'audit', 'audit_template'] @@ -170,3 +170,44 @@ class BaseInfraOptimTest(test.BaseTestCase): cls.created_objects['audit'].remove(audit_uuid) return resp + + @classmethod + def has_audit_succeeded(cls, audit_uuid): + _, audit = cls.client.show_audit(audit_uuid) + return audit.get('state') == 'SUCCEEDED' + + # ### ACTION PLANS ### # + + @classmethod + @creates('action_plan') + def start_action_plan(cls, audit_uuid, type='ONESHOT', + state='PENDING', deadline=None): + """Wrapper utility for creating a test action plan + + :param audit_uuid: Audit Template UUID this action plan will use + :param type: Audit type (either ONESHOT or CONTINUOUS) + :return: A tuple with The HTTP response and its body + """ + resp, body = cls.client.create_action_plan( + audit_uuid=audit_uuid, type=type, + state=state, deadline=deadline) + return resp, body + + @classmethod + def delete_action_plan(cls, action_plan_uuid): + """Deletes an action plan having the specified UUID + + :param action_plan_uuid: The unique identifier of the action plan. + :return: the HTTP response + """ + resp, body = cls.client.delete_action_plan(action_plan_uuid) + + if action_plan_uuid in cls.created_objects['action_plan']: + cls.created_objects['action_plan'].remove(action_plan_uuid) + + return resp + + @classmethod + def has_action_plan_finished(cls, action_plan_uuid): + _, action_plan = cls.client.show_action_plan(action_plan_uuid) + return action_plan.get('state') in ('FAILED', 'SUCCEEDED', 'CANCELLED') diff --git a/watcher_tempest_plugin/tests/api/admin/test_action_plan.py b/watcher_tempest_plugin/tests/api/admin/test_action_plan.py new file mode 100644 index 000000000..88408edba --- /dev/null +++ b/watcher_tempest_plugin/tests/api/admin/test_action_plan.py @@ -0,0 +1,165 @@ +# -*- encoding: utf-8 -*- +# Copyright (c) 2016 b<>com +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from __future__ import unicode_literals + +import functools + +from tempest import test +from tempest_lib import exceptions as lib_exc + +from watcher_tempest_plugin.tests.api.admin import base + + +class TestCreateDeleteExecuteActionPlan(base.BaseInfraOptimTest): + """Tests for action plans""" + + @test.attr(type='smoke') + def test_create_action_plan(self): + _, audit_template = self.create_audit_template() + _, audit = self.create_audit(audit_template['uuid']) + + self.assertTrue(test.call_until_true( + func=functools.partial(self.has_audit_succeeded, audit['uuid']), + duration=10, + sleep_for=.5 + )) + _, action_plans = self.client.list_action_plan_by_audit(audit['uuid']) + action_plan = action_plans['action_plans'][0] + + _, action_plan = self.client.show_action_plan(action_plan['uuid']) + + self.assertEqual(action_plan['audit_uuid'], audit['uuid']) + self.assertEqual(action_plan['state'], 'RECOMMENDED') + + @test.attr(type='smoke') + def test_delete_action_plan(self): + _, audit_template = self.create_audit_template() + _, audit = self.create_audit(audit_template['uuid']) + + self.assertTrue(test.call_until_true( + func=functools.partial(self.has_audit_succeeded, audit['uuid']), + duration=10, + sleep_for=.5 + )) + _, action_plans = self.client.list_action_plan_by_audit(audit['uuid']) + action_plan = action_plans['action_plans'][0] + + _, action_plan = self.client.show_action_plan(action_plan['uuid']) + + self.client.delete_action_plan(action_plan['uuid']) + + self.assertRaises(lib_exc.NotFound, self.client.show_action_plan, + action_plan['uuid']) + + @test.attr(type='smoke') + def test_execute_dummy_action_plan(self): + _, audit_template = self.create_audit_template() + _, audit = self.create_audit(audit_template['uuid']) + + self.assertTrue(test.call_until_true( + func=functools.partial(self.has_audit_succeeded, audit['uuid']), + duration=10, + sleep_for=.5 + )) + _, action_plans = self.client.list_action_plan_by_audit(audit['uuid']) + action_plan = action_plans['action_plans'][0] + + _, action_plan = self.client.show_action_plan(action_plan['uuid']) + + # Execute the action by changing its state to STARTING + _, updated_ap = self.client.update_action_plan( + action_plan['uuid'], + patch=[{'path': '/state', 'op': 'replace', 'value': 'STARTING'}] + ) + + self.assertTrue(test.call_until_true( + func=functools.partial( + self.has_action_plan_finished, action_plan['uuid']), + duration=10, + sleep_for=.5 + )) + _, finished_ap = self.client.show_action_plan(action_plan['uuid']) + + self.assertIn(updated_ap['state'], ('STARTING', 'ONGOING')) + self.assertEqual(finished_ap['state'], 'SUCCEEDED') + + +class TestShowListActionPlan(base.BaseInfraOptimTest): + """Tests for action_plan.""" + + @classmethod + def resource_setup(cls): + super(TestShowListActionPlan, cls).resource_setup() + _, cls.audit_template = cls.create_audit_template() + _, cls.audit = cls.create_audit(cls.audit_template['uuid']) + + assert test.call_until_true( + func=functools.partial(cls.has_audit_succeeded, cls.audit['uuid']), + duration=10, + sleep_for=.5 + ) + _, action_plans = cls.client.list_action_plan_by_audit( + cls.audit['uuid']) + cls.action_plan = action_plans['action_plans'][0] + + @test.attr(type='smoke') + def test_show_action_plan(self): + _, action_plan = self.client.show_action_plan( + self.action_plan['uuid']) + + self.assert_expected(self.action_plan, action_plan) + + @test.attr(type='smoke') + def test_show_action_plan_detail(self): + _, action_plans = self.client.list_action_plans_detail( + audit_uuid=self.audit['uuid']) + + action_plan = action_plans['action_plans'][0] + + self.assert_expected(self.action_plan, action_plan) + + @test.attr(type='smoke') + def test_show_action_plan_with_links(self): + _, action_plan = self.client.show_action_plan( + self.action_plan['uuid']) + self.assertIn('links', action_plan.keys()) + self.assertEqual(2, len(action_plan['links'])) + self.assertIn(action_plan['uuid'], + action_plan['links'][0]['href']) + + @test.attr(type="smoke") + def test_list_action_plans(self): + _, body = self.client.list_action_plans() + self.assertIn(self.action_plan['uuid'], + [i['uuid'] for i in body['action_plans']]) + # Verify self links. + for action_plan in body['action_plans']: + self.validate_self_link('action_plans', action_plan['uuid'], + action_plan['links'][0]['href']) + + @test.attr(type='smoke') + def test_list_with_limit(self): + # We create 3 extra audits to exceed the limit we fix + for _ in range(3): + self.create_audit(self.audit_template['uuid']) + + _, body = self.client.list_action_plans(limit=3) + + next_marker = body['action_plans'][-1]['uuid'] + + self.assertEqual(len(body['action_plans']), 3) + self.assertIn(next_marker, body['next'])