Move horizon test from tempest-horizon to tempest

As disscussed in Wallaby PTG[1], QA and Horizon team
decided to move the horizon dashboard test from tempest-horizon
to Tempest. As next step, we can remove the tempest-horizon
plugin which will ease the maintaince of horizon tempest test.

[1] https://etherpad.opendev.org/p/qa-wallaby-ptg

Change-Id: Id2ced856a41548a0b49e594ee5fed6ed28785f24
This commit is contained in:
Ghanshyam Mann 2021-01-20 15:27:16 -06:00 committed by Ghanshyam
parent 3e05a15d9c
commit eb3c7e37bc
5 changed files with 169 additions and 0 deletions

View File

@ -0,0 +1,6 @@
---
prelude: >
The integrated horizon dashboard test is now moved
from tempest-horizon plugin into Tempest. You do not need
to install tempest-horizon to run the horizon test which
can be run using Tempest itself.

View File

@ -59,6 +59,7 @@ def get_service_list():
# So we should set this True here.
'identity': True,
'object_storage': CONF.service_available.swift,
'dashboard': CONF.service_available.horizon,
}
return service_list

View File

@ -828,6 +828,18 @@ NetworkFeaturesGroup = [
'This value will be increased in case of conflict.')
]
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.BoolOpt('disable_ssl_certificate_validation',
default=False,
help="Set to True if using self-signed SSL certificates."),
]
validation_group = cfg.OptGroup(name='validation',
title='SSH Validation options')
@ -1173,6 +1185,9 @@ ServiceAvailableGroup = [
cfg.BoolOpt('nova',
default=True,
help="Whether or not nova is expected to be available"),
cfg.BoolOpt('horizon',
default=True,
help="Whether or not horizon is expected to be available"),
]
debug_group = cfg.OptGroup(name="debug",
@ -1236,6 +1251,7 @@ _opts = [
(image_feature_group, ImageFeaturesGroup),
(network_group, NetworkGroup),
(network_feature_group, NetworkFeaturesGroup),
(dashboard_group, DashboardGroup),
(validation_group, ValidationGroup),
(volume_group, VolumeGroup),
(volume_feature_group, VolumeFeaturesGroup),
@ -1303,6 +1319,7 @@ class TempestConfigPrivate(object):
self.image_feature_enabled = _CONF['image-feature-enabled']
self.network = _CONF.network
self.network_feature_enabled = _CONF['network-feature-enabled']
self.dashboard = _CONF.dashboard
self.validation = _CONF.validation
self.volume = _CONF.volume
self.volume_feature_enabled = _CONF['volume-feature-enabled']

View File

@ -0,0 +1,141 @@
# 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 html.parser
import ssl
from urllib import parse
from urllib import request
from tempest.common import utils
from tempest import config
from tempest.lib import decorators
from tempest import test
CONF = config.CONF
class HorizonHTMLParser(html.parser.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(test.BaseTestCase):
"""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
"""
opener = None
credentials = ['primary']
@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 = self._get_opener().open(CONF.dashboard.dashboard_url).read()
self.assertIn("id_username", response.decode("utf-8"))
def user_login(self, username, password):
response = self._get_opener().open(CONF.dashboard.dashboard_url).read()
# Grab the CSRF token and default region
parser = HorizonHTMLParser()
parser.feed(response.decode("utf-8"))
# 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._get_opener().open(req, parse.urlencode(params).encode())
def check_home_page(self):
response = self._get_opener().open(CONF.dashboard.dashboard_url).read()
self.assertIn('Overview', response.decode("utf-8"))
def _get_opener(self):
if not self.opener:
if (CONF.dashboard.disable_ssl_certificate_validation and
self._ssl_default_context_supported()):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
self.opener = request.build_opener(
request.HTTPSHandler(context=ctx),
request.HTTPCookieProcessor())
else:
self.opener = request.build_opener(
request.HTTPCookieProcessor())
return self.opener
def _ssl_default_context_supported(self):
return (hasattr(ssl, 'create_default_context'))
@decorators.attr(type='smoke')
@decorators.idempotent_id('4f8851b1-0e69-482b-b63b-84c6e76f6c80')
@utils.services('dashboard')
def test_basic_scenario(self):
creds = self.os_primary.credentials
self.check_login_page()
self.user_login(creds.username, creds.password)
self.check_home_page()

View File

@ -69,6 +69,8 @@
Former names for this job where:
* legacy-tempest-dsvm-py35
* gate-tempest-dsvm-py35
required-projects:
- openstack/horizon
vars:
tox_envlist: full
devstack_localrc:
@ -89,6 +91,8 @@
network-feature-enabled:
qos_placement_physnet: public
devstack_services:
# Enbale horizon so that we can run horizon test.
horizon: true
s-account: false
s-container: false
s-object: false