diff --git a/senlin/engine/service.py b/senlin/engine/service.py index 97f9ad011..16b7025f9 100644 --- a/senlin/engine/service.py +++ b/senlin/engine/service.py @@ -694,6 +694,11 @@ class EngineService(service.Service): def node_list(self, context, cluster_id=None, show_deleted=False, limit=None, marker=None, sort_keys=None, sort_dir=None, filters=None, tenant_safe=True): + + limit = utils.parse_int_param('limit', limit) + tenant_safe = utils.parse_bool_param('tenant_safe', tenant_safe) + show_deleted = utils.parse_bool_param('show_deleted', show_deleted) + # Maybe the cluster_id is a name or a short ID if cluster_id is not None: db_cluster = self.cluster_find(context, cluster_id) @@ -710,18 +715,12 @@ class EngineService(service.Service): @request_context def node_create(self, context, name, profile_id, cluster_id=None, role=None, tags=None): - db_profile = self.profile_find(context, profile_id) + node_profile = self.profile_find(context, profile_id) if cluster_id is not None: db_cluster = self.cluster_find(context, cluster_id) cluster_id = db_cluster.id - if context.project_id != db_cluster.project: - msg = _('Node and cluster are from different project, ' - 'operation is disallowed.') - raise exception.ProjectNotMatch(message=msg) - if profile_id != db_cluster.profile_id: - node_profile = self.profile_find(context, profile_id) cluster_profile = self.profile_find(context, db_cluster.profile_id) if node_profile.type != cluster_profile.type: @@ -733,7 +732,7 @@ class EngineService(service.Service): # Create a node instance tags = tags or {} - node = node_mod.Node(name, db_profile.id, cluster_id, context, + node = node_mod.Node(name, node_profile.id, cluster_id, context, role=role, tags=tags) node.store(context) diff --git a/senlin/tests/common/utils.py b/senlin/tests/common/utils.py index 7eff9c625..508c4545f 100644 --- a/senlin/tests/common/utils.py +++ b/senlin/tests/common/utils.py @@ -70,6 +70,7 @@ def dummy_context(user='test_username', tenant_id='test_tenant_id', roles = roles or [] return context.RequestContext.from_dict({ 'tenant_id': tenant_id, + 'project_id': tenant_id, 'tenant': 'test_tenant', 'username': user, 'user_id': user_id, diff --git a/senlin/tests/engine/test_clusters.py b/senlin/tests/engine/test_clusters.py index cde57f199..b57afe3cd 100644 --- a/senlin/tests/engine/test_clusters.py +++ b/senlin/tests/engine/test_clusters.py @@ -150,7 +150,8 @@ class ClusterTest(base.SenlinTestCase): self.assertIsNotNone(result) self.assertEqual(self.profile['id'], result['profile_id']) - self.eng.cluster_create(self.ctx, 'c-2', 0, self.profile['name']) + result = self.eng.cluster_create(self.ctx, 'c-2', 0, + self.profile['name']) self.assertIsNotNone(result) self.assertEqual(self.profile['id'], result['profile_id']) @@ -176,10 +177,10 @@ class ClusterTest(base.SenlinTestCase): self.assertIsInstance(result, list) names = [c['name'] for c in result] ids = [c['id'] for c in result] - self.assertIn(c1['name'], names[0]) - self.assertIn(c2['name'], names[1]) - self.assertIn(c1['id'], ids[0]) - self.assertIn(c2['id'], ids[1]) + self.assertEqual(c1['name'], names[0]) + self.assertEqual(c2['name'], names[1]) + self.assertEqual(c1['id'], ids[0]) + self.assertEqual(c2['id'], ids[1]) @mock.patch.object(dispatcher, 'notify') def test_cluster_list_with_limit_marker(self, notify): diff --git a/senlin/tests/engine/test_nodes.py b/senlin/tests/engine/test_nodes.py new file mode 100644 index 000000000..f3784d3ef --- /dev/null +++ b/senlin/tests/engine/test_nodes.py @@ -0,0 +1,307 @@ +# 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 mock +from oslo_messaging.rpc import dispatcher as rpc +import six + +from senlin.common import exception +from senlin.db import api as db_api +from senlin.engine.actions import base as action_mod +from senlin.engine import dispatcher +from senlin.engine import environment +from senlin.engine import service +from senlin.tests.common import base +from senlin.tests.common import utils +from senlin.tests import fakes + + +class NodeTest(base.SenlinTestCase): + + def setUp(self): + super(NodeTest, self).setUp() + self.ctx = utils.dummy_context(tenant_id='node_test_tenant') + self.eng = service.EngineService('host-a', 'topic-a') + self.eng.init_tgm() + + self.eng.dispatcher = mock.Mock() + + env = environment.global_env() + env.register_profile('TestProfile', fakes.TestProfile) + + self.profile = self.eng.profile_create( + self.ctx, 'p-test', 'TestProfile', + spec={'INT': 10, 'STR': 'string'}, perm='1111') + + def _verify_action(self, obj, action, name, target, cause, inputs=None): + if inputs is None: + inputs = {} + self.assertEqual(action, obj['action']) + self.assertEqual(name, obj['name']) + self.assertEqual(target, obj['target']) + self.assertEqual(cause, obj['cause']) + self.assertEqual(inputs, obj['inputs']) + + @mock.patch.object(dispatcher, 'notify') + def test_node_create_default(self, notify): + node = self.eng.node_create(self.ctx, 'n-1', self.profile['id']) + self.assertIsNotNone(node) + self.assertEqual('n-1', node['name']) + self.assertEqual(-1, node['index']) + self.assertEqual(self.profile['id'], node['profile_id']) + self.assertIsNone(node['cluster_id']) + self.assertIsNone(node['role']) + self.assertEqual({}, node['tags']) + + action_id = node['action'] + action = db_api.action_get(self.ctx, action_id) + self.assertIsNotNone(action) + self._verify_action(action, 'NODE_CREATE', + 'node_create_%s' % node['id'][:8], + node['id'], + cause=action_mod.CAUSE_RPC) + notify.assert_called_once_with(self.ctx, + self.eng.dispatcher.NEW_ACTION, + None, action_id=action_id) + + def test_node_create_profile_not_found(self): + ex = self.assertRaises(rpc.ExpectedException, + self.eng.node_create, + self.ctx, 'n-1', 'Bogus') + self.assertEqual(exception.ProfileNotFound, ex.exc_info[0]) + + @mock.patch.object(dispatcher, 'notify') + def test_cluster_create_with_role_and_tags(self, notify): + node = self.eng.node_create(self.ctx, 'n-1', self.profile['id'], + role='master', tags={'k': 'v'}) + + self.assertIsNotNone(node) + self.assertEqual('n-1', node['name']) + self.assertEqual('master', node['role']) + self.assertEqual({'k': 'v'}, node['tags']) + + @mock.patch.object(dispatcher, 'notify') + def test_node_create_with_profile_name_or_short_id(self, notify): + node = self.eng.node_create(self.ctx, 'n-1', self.profile['id'][:8]) + self.assertIsNotNone(node) + self.assertEqual(self.profile['id'], node['profile_id']) + + node = self.eng.node_create(self.ctx, 'n-2', self.profile['name']) + self.assertIsNotNone(node) + self.assertEqual(self.profile['id'], node['profile_id']) + + def test_node_create_with_cluster_id_not_found(self): + ex = self.assertRaises(rpc.ExpectedException, + self.eng.node_create, + self.ctx, 'n-1', self.profile['id'], + cluster_id='Bogus') + + self.assertEqual(exception.ClusterNotFound, ex.exc_info[0]) + self.assertEqual("The cluster (Bogus) could not be found.", + six.text_type(ex.exc_info[1])) + + @mock.patch.object(dispatcher, 'notify') + def test_node_create_project_not_match(self, notify): + ctx_cluster = utils.dummy_context(tenant_id='a-different-tenant') + cluster = self.eng.cluster_create(ctx_cluster, 'c-1', 0, + self.profile['id']) + + ex = self.assertRaises(rpc.ExpectedException, + self.eng.node_create, + self.ctx, 'n-1', self.profile['id'], + cluster_id=cluster['id']) + + self.assertEqual(exception.ClusterNotFound, ex.exc_info[0]) + self.assertEqual("The cluster (%s) could not be found." + "" % cluster['id'], + six.text_type(ex.exc_info[1])) + + @mock.patch.object(dispatcher, 'notify') + def test_node_create_profile_type_not_match(self, notify): + env = environment.global_env() + env.register_profile('SecondProfile', fakes.TestProfile) + cluster_profile = self.eng.profile_create( + self.ctx, 'cluster-profile', 'SecondProfile', + spec={'INT': 20, 'STR': 'string'}) + + cluster = self.eng.cluster_create(self.ctx, 'c-1', 0, + cluster_profile['id']) + + ex = self.assertRaises(rpc.ExpectedException, + self.eng.node_create, + self.ctx, 'n-1', self.profile['id'], + cluster_id=cluster['id']) + + self.assertEqual(exception.ProfileTypeNotMatch, ex.exc_info[0]) + self.assertEqual("Node and cluster have different profile type, " + "operation aborted.", + six.text_type(ex.exc_info[1])) + + @mock.patch.object(dispatcher, 'notify') + def test_node_get(self, notify): + node = self.eng.node_create(self.ctx, 'n-1', self.profile['id']) + + for identity in [node['id'], node['id'][:6], 'n-1']: + result = self.eng.node_get(self.ctx, identity) + self.assertIsInstance(result, dict) + self.assertEqual(node['id'], result['id']) + + ex = self.assertRaises(rpc.ExpectedException, + self.eng.node_get, self.ctx, 'Bogus') + self.assertEqual(exception.NodeNotFound, ex.exc_info[0]) + + @mock.patch.object(dispatcher, 'notify') + def test_node_list(self, notify): + node1 = self.eng.node_create(self.ctx, 'n1', self.profile['id']) + node2 = self.eng.node_create(self.ctx, 'n2', self.profile['id']) + result = self.eng.node_list(self.ctx) + + self.assertIsInstance(result, list) + names = [n['name'] for n in result] + ids = [n['id'] for n in result] + self.assertEqual(node1['name'], names[0]) + self.assertEqual(node2['name'], names[1]) + self.assertEqual(node1['id'], ids[0]) + self.assertEqual(node2['id'], ids[1]) + + @mock.patch.object(dispatcher, 'notify') + def test_node_list_with_limit_marker(self, notify): + node1 = self.eng.node_create(self.ctx, 'n1', self.profile['id']) + node2 = self.eng.node_create(self.ctx, 'n2', self.profile['id']) + result = self.eng.node_list(self.ctx, limit=0) + + self.assertEqual(0, len(result)) + result = self.eng.node_list(self.ctx, limit=1) + self.assertEqual(1, len(result)) + result = self.eng.node_list(self.ctx, limit=2) + self.assertEqual(2, len(result)) + result = self.eng.node_list(self.ctx, limit=3) + self.assertEqual(2, len(result)) + + result = self.eng.node_list(self.ctx, marker=node1['id']) + self.assertEqual(1, len(result)) + result = self.eng.node_list(self.ctx, marker=node2['id']) + self.assertEqual(0, len(result)) + + self.eng.node_create(self.ctx, 'n3', self.profile['id']) + + result = self.eng.node_list(self.ctx, limit=1, marker=node1['id']) + self.assertEqual(1, len(result)) + result = self.eng.node_list(self.ctx, limit=2, marker=node1['id']) + self.assertEqual(2, len(result)) + + @mock.patch.object(dispatcher, 'notify') + def test_node_list_with_sort_keys(self, notify): + node1 = self.eng.node_create(self.ctx, 'CC', self.profile['id']) + node2 = self.eng.node_create(self.ctx, 'BB', self.profile['id']) + + # default by created_time + result = self.eng.node_list(self.ctx) + self.assertEqual(node1['id'], result[0]['id']) + self.assertEqual(node2['id'], result[1]['id']) + + # use name for sorting + result = self.eng.node_list(self.ctx, sort_keys=['name']) + self.assertEqual(node2['id'], result[0]['id']) + self.assertEqual(node1['id'], result[1]['id']) + + # unknown keys will be ignored + result = self.eng.node_list(self.ctx, sort_keys=['duang']) + self.assertIsNotNone(result) + + @mock.patch.object(dispatcher, 'notify') + def test_node_list_with_sort_dir(self, notify): + node1 = self.eng.node_create(self.ctx, 'BB', self.profile['id']) + node2 = self.eng.node_create(self.ctx, 'AA', self.profile['id']) + node3 = self.eng.node_create(self.ctx, 'CC', self.profile['id']) + + # default by created_time, ascending + result = self.eng.node_list(self.ctx) + self.assertEqual(node1['id'], result[0]['id']) + self.assertEqual(node2['id'], result[1]['id']) + + # sort by created_time, descending + result = self.eng.node_list(self.ctx, sort_dir='desc') + self.assertEqual(node3['id'], result[0]['id']) + self.assertEqual(node2['id'], result[1]['id']) + + # use name for sorting, descending + result = self.eng.node_list(self.ctx, sort_keys=['name'], + sort_dir='desc') + self.assertEqual(node3['id'], result[0]['id']) + self.assertEqual(node1['id'], result[1]['id']) + + # use permission for sorting + ex = self.assertRaises(ValueError, + self.eng.node_list, self.ctx, + sort_dir='Bogus') + self.assertEqual("Unknown sort direction, must be " + "'desc' or 'asc'", six.text_type(ex)) + + @mock.patch.object(dispatcher, 'notify') + def test_node_list_show_deleted(self, notify): + node = self.eng.node_create(self.ctx, 'n1', self.profile['id']) + result = self.eng.node_list(self.ctx) + self.assertEqual(1, len(result)) + self.assertEqual(node['id'], result[0]['id']) + + db_api.node_delete(self.ctx, node['id']) + + result = self.eng.node_list(self.ctx) + self.assertEqual(0, len(result)) + + result = self.eng.node_list(self.ctx, show_deleted=True) + self.assertEqual(1, len(result)) + self.assertEqual(node['id'], result[0]['id']) + + @mock.patch.object(dispatcher, 'notify') + def test_cluster_list_with_cluster_id(self, notify): + c = self.eng.cluster_create(self.ctx, 'c-1', 0, self.profile['id']) + node = self.eng.node_create(self.ctx, 'n1', self.profile['id'], + cluster_id=c['id']) + + result = self.eng.node_list(self.ctx, cluster_id=c['id']) + self.assertEqual(1, len(result)) + self.assertEqual(node['id'], result[0]['id']) + + @mock.patch.object(dispatcher, 'notify') + def test_node_list_with_filters(self, notify): + self.eng.node_create(self.ctx, 'BB', self.profile['id']) + self.eng.node_create(self.ctx, 'AA', self.profile['id']) + self.eng.node_create(self.ctx, 'CC', self.profile['id']) + + result = self.eng.node_list(self.ctx, filters={'name': 'BB'}) + self.assertEqual(1, len(result)) + self.assertEqual('BB', result[0]['name']) + + result = self.eng.node_list(self.ctx, filters={'name': 'DD'}) + self.assertEqual(0, len(result)) + + def test_cluster_list_bad_param(self): + ex = self.assertRaises(rpc.ExpectedException, + self.eng.cluster_list, self.ctx, limit='no') + self.assertEqual(exception.InvalidParameter, ex.exc_info[0]) + + ex = self.assertRaises(rpc.ExpectedException, + self.eng.cluster_list, self.ctx, + show_deleted='no') + self.assertEqual(exception.InvalidParameter, ex.exc_info[0]) + + ex = self.assertRaises(rpc.ExpectedException, + self.eng.cluster_list, self.ctx, + tenant_safe='no') + self.assertEqual(exception.InvalidParameter, ex.exc_info[0]) + + def test_cluster_list_empty(self): + result = self.eng.node_list(self.ctx) + self.assertIsInstance(result, list) + self.assertEqual(0, len(result))