Merge "Rework class inheritance for scenario tests"
diff --git a/run_tests.sh b/run_tests.sh
index a645b22..f8636c1 100755
--- a/run_tests.sh
+++ b/run_tests.sh
@@ -11,23 +11,22 @@
   echo "  -u, --update             Update the virtual environment with any newer package versions"
   echo "  -s, --smoke              Only run smoke tests"
   echo "  -w, --whitebox           Only run whitebox tests"
-  echo "  -t, --with-testr         Run using testr instead of nose"
+  echo "  -t, --parallel           Run testr parallel"
   echo "  -c, --nova-coverage      Enable Nova coverage collection"
   echo "  -C, --config             Config file location"
   echo "  -p, --pep8               Just run pep8"
   echo "  -h, --help               Print this usage message"
   echo "  -d, --debug              Debug this script -- set -o xtrace"
-  echo "  -S, --stdout             Don't capture stdout"
   echo "  -l, --logging            Enable logging"
   echo "  -L, --logging-config     Logging config file location.  Default is etc/logging.conf"
-  echo "  -- [NOSEOPTIONS]         After the first '--' you can pass arbitrary arguments to nosetests "
+  echo "  -- [TESTROPTIONS]        After the first '--' you can pass arbitrary arguments to testr "
 }
 
-noseargs=""
+testrargs=""
 just_pep8=0
 venv=.venv
 with_venv=tools/with_venv.sh
-with_testr=0
+parallel=0
 always_venv=0
 never_venv=0
 no_site_packages=0
@@ -39,7 +38,7 @@
 logging=0
 logging_config=etc/logging.conf
 
-if ! options=$(getopt -o VNnfuswtcphdSC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,with-testr,nova-coverage,pep8,help,debug,stdout,config:,logging,logging-config: -- "$@")
+if ! options=$(getopt -o VNnfuswtcphdC:lL: -l virtual-env,no-virtual-env,no-site-packages,force,update,smoke,whitebox,parallel,nova-coverage,pep8,help,debug,config:,logging,logging-config: -- "$@")
 then
     # parse error
     usage
@@ -60,14 +59,13 @@
     -c|--nova-coverage) let nova_coverage=1;;
     -C|--config) config_file=$2; shift;;
     -p|--pep8) let just_pep8=1;;
-    -s|--smoke) noseargs="$noseargs --attr=type=smoke";;
-    -w|--whitebox) noseargs="$noseargs --attr=type=whitebox";;
-    -t|--with-testr) with_testr=1;;
-    -S|--stdout) noseargs="$noseargs -s";;
+    -s|--smoke) testrargs="$testrargs smoke";;
+    -w|--whitebox) testrargs="$testrargs whitebox";;
+    -t|--parallel) parallel=1;;
     -l|--logging) logging=1;;
     -L|--logging-config) logging_config=$2; shift;;
-    --) [ "yes" == "$first_uu" ] || noseargs="$noseargs $1"; first_uu=no  ;;
-    *) noseargs="$noseargs $1"
+    --) [ "yes" == "$first_uu" ] || testrargs="$testrargs $1"; first_uu=no  ;;
+    *) testrargs="$testrargs $1"
   esac
   shift
 done
@@ -90,24 +88,10 @@
 
 cd `dirname "$0"`
 
-export NOSE_WITH_OPENSTACK=1
-export NOSE_OPENSTACK_COLOR=1
-export NOSE_OPENSTACK_RED=15.00
-export NOSE_OPENSTACK_YELLOW=3.00
-export NOSE_OPENSTACK_SHOW_ELAPSED=1
-export NOSE_OPENSTACK_STDOUT=1
-
 if [ $no_site_packages -eq 1 ]; then
   installvenvopts="--no-site-packages"
 fi
 
-# only add tempest default if we don't specify a test
-if [[ "x$noseargs" =~ "tempest" ]]; then
-  noseargs="$noseargs"
-else
-  noseargs="$noseargs tempest"
-fi
-
 function testr_init {
   if [ ! -d .testrepository ]; then
       ${wrapper} testr init
@@ -115,12 +99,12 @@
 }
 
 function run_tests {
-  if [ $with_testr -eq 1 ]; then
-      testr_init
-      ${wrapper} find . -type f -name "*.pyc" -delete
-      ${wrapper} testr run --parallel --subunit $noseargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
+  testr_init
+  ${wrapper} find . -type f -name "*.pyc" -delete
+  if [ $parallel -eq 1 ]; then
+      ${wrapper} testr run --parallel --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
   else
-      ${wrapper} $NOSETESTS
+      ${wrapper} testr run --subunit $testrargs | ${wrapper} subunit-2to1 | ${wrapper} tools/colorizer.py
   fi
 }
 
