Restore YAQL validators support

It was broken after YAQl release since YAQL expression
object contains recursive reference and deepcopy operation
failed. So need to copy such objects manually in
a separate function.

To prevent such kind of failures,
test of hot-based package was added.

Change-Id: If3fde7552d9657055aded73c4967fb6832c17536
Closes-Bug: #1498253
This commit is contained in:
Ekaterina Chernova 2016-01-13 15:03:26 +03:00
parent 6ce981df78
commit b3bdf5f0aa
7 changed files with 88 additions and 14 deletions

View File

@ -33,7 +33,7 @@ from openstack_dashboard.api import glance
from openstack_dashboard.api import nova
from oslo_log import log as logging
import six
import yaql
from yaql import legacy
from muranoclient.common import exceptions as muranoclient_exc
from muranodashboard.api import packages as pkg_api
@ -77,7 +77,7 @@ def make_yaql_validator(validator_property):
message = validator_property.get('message', '')
def validator_func(value):
context = yaql.create_context()
context = legacy.create_context()
context['$'] = value
if not expr.evaluate(context=context):
raise forms.ValidationError(message)

View File

@ -62,6 +62,19 @@ TYPES_KWARGS = {
def _collect_fields(field_specs, form_name, service):
def careful_deepcopy(x):
"""Careful handling of deepcopy object with recursive.
There is a recursive reference in YAQL expression
(since 1.0 version) and standard deepcopy can't handle this.
"""
original_validators = x.pop('validators', None)
result = copy.deepcopy(x)
if original_validators:
result['validators'] = original_validators
return result
def process_widget(cls, kwargs):
if isinstance(cls, tuple):
cls, _w = cls
@ -127,7 +140,7 @@ def _collect_fields(field_specs, form_name, service):
return name, cls(**kwargs)
return [make_field(copy.deepcopy(_spec)) for _spec in field_specs]
return [make_field(careful_deepcopy(_spec)) for _spec in field_specs]
class DynamicFormMetaclass(forms.forms.DeclarativeFieldsMetaclass):

View File

@ -0,0 +1,21 @@
heat_template_version: 2013-05-23
description: >
Hello world HOT template that just defines a single server.
Contains just base features to verify base HOT support.
parameters:
flavor:
type: string
description: Flavor for the server to be created
default: m1.small
constraints:
- allowed_values: [ m1.small, m1.medium, m1.large ]
resources:
server:
type: OS::Nova::Server
properties:
image: cirros-0.3.4-x86_64-uec
flavor: { get_param: flavor }

View File

@ -258,12 +258,17 @@ class PackageBase(UITestCase):
cls.murano_client,
"PostgreSQL",
{"categories": ["Databases"], "tags": ["tag"]})
cls.hot_app_id = utils.upload_app_package(
cls.murano_client,
"HotExample",
{"tags": ["hot"]}, hot=True)
@classmethod
def tearDownClass(cls):
super(PackageBase, cls).tearDownClass()
cls.murano_client.packages.delete(cls.mockapp_id)
cls.murano_client.packages.delete(cls.postgre_id)
cls.murano_client.packages.delete(cls.hot_app_id)
class ImageTestCase(PackageBase):
@ -399,8 +404,8 @@ class PackageTestCase(ApplicationTestCase):
super(ApplicationTestCase, cls).setUpClass()
cls.archive_name = "ToUpload"
cls.alt_archive_name = "ModifiedAfterUpload"
cls.archive = utils.compose_package(cls.archive_name,
consts.Manifest,
cls.manifest = os.path.join(consts.PackageDir, 'manifest.yaml')
cls.archive = utils.compose_package(cls.archive_name, cls.manifest,
consts.PackageDir)
def tearDown(self):
@ -413,7 +418,7 @@ class PackageTestCase(ApplicationTestCase):
@classmethod
def tearDownClass(cls):
super(ApplicationTestCase, cls).tearDownClass()
if os.path.exists(consts.Manifest):
os.remove(consts.Manifest)
if os.path.exists(cls.manifest):
os.remove(cls.manifest)
if os.path.exists(cls.archive):
os.remove(cls.archive)

View File

@ -2,7 +2,8 @@ import os
PackageDir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'MockApp')
Manifest = os.path.join(PackageDir, 'manifest.yaml')
HotPackageDir = os.path.join(os.path.dirname(os.path.realpath(__file__)),
'HotApp')
CategorySelector = "//a[contains(text(), '{0}')][contains(@class, 'dropdown-toggle')]" # noqa
App = "//div[contains(@class, 'app-list')]//h4[contains(text(), '{0}')]"
@ -18,6 +19,7 @@ CellStatus = "//td[contains(@class, 'status_{0}')]"
ErrorMessage = '//span[contains(@class, "help-block") and contains(text(), "{0}")]' # noqa
DatabaseCategory = "select[name='add_category-categories'] > option[value='Databases']" # noqa
Action = '//a[contains(@class, "murano_action") and contains(text(), "testAction")]' # noqa
HotFlavorField = '//div[contains(@class, "has-error")]//input'
# Buttons
ButtonSubmit = ".//*[@name='wizard_goto_step'][2]"

