#
#    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.assertTrue(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.assertTrue(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.assertTrue(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.assertTrue(mock_get_resource.call_count > 0)

    @mock.patch('ceilometerclient.client.AuthPlugin.redirect_to_aodh_endpoint')
    @mock.patch('heat.engine.stack.Stack.resource_by_refid')
    def test_to_ceilometer(self, mock_get_resource, mock_redirect):
        # 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.assertTrue(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")
