Merge "Improve validation in OS::Monasca::Notification"
This commit is contained in:
commit
cb47c597fa
@ -11,6 +11,9 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import re
|
||||
from six.moves import urllib
|
||||
|
||||
from heat.common import exception
|
||||
from heat.common.i18n import _
|
||||
from heat.engine import constraints
|
||||
@ -39,6 +42,10 @@ class MonascaNotification(resource.Resource):
|
||||
|
||||
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
|
||||
) = (
|
||||
@ -73,22 +80,27 @@ class MonascaNotification(resource.Resource):
|
||||
'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. The '
|
||||
'default value is a period interval of 60 seconds. A '
|
||||
'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.'),
|
||||
'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])],
|
||||
default=60,
|
||||
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 (
|
||||
@ -96,6 +108,41 @@ class MonascaNotification(resource.Resource):
|
||||
msg = _('The period property can only be specified against a '
|
||||
'Webhook Notification type.')
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
if self.properties[self.TYPE] == self.WEBHOOK:
|
||||
address = self.properties[self.ADDRESS]
|
||||
try:
|
||||
parsed_address = urllib.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('^.+@.+$', self.properties[self.ADDRESS])):
|
||||
msg = _('Address "%(addr)s" doesn\'t satisfies allowed format for '
|
||||
'"%(email)s" type of "%(type)s" property') % {
|
||||
'addr': self.properties[self.ADDRESS],
|
||||
'email': self.EMAIL,
|
||||
'type': self.TYPE}
|
||||
raise exception.StackValidationFailed(message=msg)
|
||||
|
||||
def handle_create(self):
|
||||
args = dict(
|
||||
@ -105,7 +152,7 @@ class MonascaNotification(resource.Resource):
|
||||
address=self.properties[self.ADDRESS],
|
||||
)
|
||||
if args['type'] == self.WEBHOOK:
|
||||
args['period'] = self.properties[self.PERIOD]
|
||||
args['period'] = self._period_interval()
|
||||
|
||||
notification = self.client().notifications.create(**args)
|
||||
self.resource_id_set(notification['id'])
|
||||
@ -125,7 +172,7 @@ class MonascaNotification(resource.Resource):
|
||||
if args['type'] == self.WEBHOOK:
|
||||
updated_period = prop_diff.get(self.PERIOD)
|
||||
args['period'] = (updated_period if updated_period is not None
|
||||
else self.properties[self.PERIOD])
|
||||
else self._period_interval())
|
||||
|
||||
self.client().notifications.update(**args)
|
||||
|
||||
|
@ -12,6 +12,7 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
import six
|
||||
|
||||
from heat.common import exception
|
||||
from heat.engine.clients.os import monasca as client_plugin
|
||||
@ -80,6 +81,49 @@ class MonascaNotificationTest(common.HeatTestCase):
|
||||
self.assertRaises(exception.StackValidationFailed,
|
||||
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', six.text_type(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', six.text_type(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', six.text_type(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',
|
||||
six.text_type(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', six.text_type(ex))
|
||||
|
||||
def test_resource_handle_create(self):
|
||||
mock_notification_create = self.test_client.notifications.create
|
||||
mock_resource = self._get_mock_resource()
|
||||
@ -130,6 +174,20 @@ class MonascaNotificationTest(common.HeatTestCase):
|
||||
)
|
||||
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'
|
||||
@ -176,6 +234,28 @@ class MonascaNotificationTest(common.HeatTestCase):
|
||||
)
|
||||
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'
|
||||
|
Loading…
Reference in New Issue
Block a user