Merge "Cherry-pick the following commits from master:" into release-0.4

This commit is contained in:
Jenkins 2014-01-24 09:21:38 +00:00 committed by Gerrit Code Review
commit a4d3cb9a82
17 changed files with 820 additions and 452 deletions

View File

@ -36,18 +36,6 @@ from django.template.loader import render_to_string
log = logging.getLogger(__name__)
YAQL_FUNCTIONS = {
'test': lambda self, pattern: re.match(pattern(), self()) is not None,
}
def create_yaql_context(functions=YAQL_FUNCTIONS):
context = yaql.create_context()
for name, func in functions.iteritems():
context.register_function(func, name)
return context
def with_request(func):
"""The decorator is meant to be used together with `UpdatableFieldsForm':
apply it to the `update' method of fields inside that form.
@ -75,7 +63,7 @@ def make_yaql_validator(field, form, key, validator_property):
def validator_func(value):
data = getattr(form, 'cleaned_data', {})
data[key] = value
form.service.update_cleaned_data(form, data)
form.service.update_cleaned_data(data, form=form)
if not validator_property['expr'].__get__(field):
raise forms.ValidationError(
_(validator_property.get('message', '')))
@ -130,8 +118,31 @@ def get_murano_images(request):
return murano_images
class RawProperty(object):
def __init__(self, key, spec):
self.key = key
self.spec = spec
def finalize(self, form_name, service):
def _get(field):
data_ready, value = service.get_data(form_name, self.spec)
return value if data_ready else field.__dict__[self.key]
def _set(field, value):
field.__dict__[self.key] = value
def _del(field):
del field.__dict__[self.key]
return property(_get, _set, _del)
class CustomPropertiesField(forms.Field):
def __init__(self, form=None, key=None, *args, **kwargs):
def __init__(self, description=None, description_title=None,
*args, **kwargs):
self.description = description
self.description_title = (description_title or
unicode(kwargs.get('label', '')))
validators = []
for validator in kwargs.get('validators', []):
if hasattr(validator, '__call__'): # single regexpValidator
@ -142,31 +153,33 @@ class CustomPropertiesField(forms.Field):
if regex_validator:
validators.append(wrap_regex_validator(
regex_validator, validator.get('message', '')))
elif isinstance(expr, property):
if form:
validators.append(
make_yaql_validator(self, form, key, validator))
elif isinstance(expr, RawProperty):
validators.append(validator)
kwargs['validators'] = validators
super(CustomPropertiesField, self).__init__(*args, **kwargs)
def clean(self, value):
"""Skip all validators if field is disabled."""
# form is assigned in ServiceConfigurationForm.finalize_fields()
form = self.form
# the only place to ensure that Service object has up-to-date
# cleaned_data
form.service.update_cleaned_data(form.cleaned_data, form=form)
if getattr(self, 'enabled', True):
return super(CustomPropertiesField, self).clean(value)
else:
return super(CustomPropertiesField, self).to_python(value)
@classmethod
def push_properties(cls, kwargs):
def finalize_properties(cls, kwargs, form_name, service):
props = {}
for key, value in kwargs.iteritems():
if isinstance(value, property):
props[key] = value
for key in props.keys():
del kwargs[key]
for key, value in kwargs.items():
if isinstance(value, RawProperty):
props[key] = value.finalize(form_name, service)
del kwargs[key]
if props:
return type('cls_with_props', (cls,), props)
return type(cls.__name__, (cls,), props)
else:
return cls
@ -213,14 +226,17 @@ class PasswordField(CharField):
if err_msg.get('required'):
error_messages['required'] = err_msg.get('required')
super(PasswordField, self).__init__(
min_length=7,
max_length=255,
validators=[self.validate_password],
label=label,
error_messages=error_messages,
help_text=help_text,
widget=self.PasswordInput(render_value=True))
kwargs.update({
'min_length': 7,
'max_length': 255,
'validators': [self.validate_password],
'label': label,
'error_messages': error_messages,
'help_text': help_text,
'widget': self.PasswordInput(render_value=True),
})
super(PasswordField, self).__init__(*args, **kwargs)
def __deepcopy__(self, memo):
result = super(PasswordField, self).__deepcopy__(memo)
@ -372,7 +388,7 @@ class TableWidget(floppyforms.widgets.Input):
self.max_sync = max_sync
# FixME: we need to use this hack because TableField passes all kwargs
# to TableWidget
for kwarg in ('widget', 'key', 'form'):
for kwarg in ('widget', 'description', 'description_title'):
ignorable = kwargs.pop(kwarg, None)
super(TableWidget, self).__init__(*args, **kwargs)
@ -480,11 +496,12 @@ class FlavorChoiceField(ChoiceField):
class KeyPairChoiceField(ChoiceField):
" This widget allows to select Key Pair for VMs "
" This widget allows to select keypair for VMs "
@with_request
def update(self, request, **kwargs):
self.choices = [(keypair.name, keypair.name) for keypair in
novaclient(request).keypairs.list()]
self.choices = [('', _('No keypair'))]
for keypair in novaclient(request).keypairs.list():
self.choices.append((keypair.name, keypair.name))
class ImageChoiceField(ChoiceField):

View File

@ -12,12 +12,10 @@
# License for the specific language governing permissions and limitations
# under the License.
import re
import logging
import types
from django import forms
from django.core.validators import RegexValidator
from django.utils.translation import ugettext_lazy as _
import muranodashboard.dynamic_ui.fields as fields
import muranodashboard.dynamic_ui.helpers as helpers
@ -26,6 +24,105 @@ import yaql
log = logging.getLogger(__name__)
TYPES = {
'string': fields.CharField,
'boolean': fields.BooleanField,
'instance': fields.InstanceCountField,
'clusterip': fields.ClusterIPField,
'domain': fields.DomainChoiceField,
'password': fields.PasswordField,
'integer': fields.IntegerField,
'databaselist': fields.DatabaseListField,
'table': fields.TableField,
'flavor': fields.FlavorChoiceField,
'keypair': fields.KeyPairChoiceField,
'image': fields.ImageChoiceField,
'azone': fields.AZoneChoiceField,
'text': (fields.CharField, forms.Textarea)
}
def _collect_fields(field_specs, form_name, service):
def process_widget(kwargs, cls, widget):
widget = kwargs.get('widget', widget)
if widget is None:
widget = cls.widget
if 'widget_media' in kwargs:
media = kwargs['widget_media']
del kwargs['widget_media']
class Widget(widget):
class Media:
js = media.get('js', ())
css = media.get('css', {})
widget = Widget
if 'widget_attrs' in kwargs:
widget = widget(attrs=kwargs['widget_attrs'])
del kwargs['widget_attrs']
return widget
def parse_spec(spec, keys=None):
if keys is None:
keys = []
if not isinstance(keys, types.ListType):
keys = [keys]
key = keys and keys[-1] or None
if helpers.get_yaql_expr(spec):
return key, fields.RawProperty(key, spec)
elif isinstance(spec, types.DictType):
items = []
for k, v in spec.iteritems():
if not k in ('type', 'name'):
k = helpers.decamelize(k)
new_key, v = parse_spec(v, keys + [k])
if new_key:
k = new_key
items.append((k, v))
return key, dict(items)
elif isinstance(spec, types.ListType):
return key, [parse_spec(_spec, keys)[1] for _spec in spec]
elif isinstance(spec, basestring) and helpers.is_localizable(keys):
return key, _(spec)
else:
if key == 'type':
return key, TYPES[spec]
elif key == 'hidden' and spec is True:
return 'widget', forms.HiddenInput
elif key == 'regexp_validator':
return 'validators', [helpers.prepare_regexp(spec)]
else:
return key, spec
def make_field(field_spec, form_name, service):
cls = parse_spec(field_spec['type'], 'type')[1]
widget = None
if isinstance(cls, types.TupleType):
cls, widget = cls
kwargs = parse_spec(field_spec)[1]
kwargs['widget'] = process_widget(kwargs, cls, widget)
cls = cls.finalize_properties(kwargs, form_name, service)
attribute_names = kwargs.pop('attribute_names', None)
field = cls(**kwargs)
field.attribute_names = attribute_names
return field_spec['name'], field
return [make_field(spec, form_name, service) for spec in field_specs]
class DynamicFormMetaclass(forms.forms.DeclarativeFieldsMetaclass):
def __new__(meta, name, bases, dct):
name = dct.pop('name', name)
field_specs = dct.pop('field_specs', [])
service = dct['service']
for field_name, field in _collect_fields(field_specs, name, service):
dct[field_name] = field
return super(DynamicFormMetaclass, meta).__new__(
meta, name, bases, dct)
class UpdatableFieldsForm(forms.Form):
"""This class is supposed to be a base for forms belonging to a FormWizard
@ -59,39 +156,30 @@ class UpdatableFieldsForm(forms.Form):
class ServiceConfigurationForm(UpdatableFieldsForm):
types = {
'string': fields.CharField,
'boolean': fields.BooleanField,
'instance': fields.InstanceCountField,
'clusterip': fields.ClusterIPField,
'domain': fields.DomainChoiceField,
'password': fields.PasswordField,
'integer': fields.IntegerField,
'databaselist': fields.DatabaseListField,
'table': fields.TableField,
'flavor': fields.FlavorChoiceField,
'keypair': fields.KeyPairChoiceField,
'image': fields.ImageChoiceField,
'azone': fields.AZoneChoiceField,
'text': (fields.CharField, forms.Textarea)
}
localizable_keys = set(['label', 'help_text', 'error_messages'])
def __init__(self, *args, **kwargs):
log.info("Creating form {0}".format(self.__class__.__name__))
super(ServiceConfigurationForm, self).__init__(*args, **kwargs)
self.attribute_mappings = {}
self.context = fields.create_yaql_context()
self.insert_fields(self.fields_template)
self.context = helpers.create_yaql_context()
self.finalize_fields()
self.initial = kwargs.get('initial', self.initial)
self.update_fields()
@staticmethod
def get_yaql_expr(expr):
return isinstance(expr, types.DictType) and expr.get('YAQL', None)
def finalize_fields(self):
for field_name, field in self.fields.iteritems():
field.form = self
def init_attribute_mappings(self, field_name, kwargs):
validators = []
for v in field.validators:
expr = isinstance(v, types.DictType) and v.get('expr')
if expr and isinstance(expr, fields.RawProperty):
v = fields.make_yaql_validator(field, self, field_name, v)
validators.append(v)
field.validators = validators
self.init_attribute_mapping(field_name, field)
def init_attribute_mapping(self, field_name, field):
def set_mapping(name, value):
"""Spawns new dictionaries for each dot found in name."""
bits = name.split('.')
@ -102,8 +190,8 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
head, tail, mapping = tail[0], tail[1:], mapping[head]
mapping[head] = value
if 'attribute_names' in kwargs:
attr_names = kwargs['attribute_names']
attr_names = field.attribute_names
if attr_names is not None:
if isinstance(attr_names, types.ListType):
# allow pushing field value to multiple attributes
for attr_name in attr_names:
@ -111,131 +199,17 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
elif attr_names:
# if attributeNames = false, do not push field value
set_mapping(attr_names, field_name)
del kwargs['attribute_names']
else:
# default mapping: field to attr with same name
# do not spawn new dictionaries for any dot in field_name
self.attribute_mappings[field_name] = field_name
def init_field_descriptions(self, kwargs):
if 'description' in kwargs:
del kwargs['description']
if 'description_title' in kwargs:
del kwargs['description_title']
def insert_fields(self, field_specs):
def process_widget(kwargs, cls, widget):
widget = kwargs.get('widget', widget)
if widget is None:
widget = cls.widget
if 'widget_media' in kwargs:
media = kwargs['widget_media']
del kwargs['widget_media']
class Widget(widget):
class Media:
js = media.get('js', ())
css = media.get('css', {})
widget = Widget
if 'widget_attrs' in kwargs:
widget = widget(attrs=kwargs['widget_attrs'])
del kwargs['widget_attrs']
return widget
def append_field(field_spec):
cls = parse_spec(field_spec['type'], 'type')[1]
widget = None
if isinstance(cls, types.TupleType):
cls, widget = cls
kwargs = parse_spec(field_spec)[1]
kwargs.update({
'widget': process_widget(kwargs, cls, widget),
'form': self,
'key': field_spec['name']
})
cls = cls.push_properties(kwargs)
self.init_attribute_mappings(field_spec['name'], kwargs)
self.init_field_descriptions(kwargs)
self.fields.insert(len(self.fields),
field_spec['name'],
cls(**kwargs))
def prepare_regexp(regexp):
if regexp[0] == '/':
groups = re.match(r'^/(.*)/([A-Za-z]*)$', regexp).groups()
regexp, flags_str = groups
flags = 0
for flag in helpers.explode(flags_str):
flag = flag.upper()
if hasattr(re, flag):
flags |= getattr(re, flag)
return RegexValidator(re.compile(regexp, flags))
else:
return RegexValidator(re.compile(regexp))
def is_localizable(keys):
return set(keys).intersection(self.localizable_keys)
def make_property(key, spec):
def _get(field):
data_ready, value = self.get_data(spec)
return value if data_ready else field.__dict__[key]
def _set(field, value):
field.__dict__[key] = value
def _del(field):
del field.__dict__[key]
return property(_get, _set, _del)
def parse_spec(spec, keys=[]):
if not isinstance(keys, types.ListType):
keys = [keys]
key = keys and keys[-1] or None
if self.get_yaql_expr(spec):
return key, make_property(key, spec)
elif isinstance(spec, types.DictType):
items = []
for k, v in spec.iteritems():
if not k in ('type', 'name'):
k = helpers.decamelize(k)
newKey, v = parse_spec(v, keys + [k])
if newKey:
k = newKey
items.append((k, v))
return key, dict(items)
elif isinstance(spec, types.ListType):
return key, [parse_spec(_spec, keys)[1] for _spec in spec]
elif isinstance(spec, basestring) and is_localizable(keys):
return key, _(spec)
else:
if key == 'type':
return key, self.types[spec]
elif key == 'hidden' and spec is True:
return 'widget', forms.HiddenInput
elif key == 'regexp_validator':
return 'validators', [prepare_regexp(spec)]
else:
return key, spec
for spec in field_specs:
append_field(spec)
def get_data(self, expr, data=None):
"""First try to get value from cleaned data, if none
found, use raw data."""
data = self.service.update_cleaned_data(
self, data or getattr(self, 'cleaned_data', None))
expr = self.get_yaql_expr(expr)
value = data and yaql.parse(expr).evaluate(data, self.context)
return data != {}, value
del field.attribute_names
def get_unit_templates(self, data):
def parse_spec(spec):
if self.get_yaql_expr(spec):
data_ready, value = self.get_data(spec, data)
if helpers.get_yaql_expr(spec):
data_ready, value = self.service.get_data(
self.__class__.__name__, spec, data)
return value
elif isinstance(spec, types.ListType):
return [parse_spec(_spec) for _spec in spec]
@ -245,8 +219,8 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
for (k, v) in spec.iteritems())
else:
return spec
#TODO: add try-except if unit_templates is not in service description
return [parse_spec(spec) for spec in self.service.unit_templates]
unit_templates = getattr(self.service, 'unit_templates', [])
return [parse_spec(spec) for spec in unit_templates]
def extract_attributes(self, attributes):
def get_attr(name):
@ -262,10 +236,11 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
return self.cleaned_data
else:
cleaned_data = super(ServiceConfigurationForm, self).clean()
all_data = self.service.update_cleaned_data(self, cleaned_data)
all_data = self.service.update_cleaned_data(
cleaned_data, form=self)
error_messages = []
for validator in self.validators:
expr = self.get_yaql_expr(validator['expr'])
expr = helpers.get_yaql_expr(validator['expr'])
if not yaql.parse(expr).evaluate(all_data, self.context):
error_messages.append(_(validator.get('message', '')))
if error_messages:
@ -282,5 +257,5 @@ class ServiceConfigurationForm(UpdatableFieldsForm):
cleaned_data[name] = value
log.debug("Update cleaned data in postclean method")
self.service.update_cleaned_data(self, cleaned_data)
self.service.update_cleaned_data(cleaned_data, form=self)
return cleaned_data

