Merge "Removed RestClientXML class"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 1d368af..a2d3877 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -553,6 +553,9 @@
 # The mask bits for tenant ipv4 subnets (integer value)
 #tenant_network_mask_bits=28
 
+# Allow the execution of IPv6 tests (boolean value)
+#ipv6_enabled=true
+
 # The cidr block to allocate tenant ipv6 subnets from (string
 # value)
 #tenant_network_v6_cidr=2003::/64
diff --git a/tempest/api/network/test_extra_dhcp_options.py b/tempest/api/network/test_extra_dhcp_options.py
new file mode 100644
index 0000000..ed86d75
--- /dev/null
+++ b/tempest/api/network/test_extra_dhcp_options.py
@@ -0,0 +1,99 @@
+# 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.network import base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class ExtraDHCPOptionsTestJSON(base.BaseNetworkTest):
+    _interface = 'json'
+
+    """
+    Tests the following operations with the Extra DHCP Options Neutron API
+    extension:
+
+        port create
+        port list
+        port show
+        port update
+
+    v2.0 of the Neutron API is assumed. It is also assumed that the Extra
+    DHCP Options extension is enabled in the [network-feature-enabled]
+    section of etc/tempest.conf
+    """
+
+    @classmethod
+    def setUpClass(cls):
+        super(ExtraDHCPOptionsTestJSON, cls).setUpClass()
+        if not test.is_extension_enabled('extra_dhcp_opt', 'network'):
+            msg = "Extra DHCP Options extension not enabled."
+            raise cls.skipException(msg)
+        cls.network = cls.create_network()
+        cls.subnet = cls.create_subnet(cls.network)
+        cls.port = cls.create_port(cls.network)
+
+    @test.attr(type='smoke')
+    def test_create_list_port_with_extra_dhcp_options(self):
+        # Create a port with Extra DHCP Options
+        extra_dhcp_opts = [
+            {'opt_value': 'pxelinux.0', 'opt_name': 'bootfile-name'},
+            {'opt_value': '123.123.123.123', 'opt_name': 'tftp-server'},
+            {'opt_value': '123.123.123.45', 'opt_name': 'server-ip-address'}
+        ]
+        resp, body = self.client.create_port(
+            network_id=self.network['id'],
+            extra_dhcp_opts=extra_dhcp_opts)
+        self.assertEqual('201', resp['status'])
+        port_id = body['port']['id']
+        self.addCleanup(self.client.delete_port, port_id)
+
+        # Confirm port created has Extra DHCP Options
+        resp, body = self.client.list_ports()
+        self.assertEqual('200', resp['status'])
+        ports = body['ports']
+        port = [p for p in ports if p['id'] == port_id]
+        self.assertTrue(port)
+        self._confirm_extra_dhcp_options(port[0], extra_dhcp_opts)
+
+    @test.attr(type='smoke')
+    def test_update_show_port_with_extra_dhcp_options(self):
+        # Update port with extra dhcp options
+        extra_dhcp_opts = [
+            {'opt_value': 'pxelinux.0', 'opt_name': 'bootfile-name'},
+            {'opt_value': '123.123.123.123', 'opt_name': 'tftp-server'},
+            {'opt_value': '123.123.123.45', 'opt_name': 'server-ip-address'}
+        ]
+        name = data_utils.rand_name('new-port-name')
+        resp, body = self.client.update_port(
+            self.port['id'], name=name, extra_dhcp_opts=extra_dhcp_opts)
+        self.assertEqual('200', resp['status'])
+
+        # Confirm extra dhcp options were added to the port
+        resp, body = self.client.show_port(self.port['id'])
+        self.assertEqual('200', resp['status'])
+        self._confirm_extra_dhcp_options(body['port'], extra_dhcp_opts)
+
+    def _confirm_extra_dhcp_options(self, port, extra_dhcp_opts):
+        retrieved = port['extra_dhcp_opts']
+        self.assertEqual(len(retrieved), len(extra_dhcp_opts))
+        for retrieved_option in retrieved:
+            for option in extra_dhcp_opts:
+                if (retrieved_option['opt_value'] == option['opt_value'] and
+                    retrieved_option['opt_name'] == option['opt_name']):
+                    break
+            else:
+                self.fail('Extra DHCP option not found in port %s' %
+                          str(retrieved_option))
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 9029b1f..aba2c8e 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -467,6 +467,14 @@
     _tenant_network_cidr = CONF.network.tenant_network_v6_cidr
     _tenant_network_mask_bits = CONF.network.tenant_network_v6_mask_bits
 
+    @classmethod
+    def setUpClass(cls):
+        super(NetworksIpV6TestJSON, cls).setUpClass()
+        if not CONF.network.ipv6_enabled:
+            cls.tearDownClass()
+            skip_msg = "IPv6 Tests are disabled."
+            raise cls.skipException(skip_msg)
+
 
 class NetworksIpV6TestXML(NetworksIpV6TestJSON):
     _interface = 'xml'
