Merge "Improve validation in OS::Monasca::Notification"

This commit is contained in:
Jenkins 2016-08-30 04:30:12 +00:00 committed by Gerrit Code Review
commit cb47c597fa
2 changed files with 134 additions and 7 deletions

View File

@ -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)

View File

@ -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'