Allow wait_until=SSHABLE in scenario manager

If we can, we should always wait for servers to be ssh'able before we
try to do things to them, especially attach volumes. This improves
stability of the tests, at the expense of performance.

This also makes some volume-having scenario tests wait for SSHABLE.
The ones that don't are already using ssh to do things like create
and check timestamps, so they need not be changed to still have this
behavior.

Change-Id: I169d8d119758e4f5e79c42cfd458577a67b13fc5
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index db0aa5a..22c0530 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -14,6 +14,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import copy
 import os
 import subprocess
 
@@ -89,6 +90,16 @@
             volume_microversion=cls.volume_request_microversion,
             placement_microversion=cls.placement_request_microversion)
 
+    @classmethod
+    def setup_credentials(cls):
+        # Setting network=True, subnet=True creates a default network
+        cls.set_network_resources(
+            network=True,
+            subnet=True,
+            router=True,
+            dhcp=True)
+        super(ScenarioTest, cls).setup_credentials()
+
     def setup_compute_client(cls):
         """Compute client"""
         cls.compute_images_client = cls.os_primary.compute_images_client
@@ -309,6 +320,24 @@
             kwargs.setdefault('availability_zone',
                               CONF.compute.compute_volume_common_az)
 
+        keypair = kwargs.pop('keypair', None)
+        if wait_until == 'SSHABLE':
+            # NOTE(danms): We should do this whether valdiation is enabled or
+            # not to consistently provide the resources to the
+            # create_test_server() function. If validation is disabled, then
+            # get_test_validation_resources() is basically a no-op for
+            # performance.
+            validation_resources = self.get_test_validation_resources(
+                self.os_primary)
+            if keypair:
+                validation_resources = copy.deepcopy(validation_resources)
+                validation_resources.update(
+                    keypair=keypair)
+            kwargs.update({'validatable': True,
+                           'validation_resources': validation_resources})
+        if keypair:
+            kwargs.update({'key_name': keypair['name']})
+
         body, _ = compute.create_test_server(
             clients,
             tenant_network=tenant_network,
@@ -1054,6 +1083,20 @@
                         floating_ip['id'])
         return floating_ip
 
+    def get_floating_ip(self, server):
+        """Attempt to get an existing floating ip or a server
+
+        If one exists, return it, else return None
+        """
+        port_id, ip4 = self.get_server_port_id_and_ip4(server)
+        ips = self.floating_ips_client.list_floatingips(
+            floating_network_id=CONF.network.public_network_id,
+            port_id=port_id)
+        try:
+            return ips['floatingips'][0]['floating_ip_address']
+        except (KeyError, IndexError):
+            return None
+
     def associate_floating_ip(self, floating_ip, server):
         """Associate floating ip to server
 
@@ -1148,8 +1191,14 @@
             # The tests calling this method don't have a floating IP
             # and can't make use of the validation resources. So the
             # method is creating the floating IP there.
-            return self.create_floating_ip(
-                server, **kwargs)['floating_ip_address']
+            fip = self.get_floating_ip(server)
+            if fip:
+                # Already have a floating ip, so use it instead of creating
+                # another
+                return fip
+            else:
+                return self.create_floating_ip(
+                    server, **kwargs)['floating_ip_address']
         elif CONF.validation.connect_method == 'fixed':
             # Determine the network name to look for based on config or creds
             # provider network resources.
@@ -1198,7 +1247,7 @@
 
         create_kwargs = dict({'image_id': ''})
         if keypair:
-            create_kwargs['key_name'] = keypair['name']
+            create_kwargs['keypair'] = keypair
         if security_group:
             create_kwargs['security_groups'] = [
                 {'name': security_group['name']}]
diff --git a/tempest/scenario/test_encrypted_cinder_volumes.py b/tempest/scenario/test_encrypted_cinder_volumes.py
index 9788e19..60abc02 100644
--- a/tempest/scenario/test_encrypted_cinder_volumes.py
+++ b/tempest/scenario/test_encrypted_cinder_volumes.py
@@ -45,9 +45,7 @@
             raise cls.skipException('Encrypted volume attach is not supported')
 
     def launch_instance(self):
-        keypair = self.create_keypair()
-
-        return self.create_server(key_name=keypair['name'])
+        return self.create_server(wait_until='SSHABLE')
 
     def attach_detach_volume(self, server, volume):
         attached_volume = self.nova_volume_attach(server, volume)
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 2a15470..5e10ebf 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -133,7 +133,8 @@
         security_group = self.create_security_group()
         self.md = {'meta1': 'data1', 'meta2': 'data2', 'metaN': 'dataN'}
         self.instance = self.create_server(
-            key_name=keypair['name'],
+            keypair=keypair,
+            wait_until='SSHABLE',
             security_groups=[{'name': security_group['name']}],
             config_drive=CONF.compute_feature_enabled.config_drive,
             metadata=self.md)
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 2e87c15..5c5033a 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -187,6 +187,7 @@
             source_id=volume_origin['id'],
             source_type='volume',
             delete_on_termination=True,
+            wait_until='SSHABLE',
             name=name)
         # Create a snapshot image from the volume-backed server.
         # The compute service will have the block service create a snapshot of
@@ -200,7 +201,8 @@
         # disk for the server.
         name = data_utils.rand_name(self.__class__.__name__ +
                                     '-image-snapshot-server')
-        instance2 = self.create_server(image_id=image['id'], name=name)
+        instance2 = self.create_server(image_id=image['id'], name=name,
+                                       wait_until='SSHABLE')
 
         # Verify the server was created from the image-defined BDM.
         volume_attachments = instance2['os-extended-volumes:volumes_attached']