diff --git a/tempest/config.py b/tempest/config.py
index a2d35a9..c92a04d 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -343,6 +343,9 @@
     cfg.IntOpt('tenant_network_mask_bits',
                default=28,
                help="The mask bits for tenant ipv4 subnets"),
+    cfg.BoolOpt('ipv6_enabled',
+                default=True,
+                help="Allow the execution of IPv6 tests"),
     cfg.StrOpt('tenant_network_v6_cidr',
                default="2003::/64",
                help="The cidr block to allocate tenant ipv6 subnets from"),
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 02a3c9d..4dd51fb 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -681,18 +681,32 @@
         Create a subnet for the given network within the cidr block
         configured for tenant networks.
         """
+
+        def cidr_in_use(cidr, tenant_id):
+            """
+            :return True if subnet with cidr already exist in tenant
+                False else
+            """
+            cidr_in_use = self._list_subnets(tenant_id=tenant_id, cidr=cidr)
+            return len(cidr_in_use) != 0
+
         tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
         result = None
         # Repeatedly attempt subnet creation with sequential cidr
         # blocks until an unallocated block is found.
         for subnet_cidr in tenant_cidr.subnet(
             CONF.network.tenant_network_mask_bits):
+            str_cidr = str(subnet_cidr)
+            if cidr_in_use(str_cidr, tenant_id=network.tenant_id):
+                continue
+
             body = dict(
                 subnet=dict(
+                    name=data_utils.rand_name(namestart),
                     ip_version=4,
                     network_id=network.id,
                     tenant_id=network.tenant_id,
-                    cidr=str(subnet_cidr),
+                    cidr=str_cidr,
                 ),
             )
             try:
@@ -705,7 +719,7 @@
         self.assertIsNotNone(result, 'Unable to allocate tenant network')
         subnet = net_common.DeletableSubnet(client=self.network_client,
                                             **result['subnet'])
-        self.assertEqual(subnet.cidr, str(subnet_cidr))
+        self.assertEqual(subnet.cidr, str_cidr)
         self.set_resource(data_utils.rand_name(namestart), subnet)
         return subnet
 
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index a669efc..f6ae718 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -27,7 +27,7 @@
 
     # list of plurals used for xml serialization
     PLURALS = ['dns_nameservers', 'host_routes', 'allocation_pools',
-               'fixed_ips', 'extensions']
+               'fixed_ips', 'extensions', 'extra_dhcp_opts']
 
     def get_rest_client(self, auth_provider):
         rc = rest_client.RestClient(auth_provider)
@@ -77,6 +77,18 @@
             xml_elem = Element(name)
             xml_elem.add_attr("xsi:nil", "true")
             return xml_elem
+        elif isinstance(value, dict):
+            dict_element = Element(name)
+            for key, value in value.iteritems():
+                elem = self._get_element(key, value)
+                dict_element.append(elem)
+            return dict_element
+        elif isinstance(value, list):
+            list_element = Element(name)
+            for element in value:
+                elem = self._get_element(name[:-1], element)
+                list_element.append(elem)
+            return list_element
         else:
             return Element(name, value)
 
diff --git a/tempest/stress/run_stress.py b/tempest/stress/run_stress.py
index 76320d0..a6c2b77 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/stress/run_stress.py
@@ -87,8 +87,13 @@
             # NOTE(mkoderer): we just save the last result code
             if (step_result != 0):
                 result = step_result
+                if ns.stop:
+                    return result
     else:
-        driver.stress_openstack(tests, ns.duration, ns.number, ns.stop)
+        result = driver.stress_openstack(tests,
+                                         ns.duration,
+                                         ns.number,
+                                         ns.stop)
     return result
 
 
diff --git a/tools/verify_tempest_config.py b/tools/verify_tempest_config.py
index 29eed9d..4be812c 100755
--- a/tools/verify_tempest_config.py
+++ b/tools/verify_tempest_config.py
@@ -42,7 +42,12 @@
 def verify_nova_api_versions(os):
     # Check nova api versions - only get base URL without PATH
     os.servers_client.skip_path = True
-    __, body = RAW_HTTP.request(os.servers_client.base_url, 'GET')
+    # The nova base endpoint url includes the version but to get the versions
+    # list the unversioned endpoint is needed
+    v2_endpoint = os.servers_client.base_url
+    v2_endpoint_parts = v2_endpoint.split('/')
+    endpoint = v2_endpoint_parts[0] + '//' + v2_endpoint_parts[2]
+    __, body = RAW_HTTP.request(endpoint, 'GET')
     body = json.loads(body)
     # Restore full base_url
     os.servers_client.skip_path = False