Merge "Fix bugs about 'default' domain."
diff --git a/.mailmap b/.mailmap
index a43c0b9..3ea6ab0 100644
--- a/.mailmap
+++ b/.mailmap
@@ -1,25 +1,27 @@
<brian.waldon@rackspace.com> <bcwaldon@gmail.com>
<jeblair@hp.com> <corvus@inaugust.com>
<jeblair@hp.com> <james.blair@rackspace.com>
-Adam Gandelman <adamg@ubuntu.com> Adam Gandelman <adamg@canonical.com>
-Andrea Frittoli (andreaf) <andrea.frittoli@hpe.com> Andrea Frittoli (andreaf) <andrea.frittoli@hp.com>
-Andrea Frittoli (andreaf) <andrea.frittoli@hpe.com> Andrea Frittoli <andrea.frittoli@hp.com>
-Daryl Walleck <daryl.walleck@rackspace.com> dwalleck <daryl.walleck@rackspace.com>
+Adam Gandelman <adamg@ubuntu.com> <adamg@canonical.com>
+Andrea Frittoli <andrea.frittoli@gmail.com> <andrea.frittoli@hp.com>
+Andrea Frittoli <andrea.frittoli@gmail.com> <andrea.frittoli@hpe.com>
+Daryl Walleck <daryl.walleck@rackspace.com> <daryl.walleck@rackspace.com>
David Kranz <dkranz@redhat.com> David Kranz <david.kranz@qrclab.com>
-Ghanshyam <ghanshyam.mann@nectechnologies.in> Ghanshyam Mann <ghanshyam.mann@nectechnologies.in>
-Ghanshyam <ghanshyam.mann@nectechnologies.in> ghanshyam <ghanshyam.mann@nectechnologies.in>
-Jay Pipes <jaypipes@gmail.com> Jay Pipes <jpipes@librebox.gateway.2wire.net>
+Ghanshyam <ghanshyam.mann@nectechnologies.in> <ghanshyam.mann@nectechnologies.in>
+Ghanshyam <ghanshyam.mann@nectechnologies.in> <ghanshyam.mann@nectechnologies.in>
+Jay Pipes <jaypipes@gmail.com> <jpipes@librebox.gateway.2wire.net>
Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
-Ken'ichi Ohmichi <ken-oomichi@wx.jp.nec.com> Ken'ichi Ohmichi <oomichi@mxs.nes.nec.co.jp>
-Marc Koderer <marc@koderer.com> Marc Koderer <m.koderer@telekom.de>
-Masayuki Igawa <masayuki.igawa@gmail.com> Masayuki Igawa <igawa@mxs.nes.nec.co.jp>
-Masayuki Igawa <masayuki.igawa@gmail.com> Masayuki Igawa <mas-igawa@ut.jp.nec.com>
-Matthew Treinish <mtreinish@kortar.org> Matthew Treinish <treinish@linux.vnet.ibm.com>
-Nayna Patel <nayna.patel@hp.com> nayna-patel <nayna.patel@hp.com>
-ravikumar-venkatesan <ravikumar.venkatesan@hp.com> Ravikumar Venkatesan <ravikumar.venkatesan@hp.com>
-ravikumar-venkatesan <ravikumar.venkatesan@hp.com> ravikumar venkatesan <ravikumar.venkatesan@hp.com>
-Rohit Karajgi <rohit.karajgi@nttdata.com> Rohit Karajgi <rohit.karajgi@vertex.co.in>
-Sean Dague <sean@dague.net> Sean Dague <sdague@linux.vnet.ibm.com>
-Sean Dague <sean@dague.net> Sean Dague <sean.dague@samsung.com>
-Yuiko Takada <takada-yuiko@mxn.nes.nec.co.jp> YuikoTakada <takada-yuiko@mxn.nes.nec.co.jp>
-Zhi Kun Liu <zhikunli@cn.ibm.com> Liu, Zhi Kun <zhikunli@cn.ibm.com>
+Ken'ichi Ohmichi <ken-oomichi@wx.jp.nec.com> <oomichi@mxs.nes.nec.co.jp>
+Ken'ichi Ohmichi <ken-oomichi@wx.jp.nec.com> <ken1ohmichi@gmail.com>
+Marc Koderer <marc@koderer.com> <m.koderer@telekom.de>
+Masayuki Igawa <masayuki@igawa.me> <igawa@mxs.nes.nec.co.jp>
+Masayuki Igawa <masayuki@igawa.me> <mas-igawa@ut.jp.nec.com>
+Masayuki Igawa <masayuki@igawa.me> <masayuki.igawa@gmail.com>
+Matthew Treinish <mtreinish@kortar.org> <treinish@linux.vnet.ibm.com>
+Nayna Patel <nayna.patel@hp.com> <nayna.patel@hp.com>
+ravikumar-venkatesan <ravikumar.venkatesan@hp.com> <ravikumar.venkatesan@hp.com>
+ravikumar-venkatesan <ravikumar.venkatesan@hp.com> <ravikumar.venkatesan@hp.com>
+Rohit Karajgi <rohit.karajgi@nttdata.com> <rohit.karajgi@vertex.co.in>
+Sean Dague <sean@dague.net> <sdague@linux.vnet.ibm.com>
+Sean Dague <sean@dague.net> <sean.dague@samsung.com>
+Yuiko Takada <takada-yuiko@mxn.nes.nec.co.jp> <takada-yuiko@mxn.nes.nec.co.jp>
+Zhi Kun Liu <zhikunli@cn.ibm.com> <zhikunli@cn.ibm.com>
diff --git a/bindep.txt b/bindep.txt
index 6a28348..8914ade 100644
--- a/bindep.txt
+++ b/bindep.txt
@@ -7,5 +7,7 @@
gcc [platform:dpkg]
python-dev [platform:dpkg]
python-devel [platform:rpm]
+python3-dev [platform:dpkg]
+python3-devel [platform:rpm]
openssl-devel [platform:rpm]
libssl-dev [platform:dpkg]
diff --git a/releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml b/releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml
new file mode 100644
index 0000000..18fd5ad
--- /dev/null
+++ b/releasenotes/notes/add-compute-feature-serial-console-45583c4341e34fc9.yaml
@@ -0,0 +1,7 @@
+---
+features:
+ - |
+ A new boolean config option ``serial_console`` is added to the section
+ ``compute-feature-enabled``. If enabled, tests, which validate the
+ behavior of Nova's *serial console* feature (an alternative to VNC,
+ RDP, SPICE) can be executed.
diff --git a/releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yaml b/releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yaml
new file mode 100644
index 0000000..a0156a0
--- /dev/null
+++ b/releasenotes/notes/add-force-detach-volume-to-volumes-client-library-b2071f2954f8e8b1.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Add force detach volume feature API to v2 volumes_client library.
+ This feature enables the possibility to force a volume to detach, and
+ roll back an unsuccessful detach operation after you disconnect the volume.
diff --git a/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yaml b/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yaml
new file mode 100644
index 0000000..69320fb
--- /dev/null
+++ b/releasenotes/notes/add-identity-v3-clients-for-os-ep-filter-api-extensions-9cfd217fd2c6a61f.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Defines the identity v3 OS-EP-FILTER extension API client.
+ This client manages associations between endpoints, projects
+ along with groups.
diff --git a/releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml b/releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml
new file mode 100644
index 0000000..e6847eb
--- /dev/null
+++ b/releasenotes/notes/add-volume-quota-class-client-as-library-c4c2b22c36ff807e.yaml
@@ -0,0 +1,8 @@
+---
+features:
+ - |
+ Define v2 quota_classes_client for the volume service as library
+ interfaces, allowing other projects to use this module as stable libraries
+ without maintenance changes.
+
+ * quota_classes_client(v2)
diff --git a/setup.cfg b/setup.cfg
index b2035bc..b292970 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -39,7 +39,11 @@
cleanup = tempest.cmd.cleanup:TempestCleanup
list-plugins = tempest.cmd.list_plugins:TempestListPlugins
verify-config = tempest.cmd.verify_tempest_config:TempestVerifyConfig
- workspace = tempest.cmd.workspace:TempestWorkspace
+ workspace_register = tempest.cmd.workspace:TempestWorkspaceRegister
+ workspace_rename = tempest.cmd.workspace:TempestWorkspaceRename
+ workspace_move = tempest.cmd.workspace:TempestWorkspaceMove
+ workspace_remove = tempest.cmd.workspace:TempestWorkspaceRemove
+ workspace_list = tempest.cmd.workspace:TempestWorkspaceList
run = tempest.cmd.run:TempestRun
oslo.config.opts =
tempest.config = tempest.config:list_opts
diff --git a/tempest/api/compute/admin/test_create_server.py b/tempest/api/compute/admin/test_create_server.py
new file mode 100644
index 0000000..3449aba
--- /dev/null
+++ b/tempest/api/compute/admin/test_create_server.py
@@ -0,0 +1,109 @@
+# Copyright 2012 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.
+
+import testtools
+
+from tempest.api.compute import base
+from tempest.common.utils.linux import remote_client
+from tempest import config
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest):
+ disk_config = 'AUTO'
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.prepare_instance_network()
+ super(ServersWithSpecificFlavorTestJSON, cls).setup_credentials()
+
+ @classmethod
+ def setup_clients(cls):
+ super(ServersWithSpecificFlavorTestJSON, cls).setup_clients()
+ cls.client = cls.servers_client
+
+ @classmethod
+ def resource_setup(cls):
+ cls.set_validation_resources()
+
+ super(ServersWithSpecificFlavorTestJSON, cls).resource_setup()
+
+ @decorators.idempotent_id('b3c7bcfc-bb5b-4e22-b517-c7f686b802ca')
+ @testtools.skipUnless(CONF.validation.run_validation,
+ 'Instance validation tests are disabled.')
+ def test_verify_created_server_ephemeral_disk(self):
+ # Verify that the ephemeral disk is created when creating server
+ flavor_base = self.flavors_client.show_flavor(
+ self.flavor_ref)['flavor']
+
+ def create_flavor_with_ephemeral(ephem_disk):
+ name = 'flavor_with_ephemeral_%s' % ephem_disk
+ flavor_name = data_utils.rand_name(name)
+
+ ram = flavor_base['ram']
+ vcpus = flavor_base['vcpus']
+ disk = flavor_base['disk']
+
+ # Create a flavor with ephemeral disk
+ flavor = self.create_flavor(name=flavor_name, ram=ram, vcpus=vcpus,
+ disk=disk, ephemeral=ephem_disk)
+ return flavor['id']
+
+ flavor_with_eph_disk_id = create_flavor_with_ephemeral(ephem_disk=1)
+ flavor_no_eph_disk_id = create_flavor_with_ephemeral(ephem_disk=0)
+
+ admin_pass = self.image_ssh_password
+
+ server_no_eph_disk = self.create_test_server(
+ validatable=True,
+ wait_until='ACTIVE',
+ adminPass=admin_pass,
+ flavor=flavor_no_eph_disk_id)
+
+ # Get partition number of server without ephemeral disk.
+ server_no_eph_disk = self.client.show_server(
+ server_no_eph_disk['id'])['server']
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server_no_eph_disk),
+ self.ssh_user,
+ admin_pass,
+ self.validation_resources['keypair']['private_key'],
+ server=server_no_eph_disk,
+ servers_client=self.client)
+ disks_num = len(linux_client.get_disks().split('\n'))
+
+ # Explicit server deletion necessary for Juno compatibility
+ self.client.delete_server(server_no_eph_disk['id'])
+
+ server_with_eph_disk = self.create_test_server(
+ validatable=True,
+ wait_until='ACTIVE',
+ adminPass=admin_pass,
+ flavor=flavor_with_eph_disk_id)
+
+ server_with_eph_disk = self.client.show_server(
+ server_with_eph_disk['id'])['server']
+ linux_client = remote_client.RemoteClient(
+ self.get_server_ip(server_with_eph_disk),
+ self.ssh_user,
+ admin_pass,
+ self.validation_resources['keypair']['private_key'],
+ server=server_with_eph_disk,
+ servers_client=self.client)
+ disks_num_eph = len(linux_client.get_disks().split('\n'))
+ self.assertEqual(disks_num + 1, disks_num_eph)
diff --git a/tempest/api/compute/admin/test_delete_server.py b/tempest/api/compute/admin/test_delete_server.py
new file mode 100644
index 0000000..2569161
--- /dev/null
+++ b/tempest/api/compute/admin/test_delete_server.py
@@ -0,0 +1,52 @@
+# Copyright 2012 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 tempest.api.compute import base
+from tempest.common import waiters
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class DeleteServersAdminTestJSON(base.BaseV2ComputeAdminTest):
+ # NOTE: Server creations of each test class should be under 10
+ # for preventing "Quota exceeded for instances".
+
+ @classmethod
+ def setup_clients(cls):
+ super(DeleteServersAdminTestJSON, cls).setup_clients()
+ cls.non_admin_client = cls.servers_client
+ cls.admin_client = cls.os_adm.servers_client
+
+ @decorators.idempotent_id('99774678-e072-49d1-9d2a-49a59bc56063')
+ def test_delete_server_while_in_error_state(self):
+ # Delete a server while it's VM state is error
+ server = self.create_test_server(wait_until='ACTIVE')
+ self.admin_client.reset_state(server['id'], state='error')
+ # Verify server's state
+ server = self.non_admin_client.show_server(server['id'])['server']
+ self.assertEqual(server['status'], 'ERROR')
+ self.non_admin_client.delete_server(server['id'])
+ waiters.wait_for_server_termination(self.servers_client,
+ server['id'],
+ ignore_error=True)
+
+ @decorators.idempotent_id('73177903-6737-4f27-a60c-379e8ae8cf48')
+ def test_admin_delete_servers_of_others(self):
+ # Administrator can delete servers of others
+ server = self.create_test_server(wait_until='ACTIVE')
+ self.admin_client.delete_server(server['id'])
+ waiters.wait_for_server_termination(self.servers_client, server['id'])
diff --git a/tempest/api/compute/test_live_block_migration_negative.py b/tempest/api/compute/admin/test_live_block_migration_negative.py
similarity index 100%
rename from tempest/api/compute/test_live_block_migration_negative.py
rename to tempest/api/compute/admin/test_live_block_migration_negative.py
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index 4d0f12a..8344103 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -13,10 +13,13 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from oslo_log import log as logging
import testtools
from tempest.api.compute import base
+from tempest.common import compute
from tempest.common import waiters
from tempest import config
from tempest.lib import decorators
@@ -175,6 +178,80 @@
self.assertEqual(volume_id1, volume_id2)
+class LiveBlockMigrationRemoteConsolesV26TestJson(LiveBlockMigrationTestJSON):
+ min_microversion = '2.6'
+ max_microversion = 'latest'
+
+ @decorators.idempotent_id('6190af80-513e-4f0f-90f2-9714e84955d7')
+ @testtools.skipUnless(CONF.compute_feature_enabled.serial_console,
+ 'Serial console not supported.')
+ @testtools.skipUnless(
+ test.is_scheduler_filter_enabled("DifferentHostFilter"),
+ 'DifferentHostFilter is not available.')
+ def test_live_migration_serial_console(self):
+ """Test the live-migration of an instance which has a serial console
+
+ The serial console feature of an instance uses ports on the host.
+ These ports need to be updated when they are already in use by
+ another instance on the target host. This test checks if this
+ update behavior is correctly done, by connecting to the serial
+ consoles of the instances before and after the live migration.
+ """
+ server01_id = self.create_test_server(wait_until='ACTIVE')['id']
+ hints = {'different_host': server01_id}
+ server02_id = self.create_test_server(scheduler_hints=hints,
+ wait_until='ACTIVE')['id']
+ host01_id = self._get_host_for_server(server01_id)
+ host02_id = self._get_host_for_server(server02_id)
+ self.assertNotEqual(host01_id, host02_id)
+
+ # At this step we have 2 instances on different hosts, both with
+ # serial consoles, both with port 10000 (the default value).
+ # https://bugs.launchpad.net/nova/+bug/1455252 describes the issue
+ # when live-migrating in such a scenario.
+
+ self._verify_console_interaction(server01_id)
+ self._verify_console_interaction(server02_id)
+
+ self._migrate_server_to(server01_id, host02_id)
+ waiters.wait_for_server_status(self.servers_client,
+ server01_id, 'ACTIVE')
+ self.assertEqual(host02_id, self._get_host_for_server(server01_id))
+ self._verify_console_interaction(server01_id)
+ # At this point, both instances have a valid serial console
+ # connection, which means the ports got updated.
+
+ def _verify_console_interaction(self, server_id):
+ body = self.servers_client.get_remote_console(server_id,
+ console_type='serial',
+ protocol='serial')
+ console_url = body['remote_console']['url']
+ data = "test_live_migration_serial_console"
+ console_output = ''
+ t = 0.0
+ interval = 0.1
+
+ ws = compute.create_websocket(console_url)
+ try:
+ # NOTE (markus_z): It can take a long time until the terminal
+ # of the instance is available for interaction. Hence the
+ # long timeout value.
+ while data not in console_output and t <= 120.0:
+ try:
+ ws.send_frame(data)
+ recieved = ws.receive_frame()
+ console_output += recieved
+ except Exception:
+ # In case we had an issue with send/receive on the
+ # websocket connection, we create a new one.
+ ws = compute.create_websocket(console_url)
+ time.sleep(interval)
+ t += interval
+ finally:
+ ws.close()
+ self.assertIn(data, console_output)
+
+
class LiveAutoBlockMigrationV225TestJSON(LiveBlockMigrationTestJSON):
min_microversion = '2.25'
max_microversion = 'latest'
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index d54c3ec..d44967e 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -226,91 +226,6 @@
self.assertIn(address, network)
-class ServersWithSpecificFlavorTestJSON(base.BaseV2ComputeAdminTest):
- disk_config = 'AUTO'
-
- @classmethod
- def setup_credentials(cls):
- cls.prepare_instance_network()
- super(ServersWithSpecificFlavorTestJSON, cls).setup_credentials()
-
- @classmethod
- def setup_clients(cls):
- super(ServersWithSpecificFlavorTestJSON, cls).setup_clients()
- cls.client = cls.servers_client
-
- @classmethod
- def resource_setup(cls):
- cls.set_validation_resources()
-
- super(ServersWithSpecificFlavorTestJSON, cls).resource_setup()
-
- @decorators.idempotent_id('b3c7bcfc-bb5b-4e22-b517-c7f686b802ca')
- @testtools.skipUnless(CONF.validation.run_validation,
- 'Instance validation tests are disabled.')
- def test_verify_created_server_ephemeral_disk(self):
- # Verify that the ephemeral disk is created when creating server
- flavor_base = self.flavors_client.show_flavor(
- self.flavor_ref)['flavor']
-
- def create_flavor_with_ephemeral(ephem_disk):
- name = 'flavor_with_ephemeral_%s' % ephem_disk
- flavor_name = data_utils.rand_name(name)
-
- ram = flavor_base['ram']
- vcpus = flavor_base['vcpus']
- disk = flavor_base['disk']
-
- # Create a flavor with ephemeral disk
- flavor = self.create_flavor(name=flavor_name, ram=ram, vcpus=vcpus,
- disk=disk, ephemeral=ephem_disk)
- return flavor['id']
-
- flavor_with_eph_disk_id = create_flavor_with_ephemeral(ephem_disk=1)
- flavor_no_eph_disk_id = create_flavor_with_ephemeral(ephem_disk=0)
-
- admin_pass = self.image_ssh_password
-
- server_no_eph_disk = self.create_test_server(
- validatable=True,
- wait_until='ACTIVE',
- adminPass=admin_pass,
- flavor=flavor_no_eph_disk_id)
-
- # Get partition number of server without ephemeral disk.
- server_no_eph_disk = self.client.show_server(
- server_no_eph_disk['id'])['server']
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(server_no_eph_disk),
- self.ssh_user,
- admin_pass,
- self.validation_resources['keypair']['private_key'],
- server=server_no_eph_disk,
- servers_client=self.client)
- disks_num = len(linux_client.get_disks().split('\n'))
-
- # Explicit server deletion necessary for Juno compatibility
- self.client.delete_server(server_no_eph_disk['id'])
-
- server_with_eph_disk = self.create_test_server(
- validatable=True,
- wait_until='ACTIVE',
- adminPass=admin_pass,
- flavor=flavor_with_eph_disk_id)
-
- server_with_eph_disk = self.client.show_server(
- server_with_eph_disk['id'])['server']
- linux_client = remote_client.RemoteClient(
- self.get_server_ip(server_with_eph_disk),
- self.ssh_user,
- admin_pass,
- self.validation_resources['keypair']['private_key'],
- server=server_with_eph_disk,
- servers_client=self.client)
- disks_num_eph = len(linux_client.get_disks().split('\n'))
- self.assertEqual(disks_num + 1, disks_num_eph)
-
-
class ServersTestManualDisk(ServersTestJSON):
disk_config = 'MANUAL'
diff --git a/tempest/api/compute/servers/test_delete_server.py b/tempest/api/compute/servers/test_delete_server.py
index 8ed55e0..2b03b2b 100644
--- a/tempest/api/compute/servers/test_delete_server.py
+++ b/tempest/api/compute/servers/test_delete_server.py
@@ -117,34 +117,3 @@
waiters.wait_for_server_termination(self.client, server['id'])
waiters.wait_for_volume_resource_status(self.volumes_client,
volume['id'], 'available')
-
-
-class DeleteServersAdminTestJSON(base.BaseV2ComputeAdminTest):
- # NOTE: Server creations of each test class should be under 10
- # for preventing "Quota exceeded for instances".
-
- @classmethod
- def setup_clients(cls):
- super(DeleteServersAdminTestJSON, cls).setup_clients()
- cls.non_admin_client = cls.servers_client
- cls.admin_client = cls.os_adm.servers_client
-
- @decorators.idempotent_id('99774678-e072-49d1-9d2a-49a59bc56063')
- def test_delete_server_while_in_error_state(self):
- # Delete a server while it's VM state is error
- server = self.create_test_server(wait_until='ACTIVE')
- self.admin_client.reset_state(server['id'], state='error')
- # Verify server's state
- server = self.non_admin_client.show_server(server['id'])['server']
- self.assertEqual(server['status'], 'ERROR')
- self.non_admin_client.delete_server(server['id'])
- waiters.wait_for_server_termination(self.servers_client,
- server['id'],
- ignore_error=True)
-
- @decorators.idempotent_id('73177903-6737-4f27-a60c-379e8ae8cf48')
- def test_admin_delete_servers_of_others(self):
- # Administrator can delete servers of others
- server = self.create_test_server(wait_until='ACTIVE')
- self.admin_client.delete_server(server['id'])
- waiters.wait_for_server_termination(self.servers_client, server['id'])
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index 57aa72e..6e5d42f 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -263,7 +263,17 @@
'the config drive.', server['id'])
dev_name = self.ssh_client.exec_command(cmd_blkid)
dev_name = dev_name.rstrip()
- self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
+ try:
+ self.ssh_client.exec_command('sudo mount %s /mnt' % dev_name)
+ except exceptions.SSHExecCommandFailed:
+ # So the command failed, let's try to know why and print some
+ # useful information.
+ lsblk = self.ssh_client.exec_command('sudo lsblk --fs --ascii')
+ LOG.error("Mounting %s on /mnt failed. Right after the "
+ "failure 'lsblk' in the guest reported:\n%s",
+ dev_name, lsblk)
+ raise
+
cmd_md = 'sudo cat /mnt/openstack/latest/meta_data.json'
md_json = self.ssh_client.exec_command(cmd_md)
self.verify_device_metadata(md_json)
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 86a5764..9683e93 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -31,10 +31,7 @@
# One of those domains will be disabled
cls.setup_domains = list()
for i in range(3):
- domain = cls.domains_client.create_domain(
- name=data_utils.rand_name('domain'),
- description=data_utils.rand_name('domain-desc'),
- enabled=i < 2)['domain']
+ domain = cls.create_domain(enabled=i < 2)
cls.setup_domains.append(domain)
@classmethod
diff --git a/tempest/api/identity/admin/v3/test_domains_negative.py b/tempest/api/identity/admin/v3/test_domains_negative.py
index 61b4fa2..1a0b851 100644
--- a/tempest/api/identity/admin/v3/test_domains_negative.py
+++ b/tempest/api/identity/admin/v3/test_domains_negative.py
@@ -25,11 +25,7 @@
@decorators.attr(type=['negative', 'gate'])
@decorators.idempotent_id('1f3fbff5-4e44-400d-9ca1-d953f05f609b')
def test_delete_active_domain(self):
- d_name = data_utils.rand_name('domain')
- d_desc = data_utils.rand_name('domain-desc')
- domain = self.domains_client.create_domain(
- name=d_name,
- description=d_desc)['domain']
+ domain = self.create_domain()
domain_id = domain['id']
self.addCleanup(self.delete_domain, domain_id)
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index f630f74..49b6585 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -31,9 +31,7 @@
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
u_password = data_utils.rand_name('pass-')
- cls.domain = cls.domains_client.create_domain(
- name=data_utils.rand_name('domain-'),
- description=data_utils.rand_name('domain-desc-'))['domain']
+ cls.domain = cls.create_domain()
cls.project = cls.projects_client.create_project(
data_utils.rand_name('project-'),
description=data_utils.rand_name('project-desc-'),
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index ac56fc6..adb467c 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -38,9 +38,7 @@
u_desc = '%s description' % u_name
u_email = '%s@testmail.tm' % u_name
cls.u_password = data_utils.rand_password()
- cls.domain = cls.domains_client.create_domain(
- name=data_utils.rand_name('domain'),
- description=data_utils.rand_name('domain-desc'))['domain']
+ cls.domain = cls.create_domain()
cls.project = cls.projects_client.create_project(
data_utils.rand_name('project'),
description=data_utils.rand_name('project-desc'),
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 5dc88b8..10121d9 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -223,6 +223,7 @@
cls.role_assignments = cls.os_admin.role_assignments_client
cls.oauth_consumers_client = cls.os_adm.oauth_consumers_client
cls.domain_config_client = cls.os_adm.domain_config_client
+ cls.endpoint_filter_client = cls.os_adm.endpoint_filter_client
if CONF.identity.admin_domain_scope:
# NOTE(andreaf) When keystone policy requires it, the identity
# admin clients for these tests shall use 'domain' scoped tokens.
@@ -236,11 +237,13 @@
cls.users_client.update_user(user['id'], name=user_name, enabled=False)
@classmethod
- def create_domain(cls):
+ def create_domain(cls, **kwargs):
"""Create a domain."""
- domain = cls.domains_client.create_domain(
- name=data_utils.rand_name('test_domain'),
- description=data_utils.rand_name('desc'))['domain']
+ if 'name' not in kwargs:
+ kwargs['name'] = data_utils.rand_name('test_domain')
+ if 'description' not in kwargs:
+ kwargs['description'] = data_utils.rand_name('desc')
+ domain = cls.domains_client.create_domain(**kwargs)['domain']
return domain
def delete_domain(self, domain_id):
diff --git a/tempest/api/volume/admin/test_volume_quota_classes.py b/tempest/api/volume/admin/test_volume_quota_classes.py
new file mode 100644
index 0000000..016d87a
--- /dev/null
+++ b/tempest/api/volume/admin/test_volume_quota_classes.py
@@ -0,0 +1,89 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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 testtools import matchers
+
+from tempest.api.volume import base
+from tempest.common import tempest_fixtures as fixtures
+from tempest.lib.common.utils import data_utils
+from tempest.lib import decorators
+
+LOG = logging.getLogger(__name__)
+QUOTA_KEYS = ['gigabytes', 'snapshots', 'volumes', 'backups',
+ 'backup_gigabytes', 'per_volume_gigabytes']
+
+
+class VolumeQuotaClassesTest(base.BaseVolumeAdminTest):
+
+ def setUp(self):
+ # Note(jeremy.zhang): All test cases in this class need to externally
+ # lock on doing anything with default quota values.
+ self.useFixture(fixtures.LockFixture('volume_quotas'))
+ super(VolumeQuotaClassesTest, self).setUp()
+
+ def _restore_default_quotas(self, original_defaults):
+ LOG.debug("Restoring volume quota class defaults")
+ self.admin_quota_classes_client.update_quota_class_set(
+ 'default', **original_defaults)
+
+ @decorators.idempotent_id('abb9198e-67d0-4b09-859f-4f4a1418f176')
+ def test_show_default_quota(self):
+ default_quotas = self.admin_quota_classes_client.show_quota_class_set(
+ 'default')['quota_class_set']
+ self.assertIn('id', default_quotas)
+ self.assertEqual('default', default_quotas.pop('id'))
+ for key in QUOTA_KEYS:
+ self.assertIn(key, default_quotas)
+
+ @decorators.idempotent_id('a7644c63-2669-467a-b00e-452dd5c5397b')
+ def test_update_default_quota(self):
+ LOG.debug("Get the current default quota class values")
+ body = self.admin_quota_classes_client.show_quota_class_set(
+ 'default')['quota_class_set']
+ body.pop('id')
+
+ # Restore the defaults when the test is done
+ self.addCleanup(self._restore_default_quotas, body.copy())
+
+ # Increment some of the values for updating the default quota class.
+ # For safety, only items with value >= 0 will be updated, and items
+ # with value < 0 (-1 means unlimited) will be ignored.
+ for quota, default in body.items():
+ if default >= 0:
+ body[quota] = default + 1
+
+ LOG.debug("Update limits for the default quota class set")
+ update_body = self.admin_quota_classes_client.update_quota_class_set(
+ 'default', **body)['quota_class_set']
+ self.assertThat(update_body.items(),
+ matchers.ContainsAll(body.items()))
+
+ # Verify current project's default quotas
+ default_quotas = self.admin_quotas_client.show_default_quota_set(
+ self.os_adm.credentials.tenant_id)['quota_set']
+ self.assertThat(default_quotas.items(),
+ matchers.ContainsAll(body.items()))
+
+ # Verify a new project's default quotas
+ project_name = data_utils.rand_name('quota_class_tenant')
+ description = data_utils.rand_name('desc_')
+ project_id = self.identity_utils.create_project(
+ name=project_name, description=description)['id']
+ self.addCleanup(self.identity_utils.delete_project, project_id)
+ default_quotas = self.admin_quotas_client.show_default_quota_set(
+ project_id)['quota_set']
+ self.assertThat(default_quotas.items(),
+ matchers.ContainsAll(body.items()))
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index 58ca92f..48941c6 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -13,6 +13,7 @@
# under the License.
from tempest.api.volume import base
+from tempest.common import tempest_fixtures as fixtures
from tempest.common import waiters
from tempest.lib.common.utils import data_utils
from tempest.lib import decorators
@@ -26,6 +27,11 @@
credentials = ['primary', 'alt', 'admin']
+ def setUp(self):
+ # NOTE(jeremy.zhang): Avoid conflicts with volume quota class tests.
+ self.useFixture(fixtures.LockFixture('volume_quotas'))
+ super(BaseVolumeQuotasAdminTestJSON, self).setUp()
+
@classmethod
def setup_credentials(cls):
super(BaseVolumeQuotasAdminTestJSON, cls).setup_credentials()
diff --git a/tempest/api/volume/admin/test_volumes_actions.py b/tempest/api/volume/admin/test_volumes_actions.py
index 7f291e9..acff7cd 100644
--- a/tempest/api/volume/admin/test_volumes_actions.py
+++ b/tempest/api/volume/admin/test_volumes_actions.py
@@ -14,7 +14,12 @@
# under the License.
from tempest.api.volume import base
+from tempest.common import waiters
+from tempest import config
from tempest.lib import decorators
+from tempest import test
+
+CONF = config.CONF
class VolumesActionsTest(base.BaseVolumeAdminTest):
@@ -60,3 +65,36 @@
def test_volume_force_delete_when_volume_is_maintenance(self):
# test force delete when status of volume is maintenance
self._create_reset_and_force_delete_temp_volume('maintenance')
+
+ @decorators.idempotent_id('d38285d9-929d-478f-96a5-00e66a115b81')
+ @test.services('compute')
+ def test_force_detach_volume(self):
+ # Create a server and a volume
+ server_id = self.create_server(wait_until='ACTIVE')['id']
+ volume_id = self.create_volume()['id']
+
+ # Attach volume
+ self.volumes_client.attach_volume(
+ volume_id,
+ instance_uuid=server_id,
+ mountpoint='/dev/%s' % CONF.compute.volume_device_name)
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume_id, 'in-use')
+ self.addCleanup(waiters.wait_for_volume_resource_status,
+ self.volumes_client, volume_id, 'available')
+ self.addCleanup(self.volumes_client.detach_volume, volume_id)
+ attachment = self.volumes_client.show_volume(
+ volume_id)['volume']['attachments'][0]
+
+ # Reset volume's status to error
+ self.admin_volume_client.reset_volume_status(volume_id, status='error')
+
+ # Force detach volume
+ self.admin_volume_client.force_detach_volume(
+ volume_id, connector=None,
+ attachment_id=attachment['attachment_id'])
+ waiters.wait_for_volume_resource_status(self.volumes_client,
+ volume_id, 'available')
+ vol_info = self.volumes_client.show_volume(volume_id)['volume']
+ self.assertIn('attachments', vol_info)
+ self.assertEmpty(vol_info['attachments'])
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index d8b503d..ead9d4f 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -259,6 +259,8 @@
cls.admin_backups_client = cls.os_adm.backups_v2_client
cls.admin_encryption_types_client = \
cls.os_adm.encryption_types_v2_client
+ cls.admin_quota_classes_client = \
+ cls.os_adm.volume_quota_classes_v2_client
cls.admin_quotas_client = cls.os_adm.volume_quotas_v2_client
cls.admin_volume_limits_client = cls.os_adm.volume_v2_limits_client
cls.admin_capabilities_client = \
diff --git a/tempest/api/volume/test_volume_absolute_limits.py b/tempest/api/volume/test_volume_absolute_limits.py
index 836e489..870b9f0 100644
--- a/tempest/api/volume/test_volume_absolute_limits.py
+++ b/tempest/api/volume/test_volume_absolute_limits.py
@@ -21,7 +21,10 @@
CONF = config.CONF
-class AbsoluteLimitsTests(base.BaseVolumeTest):
+# NOTE(zhufl): This inherits from BaseVolumeAdminTest because
+# it requires force_tenant_isolation=True, which need admin
+# credentials to create non-admin users for the tests.
+class AbsoluteLimitsTests(base.BaseVolumeAdminTest):
# avoid existing volumes of pre-defined tenant
force_tenant_isolation = True
diff --git a/tempest/api/volume/test_volumes_actions.py b/tempest/api/volume/test_volumes_actions.py
index 2ed2a06..0e7f1e9 100644
--- a/tempest/api/volume/test_volumes_actions.py
+++ b/tempest/api/volume/test_volumes_actions.py
@@ -99,7 +99,7 @@
# NOTE(gfidente): the volume uploaded in Glance comes from setUpClass,
# it is shared with the other tests. After it is uploaded in Glance,
# there is no way to delete it from Cinder, so we delete it from Glance
- # using the Glance image_client and from Cinder via tearDownClass.
+ # using the Glance images_client and from Cinder via tearDownClass.
image_name = data_utils.rand_name(self.__class__.__name__ + '-Image')
body = self.volumes_client.upload_volume(
self.volume['id'], image_name=image_name,
diff --git a/tempest/clients.py b/tempest/clients.py
index 4ef6872..73a4b20 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -231,6 +231,8 @@
**params_v3)
self.domain_config_client = self.identity_v3.DomainConfigurationClient(
**params_v3)
+ self.endpoint_filter_client = \
+ self.identity_v3.EndPointsFilterClient(**params_v3)
# Token clients do not use the catalog. They only need default_params.
# They read auth_url, so they should only be set if the corresponding
@@ -275,6 +277,8 @@
self.volume_hosts_v2_client = self.volume_v2.HostsClient()
self.volume_quotas_client = self.volume_v1.QuotasClient()
self.volume_quotas_v2_client = self.volume_v2.QuotasClient()
+ self.volume_quota_classes_v2_client = \
+ self.volume_v2.QuotaClassesClient()
self.volumes_extension_client = self.volume_v1.ExtensionsClient()
self.volumes_v2_extension_client = self.volume_v2.ExtensionsClient()
self.volume_availability_zone_client = \
diff --git a/tempest/cmd/cleanup.py b/tempest/cmd/cleanup.py
index ec76103..ac73cbf 100644
--- a/tempest/cmd/cleanup.py
+++ b/tempest/cmd/cleanup.py
@@ -14,41 +14,61 @@
# under the License.
"""
-Utility for cleaning up environment after Tempest run
+Utility for cleaning up environment after Tempest test run
+
+**Usage:** ``tempest cleanup [--help] [OPTIONS]``
+
+If run with no arguments, ``tempest cleanup`` will query your OpenStack
+deployment and build a list of resources to delete and destroy them. This list
+will exclude the resources from ``saved_state.json`` and will include the
+configured admin account if the ``--delete-tempest-conf-objects`` flag is
+specified. By default the admin project is not deleted and the admin user
+specified in ``tempest.conf`` is never deleted.
+
+Example Run
+-----------
+
+**WARNING: If step 1 is skipped in the example below, the cleanup procedure
+may delete resources that existed in the cloud before the test run. This
+may cause an unwanted destruction of cloud resources, so use caution with
+this command.**
+
+``$ tempest cleanup --init-saved-state``
+
+``$ # Actual running of Tempest tests``
+
+``$ tempest cleanup``
Runtime Arguments
-----------------
-**--init-saved-state**: Before you can execute cleanup you must initialize
-the saved state by running it with the **--init-saved-state** flag
-(creating ./saved_state.json), which protects your deployment from
-cleanup deleting objects you want to keep. Typically you would run
-cleanup with **--init-saved-state** prior to a tempest run. If this is not
-the case saved_state.json must be edited, removing objects you want
-cleanup to delete.
+**--init-saved-state**: Initializes the saved state of the OpenStack deployment
+and will output a ``saved_state.json`` file containing resources from your
+deployment that will be preserved from the cleanup command. This should be
+done prior to running Tempest tests.
-**--dry-run**: Creates a report (dry_run.json) of the tenants that will be
-cleaned up (in the "_tenants_to_clean" array), and the global objects
-that will be removed (tenants, users, flavors and images). Once
-cleanup is executed in normal mode, running it again with **--dry-run**
-should yield an empty report.
+**--delete-tempest-conf-objects**: If option is present, then the command will
+delete the admin project in addition to the resources associated with them on
+clean up. If option is not present, the command will delete the resources
+associated with the Tempest and alternate Tempest users and projects but will
+not delete the projects themselves.
-**NOTE**: The _tenants_to_clean array in dry-run.json lists the
-tenants that cleanup will loop through and delete child objects, not
-delete the tenant itself. This may differ from the tenants array as you
-can clean the tempest and alternate tempest tenants but by default,
-cleanup deletes the objects in the tempest and alternate tempest tenants
-but does not delete those tenants unless the **--delete-tempest-conf-objects**
-flag is used to force their deletion.
+**--dry-run**: Creates a report (``./dry_run.json``) of the projects that will
+be cleaned up (in the ``_tenants_to_clean`` dictionary [1]_) and the global
+objects that will be removed (domains, flavors, images, roles, projects,
+and users). Once the cleanup command is executed (e.g. run without
+parameters), running it again with **--dry-run** should yield an empty report.
-**Normal mode**: running with no arguments, will query your deployment and
-build a list of objects to delete after filtering out the objects found in
-saved_state.json and based on the **--delete-tempest-conf-objects** flag.
+**--help**: Print the help text for the command and parameters.
-By default the tempest and alternate tempest users and tenants are not
-deleted and the admin user specified in tempest.conf is never deleted.
+.. [1] The ``_tenants_to_clean`` dictionary in ``dry_run.json`` lists the
+ projects that ``tempest cleanup`` will loop through to delete child
+ objects, but the command will, by default, not delete the projects
+ themselves. This may differ from the ``tenants`` list as you can clean
+ the Tempest and alternate Tempest users and projects but they will not be
+ deleted unless the **--delete-tempest-conf-objects** flag is used to
+ force their deletion.
-Please run with **--help** to see full list of options.
"""
import sys
import traceback
diff --git a/tempest/cmd/workspace.py b/tempest/cmd/workspace.py
index d2dc00d..96d2300 100644
--- a/tempest/cmd/workspace.py
+++ b/tempest/cmd/workspace.py
@@ -52,8 +52,8 @@
import sys
from cliff import command
+from cliff import lister
from oslo_concurrency import lockutils
-import prettytable
import yaml
from tempest import config
@@ -154,76 +154,97 @@
self.workspaces = yaml.safe_load(f) or {}
-class TempestWorkspace(command.Command):
- def take_action(self, parsed_args):
- self.manager = WorkspaceManager(parsed_args.workspace_path)
- if getattr(parsed_args, 'register', None):
- self.manager.register_new_workspace(
- parsed_args.name, parsed_args.path)
- elif getattr(parsed_args, 'rename', None):
- self.manager.rename_workspace(
- parsed_args.old_name, parsed_args.new_name)
- elif getattr(parsed_args, 'move', None):
- self.manager.move_workspace(
- parsed_args.name, parsed_args.path)
- elif getattr(parsed_args, 'remove', None):
- self.manager.remove_workspace(
- parsed_args.name)
- else:
- self._print_workspaces()
- sys.exit(0)
+def add_global_arguments(parser):
+ parser.add_argument(
+ '--workspace-path', required=False, default=None,
+ help="The path to the workspace file, the default is "
+ "~/.tempest/workspace.yaml")
+ return parser
+
+class TempestWorkspaceRegister(command.Command):
def get_description(self):
- return 'Tempest workspace actions'
+ return ('Registers a new tempest workspace via a given '
+ '--name and --path')
def get_parser(self, prog_name):
- parser = super(TempestWorkspace, self).get_parser(prog_name)
-
- parser.add_argument(
- '--workspace-path', required=False, default=None,
- help="The path to the workspace file, the default is "
- "~/.tempest/workspace.yaml")
-
- subparsers = parser.add_subparsers()
-
- list_parser = subparsers.add_parser(
- 'list', help='Outputs the name and path of all known tempest '
- 'workspaces')
- list_parser.set_defaults(list=True)
-
- register_parser = subparsers.add_parser(
- 'register', help='Registers a new tempest workspace via a given '
- '--name and --path')
- register_parser.add_argument('--name', required=True)
- register_parser.add_argument('--path', required=True)
- register_parser.set_defaults(register=True)
-
- update_parser = subparsers.add_parser(
- 'rename', help='Renames a tempest workspace from --old-name to '
- '--new-name')
- update_parser.add_argument('--old-name', required=True)
- update_parser.add_argument('--new-name', required=True)
- update_parser.set_defaults(rename=True)
-
- move_parser = subparsers.add_parser(
- 'move', help='Changes the path of a given tempest workspace '
- '--name to --path')
- move_parser.add_argument('--name', required=True)
- move_parser.add_argument('--path', required=True)
- move_parser.set_defaults(move=True)
-
- remove_parser = subparsers.add_parser(
- 'remove', help='Deletes the entry for a given tempest workspace '
- '--name')
- remove_parser.add_argument('--name', required=True)
- remove_parser.set_defaults(remove=True)
+ parser = super(TempestWorkspaceRegister, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ parser.add_argument('--name', required=True)
+ parser.add_argument('--path', required=True)
return parser
- def _print_workspaces(self):
- output = prettytable.PrettyTable(["Name", "Path"])
- if self.manager.list_workspaces() is not None:
- for name, path in self.manager.list_workspaces().items():
- output.add_row([name, path])
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ self.manager.register_new_workspace(parsed_args.name, parsed_args.path)
+ sys.exit(0)
- print(output)
+
+class TempestWorkspaceRename(command.Command):
+ def get_description(self):
+ return 'Renames a tempest workspace from --old-name to --new-name'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspaceRename, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ parser.add_argument('--old-name', required=True)
+ parser.add_argument('--new-name', required=True)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ self.manager.rename_workspace(
+ parsed_args.old_name, parsed_args.new_name)
+ sys.exit(0)
+
+
+class TempestWorkspaceMove(command.Command):
+ def get_description(self):
+ return 'Changes the path of a given tempest workspace --name to --path'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspaceMove, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ parser.add_argument('--name', required=True)
+ parser.add_argument('--path', required=True)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ self.manager.move_workspace(parsed_args.name, parsed_args.path)
+ sys.exit(0)
+
+
+class TempestWorkspaceRemove(command.Command):
+ def get_description(self):
+ return 'Deletes the entry for a given tempest workspace --name'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspaceRemove, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ parser.add_argument('--name', required=True)
+
+ return parser
+
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ self.manager.remove_workspace(parsed_args.name)
+ sys.exit(0)
+
+
+class TempestWorkspaceList(lister.Lister):
+ def get_description(self):
+ return 'Outputs the name and path of all known tempest workspaces'
+
+ def get_parser(self, prog_name):
+ parser = super(TempestWorkspaceList, self).get_parser(prog_name)
+ add_global_arguments(parser)
+ return parser
+
+ def take_action(self, parsed_args):
+ self.manager = WorkspaceManager(parsed_args.workspace_path)
+ return (("Name", "Path"),
+ ((n, p) for n, p in self.manager.list_workspaces().items()))
diff --git a/tempest/config.py b/tempest/config.py
index e23cc99..f5b2f0d 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -361,7 +361,10 @@
"serial console output?"),
cfg.BoolOpt('resize',
default=False,
- help="Does the test environment support resizing?"),
+ help="Does the test environment support resizing? When you "
+ "enable this feature, 'flavor_ref_alt' should be set and "
+ "it should refer to a larger flavor than 'flavor_ref' "
+ "one."),
cfg.BoolOpt('pause',
default=True,
help="Does the test environment support pausing?"),
@@ -407,6 +410,11 @@
default=False,
help='Enable RDP console. This configuration value should '
'be same as [nova.rdp]->enabled in nova.conf'),
+ cfg.BoolOpt('serial_console',
+ default=False,
+ help='Enable serial console. This configuration value '
+ 'should be the same as [nova.serial_console]->enabled '
+ 'in nova.conf'),
cfg.BoolOpt('rescue',
default=True,
help='Does the test environment support instance rescue '
diff --git a/tempest/lib/api_schema/response/compute/v2_6/__init__.py b/tempest/lib/api_schema/response/compute/v2_6/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_6/__init__.py
diff --git a/tempest/lib/api_schema/response/compute/v2_6/servers.py b/tempest/lib/api_schema/response/compute/v2_6/servers.py
new file mode 100644
index 0000000..29b3e86
--- /dev/null
+++ b/tempest/lib/api_schema/response/compute/v2_6/servers.py
@@ -0,0 +1,48 @@
+# Copyright 2016 IBM Corp.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import copy
+
+from tempest.lib.api_schema.response.compute.v2_3 import servers
+
+list_servers = copy.deepcopy(servers.list_servers)
+get_server = copy.deepcopy(servers.get_server)
+list_servers_detail = copy.deepcopy(servers.list_servers_detail)
+
+# NOTE: The consolidated remote console API got introduced with v2.6
+# with bp/consolidate-console-api. See Nova commit 578bafeda
+get_remote_consoles = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'remote_console': {
+ 'type': 'object',
+ 'properties': {
+ 'protocol': {'enum': ['vnc', 'rdp', 'serial', 'spice']},
+ 'type': {'enum': ['novnc', 'xpvnc', 'rdp-html5',
+ 'spice-html5', 'serial']},
+ 'url': {
+ 'type': 'string',
+ 'format': 'uri'
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['protocol', 'type', 'url']
+ }
+ },
+ 'additionalProperties': False,
+ 'required': ['remote_console']
+ }
+}
diff --git a/tempest/lib/api_schema/response/compute/v2_9/servers.py b/tempest/lib/api_schema/response/compute/v2_9/servers.py
index 470190c..e260e48 100644
--- a/tempest/lib/api_schema/response/compute/v2_9/servers.py
+++ b/tempest/lib/api_schema/response/compute/v2_9/servers.py
@@ -14,7 +14,7 @@
import copy
-from tempest.lib.api_schema.response.compute.v2_3 import servers
+from tempest.lib.api_schema.response.compute.v2_6 import servers
list_servers = copy.deepcopy(servers.list_servers)
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index 99ba6ab..d72b4dd 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -70,7 +70,6 @@
:param str http_timeout: Timeout in seconds to wait for the http request to
return
"""
- TYPE = "json"
# The version of the API this client implements
api_version = None
@@ -105,12 +104,6 @@
disable_ssl_certificate_validation=dscv, ca_certs=ca_certs,
timeout=http_timeout)
- def _get_type(self):
- if self.TYPE != "json":
- self.LOG.warning("Tempest has dropped XML support and the TYPE "
- "became meaningless")
- return self.TYPE
-
def get_headers(self, accept_type=None, send_type=None):
"""Return the default headers which will be used with outgoing requests
@@ -125,9 +118,9 @@
dict for outgoing request
"""
if accept_type is None:
- accept_type = self._get_type()
+ accept_type = 'json'
if send_type is None:
- send_type = self._get_type()
+ send_type = 'json'
return {'Content-Type': 'application/%s' % send_type,
'Accept': 'application/%s' % accept_type}
diff --git a/tempest/lib/services/compute/servers_client.py b/tempest/lib/services/compute/servers_client.py
index 0d355a1..ff65b25 100644
--- a/tempest/lib/services/compute/servers_client.py
+++ b/tempest/lib/services/compute/servers_client.py
@@ -27,6 +27,7 @@
from tempest.lib.api_schema.response.compute.v2_19 import servers as schemav219
from tempest.lib.api_schema.response.compute.v2_26 import servers as schemav226
from tempest.lib.api_schema.response.compute.v2_3 import servers as schemav23
+from tempest.lib.api_schema.response.compute.v2_6 import servers as schemav26
from tempest.lib.api_schema.response.compute.v2_9 import servers as schemav29
from tempest.lib.common import rest_client
from tempest.lib.services.compute import base_compute_client
@@ -37,7 +38,8 @@
schema_versions_info = [
{'min': None, 'max': '2.2', 'schema': schema},
- {'min': '2.3', 'max': '2.8', 'schema': schemav23},
+ {'min': '2.3', 'max': '2.5', 'schema': schemav23},
+ {'min': '2.6', 'max': '2.8', 'schema': schemav26},
{'min': '2.9', 'max': '2.15', 'schema': schemav29},
{'min': '2.16', 'max': '2.18', 'schema': schemav216},
{'min': '2.19', 'max': '2.25', 'schema': schemav219},
@@ -598,6 +600,29 @@
return self.action(server_id, 'os-getConsoleOutput',
schema.get_console_output, **kwargs)
+ def get_remote_console(self, server_id, console_type, protocol, **kwargs):
+ """Get a remote console.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ TODO (markus_z) The api-ref for that isn't yet available, update this
+ here when the docs in Nova are updated. The old API is at
+ http://developer.openstack.org/api-ref/compute/#get-serial-console-os-getserialconsole-action
+ """
+ param = {
+ 'remote_console': {
+ 'type': console_type,
+ 'protocol': protocol,
+ }
+ }
+ post_body = json.dumps(param)
+ resp, body = self.post("servers/%s/remote-consoles" % server_id,
+ post_body)
+ body = json.loads(body)
+ schema = self.get_schema(self.schema_versions_info)
+ self.validate_response(schema.get_remote_consoles, resp, body)
+ return rest_client.ResponseBody(resp, body)
+
def list_virtual_interfaces(self, server_id):
"""List the virtual interfaces used in an instance."""
resp, body = self.get('/'.join(['servers', server_id,
diff --git a/tempest/lib/services/identity/v3/__init__.py b/tempest/lib/services/identity/v3/__init__.py
index f2f3391..6f498d9 100644
--- a/tempest/lib/services/identity/v3/__init__.py
+++ b/tempest/lib/services/identity/v3/__init__.py
@@ -17,6 +17,8 @@
from tempest.lib.services.identity.v3.domain_configuration_client \
import DomainConfigurationClient
from tempest.lib.services.identity.v3.domains_client import DomainsClient
+from tempest.lib.services.identity.v3.endpoint_filter_client import \
+ EndPointsFilterClient
from tempest.lib.services.identity.v3.endpoints_client import EndPointsClient
from tempest.lib.services.identity.v3.groups_client import GroupsClient
from tempest.lib.services.identity.v3.identity_client import IdentityClient
@@ -37,8 +39,8 @@
from tempest.lib.services.identity.v3.versions_client import VersionsClient
__all__ = ['CredentialsClient', 'DomainsClient', 'DomainConfigurationClient',
- 'EndPointsClient', 'GroupsClient', 'IdentityClient',
- 'InheritedRolesClient', 'OAUTHConsumerClient', 'PoliciesClient',
- 'ProjectsClient', 'RegionsClient', 'RoleAssignmentsClient',
- 'RolesClient', 'ServicesClient', 'V3TokenClient', 'TrustsClient',
- 'UsersClient', 'VersionsClient']
+ 'EndPointsClient', 'EndPointsFilterClient', 'GroupsClient',
+ 'IdentityClient', 'InheritedRolesClient', 'OAUTHConsumerClient',
+ 'PoliciesClient', 'ProjectsClient', 'RegionsClient',
+ 'RoleAssignmentsClient', 'RolesClient', 'ServicesClient',
+ 'V3TokenClient', 'TrustsClient', 'UsersClient', 'VersionsClient']
diff --git a/tempest/lib/services/identity/v3/endpoint_filter_client.py b/tempest/lib/services/identity/v3/endpoint_filter_client.py
new file mode 100644
index 0000000..a8cd722
--- /dev/null
+++ b/tempest/lib/services/identity/v3/endpoint_filter_client.py
@@ -0,0 +1,68 @@
+# Copyright 2017 AT&T Corp.
+# 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.
+
+"""
+https://developer.openstack.org/api-ref/identity/v3-ext/#os-ep-filter-api
+"""
+
+from oslo_serialization import jsonutils as json
+
+from tempest.lib.common import rest_client
+
+
+class EndPointsFilterClient(rest_client.RestClient):
+ api_version = "v3"
+ ep_filter = "OS-EP-FILTER"
+
+ def list_projects_for_endpoint(self, endpoint_id):
+ """List all projects that are associated with the endpoint."""
+ resp, body = self.get(self.ep_filter + '/endpoints/%s/projects' %
+ endpoint_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def add_endpoint_to_project(self, project_id, endpoint_id):
+ """Add association between project and endpoint. """
+ body = None
+ resp, body = self.put(
+ self.ep_filter + '/projects/%s/endpoints/%s' %
+ (project_id, endpoint_id), body)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def check_endpoint_in_project(self, project_id, endpoint_id):
+ """Check association of Project with Endpoint."""
+ resp, body = self.head(
+ self.ep_filter + '/projects/%s/endpoints/%s' %
+ (project_id, endpoint_id), None)
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
+ def list_endpoints_in_project(self, project_id):
+ """List Endpoints associated with Project."""
+ resp, body = self.get(self.ep_filter + '/projects/%s/endpoints'
+ % project_id)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def delete_endpoint_from_project(self, project_id, endpoint_id):
+ """Delete association between project and endpoint."""
+ resp, body = self.delete(
+ self.ep_filter + '/projects/%s/endpoints/%s'
+ % (project_id, endpoint_id))
+ self.expected_success(204, resp.status)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v2/__init__.py b/tempest/lib/services/volume/v2/__init__.py
index 9434896..68982d9 100644
--- a/tempest/lib/services/volume/v2/__init__.py
+++ b/tempest/lib/services/volume/v2/__init__.py
@@ -23,6 +23,8 @@
from tempest.lib.services.volume.v2.hosts_client import HostsClient
from tempest.lib.services.volume.v2.limits_client import LimitsClient
from tempest.lib.services.volume.v2.qos_client import QosSpecsClient
+from tempest.lib.services.volume.v2.quota_classes_client import \
+ QuotaClassesClient
from tempest.lib.services.volume.v2.quotas_client import QuotasClient
from tempest.lib.services.volume.v2.scheduler_stats_client import \
SchedulerStatsClient
@@ -40,4 +42,5 @@
'ExtensionsClient', 'HostsClient', 'QosSpecsClient', 'QuotasClient',
'ServicesClient', 'SnapshotsClient', 'TypesClient', 'VolumesClient',
'LimitsClient', 'CapabilitiesClient', 'SchedulerStatsClient',
- 'SnapshotManageClient', 'VolumeManageClient', 'TransfersClient']
+ 'SnapshotManageClient', 'VolumeManageClient', 'TransfersClient',
+ 'QuotaClassesClient']
diff --git a/tempest/lib/services/volume/v2/quota_classes_client.py b/tempest/lib/services/volume/v2/quota_classes_client.py
new file mode 100644
index 0000000..d40d2d9
--- /dev/null
+++ b/tempest/lib/services/volume/v2/quota_classes_client.py
@@ -0,0 +1,49 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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 tempest.lib.common import rest_client
+
+
+class QuotaClassesClient(rest_client.RestClient):
+ """Volume quota class V2 client."""
+
+ api_version = "v2"
+
+ def show_quota_class_set(self, quota_class_id):
+ """List quotas for a quota class.
+
+ TODO: Current api-site doesn't contain this API description.
+ LP: https://bugs.launchpad.net/nova/+bug/1602400
+ """
+ url = 'os-quota-class-sets/%s' % quota_class_id
+ resp, body = self.get(url)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
+
+ def update_quota_class_set(self, quota_class_id, **kwargs):
+ """Update quotas for a quota class.
+
+ TODO: Current api-site doesn't contain this API description.
+ LP: https://bugs.launchpad.net/nova/+bug/1602400
+ """
+ url = 'os-quota-class-sets/%s' % quota_class_id
+ put_body = json.dumps({'quota_class_set': kwargs})
+ resp, body = self.put(url, put_body)
+ self.expected_success(200, resp.status)
+ body = json.loads(body)
+ return rest_client.ResponseBody(resp, body)
diff --git a/tempest/lib/services/volume/v2/volumes_client.py b/tempest/lib/services/volume/v2/volumes_client.py
index 43fc9b8..8b5c96f 100644
--- a/tempest/lib/services/volume/v2/volumes_client.py
+++ b/tempest/lib/services/volume/v2/volumes_client.py
@@ -286,6 +286,19 @@
resp, body = self.post('volumes/%s/action' % volume_id, post_body)
self.expected_success(202, resp.status)
+ def force_detach_volume(self, volume_id, **kwargs):
+ """Force detach a volume.
+
+ For a full list of available parameters, please refer to the official
+ API reference:
+ https://developer.openstack.org/api-ref/block-storage/v2/#force-detach-volume
+ """
+ post_body = json.dumps({'os-force_detach': kwargs})
+ url = 'volumes/%s/action' % volume_id
+ resp, body = self.post(url, post_body)
+ self.expected_success(202, resp.status)
+ return rest_client.ResponseBody(resp, body)
+
def update_volume_image_metadata(self, volume_id, **kwargs):
"""Update image metadata for the volume.
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index ae2ba2f..bde577f 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -185,9 +185,6 @@
@test.services('compute', 'network')
def test_server_connectivity_resize(self):
resize_flavor = CONF.compute.flavor_ref_alt
- if resize_flavor == CONF.compute.flavor_ref:
- msg = "Skipping test - flavor_ref and flavor_ref_alt are identical"
- raise self.skipException(msg)
keypair = self.create_keypair()
server = self._setup_server(keypair)
floating_ip = self._setup_network(server, keypair)
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index 6005e28..6d6318c 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -45,11 +45,6 @@
@decorators.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b')
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize is not available.')
- @testtools.skipUnless(CONF.compute.flavor_ref !=
- CONF.compute.flavor_ref_alt
- and CONF.compute.flavor_ref_alt != "",
- 'The flavor_ref_alt option should not be empty and '
- 'should not be identical with flavor_ref')
@test.services('compute', 'volume')
def test_resize_volume_backed_server_confirm(self):
# We create an instance for use in this test
diff --git a/tempest/tests/cmd/test_workspace.py b/tempest/tests/cmd/test_workspace.py
index 6ca4d42..dc6c0c8 100644
--- a/tempest/tests/cmd/test_workspace.py
+++ b/tempest/tests/cmd/test_workspace.py
@@ -47,23 +47,25 @@
self.assertEqual(return_code, expected, msg)
def test_run_workspace_list(self):
- cmd = ['tempest', 'workspace', '--workspace-path',
- self.store_file, 'list']
+ cmd = ['tempest', 'workspace', 'list',
+ '--workspace-path', self.store_file]
self._run_cmd_gets_return_code(cmd, 0)
def test_run_workspace_register(self):
name = data_utils.rand_uuid()
path = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, path, ignore_errors=True)
- cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
- 'register', '--name', name, '--path', path]
+ cmd = ['tempest', 'workspace', 'register',
+ '--workspace-path', self.store_file,
+ '--name', name, '--path', path]
self._run_cmd_gets_return_code(cmd, 0)
self.assertIsNotNone(self.workspace_manager.get_workspace(name))
def test_run_workspace_rename(self):
new_name = data_utils.rand_uuid()
- cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
- 'rename', "--old-name", self.name, '--new-name', new_name]
+ cmd = ['tempest', 'workspace', 'rename',
+ '--workspace-path', self.store_file,
+ '--old-name', self.name, '--new-name', new_name]
self._run_cmd_gets_return_code(cmd, 0)
self.assertIsNone(self.workspace_manager.get_workspace(self.name))
self.assertIsNotNone(self.workspace_manager.get_workspace(new_name))
@@ -71,15 +73,17 @@
def test_run_workspace_move(self):
new_path = tempfile.mkdtemp()
self.addCleanup(shutil.rmtree, new_path, ignore_errors=True)
- cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
- 'move', '--name', self.name, '--path', new_path]
+ cmd = ['tempest', 'workspace', 'move',
+ '--workspace-path', self.store_file,
+ '--name', self.name, '--path', new_path]
self._run_cmd_gets_return_code(cmd, 0)
self.assertEqual(
self.workspace_manager.get_workspace(self.name), new_path)
def test_run_workspace_remove(self):
- cmd = ['tempest', 'workspace', '--workspace-path', self.store_file,
- 'remove', '--name', self.name]
+ cmd = ['tempest', 'workspace', 'remove',
+ '--workspace-path', self.store_file,
+ '--name', self.name]
self._run_cmd_gets_return_code(cmd, 0)
self.assertIsNone(self.workspace_manager.get_workspace(self.name))
diff --git a/tempest/tests/lib/services/compute/test_servers_client.py b/tempest/tests/lib/services/compute/test_servers_client.py
index a277dfe..a857329 100644
--- a/tempest/tests/lib/services/compute/test_servers_client.py
+++ b/tempest/tests/lib/services/compute/test_servers_client.py
@@ -1168,3 +1168,34 @@
tag=self.FAKE_TAGS[0],
status=204,
to_utf=bytes_body)
+
+
+class TestServersClientMinV26(base.BaseServiceTest):
+
+ def setUp(self):
+ super(TestServersClientMinV26, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = servers_client.ServersClient(fake_auth, 'compute',
+ 'regionOne')
+ base_compute_client.COMPUTE_MICROVERSION = '2.6'
+ self.server_id = "920eaac8-a284-4fd1-9c2c-b30f0181b125"
+
+ def tearDown(self):
+ super(TestServersClientMinV26, self).tearDown()
+ base_compute_client.COMPUTE_MICROVERSION = None
+
+ def test_get_remote_consoles(self):
+ self.check_service_client_function(
+ self.client.get_remote_console,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {
+ 'remote_console': {
+ 'protocol': 'serial',
+ 'type': 'serial',
+ 'url': 'ws://127.0.0.1:6083/?token=IllAllowIt'
+ }
+ },
+ server_id=self.server_id,
+ console_type='serial',
+ protocol='serial',
+ )
diff --git a/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
new file mode 100644
index 0000000..7faf6a0
--- /dev/null
+++ b/tempest/tests/lib/services/identity/v3/test_endpoint_filter_client.py
@@ -0,0 +1,165 @@
+# Copyright 2017 AT&T Corp.
+# 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.lib.services.identity.v3 import endpoint_filter_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestEndPointsFilterClient(base.BaseServiceTest):
+ FAKE_LIST_PROJECTS_FOR_ENDPOINTS = {
+ "projects": [
+ {
+ "domain_id": "1777c7",
+ "enabled": True,
+ "id": "1234ab1",
+ "type": "compute",
+ "links": {
+ "self": "http://example.com/identity/v3/projects/1234ab1"
+ },
+ "name": "Project 1",
+ "description": "Project 1 description",
+ },
+ {
+ "domain_id": "1777c7",
+ "enabled": True,
+ "id": "5678cd2",
+ "type": "compute",
+ "links": {
+ "self": "http://example.com/identity/v3/projects/5678cd2"
+ },
+ "name": "Project 2",
+ "description": "Project 2 description",
+ }
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/OS-EP-FILTER/endpoints/\
+ u6ay5u/projects",
+ "previous": None,
+ "next": None
+ }
+ }
+
+ FAKE_LIST_ENDPOINTS_FOR_PROJECTS = {
+ "endpoints": [
+ {
+ "id": "u6ay5u",
+ "interface": "public",
+ "url": "http://example.com/identity/",
+ "region": "north",
+ "links": {
+ "self": "http://example.com/identity/v3/endpoints/u6ay5u"
+ },
+ "service_id": "5um4r",
+ },
+ {
+ "id": "u6ay5u",
+ "interface": "internal",
+ "url": "http://example.com/identity/",
+ "region": "south",
+ "links": {
+ "self": "http://example.com/identity/v3/endpoints/u6ay5u"
+ },
+ "service_id": "5um4r",
+ },
+ ],
+ "links": {
+ "self": "http://example.com/identity/v3/OS-EP-FILTER/projects/\
+ 1234ab1/endpoints",
+ "previous": None,
+ "next": None
+ }
+ }
+
+ def setUp(self):
+ super(TestEndPointsFilterClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = endpoint_filter_client.EndPointsFilterClient(
+ fake_auth, 'identity', 'regionOne')
+
+ def _test_add_endpoint_to_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.add_endpoint_to_project,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ {},
+ bytes_body,
+ status=204,
+ project_id=3,
+ endpoint_id=4)
+
+ def _test_check_endpoint_in_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.check_endpoint_in_project,
+ 'tempest.lib.common.rest_client.RestClient.head',
+ {},
+ bytes_body,
+ status=204,
+ project_id=3,
+ endpoint_id=4)
+
+ def _test_list_projects_for_endpoint(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_projects_for_endpoint,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_PROJECTS_FOR_ENDPOINTS,
+ bytes_body,
+ status=200,
+ endpoint_id=3)
+
+ def _test_list_endpoints_in_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.list_endpoints_in_project,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ self.FAKE_LIST_ENDPOINTS_FOR_PROJECTS,
+ bytes_body,
+ status=200,
+ project_id=4)
+
+ def _test_delete_endpoint_from_project(self, bytes_body=False):
+ self.check_service_client_function(
+ self.client.delete_endpoint_from_project,
+ 'tempest.lib.common.rest_client.RestClient.delete',
+ {},
+ bytes_body,
+ status=204,
+ project_id=3,
+ endpoint_id=4)
+
+ def test_add_endpoint_to_project_with_str_body(self):
+ self._test_add_endpoint_to_project()
+
+ def test_add_endpoint_to_project_with_bytes_body(self):
+ self._test_add_endpoint_to_project(bytes_body=True)
+
+ def test_check_endpoint_in_project_with_str_body(self):
+ self._test_check_endpoint_in_project()
+
+ def test_check_endpoint_in_project_with_bytes_body(self):
+ self._test_check_endpoint_in_project(bytes_body=True)
+
+ def test_list_projects_for_endpoint_with_str_body(self):
+ self._test_list_projects_for_endpoint()
+
+ def test_list_projects_for_endpoint_with_bytes_body(self):
+ self._test_list_projects_for_endpoint(bytes_body=True)
+
+ def test_list_endpoints_in_project_with_str_body(self):
+ self._test_list_endpoints_in_project()
+
+ def test_list_endpoints_in_project_with_bytes_body(self):
+ self._test_list_endpoints_in_project(bytes_body=True)
+
+ def test_delete_endpoint_from_project(self):
+ self._test_delete_endpoint_from_project()
diff --git a/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py b/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
new file mode 100644
index 0000000..e715fcc
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_quota_classes_client.py
@@ -0,0 +1,71 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.
+
+import copy
+
+from tempest.lib.services.volume.v2 import quota_classes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestQuotaClassesClient(base.BaseServiceTest):
+
+ FAKE_QUOTA_CLASS_SET = {
+ "id": "test",
+ "gigabytes": 2000,
+ "volumes": 200,
+ "snapshots": 50,
+ "backups": 20,
+ "backup_gigabytes": 1500,
+ "per_volume_gigabytes": 500,
+ }
+
+ def setUp(self):
+ super(TestQuotaClassesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = quota_classes_client.QuotaClassesClient(
+ fake_auth, 'volume', 'regionOne')
+
+ def _test_show_quota_class_set(self, bytes_body=False):
+ fake_body = {'quota_class_set': self.FAKE_QUOTA_CLASS_SET}
+ self.check_service_client_function(
+ self.client.show_quota_class_set,
+ 'tempest.lib.common.rest_client.RestClient.get',
+ fake_body,
+ bytes_body,
+ quota_class_id="test")
+
+ def _test_update_quota_class_set(self, bytes_body=False):
+ fake_quota_class_set = copy.deepcopy(self.FAKE_QUOTA_CLASS_SET)
+ fake_quota_class_set.pop("id")
+ fake_body = {'quota_class_set': fake_quota_class_set}
+ self.check_service_client_function(
+ self.client.update_quota_class_set,
+ 'tempest.lib.common.rest_client.RestClient.put',
+ fake_body,
+ bytes_body,
+ quota_class_id="test")
+
+ def test_show_quota_class_set_with_str_body(self):
+ self._test_show_quota_class_set()
+
+ def test_show_quota_class_set_with_bytes_body(self):
+ self._test_show_quota_class_set(bytes_body=True)
+
+ def test_update_quota_class_set_with_str_boy(self):
+ self._test_update_quota_class_set()
+
+ def test_update_quota_class_set_with_bytes_body(self):
+ self._test_update_quota_class_set(bytes_body=True)
diff --git a/tempest/tests/lib/services/volume/v2/test_volumes_client.py b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
new file mode 100644
index 0000000..498b963
--- /dev/null
+++ b/tempest/tests/lib/services/volume/v2/test_volumes_client.py
@@ -0,0 +1,52 @@
+# Copyright 2017 FiberHome Telecommunication Technologies CO.,LTD
+# 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.lib.services.volume.v2 import volumes_client
+from tempest.tests.lib import fake_auth_provider
+from tempest.tests.lib.services import base
+
+
+class TestVolumesClient(base.BaseServiceTest):
+
+ def setUp(self):
+ super(TestVolumesClient, self).setUp()
+ fake_auth = fake_auth_provider.FakeAuthProvider()
+ self.client = volumes_client.VolumesClient(fake_auth,
+ 'volume',
+ 'regionOne')
+
+ def _test_force_detach_volume(self, bytes_body=False):
+ kwargs = {
+ 'attachment_id': '6980e295-920f-412e-b189-05c50d605acd',
+ 'connector': {
+ 'initiator': 'iqn.2017-04.org.fake:01'
+ }
+ }
+
+ self.check_service_client_function(
+ self.client.force_detach_volume,
+ 'tempest.lib.common.rest_client.RestClient.post',
+ {},
+ to_utf=bytes_body,
+ status=202,
+ volume_id="a3be971b-8de5-4bdf-bdb8-3d8eb0fb69f8",
+ **kwargs
+ )
+
+ def test_force_detach_volume_with_str_body(self):
+ self._test_force_detach_volume()
+
+ def test_force_detach_volume_with_bytes_body(self):
+ self._test_force_detach_volume(bytes_body=True)
diff --git a/tempest/tests/lib/test_rest_client.py b/tempest/tests/lib/test_rest_client.py
index 4a83631..43bb6d0 100644
--- a/tempest/tests/lib/test_rest_client.py
+++ b/tempest/tests/lib/test_rest_client.py
@@ -91,18 +91,15 @@
class TestRestClientHeadersJSON(TestRestClientHTTPMethods):
- TYPE = "json"
def _verify_headers(self, resp):
- self.assertEqual(self.rest_client._get_type(), self.TYPE)
resp = dict((k.lower(), v) for k, v in six.iteritems(resp))
self.assertEqual(self.header_value, resp['accept'])
self.assertEqual(self.header_value, resp['content-type'])
def setUp(self):
super(TestRestClientHeadersJSON, self).setUp()
- self.rest_client.TYPE = self.TYPE
- self.header_value = 'application/%s' % self.rest_client._get_type()
+ self.header_value = 'application/json'
def test_post(self):
resp, __ = self.rest_client.post(self.url, {})