View File

@ -13,13 +13,28 @@
# under the License.
import re
from django.core.validators import RegexValidator
import types
import yaql
_LOCALIZABLE_KEYS = set(['label', 'help_text', 'error_messages'])
YAQL_FUNCTIONS = {
'test': lambda self, pattern: re.match(pattern(), self()) is not None,
}
def is_localizable(keys):
return set(keys).intersection(_LOCALIZABLE_KEYS)
def camelize(name):
"""Turns snake_case name into SnakeCase."""
return ''.join([bit.capitalize() for bit in name.split('_')])
def decamelize(name):
"""Turns CamelCase/camelCase name into camel_case."""
pat = re.compile(r'([A-Z]*[^A-Z]*)(.*)')
bits = []
while True:
@ -33,6 +48,7 @@ def decamelize(name):
def explode(string):
"""Explodes a string into a list of one-character strings."""
if not string:
return string
bits = []
@ -44,3 +60,32 @@ def explode(string):
else:
break
return bits
def prepare_regexp(regexp):
"""Converts regular expression string pattern into RegexValidator object.
Also /regexp/flags syntax is allowed, where flags is a string of
one-character flags that will be appended to the compiled regexp."""
if regexp.startswith('/'):
groups = re.match(r'^/(.*)/([A-Za-z]*)$', regexp).groups()
regexp, flags_str = groups
flags = 0
for flag in explode(flags_str):
flag = flag.upper()
if hasattr(re, flag):
flags |= getattr(re, flag)
return RegexValidator(re.compile(regexp, flags))
else:
return RegexValidator(re.compile(regexp))
def get_yaql_expr(expr):
return isinstance(expr, types.DictType) and expr.get('YAQL', None)
def create_yaql_context(functions=YAQL_FUNCTIONS):
context = yaql.create_context()
for name, func in functions.iteritems():
context.register_function(func, name)
return context

