Merge "Merge agent response schema into one file"
diff --git a/README.rst b/README.rst
index 9aaea24..ba93712 100644
--- a/README.rst
+++ b/README.rst
@@ -54,13 +54,13 @@
 
 .. note::
 
-    If you have a running devstack environment, tempest will be
+    If you have a running devstack environment, Tempest will be
     automatically configured and placed in ``/opt/stack/tempest``. It
     will have a configuration file already set up to work with your
     devstack installation.
 
 Tempest is not tied to any single test runner, but `testr`_ is the most commonly
-used tool. Also, the nosetests test runner is **not** recommended to run tempest.
+used tool. Also, the nosetests test runner is **not** recommended to run Tempest.
 
 After setting up your configuration file, you can execute the set of Tempest
 tests by using ``testr`` ::
@@ -80,7 +80,7 @@
    $> tox -efull
 
 which will run the same set of tests as the OpenStack gate. (it's exactly how
-the gate invokes tempest) Or::
+the gate invokes Tempest) Or::
 
   $> tox -esmoke
 
@@ -90,13 +90,13 @@
 Configuration
 -------------
 
-Detailed configuration of tempest is beyond the scope of this
+Detailed configuration of Tempest is beyond the scope of this
 document see :ref:`tempest-configuration` for more details on configuring
-tempest. The etc/tempest.conf.sample attempts to be a self documenting version
+Tempest. The etc/tempest.conf.sample attempts to be a self documenting version
 of the configuration.
 
 You can generate a new sample tempest.conf file, run the following
-command from the top level of the tempest directory:
+command from the top level of the Tempest directory:
 
   tox -egenconfig
 
@@ -106,7 +106,7 @@
 Unit Tests
 ----------
 
-Tempest also has a set of unit tests which test the tempest code itself. These
+Tempest also has a set of unit tests which test the Tempest code itself. These
 tests can be run by specifing the test discovery path::
 
     $> OS_TEST_PATH=./tempest/tests testr run --parallel
@@ -114,7 +114,7 @@
 By setting OS_TEST_PATH to ./tempest/tests it specifies that test discover
 should only be run on the unit test directory. The default value of OS_TEST_PATH
 is OS_TEST_PATH=./tempest/test_discover which will only run test discover on the
-tempest suite.
+Tempest suite.
 
 Alternatively, you can use the run_tests.sh script which will create a venv and
 run the unit tests. There are also the py26, py27, or py33 tox jobs which will
@@ -124,10 +124,10 @@
 ----------
 
 Starting in the kilo release the OpenStack services dropped all support for
-python 2.6. This change has been mirrored in tempest, starting after the
-tempest-2 tag. This means that proposed changes to tempest which only fix
+python 2.6. This change has been mirrored in Tempest, starting after the
+tempest-2 tag. This means that proposed changes to Tempest which only fix
 python 2.6 compatibility will be rejected, and moving forward more features not
-present in python 2.6 will be used. If you're running you're OpenStack services
-on an earlier release with python 2.6 you can easily run tempest against it
+present in python 2.6 will be used. If you're running your OpenStack services
+on an earlier release with python 2.6 you can easily run Tempest against it
 from a remote system running python 2.7. (or deploy a cloud guest in your cloud
 that has python 2.7)
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 2a35aff..80d52a4 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -422,6 +422,11 @@
 # Does the test environment have the ec2 api running? (boolean value)
 #ec2_api = true
 
+# Does Nova preserve preexisting ports from Neutron when deleting an
+# instance? This should be set to True if testing Kilo+ Nova. (boolean
+# value)
+#preserve_ports = false
+
 
 [dashboard]
 
diff --git a/requirements.txt b/requirements.txt
index e6fc09b..2328203 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -9,11 +9,9 @@
 boto>=2.32.1
 paramiko>=1.13.0
 netaddr>=0.7.12
-python-ceilometerclient>=1.0.6
 python-glanceclient>=0.15.0
 python-cinderclient>=1.1.0
 python-heatclient>=0.3.0
-python-ironicclient>=0.2.1
 python-saharaclient>=0.7.6
 python-swiftclient>=2.2.0
 testrepository>=0.0.18
