Merge "[V3] Make endpoints_client use **kwargs"
diff --git a/HACKING.rst b/HACKING.rst
index 3799046..9f7487d 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -17,6 +17,7 @@
- [T108] Check no hyphen at the end of rand_name() argument
- [T109] Cannot use testtools.skip decorator; instead use
decorators.skip_because from tempest-lib
+- [T110] Check that service client names of GET should be consistent
- [N322] Method's default argument shouldn't be mutable
Test Data/Configuration
diff --git a/tempest/api/compute/admin/test_availability_zone.py b/tempest/api/compute/admin/test_availability_zone.py
index 1b36ff2..5b02761 100644
--- a/tempest/api/compute/admin/test_availability_zone.py
+++ b/tempest/api/compute/admin/test_availability_zone.py
@@ -17,11 +17,10 @@
from tempest import test
-class AZAdminV2TestJSON(base.BaseComputeAdminTest):
+class AZAdminV2TestJSON(base.BaseV2ComputeAdminTest):
"""
Tests Availability Zone API List
"""
- _api_version = 2
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/compute/admin/test_live_migration.py b/tempest/api/compute/admin/test_live_migration.py
index f186a7d..ecb27c2 100644
--- a/tempest/api/compute/admin/test_live_migration.py
+++ b/tempest/api/compute/admin/test_live_migration.py
@@ -28,14 +28,27 @@
_host_key = 'OS-EXT-SRV-ATTR:host'
@classmethod
+ def skip_checks(cls):
+ super(LiveBlockMigrationTestJSON, cls).skip_checks()
+
+ if not CONF.compute_feature_enabled.live_migration:
+ skip_msg = ("%s skipped as live-migration is "
+ "not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ if len(cls._get_compute_hostnames()) < 2:
+ raise cls.skipTest(
+ "Less than 2 compute nodes, skipping migration test.")
+
+ @classmethod
def setup_clients(cls):
super(LiveBlockMigrationTestJSON, cls).setup_clients()
cls.admin_hosts_client = cls.os_adm.hosts_client
cls.admin_servers_client = cls.os_adm.servers_client
cls.admin_migration_client = cls.os_adm.migrations_client
- def _get_compute_hostnames(self):
- body = self.admin_hosts_client.list_hosts()['hosts']
+ @classmethod
+ def _get_compute_hostnames(cls):
+ body = cls.admin_hosts_client.list_hosts()['hosts']
return [
host_record['host_name']
for host_record in body
@@ -90,9 +103,6 @@
volume_backed, *block* migration is not used.
"""
# Live migrate an instance to another host
- if len(self._get_compute_hostnames()) < 2:
- raise self.skipTest(
- "Less than 2 compute nodes, skipping migration test.")
server_id = self._create_server(volume_backed=volume_backed)
actual_host = self._get_host_for_server(server_id)
target_host = self._get_host_other_than(actual_host)
@@ -117,14 +127,10 @@
msg)
@test.idempotent_id('1dce86b8-eb04-4c03-a9d8-9c1dc3ee0c7b')
- @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
- 'Live migration not available')
def test_live_block_migration(self):
self._test_live_migration()
@test.idempotent_id('1e107f21-61b2-4988-8f22-b196e938ab88')
- @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
- 'Live migration not available')
@testtools.skipUnless(CONF.compute_feature_enabled.pause,
'Pause is not available.')
@testtools.skipUnless(CONF.compute_feature_enabled
@@ -135,24 +141,18 @@
self._test_live_migration(state='PAUSED')
@test.idempotent_id('5071cf17-3004-4257-ae61-73a84e28badd')
- @testtools.skipUnless(CONF.compute_feature_enabled.live_migration,
- 'Live migration not available')
@test.services('volume')
def test_volume_backed_live_migration(self):
self._test_live_migration(volume_backed=True)
@test.idempotent_id('e19c0cc6-6720-4ed8-be83-b6603ed5c812')
- @testtools.skipIf(not CONF.compute_feature_enabled.live_migration or not
- CONF.compute_feature_enabled.
+ @testtools.skipIf(not CONF.compute_feature_enabled.
block_migration_for_live_migration,
'Block Live migration not available')
@testtools.skipIf(not CONF.compute_feature_enabled.
block_migrate_cinder_iscsi,
'Block Live migration not configured for iSCSI')
def test_iscsi_volume(self):
- if len(self._get_compute_hostnames()) < 2:
- raise self.skipTest(
- "Less than 2 compute nodes, skipping migration test.")
server_id = self._create_server()
actual_host = self._get_host_for_server(server_id)
target_host = self._get_host_other_than(actual_host)
diff --git a/tempest/api/compute/admin/test_networks.py b/tempest/api/compute/admin/test_networks.py
index 1da3f6e..c4aa81b 100644
--- a/tempest/api/compute/admin/test_networks.py
+++ b/tempest/api/compute/admin/test_networks.py
@@ -20,8 +20,7 @@
CONF = config.CONF
-class NetworksTest(base.BaseComputeAdminTest):
- _api_version = 2
+class NetworksTest(base.BaseV2ComputeAdminTest):
"""
Tests Nova Networks API that usually requires admin privileges.
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index ec2192f..1ee8f04 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -22,7 +22,6 @@
from tempest.common.utils import data_utils
from tempest.common import waiters
from tempest import config
-from tempest import exceptions
import tempest.test
CONF = config.CONF
@@ -30,10 +29,9 @@
LOG = logging.getLogger(__name__)
-class BaseComputeTest(tempest.test.BaseTestCase):
+class BaseV2ComputeTest(tempest.test.BaseTestCase):
"""Base test case class for all Compute API tests."""
- _api_version = 2
force_tenant_isolation = False
# TODO(andreaf) We should care also for the alt_manager here
@@ -42,22 +40,18 @@
@classmethod
def skip_checks(cls):
- super(BaseComputeTest, cls).skip_checks()
+ super(BaseV2ComputeTest, cls).skip_checks()
if not CONF.service_available.nova:
raise cls.skipException("Nova is not available")
- if cls._api_version != 2:
- msg = ("Unexpected API version is specified (%s)" %
- cls._api_version)
- raise exceptions.InvalidConfiguration(message=msg)
@classmethod
def setup_credentials(cls):
cls.set_network_resources()
- super(BaseComputeTest, cls).setup_credentials()
+ super(BaseV2ComputeTest, cls).setup_credentials()
@classmethod
def setup_clients(cls):
- super(BaseComputeTest, cls).setup_clients()
+ super(BaseV2ComputeTest, cls).setup_clients()
cls.servers_client = cls.os.servers_client
cls.server_groups_client = cls.os.server_groups_client
cls.flavors_client = cls.os.flavors_client
@@ -96,7 +90,7 @@
@classmethod
def resource_setup(cls):
- super(BaseComputeTest, cls).resource_setup()
+ super(BaseV2ComputeTest, cls).resource_setup()
cls.build_interval = CONF.compute.build_interval
cls.build_timeout = CONF.compute.build_timeout
cls.ssh_user = CONF.compute.ssh_user
@@ -117,7 +111,7 @@
cls.clear_servers()
cls.clear_security_groups()
cls.clear_server_groups()
- super(BaseComputeTest, cls).resource_cleanup()
+ super(BaseV2ComputeTest, cls).resource_cleanup()
@classmethod
def clear_servers(cls):
@@ -147,7 +141,7 @@
Method will delete the server when it's dirty.
The setUp method is responsible for creating a new server.
Exceptions raised in tearDown class are fails the test case,
- This method supposed to use only by tierDown methods, when
+ This method supposed to use only by tearDown methods, when
the shared server_id is stored in the server_id of the class.
"""
if getattr(cls, 'server_id', None) is not None:
@@ -356,22 +350,13 @@
return ip_or_server
-class BaseV2ComputeTest(BaseComputeTest):
- _api_version = 2
-
-
-class BaseComputeAdminTest(BaseComputeTest):
+class BaseV2ComputeAdminTest(BaseV2ComputeTest):
"""Base test case class for Compute Admin API tests."""
credentials = ['primary', 'admin']
@classmethod
def setup_clients(cls):
- super(BaseComputeAdminTest, cls).setup_clients()
+ super(BaseV2ComputeAdminTest, cls).setup_clients()
cls.availability_zone_admin_client = (
cls.os_adm.availability_zone_client)
-
-
-class BaseV2ComputeAdminTest(BaseComputeAdminTest):
- """Base test case class for Compute Admin V2 API tests."""
- _api_version = 2
diff --git a/tempest/api/compute/certificates/test_certificates.py b/tempest/api/compute/certificates/test_certificates.py
index 0096fc2..d5c7302 100644
--- a/tempest/api/compute/certificates/test_certificates.py
+++ b/tempest/api/compute/certificates/test_certificates.py
@@ -20,9 +20,7 @@
CONF = config.CONF
-class CertificatesV2TestJSON(base.BaseComputeTest):
-
- _api_version = 2
+class CertificatesV2TestJSON(base.BaseV2ComputeTest):
@classmethod
def skip_checks(cls):
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index e114c80..7e01296 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -17,9 +17,7 @@
from tempest import test
-class FlavorsV2TestJSON(base.BaseComputeTest):
-
- _api_version = 2
+class FlavorsV2TestJSON(base.BaseV2ComputeTest):
_min_disk = 'minDisk'
_min_ram = 'minRam'
diff --git a/tempest/api/compute/keypairs/base.py b/tempest/api/compute/keypairs/base.py
index 76e5573..15f231b 100644
--- a/tempest/api/compute/keypairs/base.py
+++ b/tempest/api/compute/keypairs/base.py
@@ -16,11 +16,9 @@
from tempest.api.compute import base
-class BaseKeypairTest(base.BaseComputeTest):
+class BaseKeypairTest(base.BaseV2ComputeTest):
"""Base test case class for all keypair API tests."""
- _api_version = 2
-
@classmethod
def setup_clients(cls):
super(BaseKeypairTest, cls).setup_clients()
diff --git a/tempest/api/compute/servers/test_availability_zone.py b/tempest/api/compute/servers/test_availability_zone.py
index 080441a..8f1f360 100644
--- a/tempest/api/compute/servers/test_availability_zone.py
+++ b/tempest/api/compute/servers/test_availability_zone.py
@@ -17,11 +17,10 @@
from tempest import test
-class AZV2TestJSON(base.BaseComputeTest):
+class AZV2TestJSON(base.BaseV2ComputeTest):
"""
Tests Availability Zone API List
"""
- _api_version = 2
@classmethod
def setup_clients(cls):
diff --git a/tempest/api/compute/test_versions.py b/tempest/api/compute/test_versions.py
index f94cee6..8b84a21 100644
--- a/tempest/api/compute/test_versions.py
+++ b/tempest/api/compute/test_versions.py
@@ -16,7 +16,7 @@
from tempest import test
-class TestVersions(base.BaseComputeTest):
+class TestVersions(base.BaseV2ComputeTest):
@test.idempotent_id('6c0a0990-43b6-4529-9b61-5fd8daf7c55c')
def test_list_api_versions(self):
diff --git a/tempest/config.py b/tempest/config.py
index 0cda018..26823de 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -255,6 +255,7 @@
"when sshing to a guest."),
cfg.StrOpt('ssh_auth_method',
default='keypair',
+ choices=('keypair', 'configured', 'adminpass', 'disabled'),
help="Auth method used for authenticate to the instance. "
"Valid choices are: keypair, configured, adminpass "
"and disabled. "
@@ -264,6 +265,7 @@
"Disabled: avoid using ssh when it is an option."),
cfg.StrOpt('ssh_connect_method',
default='floating',
+ choices=('fixed', 'floating'),
help="How to connect to the instance? "
"fixed: using the first ip belongs the fixed network "
"floating: creating and using a floating ip."),
@@ -330,7 +332,13 @@
help='Unallocated floating IP range, which will be used to '
'test the floating IP bulk feature for CRUD operation. '
'This block must not overlap an existing floating IP '
- 'pool.')
+ 'pool.'),
+ cfg.IntOpt('min_compute_nodes',
+ default=1,
+ help=('The minimum number of compute nodes expected. This will '
+ 'be utilized by some multinode specific tests to ensure '
+ 'that requests match the expected size of the cluster '
+ 'you are testing with.'))
]
compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
diff --git a/tempest/hacking/checks.py b/tempest/hacking/checks.py
index 06ca09b..936fbe8 100644
--- a/tempest/hacking/checks.py
+++ b/tempest/hacking/checks.py
@@ -30,6 +30,9 @@
RAND_NAME_HYPHEN_RE = re.compile(r".*rand_name\(.+[\-\_][\"\']\)")
mutable_default_args = re.compile(r"^\s*def .+\((.+=\{\}|.+=\[\])")
TESTTOOLS_SKIP_DECORATOR = re.compile(r'\s*@testtools\.skip\((.*)\)')
+METHOD = re.compile(r"^ def .+")
+METHOD_GET_RESOURCE = re.compile(r"^\s*def (list|show)\_.+")
+CLASS = re.compile(r"^class .+")
def import_no_clients_in_api_and_scenario_tests(physical_line, filename):
@@ -143,6 +146,45 @@
"decorators.skip_because from tempest-lib")
+def get_resources_on_service_clients(logical_line, physical_line, filename,
+ line_number, lines):
+ """Check that service client names of GET should be consistent
+
+ T110
+ """
+ if 'tempest/services/' not in filename:
+ return
+
+ ignored_list = []
+ with open('tempest/hacking/ignored_list_T110.txt') as f:
+ for line in f:
+ ignored_list.append(line.strip())
+
+ if filename in ignored_list:
+ return
+
+ if not METHOD.match(physical_line):
+ return
+
+ if pep8.noqa(physical_line):
+ return
+
+ for line in lines[line_number:]:
+ if METHOD.match(line) or CLASS.match(line):
+ # the end of a method
+ return
+
+ if 'self.get(' not in line:
+ continue
+
+ if METHOD_GET_RESOURCE.match(logical_line):
+ return
+
+ msg = ("T110: [GET /resources] methods should be list_<resource name>s"
+ " or show_<resource name>")
+ yield (0, msg)
+
+
def factory(register):
register(import_no_clients_in_api_and_scenario_tests)
register(scenario_tests_need_service_tags)
@@ -152,3 +194,4 @@
register(no_hyphen_at_end_of_rand_name)
register(no_mutable_default_args)
register(no_testtools_skip_decorator)
+ register(get_resources_on_service_clients)
diff --git a/tempest/hacking/ignored_list_T110.txt b/tempest/hacking/ignored_list_T110.txt
new file mode 100644
index 0000000..987861f
--- /dev/null
+++ b/tempest/hacking/ignored_list_T110.txt
@@ -0,0 +1,14 @@
+./tempest/services/compute/json/server_groups_client.py
+./tempest/services/compute/json/servers_client.py
+./tempest/services/database/json/flavors_client.py
+./tempest/services/identity/v3/json/credentials_client.py
+./tempest/services/identity/v3/json/identity_client.py
+./tempest/services/identity/v3/json/policy_client.py
+./tempest/services/identity/v3/json/region_client.py
+./tempest/services/messaging/json/messaging_client.py
+./tempest/services/object_storage/object_client.py
+./tempest/services/telemetry/json/telemetry_client.py
+./tempest/services/volume/json/qos_client.py
+./tempest/services/volume/json/backups_client.py
+./tempest/services/image/v2/json/image_client.py
+./tempest/services/baremetal/base.py
diff --git a/tempest/scenario/test_server_multinode.py b/tempest/scenario/test_server_multinode.py
new file mode 100644
index 0000000..ef78b4a
--- /dev/null
+++ b/tempest/scenario/test_server_multinode.py
@@ -0,0 +1,88 @@
+# 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 oslo_log import log as logging
+
+from tempest import config
+from tempest import exceptions
+from tempest.scenario import manager
+from tempest import test
+
+CONF = config.CONF
+
+LOG = logging.getLogger(__name__)
+
+
+class TestServerMultinode(manager.ScenarioTest):
+
+ """
+ This is a set of tests specific to multinode testing.
+
+ """
+ credentials = ['primary', 'admin']
+
+ @classmethod
+ def setup_clients(cls):
+ super(TestServerMultinode, cls).setup_clients()
+ # Use admin client by default
+ cls.manager = cls.admin_manager
+ # this is needed so that we can use the availability_zone:host
+ # scheduler hint, which is admin_only by default
+ cls.servers_client = cls.admin_manager.servers_client
+ super(TestServerMultinode, cls).resource_setup()
+
+ @test.idempotent_id('9cecbe35-b9d4-48da-a37e-7ce70aa43d30')
+ @test.attr(type='smoke')
+ @test.services('compute', 'network')
+ def test_schedule_to_all_nodes(self):
+ host_client = self.manager.hosts_client
+ hosts = host_client.list_hosts()['hosts']
+ hosts = [x for x in hosts if x['service'] == 'compute']
+
+ # ensure we have at least as many compute hosts as we expect
+ if len(hosts) < CONF.compute.min_compute_nodes:
+ raise exceptions.InvalidConfiguration(
+ "Host list %s is shorter than min_compute_nodes. "
+ "Did a compute worker not boot correctly?" % hosts)
+
+ # create 1 compute for each node, up to the min_compute_nodes
+ # threshold (so that things don't get crazy if you have 1000
+ # compute nodes but set min to 3).
+ servers = []
+
+ for host in hosts[:CONF.compute.min_compute_nodes]:
+ create_kwargs = {
+ 'availability_zone': '%(zone)s:%(host_name)s' % host
+ }
+
+ # by getting to active state here, this means this has
+ # landed on the host in question.
+ inst = self.create_server(image=CONF.compute.image_ref,
+ flavor=CONF.compute.flavor_ref,
+ create_kwargs=create_kwargs)
+ server = self.servers_client.show_server(inst['id'])['server']
+ servers.append(server)
+
+ # make sure we really have the number of servers we think we should
+ self.assertEqual(
+ len(servers), CONF.compute.min_compute_nodes,
+ "Incorrect number of servers built %s" % servers)
+
+ # ensure that every server ended up on a different host
+ host_ids = [x['hostId'] for x in servers]
+ self.assertEqual(
+ len(set(host_ids)), len(servers),
+ "Incorrect number of distinct host_ids scheduled to %s" % servers)