View File

@ -20,7 +20,7 @@ import logging
import shutil
import hashlib
from muranodashboard.environments.consts import CHUNK_SIZE, CACHE_DIR, \
ARCHIVE_PKG_PATH
ARCHIVE_PKG_NAME
log = logging.getLogger(__name__)
@ -65,8 +65,12 @@ def metadataclient(request):
return Client(endpoint=endpoint, token=token_id)
def get_existing_hash():
for item in os.listdir(CACHE_DIR):
def get_tenant_dir(tenant_id):
return os.path.join(CACHE_DIR, tenant_id)
def get_existing_hash(tenant_dir):
for item in os.listdir(tenant_dir):
if re.match(r'[a-f0-9]{40}', item):
return item
return None
@ -94,11 +98,11 @@ def get_hash(archive_path):
return None
def unpack_ui_package(archive_path):
def unpack_ui_package(archive_path, tenant_dir):
if not tarfile.is_tarfile(archive_path):
raise RuntimeError('{0} is not valid tarfile!'.format(archive_path))
hash = get_hash(archive_path)
dst_dir = os.path.join(CACHE_DIR, hash)
dst_dir = os.path.join(tenant_dir, hash)
if not os.path.exists(dst_dir):
os.mkdir(dst_dir)
else:
@ -138,9 +142,14 @@ def get_ui_metadata(request):
occurred, returns None.
"""
log.debug("Retrieving metadata from Repository")
hash = get_existing_hash()
tenant_dir = get_tenant_dir(request.user.tenant_id)
if not os.path.exists(tenant_dir):
os.makedirs(tenant_dir)
hash = get_existing_hash(tenant_dir)
metadata_dir = None
if hash:
metadata_dir = os.path.join(CACHE_DIR, hash)
metadata_dir = os.path.join(tenant_dir, hash)
data = None
with metadata_exceptions(request):
@ -158,19 +167,20 @@ def get_ui_metadata(request):
with tempfile.NamedTemporaryFile(delete=False) as out:
for chunk in body_iter:
out.write(chunk)
shutil.move(out.name, ARCHIVE_PKG_PATH)
archive_pkg_path = os.path.join(tenant_dir, ARCHIVE_PKG_NAME)
shutil.move(out.name, archive_pkg_path)
log.info("Successfully downloaded new metadata package to {0}".format(
ARCHIVE_PKG_PATH))
archive_pkg_path))
if hash:
log.debug('Removing outdated metadata: {0}'.format(metadata_dir))
shutil.rmtree(metadata_dir)
return unpack_ui_package(ARCHIVE_PKG_PATH), True
return unpack_ui_package(archive_pkg_path, tenant_dir), True
elif code == 304:
log.info("Metadata package hash-sum hasn't changed, doing nothing")
return metadata_dir, False
else:
msg = 'Unexpected response received: {0}'.format(code)
if hash:
if metadata_dir:
log.error('Using existing version of metadata '
'which may be outdated due to: {0}'.format(msg))
return metadata_dir, False

View File

@ -18,12 +18,14 @@ import re
import time
import logging
from muranodashboard.dynamic_ui import metadata
from muranodashboard.dynamic_ui.helpers import decamelize
from .helpers import decamelize, get_yaql_expr, create_yaql_context
try:
from collections import OrderedDict
except ImportError: # python2.6
from ordereddict import OrderedDict
import yaql
import yaml
from yaml.scanner import ScannerError
from django.utils.translation import ugettext_lazy as _
@ -31,44 +33,98 @@ import copy
from muranodashboard.environments.consts import CACHE_REFRESH_SECONDS_INTERVAL
log = logging.getLogger(__name__)
_all_services = OrderedDict()
_last_check_time = 0
_current_cache_hash = None
class Service(object):
def __init__(self, **kwargs):
import muranodashboard.dynamic_ui.forms as services
for key, value in kwargs.iteritems():
if key == 'forms':
self.forms = []
for form_data in value:
form_name, form_data = self.extract_form_data(form_data)
self.forms.append(
type(form_name, (services.ServiceConfigurationForm,),
{'service': self,
'fields_template': form_data['fields'],
'validators': form_data.get('validators', [])}))
else:
setattr(self, key, value)
"""Class for keeping service persistent data, the most important are two:
``self.forms`` list of service's steps (as Django form classes) and
``self.cleaned_data`` dictionary of data from service validated steps.
Attribute ``self.cleaned_data`` is needed for, e.g. ServiceA.Step2, be
able to reference data at ServiceA.Step1 while actual form instance
representing Step1 is already gone.
Because the need to store this data per-user, sessions must be employed
(actually, they are not the _only_ way of doing this, but the most simple
one), and because every Django session backend uses pickle serialization,
__getstate__/__setstate__ methods for custom pickle serialization must be
implemented.
"""
NON_SERIALIZABLE_ATTRS = ('forms', 'context')
def __init__(self, forms=None, **kwargs):
self.context = create_yaql_context()
self.cleaned_data = {}
self.forms = []
self._forms = []
for key, value in kwargs.iteritems():
setattr(self, key, value)
if forms:
for data in forms:
name, field_specs, validators = self.extract_form_data(data)
self._add_form(name, field_specs, validators)
# for pickling/unpickling
self._forms.append((name, field_specs, validators))
def __getstate__(self):
log.debug("Pickling service '{service.type}'".format(
service=self))
dct = dict((k, v) for (k, v) in self.__dict__.iteritems()
if not k in self.NON_SERIALIZABLE_ATTRS)
return dct
def __setstate__(self, d):
log.debug("Unpickling service '{type}'".format(**d))
for k, v in d.iteritems():
setattr(self, k, v)
# dealing with the attributes which cannot be serialized (see
# http://tinyurl.com/kxx3tam on pickle restrictions )
# yaql context is not serializable because it contains lambda functions
self.context = create_yaql_context()
# form classes are not serializable 'cause they are defined dynamically
self.forms = []
for name, field_specs, validators in d.get('_forms', []):
self._add_form(name, field_specs, validators)
def _add_form(self, _name, _specs, _validators):
import muranodashboard.dynamic_ui.forms as forms
class Form(forms.ServiceConfigurationForm):
__metaclass__ = forms.DynamicFormMetaclass
service = self
name = _name
field_specs = _specs
validators = _validators
self.forms.append(Form)
@staticmethod
def extract_form_data(form_data):
form_name = form_data.keys()[0]
return form_name, form_data[form_name]
form_data = form_data[form_name]
return form_name, form_data['fields'], form_data.get('validators', [])
def update_cleaned_data(self, form, data):
def get_data(self, form_name, expr, data=None):
"""First try to get value from cleaned data, if none
found, use raw data."""
if data:
# match = re.match('^.*-(\d)+$', form.prefix)
# index = int(match.group(1)) if match else None
# if index is not None:
# self.cleaned_data[index] = data
self.cleaned_data[form.__class__.__name__] = data
self.update_cleaned_data(data, form_name=form_name)
expr = get_yaql_expr(expr)
data = self.cleaned_data
value = data and yaql.parse(expr).evaluate(data, self.context)
return data != {}, value
def update_cleaned_data(self, data, form=None, form_name=None):
form_name = form_name or form.__class__.__name__
if data:
self.cleaned_data[form_name] = data
return self.cleaned_data
def import_service(full_service_name, service_file):
def import_service(services, full_service_name, service_file):
try:
with open(service_file) as stream:
yaml_desc = yaml.load(stream)
@ -77,17 +133,9 @@ def import_service(full_service_name, service_file):
" reason: {1!s}".format(service_file, e))
else:
service = dict((decamelize(k), v) for (k, v) in yaml_desc.iteritems())
_all_services[full_service_name] = Service(**service)
services[full_service_name] = Service(**service)
log.info("Added service '{0}' from '{1}'".format(
_all_services[full_service_name].name, service_file))
def are_caches_in_sync():
are_in_sync = (_current_cache_hash == metadata.get_existing_hash())
if not are_in_sync:
log.debug('In-memory and on-disk caches are not in sync, '
'invalidating in-memory cache')
return are_in_sync
services[full_service_name].name, service_file))
def import_all_services(request):
@ -105,30 +153,30 @@ def import_all_services(request):
If there is no YAMLs with form definitions inside <full_service_nameN>
dir, then <full_service_nameN> won't be shown in Create Service first step.
"""
global _last_check_time
global _all_services
global _current_cache_hash
if time.time() - _last_check_time > CACHE_REFRESH_SECONDS_INTERVAL:
_last_check_time = time.time()
last_check_time = request.session.get('last_check_time', 0)
if time.time() - last_check_time > CACHE_REFRESH_SECONDS_INTERVAL:
request.session['last_check_time'] = time.time()
directory, modified = metadata.get_ui_metadata(request)
session_is_empty = not request.session.get('services', {})
# check directory here in case metadata service is not available
# and None is returned as directory value.
# TODO: it is better to use redirect for that purpose (if possible)
if modified or (directory and not are_caches_in_sync()):
_all_services = {}
if directory is not None and (modified or session_is_empty):
request.session['services'] = {}
for full_service_name in os.listdir(directory):
final_dir = os.path.join(directory, full_service_name)
if os.path.isdir(final_dir) and len(os.listdir(final_dir)):
filename = os.listdir(final_dir)[0]
if filename.endswith('.yaml'):
import_service(full_service_name,
import_service(request.session['services'],
full_service_name,
os.path.join(final_dir, filename))
_current_cache_hash = metadata.get_existing_hash()
def iterate_over_services(request):
import_all_services(request)
for service in sorted(_all_services.values(), key=lambda v: v.name):
services = request.session.get('services', {})
for service in sorted(services.values(), key=lambda v: v.name):
yield service.type, service
@ -166,10 +214,11 @@ def get_service_field_descriptions(request, service_id, index):
def get_descriptions(service):
form_cls = service.forms[index]
descriptions = []
for field in form_cls.fields_template:
if 'description' in field:
title = field.get('descriptionTitle', field.get('label', ''))
descriptions.append((title, field['description']))
for field in form_cls.base_fields.itervalues():
title = field.description_title
description = field.description
if description:
descriptions.append((title, description))
return descriptions
return with_service(request, service_id, get_descriptions, [])

View File

@ -195,7 +195,8 @@ def environment_delete(request, environment_id):
def environment_get(request, environment_id):
session_id = Session.get(request, environment_id)
log.debug('Environment::Get <Id: {0}>'.format(environment_id))
log.debug('Environment::Get <Id: {0}, SessionId: {1}>'.
format(environment_id, session_id))
env = muranoclient(request).environments.get(environment_id, session_id)
log.debug('Environment::Get {0}'.format(env))
return env
@ -214,29 +215,6 @@ def environment_update(request, environment_id, name):
return muranoclient(request).environments.update(environment_id, name)
def get_environment_name(request, environment_id):
session_id = Session.get(request, environment_id)
environment = muranoclient(request).environments.get(environment_id,
session_id)
log.debug('Return environment name')
return environment.name
def get_environment_data(request, environment_id, *args):
"""
For given list of environment attributes return a values
:return list
"""
session_id = Session.get(request, environment_id)
environment = muranoclient(request).environments.get(environment_id,
session_id)
result = []
for attr in args:
result.append(getattr(environment, attr, None))
return result
def services_list(request, environment_id):
def strip(msg, to=100):
return '%s...' % msg[:to] if len(msg) > to else msg

View File

@ -22,7 +22,6 @@ CACHE_DIR = getattr(settings, 'METADATA_CACHE_DIR',
os.path.join(tempfile.gettempdir(),
'muranodashboard-cache'))
ARCHIVE_PKG_PATH = os.path.join(CACHE_DIR, ARCHIVE_PKG_NAME)
CACHE_REFRESH_SECONDS_INTERVAL = 5
#---- Forms Consts ----#

View File

@ -17,15 +17,16 @@ import json
from django import forms
from django.utils.translation import ugettext_lazy as _
from muranodashboard.dynamic_ui.services import get_service_choices
from muranodashboard.dynamic_ui.fields import get_murano_images
from muranodashboard.dynamic_ui.fields import get_murano_images, \
ImageChoiceField
log = logging.getLogger(__name__)
def filter_service_by_image_type(service, request):
def find_image_field():
for form_cls in service.forms:
for field in form_cls.fields_template:
if field.get('type') == 'image':
for field in form_cls.base_fields.itervalues():
if isinstance(field, ImageChoiceField):
return field
return None
@ -34,7 +35,7 @@ def filter_service_by_image_type(service, request):
if not image_field:
message = "Please provide Image field description in UI definition"
return filtered, message
specified_image_type = image_field.get('imageType')
specified_image_type = getattr(image_field, 'image_type', None)
if not specified_image_type:
message = "Please provide 'imageType' parameter in Image field " \
"description in UI definition"

View File

@ -36,7 +36,9 @@ class CreateService(tables.LinkAction):
def allowed(self, request, environment):
environment_id = self.table.kwargs['environment_id']
status, = api.get_environment_data(request, environment_id, 'status')
env = api.environment_get(request, environment_id)
status = getattr(env, 'status', None)
if status not in [STATUS_ID_DEPLOYING]:
return True
return False
@ -94,7 +96,8 @@ class DeleteService(tables.DeleteAction):
def allowed(self, request, service=None):
environment_id = self.table.kwargs.get('environment_id')
status, = api.get_environment_data(request, environment_id, 'status')
env = api.environment_get(request, environment_id)
status = getattr(env, 'status', None)
return False if status == STATUS_ID_DEPLOYING else True
@ -145,8 +148,10 @@ class DeployThisEnvironment(tables.Action):
def allowed(self, request, service):
environment_id = self.table.kwargs['environment_id']
status, version = api.get_environment_data(request, environment_id,
'status', 'version')
env = api.environment_get(request, environment_id)
status = getattr(env, 'status', None)
version = getattr(env, 'version', None)
if status == STATUS_ID_DEPLOYING:
return False
services = self.table.data

View File

@ -201,10 +201,8 @@ class Services(tables.DataTableView):
context = super(Services, self).get_context_data(**kwargs)
try:
environment_name = api.get_environment_name(
self.request,
self.environment_id)
context['environment_name'] = environment_name
env = api.environment_get(self.request, self.environment_id)
context['environment_name'] = env.name
except:
msg = _("Sorry, this environment does't exist anymore")
@ -244,8 +242,8 @@ class DetailServiceView(tabs.TabView):
context = super(DetailServiceView, self).get_context_data(**kwargs)
context["service"] = self.get_data()
context["service_name"] = self.service.name
context["environment_name"] = \
api.get_environment_name(self.request, self.environment_id)
env = api.environment_get(self.request, self.environment_id)
context["environment_name"] = env.name
return context
def get_data(self):
@ -321,11 +319,8 @@ class DeploymentsView(tables.DataTableView):
context = super(DeploymentsView, self).get_context_data(**kwargs)
try:
environment_name = api.get_environment_name(
self.request,
self.environment_id)
context['environment_name'] = environment_name
env = api.environment_get(self.request, self.environment_id)
context['environment_name'] = env.name
except:
msg = _("Sorry, this environment doesn't exist anymore")
redirect = reverse("horizon:murano:environments:index")
@ -359,8 +354,8 @@ class DeploymentDetailsView(tabs.TabbedTableView):
def get_context_data(self, **kwargs):
context = super(DeploymentDetailsView, self).get_context_data(**kwargs)
context["environment_id"] = self.environment_id
context["environment_name"] = \
api.get_environment_name(self.request, self.environment_id)
env = api.environment_get(self.request, self.environment_id)
context["environment_name"] = env.name
context["deployment_start_time"] = \
api.get_deployment_start(self.request,
self.environment_id,

View File

@ -2,9 +2,18 @@ import os
from django.utils.translation import ugettext_lazy as _
from openstack_dashboard import exceptions
DEBUG = True
TEMPLATE_DEBUG = DEBUG
# Required for Django 1.5.
# If horizon is running in production (DEBUG is False), set this
# with the list of host/domain names that the application can serve.
# For more information see:
# https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts
#ALLOWED_HOSTS = ['horizon.example.com', ]
# Set SSL proxy settings:
# For Django 1.4+ pass this header from the proxy after terminating the SSL,
# and don't forget to strip it from the client's request.
@ -12,15 +21,62 @@ TEMPLATE_DEBUG = DEBUG
# https://docs.djangoproject.com/en/1.4/ref/settings/#secure-proxy-ssl-header
# SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTOCOL', 'https')
# Specify a regular expression to validate user passwords.
# HORIZON_CONFIG = {
# "password_validator": {
# "regex": '.*',
# "help_text": _("Your password does not meet the requirements.")
# },
# 'help_url': "http://docs.openstack.org"
# If Horizon is being served through SSL, then uncomment the following two
# settings to better secure the cookies from security exploits
#CSRF_COOKIE_SECURE = True
#SESSION_COOKIE_SECURE = True
# Overrides for OpenStack API versions. Use this setting to force the
# OpenStack dashboard to use a specfic API version for a given service API.
# NOTE: The version should be formatted as it appears in the URL for the
# service API. For example, The identity service APIs have inconsistent
# use of the decimal point, so valid options would be "2.0" or "3".
# OPENSTACK_API_VERSIONS = {
# "identity": 3
# }
# Set this to True if running on multi-domain model. When this is enabled, it
# will require user to enter the Domain name in addition to username for login.
# OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = False
# Overrides the default domain used when running on single-domain model
# with Keystone V3. All entities will be created in the default domain.
# OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'Default'
# Set Console type:
# valid options would be "AUTO", "VNC" or "SPICE"
# CONSOLE_TYPE = "AUTO"
# Default OpenStack Dashboard configuration.
HORIZON_CONFIG = {
'dashboards': ('project', 'admin', 'settings',),
'default_dashboard': 'project',
'user_home': 'openstack_dashboard.views.get_user_home',
'ajax_queue_limit': 10,
'auto_fade_alerts': {
'delay': 3000,
'fade_duration': 1500,
'types': ['alert-success', 'alert-info']
},
'help_url': "http://docs.openstack.org",
'exceptions': {'recoverable': exceptions.RECOVERABLE,
'not_found': exceptions.NOT_FOUND,
'unauthorized': exceptions.UNAUTHORIZED},
}
# Specify a regular expression to validate user passwords.
# HORIZON_CONFIG["password_validator"] = {
# "regex": '.*',
# "help_text": _("Your password does not meet the requirements.")
# }
# Disable simplified floating IP address management for deployments with
# multiple floating IP pools or complex network requirements.
# HORIZON_CONFIG["simple_ip_management"] = False
# Turn off browser autocompletion for the login form if so desired.
# HORIZON_CONFIG["password_autocomplete"] = "off"
LOCAL_PATH = os.path.dirname(os.path.abspath(__file__))
# Set custom secret key:
@ -32,13 +88,24 @@ LOCAL_PATH = os.path.dirname(os.path.abspath(__file__))
# behind a load-balancer). Either you have to make sure that a session gets all
# requests routed to the same dashboard instance or you set the same SECRET_KEY
# for all of them.
# from horizon.utils import secret_key
# SECRET_KEY = secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH, '.secret_key_store'))
from horizon.utils import secret_key
SECRET_KEY = secret_key.generate_or_read_from_file(os.path.join(LOCAL_PATH, '.secret_key_store'))
# We recommend you use memcached for development; otherwise after every reload
# of the django development server, you will have to login again. To use
# memcached set CACHE_BACKED to something like 'memcached://127.0.0.1:11211/'
CACHE_BACKEND = 'locmem://'
# memcached set CACHES to something like
# CACHES = {
# 'default': {
# 'BACKEND' : 'django.core.cache.backends.memcached.MemcachedCache',
# 'LOCATION' : '127.0.0.1:11211',
# }
#}
CACHES = {
'default': {
'BACKEND' : 'django.core.cache.backends.locmem.LocMemCache'
}
}
# Send email to the console by default
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
@ -64,6 +131,9 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member"
# Disable SSL certificate checks (useful for self-signed certificates):
# OPENSTACK_SSL_NO_VERIFY = True
# The CA certificate to use to verify SSL connections
# OPENSTACK_SSL_CACERT = '/path/to/cacert.pem'
# The OPENSTACK_KEYSTONE_BACKEND settings can be used to identify the
# capabilities of the auth backend for Keystone.
# If Keystone has been configured to use LDAP as the auth backend then set
@ -72,18 +142,62 @@ OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member"
# TODO(tres): Remove these once Keystone has an API to identify auth backend.
OPENSTACK_KEYSTONE_BACKEND = {
'name': 'native',
'can_edit_user': True
'can_edit_user': True,
'can_edit_group': True,
'can_edit_project': True,
'can_edit_domain': True,
'can_edit_role': True
}
OPENSTACK_HYPERVISOR_FEATURES = {
'can_set_mount_point': True
'can_set_mount_point': True,
}
# The OPENSTACK_NEUTRON_NETWORK settings can be used to enable optional
# services provided by neutron. Options currenly available are load
# balancer service, security groups, quotas, VPN service.
OPENSTACK_NEUTRON_NETWORK = {
'enable_lb': False,
'enable_firewall': False,
'enable_quotas': True,
'enable_vpn': False,
# The profile_support option is used to detect if an external router can be
# configured via the dashboard. When using specific plugins the
# profile_support can be turned on if needed.
'profile_support': None,
#'profile_support': 'cisco',
}
# The OPENSTACK_IMAGE_BACKEND settings can be used to customize features
# in the OpenStack Dashboard related to the Image service, such as the list
# of supported image formats.
# OPENSTACK_IMAGE_BACKEND = {
# 'image_formats': [
# ('', ''),
# ('aki', _('AKI - Amazon Kernel Image')),
# ('ami', _('AMI - Amazon Machine Image')),
# ('ari', _('ARI - Amazon Ramdisk Image')),
# ('iso', _('ISO - Optical Disk Image')),
# ('qcow2', _('QCOW2 - QEMU Emulator')),
# ('raw', _('Raw')),
# ('vdi', _('VDI')),
# ('vhd', _('VHD')),
# ('vmdk', _('VMDK'))
# ]
# }
# OPENSTACK_ENDPOINT_TYPE specifies the endpoint type to use for the endpoints
# in the Keystone service catalog. Use this setting when Horizon is running
# external to the OpenStack environment. The default is 'internalURL'.
# external to the OpenStack environment. The default is 'publicURL'.
#OPENSTACK_ENDPOINT_TYPE = "publicURL"
# SECONDARY_ENDPOINT_TYPE specifies the fallback endpoint type to use in the
# case that OPENSTACK_ENDPOINT_TYPE is not present in the endpoints
# in the Keystone service catalog. Use this setting when Horizon is running
# external to the OpenStack environment. The default is None. This
# value should differ from OPENSTACK_ENDPOINT_TYPE if used.
#SECONDARY_ENDPOINT_TYPE = "publicURL"
# The number of objects (Swift containers/objects or images) to display
# on a single page before providing a paging element (a "more" link)
# to paginate results.
@ -94,54 +208,243 @@ API_RESULT_PAGE_SIZE = 20
# of your entire OpenStack installation, and hopefully be in UTC.
TIME_ZONE = "UTC"
# When launching an instance, the menu of available flavors is
# sorted by RAM usage, ascending. Provide a callback method here
# (and/or a flag for reverse sort) for the sorted() method if you'd
# like a different behaviour. For more info, see
# http://docs.python.org/2/library/functions.html#sorted
# CREATE_INSTANCE_FLAVOR_SORT = {
# 'key': my_awesome_callback_method,
# 'reverse': False,
# }
# The Horizon Policy Enforcement engine uses these values to load per service
# policy rule files. The content of these files should match the files the
# OpenStack services are using to determine role based access control in the
# target installation.
# Path to directory containing policy.json files
#POLICY_FILES_PATH = os.path.join(ROOT_PATH, "conf")
# Map of local copy of service policy files
#POLICY_FILES = {
# 'identity': 'keystone_policy.json',
# 'compute': 'nova_policy.json'
#}
# Trove user and database extension support. By default support for
# creating users and databases on database instances is turned on.
# To disable these extensions set the permission here to something
# unusable such as ["!"].
# TROVE_ADD_USER_PERMS = []
# TROVE_ADD_DATABASE_PERMS = []
LOGGING = {
'version': 1,
# When set to True this will disable all logging except
# for loggers specified in this configuration dictionary. Note that
# if nothing is specified here and disable_existing_loggers is True,
# django.db.backends will still log unless it is disabled explicitly.
'disable_existing_loggers': False,
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'django.utils.log.NullHandler',
},
'console': {
# Set the level to "DEBUG" for verbose output logging.
'level': 'INFO',
'class': 'logging.StreamHandler',
},
},
'loggers': {
# Logging from django.db.backends is VERY verbose, send to null
# by default.
'django.db.backends': {
'handlers': ['null'],
'propagate': False,
},
'horizon': {
'handlers': ['console'],
'propagate': False,
},
'openstack_dashboard': {
'handlers': ['console'],
'propagate': False,
},
'novaclient': {
'handlers': ['console'],
'propagate': False,
},
'keystoneclient': {
'handlers': ['console'],
'propagate': False,
},
'glanceclient': {
'handlers': ['console'],
'propagate': False,
},
'nose.plugins.manager': {
'handlers': ['console'],
'propagate': False,
}
}
'version': 1,
# When set to True this will disable all logging except
# for loggers specified in this configuration dictionary. Note that
# if nothing is specified here and disable_existing_loggers is True,
# django.db.backends will still log unless it is disabled explicitly.
'disable_existing_loggers': False,
'handlers': {
'null': {
'level': 'DEBUG',
'class': 'django.utils.log.NullHandler',
},
'console': {
# Set the level to "DEBUG" for verbose output logging.
'level': 'INFO',
'class': 'logging.StreamHandler',
},
},
'loggers': {
# Logging from django.db.backends is VERY verbose, send to null
# by default.
'django.db.backends': {
'handlers': ['null'],
'propagate': False,
},
'requests': {
'handlers': ['null'],
'propagate': False,
},
'horizon': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'openstack_dashboard': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'novaclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'cinderclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'keystoneclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'glanceclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'neutronclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'heatclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'ceilometerclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'troveclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'swiftclient': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'openstack_auth': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'nose.plugins.manager': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'django': {
'handlers': ['console'],
'level': 'DEBUG',
'propagate': False,
},
'iso8601': {
'handlers': ['null'],
'propagate': False,
},
}
}
SECURITY_GROUP_RULES = {
'all_tcp': {
'name': 'ALL TCP',
'ip_protocol': 'tcp',
'from_port': '1',
'to_port': '65535',
},
'all_udp': {
'name': 'ALL UDP',
'ip_protocol': 'udp',
'from_port': '1',
'to_port': '65535',
},
'all_icmp': {
'name': 'ALL ICMP',
'ip_protocol': 'icmp',
'from_port': '-1',
'to_port': '-1',
},
'ssh': {
'name': 'SSH',
'ip_protocol': 'tcp',
'from_port': '22',
'to_port': '22',
},
'smtp': {
'name': 'SMTP',
'ip_protocol': 'tcp',
'from_port': '25',
'to_port': '25',
},
'dns': {
'name': 'DNS',
'ip_protocol': 'tcp',
'from_port': '53',
'to_port': '53',
},
'http': {
'name': 'HTTP',
'ip_protocol': 'tcp',
'from_port': '80',
'to_port': '80',
},
'pop3': {
'name': 'POP3',
'ip_protocol': 'tcp',
'from_port': '110',
'to_port': '110',
},
'imap': {
'name': 'IMAP',
'ip_protocol': 'tcp',
'from_port': '143',
'to_port': '143',
},
'ldap': {
'name': 'LDAP',
'ip_protocol': 'tcp',
'from_port': '389',
'to_port': '389',
},
'https': {
'name': 'HTTPS',
'ip_protocol': 'tcp',
'from_port': '443',
'to_port': '443',
},
'smtps': {
'name': 'SMTPS',
'ip_protocol': 'tcp',
'from_port': '465',
'to_port': '465',
},
'imaps': {
'name': 'IMAPS',
'ip_protocol': 'tcp',
'from_port': '993',
'to_port': '993',
},
'pop3s': {
'name': 'POP3S',
'ip_protocol': 'tcp',
'from_port': '995',
'to_port': '995',
},
'ms_sql': {
'name': 'MS SQL',
'ip_protocol': 'tcp',
'from_port': '1433',
'to_port': '1433',
},
'mysql': {
'name': 'MYSQL',
'ip_protocol': 'tcp',
'from_port': '3306',
'to_port': '3306',
},
'rdp': {
'name': 'RDP',
'ip_protocol': 'tcp',
'from_port': '3389',
'to_port': '3389',
},
}