diff --git a/tempest/api/compute/admin/test_security_groups.py b/tempest/api/compute/admin/test_security_groups.py
index 578f73b..6d79a77 100644
--- a/tempest/api/compute/admin/test_security_groups.py
+++ b/tempest/api/compute/admin/test_security_groups.py
@@ -39,7 +39,7 @@
 
     @test.idempotent_id('49667619-5af9-4c63-ab5d-2cfdd1c8f7f1')
     @testtools.skipIf(CONF.service_available.neutron,
-                      "Skipped because neutron do not support all_tenants"
+                      "Skipped because neutron does not support all_tenants "
                       "search filter.")
     @test.attr(type='smoke')
     @test.services('network')
diff --git a/tempest/api_schema/response/compute/interfaces.py b/tempest/api_schema/response/compute/interfaces.py
deleted file mode 100644
index fd53eb3..0000000
--- a/tempest/api_schema/response/compute/interfaces.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright 2014 NEC Corporation.  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_schema.response.compute import parameter_types
-
-delete_interface = {
-    'status_code': [202]
-}
-
-interface_common_info = {
-    'type': 'object',
-    'properties': {
-        'port_state': {'type': 'string'},
-        'fixed_ips': {
-            'type': 'array',
-            'items': {
-                'type': 'object',
-                'properties': {
-                    'subnet_id': {
-                        'type': 'string',
-                        'format': 'uuid'
-                    },
-                    'ip_address': {
-                        'type': 'string',
-                        'format': 'ipv4'
-                    }
-                },
-                'required': ['subnet_id', 'ip_address']
-            }
-        },
-        'port_id': {'type': 'string', 'format': 'uuid'},
-        'net_id': {'type': 'string', 'format': 'uuid'},
-        'mac_addr': parameter_types.mac_address
-    },
-    'required': ['port_state', 'fixed_ips', 'port_id', 'net_id', 'mac_addr']
-}
diff --git a/tempest/api_schema/response/compute/v2_1/interfaces.py b/tempest/api_schema/response/compute/v2_1/interfaces.py
index 64d161d..4de3309 100644
--- a/tempest/api_schema/response/compute/v2_1/interfaces.py
+++ b/tempest/api_schema/response/compute/v2_1/interfaces.py
@@ -12,7 +12,46 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-from tempest.api_schema.response.compute import interfaces as common_schema
+from tempest.api_schema.response.compute import parameter_types
+
+interface_common_info = {
+    'type': 'object',
+    'properties': {
+        'port_state': {'type': 'string'},
+        'fixed_ips': {
+            'type': 'array',
+            'items': {
+                'type': 'object',
+                'properties': {
+                    'subnet_id': {
+                        'type': 'string',
+                        'format': 'uuid'
+                    },
+                    'ip_address': {
+                        'type': 'string',
+                        'format': 'ipv4'
+                    }
+                },
+                'required': ['subnet_id', 'ip_address']
+            }
+        },
+        'port_id': {'type': 'string', 'format': 'uuid'},
+        'net_id': {'type': 'string', 'format': 'uuid'},
+        'mac_addr': parameter_types.mac_address
+    },
+    'required': ['port_state', 'fixed_ips', 'port_id', 'net_id', 'mac_addr']
+}
+
+get_create_interfaces = {
+    'status_code': [200],
+    'response_body': {
+        'type': 'object',
+        'properties': {
+            'interfaceAttachment': interface_common_info
+        },
+        'required': ['interfaceAttachment']
+    }
+}
 
 list_interfaces = {
     'status_code': [200],
@@ -21,9 +60,13 @@
         'properties': {
             'interfaceAttachments': {
                 'type': 'array',
-                'items': common_schema.interface_common_info
+                'items': interface_common_info
             }
         },
         'required': ['interfaceAttachments']
     }
 }
