diff --git a/setup.cfg b/setup.cfg index 1b16447..eaaa64b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -68,6 +68,8 @@ tuning_box.cli = def_show = tuning_box.cli.resource_definitions:ShowResourceDefinition def_delete = tuning_box.cli.resource_definitions:DeleteResourceDefinition def_update = tuning_box.cli.resource_definitions:UpdateResourceDefinition + lvl_list = tuning_box.cli.hierarchy_levels:ListHierarchyLevels + lvl_show = tuning_box.cli.hierarchy_levels:ShowHierarchyLevel fuelclient = config_get = tuning_box.fuelclient:Get config_set = tuning_box.fuelclient:Set @@ -89,6 +91,8 @@ fuelclient = config_def_show = tuning_box.fuelclient:ShowResourceDefinition config_def_delete = tuning_box.fuelclient:DeleteResourceDefinition config_def_update = tuning_box.fuelclient:UpdateResourceDefinition + config_lvl_list = tuning_box.fuelclient:ListHierarchyLevels + config_lvl_show = tuning_box.fuelclient:ShowHierarchyLevel console_scripts = tuningbox_db_upgrade = tuning_box.migration:upgrade tuningbox_db_downgrade = tuning_box.migration:downgrade diff --git a/tuning_box/app.py b/tuning_box/app.py index 1acd654..11f6714 100644 --- a/tuning_box/app.py +++ b/tuning_box/app.py @@ -95,7 +95,7 @@ api.add_resource( api.add_resource( hierarchy_levels.EnvironmentHierarchyLevels, '/environments//hierarchy_levels/' - '' + '' ) diff --git a/tuning_box/cli/base.py b/tuning_box/cli/base.py index 0c62187..4158f4a 100644 --- a/tuning_box/cli/base.py +++ b/tuning_box/cli/base.py @@ -99,7 +99,7 @@ class BaseOneCommand(BaseCommand): parser.add_argument( 'id', type=int, - help='Id of the {0} to delete.'.format(self.entity_name)) + help='Id of the {0}'.format(self.entity_name)) return parser def get_url(self, parsed_args): diff --git a/tuning_box/cli/hierarchy_levels.py b/tuning_box/cli/hierarchy_levels.py new file mode 100644 index 0000000..1c473ee --- /dev/null +++ b/tuning_box/cli/hierarchy_levels.py @@ -0,0 +1,71 @@ +# 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 cliff import lister +from cliff import show +from fuelclient.cli import error as fc_error +from fuelclient.common import data_utils + +from tuning_box.cli import base + + +class HierarchyLevelsCommand(base.BaseCommand): + columns = ('id', 'name', 'parent', 'values') + + def get_parser(self, *args, **kwargs): + parser = super(HierarchyLevelsCommand, self).get_parser( + *args, **kwargs) + parser.add_argument( + '-e', '--env', + type=int, + required=True, + help="ID of environment to get data from", + ) + return parser + + def get_base_url(self, parsed_args): + return '/environments/{}/hierarchy_levels'.format(parsed_args.env) + + +class ListHierarchyLevels(HierarchyLevelsCommand, lister.Lister): + + def take_action(self, parsed_args): + result = self.get_client().get(self.get_base_url(parsed_args)) + try: + data = data_utils.get_display_data_multi(self.columns, result) + return self.columns, data + except fc_error.BadDataException: + return zip(*result.items()) + + +class ShowHierarchyLevel(HierarchyLevelsCommand, show.ShowOne): + + def get_parser(self, *args, **kwargs): + parser = super(ShowHierarchyLevel, self).get_parser(*args, **kwargs) + parser.add_argument( + 'name', + type=str, + help='Hierarchy level name' + ) + return parser + + def get_url(self, parsed_args): + base_url = self.get_base_url(parsed_args) + return base_url + '/{0}'.format(parsed_args.name) + + def take_action(self, parsed_args): + result = self.get_client().get(self.get_url(parsed_args)) + try: + data = data_utils.get_display_data_single(self.columns, result) + return self.columns, data + except fc_error.BadDataException: + return zip(*result.items()) diff --git a/tuning_box/db.py b/tuning_box/db.py index 000728e..6ee5f49 100644 --- a/tuning_box/db.py +++ b/tuning_box/db.py @@ -196,6 +196,8 @@ class EnvironmentHierarchyLevel(ModelMixin, db.Model): env_levels.append(env_levels[-1].child) return env_levels + values = db.relationship('EnvironmentHierarchyLevelValue') + class EnvironmentHierarchyLevelValue(ModelMixin, db.Model): level_id = fk(EnvironmentHierarchyLevel, ondelete='CASCADE') diff --git a/tuning_box/fuelclient.py b/tuning_box/fuelclient.py index 6d26972..757e80d 100644 --- a/tuning_box/fuelclient.py +++ b/tuning_box/fuelclient.py @@ -19,6 +19,7 @@ from tuning_box import cli from tuning_box.cli import base as cli_base from tuning_box.cli import components from tuning_box.cli import environments +from tuning_box.cli import hierarchy_levels from tuning_box.cli import resource_definitions from tuning_box.cli import resources from tuning_box import client as tb_client @@ -147,6 +148,20 @@ class UpdateResourceDefinition( pass +class ListHierarchyLevels( + FuelBaseCommand, + hierarchy_levels.ListHierarchyLevels +): + pass + + +class ShowHierarchyLevel( + FuelBaseCommand, + hierarchy_levels.ShowHierarchyLevel +): + pass + + class Config(command.Command): def get_parser(self, *args, **kwargs): parser = super(Config, self).get_parser(*args, **kwargs) diff --git a/tuning_box/library/hierarchy_levels.py b/tuning_box/library/hierarchy_levels.py index 30e9ac8..3660d9c 100644 --- a/tuning_box/library/hierarchy_levels.py +++ b/tuning_box/library/hierarchy_levels.py @@ -60,9 +60,11 @@ def get_environment_level_value(environment, levels): environment_hierarchy_level_fields = { + 'id': fields.Integer, 'name': fields.String, 'environment_id': fields.Integer, - 'parent': fields.String(attribute='parent.name') + 'parent': fields.String(attribute='parent.name'), + 'values': fields.List(fields.String(attribute='value')) } @@ -81,22 +83,28 @@ class EnvironmentHierarchyLevels(flask_restful.Resource): flask_restful.marshal_with(environment_hierarchy_level_fields) ] - def get(self, environment_id, level): - level = db.find_or_404(db.EnvironmentHierarchyLevel, - environment_id=environment_id, - name=level) + def _get_query_params(self, environment_id, id_or_name): + params = {'environment_id': environment_id} + if isinstance(id_or_name, int): + params['id'] = id_or_name + else: + params['name'] = id_or_name + return params + + def get(self, environment_id, id_or_name): + params = self._get_query_params(environment_id, id_or_name) + level = db.find_or_404(db.EnvironmentHierarchyLevel, **params) return level @db.with_transaction - def _do_update(self, environment_id, level): - level = db.find_or_404(db.EnvironmentHierarchyLevel, - environment_id=environment_id, - name=level) + def _do_update(self, environment_id, id_or_name): + params = self._get_query_params(environment_id, id_or_name) + level = db.find_or_404(db.EnvironmentHierarchyLevel, **params) level.name = flask.request.json.get('name', level.name) - def put(self, environment_id, level): - return self.patch(environment_id, level) + def put(self, environment_id, id_or_name): + return self.patch(environment_id, id_or_name) - def patch(self, environment_id, level): - self._do_update(environment_id, level) + def patch(self, environment_id, id_or_name): + self._do_update(environment_id, id_or_name) return None, 204 diff --git a/tuning_box/tests/cli/test_hierarchy_levels.py b/tuning_box/tests/cli/test_hierarchy_levels.py new file mode 100644 index 0000000..29c8b9d --- /dev/null +++ b/tuning_box/tests/cli/test_hierarchy_levels.py @@ -0,0 +1,67 @@ +# 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. + +import testscenarios + +from tuning_box.tests.cli import _BaseCLITest + + +class TestHierarchyLevels(testscenarios.WithScenarios, _BaseCLITest): + + scenarios = [ + (s[0], dict(zip(('mock_url', 'args', 'expected_result'), s[1]))) + for s in [ + ('json', ('/environments/9/hierarchy_levels', + 'lvl list -e 9 -f json', '[]')), + ('yaml', ('/environments/9/hierarchy_levels', + 'lvl list -e 9 -f yaml', '[]\n')) + ] + ] + mock_url = None + args = None + expected_result = None + + def test_get(self): + self.req_mock.get( + self.BASE_URL + self.mock_url, + headers={'Content-Type': 'application/json'}, + json={}, + ) + self.cli.run(self.args.split()) + self.assertEqual(self.expected_result, self.cli.stdout.getvalue()) + + +class TestShowHierarchyLevel(testscenarios.WithScenarios, _BaseCLITest): + + scenarios = [ + (s[0], dict(zip(('mock_url', 'args', 'expected_result'), s[1]))) + for s in [ + ('json', ('/environments/9/hierarchy_levels/n', + 'lvl show -e 9 -f json -c id n', + '{\n "id": 1\n}')), + ('yaml', ('/environments/9/hierarchy_levels/nn', + 'lvl show -e 9 -f yaml -c id nn', + 'id: 1\n')) + ] + ] + mock_url = None + args = None + expected_result = None + + def test_get(self): + self.req_mock.get( + self.BASE_URL + self.mock_url, + headers={'Content-Type': 'application/json'}, + json={'id': 1}, + ) + self.cli.run(self.args.split()) + self.assertEqual(self.expected_result, self.cli.stdout.getvalue()) diff --git a/tuning_box/tests/library/test_hierarchy_levels.py b/tuning_box/tests/library/test_hierarchy_levels.py index de3ef63..30f7e34 100644 --- a/tuning_box/tests/library/test_hierarchy_levels.py +++ b/tuning_box/tests/library/test_hierarchy_levels.py @@ -47,6 +47,31 @@ class TestLevelsHierarchy(BaseTest): self.assertEqual(level.name, 'lvl1') self.assertIsNone(level.parent) + def test_get_environment_level_values(self): + self._fixture() + env_id = 9 + with self.app.app_context(), db.db.session.begin(): + # Creating level values + hierarchy_levels.get_environment_level_value( + db.Environment(id=env_id), + [('lvl1', 'val11'), ('lvl2', 'val21')], + ) + hierarchy_levels.get_environment_level_value( + db.Environment(id=env_id), + [('lvl1', 'val11'), ('lvl2', 'val22')], + ) + hierarchy_levels.get_environment_level_value( + db.Environment(id=env_id), + [('lvl1', 'val12'), ('lvl2', 'val23')], + ) + + res = self.client.get(self.collection_url.format(9)) + lvl1 = res.json[0] + self.assertItemsEqual(['val11', 'val12'], lvl1['values']) + + lvl2 = res.json[1] + self.assertItemsEqual(['val21', 'val22', 'val23'], lvl2['values']) + def test_get_environment_level_value_bad_level(self): self._fixture() with self.app.app_context(), db.db.session.begin(): @@ -84,6 +109,11 @@ class TestLevelsHierarchy(BaseTest): self.assertEqual(200, res.status_code) self.assertEqual(level, res.json['name']) + res = self.client.get(self.object_url.format(environment_id, + res.json['id'])) + self.assertEqual(200, res.status_code) + self.assertEqual(level, res.json['name']) + def test_get_hierarchy_level_not_found(self): levels = ['lvl1', 'lvl2'] for level in levels: