Merge "Fix a misspelled addClassResourceCleanp in tempest doc"
diff --git a/.zuul.yaml b/.zuul.yaml
index ec6c59a..9f91455 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -14,6 +14,19 @@
     post-run: playbooks/post-tempest.yaml
 
 - job:
+    name: tempest-full
+    parent: devstack-tempest
+    description: |
+      Base integration test with Neutron networking and py27.
+      Former names for this job where:
+        * legacy-tempest-dsvm-neutron-full
+        * gate-tempest-dsvm-neutron-full-ubuntu-xenial
+    vars:
+      tox_venvlist: full
+      devstack_localrc:
+        ENABLE_FILE_INJECTION: True
+
+- job:
     name: tempest-tox-plugin-sanity-check
     parent: tox
     description: |
@@ -98,4 +111,15 @@
               - ^playbooks/
               - ^roles/
               - ^.zuul.yaml$
+        - tempest-full:
+            voting: false
+            irrelevant-files:
+              - ^(test-|)requirements.txt$
+              - ^.*\.rst$
+              - ^doc/.*$
+              - ^etc/.*$
+              - ^releasenotes/.*$
+              - ^setup.cfg$
+              - ^tempest/hacking/.*$
+              - ^tempest/tests/.*$
         - tempest-tox-plugin-sanity-check
diff --git a/doc/source/plugin.rst b/doc/source/plugin.rst
index 2afb1e5..6f6621d 100644
--- a/doc/source/plugin.rst
+++ b/doc/source/plugin.rst
@@ -132,7 +132,7 @@
 
 Plugin Structure
 ================
-While there are no hard and fast rules for the structure a plugin, there are
+While there are no hard and fast rules for the structure of a plugin, there are
 basically no constraints on what the plugin looks like as long as the 2 steps
 above are done. However,  there are some recommended patterns to follow to make
 it easy for people to contribute and work with your plugin. For example, if you
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 9ee8858..ac03cdc 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -439,7 +439,7 @@
             # is already detached.
             pass
 
-    def attach_volume(self, server, volume, device=None, check_reserved=False):
+    def attach_volume(self, server, volume, device=None):
         """Attaches volume to server and waits for 'in-use' volume status.
 
         The volume will be detached when the test tears down.
@@ -448,15 +448,10 @@
         :param volume: The volume to attach.
         :param device: Optional mountpoint for the attached volume. Note that
             this is not guaranteed for all hypervisors and is not recommended.
-        :param check_reserved: Consider a status of reserved as valid for
-            completion. This is to handle new Cinder attach where we more
-            accurately use 'reserved' for things like attaching to a shelved
-            server.
         """
         attach_kwargs = dict(volumeId=volume['id'])
         if device:
             attach_kwargs['device'] = device
-
         attachment = self.servers_client.attach_volume(
             server['id'], **attach_kwargs)['volumeAttachment']
         # On teardown detach the volume and wait for it to be available. This
@@ -467,11 +462,8 @@
         # Ignore 404s on detach in case the server is deleted or the volume
         # is already detached.
         self.addCleanup(self._detach_volume, server, volume)
-        statuses = ['in-use']
-        if check_reserved:
-            statuses.append('reserved')
         waiters.wait_for_volume_resource_status(self.volumes_client,
-                                                volume['id'], statuses)
+                                                volume['id'], 'in-use')
         return attachment
 
 
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 9bef80f..297e8a8 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -223,8 +223,7 @@
         num_vol = self._count_volumes(server, validation_resources)
         self._shelve_server(server, validation_resources)
         attachment = self.attach_volume(server, volume,
-                                        device=('/dev/%s' % self.device),
-                                        check_reserved=True)
+                                        device=('/dev/%s' % self.device))
 
         # Unshelve the instance and check that attached volume exists
         self._unshelve_server_and_check_volumes(
@@ -250,8 +249,7 @@
         self._shelve_server(server, validation_resources)
 
         # Attach and then detach the volume
-        self.attach_volume(server, volume, device=('/dev/%s' % self.device),
-                           check_reserved=True)
+        self.attach_volume(server, volume, device=('/dev/%s' % self.device))
         self.servers_client.detach_volume(server['id'], volume['id'])
         waiters.wait_for_volume_resource_status(self.volumes_client,
                                                 volume['id'], 'available')
diff --git a/tempest/api/identity/admin/v3/test_endpoints_negative.py b/tempest/api/identity/admin/v3/test_endpoints_negative.py
index 70dd7b5..d54e222 100644
--- a/tempest/api/identity/admin/v3/test_endpoints_negative.py
+++ b/tempest/api/identity/admin/v3/test_endpoints_negative.py
@@ -30,7 +30,6 @@
     @classmethod
     def resource_setup(cls):
         super(EndpointsNegativeTestJSON, cls).resource_setup()
-        cls.service_ids = list()
         s_name = data_utils.rand_name('service')
         s_type = data_utils.rand_name('type')
         s_description = data_utils.rand_name('description')
@@ -38,14 +37,10 @@
             cls.services_client.create_service(name=s_name, type=s_type,
                                                description=s_description)
             ['service'])