+
+delete_interface = {
+    'status_code': [202]
+}
diff --git a/tempest/cli/simple_read_only/telemetry/__init__.py b/tempest/cli/simple_read_only/telemetry/__init__.py
deleted file mode 100644
index e69de29..0000000
--- a/tempest/cli/simple_read_only/telemetry/__init__.py
+++ /dev/null
diff --git a/tempest/cli/simple_read_only/telemetry/test_ceilometer.py b/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
deleted file mode 100644
index b5e570b..0000000
--- a/tempest/cli/simple_read_only/telemetry/test_ceilometer.py
+++ /dev/null
@@ -1,62 +0,0 @@
-# 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 oslo_log import log as logging
-
-from tempest import cli
-from tempest import config
-from tempest import test
-
-CONF = config.CONF
-
-LOG = logging.getLogger(__name__)
-
-
-class SimpleReadOnlyCeilometerClientTest(cli.ClientTestBase):
-    """Basic, read-only tests for Ceilometer CLI client.
-
-    Checks return values and output of read-only commands.
-    These tests do not presume any content, nor do they create
-    their own. They only verify the structure of output if present.
-    """
-
-    @classmethod
-    def resource_setup(cls):
-        if (not CONF.service_available.ceilometer):
-            msg = ("Skipping all Ceilometer cli tests because it is "
-                   "not available")
-            raise cls.skipException(msg)
-        super(SimpleReadOnlyCeilometerClientTest, cls).resource_setup()
-
-    def ceilometer(self, *args, **kwargs):
-        return self.clients.ceilometer(
-            *args, endpoint_type=CONF.telemetry.endpoint_type, **kwargs)
-
-    @test.idempotent_id('ab717d43-a9c4-4dcf-bad8-c4777933a970')
-    def test_ceilometer_meter_list(self):
-        self.ceilometer('meter-list')
-
-    @test.attr(type='slow')
-    @test.idempotent_id('fe2e52a4-a99b-426e-a52d-d0bde50f3e4c')
-    def test_ceilometer_resource_list(self):
-        self.ceilometer('resource-list')
-
-    @test.idempotent_id('eede695c-f3bf-449f-a420-02f3cc426d52')
-    def test_ceilometermeter_alarm_list(self):
-        self.ceilometer('alarm-list')
-
-    @test.idempotent_id('0586bcc4-8e35-415f-8f23-77b590042684')
-    def test_ceilometer_version(self):
-        self.ceilometer('', flags='--version')
diff --git a/tempest/config.py b/tempest/config.py
index 119de0e..12620de 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -351,7 +351,13 @@
                      'images of running instances?'),
     cfg.BoolOpt('ec2_api',
                 default=True,
-                help='Does the test environment have the ec2 api running?')
+                help='Does the test environment have the ec2 api running?'),
+    # TODO(mriedem): Remove preserve_ports once juno-eol happens.
+    cfg.BoolOpt('preserve_ports',
+                default=False,
+                help='Does Nova preserve preexisting ports from Neutron '
+                     'when deleting an instance? This should be set to True '
+                     'if testing Kilo+ Nova.')
 ]
 
 
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index 81e771c..f8cc17c 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -14,7 +14,6 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import os
 import subprocess
 
 import netaddr
@@ -24,7 +23,6 @@
 from tempest_lib import exceptions as lib_exc
 
 from tempest import clients
-from tempest.common import cred_provider
 from tempest.common import credentials
 from tempest.common.utils.linux import remote_client
 from tempest import config
@@ -50,7 +48,6 @@
         cls.manager = clients.Manager(
             credentials=cls.credentials()
         )
-        cls.admin_manager = clients.Manager(cls.admin_credentials())
 
     @classmethod
     def setup_clients(cls):
@@ -63,7 +60,6 @@
         # Compute image client
         cls.images_client = cls.manager.images_client
         cls.keypairs_client = cls.manager.keypairs_client
-        cls.networks_client = cls.admin_manager.networks_client
         # Nova security groups client
         cls.security_groups_client = cls.manager.security_groups_client
         cls.servers_client = cls.manager.servers_client
@@ -542,6 +538,14 @@
         super(NetworkScenarioTest, cls).skip_checks()
         if not CONF.service_available.neutron:
             raise cls.skipException('Neutron not available')
+        if not credentials.is_admin_available():
+            msg = ("Missing Identity Admin API credentials in configuration.")
+            raise cls.skipException(msg)
+
+    @classmethod
+    def setup_credentials(cls):
+        super(NetworkScenarioTest, cls).setup_credentials()
+        cls.admin_manager = clients.Manager(cls.admin_credentials())
 
     @classmethod
     def resource_setup(cls):
