Merge "tempest: migrate api and scnario tests from tempest"
diff --git a/ceilometer/tests/tempest/api/base.py b/ceilometer/tests/tempest/api/base.py
index 938bc88..103a123 100644
--- a/ceilometer/tests/tempest/api/base.py
+++ b/ceilometer/tests/tempest/api/base.py
@@ -13,27 +13,43 @@
import time
from oslo_utils import timeutils
-from tempest_lib import exceptions as lib_exc
-
from tempest.common import compute
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.lib import exceptions as lib_exc
import tempest.test
+from ceilometer.tests.tempest.service import client
+
+
CONF = config.CONF
+class ClientManager(client.Manager):
+
+ load_clients = [
+ 'servers_client',
+ 'compute_networks_client',
+ 'compute_floating_ips_client',
+ 'flavors_client',
+ 'image_client',
+ 'image_client_v2',
+ 'telemetry_client',
+ ]
+
+
class BaseTelemetryTest(tempest.test.BaseTestCase):
"""Base test case class for all Telemetry API tests."""
credentials = ['primary']
+ client_manager = ClientManager
@classmethod
def skip_checks(cls):
super(BaseTelemetryTest, cls).skip_checks()
- if not CONF.service_available.ceilometer:
+ if not CONF.service_available.ceilometer_plugin:
raise cls.skipException("Ceilometer support is required")
@classmethod
@@ -44,11 +60,11 @@
@classmethod
def setup_clients(cls):
super(BaseTelemetryTest, cls).setup_clients()
- cls.telemetry_client = cls.os.telemetry_client
- cls.servers_client = cls.os.servers_client
- cls.flavors_client = cls.os.flavors_client
- cls.image_client = cls.os.image_client
- cls.image_client_v2 = cls.os.image_client_v2
+ cls.telemetry_client = cls.os_primary.telemetry_client
+ cls.servers_client = cls.os_primary.servers_client
+ cls.flavors_client = cls.os_primary.flavors_client
+ cls.image_client = cls.os_primary.image_client
+ cls.image_client_v2 = cls.os_primary.image_client_v2
@classmethod
def resource_setup(cls):
@@ -67,7 +83,7 @@
def create_server(cls):
tenant_network = cls.get_tenant_network()
body, server = compute.create_test_server(
- cls.os,
+ cls.os_primary,
tenant_network=tenant_network,
name=data_utils.rand_name('ceilometer-instance'),
wait_until='ACTIVE')
@@ -75,10 +91,11 @@
return body
@classmethod
- def create_image(cls, client):
- body = client.create_image(
- data_utils.rand_name('image'), container_format='bare',
- disk_format='raw', visibility='private')
+ def create_image(cls, client, **kwargs):
+ body = client.create_image(name=data_utils.rand_name('image'),
+ container_format='bare',
+ disk_format='raw',
+ **kwargs)
# TODO(jswarren) Move ['image'] up to initial body value assignment
# once both v1 and v2 glance clients include the full response
# object.
diff --git a/ceilometer/tests/tempest/api/test_telemetry_notification_api.py b/ceilometer/tests/tempest/api/test_telemetry_notification_api.py
new file mode 100644
index 0000000..6c1ee6b
--- /dev/null
+++ b/ceilometer/tests/tempest/api/test_telemetry_notification_api.py
@@ -0,0 +1,87 @@
+# 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.
+
+# Change-Id: I14e16a1a7d9813b324ee40545c07f0e88fb637b7
+
+import testtools
+
+from ceilometer.tests.tempest.api import base
+from tempest import config
+from tempest.lib import decorators
+from tempest import test
+
+
+CONF = config.CONF
+
+
+class TelemetryNotificationAPITest(base.BaseTelemetryTest):
+
+ @test.idempotent_id('d7f8c1c8-d470-4731-8604-315d3956caae')
+ @test.services('compute')
+ def test_check_nova_notification(self):
+
+ body = self.create_server()
+
+ query = ('resource', 'eq', body['id'])
+
+ for metric in self.nova_notifications:
+ self.await_samples(metric, query)
+
+ @test.attr(type="smoke")
+ @test.idempotent_id('04b10bfe-a5dc-47af-b22f-0460426bf499')
+ @test.services("image")
+ @testtools.skipIf(not CONF.image_feature_enabled.api_v1,
+ "Glance api v1 is disabled")
+ def test_check_glance_v1_notifications(self):
+ body = self.create_image(self.image_client, is_public=False)
+ self.image_client.update_image(body['id'], data='data')
+
+ query = 'resource', 'eq', body['id']
+
+ self.image_client.delete_image(body['id'])
+
+ for metric in self.glance_notifications:
+ self.await_samples(metric, query)
+
+ @test.attr(type="smoke")
+ @test.idempotent_id('c240457d-d943-439b-8aea-85e26d64fe8f')
+ @test.services("image")
+ @testtools.skipIf(not CONF.image_feature_enabled.api_v2,
+ "Glance api v2 is disabled")
+ def test_check_glance_v2_notifications(self):
+ body = self.create_image(self.image_client_v2, visibility='private')
+
+ self.image_client_v2.store_image_file(body['id'], "file")
+ self.image_client_v2.show_image_file(body['id'])
+
+ query = 'resource', 'eq', body['id']
+
+ for metric in self.glance_v2_notifications:
+ self.await_samples(metric, query)
+
+
+class TelemetryNotificationAdminAPITest(base.BaseTelemetryAdminTest):
+
+ @test.idempotent_id('29604198-8b45-4fc0-8af8-1cae4f94ebea')
+ @test.services('compute')
+ @decorators.skip_because(bug='1480490')
+ def test_check_nova_notification_event_and_meter(self):
+
+ body = self.create_server()
+
+ if CONF.telemetry_plugin.event_enabled:
+ query = ('instance_id', 'eq', body['id'])
+ self.await_events(query)
+
+ query = ('resource', 'eq', body['id'])
+ for metric in self.nova_notifications:
+ self.await_samples(metric, query)
diff --git a/ceilometer/tests/tempest/client.py b/ceilometer/tests/tempest/client.py
deleted file mode 100644
index b0173ef..0000000
--- a/ceilometer/tests/tempest/client.py
+++ /dev/null
@@ -1,116 +0,0 @@
-# Copyright 2014 OpenStack Foundation
-# All Rights Reserved.
-#
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
-# not use this file except in compliance with the License. You may obtain
-# a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
-# License for the specific language governing permissions and limitations
-# under the License.
-
-from oslo_serialization import jsonutils as json
-from six.moves.urllib import parse as urllib
-
-from tempest.common import service_client
-from tempest import config
-from tempest import manager
-
-
-CONF = config.CONF
-
-
-class TelemetryClient(service_client.ServiceClient):
-
- version = '2'
- uri_prefix = "v2"
-
- def deserialize(self, body):
- return json.loads(body.replace("\n", ""))
-
- def serialize(self, body):
- return json.dumps(body)
-
- def add_sample(self, sample_list, meter_name, meter_unit, volume,
- sample_type, resource_id, **kwargs):
- sample = {"counter_name": meter_name, "counter_unit": meter_unit,
- "counter_volume": volume, "counter_type": sample_type,
- "resource_id": resource_id}
- for key in kwargs:
- sample[key] = kwargs[key]
-
- sample_list.append(self.serialize(sample))
- return sample_list
-
- def create_sample(self, meter_name, sample_list):
- uri = "%s/meters/%s" % (self.uri_prefix, meter_name)
- body = self.serialize(sample_list)
- resp, body = self.post(uri, body)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
-
- def _helper_list(self, uri, query=None, period=None):
- uri_dict = {}
- if query:
- uri_dict = {'q.field': query[0],
- 'q.op': query[1],
- 'q.value': query[2]}
- if period:
- uri_dict['period'] = period
- if uri_dict:
- uri += "?%s" % urllib.urlencode(uri_dict)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBodyList(resp, body)
-
- def list_resources(self, query=None):
- uri = '%s/resources' % self.uri_prefix
- return self._helper_list(uri, query)
-
- def list_meters(self, query=None):
- uri = '%s/meters' % self.uri_prefix
- return self._helper_list(uri, query)
-
- def list_statistics(self, meter, period=None, query=None):
- uri = "%s/meters/%s/statistics" % (self.uri_prefix, meter)
- return self._helper_list(uri, query, period)
-
- def list_samples(self, meter_id, query=None):
- uri = '%s/meters/%s' % (self.uri_prefix, meter_id)
- return self._helper_list(uri, query)
-
- def list_events(self, query=None):
- uri = '%s/events' % self.uri_prefix
- return self._helper_list(uri, query)
-
- def show_resource(self, resource_id):
- uri = '%s/resources/%s' % (self.uri_prefix, resource_id)
- resp, body = self.get(uri)
- self.expected_success(200, resp.status)
- body = self.deserialize(body)
- return service_client.ResponseBody(resp, body)
-
-
-class Manager(manager.Manager):
-
- def __init__(self, credentials=None, service=None):
- super(Manager, self).__init__(credentials, service)
- self._set_telemetry_client()
-
- def _set_telemetry_client(self):
- if CONF.service_available.ceilometer:
- self.telemetry_client = TelemetryClient(
- self.auth_provider,
- CONF.telemetry.catalog_type,
- CONF.identity.region,
- endpoint_type=CONF.telemetry.endpoint_type,
- disable_ssl_certificate_validation=(
- CONF.identity.disable_ssl_certificate_validation),
- ca_certs=CONF.identity.ca_certificates_file,
- trace_requests=CONF.debug.trace_requests)
diff --git a/ceilometer/tests/tempest/config.py b/ceilometer/tests/tempest/config.py
index 96dc6a2..a83a509 100644
--- a/ceilometer/tests/tempest/config.py
+++ b/ceilometer/tests/tempest/config.py
@@ -20,12 +20,12 @@
title="Available OpenStack Services")
ServiceAvailableGroup = [
- cfg.BoolOpt('ceilometer',
+ cfg.BoolOpt('ceilometer_plugin',
default=True,
help="Whether or not Ceilometer is expected to be available"),
]
-telemetry_group = cfg.OptGroup(name='telemetry',
+telemetry_group = cfg.OptGroup(name='telemetry_plugin',
title='Telemetry Service Options')
TelemetryGroup = [
@@ -37,4 +37,7 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the telemetry service."),
+ cfg.BoolOpt('event_enabled',
+ default=True,
+ help="Runs Ceilometer event-related tests"),
]
diff --git a/ceilometer/tests/tempest/scenario/__init__.py b/ceilometer/tests/tempest/scenario/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ceilometer/tests/tempest/scenario/__init__.py
diff --git a/ceilometer/tests/tempest/scenario/test_object_storage_telemetry_middleware.py b/ceilometer/tests/tempest/scenario/test_object_storage_telemetry_middleware.py
new file mode 100644
index 0000000..4bb287d
--- /dev/null
+++ b/ceilometer/tests/tempest/scenario/test_object_storage_telemetry_middleware.py
@@ -0,0 +1,146 @@
+# Copyright 2014 Red Hat
+#
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_log import log as logging
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+from ceilometer.tests.tempest.service import client
+
+
+CONF = config.CONF
+
+LOG = logging.getLogger(__name__)
+
+# Loop for up to 120 seconds waiting on notifications
+# NOTE(chdent): The choice of 120 seconds is fairly
+# arbitrary: Long enough to give the notifications the
+# chance to travel across a highly latent bus but not
+# so long as to allow excessive latency to never be visible.
+# TODO(chdent): Ideally this value would come from configuration.
+NOTIFICATIONS_WAIT = 120
+NOTIFICATIONS_SLEEP = 1
+
+
+class ClientManager(client.Manager):
+
+ load_clients = [
+ 'telemetry_client',
+ 'container_client',
+ 'object_client',
+ ]
+
+
+class TestObjectStorageTelemetry(test.BaseTestCase):
+ """Test that swift uses the ceilometer middleware.
+
+ * create container.
+ * upload a file to the created container.
+ * retrieve the file from the created container.
+ * wait for notifications from ceilometer.
+ """
+
+ credentials = ['primary']
+ client_manager = ClientManager
+
+ @classmethod
+ def skip_checks(cls):
+ super(TestObjectStorageTelemetry, cls).skip_checks()
+ if not CONF.service_available.swift:
+ skip_msg = ("%s skipped as swift is not available" %
+ cls.__name__)
+ raise cls.skipException(skip_msg)
+ if not CONF.service_available.ceilometer_plugin:
+ skip_msg = ("%s skipped as ceilometer is not available" %
+ cls.__name__)
+ raise cls.skipException(skip_msg)
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.set_network_resources()
+ super(TestObjectStorageTelemetry, cls).setup_credentials()
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestObjectStorageTelemetry, cls).setup_clients()
+ cls.telemetry_client = cls.os_primary.telemetry_client
+ cls.container_client = cls.os_primary.container_client
+ cls.object_client = cls.os_primary.object_client
+
+ def _confirm_notifications(self, container_name, obj_name):
+ # NOTE: Loop seeking for appropriate notifications about the containers
+ # and objects sent to swift.
+
+ def _check_samples():
+ # NOTE: Return True only if we have notifications about some
+ # containers and some objects and the notifications are about
+ # the expected containers and objects.
+ # Otherwise returning False will case _check_samples to be
+ # called again.
+ results = self.telemetry_client.list_samples(
+ 'storage.objects.incoming.bytes')
+ LOG.debug('got samples %s', results)
+
+ # Extract container info from samples.
+ containers, objects = [], []
+ for sample in results:
+ meta = sample['resource_metadata']
+ if meta.get('container') and meta['container'] != 'None':
+ containers.append(meta['container'])
+ elif (meta.get('target.metadata:container') and
+ meta['target.metadata:container'] != 'None'):
+ containers.append(meta['target.metadata:container'])
+
+ if meta.get('object') and meta['object'] != 'None':
+ objects.append(meta['object'])
+ elif (meta.get('target.metadata:object') and
+ meta['target.metadata:object'] != 'None'):
+ objects.append(meta['target.metadata:object'])
+
+ return (container_name in containers and obj_name in objects)
+
+ self.assertTrue(test.call_until_true(_check_samples,
+ NOTIFICATIONS_WAIT,
+ NOTIFICATIONS_SLEEP),
+ 'Correct notifications were not received after '
+ '%s seconds.' % NOTIFICATIONS_WAIT)
+
+ def create_container(self):
+ name = data_utils.rand_name('swift-scenario-container')
+ self.container_client.create_container(name)
+ # look for the container to assure it is created
+ self.container_client.list_container_contents(name)
+ LOG.debug('Container %s created' % (name))
+ self.addCleanup(self.container_client.delete_container,
+ name)
+ return name
+
+ def upload_object_to_container(self, container_name):
+ obj_name = data_utils.rand_name('swift-scenario-object')
+ obj_data = data_utils.arbitrary_string()
+ self.object_client.create_object(container_name, obj_name, obj_data)
+ self.addCleanup(self.object_client.delete_object,
+ container_name,
+ obj_name)
+ return obj_name
+
+ @test.idempotent_id('6d6b88e5-3e38-41bc-b34a-79f713a6cb85')
+ @test.services('object_storage', 'telemetry')
+ def test_swift_middleware_notifies(self):
+ container_name = self.create_container()
+ obj_name = self.upload_object_to_container(container_name)
+ self._confirm_notifications(container_name, obj_name)
diff --git a/ceilometer/tests/tempest/service/__init__.py b/ceilometer/tests/tempest/service/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/ceilometer/tests/tempest/service/__init__.py
diff --git a/ceilometer/tests/tempest/service/client.py b/ceilometer/tests/tempest/service/client.py
new file mode 100644
index 0000000..81d67d8
--- /dev/null
+++ b/ceilometer/tests/tempest/service/client.py
@@ -0,0 +1,190 @@
+# Copyright 2014 OpenStack Foundation
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from oslo_serialization import jsonutils as json
+from six.moves.urllib import parse as urllib
+
+from tempest import config
+from tempest.lib.common import rest_client
+from tempest.lib.services.compute.flavors_client import FlavorsClient
+from tempest.lib.services.compute.floating_ips_client import FloatingIPsClient
+from tempest.lib.services.compute.networks_client import NetworksClient
+from tempest.lib.services.compute.servers_client import ServersClient
+from tempest import manager
+from tempest.services.image.v1.json.images_client import ImagesClient
+from tempest.services.image.v2.json.images_client import ImagesClientV2
+from tempest.services.object_storage.container_client import ContainerClient
+from tempest.services.object_storage.object_client import ObjectClient
+
+
+CONF = config.CONF
+
+
+class TelemetryClient(rest_client.RestClient):
+
+ version = '2'
+ uri_prefix = "v2"
+
+ def deserialize(self, body):
+ return json.loads(body.replace("\n", ""))
+
+ def serialize(self, body):
+ return json.dumps(body)
+
+ def create_sample(self, meter_name, sample_list):
+ uri = "%s/meters/%s" % (self.uri_prefix, meter_name)
+ body = self.serialize(sample_list)
+ resp, body = self.post(uri, body)
+ self.expected_success(200, resp.status)
+ body = self.deserialize(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def _helper_list(self, uri, query=None, period=None):
+ uri_dict = {}
+ if query:
+ uri_dict = {'q.field': query[0],
+ 'q.op': query[1],
+ 'q.value': query[2]}
+ if period:
+ uri_dict['period'] = period
+ if uri_dict:
+ uri += "?%s" % urllib.urlencode(uri_dict)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = self.deserialize(body)
+ return rest_client.ResponseBodyList(resp, body)
+
+ def list_resources(self, query=None):
+ uri = '%s/resources' % self.uri_prefix
+ return self._helper_list(uri, query)
+
+ def list_meters(self, query=None):
+ uri = '%s/meters' % self.uri_prefix
+ return self._helper_list(uri, query)
+
+ def list_statistics(self, meter, period=None, query=None):
+ uri = "%s/meters/%s/statistics" % (self.uri_prefix, meter)
+ return self._helper_list(uri, query, period)
+
+ def list_samples(self, meter_id, query=None):
+ uri = '%s/meters/%s' % (self.uri_prefix, meter_id)
+ return self._helper_list(uri, query)
+
+ def list_events(self, query=None):
+ uri = '%s/events' % self.uri_prefix
+ return self._helper_list(uri, query)
+
+ def show_resource(self, resource_id):
+ uri = '%s/resources/%s' % (self.uri_prefix, resource_id)
+ resp, body = self.get(uri)
+ self.expected_success(200, resp.status)
+ body = self.deserialize(body)
+ return rest_client.ResponseBody(resp, body)
+
+
+class Manager(manager.Manager):
+
+ load_clients = [
+ 'servers_client',
+ 'compute_networks_client',
+ 'compute_floating_ips_client',
+ 'flavors_client',
+ 'image_client',
+ 'image_client_v2',
+ 'telemetry_client',
+ 'container_client',
+ 'object_client',
+ ]
+
+ default_params = {
+ 'disable_ssl_certificate_validation':
+ CONF.identity.disable_ssl_certificate_validation,
+ 'ca_certs': CONF.identity.ca_certificates_file,
+ 'trace_requests': CONF.debug.trace_requests
+ }
+
+ compute_params = {
+ 'service': CONF.compute.catalog_type,
+ 'region': CONF.compute.region or CONF.identity.region,
+ 'endpoint_type': CONF.compute.endpoint_type,
+ 'build_interval': CONF.compute.build_interval,
+ 'build_timeout': CONF.compute.build_timeout,
+ }
+ compute_params.update(default_params)
+
+ image_params = {
+ 'catalog_type': CONF.image.catalog_type,
+ 'region': CONF.image.region or CONF.identity.region,
+ 'endpoint_type': CONF.image.endpoint_type,
+ 'build_interval': CONF.image.build_interval,
+ 'build_timeout': CONF.image.build_timeout,
+ }
+ image_params.update(default_params)
+
+ telemetry_params = {
+ 'service': CONF.telemetry_plugin.catalog_type,
+ 'region': CONF.identity.region,
+ 'endpoint_type': CONF.telemetry_plugin.endpoint_type,
+ }
+ telemetry_params.update(default_params)
+
+ object_storage_params = {
+ 'service': CONF.object_storage.catalog_type,
+ 'region': CONF.object_storage.region or CONF.identity.region,
+ 'endpoint_type': CONF.object_storage.endpoint_type
+ }
+ object_storage_params.update(default_params)
+
+ def __init__(self, credentials=None, service=None):
+ super(Manager, self).__init__(credentials)
+ for client in self.load_clients:
+ getattr(self, 'set_%s' % client)()
+
+ def set_servers_client(self):
+ self.servers_client = ServersClient(self.auth_provider,
+ **self.compute_params)
+
+ def set_compute_networks_client(self):
+ self.compute_networks_client = NetworksClient(self.auth_provider,
+ **self.compute_params)
+
+ def set_compute_floating_ips_client(self):
+ self.compute_floating_ips_client = FloatingIPsClient(
+ self.auth_provider,
+ **self.compute_params)
+
+ def set_flavors_client(self):
+ self.flavors_client = FlavorsClient(self.auth_provider,
+ **self.compute_params)
+
+ def set_image_client(self):
+ self.image_client = ImagesClient(self.auth_provider,
+ **self.image_params)
+
+ def set_image_client_v2(self):
+ self.image_client_v2 = ImagesClientV2(self.auth_provider,
+ **self.image_params)
+
+ def set_telemetry_client(self):
+ self.telemetry_client = TelemetryClient(self.auth_provider,
+ **self.telemetry_params)
+
+ def set_container_client(self):
+ self.container_client = ContainerClient(self.auth_provider,
+ **self.object_storage_params)
+
+ def set_object_client(self):
+ self.object_client = ObjectClient(self.auth_provider,
+ **self.object_storage_params)