Merge "Initial skeleton for devstack-tempest base job"
diff --git a/doc/source/_extra/.htaccess b/doc/source/_extra/.htaccess
new file mode 100644
index 0000000..7745594
--- /dev/null
+++ b/doc/source/_extra/.htaccess
@@ -0,0 +1 @@
+redirectmatch 301 ^/developer/tempest/(.*) /tempest/latest/$1
diff --git a/doc/source/conf.py b/doc/source/conf.py
index 067eb81..0a061b8 100644
--- a/doc/source/conf.py
+++ b/doc/source/conf.py
@@ -154,6 +154,9 @@
 # relative to this directory. They are copied after the builtin static files,
 # so a file named "default.css" will overwrite the builtin "default.css".
 html_static_path = ['_static']
+# Add any paths that contain "extra" files, such as .htaccess or
+# robots.txt.
+html_extra_path = ['_extra']
 
 # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 # using the given strftime format.
diff --git a/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml b/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
new file mode 100644
index 0000000..e23abe3
--- /dev/null
+++ b/releasenotes/notes/add-support-args-kwargs-in-call-until-true-a91k592h5a64exf7.yaml
@@ -0,0 +1,5 @@
+---
+features:
+  - Add support of args and kwargs when calling func in call_until_true,
+    also to log the cost time when call_until_true returns True or False
+    for debuggin.
diff --git a/requirements.txt b/requirements.txt
index 8a2fa99..4b8de27 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -12,7 +12,7 @@
 oslo.config>=4.6.0 # Apache-2.0
 oslo.log>=3.30.0 # Apache-2.0
 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0
-oslo.utils>=3.28.0 # Apache-2.0
+oslo.utils>=3.31.0 # Apache-2.0
 six>=1.9.0 # MIT
 fixtures>=3.0.0 # Apache-2.0/BSD
 PyYAML>=3.10 # MIT
diff --git a/tempest/api/compute/admin/test_fixed_ips.py b/tempest/api/compute/admin/test_fixed_ips.py
index ebba73c..66c2c2d 100644
--- a/tempest/api/compute/admin/test_fixed_ips.py
+++ b/tempest/api/compute/admin/test_fixed_ips.py
@@ -42,6 +42,7 @@
         super(FixedIPsTestJson, cls).resource_setup()
         server = cls.create_test_server(wait_until='ACTIVE')
         server = cls.servers_client.show_server(server['id'])['server']
+        cls.ip = None
         for ip_set in server['addresses']:
             for ip in server['addresses'][ip_set]:
                 if ip['OS-EXT-IPS:type'] == 'fixed':
@@ -49,6 +50,9 @@
                     break
             if cls.ip:
                 break
+        if cls.ip is None:
+            raise cls.skipException("No fixed ip found for server: %s"
+                                    % server['id'])
 
     @decorators.idempotent_id('16b7d848-2f7c-4709-85a3-2dfb4576cc52')
     def test_list_fixed_ip_details(self):
diff --git a/tempest/api/compute/admin/test_fixed_ips_negative.py b/tempest/api/compute/admin/test_fixed_ips_negative.py
index a5deb3c..7d41f46 100644
--- a/tempest/api/compute/admin/test_fixed_ips_negative.py
+++ b/tempest/api/compute/admin/test_fixed_ips_negative.py
@@ -43,6 +43,7 @@
         super(FixedIPsNegativeTestJson, cls).resource_setup()
         server = cls.create_test_server(wait_until='ACTIVE')
         server = cls.servers_client.show_server(server['id'])['server']
+        cls.ip = None
         for ip_set in server['addresses']:
             for ip in server['addresses'][ip_set]:
                 if ip['OS-EXT-IPS:type'] == 'fixed':
@@ -50,6 +51,9 @@
                     break
             if cls.ip:
                 break
+        if cls.ip is None:
+            raise cls.skipException("No fixed ip found for server: %s"
+                                    % server['id'])
 
     @decorators.attr(type=['negative'])
     @decorators.idempotent_id('9f17f47d-daad-4adc-986e-12370c93e407')
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 6fe4d82..bce7524 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -281,45 +281,61 @@
         self.assertEqual(self.server_id,
                          vol_after_rebuild['attachments'][0]['server_id'])
 
-    def _test_resize_server_confirm(self, stop=False):
+    def _test_resize_server_confirm(self, server_id, stop=False):
         # The server's RAM and disk space should be modified to that of
         # the provided flavor
 
         if stop:
