Initial commit based on current tempest
This creates the tempest plugin for horizon and duplicates
the existing horizon scenario test in tempest.
Change-Id: I9430b6cefc884ecc105e91ec19a07987aa70ef98
diff --git a/tempest_horizon/__init__.py b/tempest_horizon/__init__.py
new file mode 100644
index 0000000..17934b2
--- /dev/null
+++ b/tempest_horizon/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+# 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.
+
+import pbr.version
+
+
+__version__ = pbr.version.VersionInfo(
+ 'tempest_horizon').version_string()
diff --git a/tempest_horizon/config.py b/tempest_horizon/config.py
new file mode 100644
index 0000000..bb53abb
--- /dev/null
+++ b/tempest_horizon/config.py
@@ -0,0 +1,26 @@
+# 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_config import cfg
+
+dashboard_group = cfg.OptGroup(name="dashboard",
+ title="Dashboard options")
+
+DashboardGroup = [
+ cfg.StrOpt('dashboard_url',
+ default='http://localhost/',
+ help="Where the dashboard can be found"),
+ cfg.StrOpt('login_url',
+ default='http://localhost/auth/login/',
+ help="Login page for the dashboard",
+ deprecated_for_removal=True),
+]
diff --git a/tempest_horizon/plugin.py b/tempest_horizon/plugin.py
new file mode 100644
index 0000000..490abe0
--- /dev/null
+++ b/tempest_horizon/plugin.py
@@ -0,0 +1,35 @@
+# 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.
+
+import os
+
+from tempest import config
+from tempest.test_discover import plugins
+
+from tempest_horizon import config as dashboard_config
+
+
+class HorizonTempestPlugin(plugins.TempestPlugin):
+ def load_tests(self):
+ base_path = os.path.split(os.path.dirname(
+ os.path.abspath(__file__)))[0]
+ test_dir = "tempest_horizon/tests"
+ full_test_dir = os.path.join(base_path, test_dir)
+ return full_test_dir, base_path
+
+ def register_opts(self, conf):
+ if 'dashboard' not in conf:
+ config.register_opt_group(conf, dashboard_config.dashboard_group,
+ dashboard_config.BotoGroup)
+
+ def get_opt_lists(self):
+ return [('dashboard', config.DashboardGroup)]
diff --git a/tempest_horizon/tests/__init__.py b/tempest_horizon/tests/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest_horizon/tests/__init__.py
diff --git a/tempest_horizon/tests/scenario/.test_dashboard_basic_ops.py.swp b/tempest_horizon/tests/scenario/.test_dashboard_basic_ops.py.swp
new file mode 100644
index 0000000..390c94d
--- /dev/null
+++ b/tempest_horizon/tests/scenario/.test_dashboard_basic_ops.py.swp
Binary files differ
diff --git a/tempest_horizon/tests/scenario/__init__.py b/tempest_horizon/tests/scenario/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest_horizon/tests/scenario/__init__.py
diff --git a/tempest_horizon/tests/scenario/test_dashboard_basic_ops.py b/tempest_horizon/tests/scenario/test_dashboard_basic_ops.py
new file mode 100644
index 0000000..5d4f7b3
--- /dev/null
+++ b/tempest_horizon/tests/scenario/test_dashboard_basic_ops.py
@@ -0,0 +1,120 @@
+# 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 six.moves import html_parser as HTMLParser
+from six.moves.urllib import parse
+from six.moves.urllib import request
+
+from tempest import config
+from tempest.scenario import manager
+from tempest import test
+
+CONF = config.CONF
+
+
+class HorizonHTMLParser(HTMLParser.HTMLParser):
+ csrf_token = None
+ region = None
+ login = None
+
+ def _find_name(self, attrs, name):
+ for attrpair in attrs:
+ if attrpair[0] == 'name' and attrpair[1] == name:
+ return True
+ return False
+
+ def _find_value(self, attrs):
+ for attrpair in attrs:
+ if attrpair[0] == 'value':
+ return attrpair[1]
+ return None
+
+ def _find_attr_value(self, attrs, attr_name):
+ for attrpair in attrs:
+ if attrpair[0] == attr_name:
+ return attrpair[1]
+ return None
+
+ def handle_starttag(self, tag, attrs):
+ if tag == 'input':
+ if self._find_name(attrs, 'csrfmiddlewaretoken'):
+ self.csrf_token = self._find_value(attrs)
+ if self._find_name(attrs, 'region'):
+ self.region = self._find_value(attrs)
+ if tag == 'form':
+ self.login = self._find_attr_value(attrs, 'action')
+
+
+class TestDashboardBasicOps(manager.ScenarioTest):
+
+ """The test suite for dashboard basic operations
+
+ This is a basic scenario test:
+ * checks that the login page is available
+ * logs in as a regular user
+ * checks that the user home page loads without error
+ """
+
+ @classmethod
+ def skip_checks(cls):
+ super(TestDashboardBasicOps, cls).skip_checks()
+ if not CONF.service_available.horizon:
+ raise cls.skipException("Horizon support is required")
+
+ @classmethod
+ def setup_credentials(cls):
+ cls.set_network_resources()
+ super(TestDashboardBasicOps, cls).setup_credentials()
+
+ def check_login_page(self):
+ response = request.urlopen(CONF.dashboard.dashboard_url)
+ self.assertIn("id_username", response.read())
+
+ def user_login(self, username, password):
+ self.opener = request.build_opener(request.HTTPCookieProcessor())
+ response = self.opener.open(CONF.dashboard.dashboard_url).read()
+
+ # Grab the CSRF token and default region
+ parser = HorizonHTMLParser()
+ parser.feed(response)
+
+ # construct login url for dashboard, discovery accommodates non-/ web
+ # root for dashboard
+ login_url = parse.urljoin(CONF.dashboard.dashboard_url, parser.login)
+
+ # Prepare login form request
+ req = request.Request(login_url)
+ req.add_header('Content-type', 'application/x-www-form-urlencoded')
+ req.add_header('Referer', CONF.dashboard.dashboard_url)
+
+ # Pass the default domain name regardless of the auth version in order
+ # to test the scenario of when horizon is running with keystone v3
+ params = {'username': username,
+ 'password': password,
+ 'region': parser.region,
+ 'domain': CONF.auth.default_credentials_domain_name,
+ 'csrfmiddlewaretoken': parser.csrf_token}
+ self.opener.open(req, parse.urlencode(params))
+
+ def check_home_page(self):
+ response = self.opener.open(CONF.dashboard.dashboard_url)
+ self.assertIn('Overview', response.read())
+
+ @test.idempotent_id('4f8851b1-0e69-482b-b63b-84c6e76f6c80')
+ @test.services('dashboard')
+ def test_basic_scenario(self):
+ creds = self.os.credentials
+ self.check_login_page()
+ self.user_login(creds.username, creds.password)
+ self.check_home_page()