Remove stack watch service
This removes the rpc api and related code. Change-Id: Ib89bcc3ff6a542f49467e2ad6c7e2a716a0dc2b4 Partial-Bug: #1743707
This commit is contained in:
parent
5bd856627a
commit
8db1b3ea41
@ -298,7 +298,6 @@ def map_remote_error(ex):
|
|||||||
'ResourceActionNotSupported',
|
'ResourceActionNotSupported',
|
||||||
'ResourceNotFound',
|
'ResourceNotFound',
|
||||||
'ResourceNotAvailable',
|
'ResourceNotAvailable',
|
||||||
'WatchRuleNotFound',
|
|
||||||
'StackValidationFailed',
|
'StackValidationFailed',
|
||||||
'InvalidSchemaError',
|
'InvalidSchemaError',
|
||||||
'InvalidTemplateReference',
|
'InvalidTemplateReference',
|
||||||
|
@ -74,10 +74,6 @@ def launch_engine(setup_logging=True):
|
|||||||
|
|
||||||
launcher = service.launch(cfg.CONF, srv, workers=workers,
|
launcher = service.launch(cfg.CONF, srv, workers=workers,
|
||||||
restart_method='mutate')
|
restart_method='mutate')
|
||||||
if cfg.CONF.enable_cloud_watch_lite:
|
|
||||||
# We create the periodic tasks here, which mean they are created
|
|
||||||
# only in the parent process when num_engine_workers>1 is specified
|
|
||||||
srv.create_periodic_tasks()
|
|
||||||
return launcher
|
return launcher
|
||||||
|
|
||||||
|
|
||||||
|
@ -182,6 +182,9 @@ engine_opts = [
|
|||||||
' for stack locking.')),
|
' for stack locking.')),
|
||||||
cfg.BoolOpt('enable_cloud_watch_lite',
|
cfg.BoolOpt('enable_cloud_watch_lite',
|
||||||
default=False,
|
default=False,
|
||||||
|
deprecated_for_removal=True,
|
||||||
|
deprecated_reason='Heat CloudWatch Service has been removed.',
|
||||||
|
deprecated_since='10.0.0',
|
||||||
help=_('Enable the legacy OS::Heat::CWLiteAlarm resource.')),
|
help=_('Enable the legacy OS::Heat::CWLiteAlarm resource.')),
|
||||||
cfg.BoolOpt('enable_stack_abandon',
|
cfg.BoolOpt('enable_stack_abandon',
|
||||||
default=False,
|
default=False,
|
||||||
|
@ -304,11 +304,6 @@ class ClientNotAvailable(HeatException):
|
|||||||
msg_fmt = _("The client (%(client_name)s) is not available.")
|
msg_fmt = _("The client (%(client_name)s) is not available.")
|
||||||
|
|
||||||
|
|
||||||
class WatchRuleNotFound(EntityNotFound):
|
|
||||||
"""Keep this for AWS compatibility."""
|
|
||||||
msg_fmt = _("The Watch Rule (%(watch_name)s) could not be found.")
|
|
||||||
|
|
||||||
|
|
||||||
class ResourceFailure(HeatExceptionWithPath):
|
class ResourceFailure(HeatExceptionWithPath):
|
||||||
def __init__(self, exception_or_error, resource, action=None):
|
def __init__(self, exception_or_error, resource, action=None):
|
||||||
self.resource = resource
|
self.resource = resource
|
||||||
|
@ -2449,9 +2449,6 @@ class Resource(status.ResourceStatus):
|
|||||||
# this is from Ceilometer.
|
# this is from Ceilometer.
|
||||||
auto = '%(previous)s to %(current)s (%(reason)s)' % details
|
auto = '%(previous)s to %(current)s (%(reason)s)' % details
|
||||||
return 'alarm state changed from %s' % auto
|
return 'alarm state changed from %s' % auto
|
||||||
elif 'state' in details:
|
|
||||||
# this is from watchrule
|
|
||||||
return 'alarm state changed to %(state)s' % details
|
|
||||||
|
|
||||||
return 'Unknown'
|
return 'Unknown'
|
||||||
|
|
||||||
|
@ -13,14 +13,12 @@
|
|||||||
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from heat.common import exception
|
|
||||||
from heat.common.i18n import _
|
from heat.common.i18n import _
|
||||||
from heat.engine import constraints
|
from heat.engine import constraints
|
||||||
from heat.engine import properties
|
from heat.engine import properties
|
||||||
from heat.engine.resources import alarm_base
|
from heat.engine.resources import alarm_base
|
||||||
from heat.engine.resources.openstack.heat import none_resource
|
from heat.engine.resources.openstack.heat import none_resource
|
||||||
from heat.engine import support
|
from heat.engine import support
|
||||||
from heat.engine import watchrule
|
|
||||||
|
|
||||||
|
|
||||||
class AodhAlarm(alarm_base.BaseAlarm):
|
class AodhAlarm(alarm_base.BaseAlarm):
|
||||||
@ -178,17 +176,6 @@ class AodhAlarm(alarm_base.BaseAlarm):
|
|||||||
alarm = self.client().alarm.create(props)
|
alarm = self.client().alarm.create(props)
|
||||||
self.resource_id_set(alarm['alarm_id'])
|
self.resource_id_set(alarm['alarm_id'])
|
||||||
|
|
||||||
# the watchrule below is for backwards compatibility.
|
|
||||||
# 1) so we don't create watch tasks unnecessarily
|
|
||||||
# 2) to support CW stats post, we will redirect the request
|
|
||||||
# to ceilometer.
|
|
||||||
wr = watchrule.WatchRule(context=self.context,
|
|
||||||
watch_name=self.physical_resource_name(),
|
|
||||||
rule=dict(self.properties),
|
|
||||||
stack_id=self.stack.id)
|
|
||||||
wr.state = wr.CEILOMETER_CONTROLLED
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||||
if prop_diff:
|
if prop_diff:
|
||||||
new_props = json_snippet.properties(self.properties_schema,
|
new_props = json_snippet.properties(self.properties_schema,
|
||||||
@ -209,19 +196,7 @@ class AodhAlarm(alarm_base.BaseAlarm):
|
|||||||
|
|
||||||
return record_reality
|
return record_reality
|
||||||
|
|
||||||
def handle_delete(self):
|
|
||||||
try:
|
|
||||||
wr = watchrule.WatchRule.load(
|
|
||||||
self.context, watch_name=self.physical_resource_name())
|
|
||||||
wr.destroy()
|
|
||||||
except exception.EntityNotFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
return super(AodhAlarm, self).handle_delete()
|
|
||||||
|
|
||||||
def handle_check(self):
|
def handle_check(self):
|
||||||
watch_name = self.physical_resource_name()
|
|
||||||
watchrule.WatchRule.load(self.context, watch_name=watch_name)
|
|
||||||
self.client().alarm.get(self.resource_id)
|
self.client().alarm.get(self.resource_id)
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,193 +13,23 @@
|
|||||||
|
|
||||||
from oslo_config import cfg
|
from oslo_config import cfg
|
||||||
|
|
||||||
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
|
||||||
from heat.engine import watchrule
|
|
||||||
|
|
||||||
|
|
||||||
class CloudWatchAlarm(resource.Resource):
|
class CloudWatchAlarm(none_resource.NoneResource):
|
||||||
PROPERTIES = (
|
|
||||||
COMPARISON_OPERATOR, ALARM_DESCRIPTION, EVALUATION_PERIODS,
|
|
||||||
METRIC_NAME, NAMESPACE, PERIOD, STATISTIC, ALARM_ACTIONS,
|
|
||||||
OKACTIONS, DIMENSIONS, INSUFFICIENT_DATA_ACTIONS, THRESHOLD,
|
|
||||||
UNITS,
|
|
||||||
) = (
|
|
||||||
'ComparisonOperator', 'AlarmDescription', 'EvaluationPeriods',
|
|
||||||
'MetricName', 'Namespace', 'Period', 'Statistic', 'AlarmActions',
|
|
||||||
'OKActions', 'Dimensions', 'InsufficientDataActions', 'Threshold',
|
|
||||||
'Units',
|
|
||||||
)
|
|
||||||
|
|
||||||
properties_schema = {
|
|
||||||
COMPARISON_OPERATOR: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Operator used to compare the specified Statistic with '
|
|
||||||
'Threshold.'),
|
|
||||||
constraints=[
|
|
||||||
constraints.AllowedValues(['GreaterThanOrEqualToThreshold',
|
|
||||||
'GreaterThanThreshold',
|
|
||||||
'LessThanThreshold',
|
|
||||||
'LessThanOrEqualToThreshold']),
|
|
||||||
],
|
|
||||||
required=True,
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
ALARM_DESCRIPTION: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Description for the alarm.'),
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
EVALUATION_PERIODS: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Number of periods to evaluate over.'),
|
|
||||||
required=True,
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
METRIC_NAME: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Metric name watched by the alarm.'),
|
|
||||||
required=True
|
|
||||||
),
|
|
||||||
NAMESPACE: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Namespace for the metric.'),
|
|
||||||
required=True
|
|
||||||
),
|
|
||||||
PERIOD: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Period (seconds) to evaluate over.'),
|
|
||||||
required=True,
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
STATISTIC: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Metric statistic to evaluate.'),
|
|
||||||
constraints=[
|
|
||||||
constraints.AllowedValues(['SampleCount', 'Average', 'Sum',
|
|
||||||
'Minimum', 'Maximum']),
|
|
||||||
],
|
|
||||||
required=True,
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
ALARM_ACTIONS: properties.Schema(
|
|
||||||
properties.Schema.LIST,
|
|
||||||
_('A list of actions to execute when state transitions to alarm.'),
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
OKACTIONS: properties.Schema(
|
|
||||||
properties.Schema.LIST,
|
|
||||||
_('A list of actions to execute when state transitions to ok.'),
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
DIMENSIONS: properties.Schema(
|
|
||||||
properties.Schema.LIST,
|
|
||||||
_('A list of dimensions (arbitrary name/value pairs) associated '
|
|
||||||
'with the metric.')
|
|
||||||
),
|
|
||||||
INSUFFICIENT_DATA_ACTIONS: properties.Schema(
|
|
||||||
properties.Schema.LIST,
|
|
||||||
_('A list of actions to execute when state transitions to '
|
|
||||||
'insufficient-data.'),
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
THRESHOLD: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Threshold to evaluate against.'),
|
|
||||||
required=True,
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
UNITS: properties.Schema(
|
|
||||||
properties.Schema.STRING,
|
|
||||||
_('Unit for the metric.'),
|
|
||||||
constraints=[
|
|
||||||
constraints.AllowedValues(['Seconds', 'Microseconds',
|
|
||||||
'Milliseconds', 'Bytes',
|
|
||||||
'Kilobytes', 'Megabytes',
|
|
||||||
'Gigabytes', 'Terabytes', 'Bits',
|
|
||||||
'Kilobits', 'Megabits',
|
|
||||||
'Gigabits', 'Terabits', 'Percent',
|
|
||||||
'Count', 'Bytes/Second',
|
|
||||||
'Kilobytes/Second',
|
|
||||||
'Megabytes/Second',
|
|
||||||
'Gigabytes/Second',
|
|
||||||
'Terabytes/Second', 'Bits/Second',
|
|
||||||
'Kilobits/Second',
|
|
||||||
'Megabits/Second',
|
|
||||||
'Gigabits/Second',
|
|
||||||
'Terabits/Second', 'Count/Second',
|
|
||||||
None]),
|
|
||||||
],
|
|
||||||
update_allowed=True
|
|
||||||
),
|
|
||||||
}
|
|
||||||
|
|
||||||
strict_dependency = False
|
|
||||||
|
|
||||||
support_status = support.SupportStatus(
|
support_status = support.SupportStatus(
|
||||||
status=support.HIDDEN,
|
status=support.HIDDEN,
|
||||||
message=_('OS::Heat::CWLiteAlarm is deprecated, '
|
message=_('OS::Heat::CWLiteAlarm resource has been removed '
|
||||||
'use OS::Aodh::Alarm instead.'),
|
'since version 10.0.0. Existing stacks can still '
|
||||||
|
'use it, where it would do nothing for update/delete.'),
|
||||||
version='5.0.0',
|
version='5.0.0',
|
||||||
previous_status=support.SupportStatus(
|
previous_status=support.SupportStatus(
|
||||||
status=support.DEPRECATED,
|
status=support.DEPRECATED,
|
||||||
version='2014.2'
|
version='2014.2')
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
def handle_create(self):
|
|
||||||
wr = watchrule.WatchRule(context=self.context,
|
|
||||||
watch_name=self.physical_resource_name(),
|
|
||||||
rule=dict(self.properties),
|
|
||||||
stack_id=self.stack.id)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
|
||||||
# If Properties has changed, update self.properties, so we
|
|
||||||
# get the new values during any subsequent adjustment
|
|
||||||
if prop_diff:
|
|
||||||
self.properties = json_snippet.properties(self.properties_schema,
|
|
||||||
self.context)
|
|
||||||
loader = watchrule.WatchRule.load
|
|
||||||
wr = loader(self.context,
|
|
||||||
watch_name=self.physical_resource_name())
|
|
||||||
|
|
||||||
wr.rule = dict(self.properties)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
def handle_delete(self):
|
|
||||||
try:
|
|
||||||
wr = watchrule.WatchRule.load(
|
|
||||||
self.context, watch_name=self.physical_resource_name())
|
|
||||||
wr.destroy()
|
|
||||||
except exception.EntityNotFound:
|
|
||||||
pass
|
|
||||||
|
|
||||||
def handle_suspend(self):
|
|
||||||
wr = watchrule.WatchRule.load(self.context,
|
|
||||||
watch_name=self.physical_resource_name())
|
|
||||||
wr.state_set(wr.SUSPENDED)
|
|
||||||
|
|
||||||
def handle_resume(self):
|
|
||||||
wr = watchrule.WatchRule.load(self.context,
|
|
||||||
watch_name=self.physical_resource_name())
|
|
||||||
# Just set to NODATA, which will be re-evaluated next periodic task
|
|
||||||
wr.state_set(wr.NODATA)
|
|
||||||
|
|
||||||
def handle_check(self):
|
|
||||||
watch_name = self.physical_resource_name()
|
|
||||||
watchrule.WatchRule.load(self.context, watch_name=watch_name)
|
|
||||||
|
|
||||||
def get_reference_id(self):
|
|
||||||
return self.physical_resource_name_or_FnGetRefId()
|
|
||||||
|
|
||||||
def physical_resource_name(self):
|
|
||||||
return '%s-%s' % (self.stack.name, self.name)
|
|
||||||
|
|
||||||
|
|
||||||
def resource_mapping():
|
def resource_mapping():
|
||||||
cfg.CONF.import_opt('enable_cloud_watch_lite', 'heat.common.config')
|
cfg.CONF.import_opt('enable_cloud_watch_lite', 'heat.common.config')
|
||||||
|
@ -15,7 +15,6 @@ import collections
|
|||||||
import datetime
|
import datetime
|
||||||
import functools
|
import functools
|
||||||
import itertools
|
import itertools
|
||||||
import os
|
|
||||||
import pydoc
|
import pydoc
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
@ -52,22 +51,18 @@ from heat.engine import parameter_groups
|
|||||||
from heat.engine import properties
|
from heat.engine import properties
|
||||||
from heat.engine import resources
|
from heat.engine import resources
|
||||||
from heat.engine import service_software_config
|
from heat.engine import service_software_config
|
||||||
from heat.engine import service_stack_watch
|
|
||||||
from heat.engine import stack as parser
|
from heat.engine import stack as parser
|
||||||
from heat.engine import stack_lock
|
from heat.engine import stack_lock
|
||||||
from heat.engine import stk_defn
|
from heat.engine import stk_defn
|
||||||
from heat.engine import support
|
from heat.engine import support
|
||||||
from heat.engine import template as templatem
|
from heat.engine import template as templatem
|
||||||
from heat.engine import update
|
from heat.engine import update
|
||||||
from heat.engine import watchrule
|
|
||||||
from heat.engine import worker
|
from heat.engine import worker
|
||||||
from heat.objects import event as event_object
|
from heat.objects import event as event_object
|
||||||
from heat.objects import resource as resource_objects
|
from heat.objects import resource as resource_objects
|
||||||
from heat.objects import service as service_objects
|
from heat.objects import service as service_objects
|
||||||
from heat.objects import snapshot as snapshot_object
|
from heat.objects import snapshot as snapshot_object
|
||||||
from heat.objects import stack as stack_object
|
from heat.objects import stack as stack_object
|
||||||
from heat.objects import watch_data
|
|
||||||
from heat.objects import watch_rule
|
|
||||||
from heat.rpc import api as rpc_api
|
from heat.rpc import api as rpc_api
|
||||||
from heat.rpc import worker_api as rpc_worker_api
|
from heat.rpc import worker_api as rpc_worker_api
|
||||||
|
|
||||||
@ -322,7 +317,6 @@ class EngineService(service.ServiceBase):
|
|||||||
|
|
||||||
# The following are initialized here, but assigned in start() which
|
# The following are initialized here, but assigned in start() which
|
||||||
# happens after the fork when spawning multiple worker processes
|
# happens after the fork when spawning multiple worker processes
|
||||||
self.stack_watch = None
|
|
||||||
self.listener = None
|
self.listener = None
|
||||||
self.worker_service = None
|
self.worker_service = None
|
||||||
self.engine_id = None
|
self.engine_id = None
|
||||||
@ -341,35 +335,6 @@ class EngineService(service.ServiceBase):
|
|||||||
'Please keep the same if you do not want to '
|
'Please keep the same if you do not want to '
|
||||||
'delegate subset roles when upgrading.')
|
'delegate subset roles when upgrading.')
|
||||||
|
|
||||||
def create_periodic_tasks(self):
|
|
||||||
LOG.debug("Starting periodic watch tasks pid=%s", os.getpid())
|
|
||||||
# Note with multiple workers, the parent process hasn't called start()
|
|
||||||
# so we need to create a ThreadGroupManager here for the periodic tasks
|
|
||||||
if self.thread_group_mgr is None:
|
|
||||||
self.thread_group_mgr = ThreadGroupManager()
|
|
||||||
self.stack_watch = service_stack_watch.StackWatch(
|
|
||||||
self.thread_group_mgr)
|
|
||||||
|
|
||||||
def create_watch_tasks():
|
|
||||||
while True:
|
|
||||||
try:
|
|
||||||
# Create a periodic_watcher_task per-stack
|
|
||||||
admin_context = context.get_admin_context()
|
|
||||||
stacks = stack_object.Stack.get_all(
|
|
||||||
admin_context,
|
|
||||||
show_hidden=True)
|
|
||||||
for s in stacks:
|
|
||||||
self.stack_watch.start_watch_task(s.id, admin_context)
|
|
||||||
LOG.info("Watch tasks created")
|
|
||||||
return
|
|
||||||
except Exception as e:
|
|
||||||
LOG.error("Watch task creation attempt failed, %s", e)
|
|
||||||
eventlet.sleep(5)
|
|
||||||
|
|
||||||
if self.manage_thread_grp is None:
|
|
||||||
self.manage_thread_grp = threadgroup.ThreadGroup()
|
|
||||||
self.manage_thread_grp.add_thread(create_watch_tasks)
|
|
||||||
|
|
||||||
def start(self):
|
def start(self):
|
||||||
self.engine_id = service_utils.generate_engine_id()
|
self.engine_id = service_utils.generate_engine_id()
|
||||||
if self.thread_group_mgr is None:
|
if self.thread_group_mgr is None:
|
||||||
@ -819,14 +784,6 @@ class EngineService(service.ServiceBase):
|
|||||||
elif stack.status != stack.FAILED:
|
elif stack.status != stack.FAILED:
|
||||||
stack.create(msg_queue=msg_queue)
|
stack.create(msg_queue=msg_queue)
|
||||||
|
|
||||||
if (stack.action in (stack.CREATE, stack.ADOPT)
|
|
||||||
and stack.status == stack.COMPLETE):
|
|
||||||
if self.stack_watch:
|
|
||||||
# Schedule a periodic watcher task for this stack
|
|
||||||
self.stack_watch.start_watch_task(stack.id, cnxt)
|
|
||||||
else:
|
|
||||||
LOG.info("Stack create failed, status %s", stack.status)
|
|
||||||
|
|
||||||
convergence = cfg.CONF.convergence_engine
|
convergence = cfg.CONF.convergence_engine
|
||||||
|
|
||||||
stack = self._parse_template_and_validate_stack(
|
stack = self._parse_template_and_validate_stack(
|
||||||
@ -2173,106 +2130,6 @@ class EngineService(service.ServiceBase):
|
|||||||
data = snapshot_object.Snapshot.get_all(cnxt, s.id)
|
data = snapshot_object.Snapshot.get_all(cnxt, s.id)
|
||||||
return [api.format_snapshot(snapshot) for snapshot in data]
|
return [api.format_snapshot(snapshot) for snapshot in data]
|
||||||
|
|
||||||
@context.request_context
|
|
||||||
def create_watch_data(self, cnxt, watch_name, stats_data):
|
|
||||||
"""Creates data for CloudWatch and WaitConditions.
|
|
||||||
|
|
||||||
This could be used by CloudWatch and WaitConditions
|
|
||||||
and treat HA service events like any other CloudWatch.
|
|
||||||
"""
|
|
||||||
def get_matching_watches():
|
|
||||||
if watch_name:
|
|
||||||
yield watchrule.WatchRule.load(cnxt, watch_name)
|
|
||||||
else:
|
|
||||||
for wr in watch_rule.WatchRule.get_all(cnxt):
|
|
||||||
if watchrule.rule_can_use_sample(wr, stats_data):
|
|
||||||
yield watchrule.WatchRule.load(cnxt, watch=wr)
|
|
||||||
|
|
||||||
rule_run = False
|
|
||||||
for rule in get_matching_watches():
|
|
||||||
rule.create_watch_data(stats_data)
|
|
||||||
rule_run = True
|
|
||||||
|
|
||||||
if not rule_run:
|
|
||||||
if watch_name is None:
|
|
||||||
watch_name = 'Unknown'
|
|
||||||
raise exception.EntityNotFound(entity='Watch Rule',
|
|
||||||
name=watch_name)
|
|
||||||
|
|
||||||
return stats_data
|
|
||||||
|
|
||||||
@context.request_context
|
|
||||||
def show_watch(self, cnxt, watch_name):
|
|
||||||
"""Return the attributes of one watch/alarm.
|
|
||||||
|
|
||||||
:param cnxt: RPC context.
|
|
||||||
:param watch_name: Name of the watch you want to see, or None to see
|
|
||||||
all.
|
|
||||||
"""
|
|
||||||
if watch_name:
|
|
||||||
wrn = [watch_name]
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
wrn = [w.name for w in watch_rule.WatchRule.get_all(cnxt)]
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.warning('show_watch (all) db error %s', ex)
|
|
||||||
return
|
|
||||||
|
|
||||||
wrs = [watchrule.WatchRule.load(cnxt, w) for w in wrn]
|
|
||||||
result = [api.format_watch(w) for w in wrs]
|
|
||||||
return result
|
|
||||||
|
|
||||||
@context.request_context
|
|
||||||
def show_watch_metric(self, cnxt, metric_namespace=None, metric_name=None):
|
|
||||||
"""Return the datapoints for a metric.
|
|
||||||
|
|
||||||
:param cnxt: RPC context.
|
|
||||||
:param metric_namespace: Name of the namespace you want to see, or None
|
|
||||||
to see all.
|
|
||||||
:param metric_name: Name of the metric you want to see, or None to see
|
|
||||||
all.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# DB API and schema does not yet allow us to easily query by
|
|
||||||
# namespace/metric, but we will want this at some point
|
|
||||||
# for now, the API can query all metric data and filter locally
|
|
||||||
if metric_namespace is not None or metric_name is not None:
|
|
||||||
LOG.error("Filtering by namespace/metric not yet supported")
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
wds = watch_data.WatchData.get_all(cnxt)
|
|
||||||
rule_names = {
|
|
||||||
r.id: r.name for r in watch_rule.WatchRule.get_all(cnxt)
|
|
||||||
}
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.warning('show_metric (all) db error %s', ex)
|
|
||||||
return
|
|
||||||
|
|
||||||
result = [api.format_watch_data(w, rule_names) for w in wds]
|
|
||||||
return result
|
|
||||||
|
|
||||||
@context.request_context
|
|
||||||
def set_watch_state(self, cnxt, watch_name, state):
|
|
||||||
"""Temporarily set the state of a given watch.
|
|
||||||
|
|
||||||
:param cnxt: RPC context.
|
|
||||||
:param watch_name: Name of the watch.
|
|
||||||
:param state: State (must be one defined in WatchRule class.
|
|
||||||
"""
|
|
||||||
wr = watchrule.WatchRule.load(cnxt, watch_name)
|
|
||||||
if wr.state == rpc_api.WATCH_STATE_CEILOMETER_CONTROLLED:
|
|
||||||
return
|
|
||||||
actions = wr.set_watch_state(state)
|
|
||||||
for action in actions:
|
|
||||||
self.thread_group_mgr.start(wr.stack_id, action)
|
|
||||||
|
|
||||||
# Return the watch with the state overridden to indicate success
|
|
||||||
# We do not update the timestamps as we are not modifying the DB
|
|
||||||
result = api.format_watch(wr)
|
|
||||||
result[rpc_api.WATCH_STATE_VALUE] = state
|
|
||||||
return result
|
|
||||||
|
|
||||||
@context.request_context
|
@context.request_context
|
||||||
def show_software_config(self, cnxt, config_id):
|
def show_software_config(self, cnxt, config_id):
|
||||||
return self.software_config.show_software_config(cnxt, config_id)
|
return self.software_config.show_software_config(cnxt, config_id)
|
||||||
|
@ -1,109 +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 oslo_log import log as logging
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
from heat.common import context
|
|
||||||
from heat.engine import stack
|
|
||||||
from heat.engine import stk_defn
|
|
||||||
from heat.engine import watchrule
|
|
||||||
from heat.objects import stack as stack_object
|
|
||||||
from heat.objects import watch_rule as watch_rule_object
|
|
||||||
from heat.rpc import api as rpc_api
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class StackWatch(object):
|
|
||||||
def __init__(self, thread_group_mgr):
|
|
||||||
self.thread_group_mgr = thread_group_mgr
|
|
||||||
|
|
||||||
def start_watch_task(self, stack_id, cnxt):
|
|
||||||
|
|
||||||
def stack_has_a_watchrule(sid):
|
|
||||||
wrs = watch_rule_object.WatchRule.get_all_by_stack(cnxt, sid)
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
start_watch_thread = False
|
|
||||||
for wr in wrs:
|
|
||||||
# reset the last_evaluated so we don't fire off alarms when
|
|
||||||
# the engine has not been running.
|
|
||||||
watch_rule_object.WatchRule.update_by_id(
|
|
||||||
cnxt, wr.id,
|
|
||||||
{'last_evaluated': now})
|
|
||||||
|
|
||||||
if wr.state != rpc_api.WATCH_STATE_CEILOMETER_CONTROLLED:
|
|
||||||
start_watch_thread = True
|
|
||||||
|
|
||||||
children = stack_object.Stack.get_all_by_owner_id(cnxt, sid)
|
|
||||||
for child in children:
|
|
||||||
if stack_has_a_watchrule(child.id):
|
|
||||||
start_watch_thread = True
|
|
||||||
|
|
||||||
return start_watch_thread
|
|
||||||
|
|
||||||
if stack_has_a_watchrule(stack_id):
|
|
||||||
self.thread_group_mgr.add_timer(
|
|
||||||
stack_id,
|
|
||||||
self.periodic_watcher_task,
|
|
||||||
sid=stack_id)
|
|
||||||
|
|
||||||
def check_stack_watches(self, sid):
|
|
||||||
# Use admin_context for stack_get to defeat tenant
|
|
||||||
# scoping otherwise we fail to retrieve the stack
|
|
||||||
LOG.debug("Periodic watcher task for stack %s", sid)
|
|
||||||
admin_context = context.get_admin_context()
|
|
||||||
db_stack = stack_object.Stack.get_by_id(admin_context,
|
|
||||||
sid)
|
|
||||||
if not db_stack:
|
|
||||||
LOG.error("Unable to retrieve stack %s for periodic task", sid)
|
|
||||||
return
|
|
||||||
stk = stack.Stack.load(admin_context, stack=db_stack,
|
|
||||||
use_stored_context=True)
|
|
||||||
|
|
||||||
# recurse into any nested stacks.
|
|
||||||
children = stack_object.Stack.get_all_by_owner_id(admin_context, sid)
|
|
||||||
for child in children:
|
|
||||||
self.check_stack_watches(child.id)
|
|
||||||
|
|
||||||
# Get all watchrules for this stack and evaluate them
|
|
||||||
try:
|
|
||||||
wrs = watch_rule_object.WatchRule.get_all_by_stack(admin_context,
|
|
||||||
sid)
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.warning('periodic_task db error watch rule removed? %s', ex)
|
|
||||||
return
|
|
||||||
|
|
||||||
def run_alarm_action(stk, actions, details):
|
|
||||||
for action in actions:
|
|
||||||
action(details=details)
|
|
||||||
for res in stk._explicit_dependencies():
|
|
||||||
res.metadata_update()
|
|
||||||
stk_defn.update_resource_data(stk.defn, res.name,
|
|
||||||
res.node_data())
|
|
||||||
|
|
||||||
for wr in wrs:
|
|
||||||
rule = watchrule.WatchRule.load(stk.context, watch=wr)
|
|
||||||
actions = rule.evaluate()
|
|
||||||
if actions:
|
|
||||||
self.thread_group_mgr.start(sid, run_alarm_action, stk,
|
|
||||||
actions, rule.get_details())
|
|
||||||
|
|
||||||
def periodic_watcher_task(self, sid):
|
|
||||||
"""Evaluate all watch-rules defined for stack ID.
|
|
||||||
|
|
||||||
Periodic task, created for each stack, triggers watch-rule evaluation
|
|
||||||
for all rules defined for the stack sid = stack ID.
|
|
||||||
"""
|
|
||||||
self.check_stack_watches(sid)
|
|
@ -1,396 +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.
|
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
from oslo_log import log as logging
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
from heat.common import exception
|
|
||||||
from heat.common.i18n import _
|
|
||||||
from heat.engine import stack
|
|
||||||
from heat.engine import timestamp
|
|
||||||
from heat.objects import stack as stack_object
|
|
||||||
from heat.objects import watch_data as watch_data_objects
|
|
||||||
from heat.objects import watch_rule as watch_rule_objects
|
|
||||||
from heat.rpc import api as rpc_api
|
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class WatchRule(object):
|
|
||||||
WATCH_STATES = (
|
|
||||||
ALARM,
|
|
||||||
NORMAL,
|
|
||||||
NODATA,
|
|
||||||
SUSPENDED,
|
|
||||||
CEILOMETER_CONTROLLED,
|
|
||||||
) = (
|
|
||||||
rpc_api.WATCH_STATE_ALARM,
|
|
||||||
rpc_api.WATCH_STATE_OK,
|
|
||||||
rpc_api.WATCH_STATE_NODATA,
|
|
||||||
rpc_api.WATCH_STATE_SUSPENDED,
|
|
||||||
rpc_api.WATCH_STATE_CEILOMETER_CONTROLLED,
|
|
||||||
)
|
|
||||||
ACTION_MAP = {ALARM: 'AlarmActions',
|
|
||||||
NORMAL: 'OKActions',
|
|
||||||
NODATA: 'InsufficientDataActions'}
|
|
||||||
|
|
||||||
created_at = timestamp.Timestamp(watch_rule_objects.WatchRule.get_by_id,
|
|
||||||
'created_at')
|
|
||||||
updated_at = timestamp.Timestamp(watch_rule_objects.WatchRule.get_by_id,
|
|
||||||
'updated_at')
|
|
||||||
|
|
||||||
def __init__(self, context, watch_name, rule, stack_id=None,
|
|
||||||
state=NODATA, wid=None, watch_data=None,
|
|
||||||
last_evaluated=None):
|
|
||||||
|
|
||||||
self.context = context
|
|
||||||
self.now = timeutils.utcnow()
|
|
||||||
self.name = watch_name
|
|
||||||
self.state = state
|
|
||||||
self.rule = rule
|
|
||||||
self.stack_id = stack_id
|
|
||||||
period = 0
|
|
||||||
if 'Period' in rule:
|
|
||||||
period = int(rule['Period'])
|
|
||||||
elif 'period' in rule:
|
|
||||||
period = int(rule['period'])
|
|
||||||
self.timeperiod = datetime.timedelta(seconds=period)
|
|
||||||
self.id = wid
|
|
||||||
self.watch_data = watch_data or []
|
|
||||||
self.last_evaluated = last_evaluated or timeutils.utcnow()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def load(cls, context, watch_name=None, watch=None):
|
|
||||||
"""Load the watchrule object.
|
|
||||||
|
|
||||||
The object can be loaded either from the DB by name or from an existing
|
|
||||||
DB object.
|
|
||||||
"""
|
|
||||||
if watch is None:
|
|
||||||
try:
|
|
||||||
watch = watch_rule_objects.WatchRule.get_by_name(context,
|
|
||||||
watch_name)
|
|
||||||
except Exception as ex:
|
|
||||||
LOG.warning('WatchRule.load (%(watch_name)s) db error %(ex)s',
|
|
||||||
{'watch_name': watch_name, 'ex': ex})
|
|
||||||
if watch is None:
|
|
||||||
raise exception.EntityNotFound(entity='Watch Rule',
|
|
||||||
name=watch_name)
|
|
||||||
else:
|
|
||||||
return cls(context=context,
|
|
||||||
watch_name=watch.name,
|
|
||||||
rule=watch.rule,
|
|
||||||
stack_id=watch.stack_id,
|
|
||||||
state=watch.state,
|
|
||||||
wid=watch.id,
|
|
||||||
watch_data=watch.watch_data,
|
|
||||||
last_evaluated=watch.last_evaluated)
|
|
||||||
|
|
||||||
def store(self):
|
|
||||||
"""Store the watchrule in the database and return its ID.
|
|
||||||
|
|
||||||
If self.id is set, we update the existing rule.
|
|
||||||
"""
|
|
||||||
|
|
||||||
wr_values = {
|
|
||||||
'name': self.name,
|
|
||||||
'rule': self.rule,
|
|
||||||
'state': self.state,
|
|
||||||
'stack_id': self.stack_id
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.id is None:
|
|
||||||
wr = watch_rule_objects.WatchRule.create(self.context, wr_values)
|
|
||||||
self.id = wr.id
|
|
||||||
else:
|
|
||||||
watch_rule_objects.WatchRule.update_by_id(self.context, self.id,
|
|
||||||
wr_values)
|
|
||||||
|
|
||||||
def destroy(self):
|
|
||||||
"""Delete the watchrule from the database."""
|
|
||||||
if self.id is not None:
|
|
||||||
watch_rule_objects.WatchRule.delete(self.context, self.id)
|
|
||||||
|
|
||||||
def do_data_cmp(self, data, threshold):
|
|
||||||
op = self.rule['ComparisonOperator']
|
|
||||||
if op == 'GreaterThanThreshold':
|
|
||||||
return data > threshold
|
|
||||||
elif op == 'GreaterThanOrEqualToThreshold':
|
|
||||||
return data >= threshold
|
|
||||||
elif op == 'LessThanThreshold':
|
|
||||||
return data < threshold
|
|
||||||
elif op == 'LessThanOrEqualToThreshold':
|
|
||||||
return data <= threshold
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def do_Maximum(self):
|
|
||||||
data = 0
|
|
||||||
have_data = False
|
|
||||||
for d in self.watch_data:
|
|
||||||
if d.created_at < self.now - self.timeperiod:
|
|
||||||
continue
|
|
||||||
if not have_data:
|
|
||||||
data = float(d.data[self.rule['MetricName']]['Value'])
|
|
||||||
have_data = True
|
|
||||||
if float(d.data[self.rule['MetricName']]['Value']) > data:
|
|
||||||
data = float(d.data[self.rule['MetricName']]['Value'])
|
|
||||||
|
|
||||||
if not have_data:
|
|
||||||
return self.NODATA
|
|
||||||
|
|
||||||
if self.do_data_cmp(data,
|
|
||||||
float(self.rule['Threshold'])):
|
|
||||||
return self.ALARM
|
|
||||||
else:
|
|
||||||
return self.NORMAL
|
|
||||||
|
|
||||||
def do_Minimum(self):
|
|
||||||
data = 0
|
|
||||||
have_data = False
|
|
||||||
for d in self.watch_data:
|
|
||||||
if d.created_at < self.now - self.timeperiod:
|
|
||||||
continue
|
|
||||||
if not have_data:
|
|
||||||
data = float(d.data[self.rule['MetricName']]['Value'])
|
|
||||||
have_data = True
|
|
||||||
elif float(d.data[self.rule['MetricName']]['Value']) < data:
|
|
||||||
data = float(d.data[self.rule['MetricName']]['Value'])
|
|
||||||
|
|
||||||
if not have_data:
|
|
||||||
return self.NODATA
|
|
||||||
|
|
||||||
if self.do_data_cmp(data,
|
|
||||||
float(self.rule['Threshold'])):
|
|
||||||
return self.ALARM
|
|
||||||
else:
|
|
||||||
return self.NORMAL
|
|
||||||
|
|
||||||
def do_SampleCount(self):
|
|
||||||
"""Count all samples within the specified period."""
|
|
||||||
data = 0
|
|
||||||
for d in self.watch_data:
|
|
||||||
if d.created_at < self.now - self.timeperiod:
|
|
||||||
continue
|
|
||||||
data = data + 1
|
|
||||||
|
|
||||||
if self.do_data_cmp(data,
|
|
||||||
float(self.rule['Threshold'])):
|
|
||||||
return self.ALARM
|
|
||||||
else:
|
|
||||||
return self.NORMAL
|
|
||||||
|
|
||||||
def do_Average(self):
|
|
||||||
data = 0
|
|
||||||
samples = 0
|
|
||||||
for d in self.watch_data:
|
|
||||||
if d.created_at < self.now - self.timeperiod:
|
|
||||||
continue
|
|
||||||
samples = samples + 1
|
|
||||||
data = data + float(d.data[self.rule['MetricName']]['Value'])
|
|
||||||
|
|
||||||
if samples == 0:
|
|
||||||
return self.NODATA
|
|
||||||
|
|
||||||
data = data / samples
|
|
||||||
if self.do_data_cmp(data,
|
|
||||||
float(self.rule['Threshold'])):
|
|
||||||
return self.ALARM
|
|
||||||
else:
|
|
||||||
return self.NORMAL
|
|
||||||
|
|
||||||
def do_Sum(self):
|
|
||||||
data = 0
|
|
||||||
for d in self.watch_data:
|
|
||||||
if d.created_at < self.now - self.timeperiod:
|
|
||||||
LOG.debug('ignoring %s', str(d.data))
|
|
||||||
continue
|
|
||||||
data = data + float(d.data[self.rule['MetricName']]['Value'])
|
|
||||||
|
|
||||||
if self.do_data_cmp(data,
|
|
||||||
float(self.rule['Threshold'])):
|
|
||||||
return self.ALARM
|
|
||||||
else:
|
|
||||||
return self.NORMAL
|
|
||||||
|
|
||||||
def get_alarm_state(self):
|
|
||||||
fn = getattr(self, 'do_%s' % self.rule['Statistic'])
|
|
||||||
return fn()
|
|
||||||
|
|
||||||
def evaluate(self):
|
|
||||||
if self.state in [self.CEILOMETER_CONTROLLED, self.SUSPENDED]:
|
|
||||||
return []
|
|
||||||
# has enough time progressed to run the rule
|
|
||||||
self.now = timeutils.utcnow()
|
|
||||||
if self.now < (self.last_evaluated + self.timeperiod):
|
|
||||||
return []
|
|
||||||
return self.run_rule()
|
|
||||||
|
|
||||||
def get_details(self):
|
|
||||||
return {'alarm': self.name,
|
|
||||||
'state': self.state}
|
|
||||||
|
|
||||||
def run_rule(self):
|
|
||||||
new_state = self.get_alarm_state()
|
|
||||||
actions = self.rule_actions(new_state)
|
|
||||||
self.state = new_state
|
|
||||||
|
|
||||||
self.last_evaluated = self.now
|
|
||||||
self.store()
|
|
||||||
return actions
|
|
||||||
|
|
||||||
def rule_actions(self, new_state):
|
|
||||||
LOG.info('WATCH: stack:%(stack)s, watch_name:%(watch_name)s, '
|
|
||||||
'new_state:%(new_state)s', {'stack': self.stack_id,
|
|
||||||
'watch_name': self.name,
|
|
||||||
'new_state': new_state})
|
|
||||||
actions = []
|
|
||||||
if self.ACTION_MAP[new_state] not in self.rule:
|
|
||||||
LOG.info('no action for new state %s', new_state)
|
|
||||||
else:
|
|
||||||
s = stack_object.Stack.get_by_id(
|
|
||||||
self.context,
|
|
||||||
self.stack_id)
|
|
||||||
stk = stack.Stack.load(self.context, stack=s)
|
|
||||||
if (stk.action != stk.DELETE
|
|
||||||
and stk.status == stk.COMPLETE):
|
|
||||||
for refid in self.rule[self.ACTION_MAP[new_state]]:
|
|
||||||
actions.append(stk.resource_by_refid(refid).signal)
|
|
||||||
else:
|
|
||||||
LOG.warning("Could not process watch state %s for stack",
|
|
||||||
new_state)
|
|
||||||
return actions
|
|
||||||
|
|
||||||
def _to_ceilometer(self, data):
|
|
||||||
clients = self.context.clients
|
|
||||||
sample = {}
|
|
||||||
sample['counter_type'] = 'gauge'
|
|
||||||
|
|
||||||
for k, d in iter(data.items()):
|
|
||||||
if k == 'Namespace':
|
|
||||||
continue
|
|
||||||
sample['counter_name'] = k
|
|
||||||
sample['counter_volume'] = d['Value']
|
|
||||||
sample['counter_unit'] = d['Unit']
|
|
||||||
dims = d.get('Dimensions', {})
|
|
||||||
if isinstance(dims, list):
|
|
||||||
dims = dims[0]
|
|
||||||
sample['resource_metadata'] = dims
|
|
||||||
sample['resource_id'] = dims.get('InstanceId')
|
|
||||||
LOG.debug('new sample:%(k)s data:%(sample)s', {
|
|
||||||
'k': k, 'sample': sample})
|
|
||||||
clients.client('ceilometer').samples.create(**sample)
|
|
||||||
|
|
||||||
def create_watch_data(self, data):
|
|
||||||
if self.state == self.CEILOMETER_CONTROLLED:
|
|
||||||
# this is a short term measure for those that have cfn-push-stats
|
|
||||||
# within their templates, but want to use Ceilometer alarms.
|
|
||||||
|
|
||||||
self._to_ceilometer(data)
|
|
||||||
return
|
|
||||||
|
|
||||||
if self.state == self.SUSPENDED:
|
|
||||||
LOG.debug('Ignoring metric data for %s, SUSPENDED state',
|
|
||||||
self.name)
|
|
||||||
return []
|
|
||||||
|
|
||||||
if self.rule['MetricName'] not in data:
|
|
||||||
# Our simplified cloudwatch implementation only expects a single
|
|
||||||
# Metric associated with each alarm, but some cfn-push-stats
|
|
||||||
# options, e.g --haproxy try to push multiple metrics when we
|
|
||||||
# actually only care about one (the one we're alarming on)
|
|
||||||
# so just ignore any data which doesn't contain MetricName
|
|
||||||
LOG.debug('Ignoring metric data (only accept %(metric)s) '
|
|
||||||
': %(data)s' % {'metric': self.rule['MetricName'],
|
|
||||||
'data': data})
|
|
||||||
return
|
|
||||||
|
|
||||||
watch_data = {
|
|
||||||
'data': data,
|
|
||||||
'watch_rule_id': self.id
|
|
||||||
}
|
|
||||||
wd = watch_data_objects.WatchData.create(self.context, watch_data)
|
|
||||||
LOG.debug('new watch:%(name)s data:%(data)s'
|
|
||||||
% {'name': self.name, 'data': str(wd.data)})
|
|
||||||
|
|
||||||
def state_set(self, state):
|
|
||||||
"""Persistently store the watch state."""
|
|
||||||
if state not in self.WATCH_STATES:
|
|
||||||
raise ValueError(_("Invalid watch state %s") % state)
|
|
||||||
|
|
||||||
self.state = state
|
|
||||||
self.store()
|
|
||||||
|
|
||||||
def set_watch_state(self, state):
|
|
||||||
"""Temporarily set the watch state.
|
|
||||||
|
|
||||||
:returns: list of functions to be scheduled in the stack ThreadGroup
|
|
||||||
for the specified state.
|
|
||||||
"""
|
|
||||||
|
|
||||||
if state not in self.WATCH_STATES:
|
|
||||||
raise ValueError(_('Unknown watch state %s') % state)
|
|
||||||
|
|
||||||
actions = []
|
|
||||||
if state != self.state:
|
|
||||||
actions = self.rule_actions(state)
|
|
||||||
if actions:
|
|
||||||
LOG.debug("Overriding state %(self_state)s for watch "
|
|
||||||
"%(name)s with %(state)s"
|
|
||||||
% {'self_state': self.state, 'name': self.name,
|
|
||||||
'state': state})
|
|
||||||
else:
|
|
||||||
LOG.warning("Unable to override state %(state)s for "
|
|
||||||
"watch %(name)s", {'state': self.state,
|
|
||||||
'name': self.name})
|
|
||||||
return actions
|
|
||||||
|
|
||||||
|
|
||||||
def rule_can_use_sample(wr, stats_data):
|
|
||||||
def match_dimesions(rule, data):
|
|
||||||
for k, v in iter(rule.items()):
|
|
||||||
if k not in data:
|
|
||||||
return False
|
|
||||||
elif v != data[k]:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
if wr.state == WatchRule.SUSPENDED:
|
|
||||||
return False
|
|
||||||
if wr.state == WatchRule.CEILOMETER_CONTROLLED:
|
|
||||||
metric = wr.rule['meter_name']
|
|
||||||
rule_dims = {}
|
|
||||||
for k, v in iter(wr.rule.get('matching_metadata', {}).items()):
|
|
||||||
name = k.split('.')[-1]
|
|
||||||
rule_dims[name] = v
|
|
||||||
else:
|
|
||||||
metric = wr.rule['MetricName']
|
|
||||||
rule_dims = dict((d['Name'], d['Value'])
|
|
||||||
for d in wr.rule.get('Dimensions', []))
|
|
||||||
|
|
||||||
if metric not in stats_data:
|
|
||||||
return False
|
|
||||||
|
|
||||||
for k, v in iter(stats_data.items()):
|
|
||||||
if k == 'Namespace':
|
|
||||||
continue
|
|
||||||
if k == metric:
|
|
||||||
data_dims = v.get('Dimensions', {})
|
|
||||||
if isinstance(data_dims, list):
|
|
||||||
data_dims = data_dims[0]
|
|
||||||
if match_dimesions(rule_dims, data_dims):
|
|
||||||
return True
|
|
||||||
return False
|
|
@ -1,60 +0,0 @@
|
|||||||
# Copyright 2014 Intel Corp.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""WatchData object."""
|
|
||||||
|
|
||||||
from oslo_versionedobjects import base
|
|
||||||
from oslo_versionedobjects import fields
|
|
||||||
|
|
||||||
from heat.db.sqlalchemy import api as db_api
|
|
||||||
from heat.objects import base as heat_base
|
|
||||||
from heat.objects import fields as heat_fields
|
|
||||||
|
|
||||||
|
|
||||||
class WatchData(
|
|
||||||
heat_base.HeatObject,
|
|
||||||
base.VersionedObjectDictCompat,
|
|
||||||
):
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'id': fields.IntegerField(),
|
|
||||||
'data': heat_fields.JsonField(nullable=True),
|
|
||||||
'watch_rule_id': fields.StringField(),
|
|
||||||
'created_at': fields.DateTimeField(read_only=True),
|
|
||||||
'updated_at': fields.DateTimeField(nullable=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _from_db_object(context, rule, db_data):
|
|
||||||
for field in rule.fields:
|
|
||||||
rule[field] = db_data[field]
|
|
||||||
rule._context = context
|
|
||||||
rule.obj_reset_changes()
|
|
||||||
return rule
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create(cls, context, values):
|
|
||||||
db_data = db_api.watch_data_create(context, values)
|
|
||||||
return cls._from_db_object(context, cls(), db_data)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_all(cls, context):
|
|
||||||
return [cls._from_db_object(context, cls(), db_data)
|
|
||||||
for db_data in db_api.watch_data_get_all(context)]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_all_by_watch_rule_id(cls, context, watch_rule_id):
|
|
||||||
return (cls._from_db_object(context, cls(), db_data)
|
|
||||||
for db_data in db_api.watch_data_get_all_by_watch_rule_id(
|
|
||||||
context, watch_rule_id))
|
|
@ -1,87 +0,0 @@
|
|||||||
# Copyright 2014 Intel Corp.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
"""WatchRule object."""
|
|
||||||
|
|
||||||
from oslo_versionedobjects import base
|
|
||||||
from oslo_versionedobjects import fields
|
|
||||||
|
|
||||||
from heat.db.sqlalchemy import api as db_api
|
|
||||||
from heat.objects import base as heat_base
|
|
||||||
from heat.objects import fields as heat_fields
|
|
||||||
from heat.objects import watch_data
|
|
||||||
|
|
||||||
|
|
||||||
class WatchRule(
|
|
||||||
heat_base.HeatObject,
|
|
||||||
base.VersionedObjectDictCompat,
|
|
||||||
):
|
|
||||||
|
|
||||||
fields = {
|
|
||||||
'id': fields.IntegerField(),
|
|
||||||
'name': fields.StringField(nullable=True),
|
|
||||||
'rule': heat_fields.JsonField(nullable=True),
|
|
||||||
'state': fields.StringField(nullable=True),
|
|
||||||
'last_evaluated': fields.DateTimeField(nullable=True),
|
|
||||||
'stack_id': fields.StringField(),
|
|
||||||
'watch_data': fields.ListOfObjectsField(watch_data.WatchData),
|
|
||||||
'created_at': fields.DateTimeField(read_only=True),
|
|
||||||
'updated_at': fields.DateTimeField(nullable=True),
|
|
||||||
}
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _from_db_object(context, rule, db_rule):
|
|
||||||
for field in rule.fields:
|
|
||||||
if field == 'watch_data':
|
|
||||||
rule[field] = watch_data.WatchData.get_all_by_watch_rule_id(
|
|
||||||
context, db_rule['id'])
|
|
||||||
else:
|
|
||||||
rule[field] = db_rule[field]
|
|
||||||
rule._context = context
|
|
||||||
rule.obj_reset_changes()
|
|
||||||
return rule
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_by_id(cls, context, rule_id):
|
|
||||||
db_rule = db_api.watch_rule_get(context, rule_id)
|
|
||||||
return cls._from_db_object(context, cls(), db_rule)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_by_name(cls, context, watch_rule_name):
|
|
||||||
db_rule = db_api.watch_rule_get_by_name(context, watch_rule_name)
|
|
||||||
return cls._from_db_object(context, cls(), db_rule)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_all(cls, context):
|
|
||||||
return [cls._from_db_object(context, cls(), db_rule)
|
|
||||||
for db_rule in db_api.watch_rule_get_all(context)]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def get_all_by_stack(cls, context, stack_id):
|
|
||||||
return [cls._from_db_object(context, cls(), db_rule)
|
|
||||||
for db_rule in db_api.watch_rule_get_all_by_stack(context,
|
|
||||||
stack_id)]
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def update_by_id(cls, context, watch_id, values):
|
|
||||||
db_api.watch_rule_update(context, watch_id, values)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def create(cls, context, values):
|
|
||||||
return cls._from_db_object(context, cls(),
|
|
||||||
db_api.watch_rule_create(context, values))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def delete(cls, context, watch_id):
|
|
||||||
db_api.watch_rule_delete(context, watch_id)
|
|
@ -127,66 +127,6 @@ NOTIFY_KEYS = (
|
|||||||
STACK_TAGS,
|
STACK_TAGS,
|
||||||
)
|
)
|
||||||
|
|
||||||
# This is the representation of a watch we expose to the API via RPC
|
|
||||||
WATCH_KEYS = (
|
|
||||||
WATCH_ACTIONS_ENABLED, WATCH_ALARM_ACTIONS, WATCH_TOPIC,
|
|
||||||
WATCH_UPDATED_TIME, WATCH_DESCRIPTION, WATCH_NAME,
|
|
||||||
WATCH_COMPARISON, WATCH_DIMENSIONS, WATCH_PERIODS,
|
|
||||||
WATCH_INSUFFICIENT_ACTIONS, WATCH_METRIC_NAME, WATCH_NAMESPACE,
|
|
||||||
WATCH_OK_ACTIONS, WATCH_PERIOD, WATCH_STATE_REASON,
|
|
||||||
WATCH_STATE_REASON_DATA, WATCH_STATE_UPDATED_TIME, WATCH_STATE_VALUE,
|
|
||||||
WATCH_STATISTIC, WATCH_THRESHOLD, WATCH_UNIT, WATCH_STACK_ID,
|
|
||||||
) = (
|
|
||||||
'actions_enabled', 'actions', 'topic',
|
|
||||||
'updated_time', 'description', 'name',
|
|
||||||
'comparison', 'dimensions', 'periods',
|
|
||||||
'insufficient_actions', 'metric_name', 'namespace',
|
|
||||||
'ok_actions', 'period', 'state_reason',
|
|
||||||
'state_reason_data', 'state_updated_time', 'state_value',
|
|
||||||
'statistic', 'threshold', 'unit', 'stack_id',
|
|
||||||
)
|
|
||||||
|
|
||||||
# Alternate representation of a watch rule to align with DB format
|
|
||||||
# FIXME : These align with AWS naming for compatibility with the
|
|
||||||
# current cfn-push-stats & metadata server, fix when we've ported
|
|
||||||
# cfn-push-stats to use the Cloudwatch server and/or moved metric
|
|
||||||
# collection into ceilometer, these should just be WATCH_KEYS
|
|
||||||
# or each field should be stored separately in the DB watch_data
|
|
||||||
# table if we stick to storing watch data in the heat DB
|
|
||||||
WATCH_RULE_KEYS = (
|
|
||||||
RULE_ACTIONS_ENABLED, RULE_ALARM_ACTIONS, RULE_TOPIC,
|
|
||||||
RULE_UPDATED_TIME, RULE_DESCRIPTION, RULE_NAME,
|
|
||||||
RULE_COMPARISON, RULE_DIMENSIONS, RULE_PERIODS,
|
|
||||||
RULE_INSUFFICIENT_ACTIONS, RULE_METRIC_NAME, RULE_NAMESPACE,
|
|
||||||
RULE_OK_ACTIONS, RULE_PERIOD, RULE_STATE_REASON,
|
|
||||||
RULE_STATE_REASON_DATA, RULE_STATE_UPDATED_TIME, RULE_STATE_VALUE,
|
|
||||||
RULE_STATISTIC, RULE_THRESHOLD, RULE_UNIT, RULE_STACK_NAME,
|
|
||||||
) = (
|
|
||||||
'ActionsEnabled', 'AlarmActions', 'AlarmArn',
|
|
||||||
'AlarmConfigurationUpdatedTimestamp', 'AlarmDescription', 'AlarmName',
|
|
||||||
'ComparisonOperator', 'Dimensions', 'EvaluationPeriods',
|
|
||||||
'InsufficientDataActions', 'MetricName', 'Namespace',
|
|
||||||
'OKActions', 'Period', 'StateReason',
|
|
||||||
'StateReasonData', 'StateUpdatedTimestamp', 'StateValue',
|
|
||||||
'Statistic', 'Threshold', 'Unit', 'StackName',
|
|
||||||
)
|
|
||||||
|
|
||||||
WATCH_STATES = (
|
|
||||||
WATCH_STATE_OK, WATCH_STATE_ALARM, WATCH_STATE_NODATA,
|
|
||||||
WATCH_STATE_SUSPENDED, WATCH_STATE_CEILOMETER_CONTROLLED
|
|
||||||
) = (
|
|
||||||
'NORMAL', 'ALARM', 'NODATA',
|
|
||||||
'SUSPENDED', 'CEILOMETER_CONTROLLED'
|
|
||||||
)
|
|
||||||
|
|
||||||
WATCH_DATA_KEYS = (
|
|
||||||
WATCH_DATA_ALARM, WATCH_DATA_METRIC, WATCH_DATA_TIME,
|
|
||||||
WATCH_DATA_NAMESPACE, WATCH_DATA
|
|
||||||
) = (
|
|
||||||
'watch_name', 'metric_name', 'timestamp',
|
|
||||||
'namespace', 'data'
|
|
||||||
)
|
|
||||||
|
|
||||||
VALIDATE_PARAM_KEYS = (
|
VALIDATE_PARAM_KEYS = (
|
||||||
PARAM_TYPE, PARAM_DEFAULT, PARAM_NO_ECHO,
|
PARAM_TYPE, PARAM_DEFAULT, PARAM_NO_ECHO,
|
||||||
PARAM_ALLOWED_VALUES, PARAM_ALLOWED_PATTERN, PARAM_MAX_LENGTH,
|
PARAM_ALLOWED_VALUES, PARAM_ALLOWED_PATTERN, PARAM_MAX_LENGTH,
|
||||||
|
@ -676,60 +676,6 @@ class EngineClient(object):
|
|||||||
resource_status_reason=resource_status_reason),
|
resource_status_reason=resource_status_reason),
|
||||||
version='1.26')
|
version='1.26')
|
||||||
|
|
||||||
def create_watch_data(self, ctxt, watch_name, stats_data):
|
|
||||||
"""Creates data for CloudWatch and WaitConditions.
|
|
||||||
|
|
||||||
This could be used by CloudWatch and WaitConditions and treat HA
|
|
||||||
service events like any other CloudWatch.
|
|
||||||
|
|
||||||
:param ctxt: RPC context.
|
|
||||||
:param watch_name: Name of the watch/alarm
|
|
||||||
:param stats_data: The data to post.
|
|
||||||
"""
|
|
||||||
return self.call(ctxt, self.make_msg('create_watch_data',
|
|
||||||
watch_name=watch_name,
|
|
||||||
stats_data=stats_data))
|
|
||||||
|
|
||||||
def show_watch(self, ctxt, watch_name):
|
|
||||||
"""Returns the attributes of one watch/alarm.
|
|
||||||
|
|
||||||
The show_watch method returns the attributes of one watch
|
|
||||||
or all watches if no watch_name is passed.
|
|
||||||
|
|
||||||
:param ctxt: RPC context.
|
|
||||||
:param watch_name: Name of the watch/alarm you want to see,
|
|
||||||
or None to see all
|
|
||||||
"""
|
|
||||||
return self.call(ctxt, self.make_msg('show_watch',
|
|
||||||
watch_name=watch_name))
|
|
||||||
|
|
||||||
def show_watch_metric(self, ctxt, metric_namespace=None, metric_name=None):
|
|
||||||
"""Returns the datapoints for a metric.
|
|
||||||
|
|
||||||
The show_watch_metric method returns the datapoints associated
|
|
||||||
with a specified metric, or all metrics if no metric_name is passed.
|
|
||||||
|
|
||||||
:param ctxt: RPC context.
|
|
||||||
:param metric_namespace: Name of the namespace you want to see,
|
|
||||||
or None to see all
|
|
||||||
:param metric_name: Name of the metric you want to see,
|
|
||||||
or None to see all
|
|
||||||
"""
|
|
||||||
return self.call(ctxt, self.make_msg('show_watch_metric',
|
|
||||||
metric_namespace=metric_namespace,
|
|
||||||
metric_name=metric_name))
|
|
||||||
|
|
||||||
def set_watch_state(self, ctxt, watch_name, state):
|
|
||||||
"""Temporarily set the state of a given watch.
|
|
||||||
|
|
||||||
:param ctxt: RPC context.
|
|
||||||
:param watch_name: Name of the watch
|
|
||||||
:param state: State (must be one defined in WatchRule class)
|
|
||||||
"""
|
|
||||||
return self.call(ctxt, self.make_msg('set_watch_state',
|
|
||||||
watch_name=watch_name,
|
|
||||||
state=state))
|
|
||||||
|
|
||||||
def get_revision(self, ctxt):
|
def get_revision(self, ctxt):
|
||||||
return self.call(ctxt, self.make_msg('get_revision'))
|
return self.call(ctxt, self.make_msg('get_revision'))
|
||||||
|
|
||||||
|
@ -1,270 +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.
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo_messaging.rpc import dispatcher
|
|
||||||
|
|
||||||
from heat.common import exception
|
|
||||||
from heat.engine import service
|
|
||||||
from heat.engine import service_stack_watch
|
|
||||||
from heat.engine import stack
|
|
||||||
from heat.engine import watchrule
|
|
||||||
from heat.objects import stack as stack_object
|
|
||||||
from heat.objects import watch_data as watch_data_object
|
|
||||||
from heat.objects import watch_rule as watch_rule_object
|
|
||||||
from heat.rpc import api as rpc_api
|
|
||||||
from heat.tests import common
|
|
||||||
from heat.tests.engine import tools
|
|
||||||
from heat.tests import utils
|
|
||||||
|
|
||||||
|
|
||||||
class StackWatchTest(common.HeatTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(StackWatchTest, self).setUp()
|
|
||||||
|
|
||||||
self.ctx = utils.dummy_context(tenant_id='stack_watch_test_tenant')
|
|
||||||
self.eng = service.EngineService('a-host', 'a-topic')
|
|
||||||
# self.eng.engine_id = 'engine-fake-uuid'
|
|
||||||
|
|
||||||
def _create_periodic_tasks(self):
|
|
||||||
self.eng.create_periodic_tasks()
|
|
||||||
self.eng.manage_thread_grp.wait()
|
|
||||||
|
|
||||||
@mock.patch.object(service_stack_watch.StackWatch, 'start_watch_task')
|
|
||||||
@mock.patch.object(stack_object.Stack, 'get_all')
|
|
||||||
@mock.patch.object(service.service.Service, 'start')
|
|
||||||
def test_start_watches_all_stacks(self, mock_super_start, mock_get_all,
|
|
||||||
start_watch_task):
|
|
||||||
s1 = mock.Mock(id=1)
|
|
||||||
s2 = mock.Mock(id=2)
|
|
||||||
mock_get_all.return_value = [s1, s2]
|
|
||||||
start_watch_task.return_value = None
|
|
||||||
|
|
||||||
self.eng.thread_group_mgr = None
|
|
||||||
self._create_periodic_tasks()
|
|
||||||
|
|
||||||
mock_get_all.assert_called_once_with(mock.ANY,
|
|
||||||
show_hidden=True)
|
|
||||||
calls = start_watch_task.call_args_list
|
|
||||||
self.assertEqual(2, start_watch_task.call_count)
|
|
||||||
self.assertIn(mock.call(1, mock.ANY), calls)
|
|
||||||
self.assertIn(mock.call(2, mock.ANY), calls)
|
|
||||||
|
|
||||||
@tools.stack_context('service_show_watch_test_stack', False)
|
|
||||||
def test_show_watch(self):
|
|
||||||
# Insert two dummy watch rules into the DB
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmActions': [u'WebServerRestartPolicy'],
|
|
||||||
u'AlarmDescription': u'Restart the WikiDatabase',
|
|
||||||
u'Namespace': u'system/linux',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'ServiceFailure'}
|
|
||||||
self.wr = []
|
|
||||||
self.wr.append(watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='show_watch_1',
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack.id,
|
|
||||||
state='NORMAL'))
|
|
||||||
self.wr[0].store()
|
|
||||||
|
|
||||||
self.wr.append(watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='show_watch_2',
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack.id,
|
|
||||||
state='NORMAL'))
|
|
||||||
self.wr[1].store()
|
|
||||||
|
|
||||||
# watch_name=None should return all watches
|
|
||||||
result = self.eng.show_watch(self.ctx, watch_name=None)
|
|
||||||
result_names = [r.get('name') for r in result]
|
|
||||||
self.assertIn('show_watch_1', result_names)
|
|
||||||
self.assertIn('show_watch_2', result_names)
|
|
||||||
|
|
||||||
result = self.eng.show_watch(self.ctx, watch_name="show_watch_1")
|
|
||||||
self.assertEqual(1, len(result))
|
|
||||||
self.assertIn('name', result[0])
|
|
||||||
self.assertEqual('show_watch_1', result[0]['name'])
|
|
||||||
|
|
||||||
result = self.eng.show_watch(self.ctx, watch_name="show_watch_2")
|
|
||||||
self.assertEqual(1, len(result))
|
|
||||||
self.assertIn('name', result[0])
|
|
||||||
self.assertEqual('show_watch_2', result[0]['name'])
|
|
||||||
|
|
||||||
ex = self.assertRaises(dispatcher.ExpectedException,
|
|
||||||
self.eng.show_watch,
|
|
||||||
self.ctx, watch_name="nonexistent")
|
|
||||||
self.assertEqual(exception.EntityNotFound, ex.exc_info[0])
|
|
||||||
|
|
||||||
# Check the response has all keys defined in the engine API
|
|
||||||
for key in rpc_api.WATCH_KEYS:
|
|
||||||
self.assertIn(key, result[0])
|
|
||||||
|
|
||||||
@tools.stack_context('service_show_watch_metric_test_stack', False)
|
|
||||||
def test_show_watch_metric(self):
|
|
||||||
# Insert dummy watch rule into the DB
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmActions': [u'WebServerRestartPolicy'],
|
|
||||||
u'AlarmDescription': u'Restart the WikiDatabase',
|
|
||||||
u'Namespace': u'system/linux',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'ServiceFailure'}
|
|
||||||
self.wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='show_watch_metric_1',
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack.id,
|
|
||||||
state='NORMAL')
|
|
||||||
self.wr.store()
|
|
||||||
|
|
||||||
# And add a metric datapoint
|
|
||||||
watch = watch_rule_object.WatchRule.get_by_name(self.ctx,
|
|
||||||
'show_watch_metric_1')
|
|
||||||
self.assertIsNotNone(watch)
|
|
||||||
values = {'watch_rule_id': watch.id,
|
|
||||||
'data': {u'Namespace': u'system/linux',
|
|
||||||
u'ServiceFailure': {
|
|
||||||
u'Units': u'Counter', u'Value': 1}}}
|
|
||||||
watch_data_object.WatchData.create(self.ctx, values)
|
|
||||||
|
|
||||||
# Check there is one result returned
|
|
||||||
result = self.eng.show_watch_metric(self.ctx,
|
|
||||||
metric_namespace=None,
|
|
||||||
metric_name=None)
|
|
||||||
self.assertEqual(1, len(result))
|
|
||||||
|
|
||||||
# Create another metric datapoint and check we get two
|
|
||||||
watch_data_object.WatchData.create(self.ctx, values)
|
|
||||||
result = self.eng.show_watch_metric(self.ctx,
|
|
||||||
metric_namespace=None,
|
|
||||||
metric_name=None)
|
|
||||||
self.assertEqual(2, len(result))
|
|
||||||
|
|
||||||
# Check the response has all keys defined in the engine API
|
|
||||||
for key in rpc_api.WATCH_DATA_KEYS:
|
|
||||||
self.assertIn(key, result[0])
|
|
||||||
|
|
||||||
@tools.stack_context('service_show_watch_state_test_stack')
|
|
||||||
@mock.patch.object(stack.Stack, 'resource_by_refid')
|
|
||||||
def test_set_watch_state(self, mock_ref):
|
|
||||||
self._create_periodic_tasks()
|
|
||||||
# Insert dummy watch rule into the DB
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmActions': [u'WebServerRestartPolicy'],
|
|
||||||
u'AlarmDescription': u'Restart the WikiDatabase',
|
|
||||||
u'Namespace': u'system/linux',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'ServiceFailure'}
|
|
||||||
self.wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='OverrideAlarm',
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack.id,
|
|
||||||
state='NORMAL')
|
|
||||||
self.wr.store()
|
|
||||||
|
|
||||||
class DummyAction(object):
|
|
||||||
def signal(self):
|
|
||||||
return "dummyfoo"
|
|
||||||
|
|
||||||
dummy_action = DummyAction()
|
|
||||||
mock_ref.return_value = dummy_action
|
|
||||||
|
|
||||||
# Replace the real stack threadgroup with a dummy one, so we can
|
|
||||||
# check the function returned on ALARM is correctly scheduled
|
|
||||||
dtg = tools.DummyThreadGroup()
|
|
||||||
self.eng.thread_group_mgr.groups[self.stack.id] = dtg
|
|
||||||
|
|
||||||
state = watchrule.WatchRule.NODATA
|
|
||||||
result = self.eng.set_watch_state(self.ctx,
|
|
||||||
watch_name="OverrideAlarm",
|
|
||||||
state=state)
|
|
||||||
self.assertEqual(state, result[rpc_api.WATCH_STATE_VALUE])
|
|
||||||
self.assertEqual(
|
|
||||||
[], self.eng.thread_group_mgr.groups[self.stack.id].threads)
|
|
||||||
|
|
||||||
state = watchrule.WatchRule.NORMAL
|
|
||||||
result = self.eng.set_watch_state(self.ctx,
|
|
||||||
watch_name="OverrideAlarm",
|
|
||||||
state=state)
|
|
||||||
self.assertEqual(state, result[rpc_api.WATCH_STATE_VALUE])
|
|
||||||
self.assertEqual(
|
|
||||||
[], self.eng.thread_group_mgr.groups[self.stack.id].threads)
|
|
||||||
|
|
||||||
state = watchrule.WatchRule.ALARM
|
|
||||||
result = self.eng.set_watch_state(self.ctx,
|
|
||||||
watch_name="OverrideAlarm",
|
|
||||||
state=state)
|
|
||||||
self.assertEqual(state, result[rpc_api.WATCH_STATE_VALUE])
|
|
||||||
self.assertEqual(
|
|
||||||
[dummy_action.signal],
|
|
||||||
self.eng.thread_group_mgr.groups[self.stack.id].threads)
|
|
||||||
|
|
||||||
mock_ref.assert_called_once_with('WebServerRestartPolicy')
|
|
||||||
|
|
||||||
@tools.stack_context('service_show_watch_state_badstate_test_stack')
|
|
||||||
@mock.patch.object(watchrule.WatchRule, 'set_watch_state')
|
|
||||||
def test_set_watch_state_badstate(self, mock_set):
|
|
||||||
mock_set.side_effect = ValueError
|
|
||||||
# Insert dummy watch rule into the DB
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmActions': [u'WebServerRestartPolicy'],
|
|
||||||
u'AlarmDescription': u'Restart the WikiDatabase',
|
|
||||||
u'Namespace': u'system/linux',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'ServiceFailure'}
|
|
||||||
self.wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='OverrideAlarm2',
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack.id,
|
|
||||||
state='NORMAL')
|
|
||||||
self.wr.store()
|
|
||||||
|
|
||||||
for state in ["HGJHGJHG", "1234", "!\\*(&%"]:
|
|
||||||
self.assertRaises(ValueError,
|
|
||||||
self.eng.set_watch_state,
|
|
||||||
self.ctx, watch_name="OverrideAlarm2",
|
|
||||||
state=state)
|
|
||||||
|
|
||||||
calls = [mock.call("HGJHGJHG"),
|
|
||||||
mock.call("1234"),
|
|
||||||
mock.call("!\\*(&%")]
|
|
||||||
mock_set.assert_has_calls(calls)
|
|
||||||
|
|
||||||
@mock.patch.object(watchrule.WatchRule, 'load')
|
|
||||||
def test_set_watch_state_noexist(self, mock_load):
|
|
||||||
state = watchrule.WatchRule.ALARM # State valid
|
|
||||||
mock_load.side_effect = exception.EntityNotFound(entity='Watch Rule',
|
|
||||||
name='test')
|
|
||||||
|
|
||||||
ex = self.assertRaises(dispatcher.ExpectedException,
|
|
||||||
self.eng.set_watch_state,
|
|
||||||
self.ctx, watch_name="nonexistent",
|
|
||||||
state=state)
|
|
||||||
self.assertEqual(exception.EntityNotFound, ex.exc_info[0])
|
|
||||||
mock_load.assert_called_once_with(self.ctx, "nonexistent")
|
|
@ -26,7 +26,6 @@ from heat.engine import rsrc_defn
|
|||||||
from heat.engine import scheduler
|
from heat.engine import scheduler
|
||||||
from heat.engine import stack as parser
|
from heat.engine import stack as parser
|
||||||
from heat.engine import template as tmpl
|
from heat.engine import template as tmpl
|
||||||
from heat.engine import watchrule
|
|
||||||
from heat.tests import common
|
from heat.tests import common
|
||||||
from heat.tests import utils
|
from heat.tests import utils
|
||||||
|
|
||||||
@ -404,48 +403,6 @@ class AodhAlarmTest(common.HeatTestCase):
|
|||||||
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
|
||||||
self.assertIsNone(rsrc.validate())
|
self.assertIsNone(rsrc.validate())
|
||||||
|
|
||||||
def test_delete_watchrule_destroy(self):
|
|
||||||
t = template_format.parse(alarm_template)
|
|
||||||
|
|
||||||
test_stack = self.create_stack(template=json.dumps(t))
|
|
||||||
rsrc = test_stack['MEMAlarmHigh']
|
|
||||||
|
|
||||||
wr = mock.MagicMock()
|
|
||||||
self.patchobject(watchrule.WatchRule, 'load', return_value=wr)
|
|
||||||
wr.destroy.return_value = None
|
|
||||||
|
|
||||||
self.patchobject(aodh.AodhClientPlugin, 'client',
|
|
||||||
return_value=self.fa)
|
|
||||||
self.patchobject(self.fa.alarm, 'delete')
|
|
||||||
rsrc.resource_id = '12345'
|
|
||||||
|
|
||||||
self.assertEqual('12345', rsrc.handle_delete())
|
|
||||||
self.assertEqual(1, wr.destroy.call_count)
|
|
||||||
# check that super method has been called and execute deleting
|
|
||||||
self.assertEqual(1, self.fa.alarm.delete.call_count)
|
|
||||||
|
|
||||||
def test_delete_no_watchrule(self):
|
|
||||||
t = template_format.parse(alarm_template)
|
|
||||||
|
|
||||||
test_stack = self.create_stack(template=json.dumps(t))
|
|
||||||
rsrc = test_stack['MEMAlarmHigh']
|
|
||||||
|
|
||||||
wr = mock.MagicMock()
|
|
||||||
self.patchobject(watchrule.WatchRule, 'load',
|
|
||||||
side_effect=[exception.EntityNotFound(
|
|
||||||
entity='Watch Rule', name='test')])
|
|
||||||
wr.destroy.return_value = None
|
|
||||||
|
|
||||||
self.patchobject(aodh.AodhClientPlugin, 'client',
|
|
||||||
return_value=self.fa)
|
|
||||||
self.patchobject(self.fa.alarm, 'delete')
|
|
||||||
rsrc.resource_id = '12345'
|
|
||||||
|
|
||||||
self.assertEqual('12345', rsrc.handle_delete())
|
|
||||||
self.assertEqual(0, wr.destroy.call_count)
|
|
||||||
# check that super method has been called and execute deleting
|
|
||||||
self.assertEqual(1, self.fa.alarm.delete.call_count)
|
|
||||||
|
|
||||||
def _prepare_resource(self, for_check=True):
|
def _prepare_resource(self, for_check=True):
|
||||||
snippet = template_format.parse(not_string_alarm_template)
|
snippet = template_format.parse(not_string_alarm_template)
|
||||||
self.stack = utils.parse_stack(snippet)
|
self.stack = utils.parse_stack(snippet)
|
||||||
@ -457,25 +414,12 @@ class AodhAlarmTest(common.HeatTestCase):
|
|||||||
res.client().alarm.get.return_value = mock_alarm
|
res.client().alarm.get.return_value = mock_alarm
|
||||||
return res
|
return res
|
||||||
|
|
||||||
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
|
def test_check(self):
|
||||||
def test_check(self, mock_load):
|
|
||||||
res = self._prepare_resource()
|
res = self._prepare_resource()
|
||||||
scheduler.TaskRunner(res.check)()
|
scheduler.TaskRunner(res.check)()
|
||||||
self.assertEqual((res.CHECK, res.COMPLETE), res.state)
|
self.assertEqual((res.CHECK, res.COMPLETE), res.state)
|
||||||
|
|
||||||
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
|
def test_check_alarm_failure(self):
|
||||||
def test_check_watchrule_failure(self, mock_load):
|
|
||||||
res = self._prepare_resource()
|
|
||||||
exc = alarm.exception.EntityNotFound(entity='Watch Rule', name='Boom')
|
|
||||||
mock_load.side_effect = exc
|
|
||||||
|
|
||||||
self.assertRaises(exception.ResourceFailure,
|
|
||||||
scheduler.TaskRunner(res.check))
|
|
||||||
self.assertEqual((res.CHECK, res.FAILED), res.state)
|
|
||||||
self.assertIn('Boom', res.status_reason)
|
|
||||||
|
|
||||||
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
|
|
||||||
def test_check_alarm_failure(self, mock_load):
|
|
||||||
res = self._prepare_resource()
|
res = self._prepare_resource()
|
||||||
res.client().alarm.get.side_effect = Exception('Boom')
|
res.client().alarm.get.side_effect = Exception('Boom')
|
||||||
|
|
||||||
|
@ -1,120 +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.
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from heat.common import exception
|
|
||||||
from heat.common import template_format
|
|
||||||
from heat.engine import resource
|
|
||||||
from heat.engine import resources
|
|
||||||
from heat.engine.resources.openstack.heat import cloud_watch
|
|
||||||
from heat.engine import scheduler
|
|
||||||
from heat.engine import watchrule
|
|
||||||
from heat.tests import common
|
|
||||||
from heat.tests import utils
|
|
||||||
|
|
||||||
|
|
||||||
AWS_CloudWatch_Alarm = '''
|
|
||||||
HeatTemplateFormatVersion: '2012-12-12'
|
|
||||||
Description: Template which tests alarms
|
|
||||||
Resources:
|
|
||||||
test_me:
|
|
||||||
Type: AWS::CloudWatch::Alarm
|
|
||||||
Properties:
|
|
||||||
MetricName: cpu_util
|
|
||||||
Namespace: AWS/EC2
|
|
||||||
Statistic: Average
|
|
||||||
Period: '60'
|
|
||||||
EvaluationPeriods: '1'
|
|
||||||
Threshold: '50'
|
|
||||||
ComparisonOperator: GreaterThanThreshold
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class CloudWatchAlarmTest(common.HeatTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(CloudWatchAlarmTest, self).setUp()
|
|
||||||
|
|
||||||
def clear_register_class():
|
|
||||||
env = resources.global_env()
|
|
||||||
env.registry._registry.pop('CWLiteAlarmForTest')
|
|
||||||
|
|
||||||
self.ctx = utils.dummy_context()
|
|
||||||
resource._register_class('CWLiteAlarmForTest',
|
|
||||||
cloud_watch.CloudWatchAlarm)
|
|
||||||
self.addCleanup(clear_register_class)
|
|
||||||
|
|
||||||
def parse_stack(self):
|
|
||||||
t = template_format.parse(AWS_CloudWatch_Alarm)
|
|
||||||
env = {'resource_registry': {
|
|
||||||
'AWS::CloudWatch::Alarm': 'CWLiteAlarmForTest'
|
|
||||||
}}
|
|
||||||
self.stack = utils.parse_stack(t, params=env)
|
|
||||||
return self.stack
|
|
||||||
|
|
||||||
def test_resource_create_good(self):
|
|
||||||
s = self.parse_stack()
|
|
||||||
self.assertIsNone(scheduler.TaskRunner(s['test_me'].create)())
|
|
||||||
|
|
||||||
def test_resource_create_failed(self):
|
|
||||||
s = self.parse_stack()
|
|
||||||
with mock.patch.object(watchrule.WatchRule, 'store') as bad_store:
|
|
||||||
bad_store.side_effect = KeyError('any random failure')
|
|
||||||
task_func = scheduler.TaskRunner(s['test_me'].create)
|
|
||||||
self.assertRaises(exception.ResourceFailure, task_func)
|
|
||||||
|
|
||||||
def test_resource_delete_good(self):
|
|
||||||
s = self.parse_stack()
|
|
||||||
self.assertIsNone(scheduler.TaskRunner(s['test_me'].create)())
|
|
||||||
self.assertIsNone(scheduler.TaskRunner(s['test_me'].delete)())
|
|
||||||
|
|
||||||
def test_resource_delete_notfound(self):
|
|
||||||
# if a resource is not found, handle_delete() should not raise
|
|
||||||
# an exception.
|
|
||||||
s = self.parse_stack()
|
|
||||||
self.assertIsNone(scheduler.TaskRunner(s['test_me'].create)())
|
|
||||||
res_name = self.stack['test_me'].physical_resource_name()
|
|
||||||
self.wr = watchrule.WatchRule.load(self.ctx,
|
|
||||||
watch_name=res_name)
|
|
||||||
|
|
||||||
with mock.patch.object(watchrule.WatchRule, 'destroy') as bad_destroy:
|
|
||||||
watch_exc = exception.EntityNotFound(entity='Watch Rule',
|
|
||||||
name='test')
|
|
||||||
bad_destroy.side_effect = watch_exc
|
|
||||||
self.assertIsNone(scheduler.TaskRunner(s['test_me'].delete)())
|
|
||||||
|
|
||||||
def _get_watch_rule(self):
|
|
||||||
stack = self.parse_stack()
|
|
||||||
res = stack['test_me']
|
|
||||||
res.state_set(res.CREATE, res.COMPLETE)
|
|
||||||
return res
|
|
||||||
|
|
||||||
@mock.patch.object(cloud_watch.watchrule.WatchRule, 'load')
|
|
||||||
def test_check(self, mock_lock):
|
|
||||||
res = self._get_watch_rule()
|
|
||||||
|
|
||||||
scheduler.TaskRunner(res.check)()
|
|
||||||
self.assertEqual((res.CHECK, res.COMPLETE), res.state)
|
|
||||||
|
|
||||||
@mock.patch.object(cloud_watch.watchrule.WatchRule, 'load')
|
|
||||||
def test_check_fail(self, mock_load):
|
|
||||||
res = self._get_watch_rule()
|
|
||||||
exc = cloud_watch.exception.EntityNotFound(entity='Watch Rule',
|
|
||||||
name='Boom')
|
|
||||||
mock_load.side_effect = exc
|
|
||||||
|
|
||||||
self.assertRaises(exception.ResourceFailure,
|
|
||||||
scheduler.TaskRunner(res.check))
|
|
||||||
self.assertEqual((res.CHECK, res.FAILED), res.state)
|
|
||||||
self.assertIn('Boom', res.status_reason)
|
|
@ -1,161 +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.
|
|
||||||
|
|
||||||
|
|
||||||
import copy
|
|
||||||
|
|
||||||
from heat.common import template_format
|
|
||||||
from heat.engine import resource
|
|
||||||
from heat.engine.resources.openstack.heat import cloud_watch
|
|
||||||
from heat.engine import rsrc_defn
|
|
||||||
from heat.engine import scheduler
|
|
||||||
from heat.engine import watchrule
|
|
||||||
from heat.tests import common
|
|
||||||
from heat.tests import utils
|
|
||||||
|
|
||||||
|
|
||||||
alarm_template = '''
|
|
||||||
{
|
|
||||||
"AWSTemplateFormatVersion" : "2010-09-09",
|
|
||||||
"Description" : "Alarm Test",
|
|
||||||
"Parameters" : {},
|
|
||||||
"Resources" : {
|
|
||||||
"MEMAlarmHigh": {
|
|
||||||
"Type": "AWS::CloudWatch::Alarm",
|
|
||||||
"Properties": {
|
|
||||||
"AlarmDescription": "Scale-up if MEM > 50% for 1 minute",
|
|
||||||
"MetricName": "MemoryUtilization",
|
|
||||||
"Namespace": "system/linux",
|
|
||||||
"Statistic": "Average",
|
|
||||||
"Period": "60",
|
|
||||||
"EvaluationPeriods": "1",
|
|
||||||
"Threshold": "50",
|
|
||||||
"AlarmActions": [],
|
|
||||||
"Dimensions": [],
|
|
||||||
"ComparisonOperator": "GreaterThanThreshold"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
'''
|
|
||||||
|
|
||||||
|
|
||||||
class CloudWatchAlarmTest(common.HeatTestCase):
|
|
||||||
|
|
||||||
def create_alarm(self, t, stack, resource_name):
|
|
||||||
resource_defns = stack.t.resource_definitions(stack)
|
|
||||||
rsrc = cloud_watch.CloudWatchAlarm(resource_name,
|
|
||||||
resource_defns[resource_name],
|
|
||||||
stack)
|
|
||||||
self.assertIsNone(rsrc.validate())
|
|
||||||
scheduler.TaskRunner(rsrc.create)()
|
|
||||||
self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
|
|
||||||
return rsrc
|
|
||||||
|
|
||||||
def test_mem_alarm_high_update_no_replace(self):
|
|
||||||
"""Test case for updating the alarm with updatable properties.
|
|
||||||
|
|
||||||
Make sure that we can change the updatable properties
|
|
||||||
without replacing the Alarm rsrc.
|
|
||||||
"""
|
|
||||||
t = template_format.parse(alarm_template)
|
|
||||||
|
|
||||||
# short circuit the alarm's references
|
|
||||||
properties = t['Resources']['MEMAlarmHigh']['Properties']
|
|
||||||
properties['AlarmActions'] = ['a']
|
|
||||||
properties['Dimensions'] = [{'a': 'v'}]
|
|
||||||
|
|
||||||
stack = utils.parse_stack(t)
|
|
||||||
# the watch rule needs a valid stack_id
|
|
||||||
stack.store()
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
rsrc = self.create_alarm(t, stack, 'MEMAlarmHigh')
|
|
||||||
props = copy.copy(rsrc.properties.data)
|
|
||||||
props.update({
|
|
||||||
'ComparisonOperator': 'LessThanThreshold',
|
|
||||||
'AlarmDescription': 'fruity',
|
|
||||||
'EvaluationPeriods': '2',
|
|
||||||
'Period': '90',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'Threshold': '39',
|
|
||||||
})
|
|
||||||
snippet = rsrc_defn.ResourceDefinition(rsrc.name,
|
|
||||||
rsrc.type(),
|
|
||||||
props)
|
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.update, snippet)()
|
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.delete)()
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_mem_alarm_high_update_replace(self):
|
|
||||||
"""Test case for replacing the alarm with non-updatable properties.
|
|
||||||
|
|
||||||
Make sure that the Alarm resource IS replaced when non-update-able
|
|
||||||
properties are changed.
|
|
||||||
"""
|
|
||||||
t = template_format.parse(alarm_template)
|
|
||||||
|
|
||||||
# short circuit the alarm's references
|
|
||||||
properties = t['Resources']['MEMAlarmHigh']['Properties']
|
|
||||||
properties['AlarmActions'] = ['a']
|
|
||||||
properties['Dimensions'] = [{'a': 'v'}]
|
|
||||||
|
|
||||||
stack = utils.parse_stack(t)
|
|
||||||
# the watch rule needs a valid stack_id
|
|
||||||
stack.store()
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
rsrc = self.create_alarm(t, stack, 'MEMAlarmHigh')
|
|
||||||
props = copy.copy(rsrc.properties.data)
|
|
||||||
props['MetricName'] = 'temp'
|
|
||||||
snippet = rsrc_defn.ResourceDefinition(rsrc.name,
|
|
||||||
rsrc.type(),
|
|
||||||
props)
|
|
||||||
|
|
||||||
updater = scheduler.TaskRunner(rsrc.update, snippet)
|
|
||||||
self.assertRaises(resource.UpdateReplace, updater)
|
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.delete)()
|
|
||||||
self.m.VerifyAll()
|
|
||||||
|
|
||||||
def test_suspend_resume(self):
|
|
||||||
t = template_format.parse(alarm_template)
|
|
||||||
stack_name = "test_cw_alarm_sus_res_stack"
|
|
||||||
stack = utils.parse_stack(t, stack_name=stack_name)
|
|
||||||
# the watch rule needs a valid stack_id
|
|
||||||
stack.store()
|
|
||||||
|
|
||||||
self.m.ReplayAll()
|
|
||||||
rsrc = self.create_alarm(t, stack, 'MEMAlarmHigh')
|
|
||||||
scheduler.TaskRunner(rsrc.suspend)()
|
|
||||||
self.assertEqual((rsrc.SUSPEND, rsrc.COMPLETE), rsrc.state)
|
|
||||||
|
|
||||||
self.ctx = utils.dummy_context()
|
|
||||||
|
|
||||||
wr = watchrule.WatchRule.load(
|
|
||||||
self.ctx, watch_name="%s-MEMAlarmHigh" % stack_name)
|
|
||||||
|
|
||||||
self.assertEqual(watchrule.WatchRule.SUSPENDED, wr.state)
|
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.resume)()
|
|
||||||
self.assertEqual((rsrc.RESUME, rsrc.COMPLETE), rsrc.state)
|
|
||||||
|
|
||||||
wr = watchrule.WatchRule.load(
|
|
||||||
self.ctx, watch_name="%s-MEMAlarmHigh" % stack_name)
|
|
||||||
|
|
||||||
self.assertEqual(watchrule.WatchRule.NODATA, wr.state)
|
|
||||||
|
|
||||||
scheduler.TaskRunner(rsrc.delete)()
|
|
||||||
self.m.VerifyAll()
|
|
@ -1,118 +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.
|
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from heat.engine import service_stack_watch
|
|
||||||
from heat.rpc import api as rpc_api
|
|
||||||
from heat.tests import common
|
|
||||||
from heat.tests import utils
|
|
||||||
|
|
||||||
|
|
||||||
class StackServiceWatcherTest(common.HeatTestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(StackServiceWatcherTest, self).setUp()
|
|
||||||
self.ctx = utils.dummy_context(tenant_id='stack_service_test_tenant')
|
|
||||||
|
|
||||||
@mock.patch.object(service_stack_watch.stack_object.Stack,
|
|
||||||
'get_all_by_owner_id')
|
|
||||||
@mock.patch.object(service_stack_watch.watch_rule_object.WatchRule,
|
|
||||||
'get_all_by_stack')
|
|
||||||
@mock.patch.object(service_stack_watch.watch_rule_object.WatchRule,
|
|
||||||
'update_by_id')
|
|
||||||
def test_periodic_watch_task_not_created(self, watch_rule_update,
|
|
||||||
watch_rule_get_all_by_stack,
|
|
||||||
stack_get_all_by_owner_id):
|
|
||||||
"""Test case for not creating periodic task for cloud watch lite alarm.
|
|
||||||
|
|
||||||
If there is no cloud watch lite alarm, then don't create a periodic
|
|
||||||
task for it.
|
|
||||||
"""
|
|
||||||
stack_id = 83
|
|
||||||
watch_rule_get_all_by_stack.return_value = []
|
|
||||||
stack_get_all_by_owner_id.return_value = []
|
|
||||||
tg = mock.Mock()
|
|
||||||
sw = service_stack_watch.StackWatch(tg)
|
|
||||||
sw.start_watch_task(stack_id, self.ctx)
|
|
||||||
|
|
||||||
# assert that add_timer is NOT called.
|
|
||||||
self.assertEqual([], tg.add_timer.call_args_list)
|
|
||||||
|
|
||||||
@mock.patch.object(service_stack_watch.stack_object.Stack,
|
|
||||||
'get_all_by_owner_id')
|
|
||||||
@mock.patch.object(service_stack_watch.watch_rule_object.WatchRule,
|
|
||||||
'get_all_by_stack')
|
|
||||||
@mock.patch.object(service_stack_watch.watch_rule_object.WatchRule,
|
|
||||||
'update_by_id')
|
|
||||||
def test_periodic_watch_task_created(self, watch_rule_update,
|
|
||||||
watch_rule_get_all_by_stack,
|
|
||||||
stack_get_all_by_owner_id):
|
|
||||||
"""Test case for creating periodic task for cloud watch lite alarm.
|
|
||||||
|
|
||||||
If there is no cloud watch lite alarm, then DO create a periodic task
|
|
||||||
for it.
|
|
||||||
"""
|
|
||||||
stack_id = 86
|
|
||||||
wr1 = mock.Mock()
|
|
||||||
wr1.id = 4
|
|
||||||
wr1.state = rpc_api.WATCH_STATE_NODATA
|
|
||||||
|
|
||||||
watch_rule_get_all_by_stack.return_value = [wr1]
|
|
||||||
stack_get_all_by_owner_id.return_value = []
|
|
||||||
tg = mock.Mock()
|
|
||||||
sw = service_stack_watch.StackWatch(tg)
|
|
||||||
sw.start_watch_task(stack_id, self.ctx)
|
|
||||||
|
|
||||||
# assert that add_timer IS called.
|
|
||||||
self.assertEqual([mock.call(stack_id, sw.periodic_watcher_task,
|
|
||||||
sid=stack_id)],
|
|
||||||
tg.add_timer.call_args_list)
|
|
||||||
|
|
||||||
@mock.patch.object(service_stack_watch.stack_object.Stack,
|
|
||||||
'get_all_by_owner_id')
|
|
||||||
@mock.patch.object(service_stack_watch.watch_rule_object.WatchRule,
|
|
||||||
'get_all_by_stack')
|
|
||||||
@mock.patch.object(service_stack_watch.watch_rule_object.WatchRule,
|
|
||||||
'update_by_id')
|
|
||||||
def test_periodic_watch_task_created_nested(self, watch_rule_update,
|
|
||||||
watch_rule_get_all_by_stack,
|
|
||||||
stack_get_all_by_owner_id):
|
|
||||||
stack_id = 90
|
|
||||||
|
|
||||||
def my_wr_get(cnxt, sid):
|
|
||||||
if sid == stack_id:
|
|
||||||
return []
|
|
||||||
wr1 = mock.Mock()
|
|
||||||
wr1.id = 4
|
|
||||||
wr1.state = rpc_api.WATCH_STATE_NODATA
|
|
||||||
return [wr1]
|
|
||||||
|
|
||||||
watch_rule_get_all_by_stack.side_effect = my_wr_get
|
|
||||||
|
|
||||||
def my_nested_get(cnxt, sid):
|
|
||||||
if sid == stack_id:
|
|
||||||
nested_stack = mock.Mock()
|
|
||||||
nested_stack.id = 55
|
|
||||||
return [nested_stack]
|
|
||||||
return []
|
|
||||||
|
|
||||||
stack_get_all_by_owner_id.side_effect = my_nested_get
|
|
||||||
tg = mock.Mock()
|
|
||||||
sw = service_stack_watch.StackWatch(tg)
|
|
||||||
sw.start_watch_task(stack_id, self.ctx)
|
|
||||||
|
|
||||||
# assert that add_timer IS called.
|
|
||||||
self.assertEqual([mock.call(stack_id, sw.periodic_watcher_task,
|
|
||||||
sid=stack_id)],
|
|
||||||
tg.add_timer.call_args_list)
|
|
@ -219,7 +219,6 @@ class WaitConditionMetadataUpdateTest(common.HeatTestCase):
|
|||||||
def setUp(self):
|
def setUp(self):
|
||||||
super(WaitConditionMetadataUpdateTest, self).setUp()
|
super(WaitConditionMetadataUpdateTest, self).setUp()
|
||||||
self.man = service.EngineService('a-host', 'a-topic')
|
self.man = service.EngineService('a-host', 'a-topic')
|
||||||
self.man.create_periodic_tasks()
|
|
||||||
|
|
||||||
@mock.patch.object(nova.NovaClientPlugin, 'find_flavor_by_name_or_id')
|
@mock.patch.object(nova.NovaClientPlugin, 'find_flavor_by_name_or_id')
|
||||||
@mock.patch.object(glance.GlanceClientPlugin, 'find_image_by_name_or_id')
|
@mock.patch.object(glance.GlanceClientPlugin, 'find_image_by_name_or_id')
|
||||||
|
@ -301,23 +301,6 @@ class EngineRpcAPITestCase(common.HeatTestCase):
|
|||||||
details={u'wordpress': []},
|
details={u'wordpress': []},
|
||||||
sync_call=True)
|
sync_call=True)
|
||||||
|
|
||||||
def test_create_watch_data(self):
|
|
||||||
self._test_engine_api('create_watch_data', 'call',
|
|
||||||
watch_name='watch1',
|
|
||||||
stats_data={})
|
|
||||||
|
|
||||||
def test_show_watch(self):
|
|
||||||
self._test_engine_api('show_watch', 'call',
|
|
||||||
watch_name='watch1')
|
|
||||||
|
|
||||||
def test_show_watch_metric(self):
|
|
||||||
self._test_engine_api('show_watch_metric', 'call',
|
|
||||||
metric_namespace=None, metric_name=None)
|
|
||||||
|
|
||||||
def test_set_watch_state(self):
|
|
||||||
self._test_engine_api('set_watch_state', 'call',
|
|
||||||
watch_name='watch1', state="xyz")
|
|
||||||
|
|
||||||
def test_list_software_configs(self):
|
def test_list_software_configs(self):
|
||||||
self._test_engine_api('list_software_configs', 'call',
|
self._test_engine_api('list_software_configs', 'call',
|
||||||
limit=mock.ANY, marker=mock.ANY)
|
limit=mock.ANY, marker=mock.ANY)
|
||||||
|
@ -537,9 +537,6 @@ class SignalTest(common.HeatTestCase):
|
|||||||
'previous': 'SUCCESS'}
|
'previous': 'SUCCESS'}
|
||||||
ceilo_expected = 'alarm state changed from SUCCESS to foo (apples)'
|
ceilo_expected = 'alarm state changed from SUCCESS to foo (apples)'
|
||||||
|
|
||||||
watch_details = {'state': 'go_for_it'}
|
|
||||||
watch_expected = 'alarm state changed to go_for_it'
|
|
||||||
|
|
||||||
str_details = 'a string details'
|
str_details = 'a string details'
|
||||||
str_expected = str_details
|
str_expected = str_details
|
||||||
|
|
||||||
@ -547,13 +544,11 @@ class SignalTest(common.HeatTestCase):
|
|||||||
none_expected = 'No signal details provided'
|
none_expected = 'No signal details provided'
|
||||||
|
|
||||||
# Test
|
# Test
|
||||||
for test_d in (ceilo_details, watch_details, str_details,
|
for test_d in (ceilo_details, str_details, none_details):
|
||||||
none_details):
|
|
||||||
rsrc.signal(details=test_d)
|
rsrc.signal(details=test_d)
|
||||||
|
|
||||||
# Verify
|
# Verify
|
||||||
mock_add.assert_any_call('SIGNAL', 'COMPLETE', ceilo_expected)
|
mock_add.assert_any_call('SIGNAL', 'COMPLETE', ceilo_expected)
|
||||||
mock_add.assert_any_call('SIGNAL', 'COMPLETE', watch_expected)
|
|
||||||
mock_add.assert_any_call('SIGNAL', 'COMPLETE', str_expected)
|
mock_add.assert_any_call('SIGNAL', 'COMPLETE', str_expected)
|
||||||
mock_add.assert_any_call('SIGNAL', 'COMPLETE', none_expected)
|
mock_add.assert_any_call('SIGNAL', 'COMPLETE', none_expected)
|
||||||
|
|
||||||
|
@ -1,978 +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.
|
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
|
|
||||||
import mock
|
|
||||||
from oslo_utils import timeutils
|
|
||||||
|
|
||||||
from heat.common import exception
|
|
||||||
from heat.engine import stack
|
|
||||||
from heat.engine import template
|
|
||||||
from heat.engine import watchrule
|
|
||||||
from heat.objects import watch_rule
|
|
||||||
from heat.tests import common
|
|
||||||
from heat.tests import utils
|
|
||||||
|
|
||||||
|
|
||||||
class WatchData(object):
|
|
||||||
def __init__(self, data, created_at):
|
|
||||||
self.created_at = created_at
|
|
||||||
self.data = {'test_metric': {'Value': data,
|
|
||||||
'Unit': 'Count'}}
|
|
||||||
|
|
||||||
|
|
||||||
class DummyAction(object):
|
|
||||||
signal = "DummyAction"
|
|
||||||
|
|
||||||
|
|
||||||
class WatchRuleTest(common.HeatTestCase):
|
|
||||||
stack_id = None
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
super(WatchRuleTest, self).setUp()
|
|
||||||
|
|
||||||
self.username = 'watchrule_test_user'
|
|
||||||
self.ctx = utils.dummy_context()
|
|
||||||
self.ctx.auth_token = 'abcd1234'
|
|
||||||
|
|
||||||
self._setup_database()
|
|
||||||
|
|
||||||
def _setup_database(self):
|
|
||||||
if self.stack_id is not None:
|
|
||||||
return
|
|
||||||
# Create a dummy stack in the DB as WatchRule instances
|
|
||||||
# must be associated with a stack
|
|
||||||
empty_tmpl = {'HeatTemplateFormatVersion': '2012-12-12'}
|
|
||||||
tmpl = template.Template(empty_tmpl)
|
|
||||||
stack_name = 'dummystack'
|
|
||||||
dummy_stack = stack.Stack(self.ctx, stack_name, tmpl)
|
|
||||||
dummy_stack.state_set(dummy_stack.CREATE, dummy_stack.COMPLETE,
|
|
||||||
'Testing')
|
|
||||||
dummy_stack.store()
|
|
||||||
|
|
||||||
self.stack_id = dummy_stack.id
|
|
||||||
|
|
||||||
def _setup_action_mocks(self, mock_get_resource, now,
|
|
||||||
action_expected=True):
|
|
||||||
"""Setup stubs for the action tests."""
|
|
||||||
timeutils.set_time_override(now)
|
|
||||||
self.addCleanup(timeutils.clear_time_override)
|
|
||||||
|
|
||||||
if action_expected:
|
|
||||||
dummy_action = DummyAction()
|
|
||||||
mock_get_resource.return_value = dummy_action
|
|
||||||
|
|
||||||
def test_minimum(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Minimum',
|
|
||||||
'ComparisonOperator': 'LessThanOrEqualToThreshold',
|
|
||||||
'Threshold': '50'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
last = now - datetime.timedelta(seconds=320)
|
|
||||||
data = [WatchData(77, now - datetime.timedelta(seconds=100))]
|
|
||||||
|
|
||||||
# Test 1 - Values greater than 0 are normal
|
|
||||||
data.append(WatchData(53, now - datetime.timedelta(seconds=150)))
|
|
||||||
wr = watchrule.WatchRule(self.ctx,
|
|
||||||
'testwatch',
|
|
||||||
rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('NORMAL', new_state)
|
|
||||||
|
|
||||||
# Test 2
|
|
||||||
data.append(WatchData(25, now - datetime.timedelta(seconds=250)))
|
|
||||||
wr = watchrule.WatchRule(self.ctx,
|
|
||||||
'testwatch',
|
|
||||||
rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('ALARM', new_state)
|
|
||||||
|
|
||||||
def test_maximum(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
last = now - datetime.timedelta(seconds=320)
|
|
||||||
data = [WatchData(7, now - datetime.timedelta(seconds=100))]
|
|
||||||
|
|
||||||
# Test 1 - values less than 30 are normal
|
|
||||||
data.append(WatchData(23, now - datetime.timedelta(seconds=150)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('NORMAL', new_state)
|
|
||||||
|
|
||||||
# Test 2
|
|
||||||
data.append(WatchData(35, now - datetime.timedelta(seconds=150)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('ALARM', new_state)
|
|
||||||
|
|
||||||
def test_samplecount(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'SampleCount',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '3'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
last = now - datetime.timedelta(seconds=320)
|
|
||||||
data = [WatchData(1, now - datetime.timedelta(seconds=100))]
|
|
||||||
|
|
||||||
# Test 1 - 2 samples is normal
|
|
||||||
data.append(WatchData(1, now - datetime.timedelta(seconds=150)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('NORMAL', new_state)
|
|
||||||
|
|
||||||
# Test 2 - 3 samples is an alarm
|
|
||||||
data.append(WatchData(1, now - datetime.timedelta(seconds=200)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('ALARM', new_state)
|
|
||||||
|
|
||||||
# Test 3 - 3 samples (one old) is normal
|
|
||||||
data.pop(0)
|
|
||||||
data.append(WatchData(1, now - datetime.timedelta(seconds=400)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('NORMAL', new_state)
|
|
||||||
|
|
||||||
def test_sum(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Sum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '100'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
last = now - datetime.timedelta(seconds=320)
|
|
||||||
data = [WatchData(17, now - datetime.timedelta(seconds=100))]
|
|
||||||
|
|
||||||
# Test 1 - values less than 40 are normal
|
|
||||||
data.append(WatchData(23, now - datetime.timedelta(seconds=150)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('NORMAL', new_state)
|
|
||||||
|
|
||||||
# Test 2 - sum greater than 100 is an alarm
|
|
||||||
data.append(WatchData(85, now - datetime.timedelta(seconds=150)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('ALARM', new_state)
|
|
||||||
|
|
||||||
def test_average(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Average',
|
|
||||||
'ComparisonOperator': 'GreaterThanThreshold',
|
|
||||||
'Threshold': '100'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
last = now - datetime.timedelta(seconds=320)
|
|
||||||
data = [WatchData(117, now - datetime.timedelta(seconds=100))]
|
|
||||||
|
|
||||||
# Test 1
|
|
||||||
data.append(WatchData(23, now - datetime.timedelta(seconds=150)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('NORMAL', new_state)
|
|
||||||
|
|
||||||
# Test 2
|
|
||||||
data.append(WatchData(195, now - datetime.timedelta(seconds=250)))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=data,
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.now = now
|
|
||||||
new_state = wr.get_alarm_state()
|
|
||||||
self.assertEqual('ALARM', new_state)
|
|
||||||
|
|
||||||
def test_load(self):
|
|
||||||
# Setup
|
|
||||||
# Insert two dummy watch rules into the DB
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmActions': [u'WebServerRestartPolicy'],
|
|
||||||
u'AlarmDescription': u'Restart the WikiDatabase',
|
|
||||||
u'Namespace': u'system/linux',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'ServiceFailure'}
|
|
||||||
rules = []
|
|
||||||
rules.append(watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='HttpFailureAlarm',
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
state='NORMAL'))
|
|
||||||
rules[0].store()
|
|
||||||
|
|
||||||
rules.append(watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='AnotherWatch',
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
state='NORMAL'))
|
|
||||||
rules[1].store()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
for wn in ('HttpFailureAlarm', 'AnotherWatch'):
|
|
||||||
wr = watchrule.WatchRule.load(self.ctx, wn)
|
|
||||||
self.assertIsInstance(wr, watchrule.WatchRule)
|
|
||||||
self.assertEqual(wn, wr.name)
|
|
||||||
self.assertEqual('NORMAL', wr.state)
|
|
||||||
self.assertEqual(rule, wr.rule)
|
|
||||||
self.assertEqual(datetime.timedelta(seconds=int(rule['Period'])),
|
|
||||||
wr.timeperiod)
|
|
||||||
|
|
||||||
def test_store(self):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmActions': [u'WebServerRestartPolicy'],
|
|
||||||
u'AlarmDescription': u'Restart the WikiDatabase',
|
|
||||||
u'Namespace': u'system/linux',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'ServiceFailure'}
|
|
||||||
|
|
||||||
# Test
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx, watch_name='storetest',
|
|
||||||
stack_id=self.stack_id, rule=rule)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Verify
|
|
||||||
dbwr = watch_rule.WatchRule.get_by_name(self.ctx, 'storetest')
|
|
||||||
self.assertIsNotNone(dbwr)
|
|
||||||
self.assertEqual('storetest', dbwr.name)
|
|
||||||
self.assertEqual(watchrule.WatchRule.NODATA, dbwr.state)
|
|
||||||
self.assertEqual(rule, dbwr.rule)
|
|
||||||
|
|
||||||
def test_evaluate(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
timeutils.set_time_override(now)
|
|
||||||
self.addCleanup(timeutils.clear_time_override)
|
|
||||||
|
|
||||||
# Test 1 - It's not time to evaluate, so should stay NODATA
|
|
||||||
last = now - datetime.timedelta(seconds=299)
|
|
||||||
data = WatchData(25, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('NODATA', wr.state)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
# Test 2 - now - last == Period, so should set NORMAL
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
data = WatchData(25, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('NORMAL', wr.state)
|
|
||||||
self.assertEqual(now, wr.last_evaluated)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
# Test 3 - Now data breaches Threshold, so should set ALARM
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
data = WatchData(35, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('ALARM', wr.state)
|
|
||||||
self.assertEqual(now, wr.last_evaluated)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
def test_evaluate_suspend(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
timeutils.set_time_override(now)
|
|
||||||
self.addCleanup(timeutils.clear_time_override)
|
|
||||||
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
data = WatchData(35, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.state_set(wr.SUSPENDED)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual(wr.SUSPENDED, wr.state)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
def test_evaluate_ceilometer_controlled(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
timeutils.set_time_override(now)
|
|
||||||
self.addCleanup(timeutils.clear_time_override)
|
|
||||||
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
data = WatchData(35, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
wr.state_set(wr.CEILOMETER_CONTROLLED)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual(wr.CEILOMETER_CONTROLLED, wr.state)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
@mock.patch('heat.engine.stack.Stack.resource_by_refid')
|
|
||||||
def test_rule_actions_alarm_normal(self, mock_get_resource):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'AlarmActions': ['DummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
self._setup_action_mocks(mock_get_resource, now,
|
|
||||||
action_expected=False)
|
|
||||||
|
|
||||||
# Set data so rule evaluates to NORMAL state
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
data = WatchData(25, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('NORMAL', wr.state)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
self.assertEqual(0, mock_get_resource.call_count)
|
|
||||||
|
|
||||||
@mock.patch('heat.engine.stack.Stack.resource_by_refid')
|
|
||||||
def test_rule_actions_alarm_alarm(self, mock_get_resource):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'AlarmActions': ['DummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
self._setup_action_mocks(mock_get_resource, now)
|
|
||||||
|
|
||||||
# Set data so rule evaluates to ALARM state
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
data = WatchData(35, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('ALARM', wr.state)
|
|
||||||
self.assertEqual(['DummyAction'], actions)
|
|
||||||
|
|
||||||
# re-set last_evaluated so the rule will be evaluated again.
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
wr.last_evaluated = last
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('ALARM', wr.state)
|
|
||||||
self.assertEqual(['DummyAction'], actions)
|
|
||||||
self.assertGreater(mock_get_resource.call_count, 0)
|
|
||||||
|
|
||||||
@mock.patch('heat.engine.stack.Stack.resource_by_refid')
|
|
||||||
def test_rule_actions_alarm_two_actions(self, mock_get_resource):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'AlarmActions': ['DummyAction', 'AnotherDummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
self._setup_action_mocks(mock_get_resource, now)
|
|
||||||
|
|
||||||
# Set data so rule evaluates to ALARM state
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
data = WatchData(35, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('ALARM', wr.state)
|
|
||||||
self.assertEqual(['DummyAction', 'DummyAction'], actions)
|
|
||||||
self.assertGreater(mock_get_resource.call_count, 0)
|
|
||||||
|
|
||||||
@mock.patch('heat.engine.stack.Stack.resource_by_refid')
|
|
||||||
def test_rule_actions_ok_alarm(self, mock_get_resource):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'OKActions': ['DummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
self._setup_action_mocks(mock_get_resource, now, action_expected=False)
|
|
||||||
|
|
||||||
# On creation the rule evaluates to NODATA state
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('NODATA', wr.state)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
# Move time forward and add data below threshold so we transition from
|
|
||||||
# ALARM -> NORMAL, so evaluate() should output a 'DummyAction'
|
|
||||||
now = now + datetime.timedelta(seconds=300)
|
|
||||||
self._setup_action_mocks(mock_get_resource, now)
|
|
||||||
|
|
||||||
data = WatchData(25, now - datetime.timedelta(seconds=150))
|
|
||||||
wr.watch_data = [data]
|
|
||||||
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('NORMAL', wr.state)
|
|
||||||
self.assertEqual(['DummyAction'], actions)
|
|
||||||
self.assertGreater(mock_get_resource.call_count, 0)
|
|
||||||
|
|
||||||
@mock.patch('heat.engine.stack.Stack.resource_by_refid')
|
|
||||||
def test_rule_actions_nodata(self, mock_get_resource):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'InsufficientDataActions': ['DummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
self._setup_action_mocks(mock_get_resource, now, action_expected=False)
|
|
||||||
|
|
||||||
# Set data so rule evaluates to ALARM state
|
|
||||||
last = now - datetime.timedelta(seconds=300)
|
|
||||||
data = WatchData(35, now - datetime.timedelta(seconds=150))
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[data],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('ALARM', wr.state)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
# Move time forward and don't add data so we transition from
|
|
||||||
# ALARM -> NODATA, so evaluate() should output a 'DummyAction'
|
|
||||||
now = now + datetime.timedelta(seconds=300)
|
|
||||||
self._setup_action_mocks(mock_get_resource, now)
|
|
||||||
|
|
||||||
actions = wr.evaluate()
|
|
||||||
self.assertEqual('NODATA', wr.state)
|
|
||||||
self.assertEqual(['DummyAction'], actions)
|
|
||||||
self.assertGreater(mock_get_resource.call_count, 0)
|
|
||||||
|
|
||||||
@mock.patch('heat.engine.stack.Stack.resource_by_refid')
|
|
||||||
def test_to_ceilometer(self, mock_get_resource):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmDescription': u'test alarm',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'CreateDataMetric'}
|
|
||||||
testdata = {u'CreateDataMetric': {"Unit": "Counter", "Value": "1"}}
|
|
||||||
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='create_data_test',
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
rule=rule)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
mock_ceilometer_client = mock.MagicMock()
|
|
||||||
|
|
||||||
self.ctx._clients = mock.MagicMock()
|
|
||||||
self.ctx._clients.client.return_value = mock_ceilometer_client
|
|
||||||
|
|
||||||
# Test
|
|
||||||
wr._to_ceilometer(testdata)
|
|
||||||
|
|
||||||
# Verify
|
|
||||||
self.assertEqual(1, mock_ceilometer_client.samples.create.call_count)
|
|
||||||
create_kw_args = mock_ceilometer_client.samples.create.call_args[1]
|
|
||||||
expected = {
|
|
||||||
'counter_type': 'gauge',
|
|
||||||
'counter_name': 'CreateDataMetric',
|
|
||||||
'counter_volume': '1',
|
|
||||||
'counter_unit': 'Counter',
|
|
||||||
'resource_metadata': {},
|
|
||||||
'resource_id': None,
|
|
||||||
}
|
|
||||||
self.assertEqual(expected, create_kw_args)
|
|
||||||
|
|
||||||
def test_create_watch_data(self):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmDescription': u'test alarm',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'CreateDataMetric'}
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='create_data_test',
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
rule=rule)
|
|
||||||
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
data = {u'CreateDataMetric': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": []}}
|
|
||||||
wr.create_watch_data(data)
|
|
||||||
|
|
||||||
# Verify
|
|
||||||
obj_wr = watch_rule.WatchRule.get_by_name(self.ctx, 'create_data_test')
|
|
||||||
obj_wds = [wd for wd in obj_wr.watch_data]
|
|
||||||
self.assertEqual(data, obj_wds[0].data)
|
|
||||||
|
|
||||||
# Note, would be good to write another datapoint and check it
|
|
||||||
# but sqlite seems to not interpret the backreference correctly
|
|
||||||
# so dbwr.watch_data is always a list containing only the latest
|
|
||||||
# datapoint. In non-test use on mysql this is not the case, we
|
|
||||||
# correctly get a list of all datapoints where watch_rule_id ==
|
|
||||||
# watch_rule.id, so leave it as a single-datapoint test for now.
|
|
||||||
|
|
||||||
def test_create_watch_data_suspended(self):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmDescription': u'test alarm',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'MetricName': u'CreateDataMetric'}
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='create_data_test',
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
rule=rule,
|
|
||||||
state=watchrule.WatchRule.SUSPENDED)
|
|
||||||
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
data = {u'CreateDataMetric': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": []}}
|
|
||||||
wr.create_watch_data(data)
|
|
||||||
|
|
||||||
# Verify
|
|
||||||
obj_wr = watch_rule.WatchRule.get_by_name(self.ctx, 'create_data_test')
|
|
||||||
obj_wds = [wd for wd in obj_wr.watch_data]
|
|
||||||
self.assertEqual([], obj_wds)
|
|
||||||
|
|
||||||
def test_create_watch_data_match(self):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmDescription': u'test alarm',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'Dimensions': [{u'Name': 'AutoScalingGroupName',
|
|
||||||
u'Value': 'group_x'}],
|
|
||||||
u'MetricName': u'CreateDataMetric'}
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='create_data_test',
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
rule=rule)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
data = {u'CreateDataMetric': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": [{u'AutoScalingGroupName':
|
|
||||||
u'group_x'}]}}
|
|
||||||
self.assertTrue(watchrule.rule_can_use_sample(wr, data))
|
|
||||||
|
|
||||||
def test_create_watch_data_match_2(self):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmDescription': u'test alarm',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'Dimensions': [{u'Name': 'AutoScalingGroupName',
|
|
||||||
u'Value': 'group_x'}],
|
|
||||||
u'MetricName': u'CreateDataMetric'}
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='create_data_test',
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
rule=rule)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
data = {u'not_interesting': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": [
|
|
||||||
{u'AutoScalingGroupName':
|
|
||||||
u'group_x'}]},
|
|
||||||
u'CreateDataMetric': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": [
|
|
||||||
{u'AutoScalingGroupName':
|
|
||||||
u'group_x'}]}}
|
|
||||||
self.assertTrue(watchrule.rule_can_use_sample(wr, data))
|
|
||||||
|
|
||||||
def test_create_watch_data_match_3(self):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmDescription': u'test alarm',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'Dimensions': [{u'Name': 'AutoScalingGroupName',
|
|
||||||
u'Value': 'group_x'}],
|
|
||||||
u'MetricName': u'CreateDataMetric'}
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='create_data_test',
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
rule=rule)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
data = {u'CreateDataMetric': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": [
|
|
||||||
{u'AutoScalingGroupName':
|
|
||||||
u'group_x'}]}}
|
|
||||||
self.assertTrue(watchrule.rule_can_use_sample(wr, data))
|
|
||||||
|
|
||||||
def test_create_watch_data_not_match_metric(self):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmDescription': u'test alarm',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'Dimensions': [{u'Name': 'AutoScalingGroupName',
|
|
||||||
u'Value': 'group_x'}],
|
|
||||||
u'MetricName': u'CreateDataMetric'}
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='create_data_test',
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
rule=rule)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
data = {u'not_this': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": [
|
|
||||||
{u'AutoScalingGroupName':
|
|
||||||
u'group_x'}]},
|
|
||||||
u'nor_this': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": [
|
|
||||||
{u'AutoScalingGroupName':
|
|
||||||
u'group_x'}]}}
|
|
||||||
self.assertFalse(watchrule.rule_can_use_sample(wr, data))
|
|
||||||
|
|
||||||
def test_create_watch_data_not_match_dimensions(self):
|
|
||||||
# Setup
|
|
||||||
rule = {u'EvaluationPeriods': u'1',
|
|
||||||
u'AlarmDescription': u'test alarm',
|
|
||||||
u'Period': u'300',
|
|
||||||
u'ComparisonOperator': u'GreaterThanThreshold',
|
|
||||||
u'Statistic': u'SampleCount',
|
|
||||||
u'Threshold': u'2',
|
|
||||||
u'Dimensions': [{u'Name': 'AutoScalingGroupName',
|
|
||||||
u'Value': 'group_x'}],
|
|
||||||
u'MetricName': u'CreateDataMetric'}
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='create_data_test',
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
rule=rule)
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Test
|
|
||||||
data = {u'CreateDataMetric': {"Unit": "Counter",
|
|
||||||
"Value": "1",
|
|
||||||
"Dimensions": [
|
|
||||||
{u'wrong_key':
|
|
||||||
u'group_x'}]}}
|
|
||||||
self.assertFalse(watchrule.rule_can_use_sample(wr, data))
|
|
||||||
|
|
||||||
def test_destroy(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'AlarmActions': ['DummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
last = timeutils.utcnow()
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name='testwatch_destroy',
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
wr.store()
|
|
||||||
|
|
||||||
# Sanity Check
|
|
||||||
check = watchrule.WatchRule.load(context=self.ctx,
|
|
||||||
watch_name='testwatch_destroy')
|
|
||||||
self.assertIsInstance(check, watchrule.WatchRule)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
wr.destroy()
|
|
||||||
ex = self.assertRaises(exception.EntityNotFound,
|
|
||||||
watchrule.WatchRule.load,
|
|
||||||
context=self.ctx,
|
|
||||||
watch_name='testwatch_destroy')
|
|
||||||
self.assertEqual('Watch Rule', ex.kwargs.get('entity'))
|
|
||||||
|
|
||||||
def test_state_set(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'AlarmActions': ['DummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
last = timeutils.utcnow()
|
|
||||||
watcher = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch_set_state",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
watcher.state_set(watcher.SUSPENDED)
|
|
||||||
|
|
||||||
# Verify
|
|
||||||
self.assertEqual(watcher.SUSPENDED, watcher.state)
|
|
||||||
check = watchrule.WatchRule.load(context=self.ctx,
|
|
||||||
watch_name='testwatch_set_state')
|
|
||||||
self.assertEqual(watchrule.WatchRule.SUSPENDED, check.state)
|
|
||||||
|
|
||||||
@mock.patch('heat.engine.stack.Stack.resource_by_refid')
|
|
||||||
def test_set_watch_state(self, mock_get_resource):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'AlarmActions': ['DummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
self._setup_action_mocks(mock_get_resource, now)
|
|
||||||
|
|
||||||
# Set data so rule evaluates to ALARM state
|
|
||||||
last = now - datetime.timedelta(seconds=200)
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
actions = wr.set_watch_state(watchrule.WatchRule.NODATA)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
actions = wr.set_watch_state(watchrule.WatchRule.NORMAL)
|
|
||||||
self.assertEqual([], actions)
|
|
||||||
|
|
||||||
actions = wr.set_watch_state(watchrule.WatchRule.ALARM)
|
|
||||||
self.assertEqual(['DummyAction'], actions)
|
|
||||||
self.assertGreater(mock_get_resource.call_count, 0)
|
|
||||||
|
|
||||||
def test_set_watch_state_invalid(self):
|
|
||||||
# Setup
|
|
||||||
rule = {'EvaluationPeriods': '1',
|
|
||||||
'MetricName': 'test_metric',
|
|
||||||
'AlarmActions': ['DummyAction'],
|
|
||||||
'Period': '300',
|
|
||||||
'Statistic': 'Maximum',
|
|
||||||
'ComparisonOperator': 'GreaterThanOrEqualToThreshold',
|
|
||||||
'Threshold': '30'}
|
|
||||||
|
|
||||||
now = timeutils.utcnow()
|
|
||||||
|
|
||||||
last = now - datetime.timedelta(seconds=200)
|
|
||||||
wr = watchrule.WatchRule(context=self.ctx,
|
|
||||||
watch_name="testwatch",
|
|
||||||
rule=rule,
|
|
||||||
watch_data=[],
|
|
||||||
stack_id=self.stack_id,
|
|
||||||
last_evaluated=last)
|
|
||||||
|
|
||||||
# Test
|
|
||||||
self.assertRaises(ValueError, wr.set_watch_state, None)
|
|
||||||
self.assertRaises(ValueError, wr.set_watch_state, "BADSTATE")
|
|
Loading…
Reference in New Issue
Block a user