Remove Ceilometer tempest tests
Now Ceilometer and Aodh tempest plugins are implemented and all tests and supported code is present in plugins. -https://github.com/openstack/aodh/tree/master/aodh/tests/tempest -https://github.com/openstack/ceilometer/tree/master/ceilometer/tests/tempest NOTE- Need to keep config option 'CONF.service_available.ceilometer' as it is being used in Congress tests in Kilo and Liberty branch and Ceilometer tempest plugin is available since Mitaka. If we remove that then, we will be breaking Congress gate for stable branches. Change-Id: I0775bcc15dc9cbae6e075fe92f44b5f6c9b9d5d2 Depends-On: Ic0e6b72d8767d92cc63968c442c4ff65bb001cda
This commit is contained in:
parent
a66e40eb8e
commit
e4796f8de4
@ -61,5 +61,3 @@ objects:
|
||||
owner: javelin
|
||||
file: /etc/hosts
|
||||
swift_role: Member
|
||||
|
||||
telemetry: true
|
@ -1,196 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import time
|
||||
|
||||
from oslo_utils import timeutils
|
||||
|
||||
from tempest.common import compute
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest.common import waiters
|
||||
from tempest import config
|
||||
from tempest import exceptions
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
import tempest.test
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class BaseTelemetryTest(tempest.test.BaseTestCase):
|
||||
|
||||
"""Base test case class for all Telemetry API tests."""
|
||||
|
||||
credentials = ['primary']
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(BaseTelemetryTest, cls).skip_checks()
|
||||
if not CONF.service_available.ceilometer:
|
||||
raise cls.skipException("Ceilometer support is required")
|
||||
|
||||
@classmethod
|
||||
def setup_credentials(cls):
|
||||
cls.set_network_resources()
|
||||
super(BaseTelemetryTest, cls).setup_credentials()
|
||||
|
||||
@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
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(BaseTelemetryTest, cls).resource_setup()
|
||||
cls.nova_notifications = ['memory', 'vcpus', 'disk.root.size',
|
||||
'disk.ephemeral.size']
|
||||
|
||||
cls.glance_notifications = ['image.size']
|
||||
|
||||
cls.glance_v2_notifications = ['image.download', 'image.serve']
|
||||
|
||||
cls.server_ids = []
|
||||
cls.image_ids = []
|
||||
|
||||
@classmethod
|
||||
def create_server(cls):
|
||||
tenant_network = cls.get_tenant_network()
|
||||
body, server = compute.create_test_server(
|
||||
cls.os,
|
||||
tenant_network=tenant_network,
|
||||
name=data_utils.rand_name('ceilometer-instance'),
|
||||
wait_until='ACTIVE')
|
||||
cls.server_ids.append(body['id'])
|
||||
return body
|
||||
|
||||
@classmethod
|
||||
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.
|
||||
if 'image' in body:
|
||||
body = body['image']
|
||||
cls.image_ids.append(body['id'])
|
||||
return body
|
||||
|
||||
@staticmethod
|
||||
def cleanup_resources(method, list_of_ids):
|
||||
for resource_id in list_of_ids:
|
||||
try:
|
||||
method(resource_id)
|
||||
except lib_exc.NotFound:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def wait_for_server_termination(cls, server_id):
|
||||
waiters.wait_for_server_termination(cls.servers_client,
|
||||
server_id)
|
||||
|
||||
@classmethod
|
||||
def resource_cleanup(cls):
|
||||
cls.cleanup_resources(cls.servers_client.delete_server, cls.server_ids)
|
||||
cls.cleanup_resources(cls.wait_for_server_termination, cls.server_ids)
|
||||
cls.cleanup_resources(cls.image_client.delete_image, cls.image_ids)
|
||||
super(BaseTelemetryTest, cls).resource_cleanup()
|
||||
|
||||
def await_samples(self, metric, query):
|
||||
"""This method is to wait for sample to add it to database.
|
||||
|
||||
There are long time delays when using Postgresql (or Mysql)
|
||||
database as ceilometer backend
|
||||
"""
|
||||
timeout = CONF.compute.build_timeout
|
||||
start = timeutils.utcnow()
|
||||
while timeutils.delta_seconds(start, timeutils.utcnow()) < timeout:
|
||||
body = self.telemetry_client.list_samples(metric, query)
|
||||
if body:
|
||||
return body
|
||||
time.sleep(CONF.compute.build_interval)
|
||||
|
||||
raise exceptions.TimeoutException(
|
||||
'Sample for metric:%s with query:%s has not been added to the '
|
||||
'database within %d seconds' % (metric, query,
|
||||
CONF.compute.build_timeout))
|
||||
|
||||
|
||||
class BaseTelemetryAdminTest(BaseTelemetryTest):
|
||||
"""Base test case class for admin Telemetry API tests."""
|
||||
|
||||
credentials = ['primary', 'admin']
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(BaseTelemetryAdminTest, cls).setup_clients()
|
||||
cls.telemetry_admin_client = cls.os_adm.telemetry_client
|
||||
|
||||
def await_events(self, query):
|
||||
timeout = CONF.compute.build_timeout
|
||||
start = timeutils.utcnow()
|
||||
while timeutils.delta_seconds(start, timeutils.utcnow()) < timeout:
|
||||
body = self.telemetry_admin_client.list_events(query)
|
||||
if body:
|
||||
return body
|
||||
time.sleep(CONF.compute.build_interval)
|
||||
|
||||
raise exceptions.TimeoutException(
|
||||
'Event with query:%s has not been added to the '
|
||||
'database within %d seconds' % (query, CONF.compute.build_timeout))
|
||||
|
||||
|
||||
class BaseAlarmingTest(tempest.test.BaseTestCase):
|
||||
"""Base test case class for all Alarming API tests."""
|
||||
|
||||
credentials = ['primary']
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(BaseAlarmingTest, cls).skip_checks()
|
||||
if not CONF.service_available.aodh:
|
||||
raise cls.skipException("Aodh support is required")
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(BaseAlarmingTest, cls).setup_clients()
|
||||
cls.alarming_client = cls.os.alarming_client
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(BaseAlarmingTest, cls).resource_setup()
|
||||
cls.alarm_ids = []
|
||||
|
||||
@classmethod
|
||||
def create_alarm(cls, **kwargs):
|
||||
body = cls.alarming_client.create_alarm(
|
||||
name=data_utils.rand_name('telemetry_alarm'),
|
||||
type='threshold', **kwargs)
|
||||
cls.alarm_ids.append(body['alarm_id'])
|
||||
return body
|
||||
|
||||
@staticmethod
|
||||
def cleanup_resources(method, list_of_ids):
|
||||
for resource_id in list_of_ids:
|
||||
try:
|
||||
method(resource_id)
|
||||
except lib_exc.NotFound:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def resource_cleanup(cls):
|
||||
cls.cleanup_resources(cls.alarming_client.delete_alarm, cls.alarm_ids)
|
||||
super(BaseAlarmingTest, cls).resource_cleanup()
|
@ -1,110 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from tempest.api.telemetry import base
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from tempest import test
|
||||
|
||||
|
||||
class TelemetryAlarmingAPITestJSON(base.BaseAlarmingTest):
|
||||
|
||||
@classmethod
|
||||
def resource_setup(cls):
|
||||
super(TelemetryAlarmingAPITestJSON, cls).resource_setup()
|
||||
cls.rule = {'meter_name': 'cpu_util',
|
||||
'comparison_operator': 'gt',
|
||||
'threshold': 80.0,
|
||||
'period': 70}
|
||||
for i in range(2):
|
||||
cls.create_alarm(threshold_rule=cls.rule)
|
||||
|
||||
@test.idempotent_id('1c918e06-210b-41eb-bd45-14676dd77cd6')
|
||||
def test_alarm_list(self):
|
||||
# List alarms
|
||||
alarm_list = self.alarming_client.list_alarms()
|
||||
|
||||
# Verify created alarm in the list
|
||||
fetched_ids = [a['alarm_id'] for a in alarm_list]
|
||||
missing_alarms = [a for a in self.alarm_ids if a not in fetched_ids]
|
||||
self.assertEqual(0, len(missing_alarms),
|
||||
"Failed to find the following created alarm(s)"
|
||||
" in a fetched list: %s" %
|
||||
', '.join(str(a) for a in missing_alarms))
|
||||
|
||||
@test.idempotent_id('1297b095-39c1-4e74-8a1f-4ae998cedd67')
|
||||
def test_create_update_get_delete_alarm(self):
|
||||
# Create an alarm
|
||||
alarm_name = data_utils.rand_name('telemetry_alarm')
|
||||
body = self.alarming_client.create_alarm(
|
||||
name=alarm_name, type='threshold', threshold_rule=self.rule)
|
||||
self.assertEqual(alarm_name, body['name'])
|
||||
alarm_id = body['alarm_id']
|
||||
self.assertDictContainsSubset(self.rule, body['threshold_rule'])
|
||||
# Update alarm with new rule and new name
|
||||
new_rule = {'meter_name': 'cpu',
|
||||
'comparison_operator': 'eq',
|
||||
'threshold': 70.0,
|
||||
'period': 60}
|
||||
alarm_name_updated = data_utils.rand_name('telemetry-alarm-update')
|
||||
body = self.alarming_client.update_alarm(
|
||||
alarm_id,
|
||||
threshold_rule=new_rule,
|
||||
name=alarm_name_updated,
|
||||
type='threshold')
|
||||
self.assertEqual(alarm_name_updated, body['name'])
|
||||
self.assertDictContainsSubset(new_rule, body['threshold_rule'])
|
||||
# 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['threshold_rule'])
|
||||
# Get history for the alarm and verify the same
|
||||
body = self.alarming_client.show_alarm_history(alarm_id)
|
||||
self.assertEqual("rule change", body[0]['type'])
|
||||
self.assertIn(alarm_name_updated, body[0]['detail'])
|
||||
self.assertEqual("creation", body[1]['type'])
|
||||
self.assertIn(alarm_name, body[1]['detail'])
|
||||
# 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)
|
||||
|
||||
@test.idempotent_id('aca49486-70bb-4016-87e0-f6131374f741')
|
||||
def test_set_get_alarm_state(self):
|
||||
alarm_states = ['ok', 'alarm', 'insufficient data']
|
||||
alarm = self.create_alarm(threshold_rule=self.rule)
|
||||
# Set alarm state and verify
|
||||
new_state =\
|
||||
[elem for elem in alarm_states if elem != alarm['state']][0]
|
||||
state = self.alarming_client.alarm_set_state(alarm['alarm_id'],
|
||||
new_state)
|
||||
self.assertEqual(new_state, state.data)
|
||||
# Get alarm state and verify
|
||||
state = self.alarming_client.show_alarm_state(alarm['alarm_id'])
|
||||
self.assertEqual(new_state, state.data)
|
||||
|
||||
@test.idempotent_id('08d7e45a-1344-4e5c-ba6f-f6cbb77f55b9')
|
||||
def test_create_delete_alarm_with_combination_rule(self):
|
||||
rule = {"alarm_ids": self.alarm_ids,
|
||||
"operator": "or"}
|
||||
# Verifies alarm create
|
||||
alarm_name = data_utils.rand_name('combination_alarm')
|
||||
body = self.alarming_client.create_alarm(name=alarm_name,
|
||||
combination_rule=rule,
|
||||
type='combination')
|
||||
self.assertEqual(alarm_name, body['name'])
|
||||
alarm_id = body['alarm_id']
|
||||
self.assertDictContainsSubset(rule, body['combination_rule'])
|
||||
# Verify alarm delete
|
||||
self.alarming_client.delete_alarm(alarm_id)
|
||||
self.assertRaises(lib_exc.NotFound,
|
||||
self.alarming_client.show_alarm, alarm_id)
|
@ -1,69 +0,0 @@
|
||||
# Copyright 2015 GlobalLogic. 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 tempest.api.telemetry import base
|
||||
from tempest.common.utils import data_utils
|
||||
from tempest.lib import exceptions as lib_exc
|
||||
from tempest import test
|
||||
|
||||
|
||||
class TelemetryAlarmingNegativeTest(base.BaseAlarmingTest):
|
||||
"""Negative tests for show_alarm, update_alarm, show_alarm_history tests
|
||||
|
||||
** show non-existent alarm
|
||||
** show the deleted alarm
|
||||
** delete deleted alarm
|
||||
** update deleted alarm
|
||||
"""
|
||||
|
||||
@test.attr(type=['negative'])
|
||||
@test.idempotent_id('668743d5-08ad-4480-b2b8-15da34f81e7d')
|
||||
def test_get_non_existent_alarm(self):
|
||||
# get the non-existent alarm
|
||||
non_existent_id = data_utils.rand_uuid()
|
||||
self.assertRaises(lib_exc.NotFound, self.alarming_client.show_alarm,
|
||||
non_existent_id)
|
||||
|
||||
@test.attr(type=['negative'])
|
||||
@test.idempotent_id('ef45000d-0a72-4781-866d-4cb7bf2582ad')
|
||||
def test_get_update_show_history_delete_deleted_alarm(self):
|
||||
# get, update and delete the deleted alarm
|
||||
alarm_name = data_utils.rand_name('telemetry_alarm')
|
||||
rule = {'meter_name': 'cpu',
|
||||
'comparison_operator': 'eq',
|
||||
'threshold': 100.0,
|
||||
'period': 90}
|
||||
body = self.alarming_client.create_alarm(
|
||||
name=alarm_name,
|
||||
type='threshold',
|
||||
threshold_rule=rule)
|
||||
alarm_id = body['alarm_id']
|
||||
self.alarming_client.delete_alarm(alarm_id)
|
||||
# get the deleted alarm
|
||||
self.assertRaises(lib_exc.NotFound, self.alarming_client.show_alarm,
|
||||
alarm_id)
|
||||
|
||||
# update the deleted alarm
|
||||
updated_alarm_name = data_utils.rand_name('telemetry_alarm_updated')
|
||||
updated_rule = {'meter_name': 'cpu_new',
|
||||
'comparison_operator': 'eq',
|
||||
'threshold': 70,
|
||||
'period': 50}
|
||||
self.assertRaises(lib_exc.NotFound, self.alarming_client.update_alarm,
|
||||
alarm_id, threshold_rule=updated_rule,
|
||||
name=updated_alarm_name,
|
||||
type='threshold')
|
||||
# delete the deleted alarm
|
||||
self.assertRaises(lib_exc.NotFound, self.alarming_client.delete_alarm,
|
||||
alarm_id)
|
@ -1,84 +0,0 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import testtools
|
||||
|
||||
from tempest.api.telemetry import base
|
||||
from tempest import config
|
||||
from tempest.lib import decorators
|
||||
from tempest import test
|
||||
|
||||
CONF = config.CONF
|
||||
|
||||
|
||||
class TelemetryNotificationAPITestJSON(base.BaseTelemetryTest):
|
||||
|
||||
@test.idempotent_id('d7f8c1c8-d470-4731-8604-315d3956caad')
|
||||
@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-0460426bf498')
|
||||
@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-85e26d64fe8e')
|
||||
@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 TelemetryNotificationAdminAPITestJSON(base.BaseTelemetryAdminTest):
|
||||
|
||||
@test.idempotent_id('29604198-8b45-4fc0-8af8-1cae4f94ebe9')
|
||||
@test.services('compute')
|
||||
@decorators.skip_because(bug='1480490')
|
||||
def test_check_nova_notification_event_and_meter(self):
|
||||
|
||||
body = self.create_server()
|
||||
|
||||
if CONF.telemetry_feature_enabled.events:
|
||||
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)
|
@ -138,9 +138,6 @@ from tempest.services.object_storage.container_client import ContainerClient
|
||||
from tempest.services.object_storage.object_client import ObjectClient
|
||||
from tempest.services.orchestration.json.orchestration_client import \
|
||||
OrchestrationClient
|
||||
from tempest.services.telemetry.json.alarming_client import AlarmingClient
|
||||
from tempest.services.telemetry.json.telemetry_client import \
|
||||
TelemetryClient
|
||||
from tempest.services.volume.v1.json.admin.hosts_client import \
|
||||
HostsClient as VolumeHostsClient
|
||||
from tempest.services.volume.v1.json.admin.quotas_client import \
|
||||
@ -324,20 +321,6 @@ class Manager(manager.Manager):
|
||||
build_interval=CONF.network.build_interval,
|
||||
build_timeout=CONF.network.build_timeout,
|
||||
**self.default_params)
|
||||
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,
|
||||
**self.default_params_with_timeout_values)
|
||||
if CONF.service_available.aodh:
|
||||
self.alarming_client = AlarmingClient(
|
||||
self.auth_provider,
|
||||
CONF.alarming.catalog_type,
|
||||
CONF.identity.region,
|
||||
endpoint_type=CONF.alarming.endpoint_type,
|
||||
**self.default_params_with_timeout_values)
|
||||
if CONF.service_available.glance:
|
||||
self.image_client = ImagesClient(
|
||||
self.auth_provider,
|
||||
|
@ -33,7 +33,6 @@ CONF_PUB_ROUTER = None
|
||||
CONF_TENANTS = None
|
||||
CONF_USERS = None
|
||||
|
||||
IS_AODH = None
|
||||
IS_CINDER = None
|
||||
IS_GLANCE = None
|
||||
IS_HEAT = None
|
||||
@ -51,14 +50,12 @@ def init_conf():
|
||||
global CONF_PUB_ROUTER
|
||||
global CONF_TENANTS
|
||||
global CONF_USERS
|
||||
global IS_AODH
|
||||
global IS_CINDER
|
||||
global IS_GLANCE
|
||||
global IS_HEAT
|
||||
global IS_NEUTRON
|
||||
global IS_NOVA
|
||||
|
||||
IS_AODH = CONF.service_available.aodh
|
||||
IS_CINDER = CONF.service_available.cinder
|
||||
IS_GLANCE = CONF.service_available.glance
|
||||
IS_HEAT = CONF.service_available.heat
|
||||
@ -706,32 +703,6 @@ class NetworkSubnetService(NetworkService):
|
||||
self.data['subnets'] = subnets
|
||||
|
||||
|
||||
# Telemetry services
|
||||
class TelemetryAlarmService(BaseService):
|
||||
def __init__(self, manager, **kwargs):
|
||||
super(TelemetryAlarmService, self).__init__(kwargs)
|
||||
self.client = manager.alarming_client
|
||||
|
||||
def list(self):
|
||||
client = self.client
|
||||
alarms = client.list_alarms()
|
||||
LOG.debug("List count, %s Alarms" % len(alarms))
|
||||
return alarms
|
||||
|
||||
def delete(self):
|
||||
client = self.client
|
||||
alarms = self.list()
|
||||
for alarm in alarms:
|
||||
try:
|
||||
client.delete_alarm(alarm['id'])
|
||||
except Exception:
|
||||
LOG.exception("Delete Alarms exception.")
|
||||
|
||||
def dry_run(self):
|
||||
alarms = self.list()
|
||||
self.data['alarms'] = alarms
|
||||
|
||||
|
||||
# begin global services
|
||||
class FlavorService(BaseService):
|
||||
def __init__(self, manager, **kwargs):
|
||||
@ -976,8 +947,8 @@ class DomainService(BaseService):
|
||||
|
||||
def get_tenant_cleanup_services():
|
||||
tenant_services = []
|
||||
if IS_AODH:
|
||||
tenant_services.append(TelemetryAlarmService)
|
||||
# TODO(gmann): Tempest should provide some plugin hook for cleanup
|
||||
# script extension to plugin tests also.
|
||||
if IS_NOVA:
|
||||
tenant_services.append(ServerService)
|
||||
tenant_services.append(KeyPairService)
|
||||
|
@ -113,7 +113,6 @@ import unittest
|
||||
|
||||
import netaddr
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
import six
|
||||
import yaml
|
||||
|
||||
@ -138,8 +137,6 @@ from tempest.services.image.v2.json import images_client
|
||||
from tempest.services.network.json import routers_client
|
||||
from tempest.services.object_storage import container_client
|
||||
from tempest.services.object_storage import object_client
|
||||
from tempest.services.telemetry.json import alarming_client
|
||||
from tempest.services.telemetry.json import telemetry_client
|
||||
from tempest.services.volume.v1.json import volumes_client
|
||||
|
||||
CONF = config.CONF
|
||||
@ -244,18 +241,6 @@ class OSClient(object):
|
||||
build_interval=CONF.image.build_interval,
|
||||
build_timeout=CONF.image.build_timeout,
|
||||
**default_params)
|
||||
self.telemetry = telemetry_client.TelemetryClient(
|
||||
_auth,
|
||||
CONF.telemetry.catalog_type,
|
||||
CONF.identity.region,
|
||||
endpoint_type=CONF.telemetry.endpoint_type,
|
||||
**default_params_with_timeout_values)
|
||||
self.alarming = alarming_client.AlarmingClient(
|
||||
_auth,
|
||||
CONF.alarm.catalog_type,
|
||||
CONF.identity.region,
|
||||
endpoint_type=CONF.alarm.endpoint_type,
|
||||
**default_params_with_timeout_values)
|
||||
self.volumes = volumes_client.VolumesClient(
|
||||
_auth,
|
||||
CONF.volume.catalog_type,
|
||||
@ -461,7 +446,6 @@ class JavelinCheck(unittest.TestCase):
|
||||
self.check_objects()
|
||||
self.check_servers()
|
||||
self.check_volumes()
|
||||
self.check_telemetry()
|
||||
self.check_secgroups()
|
||||
|
||||
# validate neutron is enabled and ironic disabled:
|
||||
@ -563,27 +547,6 @@ class JavelinCheck(unittest.TestCase):
|
||||
found,
|
||||
"Couldn't find expected secgroup %s" % secgroup['name'])
|
||||
|
||||
def check_telemetry(self):
|
||||
"""Check that ceilometer provides a sane sample.
|
||||
|
||||
Confirm that there is more than one sample and that they have the
|
||||
expected metadata.
|
||||
|
||||
If in check mode confirm that the oldest sample available is from
|
||||
before the upgrade.
|
||||
"""
|
||||
if not self.res.get('telemetry'):
|
||||
return
|
||||
LOG.info("checking telemetry")
|
||||
for server in self.res['servers']:
|
||||
client = client_for_user(server['owner'])
|
||||
body = client.telemetry.list_samples(
|
||||
'instance',
|
||||
query=('metadata.display_name', 'eq', server['name'])
|
||||
)
|
||||
self.assertTrue(len(body) >= 1, 'expecting at least one sample')
|
||||
self._confirm_telemetry_sample(server, body[-1])
|
||||
|
||||
def check_volumes(self):
|
||||
"""Check that the volumes are still there and attached."""
|
||||
if not self.res.get('volumes'):
|
||||
@ -602,26 +565,6 @@ class JavelinCheck(unittest.TestCase):
|
||||
self.assertEqual(vol_body['id'], attachment['volume_id'])
|
||||
self.assertEqual(server_id, attachment['server_id'])
|
||||
|
||||
def _confirm_telemetry_sample(self, server, sample):
|
||||
"""Check this sample matches the expected resource metadata."""
|
||||
# Confirm display_name
|
||||
self.assertEqual(server['name'],
|
||||
sample['resource_metadata']['display_name'])
|
||||
# Confirm instance_type of flavor
|
||||
flavor = sample['resource_metadata'].get(
|
||||
'flavor.name',
|
||||
sample['resource_metadata'].get('instance_type')
|
||||
)
|
||||
self.assertEqual(server['flavor'], flavor)
|
||||
# Confirm the oldest sample was created before upgrade.
|
||||
if OPTS.mode == 'check':
|
||||
oldest_timestamp = timeutils.normalize_time(
|
||||
timeutils.parse_isotime(sample['timestamp']))
|
||||
self.assertTrue(
|
||||
oldest_timestamp < JAVELIN_START,
|
||||
'timestamp should come before start of second javelin run'
|
||||
)
|
||||
|
||||
def check_networking(self):
|
||||
"""Check that the networks are still there."""
|
||||
for res_type in ('networks', 'subnets', 'routers'):
|
||||
|
@ -93,4 +93,3 @@ objects:
|
||||
name: javelin1
|
||||
owner: javelin
|
||||
file: /etc/hosts
|
||||
telemetry: true
|
||||
|
@ -272,8 +272,6 @@ def check_service_availability(os, update):
|
||||
'object_storage': 'swift',
|
||||
'compute': 'nova',
|
||||
'orchestration': 'heat',
|
||||
'metering': 'ceilometer',
|
||||
'telemetry': 'ceilometer',
|
||||
'data_processing': 'sahara',
|
||||
'baremetal': 'ironic',
|
||||
'identity': 'keystone',
|
||||
|
@ -892,50 +892,6 @@ OrchestrationGroup = [
|
||||
]
|
||||
|
||||
|
||||
telemetry_group = cfg.OptGroup(name='telemetry',
|
||||
title='Telemetry Service Options')
|
||||
|
||||
TelemetryGroup = [
|
||||
cfg.StrOpt('catalog_type',
|
||||
default='metering',
|
||||
help="Catalog type of the Telemetry service."),
|
||||
cfg.StrOpt('endpoint_type',
|
||||
default='publicURL',
|
||||
choices=['public', 'admin', 'internal',
|
||||
'publicURL', 'adminURL', 'internalURL'],
|
||||
help="The endpoint type to use for the telemetry service."),
|
||||
cfg.BoolOpt('too_slow_to_test',
|
||||
default=True,
|
||||
deprecated_for_removal=True,
|
||||
help="This variable is used as flag to enable "
|
||||
"notification tests")
|
||||
]
|
||||
|
||||
alarming_group = cfg.OptGroup(name='alarming',
|
||||
title='Alarming Service Options')
|
||||
|
||||
AlarmingGroup = [
|
||||
cfg.StrOpt('catalog_type',
|
||||
default='alarming',
|
||||
help="Catalog type of the Alarming service."),
|
||||
cfg.StrOpt('endpoint_type',
|
||||
default='publicURL',
|
||||
choices=['public', 'admin', 'internal',
|
||||
'publicURL', 'adminURL', 'internalURL'],
|
||||
help="The endpoint type to use for the alarming service."),
|
||||
]
|
||||
|
||||
|
||||
telemetry_feature_group = cfg.OptGroup(name='telemetry-feature-enabled',
|
||||
title='Enabled Ceilometer Features')
|
||||
|
||||
TelemetryFeaturesGroup = [
|
||||
cfg.BoolOpt('events',
|
||||
default=False,
|
||||
help="Runs Ceilometer event-related tests"),
|
||||
]
|
||||
|
||||
|
||||
dashboard_group = cfg.OptGroup(name="dashboard",
|
||||
title="Dashboard options")
|
||||
|
||||
@ -1080,12 +1036,6 @@ ServiceAvailableGroup = [
|
||||
cfg.BoolOpt('heat',
|
||||
default=False,
|
||||
help="Whether or not Heat is expected to be available"),
|
||||
cfg.BoolOpt('ceilometer',
|
||||
default=True,
|
||||
help="Whether or not Ceilometer is expected to be available"),
|
||||
cfg.BoolOpt('aodh',
|
||||
default=False,
|
||||
help="Whether or not Aodh is expected to be available"),
|
||||
cfg.BoolOpt('horizon',
|
||||
default=True,
|
||||
help="Whether or not Horizon is expected to be available"),
|
||||
@ -1231,9 +1181,6 @@ _opts = [
|
||||
(object_storage_feature_group, ObjectStoreFeaturesGroup),
|
||||
(database_group, DatabaseGroup),
|
||||
(orchestration_group, OrchestrationGroup),
|
||||
(telemetry_group, TelemetryGroup),
|
||||
(telemetry_feature_group, TelemetryFeaturesGroup),
|
||||
(alarming_group, AlarmingGroup),
|
||||
(dashboard_group, DashboardGroup),
|
||||
(data_processing_group, DataProcessingGroup),
|
||||
(data_processing_feature_group, DataProcessingFeaturesGroup),
|
||||
@ -1302,8 +1249,6 @@ class TempestConfigPrivate(object):
|
||||
'object-storage-feature-enabled']
|
||||
self.database = _CONF.database
|
||||
self.orchestration = _CONF.orchestration
|
||||
self.telemetry = _CONF.telemetry
|
||||
self.telemetry_feature_enabled = _CONF['telemetry-feature-enabled']
|
||||
self.dashboard = _CONF.dashboard
|
||||
self.data_processing = _CONF['data-processing']
|
||||
self.data_processing_feature_enabled = _CONF[
|
||||
|
@ -19,8 +19,7 @@ import pep8
|
||||
|
||||
|
||||
PYTHON_CLIENTS = ['cinder', 'glance', 'keystone', 'nova', 'swift', 'neutron',
|
||||
'trove', 'ironic', 'savanna', 'heat', 'ceilometer',
|
||||
'sahara']
|
||||
'trove', 'ironic', 'savanna', 'heat', 'sahara']
|
||||
|
||||
PYTHON_CLIENT_RE = re.compile('import (%s)client' % '|'.join(PYTHON_CLIENTS))
|
||||
TEST_DEFINITION = re.compile(r'^\s*def test.*')
|
||||
|
@ -1,6 +1,4 @@
|
||||
./tempest/services/object_storage/object_client.py
|
||||
./tempest/services/telemetry/json/alarming_client.py
|
||||
./tempest/services/telemetry/json/telemetry_client.py
|
||||
./tempest/services/volume/base/base_qos_client.py
|
||||
./tempest/services/volume/base/base_backups_client.py
|
||||
./tempest/services/baremetal/base.py
|
||||
|
@ -1,102 +0,0 @@
|
||||
# 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 import config
|
||||
from tempest.scenario import manager
|
||||
from tempest import test
|
||||
|
||||
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 TestObjectStorageTelemetry(manager.ObjectStorageScenarioTest):
|
||||
"""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.
|
||||
"""
|
||||
|
||||
@classmethod
|
||||
def skip_checks(cls):
|
||||
super(TestObjectStorageTelemetry, cls).skip_checks()
|
||||
if not CONF.service_available.ceilometer:
|
||||
skip_msg = ("%s skipped as ceilometer is not available" %
|
||||
cls.__name__)
|
||||
raise cls.skipException(skip_msg)
|
||||
|
||||
@classmethod
|
||||
def setup_clients(cls):
|
||||
super(TestObjectStorageTelemetry, cls).setup_clients()
|
||||
cls.telemetry_client = cls.os_operator.telemetry_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)
|
||||
|
||||
@test.idempotent_id('6d6b88e5-3e38-41bc-b34a-79f713a6cb84')
|
||||
@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)
|
@ -1,98 +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.lib.common import rest_client
|
||||
|
||||
|
||||
class AlarmingClient(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 list_alarms(self, query=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 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 show_alarm(self, alarm_id):
|
||||
uri = '%s/alarms/%s' % (self.uri_prefix, alarm_id)
|
||||
resp, body = self.get(uri)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self.deserialize(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def show_alarm_history(self, alarm_id):
|
||||
uri = "%s/alarms/%s/history" % (self.uri_prefix, alarm_id)
|
||||
resp, body = self.get(uri)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self.deserialize(body)
|
||||
return rest_client.ResponseBodyList(resp, body)
|
||||
|
||||
def delete_alarm(self, alarm_id):
|
||||
uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id)
|
||||
resp, body = self.delete(uri)
|
||||
self.expected_success(204, resp.status)
|
||||
if body:
|
||||
body = self.deserialize(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def create_alarm(self, **kwargs):
|
||||
uri = "%s/alarms" % self.uri_prefix
|
||||
body = self.serialize(kwargs)
|
||||
resp, body = self.post(uri, body)
|
||||
self.expected_success(201, resp.status)
|
||||
body = self.deserialize(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def update_alarm(self, alarm_id, **kwargs):
|
||||
uri = "%s/alarms/%s" % (self.uri_prefix, alarm_id)
|
||||
body = self.serialize(kwargs)
|
||||
resp, body = self.put(uri, body)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self.deserialize(body)
|
||||
return rest_client.ResponseBody(resp, body)
|
||||
|
||||
def show_alarm_state(self, alarm_id):
|
||||
uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id)
|
||||
resp, body = self.get(uri)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self.deserialize(body)
|
||||
return rest_client.ResponseBodyData(resp, body)
|
||||
|
||||
def alarm_set_state(self, alarm_id, state):
|
||||
uri = "%s/alarms/%s/state" % (self.uri_prefix, alarm_id)
|
||||
body = self.serialize(state)
|
||||
resp, body = self.put(uri, body)
|
||||
self.expected_success(200, resp.status)
|
||||
body = self.deserialize(body)
|
||||
return rest_client.ResponseBodyData(resp, body)
|
@ -1,81 +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.lib.common import rest_client
|
||||
|
||||
|
||||
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)
|
@ -78,7 +78,6 @@ def get_service_list():
|
||||
'identity': True,
|
||||
'object_storage': CONF.service_available.swift,
|
||||
'dashboard': CONF.service_available.horizon,
|
||||
'telemetry': CONF.service_available.ceilometer,
|
||||
'data_processing': CONF.service_available.sahara,
|
||||
'database': CONF.service_available.trove
|
||||
}
|
||||
@ -94,7 +93,7 @@ def services(*args):
|
||||
def decorator(f):
|
||||
services = ['compute', 'image', 'baremetal', 'volume', 'orchestration',
|
||||
'network', 'identity', 'object_storage', 'dashboard',
|
||||
'telemetry', 'data_processing', 'database']
|
||||
'data_processing', 'database']
|
||||
for service in args:
|
||||
if service not in services:
|
||||
raise exceptions.InvalidServiceTag('%s is not a valid '
|
||||
|
Loading…
Reference in New Issue
Block a user