Conditionally expose resources based on available services
If the required keystone service for a given resource does not exist in the keystone catalog, raises StackResourceUnavailable. It will be effective only if default_client_name is defined in the given resource. It also fixes other test cases, where is_service_available needs to be returning True implements blueprint keystone-based-resource-availability Closes-bug: #1388047 Change-Id: I92afa9ffc3a3333b46dc25921cf7f982777cba76
This commit is contained in:
parent
887be1716a
commit
490c36f7d0
|
@ -524,3 +524,14 @@ class KeystoneServiceNameConflict(HeatException):
|
||||||
|
|
||||||
class SIGHUPInterrupt(HeatException):
|
class SIGHUPInterrupt(HeatException):
|
||||||
msg_fmt = _("System SIGHUP signal received.")
|
msg_fmt = _("System SIGHUP signal received.")
|
||||||
|
|
||||||
|
|
||||||
|
class StackResourceUnavailable(StackValidationFailed):
|
||||||
|
message = _("Service %(service_name)s does not have required endpoint in "
|
||||||
|
"service catalog for the resource %(resource_name)s")
|
||||||
|
|
||||||
|
def __init__(self, service_name, resource_name):
|
||||||
|
super(StackResourceUnavailable, self).__init__(
|
||||||
|
message=self.message % dict(
|
||||||
|
service_name=service_name,
|
||||||
|
resource_name=resource_name))
|
||||||
|
|
|
@ -205,3 +205,15 @@ class ClientPlugin(object):
|
||||||
return args
|
return args
|
||||||
# FIXME(kanagaraj-manickam) Update other client plugins to leverage
|
# FIXME(kanagaraj-manickam) Update other client plugins to leverage
|
||||||
# this method (bug 1461041)
|
# this method (bug 1461041)
|
||||||
|
|
||||||
|
def does_endpoint_exist(self,
|
||||||
|
service_type,
|
||||||
|
service_name):
|
||||||
|
endpoint_type = self._get_client_option(service_name,
|
||||||
|
'endpoint_type')
|
||||||
|
try:
|
||||||
|
self.url_for(service_type=service_type,
|
||||||
|
endpoint_type=endpoint_type)
|
||||||
|
return True
|
||||||
|
except exceptions.EndpointNotFound:
|
||||||
|
return False
|
||||||
|
|
|
@ -32,6 +32,7 @@ from heat.common import short_id
|
||||||
from heat.common import timeutils
|
from heat.common import timeutils
|
||||||
from heat.engine import attributes
|
from heat.engine import attributes
|
||||||
from heat.engine.cfn import template as cfn_tmpl
|
from heat.engine.cfn import template as cfn_tmpl
|
||||||
|
from heat.engine import clients
|
||||||
from heat.engine import environment
|
from heat.engine import environment
|
||||||
from heat.engine import event
|
from heat.engine import event
|
||||||
from heat.engine import function
|
from heat.engine import function
|
||||||
|
@ -152,8 +153,18 @@ class Resource(object):
|
||||||
resource_name=name)
|
resource_name=name)
|
||||||
except exception.TemplateNotFound:
|
except exception.TemplateNotFound:
|
||||||
ResourceClass = template_resource.TemplateResource
|
ResourceClass = template_resource.TemplateResource
|
||||||
|
|
||||||
assert issubclass(ResourceClass, Resource)
|
assert issubclass(ResourceClass, Resource)
|
||||||
|
|
||||||
|
if not ResourceClass.is_service_available(stack.context):
|
||||||
|
ex = exception.StackResourceUnavailable(
|
||||||
|
service_name=ResourceClass.default_client_name,
|
||||||
|
resource_name=name
|
||||||
|
)
|
||||||
|
LOG.error(six.text_type(ex))
|
||||||
|
|
||||||
|
raise ex
|
||||||
|
|
||||||
return super(Resource, cls).__new__(ResourceClass)
|
return super(Resource, cls).__new__(ResourceClass)
|
||||||
|
|
||||||
def __init__(self, name, definition, stack):
|
def __init__(self, name, definition, stack):
|
||||||
|
@ -514,6 +525,34 @@ class Resource(object):
|
||||||
assert client_name, "Must specify client name"
|
assert client_name, "Must specify client name"
|
||||||
return self.stack.clients.client_plugin(client_name)
|
return self.stack.clients.client_plugin(client_name)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_service_available(cls, context):
|
||||||
|
# NOTE(kanagaraj-manickam): return True to satisfy the cases like
|
||||||
|
# resource does not have endpoint, such as RandomString, OS::Heat
|
||||||
|
# resources as they are implemented within the engine.
|
||||||
|
if cls.default_client_name is None:
|
||||||
|
return True
|
||||||
|
|
||||||
|
try:
|
||||||
|
client_plugin = clients.Clients(context).client_plugin(
|
||||||
|
cls.default_client_name)
|
||||||
|
|
||||||
|
service_types = client_plugin.service_types
|
||||||
|
if not service_types:
|
||||||
|
return True
|
||||||
|
|
||||||
|
# NOTE(kanagaraj-manickam): if one of the service_type does
|
||||||
|
# exist in the keystone, then considered it as available.
|
||||||
|
for service_type in service_types:
|
||||||
|
if client_plugin.does_endpoint_exist(
|
||||||
|
service_type=service_type,
|
||||||
|
service_name=cls.default_client_name):
|
||||||
|
return True
|
||||||
|
except Exception as ex:
|
||||||
|
LOG.exception(ex)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def keystone(self):
|
def keystone(self):
|
||||||
return self.client('keystone')
|
return self.client('keystone')
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,8 @@ class HeatTestCase(testscenarios.WithScenarios,
|
||||||
generic_rsrc.ResourceWithCustomConstraint)
|
generic_rsrc.ResourceWithCustomConstraint)
|
||||||
resource._register_class('ResourceWithComplexAttributesType',
|
resource._register_class('ResourceWithComplexAttributesType',
|
||||||
generic_rsrc.ResourceWithComplexAttributes)
|
generic_rsrc.ResourceWithComplexAttributes)
|
||||||
|
resource._register_class('ResourceWithDefaultClientName',
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName)
|
||||||
|
|
||||||
def stub_wallclock(self):
|
def stub_wallclock(self):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -33,6 +33,10 @@ class GenericResource(resource.Resource):
|
||||||
attributes_schema = {'foo': attributes.Schema('A generic attribute'),
|
attributes_schema = {'foo': attributes.Schema('A generic attribute'),
|
||||||
'Foo': attributes.Schema('Another generic attribute')}
|
'Foo': attributes.Schema('Another generic attribute')}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_service_available(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
def handle_create(self):
|
def handle_create(self):
|
||||||
LOG.warn(_LW('Creating generic resource (Type "%s")'),
|
LOG.warn(_LW('Creating generic resource (Type "%s")'),
|
||||||
self.type())
|
self.type())
|
||||||
|
@ -177,3 +181,7 @@ class ResourceWithAttributeType(GenericResource):
|
||||||
return "valid_sting"
|
return "valid_sting"
|
||||||
elif name == 'attr2':
|
elif name == 'attr2':
|
||||||
return "invalid_type"
|
return "invalid_type"
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceWithDefaultClientName(resource.Resource):
|
||||||
|
default_client_name = 'sample'
|
||||||
|
|
|
@ -50,6 +50,12 @@ class FakeCronTrigger(object):
|
||||||
self.remaining_executions = 3
|
self.remaining_executions = 3
|
||||||
|
|
||||||
|
|
||||||
|
class MistralCronTriggerTestResource(cron_trigger.CronTrigger):
|
||||||
|
@classmethod
|
||||||
|
def is_service_available(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class MistralCronTriggerTest(common.HeatTestCase):
|
class MistralCronTriggerTest(common.HeatTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -64,11 +70,11 @@ class MistralCronTriggerTest(common.HeatTestCase):
|
||||||
self.rsrc_defn = resource_defns['cron_trigger']
|
self.rsrc_defn = resource_defns['cron_trigger']
|
||||||
|
|
||||||
self.client = mock.Mock()
|
self.client = mock.Mock()
|
||||||
self.patchobject(cron_trigger.CronTrigger, 'client',
|
self.patchobject(MistralCronTriggerTestResource, 'client',
|
||||||
return_value=self.client)
|
return_value=self.client)
|
||||||
|
|
||||||
def _create_resource(self, name, snippet, stack):
|
def _create_resource(self, name, snippet, stack):
|
||||||
ct = cron_trigger.CronTrigger(name, snippet, stack)
|
ct = MistralCronTriggerTestResource(name, snippet, stack)
|
||||||
self.client.cron_triggers.create.return_value = FakeCronTrigger(
|
self.client.cron_triggers.create.return_value = FakeCronTrigger(
|
||||||
'my_cron_trigger')
|
'my_cron_trigger')
|
||||||
self.client.cron_triggers.get.return_value = FakeCronTrigger(
|
self.client.cron_triggers.get.return_value = FakeCronTrigger(
|
||||||
|
|
|
@ -179,6 +179,12 @@ class FakeWorkflow(object):
|
||||||
self.name = name
|
self.name = name
|
||||||
|
|
||||||
|
|
||||||
|
class MistralWorkFlowTestResource(workflow.Workflow):
|
||||||
|
@classmethod
|
||||||
|
def is_service_available(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class TestMistralWorkflow(common.HeatTestCase):
|
class TestMistralWorkflow(common.HeatTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
@ -193,7 +199,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
self.rsrc_defn = resource_defns['workflow']
|
self.rsrc_defn = resource_defns['workflow']
|
||||||
|
|
||||||
self.mistral = mock.Mock()
|
self.mistral = mock.Mock()
|
||||||
self.patchobject(workflow.Workflow, 'mistral',
|
self.patchobject(MistralWorkFlowTestResource, 'mistral',
|
||||||
return_value=self.mistral)
|
return_value=self.mistral)
|
||||||
|
|
||||||
self.patches = []
|
self.patches = []
|
||||||
|
@ -216,7 +222,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
patch.stop()
|
patch.stop()
|
||||||
|
|
||||||
def _create_resource(self, name, snippet, stack):
|
def _create_resource(self, name, snippet, stack):
|
||||||
wf = workflow.Workflow(name, snippet, stack)
|
wf = MistralWorkFlowTestResource(name, snippet, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('test_stack-workflow-b5fiekfci3yc')]
|
FakeWorkflow('test_stack-workflow-b5fiekfci3yc')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
@ -234,7 +240,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
|
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
||||||
|
|
||||||
wf = workflow.Workflow('create_vm', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('create_vm')]
|
FakeWorkflow('create_vm')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
@ -269,7 +275,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
|
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
||||||
|
|
||||||
wf = workflow.Workflow('workflow', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('workflow', rsrc_defns, stack)
|
||||||
|
|
||||||
exc = self.assertRaises(exception.StackValidationFailed,
|
exc = self.assertRaises(exception.StackValidationFailed,
|
||||||
wf.validate)
|
wf.validate)
|
||||||
|
@ -281,7 +287,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
|
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
||||||
|
|
||||||
wf = workflow.Workflow('workflow', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('workflow', rsrc_defns, stack)
|
||||||
|
|
||||||
self.mistral.workflows.create.side_effect = Exception('boom!')
|
self.mistral.workflows.create.side_effect = Exception('boom!')
|
||||||
|
|
||||||
|
@ -379,7 +385,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
tmpl = template_format.parse(workflow_template_full)
|
tmpl = template_format.parse(workflow_template_full)
|
||||||
stack = utils.parse_stack(tmpl)
|
stack = utils.parse_stack(tmpl)
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
||||||
wf = workflow.Workflow('create_vm', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('create_vm')]
|
FakeWorkflow('create_vm')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
@ -394,7 +400,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
tmpl = template_format.parse(workflow_template_full)
|
tmpl = template_format.parse(workflow_template_full)
|
||||||
stack = utils.parse_stack(tmpl)
|
stack = utils.parse_stack(tmpl)
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
||||||
wf = workflow.Workflow('create_vm', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('create_vm')]
|
FakeWorkflow('create_vm')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
@ -417,7 +423,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
tmpl = template_format.parse(workflow_template_full)
|
tmpl = template_format.parse(workflow_template_full)
|
||||||
stack = utils.parse_stack(tmpl)
|
stack = utils.parse_stack(tmpl)
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
||||||
wf = workflow.Workflow('create_vm', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('create_vm')]
|
FakeWorkflow('create_vm')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
@ -434,7 +440,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
tmpl = template_format.parse(workflow_template_full)
|
tmpl = template_format.parse(workflow_template_full)
|
||||||
stack = utils.parse_stack(tmpl)
|
stack = utils.parse_stack(tmpl)
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
||||||
wf = workflow.Workflow('create_vm', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('create_vm')]
|
FakeWorkflow('create_vm')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
@ -456,7 +462,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
tmpl = template_format.parse(workflow_template_full)
|
tmpl = template_format.parse(workflow_template_full)
|
||||||
stack = utils.parse_stack(tmpl)
|
stack = utils.parse_stack(tmpl)
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
rsrc_defns = stack.t.resource_definitions(stack)['create_vm']
|
||||||
wf = workflow.Workflow('create_vm', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('create_vm', rsrc_defns, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('create_vm')]
|
FakeWorkflow('create_vm')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
@ -472,7 +478,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
tmpl = template_format.parse(workflow_template_with_params)
|
tmpl = template_format.parse(workflow_template_with_params)
|
||||||
stack = utils.parse_stack(tmpl)
|
stack = utils.parse_stack(tmpl)
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
||||||
wf = workflow.Workflow('workflow', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('workflow', rsrc_defns, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('workflow')]
|
FakeWorkflow('workflow')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
@ -487,7 +493,7 @@ class TestMistralWorkflow(common.HeatTestCase):
|
||||||
tmpl = template_format.parse(workflow_template_with_params_override)
|
tmpl = template_format.parse(workflow_template_with_params_override)
|
||||||
stack = utils.parse_stack(tmpl)
|
stack = utils.parse_stack(tmpl)
|
||||||
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
rsrc_defns = stack.t.resource_definitions(stack)['workflow']
|
||||||
wf = workflow.Workflow('workflow', rsrc_defns, stack)
|
wf = MistralWorkFlowTestResource('workflow', rsrc_defns, stack)
|
||||||
self.mistral.workflows.create.return_value = [
|
self.mistral.workflows.create.return_value = [
|
||||||
FakeWorkflow('workflow')]
|
FakeWorkflow('workflow')]
|
||||||
scheduler.TaskRunner(wf.create)()
|
scheduler.TaskRunner(wf.create)()
|
||||||
|
|
|
@ -107,6 +107,10 @@ class NeutronTest(common.HeatTestCase):
|
||||||
class SomeNeutronResource(nr.NeutronResource):
|
class SomeNeutronResource(nr.NeutronResource):
|
||||||
properties_schema = {}
|
properties_schema = {}
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def is_service_available(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
|
tmpl = rsrc_defn.ResourceDefinition('test_res', 'Foo')
|
||||||
stack = mock.MagicMock()
|
stack = mock.MagicMock()
|
||||||
stack.has_cache_data = mock.Mock(return_value=False)
|
stack.has_cache_data = mock.Mock(return_value=False)
|
||||||
|
|
|
@ -43,18 +43,25 @@ magnum_template = '''
|
||||||
RESOURCE_TYPE = 'OS::Magnum::BayModel'
|
RESOURCE_TYPE = 'OS::Magnum::BayModel'
|
||||||
|
|
||||||
|
|
||||||
|
class MagnumBayModelTestResource(baymodel.BayModel):
|
||||||
|
@classmethod
|
||||||
|
def is_service_available(cls, context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
class TestMagnumBayModel(common.HeatTestCase):
|
class TestMagnumBayModel(common.HeatTestCase):
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(TestMagnumBayModel, self).setUp()
|
super(TestMagnumBayModel, self).setUp()
|
||||||
self.ctx = utils.dummy_context()
|
self.ctx = utils.dummy_context()
|
||||||
resource._register_class(RESOURCE_TYPE, baymodel.BayModel)
|
resource._register_class(RESOURCE_TYPE, MagnumBayModelTestResource)
|
||||||
t = template_format.parse(magnum_template)
|
t = template_format.parse(magnum_template)
|
||||||
self.stack = utils.parse_stack(t)
|
self.stack = utils.parse_stack(t)
|
||||||
|
|
||||||
resource_defns = self.stack.t.resource_definitions(self.stack)
|
resource_defns = self.stack.t.resource_definitions(self.stack)
|
||||||
self.rsrc_defn = resource_defns['test_baymodel']
|
self.rsrc_defn = resource_defns['test_baymodel']
|
||||||
self.client = mock.Mock()
|
self.client = mock.Mock()
|
||||||
self.patchobject(baymodel.BayModel, 'client', return_value=self.client)
|
self.patchobject(MagnumBayModelTestResource, 'client',
|
||||||
|
return_value=self.client)
|
||||||
self.stub_FlavorConstraint_validate()
|
self.stub_FlavorConstraint_validate()
|
||||||
self.stub_KeypairConstraint_validate()
|
self.stub_KeypairConstraint_validate()
|
||||||
self.stub_ImageConstraint_validate()
|
self.stub_ImageConstraint_validate()
|
||||||
|
@ -65,7 +72,7 @@ class TestMagnumBayModel(common.HeatTestCase):
|
||||||
self.test_bay_model = self.stack['test_baymodel']
|
self.test_bay_model = self.stack['test_baymodel']
|
||||||
value = mock.MagicMock(uuid=self.resource_id)
|
value = mock.MagicMock(uuid=self.resource_id)
|
||||||
self.client.baymodels.create.return_value = value
|
self.client.baymodels.create.return_value = value
|
||||||
bm = baymodel.BayModel(name, snippet, stack)
|
bm = MagnumBayModelTestResource(name, snippet, stack)
|
||||||
scheduler.TaskRunner(bm.create)()
|
scheduler.TaskRunner(bm.create)()
|
||||||
return bm
|
return bm
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
import mox
|
import mox
|
||||||
|
|
||||||
from heat.common import identifier
|
from heat.common import identifier
|
||||||
|
@ -239,7 +239,9 @@ class WaitCondMetadataUpdateTest(common.HeatTestCase):
|
||||||
self.m.StubOutWithMock(scheduler.TaskRunner, '_sleep')
|
self.m.StubOutWithMock(scheduler.TaskRunner, '_sleep')
|
||||||
return stack
|
return stack
|
||||||
|
|
||||||
def test_wait_meta(self):
|
@mock.patch(('heat.engine.resources.aws.ec2.instance.Instance'
|
||||||
|
'.is_service_available'))
|
||||||
|
def test_wait_meta(self, mock_is_service_available):
|
||||||
'''
|
'''
|
||||||
1 create stack
|
1 create stack
|
||||||
2 assert empty instance metadata
|
2 assert empty instance metadata
|
||||||
|
@ -247,6 +249,7 @@ class WaitCondMetadataUpdateTest(common.HeatTestCase):
|
||||||
4 assert valid waitcond metadata
|
4 assert valid waitcond metadata
|
||||||
5 assert valid instance metadata
|
5 assert valid instance metadata
|
||||||
'''
|
'''
|
||||||
|
mock_is_service_available.return_value = True
|
||||||
self.stack = self.create_stack()
|
self.stack = self.create_stack()
|
||||||
|
|
||||||
watch = self.stack['WC']
|
watch = self.stack['WC']
|
||||||
|
|
|
@ -28,6 +28,7 @@ from heat.common import timeutils
|
||||||
from heat.db import api as db_api
|
from heat.db import api as db_api
|
||||||
from heat.engine import attributes
|
from heat.engine import attributes
|
||||||
from heat.engine.cfn import functions as cfn_funcs
|
from heat.engine.cfn import functions as cfn_funcs
|
||||||
|
from heat.engine import clients
|
||||||
from heat.engine import constraints
|
from heat.engine import constraints
|
||||||
from heat.engine import dependencies
|
from heat.engine import dependencies
|
||||||
from heat.engine import environment
|
from heat.engine import environment
|
||||||
|
@ -2263,3 +2264,127 @@ class ResourceHookTest(common.HeatTestCase):
|
||||||
res.has_hook = mock.Mock(return_value=False)
|
res.has_hook = mock.Mock(return_value=False)
|
||||||
self.assertRaises(exception.ResourceActionNotSupported,
|
self.assertRaises(exception.ResourceActionNotSupported,
|
||||||
res.signal, {'unset_hook': 'pre-create'})
|
res.signal, {'unset_hook': 'pre-create'})
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceAvailabilityTest(common.HeatTestCase):
|
||||||
|
def _mock_client_plugin(self, service_types=[], is_available=True):
|
||||||
|
mock_client_plugin = mock.Mock()
|
||||||
|
mock_service_types = mock.PropertyMock(return_value=service_types)
|
||||||
|
type(mock_client_plugin).service_types = mock_service_types
|
||||||
|
mock_client_plugin.does_endpoint_exist = mock.Mock(
|
||||||
|
return_value=is_available)
|
||||||
|
return mock_service_types, mock_client_plugin
|
||||||
|
|
||||||
|
def test_default_true_with_default_client_name_none(self):
|
||||||
|
'''
|
||||||
|
When default_client_name is None, resource is considered as available.
|
||||||
|
'''
|
||||||
|
with mock.patch(('heat.tests.generic_resource'
|
||||||
|
'.ResourceWithDefaultClientName.default_client_name'),
|
||||||
|
new_callable=mock.PropertyMock) as mock_client_name:
|
||||||
|
mock_client_name.return_value = None
|
||||||
|
self.assertTrue((generic_rsrc.ResourceWithDefaultClientName.
|
||||||
|
is_service_available(context=mock.Mock())))
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'client_plugin')
|
||||||
|
def test_default_true_empty_service_types(
|
||||||
|
self,
|
||||||
|
mock_client_plugin_method):
|
||||||
|
'''
|
||||||
|
When service_types is empty list, resource is considered as available.
|
||||||
|
'''
|
||||||
|
|
||||||
|
mock_service_types, mock_client_plugin = self._mock_client_plugin()
|
||||||
|
mock_client_plugin_method.return_value = mock_client_plugin
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName.is_service_available(
|
||||||
|
context=mock.Mock()))
|
||||||
|
mock_client_plugin_method.assert_called_once_with(
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName.default_client_name)
|
||||||
|
mock_service_types.assert_called_once_with()
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'client_plugin')
|
||||||
|
def test_service_deployed(
|
||||||
|
self,
|
||||||
|
mock_client_plugin_method):
|
||||||
|
'''
|
||||||
|
When the service is deployed, resource is considered as available.
|
||||||
|
'''
|
||||||
|
|
||||||
|
mock_service_types, mock_client_plugin = self._mock_client_plugin(
|
||||||
|
['test_type']
|
||||||
|
)
|
||||||
|
mock_client_plugin_method.return_value = mock_client_plugin
|
||||||
|
|
||||||
|
self.assertTrue(
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName.is_service_available(
|
||||||
|
context=mock.Mock()))
|
||||||
|
mock_client_plugin_method.assert_called_once_with(
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName.default_client_name)
|
||||||
|
mock_service_types.assert_called_once_with()
|
||||||
|
mock_client_plugin.does_endpoint_exist.assert_called_once_with(
|
||||||
|
service_type='test_type',
|
||||||
|
service_name=(generic_rsrc.ResourceWithDefaultClientName
|
||||||
|
.default_client_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
@mock.patch.object(clients.OpenStackClients, 'client_plugin')
|
||||||
|
def test_service_not_deployed(
|
||||||
|
self,
|
||||||
|
mock_client_plugin_method):
|
||||||
|
'''
|
||||||
|
When the service is not deployed, resource is considered as
|
||||||
|
unavailable.
|
||||||
|
'''
|
||||||
|
|
||||||
|
mock_service_types, mock_client_plugin = self._mock_client_plugin(
|
||||||
|
['test_type_un_deployed'],
|
||||||
|
False
|
||||||
|
)
|
||||||
|
mock_client_plugin_method.return_value = mock_client_plugin
|
||||||
|
|
||||||
|
self.assertFalse(
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName.is_service_available(
|
||||||
|
context=mock.Mock()))
|
||||||
|
mock_client_plugin_method.assert_called_once_with(
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName.default_client_name)
|
||||||
|
mock_service_types.assert_called_once_with()
|
||||||
|
mock_client_plugin.does_endpoint_exist.assert_called_once_with(
|
||||||
|
service_type='test_type_un_deployed',
|
||||||
|
service_name=(generic_rsrc.ResourceWithDefaultClientName
|
||||||
|
.default_client_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_service_not_deployed_throws_exception(self):
|
||||||
|
'''
|
||||||
|
When the service is not deployed, make sure resource is throwing
|
||||||
|
StackResourceUnavailable exception.
|
||||||
|
'''
|
||||||
|
with mock.patch.object(
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName,
|
||||||
|
'is_service_available') as mock_method:
|
||||||
|
mock_method.return_value = False
|
||||||
|
|
||||||
|
definition = rsrc_defn.ResourceDefinition(
|
||||||
|
name='Test Resource',
|
||||||
|
resource_type=mock.Mock())
|
||||||
|
|
||||||
|
mock_stack = mock.MagicMock()
|
||||||
|
|
||||||
|
ex = self.assertRaises(
|
||||||
|
exception.StackResourceUnavailable,
|
||||||
|
generic_rsrc.ResourceWithDefaultClientName.__new__,
|
||||||
|
cls=generic_rsrc.ResourceWithDefaultClientName,
|
||||||
|
name='test_stack',
|
||||||
|
definition=definition,
|
||||||
|
stack=mock_stack)
|
||||||
|
|
||||||
|
msg = ('Service sample does not have required endpoint in service'
|
||||||
|
' catalog for the resource test_stack')
|
||||||
|
self.assertEqual(msg,
|
||||||
|
six.text_type(ex),
|
||||||
|
'invalid exception message')
|
||||||
|
|
||||||
|
# Make sure is_service_available is called on the right class
|
||||||
|
mock_method.assert_called_once_with(mock_stack.context)
|
||||||
|
|
Loading…
Reference in New Issue