View File

@ -36,7 +36,7 @@ class UploadServiceForm(SelfHandlingForm):
messages.success(request, _('Service uploaded.'))
return result
except HTTPException as e:
log.exception(e)
log.exception(_('Uploading service failed'))
redirect = reverse('horizon:murano:service_catalog:index')
exceptions.handle(request,
_('Unable to upload service. '
@ -60,8 +60,8 @@ class UploadFileToService(SelfHandlingForm):
def handle(self, request, data):
filename = data['file'].name
log.debug('Uploading file to metadata repository {0} and assiging'
' it to {1} service'.format(filename, self.service_id))
log.debug(_('Uploading file to metadata repository {0} and assigning'
' it to {1} service'.format(filename, self.service_id)))
try:
result = metadataclient(request).metadata_admin.\
upload_file_to_service(self.data_type,
@ -75,7 +75,7 @@ class UploadFileToService(SelfHandlingForm):
except HTTPException as e:
redirect = reverse('horizon:murano:service_catalog:manage_service',
args=(self.service_id,))
log.exception(e)
log.exception(_('Uploading file failed'))
msg = _("Unable to upload {0} file of '{1}' type."
" Error code: {2}".format(filename,
self.data_type,
@ -116,7 +116,8 @@ class UploadFileForm(UploadFileToService):
return result
except HTTPException as e:
redirect = reverse('horizon:murano:service_catalog:manage_files')
log.exception(e)
log.exception(_('Uploading file or '
'modifying service manifest file failed'))
msg = _("Unable to upload {0} file of '{1}' type."
" Error code: {2}".format(filename,
self.data_type,

View File

@ -66,8 +66,8 @@ class DownloadService(tables.Action):
response['Content-Disposition'] = 'filename={name}.tar.gz'.format(
name=service_id)
return response
except HTTPException as e:
LOG.exception(e)
except HTTPException:
LOG.exception(_('Something went wrong during service downloading'))
redirect = reverse('horizon:murano:service_catalog:index')
exceptions.handle(request,
_('Unable to download service.'),
@ -85,8 +85,9 @@ class ToggleEnabled(tables.BatchAction):
for obj_id in obj_ids:
try:
metadataclient(request).metadata_admin.toggle_enabled(obj_id)
except HTTPException as e:
LOG.exception(e)
except HTTPException:
LOG.exception(_('Toggling service state in manifest file in '
'metadata repository failed'))
exceptions.handle(request,
_('Unable to toggle service state.'))
else:
@ -94,7 +95,7 @@ class ToggleEnabled(tables.BatchAction):
obj.enabled = not obj.enabled
messages.success(
request,
_("Service '{service} successfully toggled".format(
_("Service '{service}' successfully toggled".format(
service=obj_id)))
@ -105,8 +106,9 @@ class DeleteService(tables.DeleteAction):
def delete(self, request, obj_id):
try:
metadataclient(request).metadata_admin.delete_service(obj_id)
except HTTPException as e:
LOG.exception(e)
except HTTPException:
LOG.exception(_('Unable to delete service'
' in Murano Metadata server'))
exceptions.handle(request,
_('Unable to remove service.'),
redirect='horizon:murano:service_catalog:index')
@ -159,29 +161,6 @@ class ServiceCatalogTable(tables.DataTable):
DeleteService)
class ToggleEnabled(tables.BatchAction):
name = 'toggle_enabled'
data_type_singular = _('Active')
data_type_plural = _('Active')
action_present = _('Toggle')
action_past = _('Toggled')
def handle(self, table, request, obj_ids):
for obj_id in obj_ids:
try:
metadataclient(request).metadata_admin.toggle_enabled(obj_id)
except HTTPException as e:
LOG.exception(e)
exceptions.handle(request,
_('Unable to toggle service state.'))
else:
obj = table.get_object_by_id(obj_id)
obj.enabled = not obj.enabled
messages.success(request,
_("Service '{service} successfully "
"toggled".format(service=obj_id)))
class DeleteFile(tables.DeleteAction):
name = 'delete_file'
data_type_singular = _('File')
@ -190,8 +169,8 @@ class DeleteFile(tables.DeleteAction):
try:
data_type, obj_id = obj_id.split('##')
metadataclient(request).metadata_admin.delete(data_type, obj_id)
except HTTPException as e:
LOG.exception(e)
except HTTPException:
LOG.exception(_('Deleting file on Metadata'))
redirect = reverse('horizon:murano:service_catalog:manage_files')
exceptions.handle(
request,
@ -209,8 +188,9 @@ class DeleteFileFromService(tables.DeleteAction):
data_type, filename = obj_id.split('##')
metadataclient(request).metadata_admin.delete_from_service(
data_type, filename, service_id)
except HTTPException as e:
LOG.exception(e)
except HTTPException:
LOG.exception(_('Deleting manifest or file connected '
'with it failed in Metadata Service'))
redirect = reverse('horizon:murano:service_catalog:manage_service',
args=(service_id,))
exceptions.handle(
@ -246,8 +226,9 @@ class DownloadFile(tables.Action):
response['Content-Disposition'] = 'filename={name}'.format(name=
obj_id)
return response
except HTTPException as e:
LOG.exception(e)
except HTTPException:
LOG.exception(_('Error during downloading file '
'from Murano Metadata Repository'))
redirect = reverse('horizon:murano:service_catalog:manage_files')
exceptions.handle(
request,

View File

@ -45,6 +45,11 @@ def define_tables(table_name, step_verbose_name):
url = None
classes = ('ajax-modal', 'btn-create')
def allowed(self, request, service):
if self.table.name == 'ui' and self.table.data:
return False
return True
class ObjectsTable(tables.DataTable):
file_name = tables.Column('filename', verbose_name=_('File Name'))
path = tables.Column('path', verbose_name=_('Nested Path'))

View File

@ -104,11 +104,11 @@ class ComposeServiceView(WorkflowView):
raise RuntimeError('Not found service id')
else:
return files_dict
except (HTTPInternalServerError, NotFound) as e:
LOG.exception(e)
msg = _('Error with Murano Metadata Repository')
except (HTTPInternalServerError, NotFound):
err_msg = _('Error with Murano Metadata Repository')
LOG.exception(err_msg)
redirect = reverse_lazy('horizon:murano:service_catalog:index')
exceptions.handle(self.request, msg, redirect)
exceptions.handle(self.request, err_msg, redirect)
class UploadFileView2(ModalFormView):

View File

@ -1,5 +1,6 @@
import logging
import os
import tempfile
import sys
from openstack_dashboard import exceptions
@ -13,10 +14,13 @@ if ROOT_PATH not in sys.path:
DEBUG = False
TEMPLATE_DEBUG = DEBUG
METADATA_CACHE_DIR = os.path.join(tempfile.gettempdir(),
'muranodashboard-cache')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': os.path.abspath(os.path.join(ROOT_PATH, 'dashboard.sqlite'))
'NAME': os.path.join(METADATA_CACHE_DIR, 'openstack-dashboard.sqlite')
}
}
@ -126,7 +130,7 @@ TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
SESSION_ENGINE = 'django.contrib.sessions.backends.signed_cookies'
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
SESSION_COOKIE_HTTPONLY = True
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
SESSION_COOKIE_SECURE = False

View File

@ -51,7 +51,7 @@ function install_prerequisites()
if [ $? -eq 1 ]; then
retval=1
return $retval
fi
fi
find /var/lib/apt/lists/ -name "*cloud.archive*" | grep -q "havana_main"
if [ $? -ne 0 ]; then
add-apt-repository -y cloud-archive:havana >> $LOGFILE 2>&1
@ -98,11 +98,11 @@ function make_tarball()
chmod +x $setuppy
rm -rf $RUN_DIR/*.egg-info
cd $RUN_DIR && python $setuppy egg_info > /dev/null 2>&1
if [ $? -ne 0 ];then
if [ $? -ne 0 ];then
log "...\"$setuppy\" egg info creation fails, exiting!!!"
retval=1
exit 1
fi
fi
rm -rf $RUN_DIR/dist/*
log "...\"setup.py sdist\" output will be recorded in \"$LOGFILE\""
cd $RUN_DIR && $setuppy sdist >> $LOGFILE 2>&1
@ -110,7 +110,7 @@ function make_tarball()
log "...\"$setuppy\" tarball creation fails, exiting!!!"
retval=1
exit 1
fi
fi
#TRBL_FILE=$(basename $(ls $RUN_DIR/dist/*.tar.gz | head -n 1))
TRBL_FILE=$(ls $RUN_DIR/dist/*.tar.gz | head -n 1)
if [ ! -e "$TRBL_FILE" ]; then
@ -137,33 +137,33 @@ function run_pip_install()
log "...pip install fails, exiting!"
retval=1
exit 1
fi
fi
return $retval
}
modify_horizon_config()
{
INFILE=$1
REMOVE=$2
REMOVE=$2
PATTERN='from openstack_dashboard import policy'
TMPFILE="./tmpfile"
retval=0
if [ -f $INFILE ]; then
lines=$(sed -ne '/^#START_MURANO_DASHBOARD/,/^#END_MURANO_DASHBOARD/ =' $INFILE)
if [ -n "$lines" ]; then
if [ ! -z $REMOVE ]; then
if [ -n "$lines" ]; then
if [ ! -z $REMOVE ]; then
log "Removing $APPLICATION_NAME data from \"$INFILE\"..."
sed -e '/^#START_MURANO_DASHBOARD/,/^#END_MURANO_DASHBOARD/ d' -i $INFILE
if [ $? -ne 0 ];then
if [ $? -ne 0 ];then
log "...can't modify \"$INFILE\", check permissions or something else, exiting!!!"
retval=1
return $retval
else
log "...success"
fi
else
fi
else
log "\"$INFILE\" already has $APPLICATION_NAME data, you can change it manually and restart apache2/httpd service"
fi
else
fi
else
if [ -z "$REMOVE" ]; then
log "Adding $APPLICATION_NAME data to \"$INFILE\"..."
rm -f $TMPFILE
@ -197,38 +197,38 @@ NETWORK_TOPOLOGY = 'routed'
#END_MURANO_DASHBOARD
EOF
sed -ne "/$PATTERN/r $TMPFILE" -e 1x -e '2,${x;p}' -e '${x;p}' -i $INFILE
if [ $? -ne 0 ];then
if [ $? -ne 0 ];then
log "Can't modify \"$INFILE\", check permissions or something else, exiting!!!"
else
rm -f $TMPFILE
log "...success"
fi
fi
fi
else
echo "File \"$1\" not found, exiting!!!"
fi
fi
fi
else
echo "File \"$1\" not found, exiting!!!"
retval=1
fi
fi
return $retval
}
function find_horizon_config()
{
retval=0
for cfg_file in $(echo $HORIZON_CONFIGS | sed 's/,/ /')
do
do
if [ -e "$cfg_file" ]; then
log "Horizon config found at \"$cfg_file\""
modify_horizon_config $cfg_file $1
log "Horizon config found at \"$cfg_file\""
modify_horizon_config $cfg_file $1
retval=0
break
else
retval=1
fi
done
fi
done
if [ $retval -eq 1 ]; then
log "Horizon config not found or openstack-dashboard does not installed, to override this set proper \"HORIZON_CONFIGS\" variable, exiting!!!"
log "Horizon config not found or openstack-dashboard does not installed, to override this set proper \"HORIZON_CONFIGS\" variable, exiting!!!"
#exit 1
fi
fi
return $retval
}
@ -244,7 +244,7 @@ function prepare_db()
retval=1
else
log "..success"
fi
fi
return $retval
}
function rebuildstatic()
@ -264,15 +264,15 @@ function rebuildstatic()
log "...openstack-dashboard manage.py not found, exiting!!!"
retval=1
return $retval
fi
fi
_old_murano_static="$(dirname $horizon_manage)/openstack_dashboard/static/muranodashboard"
if [ -d "$_old_murano_static" ];then
log "...$APPLICATION_NAME static for \"muranodashboard\" found under \"HORIZON\" STATIC, deleting \"$_old_murano_static\"..."
rm -rf $_old_murano_static
if [ $? -ne 0 ]; then
log "...can't delete \"$_old_murano_static\, WARNING!!!"
fi
fi
fi
fi
log "Rebuilding STATIC output will be recorded in \"$LOGFILE\""
#python $horizon_manage collectstatic --noinput >> $LOGFILE 2>&1
chmod a+rw $LOGFILE
@ -280,9 +280,9 @@ function rebuildstatic()
if [ $? -ne 0 ]; then
log "...\"$horizon_manage\" collectstatic failed, exiting!!!"
retval=1
else
else
log "...success"
fi
fi
prepare_db "$horizon_manage" || retval=$?
return $retval
}
@ -291,15 +291,15 @@ function run_pip_uninstall()
find_pip
retval=0
pack_to_del=$(is_py_package_installed "$APPLICATION_NAME")
if [ $? -eq 0 ]; then
if [ $? -eq 0 ]; then
log "Running \"$PIPCMD uninstall $PIPARGS $APPLICATION_NAME\" output will be recorded in \"$LOGFILE\""
$PIPCMD uninstall $pack_to_del --yes >> $LOGFILE 2>&1
if [ $? -ne 0 ]; then
log "...can't uninstall $APPLICATION_NAME with $PIPCMD"
retval=1
else
else
log "...success"
fi
fi
else
log "Python package for \"$APPLICATION_NAME\" not found"
fi
@ -337,13 +337,13 @@ function install_application()
mk_dir "$APPLICATION_LOG_DIR" "$WEB_SERVICE_USER" "$WEB_SERVICE_GROUP" || exit $?
mk_dir "$APPLICATION_CACHE_DIR" "$WEB_SERVICE_USER" "$WEB_SERVICE_GROUP" || exit $?
horizon_etc_cfg=$(find /etc/openstack-dashboard -name "local_setting*" | head -n 1)
if [ $? -ne 0 ]; then
if [ $? -ne 0 ]; then
log "Can't find horizon config under \"/etc/openstack-dashboard...\""
retval=1
else
iniset '' 'ALLOWED_HOSTS' "'*'" $horizon_etc_cfg
iniset '' 'DEBUG' 'True' $horizon_etc_cfg
fi
fi
return $retval
}
function uninstall_application()
@ -355,7 +355,7 @@ function uninstall_application()
function postinst()
{
rebuildstatic || exit $?
sleep 2
sleep 2
chown -R $WEB_SERVICE_USER:$WEB_SERVICE_GROUP /var/lib/openstack-dashboard
service $WEB_SERVICE_SYSNAME restart
}
@ -381,17 +381,17 @@ case $COMMAND in
install_application || exit $?
postinst || exit $?
log "...success"
;;
;;
uninstall )
uninstall )
log "Uninstalling \"$APPLICATION_NAME\" from system..."
uninstall_application || exit $?
postuninst
log "Software uninstalled, application logs located at \"$APPLICATION_LOG_DIR\", cache files - at \"$APPLICATION_CACHE_DIR'\" ."
;;
;;
* )
* )
echo -e "Usage: $(basename "$0") [command] \nCommands:\n\tinstall - Install \"$APPLICATION_NAME\" software\n\tuninstall - Uninstall \"$APPLICATION_NAME\" software"
exit 1
;;
exit 1
;;
esac