diff --git a/heat/engine/clients/os/senlin.py b/heat/engine/clients/os/senlin.py index 7a026e91bc..ea5cb7adca 100644 --- a/heat/engine/clients/os/senlin.py +++ b/heat/engine/clients/os/senlin.py @@ -11,6 +11,8 @@ # License for the specific language governing permissions and limitations # under the License. +from heat.common import exception +from heat.common.i18n import _ from heat.engine.clients import client_plugin from heat.engine import constraints @@ -41,8 +43,50 @@ class SenlinClientPlugin(client_plugin.ClientPlugin): class ProfileConstraint(constraints.BaseCustomConstraint): - - expected_exceptions = (exc.sdkexc.ResourceNotFound,) + # If name is not unique, will raise exc.sdkexc.HttpException + expected_exceptions = (exc.sdkexc.HttpException,) def validate_with_client(self, client, profile): client.client(CLIENT_NAME).get_profile(profile) + + +class ClusterConstraint(constraints.BaseCustomConstraint): + # If name is not unique, will raise exc.sdkexc.HttpException + expected_exceptions = (exc.sdkexc.HttpException,) + + def validate_with_client(self, client, value): + client.client(CLIENT_NAME).get_cluster(value) + + +class ProfileTypeConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (exception.StackValidationFailed,) + + def validate_with_client(self, client, value): + senlin_client = client.client(CLIENT_NAME) + type_list = senlin_client.profile_types() + names = [pt['name'] for pt in type_list] + if value not in names: + not_found_message = ( + _("Unable to find senlin profile type '%(pt)s', " + "available profile types are %(pts)s.") % + {'pt': value, 'pts': names} + ) + raise exception.StackValidationFailed(message=not_found_message) + + +class PolicyTypeConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (exception.StackValidationFailed,) + + def validate_with_client(self, client, value): + senlin_client = client.client(CLIENT_NAME) + type_list = senlin_client.policy_types() + names = [pt['name'] for pt in type_list] + if value not in names: + not_found_message = ( + _("Unable to find senlin policy type '%(pt)s', " + "available policy types are %(pts)s.") % + {'pt': value, 'pts': names} + ) + raise exception.StackValidationFailed(message=not_found_message) diff --git a/heat/tests/clients/test_senlin_client.py b/heat/tests/clients/test_senlin_client.py index ce7be34da7..06ad240ef6 100644 --- a/heat/tests/clients/test_senlin_client.py +++ b/heat/tests/clients/test_senlin_client.py @@ -47,3 +47,74 @@ class ProfileConstraintTest(common.HeatTestCase): self.mock_get_profile.side_effect = exc.sdkexc.ResourceNotFound( 'PROFILE_ID') self.assertFalse(self.constraint.validate("PROFILE_ID", self.ctx)) + self.mock_get_profile.side_effect = exc.sdkexc.HttpException( + 'PROFILE_ID') + self.assertFalse(self.constraint.validate("PROFILE_ID", self.ctx)) + + +class ClusterConstraintTest(common.HeatTestCase): + + def setUp(self): + super(ClusterConstraintTest, self).setUp() + self.senlin_client = mock.MagicMock() + self.ctx = utils.dummy_context() + self.mock_get_cluster = mock.Mock() + self.ctx.clients.client( + 'senlin').get_cluster = self.mock_get_cluster + self.constraint = senlin_plugin.ClusterConstraint() + + def test_validate_true(self): + self.mock_get_cluster.return_value = None + self.assertTrue(self.constraint.validate("CLUSTER_ID", self.ctx)) + + def test_validate_false(self): + self.mock_get_cluster.side_effect = exc.sdkexc.ResourceNotFound( + 'CLUSTER_ID') + self.assertFalse(self.constraint.validate("CLUSTER_ID", self.ctx)) + self.mock_get_cluster.side_effect = exc.sdkexc.HttpException( + 'CLUSTER_ID') + self.assertFalse(self.constraint.validate("CLUSTER_ID", self.ctx)) + + +class ProfileTypeConstraintTest(common.HeatTestCase): + + def setUp(self): + super(ProfileTypeConstraintTest, self).setUp() + self.senlin_client = mock.MagicMock() + self.ctx = utils.dummy_context() + self.mock_profile_types = mock.Mock( + return_value=[{'name': 'os.heat.stack-1.0'}, + {'name': 'os.nova.server-1.0'}]) + self.ctx.clients.client( + 'senlin').profile_types = self.mock_profile_types + self.constraint = senlin_plugin.ProfileTypeConstraint() + + def test_validate_true(self): + self.assertTrue(self.constraint.validate("os.heat.stack-1.0", + self.ctx)) + + def test_validate_false(self): + self.assertFalse(self.constraint.validate("Invalid_type", + self.ctx)) + + +class PolicyTypeConstraintTest(common.HeatTestCase): + + def setUp(self): + super(PolicyTypeConstraintTest, self).setUp() + self.senlin_client = mock.MagicMock() + self.ctx = utils.dummy_context() + self.mock_policy_types = mock.Mock( + return_value=[{'name': 'senlin.policy.deletion-1.0'}, + {'name': 'senlin.policy.loadbalance-1.0'}]) + self.ctx.clients.client( + 'senlin').policy_types = self.mock_policy_types + self.constraint = senlin_plugin.PolicyTypeConstraint() + + def test_validate_true(self): + self.assertTrue(self.constraint.validate( + "senlin.policy.deletion-1.0", self.ctx)) + + def test_validate_false(self): + self.assertFalse(self.constraint.validate("Invalid_type", + self.ctx)) diff --git a/setup.cfg b/setup.cfg index dffe6b4a2b..a20a83b94f 100644 --- a/setup.cfg +++ b/setup.cfg @@ -119,7 +119,10 @@ heat.constraints = cron_expression = heat.engine.constraint.common_constraints:CRONExpressionConstraint monasca.notification = heat.engine.clients.os.monasca:MonascaNotificationConstraint sahara.plugin = heat.engine.clients.os.sahara:PluginConstraint + senlin.cluster = heat.engine.clients.os.senlin:ClusterConstraint + senlin.policy_type = heat.engine.clients.os.senlin:PolicyTypeConstraint senlin.profile = heat.engine.clients.os.senlin:ProfileConstraint + senlin.profile_type = heat.engine.clients.os.senlin:ProfileTypeConstraint heat.stack_lifecycle_plugins =