Merge "Remove LOG in misc.py"
diff --git a/.zuul.yaml b/.zuul.yaml
index 8ff151f..9753656 100644
--- a/.zuul.yaml
+++ b/.zuul.yaml
@@ -194,12 +194,8 @@
         c-bak: false
 
 - job:
-    name: tempest-multinode-full
+    name: tempest-multinode-full-base
     parent: devstack-tempest
-    nodeset: openstack-two-node-bionic
-    # Until the devstack changes are backported, only run this on master
-    branches:
-      - master
     description: |
       Base multinode integration test with Neutron networking and py27.
       Former names for this job were:
@@ -225,6 +221,26 @@
           USE_BLOCK_MIGRATION_FOR_LIVE_MIGRATION: true
 
 - job:
+    name: tempest-multinode-full
+    parent: tempest-multinode-full-base
+    nodeset: openstack-two-node-bionic
+    # This job runs on Bionic from stable/stein on.
+    branches: ^(?!stable/(ocata|pike|queens|rocky)).*$
+
+- job:
+    name: tempest-multinode-full
+    parent: tempest-multinode-full-base
+    nodeset: openstack-two-node-xenial
+    # This job runs on Xenial and this is for stable/pike, stable/queens
+    # and stable/rocky. This job is prepared to make sure all stable branches
+    # before stable/stein will keep running on xenial. This job can be
+    # removed once stable/rocky is EOL.
+    branches:
+      - stable/pike
+      - stable/queens
+      - stable/rocky
+
+- job:
     name: tempest-multinode-full-py3
     parent: tempest-multinode-full
     vars:
@@ -263,8 +279,6 @@
 - job:
     name: tempest-slow
     parent: tempest-multinode-full
-    branches:
-      - master
     description: |
       This multinode integration job will run all the tests tagged as slow.
       It enables the lvm multibackend setup to cover few scenario tests.
@@ -280,6 +294,13 @@
         CINDER_ENABLED_BACKENDS: lvm:lvmdriver-1,lvm:lvmdriver-2
         ENABLE_VOLUME_MULTIATTACH: true
       tempest_concurrency: 2
+    group-vars:
+      # NOTE(mriedem): The ENABLE_VOLUME_MULTIATTACH variable is used on both
+      # the controller and subnode prior to Rocky so we have to make sure the
+      # variable is set in both locations.
+      subnode:
+        devstack_localrc:
+          ENABLE_VOLUME_MULTIATTACH: true
 
 - job:
     name: tempest-slow-py3
@@ -405,10 +426,12 @@
       - git.openstack.org/openstack/sahara-tests
       - git.openstack.org/openstack/senlin
       - git.openstack.org/openstack/senlin-tempest-plugin
+      - git.openstack.org/openstack/solum-tempest-plugin
       - git.openstack.org/openstack/tap-as-a-service
       - git.openstack.org/openstack/telemetry-tempest-plugin
       - git.openstack.org/openstack/tempest-horizon
       - git.openstack.org/openstack/tobiko
+      - git.openstack.org/openstack/trio2o
       - git.openstack.org/openstack/tripleo-common-tempest-plugin
       - git.openstack.org/openstack/trove-tempest-plugin
       - git.openstack.org/openstack/valet
@@ -478,6 +501,7 @@
       - openstack-python-jobs
       - openstack-python35-jobs
       - openstack-python36-jobs
+      - openstack-python37-jobs
       - publish-openstack-docs-pti
       - release-notes-jobs-python3
     check:
diff --git a/doc/source/microversion_testing.rst b/doc/source/microversion_testing.rst
index 983fa24..4b1c145 100644
--- a/doc/source/microversion_testing.rst
+++ b/doc/source/microversion_testing.rst
@@ -338,8 +338,8 @@
 
   .. _2.26: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id23
 
-* `2.28`_

-

+  * `2.28`_
+
   .. _2.28: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html#id25
 
   * `2.32`_
diff --git a/doc/source/test_removal.rst b/doc/source/test_removal.rst
index e249bdd..ff4fa09 100644
--- a/doc/source/test_removal.rst
+++ b/doc/source/test_removal.rst
@@ -128,8 +128,9 @@
 people to respond to removal proposals please add things to the agenda by the
 Monday before the meeting.
 
