Merge "Revert "Remove autoscaling until gate issues sorted out""
diff --git a/.zuul.yaml b/.zuul.yaml
index ecb9be2..613717d 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -1,62 +1,8 @@
 - job:
     name: telemetry-tempest-base
-    description: |
-      This job is for stable branch prior to Ussuri for testing
-      on py2.
-    parent: devstack-tempest
-    irrelevant-files:
-      - ^(test-|)requirements.txt$
-      - ^setup.cfg$
-      - ^doc/.*$
-      - ^.*\.rst$
-      - ^releasenotes/.*$
-    timeout: 7800
-    required-projects:
-      - openstack/devstack-gate
-      - openstack/panko
-      - openstack/aodh
-      - openstack/ceilometer
-      - openstack/telemetry-tempest-plugin
-      - openstack/heat-tempest-plugin
-      - openstack/heat
-      - openstack/dib-utils
-      - openstack/diskimage-builder
-      - openstack/tempest
-    vars:
-      configure_swap_size: 8192
-      devstack_plugins:
-        heat: https://opendev.org/openstack/heat
-        ceilometer: https://opendev.org/openstack/ceilometer
-        aodh: https://opendev.org/openstack/aodh
-        panko: https://opendev.org/openstack/panko
-      devstack_services:
-        tempest: true
-      devstack_localrc:
-        TEMPEST_PLUGINS: '"/opt/stack/telemetry-tempest-plugin /opt/stack/heat-tempest-plugin"'
-        GNOCCHI_ARCHIVE_POLICY_TEMPEST: "ceilometer-high-rate"
-        CEILOMETER_BACKEND: "gnocchi"
-        CEILOMETER_PIPELINE_INTERVAL: 15
-        USE_PYTHON3: False
-      devstack_local_conf:
-        test-config:
-          $TEMPEST_CONFIG:
-            telemetry:
-              disable_ssl_certificate_validation: True
-      tempest_test_regex: telemetry_tempest_plugin
-      tox_envlist: all
-    branches:
-      - stable/ocata
-      - stable/pike
-      - stable/queens
-      - stable/rocky
-      - stable/stein
-      - stable/train
-
-- job:
-    name: telemetry-tempest-base
     parent: devstack-tempest
     description: |
-      This job is for testing on py3 which is Ussuri onwards.
+      This job is for testing telemetry components. We test stable branches from wallaby onwards.
     irrelevant-files:
       - ^(test-|)requirements.txt$
       - ^setup.cfg$
@@ -75,6 +21,8 @@
       - openstack/dib-utils
       - openstack/diskimage-builder
       - openstack/tempest
+      - gnocchixyz/gnocchi
+      - infrawatch/sg-core
     vars: &base_vars
       configure_swap_size: 8192
       devstack_plugins:
@@ -82,17 +30,29 @@
         ceilometer: https://opendev.org/openstack/ceilometer
         aodh: https://opendev.org/openstack/aodh
         panko: https://opendev.org/openstack/panko
+        sg-core: https://github.com/infrawatch/sg-core
       devstack_services:
         tempest: true
       devstack_localrc:
         USE_PYTHON3: True
         TEMPEST_PLUGINS: '"/opt/stack/telemetry-tempest-plugin /opt/stack/heat-tempest-plugin"'
         GNOCCHI_ARCHIVE_POLICY_TEMPEST: "ceilometer-high-rate"
+        # NOTE(jwysogla): We can define both of the variables. In versions, where
+        # the ceilometer devstack plugin doesn't support the CEILOMETER_BACKENDS,
+        # it'll just ignore it and use the CEILOMETER_BACKEND. In versions, where
+        # CEILOMETER_BACKENDS is supported, the ceilometer devstack plugin will
+        # just try to merge the variables, so the final contents in this casse will
+        # be "gnocchi,sg-core"
         CEILOMETER_BACKEND: "gnocchi"
+        CEILOMETER_BACKENDS: "gnocchi,sg-core"
         CEILOMETER_PIPELINE_INTERVAL: 15
+        CEILOMETER_ALARM_THRESHOLD: 6000000000
+        GLOBAL_VENV: False
       devstack_local_conf:
         test-config:
           $TEMPEST_CONFIG:
+            service_available:
+              sg-core: True
             telemetry:
               disable_ssl_certificate_validation: True
       tempest_test_regex: telemetry_tempest_plugin
@@ -101,37 +61,19 @@
 - job:
     name: telemetry-dsvm-integration
     parent: telemetry-tempest-base
-    branches: ^(?!stable/(ocata|pike|queens|rocky|stein|train)).*$
 
 - job:
     name: telemetry-dsvm-integration
     parent: telemetry-tempest-base
     branches:
-      - stable/train
       - stable/wallaby
     vars:
       devstack_localrc:
         USE_PYTHON3: False
 
 - job:
-    name: telemetry-dsvm-integration
-    parent: telemetry-tempest-base
-    branches:
-      - stable/pike
-      - stable/queens
-      - stable/rocky
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: False
-        GNOCCHI_ARCHIVE_POLICY_TEMPEST: "ceilometer-high"
-        GNOCCHI_ARCHIVE_POLICY: "high"
-        CEILOMETER_ALARM_AGGREGATION_METHOD: "mean"
-        CEILOMETER_ALARM_METRIC_NAME: "cpu_util"
-
-- job:
     name: telemetry-dsvm-integration-ipv6-only
     parent: devstack-tempest-ipv6