-            self.client.stop_server(self.server_id)
-            waiters.wait_for_server_status(self.client, self.server_id,
+            self.client.stop_server(server_id)
+            waiters.wait_for_server_status(self.client, server_id,
                                            'SHUTOFF')
 
-        self.client.resize_server(self.server_id, self.flavor_ref_alt)
+        self.client.resize_server(server_id, self.flavor_ref_alt)
         # NOTE(jlk): Explicitly delete the server to get a new one for later
         # tests. Avoids resize down race issues.
-        self.addCleanup(self.delete_server, self.server_id)
-        waiters.wait_for_server_status(self.client, self.server_id,
+        self.addCleanup(self.delete_server, server_id)
+        waiters.wait_for_server_status(self.client, server_id,
                                        'VERIFY_RESIZE')
 
-        self.client.confirm_resize_server(self.server_id)
+        self.client.confirm_resize_server(server_id)
         expected_status = 'SHUTOFF' if stop else 'ACTIVE'
-        waiters.wait_for_server_status(self.client, self.server_id,
+        waiters.wait_for_server_status(self.client, server_id,
                                        expected_status)
 
-        server = self.client.show_server(self.server_id)['server']
+        server = self.client.show_server(server_id)['server']
         self.assertEqual(self.flavor_ref_alt, server['flavor']['id'])
 
         if stop:
             # NOTE(mriedem): tearDown requires the server to be started.
-            self.client.start_server(self.server_id)
+            self.client.start_server(server_id)
 
     @decorators.idempotent_id('1499262a-9328-4eda-9068-db1ac57498d2')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize not available.')
     def test_resize_server_confirm(self):
-        self._test_resize_server_confirm(stop=False)
+        self._test_resize_server_confirm(self.server_id, stop=False)
+
+    @decorators.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b')
+    @decorators.related_bug('1728603')
+    @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+                          'Resize not available.')
+    @utils.services('volume')
+    def test_resize_volume_backed_server_confirm(self):
+        # We have to create a new server that is volume-backed since the one
+        # from setUp is not volume-backed.
+        server = self.create_test_server(
+            volume_backed=True, wait_until='ACTIVE')
+        self._test_resize_server_confirm(server['id'])
+        # Now do something interactive with the guest like get its console
+        # output; we don't actually care about the output, just that it doesn't
+        # raise an error.
+        self.client.get_console_output(server['id'])
 
     @decorators.idempotent_id('138b131d-66df-48c9-a171-64f45eb92962')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
                           'Resize not available.')
     def test_resize_server_confirm_from_stopped(self):
-        self._test_resize_server_confirm(stop=True)
+        self._test_resize_server_confirm(self.server_id, stop=True)
 
     @decorators.idempotent_id('c03aab19-adb1-44f5-917d-c419577e9e68')
     @testtools.skipUnless(CONF.compute_feature_enabled.resize,
diff --git a/tempest/lib/common/utils/test_utils.py b/tempest/lib/common/utils/test_utils.py
index bd0db7c..c2e93ee 100644
--- a/tempest/lib/common/utils/test_utils.py
+++ b/tempest/lib/common/utils/test_utils.py
@@ -86,22 +86,29 @@
         pass
 
 
-def call_until_true(func, duration, sleep_for):
+def call_until_true(func, duration, sleep_for, *args, **kwargs):
     """Call the given function until it returns True (and return True)
 
     or until the specified duration (in seconds) elapses (and return False).
 
-    :param func: A zero argument callable that returns True on success.
+    :param func: A callable that returns True on success.
     :param duration: The number of seconds for which to attempt a
         successful call of the function.
     :param sleep_for: The number of seconds to sleep after an unsuccessful
                       invocation of the function.
+    :param args: args that are passed to func.
+    :param kwargs: kwargs that are passed to func.
     """
     now = time.time()
+    begin_time = now
     timeout = now + duration
     while now < timeout:
-        if func():
+        if func(*args, **kwargs):
+            LOG.debug("Call %s returns true in %f seconds",
+                      getattr(func, '__name__'), time.time() - begin_time)
             return True
         time.sleep(sleep_for)
         now = time.time()
+    LOG.debug("Call %s returns false in %f seconds",
+              getattr(func, '__name__'), duration)
     return False
diff --git a/tempest/scenario/test_network_v6.py b/tempest/scenario/test_network_v6.py
index 210f0ba..934e1dd 100644
--- a/tempest/scenario/test_network_v6.py
+++ b/tempest/scenario/test_network_v6.py
@@ -12,8 +12,6 @@
 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 #    License for the specific language governing permissions and limitations
 #    under the License.
-import functools
-
 from tempest.common import utils
 from tempest import config
 from tempest.lib.common.utils import test_utils
@@ -183,17 +181,13 @@
         for i in range(n_subnets6):
             # v6 should be configured since the image supports it
             # It can take time for ipv6 automatic address to get assigned
-            srv1_v6_addr_assigned = functools.partial(
-                guest_has_address, sshv4_1, ips_from_api_1['6'][i])
+            self.assertTrue(test_utils.call_until_true(guest_has_address,
+                            CONF.validation.ping_timeout, 1,
+                            sshv4_1, ips_from_api_1['6'][i]))
 
-            srv2_v6_addr_assigned = functools.partial(
-                guest_has_address, sshv4_2, ips_from_api_2['6'][i])
-
-            self.assertTrue(test_utils.call_until_true(srv1_v6_addr_assigned,
-                            CONF.validation.ping_timeout, 1))
-
-            self.assertTrue(test_utils.call_until_true(srv2_v6_addr_assigned,
-                            CONF.validation.ping_timeout, 1))
+            self.assertTrue(test_utils.call_until_true(guest_has_address,
+                            CONF.validation.ping_timeout, 1,
+                            sshv4_2, ips_from_api_2['6'][i]))
 
         self.check_remote_connectivity(sshv4_1, ips_from_api_2['4'])
         self.check_remote_connectivity(sshv4_2, ips_from_api_1['4'])
diff --git a/tempest/scenario/test_server_advanced_ops.py b/tempest/scenario/test_server_advanced_ops.py
index d4f29ad..89b9fdd 100644
--- a/tempest/scenario/test_server_advanced_ops.py
+++ b/tempest/scenario/test_server_advanced_ops.py
@@ -42,28 +42,6 @@
         super(TestServerAdvancedOps, cls).setup_credentials()
 
     @decorators.attr(type='slow')
-    @decorators.idempotent_id('e6c28180-7454-4b59-b188-0257af08a63b')
-    @testtools.skipUnless(CONF.compute_feature_enabled.resize,
-                          'Resize is not available.')
-    @utils.services('compute', 'volume')
-    def test_resize_volume_backed_server_confirm(self):
-        # We create an instance for use in this test
-        instance = self.create_server(volume_backed=True)
-        instance_id = instance['id']
-        resize_flavor = CONF.compute.flavor_ref_alt
-        LOG.debug("Resizing instance %s from flavor %s to flavor %s",
-                  instance['id'], instance['flavor']['id'], resize_flavor)
-        self.servers_client.resize_server(instance_id, resize_flavor)
-        waiters.wait_for_server_status(self.servers_client, instance_id,
-                                       'VERIFY_RESIZE')
-
-        LOG.debug("Confirming resize of instance %s", instance_id)
-        self.servers_client.confirm_resize_server(instance_id)
-
-        waiters.wait_for_server_status(self.servers_client, instance_id,
-                                       'ACTIVE')
-
-    @decorators.attr(type='slow')
     @decorators.idempotent_id('949da7d5-72c8-4808-8802-e3d70df98e2c')
     @testtools.skipUnless(CONF.compute_feature_enabled.suspend,
                           'Suspend is not available.')
diff --git a/tempest/test_discover/plugins.py b/tempest/test_discover/plugins.py
index 1206e3f..9c18052 100644
--- a/tempest/test_discover/plugins.py
+++ b/tempest/test_discover/plugins.py
@@ -76,7 +76,7 @@
                 conf.register_opt(my_config.service_option,
                                   group='service_available')
                 conf.register_group(my_config.my_service_group)
-                conf.register_opts(my_config.MyService +
+                conf.register_opts(my_config.MyServiceGroup,
                                    my_config.my_service_group)
 
                 conf.register_group(my_config.my_service_feature_group)
diff --git a/tempest/tests/lib/common/utils/test_test_utils.py b/tempest/tests/lib/common/utils/test_test_utils.py
index 29c5684..f638ba6 100644
--- a/tempest/tests/lib/common/utils/test_test_utils.py
+++ b/tempest/tests/lib/common/utils/test_test_utils.py
@@ -81,11 +81,13 @@
     @mock.patch('time.sleep')
     @mock.patch('time.time')
     def test_call_until_true_when_f_never_returns_true(self, m_time, m_sleep):
+        def set_value(bool_value):
+            return bool_value
         timeout = 42  # The value doesn't matter as we mock time.time()
         sleep = 60  # The value doesn't matter as we mock time.sleep()
         m_time.side_effect = utils.generate_timeout_series(timeout)
         self.assertEqual(
-            False, test_utils.call_until_true(lambda: False, timeout, sleep)
+            False, test_utils.call_until_true(set_value, timeout, sleep, False)
         )
         m_sleep.call_args_list = [mock.call(sleep)] * 2
         m_time.call_args_list = [mock.call()] * 2
@@ -93,11 +95,30 @@
     @mock.patch('time.sleep')
     @mock.patch('time.time')
     def test_call_until_true_when_f_returns_true(self, m_time, m_sleep):
+        def set_value(bool_value=False):
+            return bool_value
         timeout = 42  # The value doesn't matter as we mock time.time()
         sleep = 60  # The value doesn't matter as we mock time.sleep()
         m_time.return_value = 0
         self.assertEqual(
-            True, test_utils.call_until_true(lambda: True, timeout, sleep)
+            True, test_utils.call_until_true(set_value, timeout, sleep,
+                                             bool_value=True)
         )
         self.assertEqual(0, m_sleep.call_count)
-        self.assertEqual(1, m_time.call_count)
+        # when logging cost time we need to acquire current time.
+        self.assertEqual(2, m_time.call_count)
+
+    @mock.patch('time.sleep')
+    @mock.patch('time.time')
+    def test_call_until_true_when_f_returns_true_no_param(
+            self, m_time, m_sleep):
+        def set_value(bool_value=False):
+            return bool_value
+        timeout = 42  # The value doesn't matter as we mock time.time()
+        sleep = 60  # The value doesn't matter as we mock time.sleep()
+        m_time.side_effect = utils.generate_timeout_series(timeout)
+        self.assertEqual(
+            False, test_utils.call_until_true(set_value, timeout, sleep)
+        )
+        m_sleep.call_args_list = [mock.call(sleep)] * 2
+        m_time.call_args_list = [mock.call()] * 2