Remove Monasca support

The monasca project was marked inactive during 2023.1 cycle[1] and is
being formally retired.

[1] https://review.opendev.org/c/openstack/governance/+/897520

Change-Id: I57f88f08bd0d160760703891b00aa9cb26dd1d98
Signed-off-by: Takashi Kajinami <kajinamit@oss.nttdata.com>
This commit is contained in:
Takashi Kajinami
2023-11-09 18:39:31 +09:00
parent 10656f4c7e
commit 7507522365
12 changed files with 41 additions and 985 deletions

View File

@@ -69,7 +69,6 @@ We have integration with
* https://opendev.org/openstack/python-manilaclient (shared file system service)
* https://opendev.org/openstack/python-mistralclient (workflow service)
* https://opendev.org/openstack/python-zaqarclient (messaging service)
* https://opendev.org/openstack/python-monascaclient (monitoring service)
* https://opendev.org/openstack/python-zunclient (container management service)
* https://opendev.org/openstack/python-blazarclient (reservation service)
* https://opendev.org/openstack/python-octaviaclient (Load-balancer service)

View File

@@ -482,10 +482,9 @@ def list_opts():
yield noauth_group.name, noauth_opts
yield 'clients', default_clients_opts
for client in ('aodh', 'barbican', 'cinder', 'designate',
'glance', 'heat', 'keystone', 'magnum', 'manila', 'mistral',
'monasca', 'neutron', 'nova', 'octavia',
'swift', 'trove', 'vitrage', 'zaqar'
for client in ('aodh', 'barbican', 'cinder', 'designate', 'glance', 'heat',
'keystone', 'magnum', 'manila', 'mistral', 'neutron',
'nova', 'octavia', 'swift', 'trove', 'vitrage', 'zaqar'
):
client_specific_group = 'clients_' + client
yield client_specific_group, clients_opts

View File

@@ -1,61 +0,0 @@
#
# 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.
from monascaclient import exc as monasca_exc
from monascaclient.v2_0 import client as monasca_client
from heat.common import exception as heat_exc
from heat.engine.clients import client_plugin
from heat.engine import constraints
CLIENT_NAME = 'monasca'
class MonascaClientPlugin(client_plugin.ClientPlugin):
exceptions_module = [monasca_exc]
service_types = [MONITORING] = ['monitoring']
VERSION = '2_0'
def _create(self):
interface = self._get_client_option(CLIENT_NAME, 'endpoint_type')
endpoint = self.url_for(service_type=self.MONITORING,
endpoint_type=interface)
# Directly use v2_0 client to avoid dynamic import in monasca client,
# We can switch back once https://review.opendev.org/#/c/700989 fixed.
return monasca_client.Client(
session=self.context.keystone_session,
service_type='monitoring',
endpoint=endpoint)
def is_not_found(self, ex):
return isinstance(ex, monasca_exc.NotFound)
def is_un_processable(self, ex):
return isinstance(ex, monasca_exc.UnprocessableEntity)
def get_notification(self, notification):
try:
return self.client().notifications.get(
notification_id=notification)['id']
except monasca_exc.NotFound:
raise heat_exc.EntityNotFound(entity='Monasca Notification',
name=notification)
class MonascaNotificationConstraint(constraints.BaseCustomConstraint):
resource_client_name = CLIENT_NAME
resource_getter_name = 'get_notification'

View File

@@ -12,13 +12,11 @@
# under the License.
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources.openstack.heat import none_resource
from heat.engine import support
class MonascaAlarmDefinition(resource.Resource):
class MonascaAlarmDefinition(none_resource.NoneResource):
"""Heat Template Resource for Monasca Alarm definition.
Monasca Alarm definition helps to define the required expression for
@@ -32,175 +30,19 @@ class MonascaAlarmDefinition(resource.Resource):
"""
support_status = support.SupportStatus(
version='22.0.0',
status=support.DEPRECATED,
message=_('Monasca project was marked inactive'),
version='25.0.0',
status=support.HIDDEN,
message=_('Monasca project was retired'),
previous_status=support.SupportStatus(
version='7.0.0',
version='22.0.0',
status=support.DEPRECATED,
message=_('Monasca project was marked inactive'),
previous_status=support.SupportStatus(
version='5.0.0',
status=support.UNSUPPORTED
)))
default_client_name = 'monasca'
entity = 'alarm_definitions'
SEVERITY_LEVELS = (
LOW, MEDIUM, HIGH, CRITICAL
) = (
'low', 'medium', 'high', 'critical'
)
PROPERTIES = (
NAME, DESCRIPTION, EXPRESSION, MATCH_BY, SEVERITY,
OK_ACTIONS, ALARM_ACTIONS, UNDETERMINED_ACTIONS,
ACTIONS_ENABLED
) = (
'name', 'description', 'expression', 'match_by', 'severity',
'ok_actions', 'alarm_actions', 'undetermined_actions',
'actions_enabled'
)
properties_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the alarm. By default, physical resource name is '
'used.'),
update_allowed=True
),
DESCRIPTION: properties.Schema(
properties.Schema.STRING,
_('Description of the alarm.'),
update_allowed=True
),
EXPRESSION: properties.Schema(
properties.Schema.STRING,
_('Expression of the alarm to evaluate.'),
update_allowed=False,
required=True
),
MATCH_BY: properties.Schema(
properties.Schema.LIST,
_('The metric dimensions to match to the alarm dimensions. '
'One or more dimension key names separated by a comma.'),
default=[],
),
SEVERITY: properties.Schema(
properties.Schema.STRING,
_('Severity of the alarm.'),
update_allowed=True,
constraints=[constraints.AllowedValues(
SEVERITY_LEVELS
)],
default=LOW
),
OK_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('The notification methods to use when an alarm state is OK.'),
update_allowed=True,
schema=properties.Schema(
properties.Schema.STRING,
_('Monasca notification.'),
constraints=[constraints.CustomConstraint(
'monasca.notification')
]
),
default=[],
),
ALARM_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('The notification methods to use when an alarm state is ALARM.'),
update_allowed=True,
schema=properties.Schema(
properties.Schema.STRING,
_('Monasca notification.'),
constraints=[constraints.CustomConstraint(
'monasca.notification')
]
),
default=[],
),
UNDETERMINED_ACTIONS: properties.Schema(
properties.Schema.LIST,
_('The notification methods to use when an alarm state is '
'UNDETERMINED.'),
update_allowed=True,
schema=properties.Schema(
properties.Schema.STRING,
_('Monasca notification.'),
constraints=[constraints.CustomConstraint(
'monasca.notification')
]
),
default=[],
),
ACTIONS_ENABLED: properties.Schema(
properties.Schema.BOOLEAN,
_('Whether to enable the actions or not.'),
update_allowed=True,
default=True,
),
}
def handle_create(self):
args = dict(
name=(self.properties[self.NAME] or
self.physical_resource_name()),
description=self.properties[self.DESCRIPTION],
expression=self.properties[self.EXPRESSION],
match_by=self.properties[self.MATCH_BY],
severity=self.properties[self.SEVERITY],
ok_actions=self.properties[self.OK_ACTIONS],
alarm_actions=self.properties[self.ALARM_ACTIONS],
undetermined_actions=self.properties[
self.UNDETERMINED_ACTIONS]
)
alarm = self.client().alarm_definitions.create(**args)
self.resource_id_set(alarm['id'])
# Monasca enables action by default
actions_enabled = self.properties[self.ACTIONS_ENABLED]
if not actions_enabled:
self.client().alarm_definitions.patch(
alarm_id=self.resource_id,
actions_enabled=actions_enabled
)
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
args = dict(alarm_id=self.resource_id)
if prop_diff.get(self.NAME):
args['name'] = prop_diff.get(self.NAME)
if prop_diff.get(self.DESCRIPTION):
args['description'] = prop_diff.get(self.DESCRIPTION)
if prop_diff.get(self.SEVERITY):
args['severity'] = prop_diff.get(self.SEVERITY)
if prop_diff.get(self.OK_ACTIONS):
args['ok_actions'] = prop_diff.get(self.OK_ACTIONS)
if prop_diff.get(self.ALARM_ACTIONS):
args['alarm_actions'] = prop_diff.get(self.ALARM_ACTIONS)
if prop_diff.get(self.UNDETERMINED_ACTIONS):
args['undetermined_actions'] = prop_diff.get(
self.UNDETERMINED_ACTIONS
)
if prop_diff.get(self.ACTIONS_ENABLED):
args['actions_enabled'] = prop_diff.get(self.ACTIONS_ENABLED)
self.client().alarm_definitions.patch(**args)
def handle_delete(self):
if self.resource_id is not None:
with self.client_plugin().ignore_not_found:
self.client().alarm_definitions.delete(
alarm_id=self.resource_id)
version='7.0.0',
previous_status=support.SupportStatus(
version='5.0.0',
status=support.UNSUPPORTED
))))
def resource_mapping():