-    branches: ^(?!stable/(ocata|pike|queens|rocky|stein)).*$
     description: |
       Telemetry devstack tempest tests job for IPv6-only deployment
     required-projects: *base_required_projects
@@ -140,20 +82,11 @@
 - job:
     name: telemetry-dsvm-integration-wallaby
     parent: telemetry-dsvm-integration
-    nodeset: openstack-single-node-bionic
+    nodeset: openstack-single-node-focal
     override-checkout: stable/wallaby
     voting: false
 
 - job:
-    name: telemetry-dsvm-integration-train
-    parent: telemetry-dsvm-integration
-    nodeset: openstack-single-node-bionic
-    override-checkout: stable/train
-    vars:
-      devstack_localrc:
-        USE_PYTHON3: True
-
-- job:
     name: telemetry-dsvm-integration-centos-8s
     nodeset: devstack-single-node-centos-8-stream
     parent: telemetry-dsvm-integration
@@ -206,7 +139,6 @@
       jobs:
         - telemetry-dsvm-integration
         - telemetry-dsvm-integration-wallaby
-        - telemetry-dsvm-integration-train
         - telemetry-dsvm-integration-ipv6-only
         - telemetry-dsvm-integration-centos-9s
         - telemetry-dsvm-integration-centos-9s-fips
diff --git a/babel.cfg b/babel.cfg
deleted file mode 100644
index 15cd6cb..0000000
--- a/babel.cfg
+++ /dev/null
@@ -1,2 +0,0 @@
-[python: **.py]
-
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 657cd89..3fdb643 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -15,6 +15,7 @@
    readme
    installation
    contributing
+   test_additions
 
 Indices and tables
 ==================
diff --git a/doc/source/test_additions.rst b/doc/source/test_additions.rst
new file mode 100644
index 0000000..8b1e103
--- /dev/null
+++ b/doc/source/test_additions.rst
@@ -0,0 +1,12 @@
+=======================
+Adding additional tests
+=======================
+
+:Modify .zuul.yaml:
+    Make required modifications to the devstack configuration in the .zuul.yaml. Mainly add any new required projects and env variables. If you require a non-openstack project, check that it's listed in the project-config under the openstack tenant: https://opendev.org/openstack/project-config/src/branch/master/zuul/main.yaml. Create a patch for the project-config to add that project if it isn't already there, otherwise it won't be available in check and gate jobs.
+
+:Add tests:
+    Add new tests. These can be either the scenario tests using gabbi: https://gabbi.readthedocs.io/en/latest/ or python code using tempest directly. See https://docs.openstack.org/tempest/latest/field_guide/index.html. If there is some configuration required to run the tests, take a look at the telemetry_tempest_plugin/conf.py.
+
+:Example:
+    An example of adding a simple test can be seen here: https://review.opendev.org/c/openstack/telemetry-tempest-plugin/+/898201
diff --git a/requirements.txt b/requirements.txt
index 51dcad8..4e6bb28 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -5,6 +5,5 @@
 pbr>=2.0 # Apache-2.0
 oslo.config>=6.0.0 # Apache-2.0
 oslo.utils>=3.37.0 # Apache-2.0
-six>=1.10.0 # MIT
 tempest>=17.1.0 # Apache-2.0
 gabbi>=1.30.0 # Apache-2.0
diff --git a/telemetry_tempest_plugin/aodh/api/base.py b/telemetry_tempest_plugin/aodh/api/base.py
index 29b1d61..7298170 100644
--- a/telemetry_tempest_plugin/aodh/api/base.py
+++ b/telemetry_tempest_plugin/aodh/api/base.py
@@ -29,8 +29,10 @@
     @classmethod
     def skip_checks(cls):
         super(BaseAlarmingTest, cls).skip_checks()
-        if not CONF.service_available.aodh_plugin:
+        if not CONF.service_available.aodh:
             raise cls.skipException("Aodh support is required")
+        if not CONF.service_available.gnocchi:
+            raise cls.skipException("Gnocchi support is required")
 
     @classmethod
     def setup_clients(cls):
diff --git a/telemetry_tempest_plugin/aodh/api/test_alarming_api.py b/telemetry_tempest_plugin/aodh/api/test_alarming_api.py
index 9032a1a..fbdaa75 100644
--- a/telemetry_tempest_plugin/aodh/api/test_alarming_api.py
+++ b/telemetry_tempest_plugin/aodh/api/test_alarming_api.py
@@ -9,6 +9,8 @@
 #    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 random
+
 from tempest import config
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -61,6 +63,15 @@
         alarm_id = body['alarm_id']
         self.assertDictContainsSubset(self.rule, body['event_rule'])
 