-The other option is to raise the removal on the openstack-dev mailing list.
-(for example see: http://lists.openstack.org/pipermail/openstack-dev/2016-February/086218.html )
+The other option is to raise the removal on the openstack-discuss mailing list.
+(for example see: http://lists.openstack.org/pipermail/openstack-dev/2016-February/086218.html
+or http://lists.openstack.org/pipermail/openstack-discuss/2019-March/003574.html )
 This will raise the issue to the wider community and attract at least the same
 (most likely more) attention than discussing it during the irc meeting. The
 only downside is that it might take more time to get a response, given the
diff --git a/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml b/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml
new file mode 100644
index 0000000..c707f14
--- /dev/null
+++ b/releasenotes/notes/conditional-attr-a8564ec5a70ec840.yaml
@@ -0,0 +1,6 @@
+---
+features:
+  - |
+    The ``tempest.lib.decorators.attr`` decorator now supports a ``condition``
+    kwarg which can be used to conditionally apply the attr to the test
+    function if the condition evaluates to True.
diff --git a/tempest/api/compute/servers/test_device_tagging.py b/tempest/api/compute/servers/test_device_tagging.py
index d40f937..e817587 100644
--- a/tempest/api/compute/servers/test_device_tagging.py
+++ b/tempest/api/compute/servers/test_device_tagging.py
@@ -12,9 +12,8 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import json
-
 from oslo_log import log as logging
+from oslo_serialization import jsonutils as json
 
 from tempest.api.compute import base
 from tempest.common import utils
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index f7b5b4b..8bb4eaa 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -65,6 +65,8 @@
 class AttachVolumeTestJSON(BaseAttachVolumeTest):
 
     @decorators.idempotent_id('52e9045a-e90d-4c0d-9087-79d657faffff')
+    # This test is conditionally marked slow if SSH validation is enabled.
+    @decorators.attr(type='slow', condition=CONF.validation.run_validation)
     def test_attach_detach_volume(self):
         # Stop and Start a server with an attached volume, ensuring that
         # the volume remains attached.
diff --git a/tempest/api/identity/admin/v3/test_default_project_id.py b/tempest/api/identity/admin/v3/test_default_project_id.py
index a79cbc3..73fddb7 100644
--- a/tempest/api/identity/admin/v3/test_default_project_id.py
+++ b/tempest/api/identity/admin/v3/test_default_project_id.py
@@ -21,7 +21,7 @@
 CONF = config.CONF
 
 
-class TestDefaultProjectId (base.BaseIdentityV3AdminTest):
+class TestDefaultProjectId(base.BaseIdentityV3AdminTest):
 
     @classmethod
     def setup_credentials(cls):
@@ -57,9 +57,10 @@
         # create a user in the domain, with the previous project as his
         # default project
         user_name = data_utils.rand_name('user')
+        user_pass = data_utils.rand_password()
         user_body = self.users_client.create_user(
             name=user_name,
-            password=user_name,
+            password=user_pass,
             domain_id=dom_id,
             default_project_id=proj_id)['user']
         user_id = user_body['id']
@@ -78,7 +79,7 @@
 
         # create a new client with user's credentials (NOTE: unscoped token!)
         creds = auth.KeystoneV3Credentials(username=user_name,
-                                           password=user_name,
+                                           password=user_pass,
                                            user_domain_name=dom_name)
         auth_provider = clients.get_auth_provider(creds)
         creds = auth_provider.fill_credentials()
diff --git a/tempest/api/identity/admin/v3/test_domains.py b/tempest/api/identity/admin/v3/test_domains.py
index 72b6be4..07175f4 100644
--- a/tempest/api/identity/admin/v3/test_domains.py
+++ b/tempest/api/identity/admin/v3/test_domains.py
@@ -153,18 +153,3 @@
         expected_data = {'name': d_name, 'enabled': True}
         self.assertEqual('', domain['description'])
         self.assertDictContainsSubset(expected_data, domain)
-
-
-class DefaultDomainTestJSON(base.BaseIdentityV3AdminTest):
-
-    @classmethod
-    def resource_setup(cls):
-        cls.domain_id = CONF.identity.default_domain_id
-        super(DefaultDomainTestJSON, cls).resource_setup()
-
-    @decorators.attr(type='smoke')
-    @decorators.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
-    def test_default_domain_exists(self):
-        domain = self.domains_client.show_domain(self.domain_id)['domain']
-
-        self.assertTrue(domain['enabled'])
diff --git a/tempest/api/identity/admin/v3/test_inherits.py b/tempest/api/identity/admin/v3/test_inherits.py
index 68c0225..acc5a8c 100644
--- a/tempest/api/identity/admin/v3/test_inherits.py
+++ b/tempest/api/identity/admin/v3/test_inherits.py
@@ -30,7 +30,7 @@
         u_name = data_utils.rand_name('user-')
         u_desc = '%s description' % u_name
         u_email = '%s@testmail.tm' % u_name
-        u_password = data_utils.rand_name('pass-')
+        u_password = data_utils.rand_password()
         cls.domain = cls.create_domain()
         cls.project = cls.projects_client.create_project(
             data_utils.rand_name('project-'),
diff --git a/tempest/api/identity/v3/test_domains.py b/tempest/api/identity/v3/test_domains.py
new file mode 100644
index 0000000..9f132dd
--- /dev/null
+++ b/tempest/api/identity/v3/test_domains.py
@@ -0,0 +1,39 @@
+# Copyright 2013 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.identity import base
+from tempest import config
+from tempest.lib import decorators
+
+CONF = config.CONF
+
+
+class DefaultDomainTestJSON(base.BaseIdentityV3Test):
+
+    @classmethod
+    def setup_clients(cls):
+        super(DefaultDomainTestJSON, cls).setup_clients()
+        cls.domains_client = cls.os_primary.domains_client
+
+    @classmethod
+    def resource_setup(cls):
+        super(DefaultDomainTestJSON, cls).resource_setup()
+        cls.domain_id = CONF.identity.default_domain_id
+
+    @decorators.attr(type='smoke')
+    @decorators.idempotent_id('17a5de24-e6a0-4e4a-a9ee-d85b6e5612b5')
+    def test_default_domain_exists(self):
+        domain = self.domains_client.show_domain(self.domain_id)['domain']
+        self.assertTrue(domain['enabled'])
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
deleted file mode 100644
index 033bf55..0000000
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ /dev/null
@@ -1,83 +0,0 @@
-# Copyright 2013 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.
-
-from tempest.api.network import base
-from tempest.common import utils
-from tempest.lib import decorators
-from tempest.lib import exceptions
-
-AGENT_TYPE = 'L3 agent'
-AGENT_MODES = (
-    'legacy',
-    'dvr_snat'
-)
-
-
-class L3AgentSchedulerTestJSON(base.BaseAdminNetworkTest):
-    """Tests the following operations in the Neutron API:
-
-        List routers that the given L3 agent is hosting.
-        List L3 agents hosting the given router.
-        Add and Remove Router to L3 agent
-
-    v2.0 of the Neutron API is assumed.
-
-    The l3_agent_scheduler extension is required for these tests.
-    """
-
-    @classmethod
-    def skip_checks(cls):
-        super(L3AgentSchedulerTestJSON, cls).skip_checks()
-        if not utils.is_extension_enabled('l3_agent_scheduler', 'network'):
-            msg = "L3 Agent Scheduler Extension not enabled."
-            raise cls.skipException(msg)
-
-    @classmethod
-    def resource_setup(cls):
-        super(L3AgentSchedulerTestJSON, cls).resource_setup()
-        agents = cls.admin_agents_client.list_agents(
-            agent_type=AGENT_TYPE)['agents']
-        for agent in agents:
-            if (agent['configurations']['agent_mode'] in AGENT_MODES and
-                agent['alive']):
-                cls.agent = agent
-                break
-        else:
-            msg = "L3 Agent Scheduler enabled in conf, but L3 Agent not found"
-            raise exceptions.InvalidConfiguration(msg)
-        cls.router = cls.create_router()
-
-    @decorators.idempotent_id('b7ce6e89-e837-4ded-9b78-9ed3c9c6a45a')
-    def test_list_routers_on_l3_agent(self):
-        self.admin_agents_client.list_routers_on_l3_agent(self.agent['id'])
-
-    @decorators.idempotent_id('9464e5e7-8625-49c3-8fd1-89c52be59d66')
-    def test_add_list_remove_router_on_l3_agent(self):
-        l3_agent_ids = list()
-        self.admin_agents_client.create_router_on_l3_agent(
-            self.agent['id'],
-            router_id=self.router['id'])
-        body = (
-            self.admin_routers_client.list_l3_agents_hosting_router(
-                self.router['id']))
-        for agent in body['agents']:
-            l3_agent_ids.append(agent['id'])
-            self.assertIn('agent_type', agent)
-            self.assertEqual('L3 agent', agent['agent_type'])
-        self.assertIn(self.agent['id'], l3_agent_ids)
-        body = self.admin_agents_client.delete_router_from_l3_agent(
-            self.agent['id'],
-            self.router['id'])
-        # NOTE(afazekas): The deletion not asserted, because neutron
-        # is not forbidden to reschedule the router to the same agent
diff --git a/tempest/api/network/admin/test_negative_quotas.py b/tempest/api/network/admin/test_negative_quotas.py
index a075b51..9d1e2a7 100644
--- a/tempest/api/network/admin/test_negative_quotas.py
+++ b/tempest/api/network/admin/test_negative_quotas.py
@@ -30,7 +30,7 @@
     It is also assumed that the per-project quota extension API is configured
     in /etc/neutron/neutron.conf as follows:
 
-        quota_driver = neutron.db.quota_db.DbQuotaDriver
+        quota_driver = neutron.db.quota.driver.DbQuotaDriver
     """
 
     @classmethod
diff --git a/tempest/api/network/admin/test_quotas.py b/tempest/api/network/admin/test_quotas.py
index b1e4a58..ef5ebb6 100644
--- a/tempest/api/network/admin/test_quotas.py
+++ b/tempest/api/network/admin/test_quotas.py
@@ -35,7 +35,7 @@
     It is also assumed that the per-project quota extension API is configured
     in /etc/neutron/neutron.conf as follows:
 
-        quota_driver = neutron.db.quota_db.DbQuotaDriver
+        quota_driver = neutron.db.quota.driver.DbQuotaDriver
     """
 
     @classmethod
diff --git a/tempest/cmd/cleanup_service.py b/tempest/cmd/cleanup_service.py
index 6b6a59b..3aed4e8 100644
--- a/tempest/cmd/cleanup_service.py
+++ b/tempest/cmd/cleanup_service.py
@@ -362,6 +362,7 @@
         self.metering_label_rules_client = manager.metering_label_rules_client
         self.security_groups_client = manager.security_groups_client
         self.routers_client = manager.routers_client
+        self.subnetpools_client = manager.subnetpools_client
 
     def _filter_by_conf_networks(self, item_list):
         if not item_list or not all(('network_id' in i for i in item_list)):
@@ -673,6 +674,41 @@
             self.data['subnets'][subnet['id']] = subnet['name']
 
 
+class NetworkSubnetPoolsService(BaseNetworkService):
+
+    def list(self):
+        client = self.subnetpools_client
+        pools = client.list_subnetpools(**self.tenant_filter)['subnetpools']
+        if not self.is_save_state:
+            # recreate list removing saved subnet pools
+            pools = [pool for pool in pools if pool['id']
+                     not in self.saved_state_json['subnetpools'].keys()]
+        if self.is_preserve:
+            pools = [pool for pool in pools if pool['project_id']
+                     not in CONF_PROJECTS]
+        LOG.debug("List count, %s Subnet Pools", len(pools))
+        return pools
+
+    def delete(self):
+        client = self.subnetpools_client
+        pools = self.list()
+        for pool in pools:
+            try:
+                client.delete_subnetpool(pool['id'])
+            except Exception:
+                LOG.exception("Delete Subnet Pool exception.")
+
+    def dry_run(self):
+        pools = self.list()
+        self.data['subnetpools'] = pools
+
+    def save_state(self):
+        pools = self.list()
+        self.data['subnetpools'] = {}
+        for pool in pools:
+            self.data['subnetpools'][pool['id']] = pool['name']
+
+
 # begin global services
 class FlavorService(BaseService):
     def __init__(self, manager, **kwargs):
@@ -931,6 +967,7 @@
         project_services.append(NetworkSubnetService)
         project_services.append(NetworkService)
         project_services.append(NetworkSecGroupService)
+        project_services.append(NetworkSubnetPoolsService)
     if IS_CINDER:
         project_services.append(SnapshotService)
         project_services.append(VolumeService)
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 3e84b82..823ed11 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -252,6 +252,7 @@
                             default=False)
         # execution args
         parser.add_argument('--concurrency', '-w',
+                            type=int, default=0,
                             help="The number of workers to use, defaults to "
                                  "the number of cpus")
         parallel = parser.add_mutually_exclusive_group()
diff --git a/tempest/cmd/subunit_describe_calls.py b/tempest/cmd/subunit_describe_calls.py
index 8dcf575..081fa7a 100644
--- a/tempest/cmd/subunit_describe_calls.py
+++ b/tempest/cmd/subunit_describe_calls.py
@@ -78,11 +78,11 @@
 import argparse
 import collections
 import io
-import json
 import os
 import re
 import sys
 
+from oslo_serialization import jsonutils as json
 import subunit
 import testtools
 
diff --git a/tempest/lib/common/rest_client.py b/tempest/lib/common/rest_client.py
index e612bd1..3be441e 100644
--- a/tempest/lib/common/rest_client.py
+++ b/tempest/lib/common/rest_client.py
@@ -282,7 +282,7 @@
     def get(self, url, headers=None, extra_headers=False):
         """Send a HTTP GET request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the get request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
@@ -297,7 +297,7 @@
     def delete(self, url, headers=None, body=None, extra_headers=False):
         """Send a HTTP DELETE request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the delete request to
         :param dict headers: The headers to use for the request
         :param dict body: the request body
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -313,7 +313,7 @@
     def patch(self, url, body, headers=None, extra_headers=False):
         """Send a HTTP PATCH request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the patch request to
         :param dict body: the request body
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -329,7 +329,7 @@
     def put(self, url, body, headers=None, extra_headers=False, chunked=False):
         """Send a HTTP PUT request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the put request to
         :param dict body: the request body
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
@@ -346,7 +346,7 @@
     def head(self, url, headers=None, extra_headers=False):
         """Send a HTTP HEAD request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the head request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
@@ -361,7 +361,7 @@
     def copy(self, url, headers=None, extra_headers=False):
         """Send a HTTP COPY request using keystone service catalog and auth
 
-        :param str url: the relative url to send the post request to
+        :param str url: the relative url to send the copy request to
         :param dict headers: The headers to use for the request
         :param bool extra_headers: Boolean value than indicates if the headers
                                    returned by the get_headers() method are to
diff --git a/tempest/lib/decorators.py b/tempest/lib/decorators.py
index b399aa0..4064401 100644
--- a/tempest/lib/decorators.py
+++ b/tempest/lib/decorators.py
@@ -136,10 +136,17 @@
 
     This decorator applies the testtools.testcase.attr if it is in the list of
     attributes to testtools we want to apply.
+
+    :param condition: Optional condition which if true will apply the attr. If
+        a condition is specified which is false the attr will not be applied to
+        the test function. If not specified, the attr is always applied.
     """
 
     def decorator(f):
-        if 'type' in kwargs and isinstance(kwargs['type'], str):
+        # Check to see if the attr should be conditional applied.
+        if 'condition' in kwargs and not kwargs.get('condition'):
+            return f
+        if 'type' in kwargs and isinstance(kwargs['type'], six.string_types):
             f = testtools.testcase.attr(kwargs['type'])(f)
         elif 'type' in kwargs and isinstance(kwargs['type'], list):
             for attr in kwargs['type']:
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index 4be2b29..37bcd04 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -119,6 +119,7 @@
             server, keypair, floating_ip)
 
     @decorators.idempotent_id('7b6860c2-afa3-4846-9522-adeb38dfbe08')
+    @decorators.attr(type='slow')
     @utils.services('compute', 'network')
     def test_server_connectivity_reboot(self):
         keypair = self.create_keypair()
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 1671216..02bc692 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -13,7 +13,7 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import json
+from oslo_serialization import jsonutils as json
 
 from tempest.common import utils
 from tempest.common import waiters
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index 3bc5b0b..e5730e7c 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -11,7 +11,7 @@
 #    under the License.
 
 from oslo_log import log as logging
-from oslo_serialization import jsonutils
+from oslo_serialization import jsonutils as json
 import testtools
 
 from tempest.common import utils
@@ -265,7 +265,7 @@
         bdms = image.get('block_device_mapping')
         if not bdms:
             bdms = image['properties']['block_device_mapping']
-        bdms = jsonutils.loads(bdms)
+        bdms = json.loads(bdms)
         snapshot_id = bdms[0]['snapshot_id']
         self._delete_snapshot(snapshot_id)
 
diff --git a/tempest/tests/cmd/test_cleanup_services.py b/tempest/tests/cmd/test_cleanup_services.py
index 59e5636..3262b1c 100644
--- a/tempest/tests/cmd/test_cleanup_services.py
+++ b/tempest/tests/cmd/test_cleanup_services.py
@@ -104,6 +104,7 @@
                                 cleanup_service.CONF_PROJECTS[0],
                             "ports": cleanup_service.CONF_PUB_NETWORK,
                             "routers": cleanup_service.CONF_PUB_ROUTER,
+                            "subnetpools": cleanup_service.CONF_PROJECTS[0],
                             }
 
     saved_state = {
@@ -139,7 +140,8 @@
         "metering_labels": {u'723b346ce866-4c7q': u'saved-label'},
         "ports": {u'aa74aa4v-741a': u'saved-port'},
         "security_groups": {u'7q844add-3697': u'saved-sec-group'},
-        "subnets": {u'55ttda4a-2584': u'saved-subnet'}
+        "subnets": {u'55ttda4a-2584': u'saved-subnet'},
+        "subnetpools": {u'8acf64c1-43fc': u'saved-subnet-pool'}
     }
     # Mocked methods
     get_method = 'tempest.lib.common.rest_client.RestClient.get'
@@ -1097,6 +1099,74 @@
         self._test_is_preserve_true([(self.get_method, self.response, 200)])
 
 
+class TestNetworkSubnetPoolsService(BaseCmdServiceTests):
+
+    service_class = 'NetworkSubnetPoolsService'
+    service_name = 'subnetpools'
+    response = {
+        "subnetpools": [
+            {
+                "min_prefixlen": "64",
+                "default_prefixlen": "64",
+                "id": "03f761e6-eee0-43fc-a921-8acf64c14988",
+                "max_prefixlen": "64",
+                "name": "my-subnet-pool-ipv6",
+                "is_default": False,
+                "project_id": "9fadcee8aa7c40cdb2114fff7d569c08",
+                "tenant_id": "9fadcee8aa7c40cdb2114fff7d569c08",
+                "prefixes": [
+                    "2001:db8:0:2::/64",
+                    "2001:db8::/63"
+                ],
+                "ip_version": 6,
+                "shared": False,
+                "description": "",
+                "created_at": "2016-03-08T20:19:41",
+                "updated_at": "2016-03-08T20:19:41",
+                "revision_number": 2,
+                "tags": ["tag1,tag2"]
+            },
+            {
+                "id": "8acf64c1-43fc",
+                "name": "saved-subnet-pool"
+            }
+        ]
+    }
+
+    def test_delete_fail(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, 'error', None),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock, fail=True)
+
+    def test_delete_pass(self):
+        delete_mock = [(self.get_method, self.response, 200),
+                       (self.delete_method, None, 204),
+                       (self.log_method, 'exception', None)]
+        self._test_delete(delete_mock)
+
+    def test_dry_run(self):
+        dry_mock = [(self.get_method, self.response, 200),
+                    (self.delete_method, "delete", None)]
+        self._test_dry_run_true(dry_mock)
+
+    def test_save_state(self):
+        self._test_saved_state_true([(self.get_method, self.response, 200)])
+
+    def test_preserve_list(self):
+        self.response['subnetpools'].append(
+            {
+                "min_prefixlen": "64",
+                "default_prefixlen": "64",
+                "id": "9acf64c1-43fc",
+                "name": "preserve-pool",
+                "project_id": cleanup_service.CONF_PROJECTS[0],
+                "created_at": "2016-03-08T20:19:41",
+                "updated_at": "2016-03-08T20:19:41"
+            })
+        self._test_is_preserve_true([(self.get_method, self.response, 200)])
+
+
 # begin global services
 class TestDomainService(BaseCmdServiceTests):
 
diff --git a/tempest/tests/lib/common/test_rest_client.py b/tempest/tests/lib/common/test_rest_client.py
index 4c0bb57..b861582 100644
--- a/tempest/tests/lib/common/test_rest_client.py
+++ b/tempest/tests/lib/common/test_rest_client.py
@@ -13,10 +13,10 @@
 #    under the License.
 
 import copy
-import json
 
 import fixtures
 import jsonschema
+from oslo_serialization import jsonutils as json
 import six
 
 from tempest.lib.common import http
diff --git a/tempest/tests/lib/services/identity/v2/test_token_client.py b/tempest/tests/lib/services/identity/v2/test_token_client.py
index dfce9b3..a592ada 100644
--- a/tempest/tests/lib/services/identity/v2/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v2/test_token_client.py
@@ -12,9 +12,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-import json
-
 import mock
+from oslo_serialization import jsonutils as json
 
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions
diff --git a/tempest/tests/lib/services/identity/v3/test_token_client.py b/tempest/tests/lib/services/identity/v3/test_token_client.py
index 38e8c4a..a9c58df 100644
--- a/tempest/tests/lib/services/identity/v3/test_token_client.py
+++ b/tempest/tests/lib/services/identity/v3/test_token_client.py
@@ -12,9 +12,8 @@
 # License for the specific language governing permissions and limitations
 # under the License.
 
-import json
-
 import mock
+from oslo_serialization import jsonutils as json
 
 from tempest.lib.common import rest_client
 from tempest.lib import exceptions
diff --git a/tempest/tests/lib/test_decorators.py b/tempest/tests/lib/test_decorators.py
index 0b1a599..3e6160e 100644
--- a/tempest/tests/lib/test_decorators.py
+++ b/tempest/tests/lib/test_decorators.py
@@ -32,9 +32,17 @@
         # By our decorators.attr decorator the attribute __testtools_attrs
         # will be set only for 'type' argument, so we test it first.
         if 'type' in decorator_args:
-            # this is what testtools sets
-            self.assertEqual(getattr(foo, '__testtools_attrs'),
-                             set(expected_attrs))
+            if 'condition' in decorator_args:
+                if decorator_args['condition']:
+                    # The expected attrs should be in the function.
+                    self.assertEqual(set(expected_attrs),
+                                     getattr(foo, '__testtools_attrs'))
+                else:
+                    # The expected attrs should not be in the function.
+                    self.assertNotIn('__testtools_attrs', foo)
+            else:
+                self.assertEqual(set(expected_attrs),
+                                 getattr(foo, '__testtools_attrs'))
 
     def test_attr_without_type(self):
         self._test_attr_helper(expected_attrs='baz', bar='baz')
@@ -50,6 +58,13 @@
     def test_attr_decorator_with_duplicated_type(self):
         self._test_attr_helper(expected_attrs=['foo'], type=['foo', 'foo'])
 
+    def test_attr_decorator_condition_false(self):
+        self._test_attr_helper(None, type='slow', condition=False)
+
+    def test_attr_decorator_condition_true(self):
+        self._test_attr_helper(expected_attrs=['slow'], type='slow',
+                               condition=True)
+
 
 class TestSkipBecauseDecorator(base.TestCase):
     def _test_skip_because_helper(self, expected_to_skip=True,
diff --git a/tools/tempest-plugin-sanity.sh b/tools/tempest-plugin-sanity.sh
index 661329b..16e7b8c 100644
--- a/tools/tempest-plugin-sanity.sh
+++ b/tools/tempest-plugin-sanity.sh
@@ -18,20 +18,17 @@
 # This script is intended to check the sanity of tempest plugins against
 # tempest master.
 # What it does:
-# * Creates the virtualenv
-# * Install tempest
 # * Retrieve the project lists having tempest plugin if project name is
 #   given.
-# * For each project in a list, It does:
+# * For each project in a list, it does:
+#   * Create virtualenv and install tempest in it
 #   * Clone the Project
 #   * Install the Project and also installs dependencies from
 #     test-requirements.txt.
 #   * Create Tempest workspace
 #   * List tempest plugins
 #   * List tempest plugins tests
-#   * Uninstall the project and its dependencies
-#   * Again Install tempest
-#   * Again repeat the step from cloning project
+#   * Delete virtualenv and project repo
 #
 # If one of the step fails, The script will exit with failure.
 
@@ -46,8 +43,39 @@
 
 # retrieve a list of projects having tempest plugins
 PROJECT_LIST="$(python tools/generate-tempest-plugins-list.py)"
-# List of projects having tempest plugin stale or unmaintained from long time
-BLACKLIST="networking-plumgrid,trio2o"
+# List of projects having tempest plugin stale or unmaintained for a long time
+# (6 months or more)
+# TODO(masayukig): Some of these can be removed from BLACKLIST in the future.
+# airship-tempest-plugin: https://review.openstack.org/#/c/634387/
+# barbican-tempest-plugin: https://review.openstack.org/#/c/634631/
+# intel-nfv-ci-tests: https://review.openstack.org/#/c/634640/
+# networking-ansible: https://review.openstack.org/#/c/634647/
+# networking-generic-switch: https://review.openstack.org/#/c/634846/
+# networking-l2gw-tempest-plugin: https://review.openstack.org/#/c/635093/
+# networking-midonet: https://review.openstack.org/#/c/635096/
+# networking-plumgrid: https://review.openstack.org/#/c/635096/
+# networking-spp: https://review.openstack.org/#/c/635098/
+# neutron-dynamic-routing: https://review.openstack.org/#/c/637718/
+# neutron-vpnaas: https://review.openstack.org/#/c/637719/
+# nova-lxd: https://review.openstack.org/#/c/638334/
+# valet: https://review.openstack.org/#/c/638339/
+# vitrage-tempest-plugin: https://review.openstack.org/#/c/639003/
+BLACKLIST="
+airship-tempest-plugin
+barbican-tempest-plugin
+intel-nfv-ci-tests
+networking-ansible
+networking-generic-switch
+networking-l2gw-tempest-plugin
+networking-midonet
+networking-plumgrid
+networking-spp
+neutron-dynamic-routing
+neutron-vpnaas
+nova-lxd
+valet
+vitrage-tempest-plugin
+"
 
 # Function to clone project using zuul-cloner or from git
 function clone_project() {
@@ -63,14 +91,16 @@
     fi
 }
 
-# Create virtualenv to perform sanity operation
-SANITY_DIR=$(pwd)
-virtualenv "$SANITY_DIR"/.venv
-export TVENV="$SANITY_DIR/tools/with_venv.sh"
-cd "$SANITY_DIR"
+# function to create virtualenv to perform sanity operation
+function prepare_workspace() {
+    SANITY_DIR=$(pwd)
+    virtualenv --clear "$SANITY_DIR"/.venv
+    export TVENV="$SANITY_DIR/tools/with_venv.sh"
+    cd "$SANITY_DIR"
 
-# Install tempest in a venv
-"$TVENV" pip install .
+    # Install tempest with test dependencies in a venv
+    "$TVENV" pip install -e . -r test-requirements.txt
+}
 
 # Function to install project
 function install_project() {
@@ -83,30 +113,31 @@
 
 # Function to perform sanity checking on Tempest plugin
 function tempest_sanity() {
-    "$TVENV" tempest init "$SANITY_DIR"/tempest_sanity
-    cd "$SANITY_DIR"/tempest_sanity
-    "$TVENV" tempest list-plugins
+    "$TVENV" tempest init "$SANITY_DIR"/tempest_sanity && \
+    cd "$SANITY_DIR"/tempest_sanity && \
+    "$TVENV" tempest list-plugins && \
     "$TVENV" tempest run -l
+    retval=$?
     # Delete tempest workspace
+    # NOTE: Cleaning should be done even if an error occurs.
     "$TVENV" tempest workspace remove --name tempest_sanity --rmdir
     cd "$SANITY_DIR"
-}
-
-# Function to uninstall project
-function uninstall_project() {
-    "$TVENV" pip uninstall -y "$SANITY_DIR"/openstack/"$1"
-    # Check for *requirements.txt file in a project then uninstall it.
-    if [ -e "$SANITY_DIR"/openstack/"$1"/*requirements.txt ]; then
-        "$TVENV" pip uninstall -y -r "$SANITY_DIR"/openstack/"$1"/*requirements.txt
-    fi
+    # Remove the sanity workspace in case of remaining
+    rm -fr "$SANITY_DIR"/tempest_sanity
     # Remove the project directory after sanity run
     rm -fr "$SANITY_DIR"/openstack/"$1"
+
+    return $retval
 }
 
 # Function to run sanity check on each project
 function plugin_sanity_check() {
-    clone_project "$1"  &&  install_project "$1"  &&  tempest_sanity "$1" \
-    &&  uninstall_project "$1"  &&  "$TVENV" pip install .
+    prepare_workspace && \
+    clone_project "$1" && \
+    install_project "$1" && \
+    tempest_sanity "$1"
+
+    return $?
 }
 
 # Log status
@@ -117,11 +148,12 @@
     # Remove blacklisted tempest plugins
     if ! [[ `echo $BLACKLIST | grep -c $project ` -gt 0 ]]; then
         plugin_sanity_check $project && passed_plugin+=", $project" || \
-        failed_plugin+=", $project"
+        failed_plugin+="$project, " > $SANITY_DIR/$project.txt
     fi
 done
 
 # Check for failed status
 if [[ -n $failed_plugin ]]; then
+    echo "Failed Plugins: $failed_plugin"
     exit 1
 fi