View File

@@ -11,18 +11,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
from urllib import parse
from heat.common import exception
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources.openstack.heat import none_resource
from heat.engine import support
class MonascaNotification(resource.Resource):
class MonascaNotification(none_resource.NoneResource):
"""Heat Template Resource for Monasca Notification.
A resource which is used to notificate if there is some alarm.
@@ -32,164 +26,20 @@ class MonascaNotification(resource.Resource):
"""
support_status = support.SupportStatus(
version='22.0.0',
status=support.DEPRECATED,
message=_('Monasca project was marked inactive'),
version='25.0.0',
status=support.HIDDEN,
message=_('Monasca project was retired'),
previous_status=support.SupportStatus(
version='7.0.0',
status=support.SUPPORTED,
version='22.0.0',
status=support.DEPRECATED,
message=_('Monasca project was marked inactive'),
previous_status=support.SupportStatus(
version='5.0.0',
status=support.SUPPORTED
)))
default_client_name = 'monasca'
entity = 'notifications'
# NOTE(sirushti): To conform to the autoscaling behaviour in heat, we set
# the default period interval during create/update to 60 for webhooks only.
_default_period_interval = 60
NOTIFICATION_TYPES = (
EMAIL, WEBHOOK, PAGERDUTY
) = (
'email', 'webhook', 'pagerduty'
)
PROPERTIES = (
NAME, TYPE, ADDRESS, PERIOD
) = (
'name', 'type', 'address', 'period'
)
properties_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the notification. By default, physical resource name '
'is used.'),
update_allowed=True
),
TYPE: properties.Schema(
properties.Schema.STRING,
_('Type of the notification.'),
update_allowed=True,
required=True,
constraints=[constraints.AllowedValues(
NOTIFICATION_TYPES
)]
),
ADDRESS: properties.Schema(
properties.Schema.STRING,
_('Address of the notification. It could be a valid email '
'address, url or service key based on notification type.'),
update_allowed=True,
required=True,
constraints=[constraints.Length(max=512)]
),
PERIOD: properties.Schema(
properties.Schema.INTEGER,
_('Interval in seconds to invoke webhooks if the alarm state '
'does not transition away from the defined trigger state. A '
'value of 0 will disable continuous notifications. This '
'property is only applicable for the webhook notification '
'type and has default period interval of 60 seconds.'),
support_status=support.SupportStatus(version='7.0.0'),
update_allowed=True,
constraints=[constraints.AllowedValues([0, 60])]
)
}
def _period_interval(self):
period = self.properties[self.PERIOD]
if period is None:
period = self._default_period_interval
return period
def validate(self):
super(MonascaNotification, self).validate()
if self.properties[self.PERIOD] is not None and (
self.properties[self.TYPE] != self.WEBHOOK):
msg = _('The period property can only be specified against a '
'Webhook Notification type.')
raise exception.StackValidationFailed(message=msg)
address = self.properties[self.ADDRESS]
if not address:
return
if self.properties[self.TYPE] == self.WEBHOOK:
try:
parsed_address = parse.urlparse(address)
except Exception:
msg = _('Address "%(addr)s" should have correct format '
'required by "%(wh)s" type of "%(type)s" '
'property') % {
'addr': address,
'wh': self.WEBHOOK,
'type': self.TYPE}
raise exception.StackValidationFailed(message=msg)
if not parsed_address.scheme:
msg = _('Address "%s" doesn\'t have required URL '
'scheme') % address
raise exception.StackValidationFailed(message=msg)
if not parsed_address.netloc:
msg = _('Address "%s" doesn\'t have required network '
'location') % address
raise exception.StackValidationFailed(message=msg)
if parsed_address.scheme not in ['http', 'https']:
msg = _('Address "%(addr)s" doesn\'t satisfies '
'allowed schemes: %(schemes)s') % {
'addr': address,
'schemes': ', '.join(['http', 'https'])
}
raise exception.StackValidationFailed(message=msg)
elif (self.properties[self.TYPE] == self.EMAIL and
not re.match(r'^\S+@\S+$', address)):
msg = _('Address "%(addr)s" doesn\'t satisfies allowed format for '
'"%(email)s" type of "%(type)s" property') % {
'addr': address,
'email': self.EMAIL,
'type': self.TYPE}
raise exception.StackValidationFailed(message=msg)
def handle_create(self):
args = dict(
name=(self.properties[self.NAME] or
self.physical_resource_name()),
type=self.properties[self.TYPE],
address=self.properties[self.ADDRESS],
)
if args['type'] == self.WEBHOOK:
args['period'] = self._period_interval()
notification = self.client().notifications.create(**args)
self.resource_id_set(notification['id'])
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
args = dict(notification_id=self.resource_id)
args['name'] = (prop_diff.get(self.NAME) or
self.properties[self.NAME])
args['type'] = (prop_diff.get(self.TYPE) or
self.properties[self.TYPE])
args['address'] = (prop_diff.get(self.ADDRESS) or
self.properties[self.ADDRESS])
if args['type'] == self.WEBHOOK:
updated_period = prop_diff.get(self.PERIOD)
args['period'] = (updated_period if updated_period is not None
else self._period_interval())
self.client().notifications.update(**args)
def handle_delete(self):
if self.resource_id is not None:
with self.client_plugin().ignore_not_found:
self.client().notifications.delete(
notification_id=self.resource_id)
version='7.0.0',
status=support.SUPPORTED,
previous_status=support.SupportStatus(
version='5.0.0',
status=support.SUPPORTED
))))
def resource_mapping():

