Merge "Use find_test_caller in test_utils instead of in misc"
diff --git a/README.rst b/README.rst
index 725a890..fe65dcd 100644
--- a/README.rst
+++ b/README.rst
@@ -70,12 +70,12 @@
    it's recommended that you copy or rename tempest.conf.sample to tempest.conf
    and make those changes to that file in /etc/tempest
 
-#. Setup a local working Tempest dir. This is done by using the tempest init
+#. Setup a local Tempest workspace. This is done by using the tempest init
    command::
 
     $ tempest init cloud-01
 
-   works the same as::
+   which also works the same as::
 
     $ mkdir cloud-01 && cd cloud-01 && tempest init
 
@@ -91,8 +91,19 @@
    any changes to it otherwise Tempest will not know how to load it.
 
 #. Once the configuration is done you're now ready to run Tempest. This can
-   be done with testr directly or any `testr`_ based test runner, like
-   `ostestr`_. For example, from the working dir running::
+   be done using the :ref:`tempest_run` command. This can be done by either
+   running::
+
+     $ tempest run
+
+   from the Tempest workspace directory. Or you can use the ``--workspace``
+   argument to run in the workspace you created regarless of your current
+   working directory. For example::
+
+     $ tempest run --workspace cloud-01
+
+   There is also the option to use testr directly, or any `testr`_ based test
+   runner, like `ostestr`_. For example, from the workspace dir run::
 
      $ ostestr --regex '(?!.*\[.*\bslow\b.*\])(^tempest\.(api|scenario))'
 
diff --git a/doc/source/run.rst b/doc/source/run.rst
index 07fa5f7..ce7f03e 100644
--- a/doc/source/run.rst
+++ b/doc/source/run.rst
@@ -1,3 +1,5 @@
+.. _tempest_run:
+
 -----------
 Tempest Run
 -----------
diff --git a/requirements.txt b/requirements.txt
index 7d01f69..84be219 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -14,7 +14,7 @@
 oslo.i18n>=2.1.0 # Apache-2.0
 oslo.log>=1.14.0 # Apache-2.0
 oslo.serialization>=1.10.0 # Apache-2.0
-oslo.utils>=3.11.0 # Apache-2.0
+oslo.utils>=3.14.0 # Apache-2.0
 six>=1.9.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
 testscenarios>=0.4 # Apache-2.0/BSD
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 607bebe..b53cfae 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -13,10 +13,15 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import testtools
+
 from tempest.api.identity import base
 from tempest.common.utils import data_utils
+from tempest import config
 from tempest import test
 
+CONF = config.CONF
+
 
 class ProjectsTestJSON(base.BaseIdentityV3AdminTest):
 
@@ -52,6 +57,37 @@
         self.assertEqual(project_name, body['name'])
         self.assertEqual(self.data.domain['id'], body['domain_id'])
 
+    @testtools.skipUnless(CONF.identity_feature_enabled.reseller,
+                          'Reseller not available.')
+    @test.idempotent_id('1854f9c0-70bc-4d11-a08a-1c789d339e3d')
+    def test_project_create_with_parent(self):
+        # Create root project without providing a parent_id
+        self.data.setup_test_domain()
+        domain_id = self.data.domain['id']
+
+        root_project_name = data_utils.rand_name('root_project')
+        root_project = self.projects_client.create_project(
+            root_project_name, domain_id=domain_id)['project']
+        self.addCleanup(
+            self.projects_client.delete_project, root_project['id'])
+
+        root_project_id = root_project['id']
+        parent_id = root_project['parent_id']
+        self.assertEqual(root_project_name, root_project['name'])
+        # If not provided, the parent_id must point to the top level
+        # project in the hierarchy, i.e. its domain
+        self.assertEqual(domain_id, parent_id)
+
+        # Create a project using root_project_id as parent_id
+        project_name = data_utils.rand_name('project')
+        project = self.projects_client.create_project(
+            project_name, domain_id=domain_id,
+            parent_id=root_project_id)['project']
+        self.data.projects.append(project)
+        parent_id = project['parent_id']
+        self.assertEqual(project_name, project['name'])
+        self.assertEqual(root_project_id, parent_id)
+
     @test.idempotent_id('1f66dc76-50cc-4741-a200-af984509e480')
     def test_project_create_enabled(self):
         # Create a project that is enabled
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index 7a1e3a5..df39390 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -218,21 +218,21 @@
     def teardown_all(self):
         for user in self.users:
             test_utils.call_and_ignore_notfound_exc(
-                self.users_client.delete_user, user)
+                self.users_client.delete_user, user['id'])
         for tenant in self.tenants:
             test_utils.call_and_ignore_notfound_exc(
-                self.projects_client.delete_tenant, tenant)
+                self.projects_client.delete_tenant, tenant['id'])
         for project in reversed(self.projects):
             test_utils.call_and_ignore_notfound_exc(
-                self.projects_client.delete_project, project)
+                self.projects_client.delete_project, project['id'])
         for role in self.roles:
             test_utils.call_and_ignore_notfound_exc(
-                self.roles_client.delete_role, role)
+                self.roles_client.delete_role, role['id'])
         for domain in self.domains:
             test_utils.call_and_ignore_notfound_exc(
-                self.domains_client.update_domain, domain, enabled=False)
+                self.domains_client.update_domain, domain['id'], enabled=False)
             test_utils.call_and_ignore_notfound_exc(
-                self.domains_client.delete_domain, domain)
+                self.domains_client.delete_domain, domain['id'])
 
 
 class DataGeneratorV2(BaseDataGenerator):