View File

@ -610,6 +610,30 @@ class TestSuiteApplications(base.ApplicationTestCase):
self.assertIn('Follow the white rabbit',
self.driver.find_element_by_class_name('logs').text)
def test_hot_application(self):
"""Checks that UI got the hot app is rendered correctly
Scenario:
1. Navigate Applications and click Hot app 'Quick Deploy'
2. Check for YAQL validator
3. Check that app is added to the environment
"""
self.go_to_submenu('Applications')
self.select_and_click_action_for_app('quick-add', self.hot_app_id)
field_id = "{0}_0-name".format(self.hot_app_id)
self.fill_field(by.By.ID, field_id, value='TestHotApp')
self.driver.find_element_by_xpath(c.ButtonSubmit).click()
self.fill_field(by.By.CSS_SELECTOR,
'input[id$="flavor"]',
value='testFlavor')
self.driver.find_element_by_xpath(c.InputSubmit).click()
self.check_element_on_page(by.By.XPATH, c.HotFlavorField)
self.fill_field(by.By.CSS_SELECTOR,
'input[id$="flavor"]',
value='m1.small')
self.driver.find_element_by_xpath(c.InputSubmit).click()
self.check_element_on_page(by.By.LINK_TEXT, 'TestHotApp')
class TestSuitePackages(base.PackageTestCase):
def test_modify_package_name(self):

View File

@ -30,19 +30,24 @@ class ImageException(Exception):
return self._error_string
def upload_app_package(client, app_name, data):
def upload_app_package(client, app_name, data, hot=False):
try:
archive = compose_package(app_name, consts.Manifest,
consts.PackageDir)
if not hot:
manifest = os.path.join(consts.PackageDir, 'manifest.yaml')
archive = compose_package(app_name, manifest, consts.PackageDir)
else:
manifest = os.path.join(consts.HotPackageDir, 'manifest.yaml')
archive = compose_package(app_name, manifest,
consts.HotPackageDir, hot=True)
package = client.packages.create(data, {app_name: open(archive, 'rb')})
return package.id
finally:
os.remove(archive)
os.remove(consts.Manifest)
os.remove(manifest)
def compose_package(app_name, manifest, package_dir,
require=None, archive_dir=None):
require=None, archive_dir=None, hot=False):
"""Composes a murano package
Composes package `app_name` with `manifest` file as a template for the
@ -56,7 +61,11 @@ def compose_package(app_name, manifest, package_dir,
mfest_copy = MANIFEST.copy()
mfest_copy['FullName'] = fqn
mfest_copy['Name'] = app_name
mfest_copy['Classes'] = {fqn: 'mock_muranopl.yaml'}
if hot:
mfest_copy['Format'] = 'Heat.HOT/1.0'
else:
mfest_copy['Format'] = '1.0'
mfest_copy['Classes'] = {fqn: 'mock_muranopl.yaml'}
if require:
mfest_copy['Require'] = require
f.write(yaml.dump(mfest_copy, default_flow_style=False))