View File

@@ -1,98 +0,0 @@
#
# 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.
from unittest import mock
from heat.common import exception as heat_exception
from heat.engine.clients.os import monasca as client_plugin
from heat.tests import common
from heat.tests import utils
class MonascaNotificationConstraintTest(common.HeatTestCase):
def test_expected_exceptions(self):
self.assertEqual(
(heat_exception.EntityNotFound,),
client_plugin.MonascaNotificationConstraint.expected_exceptions,
"MonascaNotificationConstraint expected exceptions error")
def test_constraint(self):
constraint = client_plugin.MonascaNotificationConstraint()
client_mock = mock.MagicMock()
client_plugin_mock = mock.MagicMock()
client_plugin_mock.get_notification.return_value = None
client_mock.client_plugin.return_value = client_plugin_mock
self.assertIsNone(constraint.validate_with_client(client_mock,
'notification_1'))
client_plugin_mock.get_notification.assert_called_once_with(
'notification_1')
class MonascaClientPluginTest(common.HeatTestCase):
def test_client(self):
context = utils.dummy_context()
plugin = context.clients.client_plugin('monasca')
client = plugin.client()
self.assertIsNotNone(client.metrics)
def test_client_uses_session(self):
context = mock.MagicMock()
monasca_client = client_plugin.MonascaClientPlugin(context=context)
self.assertIsNotNone(monasca_client._create())
class MonascaClientPluginNotificationTest(common.HeatTestCase):
sample_uuid = '477e8273-60a7-4c41-b683-fdb0bc7cd152'
sample_name = 'test-notification'
def _get_mock_notification(self):
notification = dict()
notification['id'] = self.sample_uuid
notification['name'] = self.sample_name
return notification
def setUp(self):
super(MonascaClientPluginNotificationTest, self).setUp()
self._client = mock.MagicMock()
self.client_plugin = client_plugin.MonascaClientPlugin(
context=mock.MagicMock()
)
@mock.patch.object(client_plugin.MonascaClientPlugin, 'client')
def test_get_notification(self, client_monasca):
mock_notification = self._get_mock_notification()
self._client.notifications.get.return_value = mock_notification
client_monasca.return_value = self._client
self.assertEqual(self.sample_uuid,
self.client_plugin.get_notification(
self.sample_uuid))
self._client.notifications.get.assert_called_once_with(
notification_id=self.sample_uuid)
@mock.patch.object(client_plugin.MonascaClientPlugin, 'client')
def test_get_notification_not_found(self, client_monasca):
self._client.notifications.get.side_effect = (
client_plugin.monasca_exc.NotFound)
client_monasca.return_value = self._client
ex = self.assertRaises(heat_exception.EntityNotFound,
self.client_plugin.get_notification,
self.sample_uuid)
msg = ("The Monasca Notification (%(name)s) could not be found." %
{'name': self.sample_uuid})
self.assertEqual(msg, str(ex))
self._client.notifications.get.assert_called_once_with(
notification_id=self.sample_uuid)

