Merge "javelin2: destroy functions above network resources"
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 484c34d..75f9795 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -27,9 +27,7 @@
 
     def __init__(self, *args, **kwargs):
         super(AttachVolumeTestJSON, self).__init__(*args, **kwargs)
-        self.server = None
-        self.volume = None
-        self.attached = False
+        self.attachment = None
 
     @classmethod
     def resource_setup(cls):
@@ -41,13 +39,15 @@
             raise cls.skipException(skip_msg)
 
     def _detach(self, server_id, volume_id):
-        if self.attached:
+        if self.attachment:
             self.servers_client.detach_volume(server_id, volume_id)
             self.volumes_client.wait_for_volume_status(volume_id, 'available')
 
     def _delete_volume(self):
+        # Delete the created Volumes
         if self.volume:
             self.volumes_client.delete_volume(self.volume['id'])
+            self.volumes_client.wait_for_resource_deletion(self.volume['id'])
             self.volume = None
 
     def _create_and_attach(self):
@@ -57,8 +57,8 @@
                                                  adminPass=admin_pass)
 
         # Record addresses so that we can ssh later
-        _, self.server['addresses'] = \
-            self.servers_client.list_addresses(self.server['id'])
+        _, self.server['addresses'] = (
+            self.servers_client.list_addresses(self.server['id']))
 
         # Create a volume and wait for it to become ready
         _, self.volume = self.volumes_client.create_volume(
@@ -68,12 +68,12 @@
                                                    'available')
 
         # Attach the volume to the server
-        self.servers_client.attach_volume(self.server['id'],
-                                          self.volume['id'],
-                                          device='/dev/%s' % self.device)
+        _, self.attachment = self.servers_client.attach_volume(
+            self.server['id'],
+            self.volume['id'],
+            device='/dev/%s' % self.device)
         self.volumes_client.wait_for_volume_status(self.volume['id'], 'in-use')
 
-        self.attached = True
         self.addCleanup(self._detach, self.server['id'], self.volume['id'])
 
     @testtools.skipUnless(CONF.compute.run_ssh, 'SSH required for this test')
@@ -97,8 +97,7 @@
         self.assertIn(self.device, partitions)
 
         self._detach(self.server['id'], self.volume['id'])
-        self.attached = False
-
+        self.attachment = None
         self.servers_client.stop(self.server['id'])
         self.servers_client.wait_for_server_status(self.server['id'],
                                                    'SHUTOFF')
@@ -112,6 +111,25 @@
         partitions = linux_client.get_partitions()
         self.assertNotIn(self.device, partitions)
 
+    @test.skip_because(bug="1323591", interface="xml")
+    @test.attr(type='gate')
+    def test_list_get_volume_attachments(self):
+        # Create Server, Volume and attach that Volume to Server
+        self._create_and_attach()
+        # List Volume attachment of the server
+        _, body = self.servers_client.list_volume_attachments(
+            self.server['id'])
+        self.assertEqual(1, len(body))
+        self.assertIn(self.attachment, body)
+
+        # Get Volume attachment of the server
+        _, body = self.servers_client.get_volume_attachment(
+            self.server['id'],
+            self.attachment['id'])
+        self.assertEqual(self.server['id'], body['serverId'])
+        self.assertEqual(self.volume['id'], body['volumeId'])
+        self.assertEqual(self.attachment['id'], body['id'])
+
 
 class AttachVolumeTestXML(AttachVolumeTestJSON):
     _interface = 'xml'
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index c6480a1..7ba68f7 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -155,33 +155,35 @@
         return network
 
     @classmethod
-    def create_subnet(cls, network, gateway=None, cidr=None, mask_bits=None,
-                      **kwargs):
+    def create_subnet(cls, network, gateway='', cidr=None, mask_bits=None,
+                      ip_version=None, **kwargs):
         """Wrapper utility that returns a test subnet."""
         # The cidr and mask_bits depend on the ip version.
-        if cls._ip_version == 4:
+        ip_version = ip_version if ip_version is not None else cls._ip_version
+        gateway_not_set = gateway == ''
+        if ip_version == 4:
             cidr = cidr or netaddr.IPNetwork(CONF.network.tenant_network_cidr)
             mask_bits = mask_bits or CONF.network.tenant_network_mask_bits
