Prepare network_resources as a stable interface

The network_resources attribute should not be a public interface,
so renaming it to _network_resources. Document set_network_resources
better; ensure it's only invoked before super's setup_credentials is
invoked to avoid the call being ignored and generate errors that
may be difficult to debug.

Change-Id: I4eab8f2a722b47edc20e4aab0ef453bec16842f3
diff --git a/tempest/test.py b/tempest/test.py
index 3967515..ce977fa 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -105,7 +105,10 @@
     # a list of roles - the first element of the list being a label, and the
     # rest the actual roles
     credentials = []
-    network_resources = {}
+
+    # Network resources to be provisioned for the requested test credentials.
+    # Only used with the dynamic credentials provider.
+    _network_resources = {}
 
     # Stack of resource cleanups
     _class_cleanups = []
@@ -127,6 +130,7 @@
 
     @classmethod
     def _reset_class(cls):
+        cls.__setup_credentials_called = False
         cls.__resource_cleaup_called = False
         cls._class_cleanups = []
 
@@ -272,6 +276,7 @@
         set_network_resources() method, otherwise it will create
         network resources(network resources are created in a later step).
         """
+        cls.__setup_credentials_called = True
         for credentials_type in cls.credentials:
             # This may raise an exception in case credentials are not available
             # In that case we want to let the exception through and the test
@@ -530,7 +535,7 @@
                                              False)
 
             cls._creds_provider = credentials.get_credentials_provider(
-                name=cls.__name__, network_resources=cls.network_resources,
+                name=cls.__name__, network_resources=cls._network_resources,
                 force_tenant_isolation=force_tenant_isolation)
         return cls._creds_provider
 
@@ -668,17 +673,45 @@
                               dhcp=False):
         """Specify which network resources should be created
 
+        The dynamic credentials provider by default provisions network
+        resources for each user/project that is provisioned. This behavior
+        can be altered using this method, which allows tests to define which
+        specific network resources to be provisioned - none if no parameter
+        is specified.
+
+        Credentials are provisioned as part of the class setup fixture,
+        during the `setup_credentials` step. For this to be effective this
+        helper must be invoked before super's `setup_credentials` is executed.
+
         @param network
         @param router
         @param subnet
         @param dhcp
+
+        Example::
+
+            @classmethod
+            def setup_credentials(cls):
+                # Do not setup network resources for this test
+                cls.set_network_resources()
+                super(MyTest, cls).setup_credentials()
         """
-        # network resources should be set only once from callers
+        # If this is invoked after the credentials are setup, it won't take
+        # any effect. To avoid this situation, fail the test in case this was
+        # invoked too late in the test lifecycle.
+        if cls.__setup_credentials_called:
+            raise RuntimeError(
+                "set_network_resources invoked after setup_credentials on the "
+                "super class has been already invoked. For "
+                "set_network_resources to have effect please invoke it before "
+                "the call to super().setup_credentials")
+
+        # Network resources should be set only once from callers
         # in order to ensure that even if it's called multiple times in
         # a chain of overloaded methods, the attribute is set only
-        # in the leaf class
-        if not cls.network_resources:
-            cls.network_resources = {
+        # in the leaf class.
+        if not cls._network_resources:
+            cls._network_resources = {
                 'network': network,
                 'router': router,
                 'subnet': subnet,
diff --git a/tempest/tests/test_test.py b/tempest/tests/test_test.py
index 72e3ac7..f2c28a6 100644
--- a/tempest/tests/test_test.py
+++ b/tempest/tests/test_test.py
@@ -161,6 +161,73 @@
         self.assertEqual(0, mock_clean_vr.call_count)
 
 
+class TestSetNetworkResources(base.TestCase):
+
+    def setUp(self):
+        super(TestSetNetworkResources, self).setUp()
+
+        class ParentTest(test.BaseTestCase):
+
+            @classmethod
+            def setup_credentials(cls):
+                cls.set_network_resources(dhcp=True)
+                super(ParentTest, cls).setup_credentials()
+
+            def runTest(self):
+                pass
+
+        self.parent_class = ParentTest
+
+    def test_set_network_resources_child_only(self):
+
+        class ChildTest(self.parent_class):
+
+            @classmethod
+            def setup_credentials(cls):
+                cls.set_network_resources(router=True)
+                super(ChildTest, cls).setup_credentials()
+
+        child_test = ChildTest()
+        child_test.setUpClass()
+        # Assert that the parents network resources are not set
+        self.assertFalse(child_test._network_resources['dhcp'])
+        # Assert that the child network resources are set
+        self.assertTrue(child_test._network_resources['router'])
+
+    def test_set_network_resources_right_order(self):
+
+        class ChildTest(self.parent_class):
+
+            @classmethod
+            def setup_credentials(cls):
+                super(ChildTest, cls).setup_credentials()
+                cls.set_network_resources(router=True)
+
+        child_test = ChildTest()
+        with testtools.ExpectedException(RuntimeError,
+                                         value_re='set_network_resources'):
+            child_test.setUpClass()
+
+    def test_set_network_resources_children(self):
+
+        class ChildTest(self.parent_class):
+
+            @classmethod
+            def setup_credentials(cls):
+                cls.set_network_resources(router=True)
+                super(ChildTest, cls).setup_credentials()
+
+        class GrandChildTest(ChildTest):
+            pass
+
+        # Invoke setupClass on both and check that the setup_credentials
+        # call check mechanism does not report any false negative.
+        child_test = ChildTest()
+        child_test.setUpClass()
+        grandchild_test = GrandChildTest()
+        grandchild_test.setUpClass()
+
+
 class TestTempestBaseTestClass(base.TestCase):
 
     def setUp(self):