-        cls.service_id = service_data['id']
-        cls.service_ids.append(cls.service_id)
+        cls.addClassResourceCleanup(cls.services_client.delete_service,
+                                    service_data['id'])
 
-    @classmethod
-    def resource_cleanup(cls):
-        for s in cls.service_ids:
-            cls.services_client.delete_service(s)
-        super(EndpointsNegativeTestJSON, cls).resource_cleanup()
+        cls.service_id = service_data['id']
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('ac6c137e-4d3d-448f-8c83-4f13d0942651')
diff --git a/tempest/api/identity/admin/v3/test_list_users.py b/tempest/api/identity/admin/v3/test_list_users.py
index 88cd8be..c69e4c8 100644
--- a/tempest/api/identity/admin/v3/test_list_users.py
+++ b/tempest/api/identity/admin/v3/test_list_users.py
@@ -47,21 +47,18 @@
         cls.domain_enabled_user = cls.users_client.create_user(
             name=u1_name, password=alt_password,
             email=cls.alt_email, domain_id=cls.domain['id'])['user']
+        cls.addClassResourceCleanup(cls.users_client.delete_user,
+                                    cls.domain_enabled_user['id'])
         cls.users.append(cls.domain_enabled_user)
         # Create default not enabled user
         u2_name = data_utils.rand_name('test_user')
         cls.non_domain_enabled_user = cls.users_client.create_user(
             name=u2_name, password=alt_password,
             email=cls.alt_email, enabled=False)['user']
+        cls.addClassResourceCleanup(cls.users_client.delete_user,
+                                    cls.non_domain_enabled_user['id'])
         cls.users.append(cls.non_domain_enabled_user)
 
-    @classmethod
-    def resource_cleanup(cls):
-        # Cleanup the users created during setup
-        for user in cls.users:
-            cls.users_client.delete_user(user['id'])
-        super(UsersV3TestJSON, cls).resource_cleanup()
-
     @decorators.idempotent_id('08f9aabb-dcfe-41d0-8172-82b5fa0bd73d')
     def test_list_user_domains(self):
         # List users with domain
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
index d00e408..f22a528 100644
--- a/tempest/api/identity/admin/v3/test_regions.py
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -34,14 +34,10 @@
             r_description = data_utils.rand_name('description')
             region = cls.client.create_region(
                 description=r_description)['region']
+            cls.addClassResourceCleanup(
+                cls.client.delete_region, region['id'])
             cls.setup_regions.append(region)
 
-    @classmethod
-    def resource_cleanup(cls):
-        for r in cls.setup_regions:
-            cls.client.delete_region(r['id'])
-        super(RegionsTestJSON, cls).resource_cleanup()
-
     @decorators.idempotent_id('56186092-82e4-43f2-b954-91013218ba42')
     def test_create_update_get_delete_region(self):
         # Create region
diff --git a/tempest/api/identity/admin/v3/test_roles.py b/tempest/api/identity/admin/v3/test_roles.py
index e7b005c..7cd41e9 100644
--- a/tempest/api/identity/admin/v3/test_roles.py
+++ b/tempest/api/identity/admin/v3/test_roles.py
@@ -32,6 +32,8 @@
         for _ in range(3):
             role_name = data_utils.rand_name(name='role')
             role = cls.roles_client.create_role(name=role_name)['role']
