Merge "Use skip_checks for live-migration feature in test"
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index ec2192f..b3f69c1 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -147,7 +147,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:
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index a59cb16..0754d27 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -52,7 +52,7 @@
         except Exception:
             # Rebuild server if something happened to it during a test
             self.__class__.server_id = self.rebuild_server(
-                self.server_id, validatable=True)['server']
+                self.server_id, validatable=True)
 
     def tearDown(self):
         self.server_check_teardown()
diff --git a/tempest/api/identity/admin/v2/test_users.py b/tempest/api/identity/admin/v2/test_users.py
index 8146828..6341106 100644
--- a/tempest/api/identity/admin/v2/test_users.py
+++ b/tempest/api/identity/admin/v2/test_users.py
@@ -146,7 +146,7 @@
         user_ids.append(user2['id'])
         self.data.users.append(user2)
         # List of users for the respective tenant ID
-        body = (self.client.list_users_for_tenant(self.data.tenant['id'])
+        body = (self.client.list_tenant_users(self.data.tenant['id'])
                 ['users'])
         for i in body:
             fetched_user_ids.append(i['id'])
@@ -182,7 +182,7 @@
                                             second_user['id'],
                                             role['id'])['role']
         # List of users with roles for the respective tenant ID
-        body = (self.client.list_users_for_tenant(self.data.tenant['id'])
+        body = (self.client.list_tenant_users(self.data.tenant['id'])
                 ['users'])
         for i in body:
             fetched_user_ids.append(i['id'])
diff --git a/tempest/api/identity/admin/v2/test_users_negative.py b/tempest/api/identity/admin/v2/test_users_negative.py
index 6d9d84b..a88053d 100644
--- a/tempest/api/identity/admin/v2/test_users_negative.py
+++ b/tempest/api/identity/admin/v2/test_users_negative.py
@@ -247,4 +247,4 @@
         # List the users with invalid tenant id
         for invalid in invalid_id:
             self.assertRaises(lib_exc.NotFound,
-                              self.client.list_users_for_tenant, invalid)
+                              self.client.list_tenant_users, invalid)
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/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index c0b82b6..ed691ac 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -107,13 +107,6 @@
             return None
         return int(ephemeral)
 
-    def add_floating_ip(self):
-        floating_ip = (self.floating_ips_client.create_floating_ip()
-                       ['floating_ip'])
-        self.floating_ips_client.associate_floating_ip_to_server(
-            floating_ip['ip'], self.instance['id'])
-        return floating_ip['ip']
-
     def validate_ports(self):
         for port in self.get_ports(self.node['uuid']):
             n_port_id = port['extra']['vif_port_id']
@@ -131,7 +124,7 @@
         self.validate_ports()
         self.verify_connectivity()
         if CONF.compute.ssh_connect_method == 'floating':
-            floating_ip = self.add_floating_ip()
+            floating_ip = self.create_floating_ip(self.instance)['ip']
             self.verify_connectivity(ip=floating_ip)
 
         vm_client = self.get_remote_client(self.instance)
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index e2f8adb..c55cff6 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -89,17 +89,10 @@
     def verify_ssh(self):
         if self.run_ssh:
             # Obtain a floating IP
-            self.floating_ip = (self.floating_ips_client.create_floating_ip()
-                                ['floating_ip'])
-            self.addCleanup(self.delete_wrapper,
-                            self.floating_ips_client.delete_floating_ip,
-                            self.floating_ip['id'])
-            # Attach a floating IP
-            self.floating_ips_client.associate_floating_ip_to_server(
-                self.floating_ip['ip'], self.instance['id'])
+            self.fip = self.create_floating_ip(self.instance)['ip']
             # Check ssh
             self.ssh_client = self.get_remote_client(
-                server_or_ip=self.floating_ip['ip'],
+                server_or_ip=self.fip,
                 username=self.image_utils.ssh_user(self.image_ref),
                 private_key=self.keypair['private_key'])
 
@@ -110,12 +103,11 @@
 
             def exec_cmd_and_verify_output():
                 cmd = 'curl ' + md_url
-                floating_ip = self.floating_ip['ip']
                 result = self.ssh_client.exec_command(cmd)
                 if result:
                     msg = ('Failed while verifying metadata on server. Result '
-                           'of command "%s" is NOT "%s".' % (cmd, floating_ip))
-                    self.assertEqual(floating_ip, result, msg)
+                           'of command "%s" is NOT "%s".' % (cmd, self.fip))
+                    self.assertEqual(self.fip, result, msg)
                     return 'Verification is successful!'
 
             if not test.call_until_true(exec_cmd_and_verify_output,
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)
diff --git a/tempest/scenario/test_shelve_instance.py b/tempest/scenario/test_shelve_instance.py
index bc80412..3b38888 100644
--- a/tempest/scenario/test_shelve_instance.py
+++ b/tempest/scenario/test_shelve_instance.py
@@ -79,15 +79,9 @@
                                         create_kwargs=create_kwargs)
 
         if CONF.compute.use_floatingip_for_ssh:
-            floating_ip = (self.floating_ips_client.create_floating_ip()
-                           ['floating_ip'])
-            self.addCleanup(self.delete_wrapper,
-                            self.floating_ips_client.delete_floating_ip,
-                            floating_ip['id'])
-            self.floating_ips_client.associate_floating_ip_to_server(
-                floating_ip['ip'], server['id'])
+            floating_ip = self.create_floating_ip(server)['ip']
             timestamp = self.create_timestamp(
-                floating_ip['ip'], private_key=keypair['private_key'])
+                floating_ip, private_key=keypair['private_key'])
         else:
             timestamp = self.create_timestamp(
                 server, private_key=keypair['private_key'])
@@ -97,7 +91,7 @@
         # with the instance snapshot
         self._shelve_then_unshelve_server(server)
         if CONF.compute.use_floatingip_for_ssh:
-            timestamp2 = self.get_timestamp(floating_ip['ip'],
+            timestamp2 = self.get_timestamp(floating_ip,
                                             private_key=keypair['private_key'])
         else:
             timestamp2 = self.get_timestamp(server,
diff --git a/tempest/services/identity/v2/json/identity_client.py b/tempest/services/identity/v2/json/identity_client.py
index 8f76c74..1c1bfaa 100644
--- a/tempest/services/identity/v2/json/identity_client.py
+++ b/tempest/services/identity/v2/json/identity_client.py
@@ -213,7 +213,7 @@
         self.expected_success(204, resp.status)
         return service_client.ResponseBody(resp, body)
 
-    def list_users_for_tenant(self, tenant_id):
+    def list_tenant_users(self, tenant_id):
         """List users for a Tenant."""
         resp, body = self.get('/tenants/%s/users' % tenant_id)
         self.expected_success(200, resp.status)
@@ -221,7 +221,7 @@
         return service_client.ResponseBody(resp, body)
 
     def get_user_by_username(self, tenant_id, username):
-        users = self.list_users_for_tenant(tenant_id)['users']
+        users = self.list_tenant_users(tenant_id)['users']
         for user in users:
             if user['name'] == username:
                 return user