-        elif cls._ip_version == 6:
+        elif ip_version == 6:
             cidr = (
                 cidr or netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr))
             mask_bits = mask_bits or CONF.network.tenant_network_v6_mask_bits
         # Find a cidr that is not in use yet and create a subnet with it
         for subnet_cidr in cidr.subnet(mask_bits):
-            if not gateway:
-                gateway = str(netaddr.IPAddress(subnet_cidr) + 1)
+            if gateway_not_set:
+                gateway_ip = str(netaddr.IPAddress(subnet_cidr) + 1)
+            else:
+                gateway_ip = gateway
             try:
                 resp, body = cls.client.create_subnet(
                     network_id=network['id'],
                     cidr=str(subnet_cidr),
-                    ip_version=cls._ip_version,
-                    gateway_ip=gateway,
+                    ip_version=ip_version,
+                    gateway_ip=gateway_ip,
                     **kwargs)
                 break
             except exceptions.BadRequest as e:
                 is_overlapping_cidr = 'overlaps with another subnet' in str(e)
-                # Unset gateway value if there is an overlapping subnet
-                gateway = None
                 if not is_overlapping_cidr:
                     raise
         else:
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 986a2c8..dd81a09 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -280,6 +280,10 @@
         self.subnets.pop()
 
     @test.attr(type='smoke')
+    def test_create_delete_subnet_without_gateway(self):
+        self._create_verify_delete_subnet()
+
+    @test.attr(type='smoke')
     def test_create_delete_subnet_with_gw(self):
         self._create_verify_delete_subnet(
             **self.subnet_dict(['gateway']))
@@ -492,7 +496,7 @@
         self.assertEqual(subnet['gateway_ip'], gateway)
 
     @test.attr(type='smoke')
-    def test_create_delete_subnet_without_gw(self):
+    def test_create_delete_subnet_with_default_gw(self):
         net = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
         gateway_ip = str(netaddr.IPAddress(net.first + 1))
         name = data_utils.rand_name('network-')
@@ -501,16 +505,62 @@
         # Verifies Subnet GW in IPv6
         self.assertEqual(subnet['gateway_ip'], gateway_ip)
 
+    @test.attr(type='smoke')
+    def test_create_list_subnet_with_no_gw64_one_network(self):
+        name = data_utils.rand_name('network-')
+        network = self.create_network(name)
+        ipv6_gateway = self.subnet_dict(['gateway'])['gateway']
+        subnet1 = self.create_subnet(network,
+                                     ip_version=6,
+                                     gateway=ipv6_gateway)
+        self.assertEqual(netaddr.IPNetwork(subnet1['cidr']).version, 6,
+                         'The created subnet is not IPv6')
+        subnet2 = self.create_subnet(network,
+                                     gateway=None,
+                                     ip_version=4)
+        self.assertEqual(netaddr.IPNetwork(subnet2['cidr']).version, 4,
+                         'The created subnet is not IPv4')
+        # Verifies Subnet GW is set in IPv6
+        self.assertEqual(subnet1['gateway_ip'], ipv6_gateway)
+        # Verifies Subnet GW is None in IPv4
+        self.assertEqual(subnet2['gateway_ip'], None)
+        # Verifies all 2 subnets in the same network
+        _, body = self.client.list_subnets()
+        subnets = [sub['id'] for sub in body['subnets']
+                   if sub['network_id'] == network['id']]
+        test_subnet_ids = [sub['id'] for sub in (subnet1, subnet2)]
+        self.assertItemsEqual(subnets,
+                              test_subnet_ids,
+                              'Subnet are not in the same network')
+
     @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
                           "IPv6 extended attributes for subnets not "
                           "available")
     @test.attr(type='smoke')
