diff --git a/heat/engine/clients/os/neutron/lbaas_constraints.py b/heat/engine/clients/os/neutron/lbaas_constraints.py index 3fbf0a8f62..61bffd955a 100644 --- a/heat/engine/clients/os/neutron/lbaas_constraints.py +++ b/heat/engine/clients/os/neutron/lbaas_constraints.py @@ -35,3 +35,7 @@ class PoolConstraint(nc.NeutronConstraint): resource_name = 'pool' cmd_resource = 'lbaas_pool' extension = 'lbaasv2' + + +class LBaasV2ProviderConstraint(nc.ProviderConstraint): + service_type = 'LOADBALANCERV2' diff --git a/heat/engine/clients/os/neutron/neutron_constraints.py b/heat/engine/clients/os/neutron/neutron_constraints.py index d22285026a..2a822e3e1e 100644 --- a/heat/engine/clients/os/neutron/neutron_constraints.py +++ b/heat/engine/clients/os/neutron/neutron_constraints.py @@ -16,6 +16,7 @@ from neutronclient.common import exceptions as qe from heat.common import exception +from heat.common.i18n import _ from heat.engine.clients.os import nova from heat.engine import constraints @@ -83,3 +84,31 @@ class QoSPolicyConstraint(NeutronConstraint): resource_name = 'policy' cmd_resource = 'qos_policy' extension = 'qos' + + +class ProviderConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (exception.StackValidationFailed,) + service_type = None + + def validate_with_client(self, client, value): + params = {} + neutron_client = client.client(CLIENT_NAME) + if self.service_type: + params['service_type'] = self.service_type + providers = neutron_client.list_service_providers( + retrieve_all=True, + **params + )['service_providers'] + names = [provider['name'] for provider in providers] + if value not in names: + not_found_message = ( + _("Unable to find neutron provider '%(provider)s', " + "available providers are %(providers)s.") % + {'provider': value, 'providers': names} + ) + raise exception.StackValidationFailed(message=not_found_message) + + +class LBaasV1ProviderConstraint(ProviderConstraint): + service_type = 'LOADBALANCER' diff --git a/heat/engine/resources/openstack/neutron/loadbalancer.py b/heat/engine/resources/openstack/neutron/loadbalancer.py index dc1c374999..b863949d2a 100644 --- a/heat/engine/resources/openstack/neutron/loadbalancer.py +++ b/heat/engine/resources/openstack/neutron/loadbalancer.py @@ -273,6 +273,9 @@ class Pool(neutron.NeutronResource): properties.Schema.STRING, _('LBaaS provider to implement this load balancer instance.'), support_status=support.SupportStatus(version='5.0.0'), + constraints=[ + constraints.CustomConstraint('neutron.lb.provider') + ], ), VIP: properties.Schema( properties.Schema.MAP, diff --git a/heat/tests/clients/test_neutron_client.py b/heat/tests/clients/test_neutron_client.py index 5bf033ea33..aafb64590d 100644 --- a/heat/tests/clients/test_neutron_client.py +++ b/heat/tests/clients/test_neutron_client.py @@ -241,6 +241,33 @@ class NeutronConstraintsValidate(common.HeatTestCase): cmd_resource=self.cmd_resource)]) +class NeutronProviderConstraintsValidate(common.HeatTestCase): + scenarios = [ + ('validate_lbaasv1', + dict(constraint_class=nc.LBaasV1ProviderConstraint, + service_type='LOADBALANCER')), + ('validate_lbaasv2', + dict(constraint_class=lc.LBaasV2ProviderConstraint, + service_type='LOADBALANCERV2')) + ] + + def test_provider_validate(self): + nc = mock.Mock() + mock_create = self.patchobject(neutron.NeutronClientPlugin, '_create') + mock_create.return_value = nc + providers = { + 'service_providers': [ + {'service_type': 'LOADBANALCERV2', 'name': 'haproxy'}, + {'service_type': 'LOADBANALCER', 'name': 'haproxy'} + ] + } + nc.list_service_providers.return_value = providers + constraint = self.constraint_class() + ctx = utils.dummy_context() + self.assertTrue(constraint.validate('haproxy', ctx)) + self.assertFalse(constraint.validate("bar", ctx)) + + class NeutronClientPluginExtensionsTests(NeutronClientPluginTestCase): """Tests for extensions in neutronclient.""" diff --git a/heat/tests/common.py b/heat/tests/common.py index e7295c3a1e..6889e2368b 100644 --- a/heat/tests/common.py +++ b/heat/tests/common.py @@ -293,3 +293,7 @@ class HeatTestCase(testscenarios.WithScenarios, def stub_SaharaPluginConstraint(self): validate = self.patchobject(sahara.PluginConstraint, 'validate') validate.return_value = True + + def stub_ProviderConstraint_validate(self): + validate = self.patchobject(neutron.ProviderConstraint, 'validate') + validate.return_value = True diff --git a/heat/tests/openstack/neutron/test_neutron_loadbalancer.py b/heat/tests/openstack/neutron/test_neutron_loadbalancer.py index 6e255555d0..c84327dad8 100644 --- a/heat/tests/openstack/neutron/test_neutron_loadbalancer.py +++ b/heat/tests/openstack/neutron/test_neutron_loadbalancer.py @@ -600,6 +600,7 @@ class PoolTest(common.HeatTestCase): neutronclient.Client.show_vip('xyz').AndReturn( {'vip': {'status': 'ACTIVE'}}) snippet = template_format.parse(pool_template_with_provider) + self.stub_ProviderConstraint_validate() self.stack = utils.parse_stack(snippet) resource_defns = self.stack.t.resource_definitions(self.stack) rsrc = loadbalancer.Pool( diff --git a/setup.cfg b/setup.cfg index 4928e136b7..cd8babbd2a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -83,9 +83,11 @@ heat.constraints = neutron.subnet = heat.engine.clients.os.neutron.neutron_constraints:SubnetConstraint neutron.subnetpool = heat.engine.clients.os.neutron.neutron_constraints:SubnetPoolConstraint neutron.qos_policy = heat.engine.clients.os.neutron.neutron_constraints:QoSPolicyConstraint + neutron.lb.provider = heat.engine.clients.os.neutron.neutron_constraints:LBaasV1ProviderConstraint neutron.lbaas.loadbalancer = heat.engine.clients.os.neutron.lbaas_constraints:LoadbalancerConstraint neutron.lbaas.listener = heat.engine.clients.os.neutron.lbaas_constraints:ListenerConstraint neutron.lbaas.pool = heat.engine.clients.os.neutron.lbaas_constraints:PoolConstraint + neutron.lbaas.provider = heat.engine.clients.os.neutron.lbaas_constraints:LBaasV2ProviderConstraint glance.image = heat.engine.clients.os.glance:ImageConstraint iso_8601 = heat.engine.constraint.common_constraints:ISO8601Constraint nova.server = heat.engine.clients.os.nova:ServerConstraint