+        # Update alarm with same rule and name
+        body = self.alarming_client.update_alarm(
+            alarm_id,
+            event_rule=self.rule,
+            name=alarm_name,
+            type='event')
+        self.assertEqual(alarm_name, body['name'])
+        self.assertDictContainsSubset(self.rule, body['event_rule'])
+
         # Update alarm with new rule and new name
         new_rule = {
             "event_type": "compute.instance.create",
@@ -79,10 +90,19 @@
         self.assertEqual(alarm_name_updated, body['name'])
         self.assertDictContainsSubset(new_rule, body['event_rule'])
 
+        # Update severity
+        body = self.alarming_client.update_alarm(
+            alarm_id,
+            event_rule=new_rule,
+            name=alarm_name_updated,
+            type='event',
+            severity='low')
+
         # Get and verify details of an alarm after update
         body = self.alarming_client.show_alarm(alarm_id)
         self.assertEqual(alarm_name_updated, body['name'])
         self.assertDictContainsSubset(new_rule, body['event_rule'])
+        self.assertEqual('low', body['severity'])
 
         # Get history for the alarm and verify the same
         body = self.alarming_client.show_alarm_history(alarm_id)
@@ -91,11 +111,141 @@
         self.assertEqual("creation", body[1]['type'])
         self.assertIn(alarm_name, body[1]['detail'])
 
+        # Query by state and verify
+        query = ['state', 'eq', 'insufficient data']
+        body = self.alarming_client.list_alarms(query)
+        self.assertNotEqual(0, len(body))
+        self.assertEqual(set(['insufficient data']),
+                         set(alarm['state'] for alarm in body))
+
+        # Query by type and verify
+        query = ['type', 'eq', 'event']
+        body = self.alarming_client.list_alarms(query)
+        self.assertNotEqual(0, len(body))
+        self.assertEqual(set(['event']), set(alarm['type'] for alarm in body))
+
         # Delete alarm and verify if deleted
         self.alarming_client.delete_alarm(alarm_id)
         self.assertRaises(lib_exc.NotFound,
                           self.alarming_client.show_alarm, alarm_id)
 
+    @decorators.idempotent_id('b666f7d5-ce8c-4b2d-887e-15993433c2e9')
+    def test_create_query_delete_disabled_alarm(self):
+        # Create an alarm
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        body = self.alarming_client.create_alarm(
+            name=alarm_name, type='event', enabled=False,
+            event_rule=self.rule)
+        self.assertEqual(alarm_name, body['name'])
+        alarm_id = body['alarm_id']
+        self.assertDictContainsSubset(self.rule, body['event_rule'])
+        self.assertFalse(body['enabled'])
+
+        # Query by enabled false and verify
+        query = ['enabled', 'eq', 'false']
+        body = self.alarming_client.list_alarms(query)
+        self.assertNotEqual(0, len(body))
+        self.assertEqual(set([False]),
+                         set(alarm['enabled'] for alarm in body))
+
+        # Delete alarm and verify if deleted
+        self.alarming_client.delete_alarm(alarm_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.alarming_client.show_alarm, alarm_id)
+
+    @decorators.idempotent_id('bf44b72f-0384-4b34-ab78-bdfd3bf1b16c')
+    def test_create_delete_alarm_defaults(self):
+        # Create an alarm
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        body = self.alarming_client.create_alarm(
+            name=alarm_name, type='event',
+            event_rule=self.rule)
+        self.assertEqual(alarm_name, body['name'])
+        alarm_id = body['alarm_id']
+        self.assertDictContainsSubset(self.rule, body['event_rule'])
+
+        # Verify default
+        expected_defaults = {
+            'enabled': True,
+            'ok_actions': [],
+            'alarm_actions': [],
+            'insufficient_data_actions': [],
+            'repeat_actions': False,
+        }
+        for key in expected_defaults:
+            self.assertEqual(expected_defaults[key], body[key])
+
+        # Delete alarm and verify if deleted
+        self.alarming_client.delete_alarm(alarm_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.alarming_client.show_alarm, alarm_id)
+
+    @decorators.idempotent_id('0a883365-7158-4dbb-a946-3b07dc171c93')
+    def test_create_excercise_delete_alarm_state(self):
+        # Create an alarm
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        body = self.alarming_client.create_alarm(
+            name=alarm_name, type='event',
+            event_rule=self.rule)
+        self.assertEqual(alarm_name, body['name'])
+        alarm_id = body['alarm_id']
+        self.assertDictContainsSubset(self.rule, body['event_rule'])
+
+        # Verify initial state
+        body = self.alarming_client.show_alarm(alarm_id)
+        self.assertEqual("insufficient data", body['state'])
+        self.assertEqual("Not evaluated yet", body['state_reason'])
+
+        # Update state and verify
+        self.alarming_client.alarm_set_state(alarm_id, state='ok')
+        body = self.alarming_client.show_alarm(alarm_id)
+        self.assertEqual('ok', body['state'])
+        self.assertEqual('Manually set via API', body['state_reason'])
+
+        # Update state and verify reason read only
+        body['state'] = 'alarm'
+        body['state_reason'] = 'Oops!'
+        self.alarming_client.update_alarm(**body)
+        body = self.alarming_client.show_alarm(alarm_id)
+        self.assertEqual('alarm', body['state'])
+        self.assertEqual('Manually set via API', body['state_reason'])
+
+        # Delete alarm and verify if deleted
+        self.alarming_client.delete_alarm(alarm_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.alarming_client.show_alarm, alarm_id)
+
+    @decorators.idempotent_id('ced16dd6-cbd8-4a2b-a9c3-aed6e4a7102c')
+    def test_create_query_delete_alarm_same_name(self):
+        # Create two alarms with same name
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        body1 = self.alarming_client.create_alarm(
+            name=alarm_name, type='event',
+            event_rule=self.rule)
+        body2 = self.alarming_client.create_alarm(
+            name=alarm_name, type='event',
+            event_rule=self.rule)
+        alarm1_id = body1['alarm_id']
+        alarm2_id = body2['alarm_id']
+        self.assertEqual(alarm_name, body1['name'])
+        self.assertEqual(alarm_name, body2['name'])
+        self.assertNotEqual(alarm1_id, alarm2_id)
+
+        # Query by name and verify
+        query = ['name', 'eq', alarm_name]
+        body = self.alarming_client.list_alarms(query)
+        self.assertEqual(2, len(body))
+        self.assertEqual(set([alarm_name]),
+                         set(alarm['name'] for alarm in body))
+
+        # Delete alarms and verify if deleted
+        self.alarming_client.delete_alarm(alarm1_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.alarming_client.show_alarm, alarm1_id)
+        self.alarming_client.delete_alarm(alarm2_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.alarming_client.show_alarm, alarm2_id)
+
     @decorators.idempotent_id('aca49486-70bb-4016-87e0-f6131374f742')
     def test_set_get_alarm_state(self):
         alarm_states = ['ok', 'alarm', 'insufficient data']
@@ -111,3 +261,141 @@
         # Get alarm state and verify
         state = self.alarming_client.show_alarm_state(alarm['alarm_id'])
         self.assertEqual(new_state, state.data)
+
+    @decorators.idempotent_id('0cc2f5d1-6f48-4274-bfa8-f62f82eab6ed')
+    def test_get_capabilities(self):
+
+        response = self.alarming_client.show_capabilities()
+        self.assertIsNotNone(response)
+        self.assertNotEqual({}, response)
+        self.assertIn('api', response)
+        self.assertIn('alarm_storage', response)
+
+    @decorators.idempotent_id('d42d0103-0497-4109-9746-dacaa17e831c')
+    def test_get_versions(self):
+
+        response = self.alarming_client.show_version()
+        media_types = [
+            {
+                'base': 'application/json',
+                'type': 'application/vnd.openstack.telemetry-v2+json'
+            }, {
+                'base': 'application/xml',
+                'type': 'application/vnd.openstack.telemetry-v2+xml'
+            }
+        ]
+        self.assertIsNotNone(response)
+        self.assertNotEqual({}, response)
+        self.assertEqual('v2', response['versions']['values'][0]['id'])
+        self.assertIn('links', response['versions']['values'][0])
+        self.assertEqual(media_types, response['versions']['values'][0][
+            'media-types'])
+        self.assertIn('status', response['versions']['values'][0])
+        self.assertIn('updated', response['versions']['values'][0])
+
+    @decorators.idempotent_id('a45a95f6-7855-4dbc-a507-af0f48cc3370')
+    def test_create_n_delete_alarm_duplicate_actions(self):
+        # create dual actions
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        body = self.alarming_client.create_alarm(
+            name=alarm_name,
+            alarm_actions=['http://no.where', 'http://no.where'],
+            type='gnocchi_aggregation_by_metrics_threshold',
+            gnocchi_aggregation_by_metrics_threshold_rule=rule
+        )
+        alarm_id = body['alarm_id']
+        alarms = self.alarming_client.list_alarms(['name', 'eq', alarm_name])
+        self.assertEqual(1, len(alarms))
+        self.assertEqual(['http://no.where'], alarms[0]['alarm_actions'])
+
+        # Delete alarm and verify if deleted
+        self.alarming_client.delete_alarm(alarm_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.alarming_client.show_alarm, alarm_id)
+
+    @decorators.idempotent_id('c1dcefdf-3b96-40d0-8f39-04fc0702ab6b')
+    def test_create_n_delete_alarm_rule_loadbalancer(self):
+        # create dual actions
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {
+            "pool_id": "2177ccd8-b09c-417a-89a0-e8d2419be612",
+            "stack_id": "1b974012-ebcb-4888-8ae2-47714d4d2c4d",
+            "autoscaling_group_id": "681c9266-61d2-4c9a-ad18-526807f6adc0"
+        }
+        body = self.alarming_client.create_alarm(
+            name=alarm_name,
+            type='loadbalancer_member_health',
+            loadbalancer_member_health_rule=rule
+        )
+        alarm_id = body['alarm_id']
+        alarms = self.alarming_client.list_alarms(['name', 'eq', alarm_name])
+        self.assertEqual(1, len(alarms))
+
+        # Check the actions are empty
+        self.assertEqual([], alarms[0]['alarm_actions'])
+
+        # Delete alarm and verify if deleted
+        self.alarming_client.delete_alarm(alarm_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.alarming_client.show_alarm, alarm_id)
+
+    @decorators.idempotent_id('e1d65c3c-a64d-4968-949c-96f2b2d8b363')
+    def test_create_list_sort_limit_delete_alarm(self):
+        # create test alarms
+        alarms = {}
+        for i in range(3):
+            alarm_name = data_utils.rand_name('sorted_alarms')
+            alarms[alarm_name] = []
+            for j in range(random.randint(2, 4)):
+                body = self.alarming_client.create_alarm(
+                    name=alarm_name, type='event',
+                    event_rule=self.rule)
+                alarms[alarm_name].append(body['alarm_id'])
+        ordered_alarms = []
+        for key in sorted(alarms):
+            ordered_alarms.extend([(key, a) for a in sorted(alarms[key])])
+
+        # Sort by name and verify
+        sort = ['name:asc']
+        body = self.alarming_client.list_alarms(sort=sort)
+        self.assertEqual([alarm[0] for alarm in ordered_alarms],
+                         [alarm['name'] for alarm in body if alarm[
+                             'name'].startswith('tempest-sorted_alarms')])
+        sort = ['name']
+        body = self.alarming_client.list_alarms(sort=sort)
+        self.assertEqual([alarm[0] for alarm in ordered_alarms],
+                         [alarm['name'] for alarm in body if alarm[
+                             'name'].startswith('tempest-sorted_alarms')])
+
+        # multiple sorts
+        sort = ['name:asc', 'alarm_id:asc']
+        body = self.alarming_client.list_alarms(sort=sort)
+        name_ids = [(a['name'], a['alarm_id']) for a in body if a[
+            'name'].startswith('tempest-sorted_alarms')]
+        self.assertEqual(ordered_alarms, name_ids)
+
+        # limit and sort
+        sort = ['name:asc', 'alarm_id:asc']
+        limit = 2
+        body = self.alarming_client.list_alarms(limit=limit)
+        self.assertEqual(2, len(body))
+        body = self.alarming_client.list_alarms(sort=sort, limit=limit)
+        self.assertEqual(2, len(body))
+        self.assertEqual([ordered_alarms[0][0], ordered_alarms[1][0]],
+                         [body[0]['name'], body[1]['name']])
+        body = self.alarming_client.list_alarms(
+            sort=sort, marker=ordered_alarms[1][1])
+        name_ids = [(a['name'], a['alarm_id']) for a in body if a[
+            'name'].startswith('tempest-sorted_alarms')]
+        self.assertEqual(ordered_alarms[2:], name_ids)
+
+        # Delete alarms and verify if deleted
+        for name, alarm_id in ordered_alarms:
+            self.alarming_client.delete_alarm(alarm_id)
+            self.assertRaises(lib_exc.NotFound,
+                              self.alarming_client.show_alarm, alarm_id)
diff --git a/telemetry_tempest_plugin/aodh/api/test_alarming_api_negative.py b/telemetry_tempest_plugin/aodh/api/test_alarming_api_negative.py
index bc5650f..a1bc0d8 100644
--- a/telemetry_tempest_plugin/aodh/api/test_alarming_api_negative.py
+++ b/telemetry_tempest_plugin/aodh/api/test_alarming_api_negative.py
@@ -12,6 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import datetime
+
 from oslo_utils import uuidutils
 from tempest.lib.common.utils import data_utils
 from tempest.lib import decorators
@@ -38,6 +40,32 @@
                           non_existent_id)
 
     @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('d6adbf73-03ca-4730-b1d7-13233475d271')