-    def test_create_delete_subnet_with_v6_attributes(self):
+    def test_create_delete_subnet_with_v6_attributes_stateful(self):
         self._create_verify_delete_subnet(
             gateway=self._subnet_data[self._ip_version]['gateway'],
+            ipv6_ra_mode='dhcpv6-stateful',
+            ipv6_address_mode='dhcpv6-stateful')
+
+    @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+                          "IPv6 extended attributes for subnets not "
+                          "available")
+    @test.attr(type='smoke')
+    def test_create_delete_subnet_with_v6_attributes_slaac(self):
+        self._create_verify_delete_subnet(
             ipv6_ra_mode='slaac',
             ipv6_address_mode='slaac')
 
+    @testtools.skipUnless(CONF.network_feature_enabled.ipv6_subnet_attributes,
+                          "IPv6 extended attributes for subnets not "
+                          "available")
+    @test.attr(type='smoke')
+    def test_create_delete_subnet_with_v6_attributes_stateless(self):
+        self._create_verify_delete_subnet(
+            ipv6_ra_mode='dhcpv6-stateless',
+            ipv6_address_mode='dhcpv6-stateless')
+
 
 class NetworksIpV6TestXML(NetworksIpV6TestJSON):
     _interface = 'xml'
diff --git a/tempest/api_schema/response/compute/v2/servers.py b/tempest/api_schema/response/compute/v2/servers.py
index 5fc2008..09abaed 100644
--- a/tempest/api_schema/response/compute/v2/servers.py
+++ b/tempest/api_schema/response/compute/v2/servers.py
@@ -117,21 +117,23 @@
     }
 }
 
+common_attach_volume_info = {
+    'type': 'object',
+    'properties': {
+        'id': {'type': 'string'},
+        'device': {'type': 'string'},
+        'volumeId': {'type': 'string'},
+        'serverId': {'type': ['integer', 'string']}
+    },
+    'required': ['id', 'device', 'volumeId', 'serverId']
+}
+
 attach_volume = {
     'status_code': [200],
     'response_body': {
         'type': 'object',
         'properties': {
-            'volumeAttachment': {
-                'type': 'object',
-                'properties': {
-                    'id': {'type': 'string'},
-                    'device': {'type': 'string'},
-                    'volumeId': {'type': 'string'},
-                    'serverId': {'type': ['integer', 'string']}
-                },
-                'required': ['id', 'device', 'volumeId', 'serverId']
-            }
+            'volumeAttachment': common_attach_volume_info
         },
         'required': ['volumeAttachment']
     }
@@ -141,6 +143,27 @@
     'status_code': [202]
 }
 