+            cls.addClassResourceCleanup(cls.roles_client.delete_role,
+                                        role['id'])
             cls.roles.append(role)
         u_name = data_utils.rand_name('user')
         u_desc = '%s description' % u_name
@@ -42,25 +44,23 @@
             data_utils.rand_name('project'),
             description=data_utils.rand_name('project-desc'),
             domain_id=cls.domain['id'])['project']
+        cls.addClassResourceCleanup(cls.projects_client.delete_project,
+                                    cls.project['id'])
         cls.group_body = cls.groups_client.create_group(
             name=data_utils.rand_name('Group'), project_id=cls.project['id'],
             domain_id=cls.domain['id'])['group']
+        cls.addClassResourceCleanup(cls.groups_client.delete_group,
+                                    cls.group_body['id'])
         cls.user_body = cls.users_client.create_user(
             name=u_name, description=u_desc, password=cls.u_password,
             email=u_email, project_id=cls.project['id'],
             domain_id=cls.domain['id'])['user']
+        cls.addClassResourceCleanup(cls.users_client.delete_user,
+                                    cls.user_body['id'])
         cls.role = cls.roles_client.create_role(
             name=data_utils.rand_name('Role'))['role']
-
-    @classmethod
-    def resource_cleanup(cls):
-        cls.roles_client.delete_role(cls.role['id'])
-        cls.groups_client.delete_group(cls.group_body['id'])
-        cls.users_client.delete_user(cls.user_body['id'])
-        cls.projects_client.delete_project(cls.project['id'])
-        for role in cls.roles:
-            cls.roles_client.delete_role(role['id'])
-        super(RolesV3TestJSON, cls).resource_cleanup()
+        cls.addClassResourceCleanup(cls.roles_client.delete_role,
+                                    cls.role['id'])
 
     @decorators.attr(type='smoke')
     @decorators.idempotent_id('18afc6c0-46cf-4911-824e-9989cc056c3a')
diff --git a/tempest/common/compute.py b/tempest/common/compute.py
index 86fe3f5..f0fceb3 100644
--- a/tempest/common/compute.py
+++ b/tempest/common/compute.py
@@ -289,13 +289,21 @@
 
 def create_websocket(url):
     url = urlparse.urlparse(url)
-    if url.scheme == 'https':
-        client_socket = ssl.wrap_socket(socket.socket(socket.AF_INET,
-                                                      socket.SOCK_STREAM))
+    for res in socket.getaddrinfo(url.hostname, url.port,
+                                  socket.AF_UNSPEC, socket.SOCK_STREAM):
+        af, socktype, proto, _, sa = res
+        client_socket = socket.socket(af, socktype, proto)
+        if url.scheme == 'https':
+            client_socket = ssl.wrap_socket(client_socket)
+        client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        try:
+            client_socket.connect(sa)
+        except socket.error:
+            client_socket.close()
+            continue
+        break
     else:
-        client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-    client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-    client_socket.connect((url.hostname, url.port))
+        raise socket.error('WebSocket creation failed')
     # Turn the Socket into a WebSocket to do the communication
     return _WebSocket(client_socket, url)
 
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index 10afee0..08e2a12 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -179,15 +179,13 @@
     raise lib_exc.TimeoutException(message)
 
 