+    def test_get_non_existent_alarm_timestamp(self):
+        # list invalid query
+        date_time = datetime.datetime(2012, 7, 2, 10, 41)
+        isotime = date_time.isoformat()
+        query = ['timestamp', 'gt', isotime]
+        self.assertRaises(lib_exc.BadRequest, self.alarming_client.list_alarms,
+                          query)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('f6f9e0a3-9623-43bb-a39a-284073c38b1f')
+    def test_get_alarms_all_projects(self):
+        # don't get alarms from all projects
+        query = ['all_projects', 'eq', 'true']
+        self.assertRaises(lib_exc.Forbidden, self.alarming_client.list_alarms,
+                          query)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('b3f61233-92f6-4402-a98d-fd5858379949')
+    def test_get_alarms_invalid_op(self):
+        # fail on op
+        query = ['project_id', 'ne', 'b3f61233-92f6-4402-a98d-fd5858379949']
+        self.assertRaises(lib_exc.BadRequest, self.alarming_client.list_alarms,
+                          query)
+
+    @decorators.attr(type=['negative'])
     @decorators.idempotent_id('ef45000d-0a72-4781-866d-4cb7bf2582ae')
     def test_get_update_show_history_delete_deleted_alarm(self):
         # get, update and delete the deleted alarm