@@ -139,8 +123,6 @@
   ${wrapper} python tools/tempest_coverage.py -c report
 }
 
-NOSETESTS="nosetests $noseargs"
-
 if [ $never_venv -eq 0 ]
 then
   # Remove the virtual environment if --force used
@@ -187,7 +169,7 @@
     run_coverage_report
 fi
 
-if [ -z "$noseargs" ]; then
+if [ -z "$testrargs" ]; then
     run_pep8
 fi
 
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 703f143..e09a23f 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -255,6 +255,20 @@
         self.assertRaises(exceptions.NotFound, self.client.get_server,
                           '999erra43')
 
+    @attr(type=['negative', 'gate'])
+    def test_stop_non_existent_server(self):
+        # Stop a non existent server
+        non_exist_id = rand_name('non-existent-server')
+        self.assertRaises(exceptions.NotFound, self.servers_client.stop,
+                          non_exist_id)
+
+    @attr(type=['negative', 'gate'])
+    def test_pause_non_existent_server(self):
+        # pause a non existent server
+        non_exist_id = rand_name('non-existent-server')
+        self.assertRaises(exceptions.NotFound, self.client.pause_server,
+                          non_exist_id)
+
 
 class ServersNegativeTestXML(ServersNegativeTestJSON):
     _interface = 'xml'
diff --git a/tempest/scenario/test_volume_snapshot_pattern.py b/tempest/scenario/test_volume_snapshot_pattern.py
new file mode 100644
index 0000000..4d8a400
--- /dev/null
+++ b/tempest/scenario/test_volume_snapshot_pattern.py
@@ -0,0 +1,122 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+#    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.openstack.common import log as logging
+
+from tempest.common.utils.data_utils import rand_name
+from tempest.scenario import manager
+
+LOG = logging.getLogger(__name__)
+
+
+class TestVolumeSnapshotPattern(manager.OfficialClientTest):
+
+    """
+    This test case attempts to reproduce the following steps:
+
+     * Create in Cinder some bootable volume importing a Glance image
+     * Boot an instance from the bootable volume
+     * Create a volume snapshot while the instance is running
+     * Boot an additional instance from the new snapshot based volume
+    """
+
+    def _create_volume_from_image(self):
+        img_uuid = self.config.compute.image_ref
+        vol_name = rand_name('volume-origin')
+        vol = self.volume_client.volumes.create(size=1,
+                                                display_name=vol_name,
+                                                imageRef=img_uuid)
+        self.set_resource(vol.id, vol)
+        self.status_timeout(self.volume_client.volumes,
+                            vol.id,
+                            'available')
+        return vol
+
+    def _boot_instance_from_volume(self, vol_id):
+        # NOTE(gfidente): the img_uuid here is only needed because
+        # the novaclient requires it to be passed as arg
+        img_uuid = self.config.compute.image_ref
+        i_name = rand_name('instance')
+        flavor_id = self.config.compute.flavor_ref
+        # NOTE(gfidente): the syntax for block_device_mapping is
+        # dev_name=id:type:size:delete_on_terminate
+        # where type needs to be "snap" if the server is booted
+        # from a snapshot, size instead can be safely left empty
+        bd_map = {
+            'vda': vol_id + ':::0'
+        }
+        create_kwargs = {
+            'block_device_mapping': bd_map
+        }
+        i = self.compute_client.servers.create(name=i_name,
+                                               image=img_uuid,
+                                               flavor=flavor_id,
+                                               **create_kwargs)
+        self.set_resource(i.id, i)
+        self.status_timeout(self.compute_client.servers,
+                            i.id,
+                            'ACTIVE')
+        return i
+
+    def _create_snapshot_from_volume(self, vol_id):
+        volume_snapshots = self.volume_client.volume_snapshots
+        snap_name = rand_name('snapshot')
+        snap = volume_snapshots.create(volume_id=vol_id,
+                                       force=True,
+                                       display_name=snap_name)
+        self.set_resource(snap.id, snap)
+        self.status_timeout(volume_snapshots,
+                            snap.id,
+                            'available')
+        return snap
+
+    def _create_volume_from_snapshot(self, snap_id):
+        vol_name = rand_name('volume')
+        vol = self.volume_client.volumes.create(size=1,
+                                                display_name=vol_name,
+                                                snapshot_id=snap_id)
+        self.set_resource(vol.id, vol)
+        self.status_timeout(self.volume_client.volumes,
+                            vol.id,
+                            'available')
+        return vol
+
+    def _stop_instances(self, instances):
+        # NOTE(gfidente): two loops so we do not wait for the status twice
+        for i in instances:
+            self.compute_client.servers.stop(i)
+        for i in instances:
+            self.status_timeout(self.compute_client.servers,
+                                i.id,
+                                'SHUTOFF')
+
+    def _detach_volumes(self, volumes):
+        # NOTE(gfidente): two loops so we do not wait for the status twice
+        for v in volumes:
+            self.volume_client.volumes.detach(v)
+        for v in volumes:
+            self.status_timeout(self.volume_client.volumes,
+                                v.id,
+                                'available')
+
+    def test_volume_snapshot_pattern(self):
+        volume_origin = self._create_volume_from_image()
+        i_origin = self._boot_instance_from_volume(volume_origin.id)
+        snapshot = self._create_snapshot_from_volume(volume_origin.id)
+        volume = self._create_volume_from_snapshot(snapshot.id)
+        i = self._boot_instance_from_volume(volume.id)
+        # NOTE(gfidente): ensure resources are in clean state for
+        # deletion operations to succeed
+        self._stop_instances([i_origin, i])
+        self._detach_volumes([volume_origin, volume])
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
index 490d263..dd7b682 100755
--- a/tools/pretty_tox_serial.sh
+++ b/tools/pretty_tox_serial.sh
@@ -5,5 +5,5 @@
 if [ ! -d .testrepository ]; then
     testr init
 fi
