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', 'ResourceActionNotSupported',
'ResourceNotFound', 'ResourceNotFound',
'ResourceNotAvailable', 'ResourceNotAvailable',
'WatchRuleNotFound',
'StackValidationFailed', 'StackValidationFailed',
'InvalidSchemaError', 'InvalidSchemaError',
'InvalidTemplateReference', 'InvalidTemplateReference',

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

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): 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')

View File

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

View File

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

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