From e875d888134a50b9f4d01bcf1b4df14a4f91cddb Mon Sep 17 00:00:00 2001 From: Angus Salkeld Date: Mon, 23 Jun 2014 10:42:09 +1000 Subject: [PATCH] Add the basic pipeline commands Change-Id: I699e60a261c3c492706d8b5918f55d28b1bba10d --- solumclient/solum.py | 61 +++++++++ solumclient/tests/test_solum.py | 66 +++++++++- solumclient/tests/v1/test_pipeline.py | 183 ++++++++++++++++++++++++++ solumclient/v1/client.py | 2 + solumclient/v1/pipeline.py | 55 ++++++++ 5 files changed, 365 insertions(+), 2 deletions(-) create mode 100644 solumclient/tests/v1/test_pipeline.py create mode 100644 solumclient/v1/pipeline.py diff --git a/solumclient/solum.py b/solumclient/solum.py index 02b510b..81916e2 100644 --- a/solumclient/solum.py +++ b/solumclient/solum.py @@ -50,6 +50,7 @@ from solumclient.common import cli_utils from solumclient.openstack.common import cliutils from solumclient.openstack.common import strutils from solumclient.v1 import assembly as cli_assem +from solumclient.v1 import pipeline as cli_pipe from solumclient.v1 import plan as cli_plan @@ -181,6 +182,65 @@ class ComponentCommands(cli_utils.CommandsBase): cliutils.print_list(response, fields) +class PipelineCommands(cli_utils.CommandsBase): + """Pipeline targets.""" + + def create(self): + """Create a pipeline.""" + self.parser.add_argument('plan_uri', + help="Tenant/project-wide unique " + "plan (uri/uuid or name)") + self.parser.add_argument('workbook_name', + help="Workbook name") + self.parser.add_argument('name', + help="Pipeline name") + args = self.parser.parse_args() + plan_uri = args.plan_uri + if '/' not in plan_uri: + # might be a plan uuid/name + # let's try and be helpful and get the real plan_uri. + plan = self.client.plans.find(name_or_id=args.plan_uri) + plan_uri = plan.uri + print('Note: using plan_uri=%s' % plan_uri) + + pipeline = self.client.pipelines.create( + name=args.name, + plan_uri=plan_uri, + workbook_name=args.workbook_name) + fields = ['uuid', 'name', 'description', + 'trigger_uri'] + data = dict([(f, getattr(pipeline, f, '')) + for f in fields]) + cliutils.print_dict(data, wrap=72) + + def delete(self): + """Delete an pipeline.""" + self.parser.add_argument('pipeline_uuid', + help="Pipeline uuid or name") + args = self.parser.parse_args() + pipeline = self.client.pipelines.find(name_or_id=args.pipeline_uuid) + cli_pipe.PipelineManager(self.client).delete( + pipeline_id=str(pipeline.uuid)) + + def list(self): + """List all pipelines.""" + fields = ['uuid', 'name', 'description'] + response = self.client.pipelines.list() + cliutils.print_list(response, fields) + + def show(self): + """Show a pipeline's resource.""" + self.parser.add_argument('pipeline_uuid', + help="Pipeline uuid or name") + args = self.parser.parse_args() + response = self.client.pipelines.find(name_or_id=args.pipeline_uuid) + fields = ['uuid', 'name', 'description', + 'trigger_uri'] + data = dict([(f, getattr(response, f, '')) + for f in fields]) + cliutils.print_dict(data, wrap=72) + + class LanguagePackCommands(cli_utils.CommandsBase): """Language Pack targets.""" @@ -239,6 +299,7 @@ def main(): resources = { 'app': AppCommands, 'assembly': AssemblyCommands, + 'pipeline': PipelineCommands, 'languagepack': LanguagePackCommands, 'component': ComponentCommands } diff --git a/solumclient/tests/test_solum.py b/solumclient/tests/test_solum.py index 3539f0d..722e999 100644 --- a/solumclient/tests/test_solum.py +++ b/solumclient/tests/test_solum.py @@ -31,6 +31,7 @@ from solumclient.tests import base from solumclient.v1 import assembly from solumclient.v1 import component from solumclient.v1 import languagepack +from solumclient.v1 import pipeline from solumclient.v1 import plan FAKE_ENV = {'OS_USERNAME': 'username', @@ -77,7 +78,7 @@ class TestSolum(base.TestCase): self.useFixture(fixtures.MonkeyPatch('os.environ', env)) @mock.patch.object(extension.ExtensionManager, "map") - def shell(self, argstr, mock_mgr_map): + def shell(self, argstr, mock_mgr_map, exit_code=0): class FakePlugin(BaseFakePlugin): def authenticate(self, cls): cls.request( @@ -97,7 +98,7 @@ class TestSolum(base.TestCase): solum.main() except SystemExit: exc_type, exc_value, exc_traceback = sys.exc_info() - self.assertEqual(0, exc_value.code) + self.assertEqual(exit_code, exc_value.code) finally: out = sys.stdout.getvalue() sys.stdout.close() @@ -179,6 +180,67 @@ class TestSolum(base.TestCase): self.shell("assembly show app2") mock_assembly_find.assert_called_once_with(name_or_id='app2') + # Pipeline Tests # + @mock.patch.object(pipeline.PipelineManager, "list") + def test_pipeline_list(self, mock_pipeline_list): + self.make_env() + self.shell("pipeline list") + mock_pipeline_list.assert_called_once_with() + + @mock.patch.object(pipeline.PipelineManager, "create") + def test_pipeline_create(self, mock_pipeline_create): + self.make_env() + self.shell("pipeline create http://example.com/a.yaml workbook test") + mock_pipeline_create.assert_called_once_with( + name='test', + workbook_name='workbook', + plan_uri='http://example.com/a.yaml') + + @mock.patch.object(pipeline.PipelineManager, "create") + def test_pipeline_create_without_name(self, mock_pipeline_create): + self.make_env() + self.shell("pipeline create http://example.com/a.yaml workbook", + exit_code=2) + + @mock.patch.object(plan.PlanManager, "find") + @mock.patch.object(pipeline.PipelineManager, "create") + def test_pipeline_create_with_plan_name(self, mock_pipeline_create, + mock_app_find): + class FakePlan(object): + uri = 'http://example.com/the-plan.yaml' + + self.make_env() + mock_app_find.return_value = FakePlan() + self.shell("pipeline create the-plan-name workbook test") + mock_app_find.assert_called_once_with(name_or_id='the-plan-name') + mock_pipeline_create.assert_called_once_with( + name='test', + workbook_name='workbook', + plan_uri='http://example.com/the-plan.yaml') + + @mock.patch.object(pipeline.PipelineManager, "delete") + @mock.patch.object(pipeline.PipelineManager, "find") + def test_pipeline_delete(self, mock_pipeline_find, mock_pipeline_delete): + self.make_env() + the_id = str(uuid.uuid4()) + self.shell("pipeline delete %s" % the_id) + mock_pipeline_find.assert_called_once_with( + name_or_id=the_id) + mock_pipeline_delete.assert_called_once() + + @mock.patch.object(pipeline.PipelineManager, "find") + def test_pipeline_get(self, mock_pipeline_find): + self.make_env() + the_id = str(uuid.uuid4()) + self.shell("pipeline show %s" % the_id) + mock_pipeline_find.assert_called_once_with(name_or_id=the_id) + + @mock.patch.object(pipeline.PipelineManager, "find") + def test_pipeline_get_by_name(self, mock_pipeline_find): + self.make_env() + self.shell("pipeline show app2") + mock_pipeline_find.assert_called_once_with(name_or_id='app2') + # Plan Tests # @mock.patch.object(cliutils, "print_dict") @mock.patch.object(plan.PlanManager, "create") diff --git a/solumclient/tests/v1/test_pipeline.py b/solumclient/tests/v1/test_pipeline.py new file mode 100644 index 0000000..43d6901 --- /dev/null +++ b/solumclient/tests/v1/test_pipeline.py @@ -0,0 +1,183 @@ +# Copyright 2014 - Rackspace Hosting +# +# 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 solumclient.openstack.common.apiclient import exceptions +from solumclient.openstack.common.apiclient import fake_client +from solumclient.tests import base +from solumclient.v1 import client as sclient +from solumclient.v1 import pipeline + +pipeline_list = [ + { + 'uri': 'http://example.com/v1/pipelines/x1', + 'name': 'database', + 'type': 'pipeline', + 'description': 'A mysql database', + 'tags': ['small'], + 'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94', + 'user_id': '55f41cf46df74320b9486a35f5d28a11', + 'component_links': [{ + 'href': 'http://example.com:9777/v1/components/x1', + 'target_name': 'x1'}], + 'operations_uri': 'http://example.com:9777/v1/operations/o1', + 'sensors_uri': 'http://example.com:9777/v1/sensors/s1' + }, + { + 'uri': 'http://example.com/v1/pipelines/x2', + 'name': 'load_balancer', + 'type': 'pipeline', + 'description': 'A load balancer', + 'tags': ['small'], + 'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94', + 'user_id': '55f41cf46df74320b9486a35f5d28a11', + 'component_links': [{ + 'href': 'http://example.com:9777/v1/components/x2', + 'target_name': 'x2'}], + 'operations_uri': 'http://example.com:9777/v1/operations/o2', + 'sensors_uri': 'http://example.com:9777/v1/sensors/s2' + } +] + +pipeline_fixture = { + 'uri': 'http://example.com/v1/pipelines/x1', + 'name': 'database', + 'type': 'pipeline', + 'description': 'A mysql database', + 'tags': ['small'], + 'project_id': '1dae5a09ef2b4d8cbf3594b0eb4f6b94', + 'user_id': '55f41cf46df74320b9486a35f5d28a11', + 'component_links': [{ + 'href': 'http://example.com:9777/v1/components/x1', + 'target_name': 'x1'}], + 'operations_uri': 'http://example.com:9777/v1/operations/o1', + 'sensors_uri': 'http://example.com:9777/v1/sensors/s1' +} + +fixtures_list = { + '/v1/pipelines': { + 'GET': ( + {}, + pipeline_list + ), + } +} + + +fixtures_get = { + '/v1/pipelines/x1': { + 'GET': ( + {}, + pipeline_fixture + ), + } +} + + +fixtures_create = { + '/v1/pipelines': { + 'POST': ( + {}, + pipeline_fixture + ), + } +} + +fixtures_put = { + '/v1/pipelines/x1': { + 'PUT': ( + {}, + pipeline_fixture + ), + } +} + +fixtures_delete = { + '/v1/pipelines/x1': { + 'DELETE': ( + {}, + {}, + ), + } +} + + +class PipelineManagerTest(base.TestCase): + + def assert_pipeline_object(self, pipeline_obj): + self.assertIn('Pipeline', repr(pipeline_obj)) + self.assertEqual(pipeline_fixture['uri'], pipeline_obj.uri) + self.assertEqual(pipeline_fixture['type'], pipeline_obj.type) + self.assertEqual(pipeline_fixture['project_id'], + pipeline_obj.project_id) + self.assertEqual(pipeline_fixture['user_id'], pipeline_obj.user_id) + + def test_list_all(self): + fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list) + api_client = sclient.Client(fake_http_client) + mgr = pipeline.PipelineManager(api_client) + pipelines = mgr.list() + self.assertEqual(len(pipelines), 2) + self.assertIn('Pipeline', repr(pipelines[0])) + self.assertEqual(pipeline_list[0]['uri'], pipelines[0].uri) + self.assertEqual(pipeline_list[1]['uri'], pipelines[1].uri) + + def test_find_one(self): + fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list) + api_client = sclient.Client(fake_http_client) + mgr = pipeline.PipelineManager(api_client) + pipelines = mgr.findall(name='database') + self.assertEqual(len(pipelines), 1) + self.assertIn('Pipeline', repr(pipelines[0])) + self.assertEqual(pipeline_list[0]['uri'], pipelines[0].uri) + + def test_find_one_only(self): + fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list) + api_client = sclient.Client(fake_http_client) + mgr = pipeline.PipelineManager(api_client) + result = mgr.find(name_or_id='database') + self.assertEqual(pipeline_list[0]['uri'], result.uri) + + def test_find_none(self): + fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_list) + api_client = sclient.Client(fake_http_client) + mgr = pipeline.PipelineManager(api_client) + self.assertRaises(exceptions.NotFound, mgr.find, name_or_id='what') + + def test_create(self): + fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_create) + api_client = sclient.Client(fake_http_client) + mgr = pipeline.PipelineManager(api_client) + pipeline_obj = mgr.create() + self.assert_pipeline_object(pipeline_obj) + + def test_get(self): + fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_get) + api_client = sclient.Client(fake_http_client) + mgr = pipeline.PipelineManager(api_client) + pipeline_obj = mgr.get(pipeline_id='x1') + self.assert_pipeline_object(pipeline_obj) + + def test_put(self): + fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_put) + api_client = sclient.Client(fake_http_client) + mgr = pipeline.PipelineManager(api_client) + pipeline_obj = mgr.put(pipeline_id='x1') + self.assert_pipeline_object(pipeline_obj) + + def test_delete(self): + fake_http_client = fake_client.FakeHTTPClient(fixtures=fixtures_delete) + api_client = sclient.Client(fake_http_client) + mgr = pipeline.PipelineManager(api_client) + mgr.delete(pipeline_id='x1') + fake_http_client.assert_called('DELETE', '/v1/pipelines/x1') diff --git a/solumclient/v1/client.py b/solumclient/v1/client.py index e219c59..163c1fb 100644 --- a/solumclient/v1/client.py +++ b/solumclient/v1/client.py @@ -16,6 +16,7 @@ from solumclient.openstack.common.apiclient import client from solumclient.v1 import assembly from solumclient.v1 import component from solumclient.v1 import languagepack +from solumclient.v1 import pipeline from solumclient.v1 import plan from solumclient.v1 import platform @@ -30,6 +31,7 @@ class Client(client.BaseClient): super(Client, self).__init__(http_client, extensions) self.assemblies = assembly.AssemblyManager(self) self.components = component.ComponentManager(self) + self.pipelines = pipeline.PipelineManager(self) self.platform = platform.PlatformManager(self) self.plans = plan.PlanManager(self) self.languagepacks = languagepack.LanguagePackManager(self) diff --git a/solumclient/v1/pipeline.py b/solumclient/v1/pipeline.py new file mode 100644 index 0000000..7c3b3af --- /dev/null +++ b/solumclient/v1/pipeline.py @@ -0,0 +1,55 @@ +# Copyright 2014 - Rackspace Hosting +# +# 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 solumclient.common import base as solum_base +from solumclient.openstack.common.apiclient import base as apiclient_base +from solumclient.openstack.common import uuidutils + + +class Pipeline(apiclient_base.Resource): + def __repr__(self): + return "" % self._info + + +class PipelineManager(solum_base.CrudManager, solum_base.FindMixin): + resource_class = Pipeline + collection_key = 'pipelines' + key = 'pipeline' + + def list(self, **kwargs): + return super(PipelineManager, self).list(base_url="/v1", **kwargs) + + def create(self, **kwargs): + return super(PipelineManager, self).create(base_url="/v1", **kwargs) + + def get(self, **kwargs): + return super(PipelineManager, self).get(base_url="/v1", **kwargs) + + def put(self, **kwargs): + return super(PipelineManager, self).put(base_url="/v1", **kwargs) + + def delete(self, **kwargs): + return super(PipelineManager, self).delete(base_url="/v1", **kwargs) + + def find(self, **kwargs): + if 'pipeline_id' in kwargs: + return super(PipelineManager, self).get(base_url="/v1", **kwargs) + elif 'name_or_id' in kwargs: + name_or_uuid = kwargs['name_or_id'] + if uuidutils.is_uuid_like(name_or_uuid): + return super(PipelineManager, self).get( + base_url="/v1", + pipeline_id=name_or_uuid) + else: + return super(PipelineManager, self).findone(name=name_or_uuid)