-testr run --subunit $TESTRARGS | subunit-2to1 | tools/colorizer.py
+testr run --subunit $TESTRARGS | subunit2pyunit
 testr slowest
diff --git a/tox.ini b/tox.ini
index c3562e6..53f85c1 100644
--- a/tox.ini
+++ b/tox.ini
@@ -13,22 +13,11 @@
 commands =
   python setup.py testr --slowest
 
-
 [testenv:full]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
-         NOSE_WITH_OPENSTACK=1
-         NOSE_OPENSTACK_COLOR=1
-         NOSE_OPENSTACK_RED=15
-         NOSE_OPENSTACK_YELLOW=3
-         NOSE_OPENSTACK_SHOW_ELAPSED=1
-         NOSE_OPENSTACK_STDOUT=1
-commands =
-  nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli
-
-[testenv:testr-serial]
-sitepackages = True
-setenv = VIRTUAL_ENV={envdir}
+# The regex below is used to select which tests to run and exclude the slow tag:
+# See the testrepostiory bug: https://bugs.launchpad.net/testrepository/+bug/1208610
 commands =
   sh tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
 
@@ -41,27 +30,15 @@
 [testenv:smoke]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
-         NOSE_WITH_OPENSTACK=1
-         NOSE_OPENSTACK_COLOR=1
-         NOSE_OPENSTACK_RED=15
-         NOSE_OPENSTACK_YELLOW=3
-         NOSE_OPENSTACK_SHOW_ELAPSED=1
-         NOSE_OPENSTACK_STDOUT=1
 commands =
-   nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit -sv --attr=type=smoke --xunit-file=nosetests-smoke.xml tempest
+   sh tools/pretty_tox_serial.sh 'smoke {posargs}'
 
 [testenv:coverage]
 sitepackages = True
 setenv = VIRTUAL_ENV={envdir}
-         NOSE_WITH_OPENSTACK=1
-         NOSE_OPENSTACK_COLOR=1
-         NOSE_OPENSTACK_RED=15
-         NOSE_OPENSTACK_YELLOW=3
-         NOSE_OPENSTACK_SHOW_ELAPSED=1
-         NOSE_OPENSTACK_STDOUT=1
 commands =
    python -m tools/tempest_coverage -c start --combine
-   nosetests --logging-format '%(asctime)-15s %(message)s' --with-xunit --xunit-file=nosetests-full.xml -sv tempest/api tempest/scenario tempest/thirdparty tempest/cli
+   sh tools/pretty_tox_serial.sh '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario|thirdparty|cli)) {posargs}'
    python -m tools/tempest_coverage -c report --html {posargs}
 
 [testenv:stress]