@@ -1283,9 +1287,17 @@
     """
 
     @classmethod
+    def skip_checks(cls):
+        super(EncryptionScenarioTest, cls).skip_checks()
+        if not credentials.is_admin_available():
+            msg = ("Missing Identity Admin API credentials in configuration.")
+            raise cls.skipException(msg)
+
+    @classmethod
     def setup_clients(cls):
         super(EncryptionScenarioTest, cls).setup_clients()
-        cls.admin_volume_types_client = cls.admin_manager.volume_types_client
+        admin_manager = clients.Manager(cls.admin_credentials())
+        cls.admin_volume_types_client = admin_manager.volume_types_client
 
     def _wait_for_volume_status(self, status):
         self.status_timeout(
@@ -1324,49 +1336,6 @@
             control_location=control_location)
 
 
-class OrchestrationScenarioTest(ScenarioTest):
-    """
-    Base class for orchestration scenario tests
-    """
-
-    @classmethod
-    def skip_checks(cls):
-        super(OrchestrationScenarioTest, cls).skip_checks()
-        if not CONF.service_available.heat:
-            raise cls.skipException("Heat support is required")
-
-    @classmethod
-    def credentials(cls):
-        admin_creds = cred_provider.get_configured_credentials(
-            'identity_admin')
-        creds = cred_provider.get_configured_credentials('user')
-        admin_creds.tenant_name = creds.tenant_name
-        return admin_creds
-
-    def _load_template(self, base_file, file_name):
-        filepath = os.path.join(os.path.dirname(os.path.realpath(base_file)),
-                                file_name)
-        with open(filepath) as f:
-            return f.read()
-
-    @classmethod
-    def _stack_rand_name(cls):
-        return data_utils.rand_name(cls.__name__ + '-')
-
-    @classmethod
-    def _get_default_network(cls):
-        networks = cls.networks_client.list_networks()
-        for net in networks:
-            if net['label'] == CONF.compute.fixed_network_name:
-                return net
-
-    @staticmethod
-    def _stack_output(stack, output_key):
-        """Return a stack output value for a given key."""
-        return next((o['output_value'] for o in stack['outputs']
-                    if o['output_key'] == output_key), None)
-
-
 class SwiftScenarioTest(ScenarioTest):
     """
     Provide harness to do Swift scenario tests.
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index bb668f7..3d6abff 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -15,7 +15,6 @@
 
 from oslo_log import log as logging
 from tempest_lib.common.utils import data_utils
-from tempest_lib import decorators
 import testtools
 
 from tempest import config
@@ -94,8 +93,8 @@
         self.servers_client.wait_for_server_status(self.server['id'], 'ACTIVE')
         self._check_network_connectivity()
 
-    @decorators.skip_because(bug="1323658")
     @test.idempotent_id('61f1aa9a-1573-410e-9054-afa557cab021')
+    @test.stresstest(class_setup_per='process')
     @test.services('compute', 'network')
     def test_server_connectivity_stop_start(self):
         self._setup_network_and_servers()
@@ -147,7 +146,6 @@
         self.servers_client.resume_server(self.server['id'])
         self._wait_server_status_and_check_network_connectivity()
 
-    @decorators.skip_because(bug="1323658")
     @test.idempotent_id('719eb59d-2f42-4b66-b8b1-bb1254473967')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize is not available.')
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index af7b683..bb19853 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -101,13 +101,19 @@
         self.servers = []
 
     def _setup_network_and_servers(self, **kwargs):
+        boot_with_port = kwargs.pop('boot_with_port', False)
         self.security_group = \
             self._create_security_group(tenant_id=self.tenant_id)
         self.network, self.subnet, self.router = self.create_networks(**kwargs)
         self.check_networks()
 
+        self.port_id = None
+        if boot_with_port:
+            # create a port on the network and boot with that
+            self.port_id = self._create_port(self.network['id']).id
+
         name = data_utils.rand_name('server-smoke')
-        server = self._create_server(name, self.network)
+        server = self._create_server(name, self.network, self.port_id)
         self._check_tenant_network_connectivity()
 
         floating_ip = self.create_floating_ip(server)
@@ -141,7 +147,7 @@
             self.assertIn(self.router.id,
                           seen_router_ids)
 
-    def _create_server(self, name, network):
+    def _create_server(self, name, network, port_id=None):
         keypair = self.create_keypair()
         self.keypairs[keypair['name']] = keypair
         security_groups = [{'name': self.security_group['name']}]
@@ -152,6 +158,8 @@
             'key_name': keypair['name'],
             'security_groups': security_groups,
         }
+        if port_id is not None:
+            create_kwargs['networks'][0]['port'] = port_id
         server = self.create_server(name=name, create_kwargs=create_kwargs)
         self.servers.append(server)
         return server
@@ -605,3 +613,39 @@
         self.check_public_network_connectivity(
             should_connect=True, msg="after updating "
             "admin_state_up of instance port to True")