diff --git a/tempest/cmd/run.py b/tempest/cmd/run.py
index 8777c0e..5270cac 100644
--- a/tempest/cmd/run.py
+++ b/tempest/cmd/run.py
@@ -64,6 +64,14 @@
 directly with you current working directory being the workspace, Tempest will
 take care of managing everything to be executed from there.
 
+Running from Anywhere
+---------------------
+Tempest run provides you with an option to execute tempest from anywhere on
+your system. You are required to provide a config file in this case with the
+``--config-file`` option. When run tempest will create a .testrepository
+directory and a .testr.conf file in your current working directory. This way
+you can use testr commands directly to inspect the state of the previous run.
+
 Test Output
 ===========
 By default tempest run's output to STDOUT will be generated using the
@@ -83,6 +91,7 @@
 from oslo_log import log as logging
 from testrepository.commands import run_argv
 
+from tempest.cmd import init
 from tempest.cmd import workspace
 from tempest import config
 
@@ -93,7 +102,9 @@
 
 class TempestRun(command.Command):
 
-    def _set_env(self):
+    def _set_env(self, config_file=None):
+        if config_file:
+            CONF.set_config_path(os.path.abspath(config_file))
         # NOTE(mtreinish): This is needed so that testr doesn't gobble up any
         # stacktraces on failure.
         if 'TESTR_PDB' in os.environ:
@@ -108,9 +119,19 @@
             if returncode:
                 sys.exit(returncode)
 
+    def _create_testr_conf(self):
+        top_level_path = os.path.dirname(os.path.dirname(__file__))
+        discover_path = os.path.join(top_level_path, 'test_discover')
+        file_contents = init.TESTR_CONF % (top_level_path, discover_path)
+        with open('.testr.conf', 'w+') as testr_conf_file:
+                testr_conf_file.write(file_contents)
+
     def take_action(self, parsed_args):
-        self._set_env()
         returncode = 0
+        if parsed_args.config_file:
+            self._set_env(parsed_args.config_file)
+        else:
+            self._set_env()
         # Workspace execution mode
         if parsed_args.workspace:
             workspace_mgr = workspace.WorkspaceManager(
@@ -126,6 +147,10 @@
             # If you're running in local execution mode and there is not a
             # testrepository dir create one
             self._create_testrepository()
+        # local execution with config file mode
+        elif parsed_args.config_file:
+            self._create_testr_conf()
+            self._create_testrepository()
         else:
             print("No .testr.conf file was found for local execution")
             sys.exit(2)
@@ -157,6 +182,9 @@
                             dest='workspace_path',
                             help="The path to the workspace file, the default "
                                  "is ~/.tempest/workspace.yaml")
+        # Configuration flags
+        parser.add_argument('--config-file', default=None, dest='config_file',
+                            help='Configuration file to run tempest with')
         # test selection args
         regex = parser.add_mutually_exclusive_group()
         regex.add_argument('--smoke', action='store_true',
diff --git a/tempest/config.py b/tempest/config.py
index eb5e23a..8ec8b24 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -191,7 +191,12 @@
                 help="A list of enabled identity extensions with a special "
                      "entry all which indicates every extension is enabled. "
                      "Empty list indicates all extensions are disabled. "
-                     "To get the list of extensions run: 'keystone discover'")
+                     "To get the list of extensions run: 'keystone discover'"),
+    # TODO(rodrigods): Remove the reseller flag when Kilo and Liberty is end
+    # of life.
+    cfg.BoolOpt('reseller',
+                default=False,
+                help='Does the environment support reseller?')
 ]
 
 compute_group = cfg.OptGroup(name='compute',