@@ -73,3 +101,480 @@
         # delete the deleted alarm
         self.assertRaises(lib_exc.NotFound, self.alarming_client.delete_alarm,
                           alarm_id)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('3bd874d7-dcbf-410e-ab08-829969282d92')
+    def test_create_invalid_alarm_constraint_start(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        tc_name = data_utils.rand_name('time_constraint')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        tc = [{'name': tc_name,
+               'start': '11:00am',
+               'duration': 10}]
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            time_constraints=tc,
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.skip_because(bug="2045115")
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('384dec5a-af5b-4a9e-8dc1-9d056fca71d6')
+    def test_create_null_alarm_constraint(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        tc = None
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            time_constraints=tc,
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('f358ee93-798e-4641-8fdc-13dc2f7a71c5')
+    def test_create_duplicate_alarm_constraint_name(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        tc_name = data_utils.rand_name('time_constraint')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        tc = [{'name': tc_name,
+               'start': '* 11 * * *',
+               'duration': 10},
+              {'name': tc_name,
+               'start': '* * * * *',
+               'duration': 20}]
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            time_constraints=tc,
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('e1d5d605-61a0-415b-bcaf-dc3a15717344')
+    def test_create_invalid_alarm_constraint_timezone(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        tc_name = data_utils.rand_name('time_constraint')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        tc = [{'name': tc_name,
+               'start': '* 11 * * *',
+               'duration': 10,
+               'timezone': 'aaaa'}]
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            time_constraints=tc,
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('7d3ee20c-8a0f-4a43-8a1a-2168da9905da')
+    def test_create_invalid_alarm_constraint_duration(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        tc_name = data_utils.rand_name('time_constraint')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        tc = [{'name': tc_name,
+               'start': '* 11 * * *',
+               'duration': -10}]
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            time_constraints=tc,
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('21ab2919-8680-44d8-be90-57aac5b22474')
+    def test_create_invalid_alarm_rule_granularity(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        tc_name = data_utils.rand_name('time_constraint')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0,
+                'granularity': -1}
+        tc = [{'name': tc_name,
+               'start': '* 11 * * *',
+               'duration': 10}]
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            time_constraints=tc,
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('b942eeb3-7fc2-4ba4-ad8f-674f3ac7fb96')
+    def test_create_invalid_alarm_none_rule(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        tc_name = data_utils.rand_name('time_constraint')
+        rule = None
+        tc = [{'name': tc_name,
+               'start': '* 11 * * *',
+               'duration': -10}]
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            time_constraints=tc,
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('a805f2f3-ea3e-4574-b616-3dbe86cb95f8')
+    def test_create_invalid_alarm_input_state(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            state='bad_state',
+            type='gnocchi_aggregation_by_metrics_threshold',
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('ea90ef4a-135d-48f2-b984-c156a89f627c')
+    def test_create_invalid_alarm_input_severity(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            state='ok',
+            severity='bad_value',
+            type='gnocchi_aggregation_by_metrics_threshold',
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('7373e681-8e11-4071-a955-aa9582bef914')
+    def test_create_invalid_alarm_input_type(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            state='ok',
+            type='bad_type',
+            bad_type_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('3af990af-f134-4b87-9234-a25280afc176')
+    def test_create_invalid_alarm_input_enabled_string(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            state='ok',
+            enabled='bad_value',
+            type='gnocchi_aggregation_by_metrics_threshold',
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.skip_because(bug="2045118")
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('db999b7e-3bc8-4be3-90f3-9d0034f61e8a')
+    def test_create_invalid_alarm_input_enabled_int(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            state='ok',
+            enabled=0,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('0c64f10f-8529-46a5-9172-f0a64789632f')
+    def test_create_invalid_alarm_statistic(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'magic',
+                'comparison_operator': 'gt',
+                'threshold': 2.0}
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('0c421fe7-69e9-4a7c-9df0-547f7bc4c91a')
+    def test_create_invalid_alarm_com_op(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'comparison_operator': 'bd_co',
+                'threshold': 20.0}
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type='gnocchi_aggregation_by_metrics_threshold',
+            gnocchi_aggregation_by_metrics_threshold_rule=rule)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('6d3f39dd-64ee-438d-804e-f193dc2c7d4b')
+    def test_list_invalid_project(self):
+        # Get invalid paths
+        self.assertRaises(
+            lib_exc.Unauthorized,
+            self.alarming_client.list_alarms,
+            ['project_id', 'eq', 'other-project'])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('c4718d72-3091-4965-b3f2-6d6c2375d26a')
+    def test_list_non_existing_history(self):
+        # Get invalid paths
+        alarm_id = data_utils.rand_name('aid')
+        self.assertRaises(
+            lib_exc.NotFound,
+            self.alarming_client.show_alarm_history,
+            alarm_id=alarm_id)
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('5bbccedd-dd80-462c-a6da-d796d7667b5e')
+    def test_create_alarm_wsme_workaround(self):
+        # create bad time constraint
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule_key = 'gnocchi_aggregation_by_metrics_threshold_rule'
+        rules = {
+            'type': {
+                'name': alarm_name,
+                rule_key: {
+                    'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                                'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                    'aggregation_method': 'mean',
+                    'threshold': 2.0,
+                }
+            },
+            'name': {
+                'type': 'gnocchi_aggregation_by_metrics_threshold',
+                rule_key: {
+                    'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                                'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                    'aggregation_method': 'mean',
+                    'threshold': 2.0,
+                }
+            },
+            'threshold_rule/metrics': {
+                'name': alarm_name,
+                'type': 'gnocchi_aggregation_by_metrics_threshold',
+                rule_key: {
+                    'aggregation_method': 'mean',
+                    'threshold': 2.0,
+                }
+            },
+            'threshold_rule/threshold': {
+                'name': alarm_name,
+                'type': 'gnocchi_aggregation_by_metrics_threshold',
+                rule_key: {
+                    'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                                'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                    'aggregation_method': 'mean',
+                }
+            },
+        }
+
+        for field, adef in rules.items():
+            self.assertRaises(
+                lib_exc.BadRequest,
+                self.alarming_client.create_alarm,
+                name=getattr(adef, 'name', None),
+                type=getattr(adef, 'type', None),
+                gnocchi_aggregation_by_metrics_threshold_rule=adef[rule_key]
+                )
+
+    def _do_create_alarm_invalid_action(self, ok_actions=None,
+                                        alarm_actions=None,
+                                        insufficient_data_actions=None,
+                                        error_message=None):
+
+        ok_actions = ok_actions or []
+        alarm_actions = alarm_actions or []
+        insufficient_data_actions = insufficient_data_actions or []
+        rule_key = 'gnocchi_aggregation_by_metrics_threshold_rule'
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        adef = {
+            'enabled': False,
+            'state': 'ok',
+            'type': 'gnocchi_aggregation_by_metrics_threshold',
+            'ok_actions': ok_actions,
+            'alarm_actions': alarm_actions,
+            'insufficient_data_actions': insufficient_data_actions,
+            'repeat_actions': True,
+            rule_key: {
+                'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'comparison_operator': 'le',
+                'aggregation_method': 'count',
+                'threshold': 50,
+                'evaluation_periods': '3',
+                'granularity': '180',
+            }
+        }
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.create_alarm,
+            name=alarm_name,
+            type=adef['type'],
+            enabled=adef['enabled'],
+            state=adef['state'],
+            ok_actions=adef['ok_actions'],
+            alarm_actions=adef['alarm_actions'],
+            insufficient_data_actions=adef['insufficient_data_actions'],
+            repeat_actions=adef['repeat_actions'],
+            gnocchi_aggregation_by_metrics_threshold_rule=adef[rule_key])
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('95e505c3-51e0-4ac8-8e75-6dfb5828fcd2')
+    def test_create_invalid_alarm_ok_actions(self):
+        self._do_create_alarm_invalid_action(
+            ok_actions=['spam://something/ok'],
+            error_message='Unsupported action spam://something/ok')
+
+    @decorators.skip_because(bug="2045116")
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('4b2b7be0-9d13-4e36-ad28-d11c5aa06411')
+    def test_create_invalid_alarm_too_many_actions(self):
+        self._do_create_alarm_invalid_action(
+            ok_actions=['http://no.where', 'http://no.where2'],
+            error_message="alarm_actions count exceeds maximum value 1")
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('2332b637-8193-448c-8b37-e0b6a6fded34')
+    def test_post_invalid_alarm_alarm_actions(self):
+        self._do_create_alarm_invalid_action(
+            alarm_actions=['spam://something/alarm'],
+            error_message='Unsupported action spam://something/alarm')
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('78029ec7-cb70-4f89-a1b6-df235d6688b6')
+    def test_post_invalid_alarm_insufficient_data_actions(self):
+        self._do_create_alarm_invalid_action(
+            insufficient_data_actions=['spam://something/insufficient'],
+            error_message='Unsupported action spam://something/insufficient')
+
+    @decorators.attr(type=['negative'])
+    @decorators.idempotent_id('350dae2b-8720-48ae-b3a6-d92dbba55d07')
+    def test_list_invalid_sort_key(self):
+        # list with invalid sort key
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.list_alarms,
+            sort=['invalid_key:asc'])
+
+    @decorators.attr(type='negative')
+    @decorators.idempotent_id('32bbc39e-7423-4309-acf9-39ff584764fc')
+    def test_create_update_with_faults_delete_alarm(self):
+        # create dual actions
+        alarm_name = data_utils.rand_name('telemetry_alarm')
+        rule = {'metrics': ['41869681-5776-46d6-91ed-cccc43b6e4e3',
+                            'a1fb80f4-c242-4f57-87c6-68f47521059e'],
+                'aggregation_method': 'mean',
+                'comparison_operator': 'eq',
+                'threshold': 300.0}
+        body = self.alarming_client.create_alarm(
+            name=alarm_name,
+            alarm_actions=['http://no.where'],
+            type='gnocchi_aggregation_by_metrics_threshold',
+            gnocchi_aggregation_by_metrics_threshold_rule=rule
+        )
+        alarm_id = body['alarm_id']
+        alarms = self.alarming_client.list_alarms(['name', 'eq', alarm_name])
+        self.assertEqual(1, len(alarms))
+
+        # update the alarm with wrong fields.
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.update_alarm,
+            alarm_id=alarm_id,
+            this_can_not_be_correct='ha')
+
+        # update the alarm with invalid action
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.update_alarm,
+            alarm_id=alarm_id,
+            ok_actions='spam://something/ok')
+
+        # update the alarm with invalid state
+        self.assertRaises(
+            lib_exc.BadRequest,
+            self.alarming_client.alarm_set_state,
+            alarm_id=alarm_id,
+            state='not valid')
+
+        # Delete alarm and verify if deleted
+        self.alarming_client.delete_alarm(alarm_id)
+        self.assertRaises(lib_exc.NotFound,
+                          self.alarming_client.show_alarm, alarm_id)
diff --git a/telemetry_tempest_plugin/aodh/service/client.py b/telemetry_tempest_plugin/aodh/service/client.py
index 80a16f9..369a9ed 100644
--- a/telemetry_tempest_plugin/aodh/service/client.py
+++ b/telemetry_tempest_plugin/aodh/service/client.py
@@ -15,7 +15,8 @@
 
 import json
 
-from six.moves.urllib import parse as urllib
+from urllib import parse
+
 from tempest import clients as tempest_clients
 from tempest import config
 from tempest.lib.common import rest_client
@@ -36,15 +37,21 @@
     def serialize(self, body):
         return json.dumps(body)
 
-    def list_alarms(self, query=None):
+    def list_alarms(self, query=None, sort=None, limit=None, marker=None):
         uri = '%s/alarms' % self.uri_prefix
         uri_dict = {}
         if query:
             uri_dict = {'q.field': query[0],
                         'q.op': query[1],
                         'q.value': query[2]}
+        if sort:
+            uri_dict.update({'sort': sort})
+        if limit is not None:
+            uri_dict.update({'limit': int(limit)})
+        if marker:
+            uri_dict.update({'marker': marker})
         if uri_dict:
-            uri += "?%s" % urllib.urlencode(uri_dict)
+            uri += "?%s" % parse.urlencode(uri_dict, doseq=True)
         resp, body = self.get(uri)
         self.expected_success(200, resp.status)
         body = self.deserialize(body)
@@ -103,6 +110,28 @@
         body = self.deserialize(body)
         return rest_client.ResponseBodyData(resp, body)
 
+    def invalid_path(self, headers=None):
+        uri = "invalid_path"
+        extra_headers = headers is not None
+        resp, body = self.get(uri, headers, extra_headers)
+        self.expected_success(404, resp.status)
+        body = self.deserialize(body)
+        return rest_client.ResponseBodyData(resp, body)
+
+    def show_capabilities(self):
+        uri = "%s/capabilities" % (self.uri_prefix)
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = self.deserialize(body)
+        return rest_client.ResponseBody(resp, body)
+
+    def show_version(self):
+        uri = '/'
+        resp, body = self.get(uri)
+        self.expected_success(200, resp.status)
+        body = self.deserialize(body)
+        return rest_client.ResponseBody(resp, body)
+
 
 class Manager(clients.ServiceClients):
 
diff --git a/telemetry_tempest_plugin/config.py b/telemetry_tempest_plugin/config.py
index e12d3f9..7546834 100644
--- a/telemetry_tempest_plugin/config.py
+++ b/telemetry_tempest_plugin/config.py
@@ -30,6 +30,10 @@
                   cfg.BoolOpt('gnocchi',
                               default=True,
                               help="Whether or not Gnocchi is expected to be"
+                                   "available"),
+                  cfg.BoolOpt('sg_core',
+                              default=False,
+                              help="Whether or not sg-core is expected to be"
                                    "available")]
 
 telemetry_group = cfg.OptGroup(name='telemetry',
@@ -70,6 +74,10 @@
                 default=False,
                 help="Disable SSL certificate validation when running "
                      "scenario tests"),
+    cfg.StrOpt('sg_core_service_url',
+               default="127.0.0.1:3000",
+               help="URL to sg-core prometheus endpoint"),
+
 ]
 
 event_opts = [
diff --git a/telemetry_tempest_plugin/scenario/gnocchi_gabbits/live.yaml b/telemetry_tempest_plugin/scenario/gnocchi_gabbits/live.yaml
index 9dd7347..c62c012 100644
--- a/telemetry_tempest_plugin/scenario/gnocchi_gabbits/live.yaml
+++ b/telemetry_tempest_plugin/scenario/gnocchi_gabbits/live.yaml
@@ -724,7 +724,7 @@
     - name: delete single archive policy cleanup
       DELETE: $ENVIRON['GNOCCHI_SERVICE_URL']/v1/archive_policy/gabbilive
       poll:
-          count: 360
+          count: 1000
           delay: 1
       status: 204
 
diff --git a/telemetry_tempest_plugin/scenario/telemetry_integration_prometheus_gabbits/ceilometer-sg-core-integration.yaml b/telemetry_tempest_plugin/scenario/telemetry_integration_prometheus_gabbits/ceilometer-sg-core-integration.yaml
new file mode 100644
index 0000000..f4cd0b1
--- /dev/null
+++ b/telemetry_tempest_plugin/scenario/telemetry_integration_prometheus_gabbits/ceilometer-sg-core-integration.yaml
@@ -0,0 +1,10 @@
+tests:
+  - name: Check for ceilometer metrics
+    desc: Check the sg-core prometheus endpoint for ceilometer metrics
+    GET: $ENVIRON['SG_CORE_SERVICE_URL']/metrics
+    poll:
+      count: 60
+      delay: 2
+    response_strings:
+      - "ceilometer_image_size"
+      - "sg_total_ceilometer_metric_decode_error_count{source=\"SG\"} 0"
diff --git a/telemetry_tempest_plugin/scenario/test_telemetry_integration.py b/telemetry_tempest_plugin/scenario/test_telemetry_integration.py
index 7db4fd5..3cd7301 100644
--- a/telemetry_tempest_plugin/scenario/test_telemetry_integration.py
+++ b/telemetry_tempest_plugin/scenario/test_telemetry_integration.py
@@ -94,6 +94,8 @@
             "HEAT_SERVICE_URL": self._get_endpoint(auth, "heat_plugin"),
             "NOVA_SERVICE_URL": self._get_endpoint(auth, "compute"),
             "GLANCE_SERVICE_URL": self._get_endpoint(auth, "image"),
+            "SG_CORE_SERVICE_URL":
+            str(config.CONF.telemetry.sg_core_service_url),
             "GLANCE_IMAGE_NAME": self.image_create(),
             "NOVA_FLAVOR_REF": config.CONF.compute.flavor_ref,
             "NEUTRON_NETWORK": networks[0].get('id'),
diff --git a/telemetry_tempest_plugin/scenario/test_telemetry_integration_prometheus.py b/telemetry_tempest_plugin/scenario/test_telemetry_integration_prometheus.py
new file mode 100644
index 0000000..c379470
--- /dev/null
+++ b/telemetry_tempest_plugin/scenario/test_telemetry_integration_prometheus.py
@@ -0,0 +1,44 @@
+#    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 os
+
+from tempest import config
+import tempest.test
+
+from telemetry_tempest_plugin.scenario import utils
+
+CONF = config.CONF
+
+TEST_DIR = os.path.join(os.path.dirname(__file__),
+                        'telemetry_integration_prometheus_gabbits')
+
+
+class PrometheusGabbiTest(tempest.test.BaseTestCase):
+    credentials = ['admin']
+
+    TIMEOUT_SCALING_FACTOR = 5
+
+    @classmethod
+    def skip_checks(cls):
+        super(PrometheusGabbiTest, cls).skip_checks()
+        if not CONF.service_available.sg_core:
+            raise cls.skipException("sg-core support is required")
+
+    def _prep_test(self, filename):
+        os.environ.update({
+            "SG_CORE_SERVICE_URL":
+            str(config.CONF.telemetry.sg_core_service_url),
+        })
+
+
+utils.generate_tests(PrometheusGabbiTest, TEST_DIR)