+
+    @test.idempotent_id('759462e1-8535-46b0-ab3a-33aa45c55aaa')
+    @testtools.skipUnless(CONF.compute_feature_enabled.preserve_ports,
+                          'Preserving ports on instance delete may not be '
+                          'supported in the version of Nova being tested.')
+    @test.attr(type='smoke')
+    @test.services('compute', 'network')
+    def test_preserve_preexisting_port(self):
+        """Tests that a pre-existing port provided on server boot is not
+        deleted if the server is deleted.
+
+        Nova should unbind the port from the instance on delete if the port was
+        not created by Nova as part of the boot request.
+        """
+        # Setup the network, create a port and boot the server from that port.
+        self._setup_network_and_servers(boot_with_port=True)
+        _, server = self.floating_ip_tuple
+        self.assertIsNotNone(self.port_id,
+                             'Server should have been created from a '
+                             'pre-existing port.')
+        # Assert the port is bound to the server.
+        port_list = self._list_ports(device_id=server['id'],
+                                     network_id=self.network['id'])
+        self.assertEqual(1, len(port_list),
+                         'There should only be one port created for '
+                         'server %s.' % server['id'])
+        self.assertEqual(self.port_id, port_list[0]['id'])
+        # Delete the server.
+        self.servers_client.delete_server(server['id'])
+        self.servers_client.wait_for_server_termination(server['id'])
+        # Assert the port still exists on the network but is unbound from
+        # the deleted server.
+        port = self.network_client.show_port(self.port_id)['port']
+        self.assertEqual(self.network['id'], port['network_id'])
+        self.assertEqual('', port['device_id'])
+        self.assertEqual('', port['device_owner'])
diff --git a/tempest/services/compute/json/interfaces_client.py b/tempest/services/compute/json/interfaces_client.py
index 84a3271..c3bfa99 100644
--- a/tempest/services/compute/json/interfaces_client.py
+++ b/tempest/services/compute/json/interfaces_client.py
@@ -16,7 +16,6 @@
 import json
 import time
 
-from tempest.api_schema.response.compute import interfaces as common_schema
 from tempest.api_schema.response.compute import servers as servers_schema
 from tempest.api_schema.response.compute.v2_1 import interfaces as schema
 from tempest.common import service_client
@@ -46,17 +45,19 @@
         resp, body = self.post('servers/%s/os-interface' % server,
                                body=post_body)
         body = json.loads(body)
+        self.validate_response(schema.get_create_interfaces, resp, body)
         return service_client.ResponseBody(resp, body['interfaceAttachment'])
 
     def show_interface(self, server, port_id):
         resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id))
         body = json.loads(body)
+        self.validate_response(schema.get_create_interfaces, resp, body)
         return service_client.ResponseBody(resp, body['interfaceAttachment'])
 
     def delete_interface(self, server, port_id):
         resp, body = self.delete('servers/%s/os-interface/%s' % (server,
                                                                  port_id))
-        self.validate_response(common_schema.delete_interface, resp, body)
+        self.validate_response(schema.delete_interface, resp, body)
         return service_client.ResponseBody(resp, body)
 
     def wait_for_interface_status(self, server, port_id, status):
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index d095b53..e84d627 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -132,7 +132,14 @@
         computes = _get_compute_nodes(controller, ssh_user, ssh_key)
         for node in computes:
             do_ssh("rm -f %s" % logfiles, node, ssh_user, ssh_key)
+    skip = False
     for test in tests:
+        for service in test.get('required_services', []):
+            if not CONF.service_available.get(service):
+                skip = True
+                break
+        if skip:
+            break
         if test.get('use_admin', False):
             manager = admin_manager
         else:
diff --git a/tempest/stress/etc/stress-tox-job.json b/tempest/stress/etc/stress-tox-job.json
index dffc469..9cee316 100644
--- a/tempest/stress/etc/stress-tox-job.json
+++ b/tempest/stress/etc/stress-tox-job.json
@@ -15,5 +15,14 @@
   "use_admin": false,
   "use_isolated_tenants": false,
   "kwargs": {}
+  },
+  {"action": "tempest.stress.actions.unit_test.UnitTest",
+  "threads": 4,
+  "use_admin": false,
+  "use_isolated_tenants": false,
+  "required_services": ["neutron"],
+  "kwargs": {"test_method": "tempest.scenario.test_network_advanced_server_ops.TestNetworkAdvancedServerOps.test_server_connectivity_stop_start",
+             "class_setup_per": "process"}
   }
 ]
+