# Copyright 2015 IBM Corp. # # 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 copy import mock from oslo_config import cfg import six from heat.common import exception from heat.common import template_format from heat.engine.clients.os import senlin from heat.engine.resources.openstack.senlin import cluster as sc from heat.engine import scheduler from heat.engine import template from heat.tests import common from heat.tests import utils from openstack import exceptions cluster_stack_template = """ heat_template_version: 2016-04-08 description: Senlin Cluster Template resources: senlin-cluster: type: OS::Senlin::Cluster properties: name: SenlinCluster profile: fake_profile policies: - policy: fake_policy enabled: true min_size: 0 max_size: -1 desired_capacity: 1 timeout: 3600 metadata: foo: bar """ class FakeCluster(object): def __init__(self, id='some_id', status='ACTIVE'): self.status = status self.status_reason = 'Unknown' self.id = id self.name = "SenlinCluster" self.metadata = {} self.nodes = ['node1'] self.desired_capacity = 1 self.metadata = {'foo': 'bar'} self.timeout = 3600 self.max_size = -1 self.min_size = 0 self.location = 'actions/fake-action' self.profile_name = 'fake_profile' self.profile_id = 'fake_profile_id' def to_dict(self): return { 'id': self.id, 'status': self.status, 'status_reason': self.status_reason, 'name': self.name, 'metadata': self.metadata, 'timeout': self.timeout, 'desired_capacity': self.desired_capacity, 'max_size': self.max_size, 'min_size': self.min_size, 'nodes': self.nodes, 'profile_name': self.profile_name, 'profile_id': self.profile_id } class SenlinClusterTest(common.HeatTestCase): def setUp(self): super(SenlinClusterTest, self).setUp() self.senlin_mock = mock.MagicMock() self.senlin_mock.get_profile.return_value = mock.Mock( id='fake_profile_id' ) self.patchobject(sc.Cluster, 'client', return_value=self.senlin_mock) self.patchobject(senlin.SenlinClientPlugin, 'client', return_value=self.senlin_mock) self.patchobject(senlin.ProfileConstraint, 'validate', return_value=True) self.patchobject(senlin.PolicyConstraint, 'validate', return_value=True) self.fake_cl = FakeCluster() self.t = template_format.parse(cluster_stack_template) def _init_cluster(self, template): self.stack = utils.parse_stack(template) cluster = self.stack['senlin-cluster'] return cluster def _create_cluster(self, template): cluster = self._init_cluster(template) self.senlin_mock.create_cluster.return_value = self.fake_cl self.senlin_mock.get_cluster.return_value = self.fake_cl self.senlin_mock.get_action.return_value = mock.Mock( status='SUCCEEDED') self.senlin_mock.get_policy.return_value = mock.Mock( id='fake_policy_id' ) self.senlin_mock.cluster_policies.return_value = [ {'policy_id': 'fake_policy_id', 'enabled': True} ] scheduler.TaskRunner(cluster.create)() self.assertEqual((cluster.CREATE, cluster.COMPLETE), cluster.state) self.assertEqual(self.fake_cl.id, cluster.resource_id) self.assertEqual(1, self.senlin_mock.get_action.call_count) self.assertEqual(1, self.senlin_mock.get_cluster.call_count) return cluster def test_cluster_create_success(self): self._create_cluster(self.t) create_cluster_kwargs = { 'name': 'SenlinCluster', 'profile_id': 'fake_profile_id', 'desired_capacity': 1, 'min_size': 0, 'max_size': -1, 'metadata': {'foo': 'bar'}, 'timeout': 3600, } attach_policy_kwargs = { 'cluster': self.fake_cl.id, 'policy': 'fake_policy_id', 'enabled': True } self.senlin_mock.create_cluster.assert_called_once_with( **create_cluster_kwargs) self.senlin_mock.cluster_attach_policy.assert_called_once_with( **attach_policy_kwargs) def test_cluster_create_error(self): cfg.CONF.set_override('action_retry_limit', 0) cluster = self._init_cluster(self.t) self.senlin_mock.create_cluster.return_value = self.fake_cl mock_cluster = mock.MagicMock() mock_cluster.status = 'ERROR' mock_cluster.status_reason = 'oops' self.senlin_mock.get_policy.return_value = mock.Mock( id='fake_policy_id' ) self.senlin_mock.get_cluster.return_value = mock_cluster create_task = scheduler.TaskRunner(cluster.create) ex = self.assertRaises(exception.ResourceFailure, create_task) expected = ('ResourceInError: resources.senlin-cluster: ' 'Went to status ERROR due to "oops"') self.assertEqual(expected, six.text_type(ex)) def test_cluster_delete_success(self): cluster = self._create_cluster(self.t) self.senlin_mock.get_cluster.side_effect = [ exceptions.ResourceNotFound('SenlinCluster'), ] scheduler.TaskRunner(cluster.delete)() self.senlin_mock.delete_cluster.assert_called_once_with( cluster.resource_id) def test_cluster_delete_error(self): cluster = self._create_cluster(self.t) self.senlin_mock.get_cluster.side_effect = exception.Error('oops') delete_task = scheduler.TaskRunner(cluster.delete) ex = self.assertRaises(exception.ResourceFailure, delete_task) expected = 'Error: resources.senlin-cluster: oops' self.assertEqual(expected, six.text_type(ex)) def test_cluster_update_profile(self): cluster = self._create_cluster(self.t) # Mock translate rules self.senlin_mock.get_profile.side_effect = [ mock.Mock(id='new_profile_id'), mock.Mock(id='fake_profile_id'), mock.Mock(id='new_profile_id'), ] new_t = copy.deepcopy(self.t) props = new_t['resources']['senlin-cluster']['properties'] props['profile'] = 'new_profile' props['name'] = 'new_name' rsrc_defns = template.Template(new_t).resource_definitions(self.stack) new_cluster = rsrc_defns['senlin-cluster'] self.senlin_mock.update_cluster.return_value = mock.Mock( location='/actions/fake-action') self.senlin_mock.get_action.return_value = mock.Mock( status='SUCCEEDED') scheduler.TaskRunner(cluster.update, new_cluster)() self.assertEqual((cluster.UPDATE, cluster.COMPLETE), cluster.state) cluster_update_kwargs = { 'profile_id': 'new_profile_id', 'name': 'new_name' } self.senlin_mock.update_cluster.assert_called_once_with( cluster=self.fake_cl, **cluster_update_kwargs) self.assertEqual(2, self.senlin_mock.get_action.call_count) def test_cluster_update_desire_capacity(self): cluster = self._create_cluster(self.t) new_t = copy.deepcopy(self.t) props = new_t['resources']['senlin-cluster']['properties'] props['desired_capacity'] = 10 rsrc_defns = template.Template(new_t).resource_definitions(self.stack) new_cluster = rsrc_defns['senlin-cluster'] self.senlin_mock.cluster_resize.return_value = { 'action': 'fake-action'} self.senlin_mock.get_action.return_value = mock.Mock( status='SUCCEEDED') scheduler.TaskRunner(cluster.update, new_cluster)() self.assertEqual((cluster.UPDATE, cluster.COMPLETE), cluster.state) cluster_resize_kwargs = { 'adjustment_type': 'EXACT_CAPACITY', 'number': 10 } self.senlin_mock.cluster_resize.assert_called_once_with( cluster=cluster.resource_id, **cluster_resize_kwargs) self.assertEqual(2, self.senlin_mock.get_action.call_count) def test_cluster_update_policy_add_remove(self): cluster = self._create_cluster(self.t) # Mock translate rules self.senlin_mock.get_policy.side_effect = [ mock.Mock(id='new_policy_id'), mock.Mock(id='fake_policy_id'), mock.Mock(id='new_policy_id'), ] new_t = copy.deepcopy(self.t) props = new_t['resources']['senlin-cluster']['properties'] props['policies'] = [{'policy': 'new_policy'}] rsrc_defns = template.Template(new_t).resource_definitions(self.stack) new_cluster = rsrc_defns['senlin-cluster'] self.senlin_mock.cluster_detach_policy.return_value = { 'action': 'fake-action'} self.senlin_mock.cluster_attach_policy.return_value = { 'action': 'fake-action'} self.senlin_mock.get_action.return_value = mock.Mock( status='SUCCEEDED') scheduler.TaskRunner(cluster.update, new_cluster)() self.assertEqual((cluster.UPDATE, cluster.COMPLETE), cluster.state) detach_policy_kwargs = { 'policy': 'fake_policy_id', 'cluster': cluster.resource_id, 'enabled': True, } self.assertEqual(2, self.senlin_mock.cluster_attach_policy.call_count) self.senlin_mock.cluster_detach_policy.assert_called_once_with( **detach_policy_kwargs) self.assertEqual(0, self.senlin_mock.cluster_update_policy.call_count) self.assertEqual(3, self.senlin_mock.get_action.call_count) def test_cluster_update_policy_exists(self): cluster = self._create_cluster(self.t) new_t = copy.deepcopy(self.t) props = new_t['resources']['senlin-cluster']['properties'] props['policies'] = [{'policy': 'fake_policy', 'enabled': False}] rsrc_defns = template.Template(new_t).resource_definitions(self.stack) new_cluster = rsrc_defns['senlin-cluster'] self.senlin_mock.cluster_update_policy.return_value = { 'action': 'fake-action'} self.senlin_mock.get_action.return_value = mock.Mock( status='SUCCEEDED') scheduler.TaskRunner(cluster.update, new_cluster)() self.assertEqual((cluster.UPDATE, cluster.COMPLETE), cluster.state) update_policy_kwargs = { 'policy': 'fake_policy_id', 'cluster': cluster.resource_id, 'enabled': False, } self.senlin_mock.cluster_update_policy.assert_called_once_with( **update_policy_kwargs) self.assertEqual(1, self.senlin_mock.cluster_attach_policy.call_count) self.assertEqual(0, self.senlin_mock.cluster_detach_policy.call_count) def test_cluster_update_failed(self): cluster = self._create_cluster(self.t) new_t = copy.deepcopy(self.t) props = new_t['resources']['senlin-cluster']['properties'] props['desired_capacity'] = 3 rsrc_defns = template.Template(new_t).resource_definitions(self.stack) update_snippet = rsrc_defns['senlin-cluster'] self.senlin_mock.cluster_resize.return_value = { 'action': 'fake-action'} self.senlin_mock.get_action.return_value = mock.Mock( status='FAILED', status_reason='Unknown') exc = self.assertRaises( exception.ResourceFailure, scheduler.TaskRunner(cluster.update, update_snippet)) self.assertEqual('ResourceInError: resources.senlin-cluster: ' 'Went to status FAILED due to "Unknown"', six.text_type(exc)) def test_cluster_get_attr_collect(self): cluster = self._create_cluster(self.t) self.senlin_mock.collect_cluster_attrs.return_value = [ mock.Mock(attr_value='ip1')] attr_path1 = ['details.addresses.private[0].addr'] self.assertEqual( ['ip1'], cluster.get_attribute(cluster.ATTR_COLLECT, *attr_path1)) attr_path2 = ['details.addresses.private[0].addr', 0] self.assertEqual( 'ip1', cluster.get_attribute(cluster.ATTR_COLLECT, *attr_path2)) self.senlin_mock.collect_cluster_attrs.assert_called_with( cluster.resource_id, attr_path2[0]) def test_cluster_resolve_attribute(self): excepted_show = { 'id': 'some_id', 'status': 'ACTIVE', 'status_reason': 'Unknown', 'name': 'SenlinCluster', 'metadata': {'foo': 'bar'}, 'timeout': 3600, 'desired_capacity': 1, 'max_size': -1, 'min_size': 0, 'nodes': ['node1'], 'profile_name': 'fake_profile', 'profile_id': 'fake_profile_id', 'policies': [{'policy_id': 'fake_policy_id', 'enabled': True}] } cluster = self._create_cluster(self.t) self.assertEqual(self.fake_cl.desired_capacity, cluster._resolve_attribute('desired_capacity')) self.assertEqual(['node1'], cluster._resolve_attribute('nodes')) self.assertEqual(excepted_show, cluster._show_resource()) def test_cluster_get_live_state(self): expected_reality = { 'name': 'SenlinCluster', 'metadata': {'foo': 'bar'}, 'timeout': 3600, 'desired_capacity': 1, 'max_size': -1, 'min_size': 0, 'profile': 'fake_profile_id', 'policies': [{'policy': 'fake_policy_id', 'enabled': True}] } cluster = self._create_cluster(self.t) self.senlin_mock.get_cluster.return_value = self.fake_cl reality = cluster.get_live_state(cluster.properties) self.assertEqual(expected_reality, reality) class TestSenlinClusterValidation(common.HeatTestCase): def setUp(self): super(TestSenlinClusterValidation, self).setUp() self.t = template_format.parse(cluster_stack_template) def test_invalid_min_max_size(self): self.t['resources']['senlin-cluster']['properties']['min_size'] = 2 self.t['resources']['senlin-cluster']['properties']['max_size'] = 1 stack = utils.parse_stack(self.t) ex = self.assertRaises(exception.StackValidationFailed, stack['senlin-cluster'].validate) self.assertEqual('min_size can not be greater than max_size', six.text_type(ex)) def test_invalid_desired_capacity(self): self.t['resources']['senlin-cluster']['properties']['min_size'] = 1 self.t['resources']['senlin-cluster']['properties']['max_size'] = 2 self.t['resources']['senlin-cluster']['properties'][ 'desired_capacity'] = 3 stack = utils.parse_stack(self.t) ex = self.assertRaises(exception.StackValidationFailed, stack['senlin-cluster'].validate) self.assertEqual( 'desired_capacity must be between min_size and max_size', six.text_type(ex) )