-def wait_for_volume_resource_status(client, resource_id, statuses):
-    """Waits for a volume resource to reach any of the specified statuses.
+def wait_for_volume_resource_status(client, resource_id, status):
+    """Waits for a volume resource to reach a given status.
 
     This function is a common function for volume, snapshot and backup
     resources. The function extracts the name of the desired resource from
     the client class name of the resource.
     """
-    if not isinstance(statuses, list):
-        statuses = [statuses]
     resource_name = re.findall(
         r'(volume|group-snapshot|snapshot|backup|group)',
         client.resource_type)[-1].replace('-', '_')
@@ -195,11 +193,11 @@
     resource_status = show_resource(resource_id)[resource_name]['status']
     start = int(time.time())
 
-    while resource_status not in statuses:
+    while resource_status != status:
         time.sleep(client.build_interval)
         resource_status = show_resource(resource_id)[
             '{}'.format(resource_name)]['status']
-        if resource_status == 'error' and resource_status not in statuses:
+        if resource_status == 'error' and resource_status != status:
             raise exceptions.VolumeResourceBuildErrorException(
                 resource_name=resource_name, resource_id=resource_id)
         if resource_name == 'volume' and resource_status == 'error_restoring':
@@ -208,11 +206,11 @@
         if int(time.time()) - start >= client.build_timeout:
             message = ('%s %s failed to reach %s status (current %s) '
                        'within the required time (%s s).' %
-                       (resource_name, resource_id, statuses, resource_status,
+                       (resource_name, resource_id, status, resource_status,
                         client.build_timeout))
             raise lib_exc.TimeoutException(message)
     LOG.info('%s %s reached %s after waiting for %f seconds',
-             resource_name, resource_id, statuses, time.time() - start)
+             resource_name, resource_id, status, time.time() - start)
 
 
 def wait_for_volume_retype(client, volume_id, new_volume_type):
diff --git a/tempest/config.py b/tempest/config.py
index b14d4fd..5eee86d 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -312,9 +312,9 @@
                default=0,
                help='Time in seconds before a shelved instance is eligible '
                     'for removing from a host.  -1 never offload, 0 offload '
-                    'when shelved. This time should be the same as the time '
-                    'of nova.conf, and some tests will run for as long as the '
-                    'time.'),
+                    'when shelved. This configuration value should be same as '
+                    '[nova.DEFAULT]->shelved_offload_time in nova.conf, and '
+                    'some tests will run for as long as the time.'),
     cfg.IntOpt('min_compute_nodes',
                default=1,
                help=('The minimum number of compute nodes expected. This will '
diff --git a/tools/tox_install.sh b/tools/tox_install.sh
deleted file mode 100755
index 43468e4..0000000
--- a/tools/tox_install.sh
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/usr/bin/env bash
-
-# Client constraint file contains this client version pin that is in conflict
-# with installing the client from source. We should remove the version pin in
-# the constraints file before applying it for from-source installation.
-
-CONSTRAINTS_FILE=$1
-shift 1
-
-set -e
-
-# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
-# published to logs.openstack.org for easy debugging.
-localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
-
-if [[ $CONSTRAINTS_FILE != http* ]]; then
-    CONSTRAINTS_FILE=file://$CONSTRAINTS_FILE
-fi
-# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
-curl $CONSTRAINTS_FILE --insecure --progress-bar --output $localfile
-
-pip install -c$localfile openstack-requirements
-
-# This is the main purpose of the script: Allow local installation of
-# the current repo. It is listed in constraints file and thus any
-# install will be constrained and we need to unconstrain it.
-edit-constraints $localfile -- $CLIENT_NAME
-
-pip install -c$localfile -U $*
-exit $?
diff --git a/tox.ini b/tox.ini
index db5976f..e7ea1e2 100644
--- a/tox.ini
+++ b/tox.ini
@@ -8,9 +8,8 @@
 setenv =
     VIRTUAL_ENV={envdir}
     OS_TEST_PATH=./tempest/test_discover
-    BRANCH_NAME=master
-    CLIENT_NAME=tempest
 deps =
+    -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
     -r{toxinidir}/requirements.txt
 
 [testenv]
@@ -18,14 +17,12 @@
     VIRTUAL_ENV={envdir}
     OS_LOG_CAPTURE=1
     PYTHONWARNINGS=default::DeprecationWarning
-    BRANCH_NAME=master
-    CLIENT_NAME=tempest
 passenv = OS_STDOUT_CAPTURE OS_STDERR_CAPTURE OS_TEST_TIMEOUT OS_TEST_LOCK_PATH TEMPEST_CONFIG TEMPEST_CONFIG_DIR http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY ZUUL_CACHE_DIR REQUIREMENTS_PIP_LOCATION GENERATE_TEMPEST_PLUGIN_LIST
 usedevelop = True
-install_command =
-    {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
+install_command = pip install {opts} {packages}
 whitelist_externals = *
 deps =
+    -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt}
     -r{toxinidir}/requirements.txt
     -r{toxinidir}/test-requirements.txt
 commands =