Merge "Cherry-pick the following commits from master:" into release-0.4
This commit is contained in:
commit
a4d3cb9a82
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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, [])
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 ----#
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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'))
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
90
setup.sh
90
setup.sh
|
@ -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
|
Loading…
Reference in New Issue