Remove stack watch service

This removes the rpc api and related code.

Change-Id: Ib89bcc3ff6a542f49467e2ad6c7e2a716a0dc2b4
Partial-Bug: #1743707
This commit is contained in:
rabi 2017-10-27 12:57:24 +05:30
parent 5bd856627a
commit 8db1b3ea41
23 changed files with 12 additions and 2852 deletions

View File

@ -298,7 +298,6 @@ def map_remote_error(ex):
'ResourceActionNotSupported',
'ResourceNotFound',
'ResourceNotAvailable',
'WatchRuleNotFound',
'StackValidationFailed',
'InvalidSchemaError',
'InvalidTemplateReference',

View File

@ -74,10 +74,6 @@ def launch_engine(setup_logging=True):
launcher = service.launch(cfg.CONF, srv, workers=workers,
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

View File

@ -182,6 +182,9 @@ engine_opts = [
' for stack locking.')),
cfg.BoolOpt('enable_cloud_watch_lite',
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.')),
cfg.BoolOpt('enable_stack_abandon',
default=False,

View File

@ -304,11 +304,6 @@ class ClientNotAvailable(HeatException):
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):
def __init__(self, exception_or_error, resource, action=None):
self.resource = resource

View File

@ -2449,9 +2449,6 @@ class Resource(status.ResourceStatus):
# this is from Ceilometer.
auto = '%(previous)s to %(current)s (%(reason)s)' % details
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'

View File

@ -13,14 +13,12 @@
import six
from heat.common import exception
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine.resources import alarm_base
from heat.engine.resources.openstack.heat import none_resource
from heat.engine import support
from heat.engine import watchrule
class AodhAlarm(alarm_base.BaseAlarm):
@ -178,17 +176,6 @@ class AodhAlarm(alarm_base.BaseAlarm):
alarm = self.client().alarm.create(props)
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):
if prop_diff:
new_props = json_snippet.properties(self.properties_schema,
@ -209,19 +196,7 @@ class AodhAlarm(alarm_base.BaseAlarm):
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):
watch_name = self.physical_resource_name()
watchrule.WatchRule.load(self.context, watch_name=watch_name)
self.client().alarm.get(self.resource_id)

View File

@ -13,193 +13,23 @@
from oslo_config import cfg
from heat.common import exception
from heat.common.i18n import _
from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine.resources.openstack.heat import none_resource
from heat.engine import support
from heat.engine import watchrule
class CloudWatchAlarm(resource.Resource):
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
class CloudWatchAlarm(none_resource.NoneResource):
support_status = support.SupportStatus(
status=support.HIDDEN,
message=_('OS::Heat::CWLiteAlarm is deprecated, '
'use OS::Aodh::Alarm instead.'),
message=_('OS::Heat::CWLiteAlarm resource has been removed '
'since version 10.0.0. Existing stacks can still '
'use it, where it would do nothing for update/delete.'),
version='5.0.0',
previous_status=support.SupportStatus(
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():
cfg.CONF.import_opt('enable_cloud_watch_lite', 'heat.common.config')

View File

@ -15,7 +15,6 @@ import collections
import datetime
import functools
import itertools
import os
import pydoc
import socket
@ -52,22 +51,18 @@ from heat.engine import parameter_groups
from heat.engine import properties
from heat.engine import resources
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_lock
from heat.engine import stk_defn
from heat.engine import support
from heat.engine import template as templatem
from heat.engine import update
from heat.engine import watchrule
from heat.engine import worker
from heat.objects import event as event_object
from heat.objects import resource as resource_objects
from heat.objects import service as service_objects
from heat.objects import snapshot as snapshot_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 worker_api as rpc_worker_api
@ -322,7 +317,6 @@ class EngineService(service.ServiceBase):
# The following are initialized here, but assigned in start() which
# happens after the fork when spawning multiple worker processes
self.stack_watch = None
self.listener = None
self.worker_service = None
self.engine_id = None
@ -341,35 +335,6 @@ class EngineService(service.ServiceBase):
'Please keep the same if you do not want to '
'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):
self.engine_id = service_utils.generate_engine_id()
if self.thread_group_mgr is None:
@ -819,14 +784,6 @@ class EngineService(service.ServiceBase):
elif stack.status != stack.FAILED:
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
stack = self._parse_template_and_validate_stack(
@ -2173,106 +2130,6 @@ class EngineService(service.ServiceBase):
data = snapshot_object.Snapshot.get_all(cnxt, s.id)
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
def show_software_config(self, cnxt, config_id):
return self.software_config.show_software_config(cnxt, config_id)

View File

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

View File

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

View File

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

View File

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

View File

@ -127,66 +127,6 @@ NOTIFY_KEYS = (
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 = (
PARAM_TYPE, PARAM_DEFAULT, PARAM_NO_ECHO,
PARAM_ALLOWED_VALUES, PARAM_ALLOWED_PATTERN, PARAM_MAX_LENGTH,

View File

@ -676,60 +676,6 @@ class EngineClient(object):
resource_status_reason=resource_status_reason),
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):
return self.call(ctxt, self.make_msg('get_revision'))

View File

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

View File

@ -26,7 +26,6 @@ from heat.engine import rsrc_defn
from heat.engine import scheduler
from heat.engine import stack as parser
from heat.engine import template as tmpl
from heat.engine import watchrule
from heat.tests import common
from heat.tests import utils
@ -404,48 +403,6 @@ class AodhAlarmTest(common.HeatTestCase):
'MEMAlarmHigh', resource_defns['MEMAlarmHigh'], stack)
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):
snippet = template_format.parse(not_string_alarm_template)
self.stack = utils.parse_stack(snippet)
@ -457,25 +414,12 @@ class AodhAlarmTest(common.HeatTestCase):
res.client().alarm.get.return_value = mock_alarm
return res
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
def test_check(self, mock_load):
def test_check(self):
res = self._prepare_resource()
scheduler.TaskRunner(res.check)()
self.assertEqual((res.CHECK, res.COMPLETE), res.state)
@mock.patch.object(alarm.watchrule.WatchRule, 'load')
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):
def test_check_alarm_failure(self):
res = self._prepare_resource()
res.client().alarm.get.side_effect = Exception('Boom')

View File

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

View File

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

View File

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

View File

@ -219,7 +219,6 @@ class WaitConditionMetadataUpdateTest(common.HeatTestCase):
def setUp(self):
super(WaitConditionMetadataUpdateTest, self).setUp()
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(glance.GlanceClientPlugin, 'find_image_by_name_or_id')

View File

@ -301,23 +301,6 @@ class EngineRpcAPITestCase(common.HeatTestCase):
details={u'wordpress': []},
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):
self._test_engine_api('list_software_configs', 'call',
limit=mock.ANY, marker=mock.ANY)

View File

@ -537,9 +537,6 @@ class SignalTest(common.HeatTestCase):
'previous': 'SUCCESS'}
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_expected = str_details
@ -547,13 +544,11 @@ class SignalTest(common.HeatTestCase):
none_expected = 'No signal details provided'
# Test
for test_d in (ceilo_details, watch_details, str_details,
none_details):
for test_d in (ceilo_details, str_details, none_details):
rsrc.signal(details=test_d)
# Verify
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', none_expected)

View File

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