+get_volume_attachment = copy.deepcopy(attach_volume)
+get_volume_attachment['response_body']['properties'][
+    'volumeAttachment']['properties'].update({'serverId': {'type': 'string'}})
+
+list_volume_attachments = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'volumeAttachments': {
+                'type': 'array',
+                'items': common_attach_volume_info
+            }
+        },
+        'required': ['volumeAttachments']
+    }
+}
+list_volume_attachments['response_body']['properties'][
+    'volumeAttachments']['items']['properties'].update(
+    {'serverId': {'type': 'string'}})
+
 set_get_server_metadata_item = {
     'status_code': [200],
     'response_body': {
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 990a392..2ebfdd1 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -411,9 +411,8 @@
 
     def nova_volume_attach(self):
         # TODO(andreaf) Device should be here CONF.compute.volume_device_name
-        _, volume_attachment = self.servers_client.attach_volume(
+        _, volume = self.servers_client.attach_volume(
             self.server['id'], self.volume['id'], '/dev/vdb')
-        volume = volume_attachment['volumeAttachment']
         self.assertEqual(self.volume['id'], volume['id'])
         self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
         # Refresh the volume after the attachment
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index 35571c6..ea10140 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -132,7 +132,6 @@
         # We expect the ephemeral partition to be mounted on /mnt and to have
         # the same size as our flavor definition.
         eph_size = self.get_flavor_ephemeral_size()
-        self.assertIsNotNone(eph_size)
         if eph_size > 0:
             preserve_ephemeral = True
 
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 8a8e387..ead021e 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -16,6 +16,7 @@
 from tempest.common import custom_matchers
 from tempest.common import debug
 from tempest import config
+from tempest import exceptions
 from tempest.openstack.common import log as logging
 from tempest.scenario import manager
 from tempest import test
@@ -77,9 +78,8 @@
 
     def nova_volume_attach(self):
         volume_device_path = '/dev/' + CONF.compute.volume_device_name
-        _, volume_attachment = self.servers_client.attach_volume(
+        _, volume = self.servers_client.attach_volume(
             self.server['id'], self.volume['id'], volume_device_path)
-        volume = volume_attachment['volumeAttachment']
         self.assertEqual(self.volume['id'], volume['id'])
         self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
         # Refresh the volume after the attachment
@@ -130,6 +130,17 @@
         self.addCleanup(self.servers_client.remove_security_group,
                         self.server['id'], secgroup['name'])
 
+        def wait_for_secgroup_add():
+            _, body = self.servers_client.get_server(self.server['id'])
+            return {'name': secgroup['name']} in body['security_groups']
+
+        if not test.call_until_true(wait_for_secgroup_add,
+                                    CONF.compute.build_timeout,
+                                    CONF.compute.build_interval):
+            msg = ('Timed out waiting for adding security group %s to server '
+                   '%s' % (secgroup['id'], self.server['id']))
+            raise exceptions.TimeoutException(msg)
+
     @test.services('compute', 'volume', 'image', 'network')
     def test_minimum_basic_scenario(self):
         self.glance_image_create()
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 8ea2814..7fc1edf 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -115,7 +115,6 @@
         # TODO(andreaf) we should use device from config instead if vdb
         _, attached_volume = self.servers_client.attach_volume(
             server['id'], volume['id'], device='/dev/vdb')
-        attached_volume = attached_volume['volumeAttachment']
         self.assertEqual(volume['id'], attached_volume['id'])
         self._wait_for_volume_status(attached_volume, 'in-use')
 
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 947ba7a..4268b1a 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -369,7 +369,7 @@
                                post_body)
         body = json.loads(body)
         self.validate_response(schema.attach_volume, resp, body)
-        return resp, body
+        return resp, body['volumeAttachment']
 
     def detach_volume(self, server_id, volume_id):
         """Detaches a volume from a server instance."""
@@ -378,6 +378,22 @@
         self.validate_response(schema.detach_volume, resp, body)
         return resp, body
 
+    def get_volume_attachment(self, server_id, attach_id):
+        """Return details about the given volume attachment."""
+        resp, body = self.get('servers/%s/os-volume_attachments/%s' % (
+            str(server_id), attach_id))
+        body = json.loads(body)
+        self.validate_response(schema.get_volume_attachment, resp, body)
+        return resp, body['volumeAttachment']
+
+    def list_volume_attachments(self, server_id):
+        """Returns the list of volume attachments for a given instance."""
+        resp, body = self.get('servers/%s/os-volume_attachments' % (
+            str(server_id)))
+        body = json.loads(body)
+        self.validate_response(schema.list_volume_attachments, resp, body)
+        return resp, body['volumeAttachments']
+
     def add_security_group(self, server_id, name):
         """Adds a security group to the server."""
         return self.action(server_id, 'addSecurityGroup', None, name=name)
diff --git a/tools/check_logs.py b/tools/check_logs.py
index 7cf9d85..c8d3a1a 100755
--- a/tools/check_logs.py
+++ b/tools/check_logs.py
@@ -31,7 +31,7 @@
 is_grenade = os.environ.get('DEVSTACK_GATE_GRENADE') is not None
 dump_all_errors = True
 
-# As logs are made clean, add to this set
+# As logs are made clean, remove from this set
 allowed_dirty = set([
     'c-api',
     'ceilometer-acentral',
diff --git a/tox.ini b/tox.ini
index e575b4f..9f52f0d 100644
--- a/tox.ini
+++ b/tox.ini
@@ -28,7 +28,9 @@
 
 [testenv:all]
 sitepackages = {[tempestenv]sitepackages}
+# 'all' includes slow tests
 setenv = {[tempestenv]setenv}
+         OS_TEST_TIMEOUT=1200
 deps = {[tempestenv]deps}
 commands =
   bash tools/pretty_tox.sh '{posargs}'