Merge "Remove PYTHONHASHSEED=0 from tox pep8 job"
diff --git a/HACKING.rst b/HACKING.rst
index 29d5bf4..83d67a9 100644
--- a/HACKING.rst
+++ b/HACKING.rst
@@ -259,7 +259,7 @@
 docstrings for the workflow in each test methods can be used instead. A good
 example of this would be::
 
-    class TestVolumeBootPattern(manager.OfficialClientTest):
+    class TestVolumeBootPattern(manager.ScenarioTest):
     """
     This test case attempts to reproduce the following steps:
 
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index 6496176..6507ce1 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -126,6 +126,8 @@
 
     @classmethod
     def clear_servers(cls):
+        LOG.debug('Clearing servers: %s', ','.join(
+            server['id'] for server in cls.servers))
         for server in cls.servers:
             try:
                 cls.servers_client.delete_server(server['id'])
@@ -165,6 +167,7 @@
 
     @classmethod
     def clear_images(cls):
+        LOG.debug('Clearing images: %s', ','.join(cls.images))
         for image_id in cls.images:
             try:
                 cls.images_client.delete_image(image_id)
@@ -176,6 +179,8 @@
 
     @classmethod
     def clear_security_groups(cls):
+        LOG.debug('Clearing security groups: %s', ','.join(
+            str(sg['id']) for sg in cls.security_groups))
         for sg in cls.security_groups:
             try:
                 resp, body =\
@@ -190,6 +195,7 @@
 
     @classmethod
     def clear_server_groups(cls):
+        LOG.debug('Clearing server groups: %s', ','.join(cls.server_groups))
         for server_group_id in cls.server_groups:
             try:
                 cls.client.delete_server_group(server_group_id)
diff --git a/tempest/api/compute/servers/test_create_server.py b/tempest/api/compute/servers/test_create_server.py
index 25dc87d..bc452aa 100644
--- a/tempest/api/compute/servers/test_create_server.py
+++ b/tempest/api/compute/servers/test_create_server.py
@@ -132,17 +132,27 @@
         # preserved within the server.
         name_net1 = data_utils.rand_name(self.__class__.__name__)
         _, net1 = self.network_client.create_network(name=name_net1)
+        self.addCleanup(self.network_client.delete_network,
+                        net1['network']['id'])
+
         name_net2 = data_utils.rand_name(self.__class__.__name__)
         _, net2 = self.network_client.create_network(name=name_net2)
+        self.addCleanup(self.network_client.delete_network,
+                        net2['network']['id'])
 
         _, subnet1 = self.network_client.create_subnet(
             network_id=net1['network']['id'],
             cidr='19.80.0.0/24',
             ip_version=4)
+        self.addCleanup(self.network_client.delete_subnet,
+                        subnet1['subnet']['id'])
+
         _, subnet2 = self.network_client.create_subnet(
             network_id=net2['network']['id'],
             cidr='19.86.0.0/24',
             ip_version=4)
+        self.addCleanup(self.network_client.delete_subnet,
+                        subnet2['subnet']['id'])
 
         networks = [{'uuid': net1['network']['id']},
                     {'uuid': net2['network']['id']}]
@@ -150,6 +160,18 @@
         _, server_multi_nics = self.create_test_server(
             networks=networks, wait_until='ACTIVE')
 
+        # Cleanup server; this is needed in the test case because with the LIFO
+        # nature of the cleanups, if we don't delete the server first, the port
+        # will still be part of the subnet and we'll get a 409 from Neutron
+        # when trying to delete the subnet. The tear down in the base class
+        # will try to delete the server and get a 404 but it's ignored so
+        # we're OK.
+        def cleanup_server():
+            self.client.delete_server(server_multi_nics['id'])
+            self.client.wait_for_server_termination(server_multi_nics['id'])
+
+        self.addCleanup(cleanup_server)
+
         _, addresses = self.client.list_addresses(server_multi_nics['id'])
 
         expected_addr = ['19.80.0.2', '19.86.0.2']
diff --git a/tempest/api/network/test_ports.py b/tempest/api/network/test_ports.py
index cdd3a29..d6db64d 100644
--- a/tempest/api/network/test_ports.py
+++ b/tempest/api/network/test_ports.py
@@ -151,6 +151,41 @@
         port = self.update_port(port, fixed_ips=fixed_ip_1)
         self.assertEqual(1, len(port['fixed_ips']))
 
+    def _update_port_with_security_groups(self, security_groups_names):
+        post_body = {"network_id": self.network['id']}
+        self.create_subnet(self.network)
+        security_groups_list = list()
+        for name in security_groups_names:
+            _, group_create_body = self.client.create_security_group(
+                name=name)
+            self.addCleanup(self.client.delete_security_group,
+                            group_create_body['security_group']['id'])
+            security_groups_list.append(group_create_body['security_group']
+                                        ['id'])
+        # Create a port
+        _, body = self.client.create_port(**post_body)
+        self.addCleanup(self.client.delete_port, body['port']['id'])
+        port = body['port']
+        # Update the port with security groups
+        update_body = {"security_groups": security_groups_list}
+        _, body = self.client.update_port(
+            port['id'], **update_body)
+        # Verify the security groups updated to port
+        port_show = body['port']
+        for security_group in security_groups_list:
+            self.assertIn(security_group, port_show['security_groups'])
+
+    @test.attr(type='smoke')
+    def test_update_port_with_security_group(self):
+        self._update_port_with_security_groups(
+            [data_utils.rand_name('secgroup')])
+
+    @test.attr(type='smoke')
+    def test_update_port_with_two_security_groups(self):
+        self._update_port_with_security_groups(
+            [data_utils.rand_name('secgroup'),
+             data_utils.rand_name('secgroup')])
+
 
 class PortsTestXML(PortsTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/volume/admin/test_multi_backend.py b/tempest/api/volume/admin/test_multi_backend.py
index 6f2ee06..042cde9 100644
--- a/tempest/api/volume/admin/test_multi_backend.py
+++ b/tempest/api/volume/admin/test_multi_backend.py
@@ -21,19 +21,19 @@
 LOG = logging.getLogger(__name__)
 
 
-class VolumeMultiBackendTest(base.BaseVolumeV1AdminTest):
+class VolumeMultiBackendV2Test(base.BaseVolumeAdminTest):
     _interface = "json"
 
     @classmethod
     def resource_setup(cls):
-        super(VolumeMultiBackendTest, cls).resource_setup()
+        super(VolumeMultiBackendV2Test, cls).resource_setup()
         if not CONF.volume_feature_enabled.multi_backend:
             raise cls.skipException("Cinder multi-backend feature disabled")
 
         cls.backend1_name = CONF.volume.backend1_name
         cls.backend2_name = CONF.volume.backend2_name
 
-        cls.volume_client = cls.os_adm.volumes_client
+        cls.name_field = cls.special_fields['name_field']
         cls.volume_type_id_list = []
         cls.volume_id_list_with_prefix = []
         cls.volume_id_list_without_prefix = []
@@ -64,8 +64,9 @@
             type_name, extra_specs=extra_specs)
         self.volume_type_id_list.append(self.type['id'])
 
-        _, self.volume = self.volume_client.create_volume(
-            size=1, display_name=vol_name, volume_type=type_name)
+        params = {self.name_field: vol_name, 'volume_type': type_name}
+
+        _, self.volume = self.volume_client.create_volume(size=1, **params)
         if with_prefix:
             self.volume_id_list_with_prefix.append(self.volume['id'])
         else:
@@ -92,7 +93,7 @@
         for volume_type_id in volume_type_id_list:
             cls.volume_types_client.delete_volume_type(volume_type_id)
 
-        super(VolumeMultiBackendTest, cls).resource_cleanup()
+        super(VolumeMultiBackendV2Test, cls).resource_cleanup()
 
     @test.attr(type='smoke')
     def test_backend_name_reporting(self):
@@ -149,3 +150,7 @@
         msg = ("volumes %s and %s were created in the same backend" %
                (volume1_id, volume2_id))
         self.assertNotEqual(volume1_host, volume2_host, msg)
+
+
+class VolumeMultiBackendV1Test(VolumeMultiBackendV2Test):
+    _api_version = 1
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 5815f68..d78ddb6 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -167,18 +167,21 @@
 
         cls.hosts_client = cls.os_adm.volume_hosts_client
         cls.quotas_client = cls.os_adm.volume_quotas_client
-        cls.volume_types_client = cls.os_adm.volume_types_client
 
         if cls._api_version == 1:
             if not CONF.volume_feature_enabled.api_v1:
                 msg = "Volume API v1 is disabled"
                 raise cls.skipException(msg)
             cls.volume_qos_client = cls.os_adm.volume_qos_client
+            cls.volume_types_client = cls.os_adm.volume_types_client
+            cls.volume_client = cls.os_adm.volumes_client
         elif cls._api_version == 2:
             if not CONF.volume_feature_enabled.api_v2:
                 msg = "Volume API v2 is disabled"
                 raise cls.skipException(msg)
             cls.volume_qos_client = cls.os_adm.volume_qos_v2_client
+            cls.volume_types_client = cls.os_adm.volume_types_v2_client
+            cls.volume_client = cls.os_adm.volumes_v2_client
 
     @classmethod
     def resource_cleanup(cls):
diff --git a/tempest/clients.py b/tempest/clients.py
index 2d07852..cf04929 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -181,6 +181,8 @@
 from tempest.services.volume.json.qos_client import QosSpecsClientJSON
 from tempest.services.volume.json.snapshots_client import SnapshotsClientJSON
 from tempest.services.volume.json.volumes_client import VolumesClientJSON
+from tempest.services.volume.v2.json.admin.volume_types_client import \
+    VolumeTypesV2ClientJSON
 from tempest.services.volume.v2.json.availability_zone_client import \
     VolumeV2AvailabilityZoneClientJSON
 from tempest.services.volume.v2.json.extensions_client import \
@@ -332,6 +334,8 @@
             self.volumes_v2_client = VolumesV2ClientJSON(self.auth_provider)
             self.volume_types_client = VolumeTypesClientJSON(
                 self.auth_provider)
+            self.volume_types_v2_client = VolumeTypesV2ClientJSON(
+                self.auth_provider)
             self.identity_client = IdentityClientJSON(self.auth_provider)
             self.identity_v3_client = IdentityV3ClientJSON(
                 self.auth_provider)
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index c65390e..4a8dddc 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -26,7 +26,7 @@
     PLURALS = ['dns_nameservers', 'host_routes', 'allocation_pools',
                'fixed_ips', 'extensions', 'extra_dhcp_opts', 'pools',
                'health_monitors', 'vips', 'members', 'allowed_address_pairs',
-               'firewall_rules']
+               'firewall_rules', 'security_groups']
 
     def get_rest_client(self, auth_provider):
         rc = rest_client.RestClient(auth_provider)
diff --git a/tempest/services/volume/json/admin/volume_types_client.py b/tempest/services/volume/json/admin/volume_types_client.py
index ca486d2..eedf880 100644
--- a/tempest/services/volume/json/admin/volume_types_client.py
+++ b/tempest/services/volume/json/admin/volume_types_client.py
@@ -23,13 +23,13 @@
 CONF = config.CONF
 
 
-class VolumeTypesClientJSON(rest_client.RestClient):
+class BaseVolumeTypesClientJSON(rest_client.RestClient):
     """
     Client class to send CRUD Volume Types API requests to a Cinder endpoint
     """
 
     def __init__(self, auth_provider):
-        super(VolumeTypesClientJSON, self).__init__(auth_provider)
+        super(BaseVolumeTypesClientJSON, self).__init__(auth_provider)
 
         self.service = CONF.volume.catalog_type
         self.build_interval = CONF.volume.build_interval
@@ -193,3 +193,7 @@
         resp, body = self.delete(
             "/types/%s/encryption/provider" % str(vol_type_id))
         self.expected_success(202, resp.status)
+
+
+class VolumeTypesClientJSON(BaseVolumeTypesClientJSON):
+    """Volume V1 Volume Types client"""
diff --git a/tempest/services/volume/v2/json/admin/__init__.py b/tempest/services/volume/v2/json/admin/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/services/volume/v2/json/admin/__init__.py
diff --git a/tempest/services/volume/v2/json/admin/volume_types_client.py b/tempest/services/volume/v2/json/admin/volume_types_client.py
new file mode 100644
index 0000000..76fa45d
--- /dev/null
+++ b/tempest/services/volume/v2/json/admin/volume_types_client.py
@@ -0,0 +1,28 @@
+# 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 tempest.services.volume.json.admin import volume_types_client
+
+
+class VolumeTypesV2ClientJSON(volume_types_client.BaseVolumeTypesClientJSON):
+    """
+    Client class to send CRUD Volume V2 API requests to a Cinder endpoint
+    """
+
+    def __init__(self, auth_provider):
+        super(VolumeTypesV2ClientJSON, self).__init__(auth_provider)
+
+        self.api_version = "v2"