View File

@@ -1,198 +0,0 @@
#
# 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.
from unittest import mock
from heat.engine.clients.os import monasca as client_plugin
from heat.engine.resources.openstack.monasca import alarm_definition
from heat.engine import stack
from heat.engine import template
from heat.tests import common
from heat.tests import utils
sample_template = {
'heat_template_version': '2015-10-15',
'resources': {
'test_resource': {
'type': 'OS::Monasca::AlarmDefinition',
'properties': {
'name': 'sample_alarm_id',
'description': 'sample alarm def',
'expression': 'sample expression',
'match_by': ['match_by'],
'severity': 'low',
'ok_actions': ['sample_notification'],
'alarm_actions': ['sample_notification'],
'undetermined_actions': ['sample_notification'],
'actions_enabled': False
}
}
}
}
class MonascaAlarmDefinitionTest(common.HeatTestCase):
def setUp(self):
super(MonascaAlarmDefinitionTest, self).setUp()
self.ctx = utils.dummy_context()
self.stack = stack.Stack(
self.ctx, 'test_stack',
template.Template(sample_template)
)
self.test_resource = self.stack['test_resource']
# Mock client
self.test_client = mock.MagicMock()
self.test_resource.client = mock.MagicMock(
return_value=self.test_client)
# Mock client plugin
self.test_client_plugin = client_plugin.MonascaClientPlugin(self.ctx)
self.test_client_plugin._create = mock.MagicMock(
return_value=self.test_client)
self.test_resource.client_plugin = mock.MagicMock(
return_value=self.test_client_plugin)
self.test_client_plugin.get_notification = mock.MagicMock(
return_value='sample_notification')
def _get_mock_resource(self):
value = dict(id='477e8273-60a7-4c41-b683-fdb0bc7cd152')
return value
def test_resource_handle_create(self):
mock_alarm_create = self.test_client.alarm_definitions.create
mock_alarm_patch = self.test_client.alarm_definitions.patch
mock_resource = self._get_mock_resource()
mock_alarm_create.return_value = mock_resource
# validate the properties
self.assertEqual(
'sample_alarm_id',
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.NAME))
self.assertEqual(
'sample alarm def',
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.DESCRIPTION))
self.assertEqual(
'sample expression',
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.EXPRESSION))
self.assertEqual(
['match_by'],
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.MATCH_BY))
self.assertEqual(
'low',
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.SEVERITY))
self.assertEqual(
['sample_notification'],
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.OK_ACTIONS))
self.assertEqual(
['sample_notification'],
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.ALARM_ACTIONS))
self.assertEqual(
['sample_notification'],
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.UNDETERMINED_ACTIONS))
self.assertEqual(
False,
self.test_resource.properties.get(
alarm_definition.MonascaAlarmDefinition.ACTIONS_ENABLED))
self.test_resource.data_set = mock.Mock()
self.test_resource.handle_create()
# validate physical resource id
self.assertEqual(mock_resource['id'], self.test_resource.resource_id)
args = dict(
name='sample_alarm_id',
description='sample alarm def',
expression='sample expression',
match_by=['match_by'],
severity='low',
ok_actions=['sample_notification'],
alarm_actions=['sample_notification'],
undetermined_actions=['sample_notification']
)
mock_alarm_create.assert_called_once_with(**args)
mock_alarm_patch.assert_called_once_with(
alarm_id=self.test_resource.resource_id,
actions_enabled=False)
def test_resource_handle_update(self):
mock_alarm_patch = self.test_client.alarm_definitions.patch
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
prop_diff = {
alarm_definition.MonascaAlarmDefinition.NAME:
'name-updated',
alarm_definition.MonascaAlarmDefinition.DESCRIPTION:
'description-updated',
alarm_definition.MonascaAlarmDefinition.ACTIONS_ENABLED:
True,
alarm_definition.MonascaAlarmDefinition.SEVERITY:
'medium',
alarm_definition.MonascaAlarmDefinition.OK_ACTIONS:
['sample_notification'],
alarm_definition.MonascaAlarmDefinition.ALARM_ACTIONS:
['sample_notification'],
alarm_definition.MonascaAlarmDefinition.UNDETERMINED_ACTIONS:
['sample_notification']}
self.test_resource.handle_update(json_snippet=None,
tmpl_diff=None,
prop_diff=prop_diff)
args = dict(
alarm_id=self.test_resource.resource_id,
name='name-updated',
description='description-updated',
actions_enabled=True,
severity='medium',
ok_actions=['sample_notification'],
alarm_actions=['sample_notification'],
undetermined_actions=['sample_notification']
)
mock_alarm_patch.assert_called_once_with(**args)
def test_resource_handle_delete(self):
mock_alarm_delete = self.test_client.alarm_definitions.delete
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
mock_alarm_delete.return_value = None
self.assertIsNone(self.test_resource.handle_delete())
mock_alarm_delete.assert_called_once_with(
alarm_id=self.test_resource.resource_id
)
def test_resource_handle_delete_resource_id_is_none(self):
self.test_resource.resource_id = None
self.assertIsNone(self.test_resource.handle_delete())
def test_resource_handle_delete_not_found(self):
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
mock_alarm_delete = self.test_client.alarm_definitions.delete
mock_alarm_delete.side_effect = client_plugin.monasca_exc.NotFound
self.assertIsNone(self.test_resource.handle_delete())

