Remove Monasca support

The monasca project was marked inactive during this cycle[1]. Its
project health has not been improved even at m-2 thus 2024.1 release
will not be created for this project.

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

Change-Id: I57f88f08bd0d160760703891b00aa9cb26dd1d98
This commit is contained in:
Takashi Kajinami 2023-11-09 18:39:31 +09:00
parent 30a1ca1137
commit 784bf1748e
11 changed files with 38 additions and 981 deletions

View File

@ -70,7 +70,6 @@ We have integration with
* https://opendev.org/openstack/python-manilaclient (shared file system service) * https://opendev.org/openstack/python-manilaclient (shared file system service)
* https://opendev.org/openstack/python-mistralclient (workflow service) * https://opendev.org/openstack/python-mistralclient (workflow service)
* https://opendev.org/openstack/python-zaqarclient (messaging 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-zunclient (container management service)
* https://opendev.org/openstack/python-blazarclient (reservation service) * https://opendev.org/openstack/python-blazarclient (reservation service)
* https://opendev.org/openstack/python-octaviaclient.git (Load-balancer service) * https://opendev.org/openstack/python-octaviaclient.git (Load-balancer service)

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. # under the License.
from heat.common.i18n import _ from heat.common.i18n import _
from heat.engine import constraints from heat.engine.resources.openstack.heat import none_resource
from heat.engine import properties
from heat.engine import resource
from heat.engine import support from heat.engine import support
class MonascaAlarmDefinition(resource.Resource): class MonascaAlarmDefinition(none_resource.NoneResource):
"""Heat Template Resource for Monasca Alarm definition. """Heat Template Resource for Monasca Alarm definition.
Monasca Alarm definition helps to define the required expression for Monasca Alarm definition helps to define the required expression for
@ -32,175 +30,19 @@ class MonascaAlarmDefinition(resource.Resource):
""" """
support_status = support.SupportStatus( support_status = support.SupportStatus(
version='22.0.0', version='23.0.0',
status=support.DEPRECATED, status=support.HIDDEN,
message=_('Monasca project was marked inactive'), message=_('Monasca project is no longer maintained'),
previous_status=support.SupportStatus( 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( previous_status=support.SupportStatus(
version='5.0.0', version='7.0.0',
status=support.UNSUPPORTED 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)
def resource_mapping(): def resource_mapping():

View File

@ -11,18 +11,12 @@
# 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 re
from urllib import parse
from heat.common import exception
from heat.common.i18n import _ from heat.common.i18n import _
from heat.engine import constraints from heat.engine.resources.openstack.heat import none_resource
from heat.engine import properties
from heat.engine import resource
from heat.engine import support from heat.engine import support
class MonascaNotification(resource.Resource): class MonascaNotification(none_resource.NoneResource):
"""Heat Template Resource for Monasca Notification. """Heat Template Resource for Monasca Notification.
A resource which is used to notificate if there is some alarm. A resource which is used to notificate if there is some alarm.
@ -32,164 +26,20 @@ class MonascaNotification(resource.Resource):
""" """
support_status = support.SupportStatus( support_status = support.SupportStatus(
version='22.0.0', version='23.0.0',
status=support.DEPRECATED, status=support.HIDDEN,
message=_('Monasca project was marked inactive'), message=_('Monasca project is no longer maintained'),
previous_status=support.SupportStatus( previous_status=support.SupportStatus(
version='7.0.0', version='22.0.0',
status=support.SUPPORTED, status=support.DEPRECATED,
message=_('Monasca project was marked inactive'),
previous_status=support.SupportStatus( previous_status=support.SupportStatus(
version='5.0.0', version='7.0.0',
status=support.SUPPORTED status=support.SUPPORTED,
))) previous_status=support.SupportStatus(
version='5.0.0',
default_client_name = 'monasca' status=support.SUPPORTED
))))
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)
def resource_mapping(): 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 is
no longer maintained. 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

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

View File

@ -84,7 +84,6 @@ heat.clients =
magnum = heat.engine.clients.os.magnum:MagnumClientPlugin magnum = heat.engine.clients.os.magnum:MagnumClientPlugin
manila = heat.engine.clients.os.manila:ManilaClientPlugin manila = heat.engine.clients.os.manila:ManilaClientPlugin
mistral = heat.engine.clients.os.mistral:MistralClientPlugin mistral = heat.engine.clients.os.mistral:MistralClientPlugin
monasca = heat.engine.clients.os.monasca:MonascaClientPlugin
nova = heat.engine.clients.os.nova:NovaClientPlugin nova = heat.engine.clients.os.nova:NovaClientPlugin
neutron = heat.engine.clients.os.neutron:NeutronClientPlugin neutron = heat.engine.clients.os.neutron:NeutronClientPlugin
octavia = heat.engine.clients.os.octavia:OctaviaClientPlugin octavia = heat.engine.clients.os.octavia:OctaviaClientPlugin
@ -134,7 +133,6 @@ heat.constraints =
manila.share_snapshot = heat.engine.clients.os.manila:ManilaShareSnapshotConstraint manila.share_snapshot = heat.engine.clients.os.manila:ManilaShareSnapshotConstraint
manila.share_type = heat.engine.clients.os.manila:ManilaShareTypeConstraint manila.share_type = heat.engine.clients.os.manila:ManilaShareTypeConstraint
mistral.workflow = heat.engine.clients.os.mistral:WorkflowConstraint 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.address_scope = heat.engine.clients.os.neutron.neutron_constraints:AddressScopeConstraint
neutron.flow_classifier = heat.engine.clients.os.neutron.neutron_constraints:FlowClassifierConstraint neutron.flow_classifier = heat.engine.clients.os.neutron.neutron_constraints:FlowClassifierConstraint
neutron.lbaas.listener = heat.engine.clients.os.neutron.lbaas_constraints:ListenerConstraint neutron.lbaas.listener = heat.engine.clients.os.neutron.lbaas_constraints:ListenerConstraint