Merge "Basic starter scenario for testing the dashboard"
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 92371e8..033bc82 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -321,6 +321,13 @@
 # any key, which will generate a keypair for each test class
 #keypair_name = heat_key
 
+[dashboard]
+# URL where to find the dashboard home page
+dashboard_url = 'http://localhost/'
+
+# URL where to submit the login form
+login_url = 'http://localhost/auth/login/'
+
 [scenario]
 # Directory containing image files
 img_dir = /opt/stack/new/devstack/files/images/cirros-0.3.1-x86_64-uec
@@ -360,3 +367,5 @@
 nova = True
 # Whether or not Heat is expected to be available
 heat = false
+# Whether or not horizon is expected to be available
+horizon = True
diff --git a/tempest/config.py b/tempest/config.py
index eeb7b9d..a918d0b 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -414,6 +414,26 @@
     for opt in OrchestrationGroup:
         conf.register_opt(opt, group='orchestration')
 
+
+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"),
+]
+
+
+def register_dashboard_opts(conf):
+    conf.register_group(scenario_group)
+    for opt in DashboardGroup:
+        conf.register_opt(opt, group='dashboard')
+
+
 boto_group = cfg.OptGroup(name='boto',
                           title='EC2/S3 options')
 BotoConfig = [
@@ -558,6 +578,9 @@
     cfg.BoolOpt('heat',
                 default=False,
                 help="Whether or not Heat is expected to be available"),
+    cfg.BoolOpt('horizon',
+                default=True,
+                help="Whether or not Horizon is expected to be available"),
 ]
 
 
@@ -613,6 +636,7 @@
         register_volume_opts(cfg.CONF)
         register_object_storage_opts(cfg.CONF)
         register_orchestration_opts(cfg.CONF)
+        register_dashboard_opts(cfg.CONF)
         register_boto_opts(cfg.CONF)
         register_compute_admin_opts(cfg.CONF)
         register_stress_opts(cfg.CONF)
@@ -626,6 +650,7 @@
         self.volume = cfg.CONF.volume
         self.object_storage = cfg.CONF['object-storage']
         self.orchestration = cfg.CONF.orchestration
+        self.dashboard = cfg.CONF.dashboard
         self.boto = cfg.CONF.boto
         self.compute_admin = cfg.CONF['compute-admin']
         self.stress = cfg.CONF.stress
diff --git a/tempest/scenario/test_dashboard_basic_ops.py b/tempest/scenario/test_dashboard_basic_ops.py
new file mode 100644
index 0000000..9a45572
--- /dev/null
+++ b/tempest/scenario/test_dashboard_basic_ops.py
@@ -0,0 +1,72 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# 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.
+
+import urllib
+import urllib2
+
+from lxml import html
+
+from tempest.scenario import manager
+
+
+class TestDashboardBasicOps(manager.OfficialClientTest):
+
+    """
+    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 setUpClass(cls):
+        super(TestDashboardBasicOps, cls).setUpClass()
+
+        if not cls.config.service_available.horizon:
+            raise cls.skipException("Horizon support is required")
+
+    def check_login_page(self):
+        response = urllib2.urlopen(self.config.dashboard.dashboard_url)
+        self.assertIn("<h3>Log In</h3>", response.read())
+
+    def user_login(self):
+        self.opener = urllib2.build_opener(urllib2.HTTPCookieProcessor())
+        response = self.opener.open(self.config.dashboard.dashboard_url).read()
+
+        # Grab the CSRF token and default region
+        csrf_token = html.fromstring(response).xpath(
+            '//input[@name="csrfmiddlewaretoken"]/@value')[0]
+        region = html.fromstring(response).xpath(
+            '//input[@name="region"]/@value')[0]
+
+        # Prepare login form request
+        req = urllib2.Request(self.config.dashboard.login_url)
+        req.add_header('Content-type', 'application/x-www-form-urlencoded')
+        req.add_header('Referer', self.config.dashboard.dashboard_url)
+        params = {'username': self.config.identity.username,
+                  'password': self.config.identity.password,
+                  'region': region,
+                  'csrfmiddlewaretoken': csrf_token}
+        self.opener.open(req, urllib.urlencode(params))
+
+    def check_home_page(self):
+        response = self.opener.open(self.config.dashboard.dashboard_url)
+        self.assertIn('Overview', response.read())
+
+    def test_basic_scenario(self):
+        self.check_login_page()
+        self.user_login()
+        self.check_home_page()