View File

@@ -1,285 +0,0 @@
#
# 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.
from unittest import mock
from heat.common import exception
from heat.engine.cfn import functions as cfn_funcs
from heat.engine.clients.os import monasca as client_plugin
from heat.engine.resources.openstack.monasca import notification
from heat.engine import stack
from heat.engine import template
from heat.tests import common
from heat.tests import utils
sample_template = {
'heat_template_version': '2015-10-15',
'resources': {
'test_resource': {
'type': 'OS::Monasca::Notification',
'properties': {
'name': 'test-notification',
'type': 'webhook',
'address': 'http://localhost:80/',
'period': 60
}
}
}
}
class MonascaNotificationTest(common.HeatTestCase):
def setUp(self):
super(MonascaNotificationTest, self).setUp()
self.ctx = utils.dummy_context()
self.stack = stack.Stack(
self.ctx, 'test_stack',
template.Template(sample_template)
)
self.test_resource = self.stack['test_resource']
# Mock client
self.test_client = mock.MagicMock()
self.test_resource.client = mock.MagicMock(
return_value=self.test_client)
# Mock client plugin
self.test_client_plugin = client_plugin.MonascaClientPlugin(self.ctx)
self.test_client_plugin._create = mock.MagicMock(
return_value=self.test_client)
self.test_resource.client_plugin = mock.MagicMock(
return_value=self.test_client_plugin)
def _get_mock_resource(self):
value = dict(id='477e8273-60a7-4c41-b683-fdb0bc7cd152')
return value
def test_validate_success_no_period(self):
self.test_resource.properties.data.pop('period')
self.test_resource.validate()
def test_validate_invalid_type_with_period(self):
self.test_resource.properties.data['type'] = self.test_resource.EMAIL
self.assertRaises(exception.StackValidationFailed,
self.test_resource.validate)
def test_validate_no_scheme_address_for_get_attr(self):
self.test_resource.properties.data['type'] = self.test_resource.WEBHOOK
self.patchobject(cfn_funcs, 'GetAtt', return_value=None)
get_att = cfn_funcs.GetAtt(self.stack, 'Fn::GetAtt',
["ResourceA", "abc"])
self.test_resource.properties.data['address'] = get_att
self.assertIsNone(self.test_resource.validate())
def test_validate_no_scheme_address_for_webhook(self):
self.test_resource.properties.data['type'] = self.test_resource.WEBHOOK
self.test_resource.properties.data['address'] = 'abc@def.com'
ex = self.assertRaises(exception.StackValidationFailed,
self.test_resource.validate)
self.assertEqual('Address "abc@def.com" doesn\'t have '
'required URL scheme', str(ex))
def test_validate_no_netloc_address_for_webhook(self):
self.test_resource.properties.data['type'] = self.test_resource.WEBHOOK
self.test_resource.properties.data['address'] = 'https://'
ex = self.assertRaises(exception.StackValidationFailed,
self.test_resource.validate)
self.assertEqual('Address "https://" doesn\'t have '
'required network location', str(ex))
def test_validate_prohibited_address_for_webhook(self):
self.test_resource.properties.data['type'] = self.test_resource.WEBHOOK
self.test_resource.properties.data['address'] = 'ftp://127.0.0.1'
ex = self.assertRaises(exception.StackValidationFailed,
self.test_resource.validate)
self.assertEqual('Address "ftp://127.0.0.1" doesn\'t satisfies '
'allowed schemes: http, https', str(ex))
def test_validate_incorrect_address_for_email(self):
self.test_resource.properties.data['type'] = self.test_resource.EMAIL
self.test_resource.properties.data['address'] = 'abc#def.com'
self.test_resource.properties.data.pop('period')
ex = self.assertRaises(exception.StackValidationFailed,
self.test_resource.validate)
self.assertEqual('Address "abc#def.com" doesn\'t satisfies allowed '
'format for "email" type of "type" property',
str(ex))
def test_validate_invalid_address_parsing(self):
self.test_resource.properties.data['type'] = self.test_resource.WEBHOOK
self.test_resource.properties.data['address'] = "https://example.com]"
ex = self.assertRaises(exception.StackValidationFailed,
self.test_resource.validate)
self.assertEqual('Address "https://example.com]" should have correct '
'format required by "webhook" type of "type" '
'property', str(ex))
def test_resource_handle_create(self):
mock_notification_create = self.test_client.notifications.create
mock_resource = self._get_mock_resource()
mock_notification_create.return_value = mock_resource
# validate the properties
self.assertEqual(
'test-notification',
self.test_resource.properties.get(
notification.MonascaNotification.NAME))
self.assertEqual(
'webhook',
self.test_resource.properties.get(
notification.MonascaNotification.TYPE))
self.assertEqual(
'http://localhost:80/',
self.test_resource.properties.get(
notification.MonascaNotification.ADDRESS))
self.assertEqual(
60, self.test_resource.properties.get(
notification.MonascaNotification.PERIOD))
self.test_resource.data_set = mock.Mock()
self.test_resource.handle_create()
args = dict(
name='test-notification',
type='webhook',
address='http://localhost:80/',
period=60
)
mock_notification_create.assert_called_once_with(**args)
# validate physical resource id
self.assertEqual(mock_resource['id'], self.test_resource.resource_id)
def test_resource_handle_create_default_period(self):
self.test_resource.properties.data.pop('period')
mock_notification_create = self.test_client.notifications.create
self.test_resource.handle_create()
args = dict(
name='test-notification',
type='webhook',
address='http://localhost:80/',
period=60
)
mock_notification_create.assert_called_once_with(**args)
def test_resource_handle_create_no_period(self):
self.test_resource.properties.data.pop('period')
self.test_resource.properties.data['type'] = 'email'
self.test_resource.properties.data['address'] = 'abc@def.com'
mock_notification_create = self.test_client.notifications.create
self.test_resource.handle_create()
args = dict(
name='test-notification',
type='email',
address='abc@def.com'
)
mock_notification_create.assert_called_once_with(**args)
def test_resource_handle_update(self):
mock_notification_update = self.test_client.notifications.update
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
prop_diff = {notification.MonascaNotification.ADDRESS:
'http://localhost:1234/',
notification.MonascaNotification.NAME: 'name-updated',
notification.MonascaNotification.TYPE: 'webhook',
notification.MonascaNotification.PERIOD: 0}
self.test_resource.handle_update(json_snippet=None,
tmpl_diff=None,
prop_diff=prop_diff)
args = dict(
notification_id=self.test_resource.resource_id,
name='name-updated',
type='webhook',
address='http://localhost:1234/',
period=0
)
mock_notification_update.assert_called_once_with(**args)
def test_resource_handle_update_default_period(self):
mock_notification_update = self.test_client.notifications.update
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
self.test_resource.properties.data.pop('period')
prop_diff = {notification.MonascaNotification.ADDRESS:
'http://localhost:1234/',
notification.MonascaNotification.NAME: 'name-updated',
notification.MonascaNotification.TYPE: 'webhook'}
self.test_resource.handle_update(json_snippet=None,
tmpl_diff=None,
prop_diff=prop_diff)
args = dict(
notification_id=self.test_resource.resource_id,
name='name-updated',
type='webhook',
address='http://localhost:1234/',
period=60
)
mock_notification_update.assert_called_once_with(**args)
def test_resource_handle_update_no_period(self):
mock_notification_update = self.test_client.notifications.update
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
self.test_resource.properties.data.pop('period')
prop_diff = {notification.MonascaNotification.ADDRESS:
'abc@def.com',
notification.MonascaNotification.NAME: 'name-updated',
notification.MonascaNotification.TYPE: 'email'}
self.test_resource.handle_update(json_snippet=None,
tmpl_diff=None,
prop_diff=prop_diff)
args = dict(
notification_id=self.test_resource.resource_id,
name='name-updated',
type='email',
address='abc@def.com'
)
mock_notification_update.assert_called_once_with(**args)
def test_resource_handle_delete(self):
mock_notification_delete = self.test_client.notifications.delete
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
mock_notification_delete.return_value = None
self.assertIsNone(self.test_resource.handle_delete())
mock_notification_delete.assert_called_once_with(
notification_id=self.test_resource.resource_id
)
def test_resource_handle_delete_resource_id_is_none(self):
self.test_resource.resource_id = None
self.assertIsNone(self.test_resource.handle_delete())
def test_resource_handle_delete_not_found(self):
self.test_resource.resource_id = '477e8273-60a7-4c41-b683-fdb0bc7cd151'
mock_notification_delete = self.test_client.notifications.delete
mock_notification_delete.side_effect = (
client_plugin.monasca_exc.NotFound)
self.assertIsNone(self.test_resource.handle_delete())

View File

@@ -0,0 +1,11 @@
---
upgrade:
- |
Integration with monasca has been removed because the monasca project was
retired. Because of the removal, the following resource types are no longer
supported and now hidden.
- ``OS::Monasca::AlarmDefinition``
- ``OS::Monasca::Notifications``
Also, the options in ``[clients_monasca]`` section have been removed.

View File

@@ -42,7 +42,6 @@ python-keystoneclient>=3.8.0 # Apache-2.0
python-magnumclient>=2.3.0 # Apache-2.0
python-manilaclient>=1.16.0 # Apache-2.0
python-mistralclient>=3.1.0 # Apache-2.0
python-monascaclient>=1.12.0 # Apache-2.0
python-neutronclient>=7.7.0 # Apache-2.0
python-novaclient>=9.1.0 # Apache-2.0
python-octaviaclient>=1.8.0 # Apache-2.0

View File

@@ -82,7 +82,6 @@ heat.clients =
magnum = heat.engine.clients.os.magnum:MagnumClientPlugin
manila = heat.engine.clients.os.manila:ManilaClientPlugin
mistral = heat.engine.clients.os.mistral:MistralClientPlugin
monasca = heat.engine.clients.os.monasca:MonascaClientPlugin
nova = heat.engine.clients.os.nova:NovaClientPlugin
neutron = heat.engine.clients.os.neutron:NeutronClientPlugin
octavia = heat.engine.clients.os.octavia:OctaviaClientPlugin
@@ -131,7 +130,6 @@ heat.constraints =
manila.share_snapshot = heat.engine.clients.os.manila:ManilaShareSnapshotConstraint
manila.share_type = heat.engine.clients.os.manila:ManilaShareTypeConstraint
mistral.workflow = heat.engine.clients.os.mistral:WorkflowConstraint
monasca.notification = heat.engine.clients.os.monasca:MonascaNotificationConstraint
neutron.address_scope = heat.engine.clients.os.neutron.neutron_constraints:AddressScopeConstraint
neutron.flow_classifier = heat.engine.clients.os.neutron.neutron_constraints:FlowClassifierConstraint
neutron.lbaas.listener = heat.engine.clients.os.neutron.lbaas_constraints:ListenerConstraint