Implement dynamic UI for all current service creation forms.
1. Show only selected service description in Create Service step0 form. 2. Move template names and params postprocessing into service forms. 3. Move all forms post-processing (before passing to api) into declarative specs. All post-processing is actually performed in extract_attributes form functions according to attributeNames attr. for each field in each form. 4. Support generation of multiple forms (steps) from one service definition. 5. Dynamic forms UI use services yaml-description. 6. Use predefined field types instead of eval-ing python object names. Also use camelCased names instead of pythonic_ones. 7. Localize string values for predefined keys and do not use string prefix. 8. Add verify password fields automatically for each password field. 9. Use attributeNames for each field description instead. Value of attributeNames can be: * list - field value will propagated to multiple attributes; * omitted - field value will be propagated to attribute with same name; * false - field value won't be propagated to any attribute; * string - field value will be propagated to attribute whose name is specified by the string, with one note: `attributeNames: domain.x' for field `A' will produce attribute value `domain: {x: A.value}' 10. Remove django template from service description. Use per-field descriptions. If the field in service's description has attribute `description', it is automatically inserted into appropriate form description. If `descriptionTitle' attribute is present, it becomes the title of field description, else `label' attribute is used. 11. Also in addition to an existing regexp literal (quoted string) add new one: quoted string with slashes: '/some-regexp/flags'. Flags is split into list of 1-letter strings <flag>, each of them is uppercased to <FLAG> and sent to re.compile as getattr(re, <FLAG>). Change-Id: I76c25cbbd682823cbef2693b51ccd69ffe83b6d4
This commit is contained in:
parent
5bc5102057
commit
9c9b9d7778
|
@ -1,3 +1,5 @@
|
|||
*~
|
||||
*.orig
|
||||
*.pyc
|
||||
*.swp
|
||||
.environment_version
|
||||
|
|
|
@ -65,15 +65,6 @@ class NodeDataGrid(DataGrid):
|
|||
super(NodeDataGrid, self).load_state(render_context)
|
||||
|
||||
|
||||
# def add(self):
|
||||
# id = self.pk.next()
|
||||
# self.queryset.add({'name': 'node%s' % (id,), 'is_sync': False,
|
||||
# 'is_async': False, 'is_primary': False, 'id': id})
|
||||
#
|
||||
# def remove(self):
|
||||
# pass
|
||||
|
||||
|
||||
class DataGridWidget(forms.widgets.Input):
|
||||
template_name = 'data_grid_field.html'
|
||||
|
||||
|
@ -116,16 +107,3 @@ class DataGridCompound(forms.MultiWidget):
|
|||
return [json.loads(value), value]
|
||||
else:
|
||||
return [None, None]
|
||||
|
||||
|
||||
class DataGridField(forms.MultiValueField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(DataGridField, self).__init__(
|
||||
(forms.CharField(required=False), forms.CharField()),
|
||||
*args, widget=DataGridCompound, **kwargs)
|
||||
|
||||
def update_request(self, request):
|
||||
self.widget.update_request(request)
|
||||
|
||||
def compress(self, data_list):
|
||||
return data_list[1]
|
||||
|
|
|
@ -20,7 +20,7 @@ from horizon.exceptions import ServiceCatalogException
|
|||
from muranoclient.common.exceptions import HTTPForbidden, HTTPNotFound
|
||||
from openstack_dashboard.api.base import url_for
|
||||
from muranoclient.v1.client import Client
|
||||
from consts import *
|
||||
from muranodashboard.panel.services import get_service_name
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
@ -233,8 +233,8 @@ def services_list(request, environment_id):
|
|||
|
||||
for service_item in environment.services:
|
||||
service_data = service_item
|
||||
service_data['full_service_name'] = \
|
||||
SERVICE_NAME_DICT[service_data['type']]
|
||||
service_data['full_service_name'] = get_service_name(
|
||||
service_data['type'])
|
||||
|
||||
if service_data['id'] in reports and reports[service_data['id']]:
|
||||
last_operation = str(reports[service_data['id']].text)
|
||||
|
@ -323,7 +323,7 @@ def get_deployment_descr(request, environment_id, deployment_id):
|
|||
descr = deployment.description
|
||||
if 'services' in descr:
|
||||
for service in descr['services']:
|
||||
service['full_service_name'] = SERVICE_NAME_DICT.get(
|
||||
service['type'], service['type'])
|
||||
service['full_service_name'] = get_service_name(
|
||||
service['type'])
|
||||
return descr
|
||||
return None
|
||||
|
|
|
@ -11,26 +11,6 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
AD_NAME = 'activeDirectory'
|
||||
IIS_NAME = 'webServer'
|
||||
ASP_NAME = 'aspNetApp'
|
||||
IIS_FARM_NAME = 'webServerFarm'
|
||||
ASP_FARM_NAME = 'aspNetAppFarm'
|
||||
MSSQL_NAME = 'msSqlServer'
|
||||
MSSQL_CLUSTER_NAME = 'msSqlClusterServer'
|
||||
|
||||
SERVICE_NAMES = (AD_NAME, IIS_NAME, ASP_NAME,
|
||||
IIS_FARM_NAME, ASP_FARM_NAME, MSSQL_NAME, MSSQL_CLUSTER_NAME)
|
||||
|
||||
SERVICE_NAME_DICT = {AD_NAME: 'Active Directory',
|
||||
IIS_NAME: 'IIS',
|
||||
ASP_NAME: 'ASP.NET Application',
|
||||
IIS_FARM_NAME: 'IIS Farm',
|
||||
ASP_FARM_NAME: 'ASP.NET Farm',
|
||||
MSSQL_NAME: 'Microsoft SQL Server',
|
||||
MSSQL_CLUSTER_NAME: 'Microsoft SQL Server Cluster'}
|
||||
|
||||
STATUS_ID_READY = 'ready'
|
||||
STATUS_ID_PENDING = 'pending'
|
||||
STATUS_ID_DEPLOYING = 'deploying'
|
||||
|
|
|
@ -13,645 +13,28 @@
|
|||
# under the License.
|
||||
|
||||
import logging
|
||||
|
||||
import re
|
||||
import ast
|
||||
import json
|
||||
from netaddr import all_matching_cidrs
|
||||
from django import forms
|
||||
from django.core.validators import RegexValidator, validate_ipv4_address
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import smart_text
|
||||
from openstack_dashboard.api import glance
|
||||
from horizon import exceptions, messages
|
||||
from openstack_dashboard.api.nova import novaclient
|
||||
from muranodashboard.panel import api
|
||||
from consts import *
|
||||
from muranodashboard.datagrids import DataGridField
|
||||
|
||||
from muranodashboard.panel.services import iterate_over_service_forms, \
|
||||
get_service_choices
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
CONFIRM_ERR_DICT = {'required': _('Please confirm your password')}
|
||||
|
||||
validate_name = RegexValidator(re.compile(r'^[-\w]+$'),
|
||||
_(u'Just letters, numbers, underscores \
|
||||
and hyphens are allowed.'), 'invalid')
|
||||
|
||||
|
||||
def perform_password_check(password1, password2, type):
|
||||
if password1 != password2:
|
||||
raise forms.ValidationError(
|
||||
_(' %s passwords don\'t match' % type))
|
||||
|
||||
|
||||
def validate_domain_name(name_to_check):
|
||||
subdomain_list = name_to_check.split('.')
|
||||
if len(subdomain_list) == 1:
|
||||
raise forms.ValidationError(
|
||||
_('Single-level domain is not appropriate. '))
|
||||
domain_name_re = re.compile(r'^[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]$')
|
||||
for subdomain in subdomain_list:
|
||||
if not domain_name_re.match(subdomain):
|
||||
raise forms.ValidationError(
|
||||
_('Only letters, numbers and dashes in the middle are allowed.'
|
||||
' Period characters are allowed only when they are used to '
|
||||
'delimit the components of domain style names.'))
|
||||
|
||||
|
||||
def validate_cluster_ip(request, ip_ranges):
|
||||
def perform_checking(ip):
|
||||
validate_ipv4_address(ip)
|
||||
if not all_matching_cidrs(ip, ip_ranges) and ip_ranges:
|
||||
raise forms.ValidationError(_('Specified Cluster Static IP is'
|
||||
'not in valid IP range'))
|
||||
try:
|
||||
ip_info = novaclient(request).fixed_ips.get(ip)
|
||||
except exceptions.UNAUTHORIZED:
|
||||
exceptions.handle(request, _("Unable to retrieve information "
|
||||
"about fixed IP or IP is not valid."),
|
||||
ignore=True)
|
||||
else:
|
||||
if ip_info.hostname:
|
||||
raise forms.ValidationError(_('Specified Cluster Static IP '
|
||||
'is already in use'))
|
||||
return perform_checking
|
||||
|
||||
|
||||
def validate_hostname_template(template, instance_count):
|
||||
if template and instance_count > 1:
|
||||
if not '#' in template:
|
||||
raise forms.ValidationError(_('Incrementation symbol "#" is '
|
||||
'required in the Hostname template'))
|
||||
|
||||
|
||||
class PasswordWidget(forms.PasswordInput):
|
||||
class Media:
|
||||
js = ('muranodashboard/js/passwordfield.js',)
|
||||
|
||||
|
||||
class PasswordField(forms.CharField):
|
||||
special_characters = '!@#$%^&*()_+|\/.,~?><:{}'
|
||||
password_re = re.compile('^.*(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[%s]).*$'
|
||||
% special_characters)
|
||||
validate_password = RegexValidator(
|
||||
password_re, _('The password must contain at least one letter, one \
|
||||
number and one special character'), 'invalid')
|
||||
|
||||
def __init__(self, label, *args, **kwargs):
|
||||
help_text = kwargs.get('help_text')
|
||||
if not help_text:
|
||||
help_text = _('Enter a complex password with at least one letter, \
|
||||
one number and one special character')
|
||||
|
||||
error_messages = {
|
||||
'invalid': self.validate_password.message}
|
||||
err_msg = kwargs.get('error_messages')
|
||||
if err_msg:
|
||||
if err_msg.get('required'):
|
||||
error_messages['required'] = err_msg.get('required')
|
||||
|
||||
required = kwargs.get('required')
|
||||
if required is None:
|
||||
required = True
|
||||
|
||||
super(PasswordField, self).__init__(
|
||||
min_length=7,
|
||||
max_length=255,
|
||||
validators=[self.validate_password],
|
||||
label=label,
|
||||
required=required,
|
||||
error_messages=error_messages,
|
||||
help_text=help_text,
|
||||
widget=PasswordWidget(render_value=True))
|
||||
|
||||
|
||||
class DatabaseListField(forms.CharField):
|
||||
validate_mssql_identifier = RegexValidator(
|
||||
re.compile(r'^[a-zA-z_][a-zA-Z0-9_$#@]*$'),
|
||||
_((u'First symbol should be latin letter or underscore. Subsequent ' +
|
||||
u'symbols can be latin letter, numeric, underscore, at sign, ' +
|
||||
u'number sign or dollar sign')))
|
||||
|
||||
default_error_messages = {'invalid': validate_mssql_identifier.message}
|
||||
|
||||
def to_python(self, value):
|
||||
"""Normalize data to a list of strings."""
|
||||
|
||||
# Return an empty list if no input was given.
|
||||
if not value:
|
||||
return []
|
||||
return value.split(',')
|
||||
|
||||
def validate(self, value):
|
||||
"""Check if value consists only of valid names."""
|
||||
|
||||
# Use the parent's handling of required fields, etc.
|
||||
super(DatabaseListField, self).validate(value)
|
||||
for db_name in value:
|
||||
self.validate_mssql_identifier(db_name.strip())
|
||||
# has to be returned to all forms
|
||||
# def validate_hostname_template(template, instance_count):
|
||||
# if template and instance_count > 1:
|
||||
# if not '#' in template:
|
||||
# raise forms.ValidationError(
|
||||
# _('Incrementation symbol "#" is '
|
||||
# 'required in the Hostname template'))
|
||||
#
|
||||
#
|
||||
|
||||
|
||||
class WizardFormServiceType(forms.Form):
|
||||
ad_service = (AD_NAME, 'Active Directory')
|
||||
iis_service = (IIS_NAME, 'Internet Information Services')
|
||||
asp_service = (ASP_NAME, 'ASP.NET Application')
|
||||
iis_farm_service = (IIS_FARM_NAME,
|
||||
'Internet Information Services Web Farm')
|
||||
asp_farm_service = (ASP_FARM_NAME, 'ASP.NET Application Web Farm')
|
||||
ms_sql_service = (MSSQL_NAME, 'MS SQL Server')
|
||||
ms_sql_cluster = (MSSQL_CLUSTER_NAME, 'MS SQL Cluster Server')
|
||||
service = forms.ChoiceField(label=_('Service Type'),
|
||||
choices=[
|
||||
ad_service,
|
||||
iis_service,
|
||||
asp_service,
|
||||
iis_farm_service,
|
||||
asp_farm_service,
|
||||
ms_sql_service,
|
||||
ms_sql_cluster
|
||||
])
|
||||
choices=get_service_choices())
|
||||
|
||||
|
||||
class CommonPropertiesExtension(object):
|
||||
|
||||
hostname_re = re.compile(
|
||||
r'^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)'
|
||||
r'*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$')
|
||||
validate_hostname = RegexValidator(hostname_re, _('text'))
|
||||
|
||||
def __init__(self):
|
||||
self.fields.insert(
|
||||
len(self.fields), 'unit_name_template',
|
||||
forms.CharField(
|
||||
label=_('Hostname template'),
|
||||
required=False,
|
||||
validators=[self.validate_hostname],
|
||||
help_text='Optional field for a machine hostname template.'))
|
||||
|
||||
for field, instance in self.fields.iteritems():
|
||||
if not instance.required:
|
||||
instance.widget.attrs['placeholder'] = 'Optional'
|
||||
if field in ['adm_password1', 'password_field1']:
|
||||
instance.widget.attrs['class'] = 'password'
|
||||
if field in ['adm_password2', 'password_field2']:
|
||||
instance.widget.attrs['class'] = 'confirm_password'
|
||||
|
||||
|
||||
class WizardFormADConfiguration(forms.Form,
|
||||
CommonPropertiesExtension):
|
||||
|
||||
dc_name = forms.CharField(
|
||||
label=_('Domain Name'),
|
||||
min_length=2,
|
||||
max_length=64,
|
||||
validators=[validate_domain_name],
|
||||
help_text=_('Just letters, numbers and dashes are allowed. \
|
||||
Single-level domain is not appropriate.'))
|
||||
|
||||
dc_count = forms.IntegerField(
|
||||
label=_('Instance Count'),
|
||||
min_value=1,
|
||||
max_value=100,
|
||||
initial=1,
|
||||
help_text=_('Enter an integer value between 1 and 100'))
|
||||
|
||||
adm_user = forms.CharField(
|
||||
label=_('Account Name'),
|
||||
initial='Administrator',
|
||||
validators=[validate_name])
|
||||
|
||||
adm_password1 = PasswordField(_('Administrator password'),)
|
||||
|
||||
adm_password2 = PasswordField(
|
||||
_('Confirm password'),
|
||||
help_text=_('Retype your password'),
|
||||
error_messages=CONFIRM_ERR_DICT)
|
||||
|
||||
password_field1 = PasswordField(_('Recovery password'))
|
||||
|
||||
password_field2 = PasswordField(
|
||||
_('Confirm password'),
|
||||
error_messages=CONFIRM_ERR_DICT,
|
||||
help_text=_('Retype your password'))
|
||||
|
||||
def clean(self):
|
||||
admin_password1 = self.cleaned_data.get('adm_password1')
|
||||
admin_password2 = self.cleaned_data.get('adm_password2')
|
||||
perform_password_check(admin_password1,
|
||||
admin_password2,
|
||||
'Administrator')
|
||||
recovery_password1 = self.cleaned_data.get('password_field1')
|
||||
recovery_password2 = self.cleaned_data.get('password_field2')
|
||||
perform_password_check(recovery_password1,
|
||||
recovery_password2,
|
||||
'Recovery')
|
||||
|
||||
instance_count = self.cleaned_data.get('dc_count')
|
||||
hostname_template = self.cleaned_data.get('unit_name_template')
|
||||
validate_hostname_template(hostname_template, instance_count)
|
||||
|
||||
return self.cleaned_data
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardFormADConfiguration, self).__init__(*args, **kwargs)
|
||||
CommonPropertiesExtension.__init__(self)
|
||||
|
||||
|
||||
class WizardFormIISConfiguration(forms.Form,
|
||||
CommonPropertiesExtension):
|
||||
|
||||
service_name = forms.CharField(
|
||||
label=_('Service Name'),
|
||||
min_length=2,
|
||||
max_length=64,
|
||||
validators=[validate_name],
|
||||
error_messages={'invalid': validate_name.message},
|
||||
help_text=_('Just letters, numbers, underscores \
|
||||
and hyphens are allowed'))
|
||||
|
||||
adm_password1 = PasswordField(
|
||||
_('Administrator password'),
|
||||
help_text=_('Enter a complex password with at least one letter, one \
|
||||
number and one special character'))
|
||||
adm_password2 = PasswordField(
|
||||
_('Confirm password'),
|
||||
error_messages=CONFIRM_ERR_DICT,
|
||||
help_text=_('Retype your password'))
|
||||
|
||||
domain = forms.ChoiceField(
|
||||
label=_('Active Directory Domain'),
|
||||
required=False,
|
||||
help_text=_('Optional field for a domain to which service can be \
|
||||
joined '))
|
||||
|
||||
def clean(self):
|
||||
admin_password1 = self.cleaned_data.get('adm_password1')
|
||||
admin_password2 = self.cleaned_data.get('adm_password2')
|
||||
perform_password_check(admin_password1,
|
||||
admin_password2,
|
||||
'Administrator')
|
||||
instance_count = self.cleaned_data.get('instance_count')
|
||||
hostname_template = self.cleaned_data.get('unit_name_template')
|
||||
validate_hostname_template(hostname_template, instance_count)
|
||||
return self.cleaned_data
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardFormIISConfiguration, self).__init__(*args, **kwargs)
|
||||
request = self.initial.get('request')
|
||||
if not request:
|
||||
raise forms.ValidationError('Can\'t get a request information')
|
||||
link = request.__dict__['META']['HTTP_REFERER']
|
||||
environment_id = re.search('murano/(\w+)', link).group(0)[7:]
|
||||
domains = api.service_list_by_type(request, environment_id, AD_NAME)
|
||||
|
||||
self.fields['domain'].choices = \
|
||||
[("", "Not in domain")] + [(domain.name, domain.name)
|
||||
for domain in domains]
|
||||
CommonPropertiesExtension.__init__(self)
|
||||
|
||||
|
||||
class WebFarmExtension(forms.Form):
|
||||
instance_count = forms.IntegerField(
|
||||
label=_('Instance Count'),
|
||||
min_value=1,
|
||||
max_value=100,
|
||||
initial=2,
|
||||
help_text=_('Enter an integer value between 1 and 100'))
|
||||
|
||||
lb_port = forms.IntegerField(
|
||||
label=_('Load Balancer port'),
|
||||
min_value=1,
|
||||
max_value=65536,
|
||||
initial=80,
|
||||
help_text=_('Enter an integer value from 1 to 65536'))
|
||||
|
||||
|
||||
class WizardFormAspNetAppConfiguration(WizardFormIISConfiguration,
|
||||
CommonPropertiesExtension):
|
||||
git_repo_re = re.compile(r'(\w+://)(.+@)*([\w\d\.]+)(:[\d]+)?/*(.*)',
|
||||
re.IGNORECASE)
|
||||
validate_git = RegexValidator(
|
||||
git_repo_re, _('Enter correct git repository url'), 'invalid')
|
||||
|
||||
repository = forms.CharField(
|
||||
label=_('Git repository'),
|
||||
validators=[validate_git],
|
||||
error_messages={'invalid': validate_git.message},
|
||||
help_text='Enter a valid git repository URL')
|
||||
|
||||
|
||||
class WizardFormIISFarmConfiguration(WizardFormIISConfiguration,
|
||||
WebFarmExtension,
|
||||
CommonPropertiesExtension):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardFormIISFarmConfiguration, self).__init__(
|
||||
*args, **kwargs)
|
||||
CommonPropertiesExtension.__init__(self)
|
||||
|
||||
|
||||
class WizardFormAspNetFarmConfiguration(WizardFormAspNetAppConfiguration,
|
||||
WebFarmExtension,
|
||||
CommonPropertiesExtension):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardFormAspNetFarmConfiguration, self).__init__(
|
||||
*args, **kwargs)
|
||||
CommonPropertiesExtension.__init__(self)
|
||||
|
||||
|
||||
class WizardFormMSSQLConfiguration(WizardFormIISConfiguration,
|
||||
CommonPropertiesExtension):
|
||||
mixed_mode = forms.BooleanField(
|
||||
label=_('Mixed-mode Authentication '),
|
||||
initial=True,
|
||||
required=False)
|
||||
mixed_mode.widget.attrs['class'] = 'checkbox'
|
||||
password_field1 = PasswordField(
|
||||
_('SA password'),
|
||||
help_text=_('SQL server System Administrator account'))
|
||||
|
||||
password_field2 = PasswordField(
|
||||
_('Confirm password'),
|
||||
error_messages=CONFIRM_ERR_DICT,
|
||||
help_text=_('Retype your password'))
|
||||
|
||||
def clean(self):
|
||||
mixed_mode = self.cleaned_data.get('mixed_mode')
|
||||
if not mixed_mode:
|
||||
for i in xrange(1, 3, 1):
|
||||
self.fields['password_field' + str(i)].required = False
|
||||
if self.errors.get('password_field' + str(i)):
|
||||
del self.errors['password_field' + str(i)]
|
||||
|
||||
admin_password1 = self.cleaned_data.get('adm_password1')
|
||||
admin_password2 = self.cleaned_data.get('adm_password2')
|
||||
perform_password_check(admin_password1,
|
||||
admin_password2,
|
||||
'Administrator')
|
||||
sa_password1 = self.cleaned_data.get('password_field1')
|
||||
sa_password2 = self.cleaned_data.get('password_field2')
|
||||
perform_password_check(sa_password1,
|
||||
sa_password2,
|
||||
'Recovery')
|
||||
return self.cleaned_data
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardFormMSSQLConfiguration, self).__init__(*args, **kwargs)
|
||||
CommonPropertiesExtension.__init__(self)
|
||||
|
||||
class Media:
|
||||
js = ('muranodashboard/js/mixed-mode.js',)
|
||||
|
||||
|
||||
class WizardFormMSSQLClusterConfiguration(WizardFormMSSQLConfiguration):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardFormMSSQLClusterConfiguration, self).__init__(*args,
|
||||
**kwargs)
|
||||
self.fields.insert(3, 'external_ad', forms.BooleanField(
|
||||
label=_('Active Directory is configured '
|
||||
'by the System Administrator'),
|
||||
required=False))
|
||||
self.fields['external_ad'].widget.attrs['class'] = \
|
||||
'checkbox external-ad'
|
||||
|
||||
self.fields.insert(4, 'ad_user', forms.CharField(
|
||||
label=_('Active Directory User'),
|
||||
required=False,
|
||||
validators=[validate_name]
|
||||
))
|
||||
self.fields.insert(5, 'ad_password1', forms.CharField(
|
||||
label=_('Active Directory Password'),
|
||||
required=False,
|
||||
widget=forms.PasswordInput(render_value=True))
|
||||
)
|
||||
self.fields.insert(6, 'ad_password2', forms.CharField(
|
||||
label=_('Confirm Password'),
|
||||
required=False,
|
||||
widget=forms.PasswordInput(render_value=True))
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
super(WizardFormMSSQLClusterConfiguration, self).clean()
|
||||
if not self.cleaned_data.get('external_ad'):
|
||||
if not self.cleaned_data.get('domain'):
|
||||
raise forms.ValidationError(
|
||||
_('Domain for MS SQL Cluster is required. '
|
||||
'Configure Active Directory service first.'))
|
||||
else:
|
||||
password1 = self.cleaned_data.get('ad_password1')
|
||||
password2 = self.cleaned_data.get('ad_password2')
|
||||
if not (self.cleaned_data.get('ad_user') and password1):
|
||||
raise forms.ValidationError(
|
||||
_('Existent AD User and AD Password is required '
|
||||
'for service installation '))
|
||||
perform_password_check(password1, password2, 'Active Directory')
|
||||
return self.cleaned_data
|
||||
|
||||
class Media:
|
||||
js = ('muranodashboard/js/external-ad.js',)
|
||||
|
||||
|
||||
class WizardMSSQLConfigureAG(forms.Form):
|
||||
clusterName = forms.CharField(
|
||||
label='Cluster Name',
|
||||
help_text='Service name for new SQL Cluster service.'
|
||||
)
|
||||
|
||||
agGroupName = forms.CharField(
|
||||
label='Availability Group Name',
|
||||
help_text='Name of AG during SQL setup'
|
||||
)
|
||||
|
||||
agListenerName = forms.CharField(
|
||||
label='Availability Group Listener Name',
|
||||
validators=[validate_name],
|
||||
help_text='FQDN name of a new DNS entry for AG Listener endpoint')
|
||||
|
||||
sqlServiceUserName = forms.CharField(
|
||||
label='SQL User Name',
|
||||
validators=[validate_name])
|
||||
|
||||
sqlServicePassword1 = PasswordField(
|
||||
label='SQL User Password')
|
||||
|
||||
sqlServicePassword2 = PasswordField(
|
||||
label='Confirm Password')
|
||||
|
||||
instance_count = forms.IntegerField(
|
||||
label=_('Instance Count'),
|
||||
min_value=2,
|
||||
max_value=5,
|
||||
initial=2,
|
||||
help_text=_('Enter an integer value between 2 and 5'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardMSSQLConfigureAG, self).__init__(*args, **kwargs)
|
||||
request = self.initial.get('request')
|
||||
try:
|
||||
network_list = novaclient(request).networks.list()
|
||||
except:
|
||||
network_list = []
|
||||
ip_ranges = []
|
||||
else:
|
||||
ip_ranges = [network.cidr for network in network_list]
|
||||
ranges = ''
|
||||
for cidr in ip_ranges:
|
||||
ranges += cidr
|
||||
if cidr != ip_ranges[-1]:
|
||||
ranges += ', '
|
||||
if ip_ranges:
|
||||
help_text = _('Select IP from available range: ' + ranges)
|
||||
else:
|
||||
help_text = _('Specify valid fixed IP')
|
||||
|
||||
self.fields.insert(0, 'fixed_ip', forms.CharField(
|
||||
label=_('Cluster Static IP'),
|
||||
validators=[validate_cluster_ip(request, ip_ranges)],
|
||||
help_text=help_text,
|
||||
error_messages={'invalid': validate_ipv4_address.message}))
|
||||
|
||||
self.fields.insert(4, 'agListenerIP', forms.CharField(
|
||||
label=_('Availability Group Listener IP'),
|
||||
validators=[validate_cluster_ip(request, ip_ranges)],
|
||||
help_text=help_text,
|
||||
error_messages={'invalid': validate_ipv4_address.message}))
|
||||
|
||||
self.fields['sqlServicePassword1'].widget.attrs[
|
||||
'class'] = 'password'
|
||||
self.fields['sqlServicePassword2'].widget.attrs[
|
||||
'class'] = 'confirm_password'
|
||||
|
||||
def clean(self):
|
||||
fixed_ip = self.cleaned_data.get('fixed_ip')
|
||||
agListenerIP = self.cleaned_data.get('agListenerIP')
|
||||
if fixed_ip == agListenerIP:
|
||||
raise forms.ValidationError(_('Listener IP and Cluster '
|
||||
'Static IP should be different'))
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class WizardMSSQLDatagrid(forms.Form):
|
||||
nodes = DataGridField()
|
||||
|
||||
databases = DatabaseListField(
|
||||
label=_('Database list'),
|
||||
help_text=_(u'Enter comma separated list of databases '
|
||||
u'that will be created'))
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardMSSQLDatagrid, self).__init__(*args, **kwargs)
|
||||
self.fields['nodes'].update_request(self.initial.get('request'))
|
||||
nodes = []
|
||||
instance_count = self.initial.get('instance_count')
|
||||
if instance_count:
|
||||
for index in xrange(instance_count):
|
||||
initial_data_grid = {}
|
||||
initial_data_grid['name'] = 'node' + str(index + 1)
|
||||
initial_data_grid['is_sync'] = True
|
||||
if index == 0:
|
||||
initial_data_grid['is_primary'] = True
|
||||
else:
|
||||
initial_data_grid['is_primary'] = False
|
||||
nodes.append(initial_data_grid)
|
||||
|
||||
self.fields['nodes'].initial = json.dumps(nodes)
|
||||
if 'mssql_datagrid-nodes_1' in self.data:
|
||||
self.data = self.data.copy()
|
||||
self.data['mssql_datagrid-nodes_1'] = json.dumps(nodes)
|
||||
|
||||
def clean(self):
|
||||
databases = self.cleaned_data.get('databases')
|
||||
if databases:
|
||||
strip_list = []
|
||||
|
||||
for db in databases:
|
||||
strip_list.append(db.strip())
|
||||
self.cleaned_data['databases'] = strip_list
|
||||
return self.cleaned_data
|
||||
|
||||
|
||||
class WizardInstanceConfiguration(forms.Form):
|
||||
flavor = forms.ChoiceField(label=_('Instance flavor'))
|
||||
|
||||
image = forms.ChoiceField(label=_('Instance image'))
|
||||
|
||||
availability_zone = forms.ChoiceField(label=_('Availability zone'),
|
||||
required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(WizardInstanceConfiguration, self).__init__(
|
||||
*args, **kwargs)
|
||||
request = self.initial.get('request')
|
||||
if not request:
|
||||
raise forms.ValidationError(
|
||||
'Can\'t get a request information')
|
||||
flavors = novaclient(request).flavors.list()
|
||||
flavor_choices = [(flavor.name, flavor.name) for flavor in flavors]
|
||||
|
||||
self.fields['flavor'].choices = flavor_choices
|
||||
for flavor in flavor_choices:
|
||||
if 'medium' in flavor[1]:
|
||||
self.fields['flavor'].initial = flavor[0]
|
||||
break
|
||||
try:
|
||||
images, _more = glance.image_list_detailed(request)
|
||||
except:
|
||||
images = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve public images."))
|
||||
|
||||
image_mapping = {}
|
||||
image_choices = []
|
||||
for image in images:
|
||||
murano_property = image.properties.get('murano_image_info')
|
||||
if murano_property:
|
||||
#convert to dict because
|
||||
# only string can be stored in image metadata property
|
||||
try:
|
||||
murano_json = ast.literal_eval(murano_property)
|
||||
except ValueError:
|
||||
messages.error(request,
|
||||
_("Invalid value in image metadata"))
|
||||
else:
|
||||
title = murano_json.get('title')
|
||||
image_id = murano_json.get('id')
|
||||
if title and image_id:
|
||||
image_mapping[smart_text(title)] = smart_text(image_id)
|
||||
|
||||
for name in sorted(image_mapping.keys()):
|
||||
image_choices.append((image_mapping[name], name))
|
||||
if image_choices:
|
||||
image_choices.insert(0, ("", _("Select Image")))
|
||||
else:
|
||||
image_choices.insert(0, ("", _("No images available")))
|
||||
|
||||
self.fields['image'].choices = image_choices
|
||||
|
||||
try:
|
||||
availability_zones = novaclient(request).availability_zones.\
|
||||
list(detailed=False)
|
||||
except:
|
||||
availability_zones = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve availability zones."))
|
||||
|
||||
az_choices = [(az.zoneName, az.zoneName)
|
||||
for az in availability_zones if az.zoneState]
|
||||
if az_choices:
|
||||
az_choices.insert(0, ("", _("Select Availability Zone")))
|
||||
else:
|
||||
az_choices.insert(0, ("", _("No availability zones available")))
|
||||
|
||||
self.fields['availability_zone'].choices = az_choices
|
||||
|
||||
|
||||
FORMS = [('service_choice', WizardFormServiceType),
|
||||
(AD_NAME, WizardFormADConfiguration),
|
||||
(IIS_NAME, WizardFormIISConfiguration),
|
||||
(ASP_NAME, WizardFormAspNetAppConfiguration),
|
||||
(IIS_FARM_NAME, WizardFormIISFarmConfiguration),
|
||||
(ASP_FARM_NAME, WizardFormAspNetFarmConfiguration),
|
||||
(MSSQL_NAME, WizardFormMSSQLConfiguration),
|
||||
(MSSQL_CLUSTER_NAME, WizardFormMSSQLClusterConfiguration),
|
||||
('mssql_ag_configuration', WizardMSSQLConfigureAG),
|
||||
('mssql_datagrid', WizardMSSQLDatagrid),
|
||||
('instance_configuration', WizardInstanceConfiguration)]
|
||||
FORMS = [('service_choice', WizardFormServiceType)]
|
||||
FORMS.extend(iterate_over_service_forms())
|
||||
|
|
|
@ -0,0 +1,116 @@
|
|||
name: Active Directory
|
||||
type: activeDirectory
|
||||
|
||||
description: >-
|
||||
<strong> The Active Directory Service </strong>
|
||||
includes one primary and optionally a few secondary
|
||||
Domain Controllers, with DNS
|
||||
|
||||
unitTemplates:
|
||||
- isMaster: true
|
||||
recoveryPassword: $recoveryPassword
|
||||
location: west-dc
|
||||
- isMaster: false
|
||||
recoveryPassword: $recoveryPassword
|
||||
|
||||
forms:
|
||||
- - name: configuration
|
||||
type: string
|
||||
hidden: true
|
||||
initial: standalone
|
||||
- name: name
|
||||
type: string
|
||||
label: Domain Name
|
||||
description: >-
|
||||
Enter a desired name for a new domain. This name should fit
|
||||
in standard Windows domain name requirements: it should contain
|
||||
only A-Z, a-z, 0-9, and (-) and should not end with a dash.
|
||||
DNS server will be automatically set up on each of the Domain
|
||||
Controller instances.
|
||||
attributeNames: [name, domain]
|
||||
minLength: 2
|
||||
maxLength: 64
|
||||
regexpValidator: '^([a-zA-Z\d][a-zA-Z\d-]*[a-zA-Z\d]\.){1,}[a-zA-Z\d][a-zA-Z\d-]*[a-zA-Z\d]$'
|
||||
errorMessages:
|
||||
invalid: >-
|
||||
Only letters, numbers and dashes in the middle are
|
||||
allowed. Period characters are allowed only when they are
|
||||
used to delimit the components of domain style
|
||||
names. Single-level domain is not appropriate.
|
||||
helpText: >-
|
||||
Just letters, numbers and dashes are allowed.
|
||||
A dot can be used to create subdomains
|
||||
- name: dcInstances
|
||||
type: instance
|
||||
label: Instance Count
|
||||
description: >-
|
||||
You can create several Active Directory instances by setting
|
||||
instance number larger than one. One primary Domain Controller
|
||||
and a few secondary DCs will be created.
|
||||
attributeNames: units
|
||||
minValue: 1
|
||||
maxValue: 100
|
||||
initial: 1
|
||||
helpText: Enter an integer value between 1 and 100
|
||||
- name: adminAccountName
|
||||
type: string
|
||||
label: Account Name
|
||||
initial: Administrator
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: 'Just letters, numbers, underscores and hyphens are allowed.'
|
||||
- name: adminPassword
|
||||
type: password
|
||||
label: Administrator password
|
||||
descriptionTitle: Passwords
|
||||
description: >-
|
||||
"Windows requires strong password for service administration.
|
||||
Your password should have at least one letter in each
|
||||
register, a number and a special character. Password length should be
|
||||
a minimum of 8 characters.
|
||||
|
||||
Once you forget your password you won't be able to
|
||||
operate the service until recovery password would be entered. So it's
|
||||
better for Recovery and Administrator password to be different."
|
||||
- name: recoveryPassword
|
||||
type: password
|
||||
label: Recovery password
|
||||
attributeNames: false
|
||||
- name: unitNamingPattern
|
||||
type: string
|
||||
label: Hostname template
|
||||
attributeNames: false
|
||||
description: >-
|
||||
"For your convenience all instance hostnames can be named
|
||||
in the same way. Enter a name and use # character for incrementation.
|
||||
For example, host# turns into host1, host2, etc. Please follow Windows
|
||||
hostname restrictions."
|
||||
required: false
|
||||
regexpValidator: '^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$'
|
||||
helpText: Optional field for a machine hostname template
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
descriptionTitle: Instance Configuration
|
||||
description: Specify some instance parameters on which service would be created.
|
||||
- name: flavor
|
||||
type: flavor
|
||||
label: Instance flavor
|
||||
description: >-
|
||||
Select registered in Openstack flavor. Consider that service performance
|
||||
depends on this parameter.
|
||||
required: false
|
||||
- name: osImage
|
||||
type: image
|
||||
label: Instance image
|
||||
description: >-
|
||||
Select valid image for a service. Image should already be prepared and
|
||||
registered in glance.
|
||||
required: false
|
||||
- name: availabilityZone
|
||||
type: azone
|
||||
label: Availability zone
|
||||
description: Select availability zone where service would be installed.
|
||||
required: false
|
|
@ -0,0 +1,93 @@
|
|||
name: Internet Information Services
|
||||
type: webServer
|
||||
|
||||
description: >-
|
||||
<strong> The Internet Information Service </strong>
|
||||
sets up an IIS server and joins it into an existing domain
|
||||
|
||||
unitTemplates:
|
||||
- {}
|
||||
|
||||
forms:
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
description: Standalone IIS Server
|
||||
- name: name
|
||||
type: string
|
||||
label: Service Name
|
||||
description: >-
|
||||
Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and
|
||||
underline are allowed.
|
||||
minLength: 2
|
||||
maxLength: 64
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
helpText: Just letters, numbers, underscores and hyphens are allowed.
|
||||
- name: dcInstances
|
||||
type: instance
|
||||
hidden: true
|
||||
attributeNames: units
|
||||
initial: 1
|
||||
- name: adminPassword
|
||||
type: password
|
||||
label: Administrator password
|
||||
descriptionTitle: Passwords
|
||||
description: >-
|
||||
"Windows requires strong password for service administration.
|
||||
Your password should have at least one letter in each
|
||||
register, a number and a special character. Password length should be
|
||||
a minimum of 8 characters.
|
||||
|
||||
Once you forget your password you won't be able to
|
||||
operate the service until recovery password would be entered. So it's
|
||||
better for Recovery and Administrator password to be different."
|
||||
- name: domain
|
||||
type: domain
|
||||
label: Domain
|
||||
required: false
|
||||
description: >-
|
||||
Service can be joined to the Active Directory domain. If you want to
|
||||
create an AD domain create the AD Service first.
|
||||
helpText: Optional field for a domain to which service can be joined
|
||||
- name: unitNamingPattern
|
||||
type: string
|
||||
label: Hostname template
|
||||
attributeNames: false
|
||||
description: >-
|
||||
"For your convenience all instance hostnames can be named
|
||||
in the same way. Enter a name and use # character for incrementation.
|
||||
For example, host# turns into host1, host2, etc. Please follow Windows
|
||||
hostname restrictions."
|
||||
required: false
|
||||
regexpValidator: '^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$'
|
||||
helpText: Optional field for a machine hostname template
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
descriptionTitle: Instance Configuration
|
||||
description: Specify some instance parameters on which service would be created.
|
||||
- name: flavor
|
||||
type: flavor
|
||||
label: Instance flavor
|
||||
description: >-
|
||||
Select registered in Openstack flavor. Consider that service performance
|
||||
depends on this parameter.
|
||||
required: false
|
||||
- name: osImage
|
||||
type: image
|
||||
label: Instance image
|
||||
description: >-
|
||||
Select valid image for a service. Image should already be prepared and
|
||||
registered in glance.
|
||||
required: false
|
||||
- name: availabilityZone
|
||||
type: azone
|
||||
label: Availability zone
|
||||
description: Select availability zone where service would be installed.
|
||||
required: false
|
|
@ -0,0 +1,102 @@
|
|||
name: ASP.NET Application
|
||||
type: aspNetApp
|
||||
|
||||
description: >-
|
||||
<strong> The ASP.NET Application Service </strong> installs
|
||||
custom application onto one IIS Web Server
|
||||
|
||||
unitTemplates:
|
||||
- {}
|
||||
|
||||
forms:
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
description: ASP.NET application will be installed onto one IISWeb Server
|
||||
- name: name
|
||||
type: string
|
||||
label: Service Name
|
||||
description: >-
|
||||
Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and
|
||||
underline are allowed.
|
||||
minLength: 2
|
||||
maxLength: 64
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
helpText: Just letters, numbers, underscores and hyphens are allowed.
|
||||
- name: dcInstances
|
||||
type: instance
|
||||
hidden: true
|
||||
attributeNames: units
|
||||
initial: 1
|
||||
- name: adminPassword
|
||||
type: password
|
||||
label: Administrator password
|
||||
descriptionTitle: Passwords
|
||||
description: >-
|
||||
"Windows requires strong password for service administration.
|
||||
Your password should have at least one letter in each
|
||||
register, a number and a special character. Password length should be
|
||||
a minimum of 8 characters.
|
||||
|
||||
Once you forget your password you won't be able to
|
||||
operate the service until recovery password would be entered. So it's
|
||||
better for Recovery and Administrator password to be different."
|
||||
- name: domain
|
||||
type: domain
|
||||
label: Domain
|
||||
required: false
|
||||
description: >-
|
||||
Service can be joined to the Active Directory domain. If you want to
|
||||
create an AD domain create the AD Service first.
|
||||
helpText: Optional field for a domain to which service can be joined
|
||||
- name: repository
|
||||
type: string
|
||||
label: Git repository
|
||||
description: >-
|
||||
URL of a git repository with the application you want to deploy.
|
||||
regexpValidator: '/(\w+://)(.+@)*([\w\d\.]+)(:[\d]+)?/*(.*)/i'
|
||||
errorMessages:
|
||||
invalid: Enter correct git repository url
|
||||
helpText: Enter a valid git repository URL
|
||||
- name: unitNamingPattern
|
||||
type: string
|
||||
label: Hostname template
|
||||
attributeNames: false
|
||||
description: >-
|
||||
"For your convenience all instance hostnames can be named
|
||||
in the same way. Enter a name and use # character for incrementation.
|
||||
For example, host# turns into host1, host2, etc. Please follow Windows
|
||||
hostname restrictions."
|
||||
required: false
|
||||
regexpValidator: '^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$'
|
||||
helpText: Optional field for a machine hostname template
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
descriptionTitle: Instance Configuration
|
||||
description: Specify some instance parameters on which service would be created.
|
||||
- name: flavor
|
||||
type: flavor
|
||||
label: Instance flavor
|
||||
description: >-
|
||||
Select registered in Openstack flavor. Consider that service performance
|
||||
depends on this parameter.
|
||||
required: false
|
||||
- name: osImage
|
||||
type: image
|
||||
label: Instance image
|
||||
description: >-
|
||||
Select valid image for a service. Image should already be prepared and
|
||||
registered in glance.
|
||||
required: false
|
||||
- name: availabilityZone
|
||||
type: azone
|
||||
label: Availability zone
|
||||
description: Select availability zone where service would be installed.
|
||||
required: false
|
|
@ -0,0 +1,110 @@
|
|||
name: Internet Information Services Web Farm
|
||||
type: webServerFarm
|
||||
|
||||
description: >-
|
||||
<strong> The IIS Farm Service </strong> sets up a load-balanced set of IIS servers
|
||||
|
||||
unitTemplates:
|
||||
- {}
|
||||
|
||||
forms:
|
||||
- - name: title
|
||||
type: string
|
||||
hidden: true
|
||||
required: false
|
||||
attributeNames: false
|
||||
description: A load-balanced array of IIS servers
|
||||
- name: name
|
||||
type: string
|
||||
label: Service Name
|
||||
description: >-
|
||||
Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and
|
||||
underline are allowed.
|
||||
minLength: 2
|
||||
maxLength: 64
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
helpText: Just letters, numbers, underscores and hyphens are allowed.
|
||||
- name: username
|
||||
type: string
|
||||
hidden: true
|
||||
initial: Administrator
|
||||
attributeNames: credentials.username
|
||||
- name: adminPassword
|
||||
type: password
|
||||
attributeNames: [adminPassword, credentials.password]
|
||||
label: Administrator password
|
||||
descriptionTitle: Passwords
|
||||
description: >-
|
||||
"Windows requires strong password for service administration.
|
||||
Your password should have at least one letter in each
|
||||
register, a number and a special character. Password length should be
|
||||
a minimum of 8 characters.
|
||||
|
||||
Once you forget your password you won't be able to
|
||||
operate the service until recovery password would be entered. So it's
|
||||
better for Recovery and Administrator password to be different."
|
||||
- name: domain
|
||||
type: domain
|
||||
label: Domain
|
||||
required: false
|
||||
description: >-
|
||||
Service can be joined to the Active Directory domain. If you want to
|
||||
create an AD domain create the AD Service first.
|
||||
helpText: Optional field for a domain to which service can be joined
|
||||
- name: dcInstances
|
||||
type: instance
|
||||
minValue: 1
|
||||
maxValue: 100
|
||||
attributeNames: [units, instanceCount]
|
||||
initial: 1
|
||||
label: Instance Count
|
||||
description: Several instances with IIS Service can be created at one time.
|
||||
helpText: Enter an integer value between 1 and 100
|
||||
- name: loadBalancerPort
|
||||
type: integer
|
||||
label: Load Balancer port
|
||||
minValue: 1
|
||||
maxValue: 65536
|
||||
initial: 80
|
||||
description: Specify port number where Load Balancer will be running
|
||||
helpText: Enter an integer value from 1 to 65536
|
||||
- name: unitNamingPattern
|
||||
type: string
|
||||
label: Hostname template
|
||||
attributeNames: false
|
||||
description: >-
|
||||
"For your convenience all instance hostnames can be named
|
||||
in the same way. Enter a name and use # character for incrementation.
|
||||
For example, host# turns into host1, host2, etc. Please follow Windows
|
||||
hostname restrictions."
|
||||
required: false
|
||||
regexpValidator: '^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$'
|
||||
helpText: Optional field for a machine hostname template
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
descriptionTitle: Instance Configuration
|
||||
description: Specify some instance parameters on which service would be created.
|
||||
- name: flavor
|
||||
type: flavor
|
||||
label: Instance flavor
|
||||
description: >-
|
||||
Select registered in Openstack flavor. Consider that service performance
|
||||
depends on this parameter.
|
||||
required: false
|
||||
- name: osImage
|
||||
type: image
|
||||
label: Instance image
|
||||
description: >-
|
||||
Select valid image for a service. Image should already be prepared and
|
||||
registered in glance.
|
||||
required: false
|
||||
- name: availabilityZone
|
||||
type: azone
|
||||
label: Availability zone
|
||||
description: Select availability zone where service would be installed.
|
||||
required: false
|
|
@ -0,0 +1,118 @@
|
|||
name: ASP.NET Application Web Farm
|
||||
type: aspNetAppFarm
|
||||
|
||||
description: >-
|
||||
<strong> The ASP.NET Farm Service </strong> installs a custom application
|
||||
on a load-balanced array of IIS servers
|
||||
|
||||
unitTemplates:
|
||||
- {}
|
||||
|
||||
forms:
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
description: >-
|
||||
The ASP.NET application will be installed on a number of IIS Web
|
||||
Servers, and load balancing will be configured.
|
||||
- name: name
|
||||
type: string
|
||||
label: Service Name
|
||||
description: >-
|
||||
Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and
|
||||
underline are allowed.
|
||||
minLength: 2
|
||||
maxLength: 64
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
helpText: Just letters, numbers, underscores and hyphens are allowed.
|
||||
- name: dcInstances
|
||||
type: instance
|
||||
hidden: true
|
||||
attributeNames: units
|
||||
initial: 1
|
||||
- name: username
|
||||
type: string
|
||||
hidden: true
|
||||
initial: Administrator
|
||||
attributeNames: credentials.username
|
||||
- name: adminPassword
|
||||
type: password
|
||||
attributeNames: [adminPassword, credentials.password]
|
||||
label: Administrator password
|
||||
descriptionTitle: Passwords
|
||||
description: >-
|
||||
"Windows requires strong password for service administration.
|
||||
Your password should have at least one letter in each
|
||||
register, a number and a special character. Password length should be
|
||||
a minimum of 8 characters.
|
||||
|
||||
Once you forget your password you won't be able to
|
||||
operate the service until recovery password would be entered. So it's
|
||||
better for Recovery and Administrator password to be different."
|
||||
- name: domain
|
||||
type: domain
|
||||
label: Domain
|
||||
required: false
|
||||
description: >-
|
||||
Service can be joined to the Active Directory domain. If you want to
|
||||
create an AD domain create the AD Service first.
|
||||
helpText: Optional field for a domain to which service can be joined
|
||||
- name: repository
|
||||
type: string
|
||||
label: Git repository
|
||||
description: >-
|
||||
URL of a git repository with the application you want to deploy.
|
||||
regexpValidator: '/(\w+://)(.+@)*([\w\d\.]+)(:[\d]+)?/*(.*)/i'
|
||||
errorMessages:
|
||||
invalid: Enter correct git repository url
|
||||
helpText: Enter a valid git repository URL
|
||||
- name: loadBalancerPort
|
||||
type: integer
|
||||
label: Load Balancer port
|
||||
minValue: 1
|
||||
maxValue: 65536
|
||||
initial: 80
|
||||
description: Specify port number where Load Balancer will be running
|
||||
helpText: Enter an integer value from 1 to 65536
|
||||
- name: unitNamingPattern
|
||||
type: string
|
||||
label: Hostname template
|
||||
attributeNames: false
|
||||
description: >-
|
||||
"For your convenience all instance hostnames can be named
|
||||
in the same way. Enter a name and use # character for incrementation.
|
||||
For example, host# turns into host1, host2, etc. Please follow Windows
|
||||
hostname restrictions."
|
||||
required: false
|
||||
regexpValidator: '^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$'
|
||||
helpText: Optional field for a machine hostname template
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
descriptionTitle: Instance Configuration
|
||||
description: Specify some instance parameters on which service would be created.
|
||||
- name: flavor
|
||||
type: flavor
|
||||
label: Instance flavor
|
||||
description: >-
|
||||
Select registered in Openstack flavor. Consider that service performance
|
||||
depends on this parameter.
|
||||
required: false
|
||||
- name: osImage
|
||||
type: image
|
||||
label: Instance image
|
||||
description: >-
|
||||
Select valid image for a service. Image should already be prepared and
|
||||
registered in glance.
|
||||
required: false
|
||||
- name: availabilityZone
|
||||
type: azone
|
||||
label: Availability zone
|
||||
description: Select availability zone where service would be installed.
|
||||
required: false
|
|
@ -0,0 +1,112 @@
|
|||
name: MS SQL Server
|
||||
type: msSqlServer
|
||||
|
||||
description: >-
|
||||
<strong> The MS SQL Service </strong> installs an instance of
|
||||
Microsoft SQL Server
|
||||
|
||||
unitTemplates:
|
||||
- {}
|
||||
|
||||
forms:
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
description: MS SQL Server
|
||||
# temporaryHack
|
||||
widgetMedia:
|
||||
js: [muranodashboard/js/mixed-mode.js]
|
||||
- name: name
|
||||
type: string
|
||||
label: Service Name
|
||||
description: >-
|
||||
Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and
|
||||
underline are allowed.
|
||||
minLength: 2
|
||||
maxLength: 64
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
helpText: Just letters, numbers, underscores and hyphens are allowed.
|
||||
- name: dcInstances
|
||||
type: instance
|
||||
hidden: true
|
||||
attributeNames: units
|
||||
initial: 1
|
||||
- name: adminPassword
|
||||
type: password
|
||||
label: Administrator password
|
||||
descriptionTitle: Passwords
|
||||
description: >-
|
||||
"Windows requires strong password for service administration.
|
||||
Your password should have at least one letter in each
|
||||
register, a number and a special character. Password length should be
|
||||
a minimum of 8 characters.
|
||||
|
||||
Once you forget your password you won't be able to
|
||||
operate the service until recovery password would be entered. So it's
|
||||
better for Recovery and Administrator password to be different."
|
||||
- name: domain
|
||||
type: domain
|
||||
label: Domain
|
||||
required: false
|
||||
description: >-
|
||||
Service can be joined to the Active Directory domain. If you want to
|
||||
create an AD domain create the AD Service first.
|
||||
helpText: Optional field for a domain to which service can be joined
|
||||
- name: mixedModeAuth
|
||||
type: boolean
|
||||
label: Mixed-mode Authentication
|
||||
initial: true
|
||||
required: false
|
||||
description: >-
|
||||
Mixed authentication mode allows the use of Windows
|
||||
credentials but supplements them with local SQL Server user
|
||||
accounts that the administrator may create and maintain within
|
||||
SQL Server. If this mode is on SA password is required
|
||||
- name: saPassword
|
||||
type: password
|
||||
label: SA Password
|
||||
description: Set system administrator password for the MS SQL Server.
|
||||
helpText: SQL server System Administrator account
|
||||
required: $mixedModeAuth
|
||||
- name: unitNamingPattern
|
||||
type: string
|
||||
label: Hostname template
|
||||
attributeNames: false
|
||||
description: >-
|
||||
"For your convenience all instance hostnames can be named
|
||||
in the same way. Enter a name and use # character for incrementation.
|
||||
For example, host# turns into host1, host2, etc. Please follow Windows
|
||||
hostname restrictions."
|
||||
required: false
|
||||
regexpValidator: '^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$'
|
||||
helpText: Optional field for a machine hostname template
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
descriptionTitle: Instance Configuration
|
||||
description: Specify some instance parameters on which service would be created.
|
||||
- name: flavor
|
||||
type: flavor
|
||||
label: Instance flavor
|
||||
description: >-
|
||||
Select registered in Openstack flavor. Consider that service performance
|
||||
depends on this parameter.
|
||||
required: false
|
||||
- name: osImage
|
||||
type: image
|
||||
label: Instance image
|
||||
description: >-
|
||||
Select valid image for a service. Image should already be prepared and
|
||||
registered in glance.
|
||||
required: false
|
||||
- name: availabilityZone
|
||||
type: azone
|
||||
label: Availability zone
|
||||
description: Select availability zone where service would be installed.
|
||||
required: false
|
|
@ -0,0 +1,194 @@
|
|||
name: MS SQL Server Cluster
|
||||
type: msSqlClusterServer
|
||||
|
||||
description: >-
|
||||
<strong> The MS SQL Failover Cluster </strong> installs
|
||||
Microsoft SQL Failover Cluster Server
|
||||
|
||||
unitTemplates:
|
||||
- {}
|
||||
|
||||
forms:
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
description: MS SQL Failover Cluster
|
||||
# temporaryHack
|
||||
widgetMedia:
|
||||
js: [muranodashboard/js/mixed-mode.js, muranodashboard/js/external-ad.js]
|
||||
- name: name
|
||||
type: string
|
||||
label: Service Name
|
||||
description: >-
|
||||
Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and
|
||||
underline are allowed.
|
||||
minLength: 2
|
||||
maxLength: 64
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
helpText: Just letters, numbers, underscores and hyphens are allowed.
|
||||
- name: adminPassword
|
||||
type: password
|
||||
label: Administrator password
|
||||
descriptionTitle: Passwords
|
||||
description: >-
|
||||
"Windows requires strong password for service administration.
|
||||
Your password should have at least one letter in each
|
||||
register, a number and a special character. Password length should be
|
||||
a minimum of 8 characters.
|
||||
|
||||
Once you forget your password you won't be able to
|
||||
operate the service until recovery password would be entered. So it's
|
||||
better for Recovery and Administrator password to be different."
|
||||
- name: externalAD
|
||||
type: boolean
|
||||
label: Active Directory is configured by the System Administrator
|
||||
widgetAttrs: # temporary hack
|
||||
class: external-ad
|
||||
required: false
|
||||
- name: domainAdminUserName
|
||||
type: string
|
||||
label: Active Directory User
|
||||
required: $externalAD
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: 'Just letters, numbers, underscores and hyphens are allowed.'
|
||||
- name: domainAdminPassword
|
||||
type: password
|
||||
label: Active Directory Password
|
||||
required: $externalAD
|
||||
- name: domain
|
||||
type: domain
|
||||
label: Domain
|
||||
required: false
|
||||
description: >-
|
||||
Service can be joined to the Active Directory domain. If you want to
|
||||
create an AD domain create the AD Service first.
|
||||
helpText: Optional field for a domain to which service can be joined
|
||||
- name: mixedModeAuth
|
||||
type: boolean
|
||||
label: Mixed-mode Authentication
|
||||
initial: true
|
||||
required: false
|
||||
description: >-
|
||||
Mixed authentication mode allows the use of Windows
|
||||
credentials but supplements them with local SQL Server user
|
||||
accounts that the administrator may create and maintain within
|
||||
SQL Server. If this mode is on SA password is required
|
||||
- name: saPassword
|
||||
type: password
|
||||
label: SA Password
|
||||
description: Set system administrator password for the MS SQL Server.
|
||||
helpText: SQL server System Administrator account
|
||||
required: $mixedModeAuth
|
||||
- name: unitNamingPattern
|
||||
type: string
|
||||
label: Hostname template
|
||||
attributeNames: false
|
||||
description: >-
|
||||
"For your convenience all instance hostnames can be named
|
||||
in the same way. Enter a name and use # character for incrementation.
|
||||
For example, host# turns into host1, host2, etc. Please follow Windows
|
||||
hostname restrictions."
|
||||
required: false
|
||||
regexpValidator: '^(([a-zA-Z0-9#][a-zA-Z0-9-#]*[a-zA-Z0-9#])\.)*([A-Za-z0-9#]|[A-Za-z0-9#][A-Za-z0-9-#]*[A-Za-z0-9#])$'
|
||||
helpText: Optional field for a machine hostname template
|
||||
- - name: clusterIP
|
||||
type: clusterip
|
||||
label: Cluster Static IP
|
||||
description: Specify a valid IPv4 fixed IP.
|
||||
- name: clusterName
|
||||
type: string
|
||||
label: Cluster Name
|
||||
helpText: Service name for new SQL Cluster service
|
||||
description: >-
|
||||
Specify a name of a cluster. Just A-Z, a-z, 0-9, dash and underline are allowed.
|
||||
- name: agGroupName
|
||||
type: string
|
||||
label: Availability Group Name
|
||||
helpText: Name of AG during SQL setup
|
||||
description: >-
|
||||
Specify a name of an AG. Just A-Z, a-z, 0-9, dash and underline are allowed.
|
||||
- name: agListenerName
|
||||
type: string
|
||||
label: Availability Group Listener Name
|
||||
helpText: FQDN name of a new DNS entry for AG Listener endpoint
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
description: >-
|
||||
Specify a name of an AG Listener . Just A-Z, a-z, 0-9, dash and underline are allowed.
|
||||
- name: agListenerIP
|
||||
type: clusterip
|
||||
label: Availability Group Listener IP
|
||||
description: Specify a valid IPv4 fixed IP.
|
||||
- name: sqlServiceUserName
|
||||
type: string
|
||||
label: SQL User Name
|
||||
regexpValidator: '^[-\w]+$'
|
||||
errorMessages:
|
||||
invalid: Just letters, numbers, underscores and hyphens are allowed.
|
||||
description: User name that will be created to manage cluster instances.
|
||||
- name: sqlServicePassword
|
||||
type: password
|
||||
label: SQL User Password
|
||||
description: User password that will be created to manage cluster instances.
|
||||
- name: dcInstances
|
||||
type: instance
|
||||
label: Instance Count
|
||||
minValue: 2
|
||||
maxValue: 5
|
||||
initial: 2
|
||||
attributeNames: units
|
||||
helpText: Enter an integer value between 2 and 5
|
||||
description: Microsoft SQL Failover Cluster includes up to 5 instances.
|
||||
- - name: nodes
|
||||
type: datagrid
|
||||
label: Nodes
|
||||
description: >-
|
||||
Configure cluster instances. Cluster node quantity can be set
|
||||
with 'Add' and 'Remove' buttons. Configure Sync mode by
|
||||
enabling corresponding checkbox. All other nodes will be in
|
||||
Async mode. Just 2 nodes are allowed to be Sync. Also one
|
||||
Master node need to be selected. SQL Failover cluster has
|
||||
limit of 5 instances.
|
||||
- name: databases
|
||||
type: databaselist
|
||||
label: Database list
|
||||
description: >-
|
||||
Specify names for new databases which will be created as part
|
||||
of service installation. Here should come comma-separated list
|
||||
of database names, where each name has the following syntax:
|
||||
first symbol should be latin letter or underscore; subsequent
|
||||
symbols can be latin letter, numeric, underscore, at sign,
|
||||
number sign or dollar sign.
|
||||
helpText: Enter comma separated list of databases that will be created
|
||||
- - name: title
|
||||
type: string
|
||||
required: false
|
||||
hidden: true
|
||||
attributeNames: false
|
||||
descriptionTitle: Instance Configuration
|
||||
description: Specify some instance parameters on which service would be created.
|
||||
- name: flavor
|
||||
type: flavor
|
||||
label: Instance flavor
|
||||
description: >-
|
||||
Select registered in Openstack flavor. Consider that service performance
|
||||
depends on this parameter.
|
||||
required: false
|
||||
- name: osImage
|
||||
type: image
|
||||
label: Instance image
|
||||
description: >-
|
||||
Select valid image for a service. Image should already be prepared and
|
||||
registered in glance.
|
||||
required: false
|
||||
- name: availabilityZone
|
||||
type: azone
|
||||
label: Availability zone
|
||||
description: Select availability zone where service would be installed.
|
||||
required: false
|
|
@ -0,0 +1,542 @@
|
|||
import re
|
||||
import ast
|
||||
import json
|
||||
from django import forms
|
||||
from django.core.validators import RegexValidator, validate_ipv4_address
|
||||
from netaddr import all_matching_cidrs
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.encoding import smart_text
|
||||
from muranodashboard.panel import api
|
||||
from horizon import exceptions, messages
|
||||
from openstack_dashboard.api import glance
|
||||
from openstack_dashboard.api.nova import novaclient
|
||||
from muranodashboard.datagrids import DataGridCompound
|
||||
import copy
|
||||
from django.template.defaultfilters import pluralize
|
||||
|
||||
|
||||
CONFIRM_ERR_DICT = {'required': _('Please confirm your password')}
|
||||
|
||||
|
||||
class PasswordField(forms.CharField):
|
||||
special_characters = '!@#$%^&*()_+|\/.,~?><:{}'
|
||||
password_re = re.compile('^.*(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[%s]).*$'
|
||||
% special_characters)
|
||||
has_clone = False
|
||||
validate_password = RegexValidator(
|
||||
password_re, _('The password must contain at least one letter, one \
|
||||
number and one special character'), 'invalid')
|
||||
|
||||
class PasswordInput(forms.PasswordInput):
|
||||
class Media:
|
||||
js = ('muranodashboard/js/passwordfield.js',)
|
||||
|
||||
def __init__(self, label, *args, **kwargs):
|
||||
help_text = kwargs.get('help_text')
|
||||
if not help_text:
|
||||
help_text = _('Enter a complex password with at least one letter, \
|
||||
one number and one special character')
|
||||
|
||||
error_messages = {
|
||||
'invalid': self.validate_password.message}
|
||||
err_msg = kwargs.get('error_messages')
|
||||
if err_msg:
|
||||
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))
|
||||
|
||||
def is_original(self):
|
||||
return hasattr(self, 'original') and self.original
|
||||
|
||||
def clone_field(self):
|
||||
self.has_clone = True
|
||||
field = copy.deepcopy(self)
|
||||
self.original = True
|
||||
field.label = _('Confirm password')
|
||||
field.error_messages = {
|
||||
'required': _('Please confirm your password')
|
||||
}
|
||||
field.help_text = _('Retype your password')
|
||||
return field
|
||||
|
||||
|
||||
class InstanceCountField(forms.IntegerField):
|
||||
def clean(self, value):
|
||||
self.value = super(InstanceCountField, self).clean(value)
|
||||
return self.value
|
||||
|
||||
def postclean(self, form, data):
|
||||
value = []
|
||||
for dc in range(self.value):
|
||||
templates = form.get_unit_templates(data)
|
||||
if dc < len(templates) - 1:
|
||||
value.append(templates[dc])
|
||||
else:
|
||||
value.append(templates[-1])
|
||||
return value
|
||||
|
||||
|
||||
def with_request(func):
|
||||
def update(self, initial):
|
||||
request = initial.get('request')
|
||||
if request:
|
||||
func(self, request, initial)
|
||||
else:
|
||||
raise forms.ValidationError("Can't get a request information")
|
||||
return update
|
||||
|
||||
|
||||
class DataGridField(forms.MultiValueField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['widget'] = DataGridCompound
|
||||
super(DataGridField, self).__init__(
|
||||
(forms.CharField(required=False), forms.CharField()),
|
||||
*args, **kwargs)
|
||||
|
||||
def compress(self, data_list):
|
||||
return data_list[1]
|
||||
|
||||
@with_request
|
||||
def update(self, request, initial):
|
||||
self.widget.update_request(request)
|
||||
nodes = []
|
||||
instance_count = initial.get('instance_count')
|
||||
if instance_count:
|
||||
for index in xrange(instance_count):
|
||||
nodes.append({'name': 'node' + str(index + 1),
|
||||
'is_sync': index < 2,
|
||||
'is_primary': index == 0})
|
||||
self.initial = json.dumps(nodes)
|
||||
|
||||
|
||||
class DomainChoiceField(forms.ChoiceField):
|
||||
@with_request
|
||||
def update(self, request, initial):
|
||||
self.choices = [("", "Not in domain")]
|
||||
link = request.__dict__['META']['HTTP_REFERER']
|
||||
environment_id = re.search(
|
||||
'murano/(\w+)', link).group(0)[7:]
|
||||
domains = api.service_list_by_type(request, environment_id,
|
||||
'activeDirectory')
|
||||
self.choices.extend(
|
||||
[(domain.name, domain.name) for domain in domains])
|
||||
|
||||
|
||||
class FlavorChoiceField(forms.ChoiceField):
|
||||
@with_request
|
||||
def update(self, request, initial):
|
||||
self.choices = [(flavor.name, flavor.name) for flavor in
|
||||
novaclient(request).flavors.list()]
|
||||
for flavor in self.choices:
|
||||
if 'medium' in flavor[1]:
|
||||
self.initial = flavor[0]
|
||||
break
|
||||
|
||||
|
||||
class ImageChoiceField(forms.ChoiceField):
|
||||
@with_request
|
||||
def update(self, request, initial):
|
||||
try:
|
||||
# public filter removed
|
||||
images, _more = glance.image_list_detailed(request)
|
||||
except:
|
||||
images = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve public images."))
|
||||
|
||||
image_mapping, image_choices = {}, []
|
||||
for image in images:
|
||||
murano_property = image.properties.get('murano_image_info')
|
||||
if murano_property:
|
||||
# convert to dict because
|
||||
# only string can be stored in image metadata property
|
||||
try:
|
||||
murano_json = ast.literal_eval(murano_property)
|
||||
except ValueError:
|
||||
messages.error(request,
|
||||
_("Invalid value in image metadata"))
|
||||
else:
|
||||
title = murano_json.get('title')
|
||||
image_id = murano_json.get('id')
|
||||
if title and image_id:
|
||||
image_mapping[smart_text(title)] = smart_text(image_id)
|
||||
|
||||
for name in sorted(image_mapping.keys()):
|
||||
image_choices.append((image_mapping[name], name))
|
||||
if image_choices:
|
||||
image_choices.insert(0, ("", _("Select Image")))
|
||||
else:
|
||||
image_choices.insert(0, ("", _("No images available")))
|
||||
|
||||
self.choices = image_choices
|
||||
|
||||
|
||||
class AZoneChoiceField(forms.ChoiceField):
|
||||
@with_request
|
||||
def update(self, request, initial):
|
||||
try:
|
||||
availability_zones = novaclient(request).availability_zones.\
|
||||
list(detailed=False)
|
||||
except:
|
||||
availability_zones = []
|
||||
exceptions.handle(request,
|
||||
_("Unable to retrieve availability zones."))
|
||||
|
||||
az_choices = [(az.zoneName, az.zoneName)
|
||||
for az in availability_zones if az.zoneState]
|
||||
if az_choices:
|
||||
az_choices.insert(0, ("", _("Select Availability Zone")))
|
||||
else:
|
||||
az_choices.insert(0, ("", _("No availability zones available")))
|
||||
|
||||
self.choices = az_choices
|
||||
|
||||
|
||||
def get_clone_name(name):
|
||||
return name + '-clone'
|
||||
|
||||
|
||||
def camelize(name):
|
||||
return ''.join([bit.capitalize() for bit in name.split('_')])
|
||||
|
||||
|
||||
def decamelize(name):
|
||||
pat = re.compile(r'([A-Z]*[^A-Z]*)(.*)')
|
||||
bits = []
|
||||
while True:
|
||||
head, tail = re.match(pat, name).groups()
|
||||
bits.append(head)
|
||||
if tail:
|
||||
name = tail
|
||||
else:
|
||||
break
|
||||
return '_'.join([bit.lower() for bit in bits])
|
||||
|
||||
|
||||
def explode(string):
|
||||
if not string:
|
||||
return string
|
||||
bits = []
|
||||
while True:
|
||||
head, tail = string[0], string[1:]
|
||||
bits.append(head)
|
||||
if tail:
|
||||
string = tail
|
||||
else:
|
||||
break
|
||||
return bits
|
||||
|
||||
|
||||
class UpdatableFieldsForm(forms.Form):
|
||||
def update_fields(self):
|
||||
# duplicate all password fields
|
||||
while True:
|
||||
index, inserted = 0, False
|
||||
for name, field in self.fields.iteritems():
|
||||
if isinstance(field, PasswordField) and not field.has_clone:
|
||||
self.fields.insert(index + 1,
|
||||
get_clone_name(name),
|
||||
field.clone_field())
|
||||
inserted = True
|
||||
break
|
||||
index += 1
|
||||
if not inserted:
|
||||
break
|
||||
|
||||
for name, field in self.fields.iteritems():
|
||||
if hasattr(field, 'update'):
|
||||
field.update(self.initial)
|
||||
if not field.required:
|
||||
field.widget.attrs['placeholder'] = 'Optional'
|
||||
|
||||
|
||||
class BooleanField(forms.BooleanField):
|
||||
def __init__(self, *args, **kwargs):
|
||||
kwargs['widget'] = forms.CheckboxInput(attrs={'class': 'checkbox'})
|
||||
super(BooleanField, self).__init__(*args, **kwargs)
|
||||
|
||||
|
||||
class ClusterIPField(forms.CharField):
|
||||
@staticmethod
|
||||
def validate_cluster_ip(request, ip_ranges):
|
||||
def perform_checking(ip):
|
||||
validate_ipv4_address(ip)
|
||||
if not all_matching_cidrs(ip, ip_ranges) and ip_ranges:
|
||||
raise forms.ValidationError(_('Specified Cluster Static IP is'
|
||||
'not in valid IP range'))
|
||||
try:
|
||||
ip_info = novaclient(request).fixed_ips.get(ip)
|
||||
except exceptions.UNAUTHORIZED:
|
||||
exceptions.handle(
|
||||
request, _("Unable to retrieve information ",
|
||||
"about fixed IP or IP is not valid."),
|
||||
ignore=True)
|
||||
else:
|
||||
if ip_info.hostname:
|
||||
raise forms.ValidationError(
|
||||
_('Specified Cluster Static IP is already in use'))
|
||||
return perform_checking
|
||||
|
||||
@with_request
|
||||
def update(self, request, initial):
|
||||
try:
|
||||
network_list = novaclient(request).networks.list()
|
||||
ip_ranges = [network.cidr for network in network_list]
|
||||
ranges = ', '.join(ip_ranges)
|
||||
except StandardError:
|
||||
ip_ranges, ranges = [], ''
|
||||
if ip_ranges:
|
||||
self.help_text = _('Select IP from available range: ' + ranges)
|
||||
else:
|
||||
self.help_text = _('Specify valid fixed IP')
|
||||
self.validators = [self.validate_cluster_ip(request, ip_ranges)]
|
||||
self.error_messages = {'invalid': validate_ipv4_address.message}
|
||||
|
||||
def postclean(self, form, data):
|
||||
# hack to compare two IPs
|
||||
ips = []
|
||||
for key, field in form.fields.items():
|
||||
if isinstance(field, ClusterIPField):
|
||||
ips.append(data[key])
|
||||
if ips[0] == ips[1]:
|
||||
raise forms.ValidationError(_(
|
||||
'Listener IP and Cluster Static IP should be different'))
|
||||
|
||||
|
||||
class DatabaseListField(forms.CharField):
|
||||
validate_mssql_identifier = RegexValidator(
|
||||
re.compile(r'^[a-zA-z_][a-zA-Z0-9_$#@]*$'),
|
||||
_((u'First symbol should be latin letter or underscore. Subsequent ' +
|
||||
u'symbols can be latin letter, numeric, underscore, at sign, ' +
|
||||
u'number sign or dollar sign')))
|
||||
|
||||
default_error_messages = {'invalid': validate_mssql_identifier.message}
|
||||
|
||||
def to_python(self, value):
|
||||
"""Normalize data to a list of strings."""
|
||||
if not value:
|
||||
return []
|
||||
return [name.strip() for name in value.split(',')]
|
||||
|
||||
def validate(self, value):
|
||||
"""Check if value consists only of valid names."""
|
||||
super(DatabaseListField, self).validate(value)
|
||||
for db_name in value:
|
||||
self.validate_mssql_identifier(db_name)
|
||||
|
||||
|
||||
class ServiceConfigurationForm(UpdatableFieldsForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ServiceConfigurationForm, self).__init__(*args, **kwargs)
|
||||
self.attribute_mappings = {}
|
||||
self.insert_fields(self.fields_template)
|
||||
self.initial = kwargs.get('initial', self.initial)
|
||||
self.update_fields()
|
||||
|
||||
EVAL_PREFIX = '$'
|
||||
|
||||
types = {
|
||||
'string': forms.CharField,
|
||||
'boolean': BooleanField,
|
||||
'instance': InstanceCountField,
|
||||
'clusterip': ClusterIPField,
|
||||
'domain': DomainChoiceField,
|
||||
'password': PasswordField,
|
||||
'integer': forms.IntegerField,
|
||||
'databaselist': DatabaseListField,
|
||||
'datagrid': DataGridField,
|
||||
'flavor': FlavorChoiceField,
|
||||
'image': ImageChoiceField,
|
||||
'azone': AZoneChoiceField,
|
||||
'text': (forms.CharField, forms.Textarea)
|
||||
}
|
||||
|
||||
localizable_keys = set(['label', 'help_text', 'error_messages'])
|
||||
|
||||
def init_attribute_mappings(self, field_name, kwargs):
|
||||
def set_mapping(name, value):
|
||||
"""Spawns new dictionaries for each dot found in name."""
|
||||
bits = name.split('.')
|
||||
head, tail, mapping = bits[0], bits[1:], self.attribute_mappings
|
||||
while tail:
|
||||
if not head in mapping:
|
||||
mapping[head] = {}
|
||||
head, tail, mapping = tail[0], tail[1:], mapping[head]
|
||||
mapping[head] = value
|
||||
|
||||
if 'attribute_names' in kwargs:
|
||||
attr_names = kwargs['attribute_names']
|
||||
if type(attr_names) == list:
|
||||
# allow pushing field value to multiple attributes
|
||||
for attr_name in attr_names:
|
||||
set_mapping(attr_name, field_name)
|
||||
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_properties(cls, kwargs):
|
||||
props = {}
|
||||
for key, value in kwargs.iteritems():
|
||||
if isinstance(value, property):
|
||||
props[key] = value
|
||||
for key in props.keys():
|
||||
del kwargs[key]
|
||||
if props:
|
||||
return type('cls_with_props', (cls,), props)
|
||||
else:
|
||||
return cls
|
||||
|
||||
def append_field(field_spec):
|
||||
_, cls = parse_spec(field_spec['type'], 'type')
|
||||
widget = None
|
||||
if type(cls) == tuple:
|
||||
cls, widget = cls
|
||||
_, kwargs = parse_spec(field_spec)
|
||||
kwargs['widget'] = process_widget(kwargs, cls, widget)
|
||||
cls = append_properties(cls, 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 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 parse_spec(spec, keys=[]):
|
||||
if not type(keys) == list:
|
||||
keys = [keys]
|
||||
key = keys and keys[-1] or None
|
||||
if type(spec) == dict:
|
||||
items = []
|
||||
for k, v in spec.iteritems():
|
||||
if not k in ('type', 'name'):
|
||||
k = decamelize(k)
|
||||
newKey, v = parse_spec(v, keys + [k])
|
||||
if newKey:
|
||||
k = newKey
|
||||
items.append((k, v))
|
||||
return key, dict(items)
|
||||
elif type(spec) == list:
|
||||
return key, [parse_spec(_spec, keys)[1] for _spec in spec]
|
||||
elif type(spec) in (str, unicode) 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)]
|
||||
elif (type(spec) in (str, unicode) and
|
||||
spec[0] == self.EVAL_PREFIX):
|
||||
def _get(field):
|
||||
name = self.add_prefix(spec[1:])
|
||||
return self.data.get(name, False)
|
||||
|
||||
def _set(field, value):
|
||||
field.__dict__[key] = value
|
||||
|
||||
def _del(field):
|
||||
del field.__dict__[key]
|
||||
|
||||
return key, property(_get, _set, _del)
|
||||
else:
|
||||
return key, spec
|
||||
|
||||
for spec in field_specs:
|
||||
append_field(spec)
|
||||
|
||||
def get_unit_templates(self, data):
|
||||
def parse_spec(spec):
|
||||
if type(spec) == list:
|
||||
return [parse_spec(_spec) for _spec in spec]
|
||||
elif type(spec) == dict:
|
||||
return {parse_spec(k): parse_spec(v)
|
||||
for k, v in spec.iteritems()}
|
||||
elif (type(spec) in (str, unicode) and
|
||||
spec[0] == self.EVAL_PREFIX):
|
||||
return data.get(spec[1:])
|
||||
else:
|
||||
return spec
|
||||
return [parse_spec(spec) for spec in self.service.unit_templates]
|
||||
|
||||
def extract_attributes(self, attributes):
|
||||
def get_data(name):
|
||||
if type(name) == dict:
|
||||
return {k: get_data(v) for k, v in name.iteritems()}
|
||||
else:
|
||||
return self.cleaned_data[name]
|
||||
for attr_name, field_name in self.attribute_mappings.iteritems():
|
||||
attributes[attr_name] = get_data(field_name)
|
||||
|
||||
def clean(self):
|
||||
form_data = self.cleaned_data
|
||||
|
||||
def compare(name, label):
|
||||
if form_data.get(name) != form_data.get(get_clone_name(name)):
|
||||
raise forms.ValidationError(_(u"{0}{1} don't match".format(
|
||||
label, pluralize(2))))
|
||||
|
||||
for name, field in self.fields.iteritems():
|
||||
if isinstance(field, PasswordField) and field.is_original():
|
||||
compare(name, field.label)
|
||||
|
||||
if hasattr(field, 'postclean'):
|
||||
value = field.postclean(self, form_data)
|
||||
if value:
|
||||
self.cleaned_data[name] = value
|
||||
|
||||
return self.cleaned_data
|
|
@ -0,0 +1,116 @@
|
|||
import os
|
||||
from django.template.defaultfilters import slugify
|
||||
from collections import OrderedDict
|
||||
from yaml.scanner import ScannerError
|
||||
import yaml
|
||||
import re
|
||||
|
||||
|
||||
_all_services = OrderedDict()
|
||||
|
||||
|
||||
def import_all_services():
|
||||
from muranodashboard.panel.services.__common import decamelize
|
||||
directory = os.path.dirname(__file__)
|
||||
for fname in sorted(os.listdir(directory)):
|
||||
try:
|
||||
if fname.endswith('.yaml'):
|
||||
name = os.path.splitext(fname)[0]
|
||||
path = os.path.join(directory, fname)
|
||||
modified_on = os.stat(path).st_mtime
|
||||
if (not name in _all_services or
|
||||
_all_services[name][0] < modified_on):
|
||||
with open(path) as f:
|
||||
kwargs = {decamelize(k): v
|
||||
for k, v in yaml.load(f).iteritems()}
|
||||
_all_services[name] = (modified_on,
|
||||
type(name, (), kwargs))
|
||||
except ScannerError:
|
||||
pass
|
||||
except OSError:
|
||||
pass
|
||||
|
||||
|
||||
def iterate_over_services():
|
||||
import_all_services()
|
||||
for id, service_data in _all_services.items():
|
||||
modified_on, service_cls = service_data
|
||||
from muranodashboard.panel.services.__common import \
|
||||
ServiceConfigurationForm
|
||||
forms = []
|
||||
for fields in service_cls.forms:
|
||||
class Form(ServiceConfigurationForm):
|
||||
service = service_cls
|
||||
fields_template = fields
|
||||
forms.append(Form)
|
||||
yield slugify(service_cls.name), service_cls, forms
|
||||
|
||||
|
||||
def iterate_over_service_forms():
|
||||
for slug, Service, forms in iterate_over_services():
|
||||
for step, form in zip(xrange(len(forms)), forms):
|
||||
yield '{0}-{1}'.format(slug, step), form
|
||||
|
||||
|
||||
def with_service(slug, getter, default):
|
||||
import_all_services()
|
||||
match = re.match('(.*)-[0-9]+', slug)
|
||||
if match:
|
||||
slug = match.group(1)
|
||||
for _slug, Service, forms in iterate_over_services():
|
||||
if _slug == slug:
|
||||
return getter(Service)
|
||||
return default
|
||||
|
||||
|
||||
def get_service_template(slug):
|
||||
return with_service(slug, lambda Service: Service.template, '')
|
||||
|
||||
|
||||
def get_service_name(slug):
|
||||
return with_service(slug, lambda Service: Service.name, '')
|
||||
|
||||
|
||||
def get_service_client(slug):
|
||||
return with_service(slug, lambda Service: Service.type, None)
|
||||
|
||||
|
||||
def get_service_field_descriptions(slug, index):
|
||||
def get_descriptions(Service):
|
||||
form = Service.forms[index]
|
||||
descriptions = []
|
||||
for field in form:
|
||||
if 'description' in field:
|
||||
title = field.get('descriptionTitle', field.get('label', ''))
|
||||
descriptions.append((title, field['description']))
|
||||
return descriptions
|
||||
return with_service(slug, get_descriptions, [])
|
||||
|
||||
|
||||
def get_service_type(wizard):
|
||||
cleaned_data = wizard.get_cleaned_data_for_step('service_choice') \
|
||||
or {'service': 'none'}
|
||||
return cleaned_data.get('service')
|
||||
|
||||
|
||||
def get_service_choices():
|
||||
return [(slug, Service.name) for slug, Service, forms in
|
||||
iterate_over_services()]
|
||||
|
||||
|
||||
def get_service_checkers():
|
||||
import_all_services()
|
||||
|
||||
def make_comparator(slug):
|
||||
def compare(wizard):
|
||||
match = re.match('(.*)-[0-9]+', slug)
|
||||
return match and match.group(1) == get_service_type(wizard)
|
||||
return compare
|
||||
|
||||
return [(slug, make_comparator(slug)) for slug, form
|
||||
in iterate_over_service_forms()]
|
||||
|
||||
|
||||
def get_service_descriptions():
|
||||
return [(slug, Service.description) for slug, Service, forms in
|
||||
iterate_over_services()]
|
|
@ -20,24 +20,20 @@ from views import CreateEnvironmentView
|
|||
from views import DetailServiceView
|
||||
from views import DeploymentsView
|
||||
from views import Wizard, EditEnvironmentView
|
||||
from views import SERVICE_CHECKER, is_service_mssql_cluster
|
||||
from forms import FORMS
|
||||
from consts import SERVICE_NAMES
|
||||
from muranodashboard.panel.services import get_service_checkers
|
||||
from openstack_dashboard.dashboards.project.instances.views import DetailView
|
||||
|
||||
VIEW_MOD = 'openstack_dashboard.dashboards.project.murano.views'
|
||||
ENVIRONMENT_ID = r'^(?P<environment_id>[^/]+)'
|
||||
|
||||
condition_dict_for_wizard = dict(zip(SERVICE_NAMES, SERVICE_CHECKER))
|
||||
condition_dict_for_wizard['mssql_datagrid'] = is_service_mssql_cluster
|
||||
condition_dict_for_wizard['mssql_ag_configuration'] = is_service_mssql_cluster
|
||||
|
||||
urlpatterns = patterns(
|
||||
VIEW_MOD,
|
||||
url(r'^environments$', IndexView.as_view(), name='index'),
|
||||
|
||||
url(r'^create/$',
|
||||
Wizard.as_view(FORMS, condition_dict=condition_dict_for_wizard),
|
||||
Wizard.as_view(FORMS,
|
||||
condition_dict=dict(get_service_checkers())),
|
||||
name='create'),
|
||||
|
||||
url(r'^create_environment/$', CreateEnvironmentView.as_view(),
|
||||
|
|
|
@ -38,52 +38,11 @@ from muranodashboard.panel import api
|
|||
from muranoclient.common.exceptions import HTTPUnauthorized, \
|
||||
CommunicationError, HTTPInternalServerError, HTTPForbidden, HTTPNotFound
|
||||
|
||||
from consts import AD_NAME, IIS_NAME, ASP_NAME, IIS_FARM_NAME, ASP_FARM_NAME, \
|
||||
MSSQL_NAME, MSSQL_CLUSTER_NAME, SERVICE_NAME_DICT
|
||||
|
||||
from muranodashboard.panel.services import get_service_descriptions, \
|
||||
get_service_name, get_service_client, get_service_field_descriptions
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_service_type(wizard):
|
||||
cleaned_data = wizard.get_cleaned_data_for_step('service_choice') \
|
||||
or {'service': 'none'}
|
||||
return cleaned_data.get('service')
|
||||
|
||||
|
||||
def is_service_ad(wizard):
|
||||
return get_service_type(wizard) == AD_NAME
|
||||
|
||||
|
||||
def is_service_iis(wizard):
|
||||
return get_service_type(wizard) == IIS_NAME
|
||||
|
||||
|
||||
def is_service_asp(wizard):
|
||||
return get_service_type(wizard) == ASP_NAME
|
||||
|
||||
|
||||
def is_service_iis_farm(wizard):
|
||||
return get_service_type(wizard) == IIS_FARM_NAME
|
||||
|
||||
|
||||
def is_service_asp_farm(wizard):
|
||||
return get_service_type(wizard) == ASP_FARM_NAME
|
||||
|
||||
|
||||
def is_service_mssql(wizard):
|
||||
return get_service_type(wizard) == MSSQL_NAME
|
||||
|
||||
|
||||
def is_service_mssql_cluster(wizard):
|
||||
return get_service_type(wizard) == MSSQL_CLUSTER_NAME
|
||||
|
||||
|
||||
SERVICE_CHECKER = (is_service_ad, is_service_iis,
|
||||
is_service_asp, is_service_iis_farm,
|
||||
is_service_asp_farm, is_service_mssql,
|
||||
is_service_mssql_cluster)
|
||||
|
||||
|
||||
class Wizard(ModalFormMixin, SessionWizardView):
|
||||
template_name = 'services/wizard_create.html'
|
||||
|
||||
|
@ -95,104 +54,30 @@ class Wizard(ModalFormMixin, SessionWizardView):
|
|||
args=(environment_id,))
|
||||
|
||||
step0_data = form_list[0].cleaned_data
|
||||
step1_data = form_list[1].cleaned_data
|
||||
last_step_data = form_list[-1].cleaned_data
|
||||
slug = step0_data.get('service', '')
|
||||
attributes = {'type': get_service_client(slug)}
|
||||
|
||||
service_type = step0_data.get('service', '')
|
||||
parameters = {'type': service_type}
|
||||
for form in form_list[1:]:
|
||||
form.extract_attributes(attributes)
|
||||
|
||||
parameters['units'] = []
|
||||
parameters['unitNamingPattern'] = step1_data.get('unit_name_template')
|
||||
parameters['availabilityZone'] = \
|
||||
last_step_data.get('availability_zone')
|
||||
parameters['flavor'] = last_step_data.get('flavor')
|
||||
parameters['osImage'] = last_step_data.get('image')
|
||||
# hack to fill units with data from nodes datagrid
|
||||
if 'nodes' in attributes:
|
||||
units = []
|
||||
for node in json.loads(attributes['nodes']):
|
||||
units.append({'isMaster': node['is_primary'],
|
||||
'isSync': node['is_sync']})
|
||||
attributes['units'] = units
|
||||
del attributes['nodes']
|
||||
|
||||
if service_type == AD_NAME:
|
||||
parameters['name'] = str(step1_data.get('dc_name', 'noname'))
|
||||
parameters['domain'] = parameters['name'] # Fix Me in orchestrator
|
||||
parameters['adminPassword'] = \
|
||||
str(step1_data.get('adm_password1', ''))
|
||||
recovery_password = str(step1_data.get('password_field1', ''))
|
||||
parameters['units'].append({'isMaster': True,
|
||||
'recoveryPassword': recovery_password})
|
||||
dc_count = int(step1_data.get('dc_count', 1))
|
||||
parameters['adminAccountName'] = step1_data.get('adm_user', '')
|
||||
for dc in range(dc_count - 1):
|
||||
parameters['units'].append({
|
||||
'isMaster': False,
|
||||
'recoveryPassword': recovery_password
|
||||
})
|
||||
|
||||
elif service_type in [IIS_NAME, ASP_NAME,
|
||||
IIS_FARM_NAME, ASP_FARM_NAME, MSSQL_NAME,
|
||||
MSSQL_CLUSTER_NAME]:
|
||||
parameters['name'] = str(step1_data.get('service_name', 'noname'))
|
||||
parameters['domain'] = str(step1_data.get('domain', ''))
|
||||
parameters['adminPassword'] = step1_data.get('adm_password1', '')
|
||||
|
||||
if service_type in [MSSQL_NAME, MSSQL_CLUSTER_NAME]:
|
||||
mixed_mode = step1_data.get('mixed_mode', False)
|
||||
sa_password = str(
|
||||
step1_data.get('password_field1', ''))
|
||||
parameters['saPassword'] = sa_password
|
||||
parameters['mixedModeAuth'] = mixed_mode
|
||||
if parameters['domain'] == '':
|
||||
parameters['domain'] = None
|
||||
|
||||
if service_type == ASP_NAME or service_type == ASP_FARM_NAME:
|
||||
parameters['repository'] = step1_data.get('repository', '')
|
||||
|
||||
if service_type in [IIS_FARM_NAME, ASP_FARM_NAME]:
|
||||
parameters['loadBalancerPort'] = step1_data.get('lb_port', 80)
|
||||
|
||||
instance_count = 1
|
||||
if service_type in [IIS_FARM_NAME, ASP_FARM_NAME]:
|
||||
instance_count = int(step1_data.get('instance_count', 1))
|
||||
|
||||
for unit in range(instance_count):
|
||||
parameters['units'].append({})
|
||||
|
||||
if service_type == MSSQL_CLUSTER_NAME:
|
||||
parameters['domainAdminUserName'] = \
|
||||
step1_data.get('ad_user', '')
|
||||
parameters['domainAdminPassword'] = \
|
||||
step1_data.get('ad_password', '')
|
||||
|
||||
step2_data = form_list[2].cleaned_data
|
||||
parameters['clusterName'] = step2_data.get('clusterName', '')
|
||||
parameters['clusterIP'] = str(step2_data.get('fixed_ip', ''))
|
||||
parameters['agGroupName'] = step2_data.get('agGroupName', '')
|
||||
parameters['agListenerIP'] = step2_data.get('agListenerIP', '')
|
||||
parameters['agListenerName'] = step2_data.get('agListenerName',
|
||||
'')
|
||||
parameters['sqlServicePassword'] = \
|
||||
step2_data.get('sqlServicePassword1', '')
|
||||
parameters['sqlServiceUserName'] = \
|
||||
step2_data.get('sqlServiceUserName', '')
|
||||
|
||||
step3_data = form_list[3].cleaned_data
|
||||
parameters['databases'] = step3_data.get('databases')
|
||||
form_nodes = step3_data.get('nodes')
|
||||
units = []
|
||||
if form_nodes:
|
||||
nodes = json.loads(step3_data.get('nodes'))
|
||||
for node in nodes:
|
||||
unit = {}
|
||||
unit['isMaster'] = node['is_primary']
|
||||
unit['isSync'] = node['is_sync']
|
||||
units.append(unit)
|
||||
parameters['units'] = units
|
||||
try:
|
||||
api.service_create(self.request, environment_id, parameters)
|
||||
api.service_create(self.request, environment_id, attributes)
|
||||
except HTTPForbidden:
|
||||
msg = _('Sorry, you can\'t create service right now.'
|
||||
'The environment is deploying.')
|
||||
redirect = reverse("horizon:project:murano:index")
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
else:
|
||||
message = "The %s service successfully created." \
|
||||
% SERVICE_NAME_DICT[service_type]
|
||||
message = "The %s service successfully created." % slug
|
||||
messages.success(self.request, message)
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
|
@ -201,19 +86,25 @@ class Wizard(ModalFormMixin, SessionWizardView):
|
|||
if step != 'service_choice':
|
||||
init_dict['request'] = self.request
|
||||
|
||||
if step == 'mssql_datagrid':
|
||||
instance_count = self.storage.data['step_data'][
|
||||
'mssql_ag_configuration'].\
|
||||
get('mssql_ag_configuration-instance_count')
|
||||
# hack to pass number of nodes from one form to another
|
||||
if step == 'ms-sql-server-cluster-2':
|
||||
form_id = 'ms-sql-server-cluster-1'
|
||||
form_data = self.storage.data['step_data'].get(form_id, {})
|
||||
instance_count = form_data.get(form_id + '-dcInstances')
|
||||
if instance_count:
|
||||
init_dict['instance_count'] = int(instance_count[0])
|
||||
return self.initial_dict.get(step, init_dict)
|
||||
|
||||
def get_context_data(self, form, **kwargs):
|
||||
context = super(Wizard, self).get_context_data(form=form, **kwargs)
|
||||
context['service_descriptions'] = get_service_descriptions()
|
||||
if self.steps.index > 0:
|
||||
data = self.get_cleaned_data_for_step('service_choice')
|
||||
context.update({'type': SERVICE_NAME_DICT[data['service']]})
|
||||
slug = data['service']
|
||||
context['field_descriptions'] = get_service_field_descriptions(
|
||||
slug, self.steps.index - 1)
|
||||
context.update({'type': get_service_client(slug),
|
||||
'service_name': get_service_name(slug)})
|
||||
return context
|
||||
|
||||
|
||||
|
|
|
@ -89,7 +89,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
|
|||
TEMPLATE_LOADERS = (
|
||||
'django.template.loaders.filesystem.Loader',
|
||||
'django.template.loaders.app_directories.Loader',
|
||||
'horizon.loaders.TemplateLoader'
|
||||
'horizon.loaders.TemplateLoader',
|
||||
)
|
||||
|
||||
TEMPLATE_DIRS = (
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
/**
|
||||
* Created with PyCharm.
|
||||
* User: tsufiev
|
||||
* Date: 30.07.13
|
||||
* Time: 20:27
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
$(function() {
|
||||
var MAX_NODES = 5,
|
||||
MIN_NODES = 2;
|
||||
|
|
|
@ -1,25 +1,18 @@
|
|||
/**
|
||||
* Created with PyCharm.
|
||||
* User: tsufiev
|
||||
* Date: 12.08.13
|
||||
* Time: 12:25
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
$(function() {
|
||||
function check_preconfigured_ad(){
|
||||
var checked = $("input[id*='external_ad']").prop('checked')
|
||||
var checked = $("input[id*='externalAD']").prop('checked')
|
||||
if ( checked== true) {
|
||||
$("select[id*='-domain']").attr("disabled", "disabled");
|
||||
$("label[for*='ad_user']").parent().css({'display': 'inline-block'});
|
||||
$("label[for*='ad_password']").parent().css({'display': 'inline-block'});
|
||||
$("label[for*='domainAdminUserName']").parent().css({'display': 'inline-block'});
|
||||
$("label[for*='domainAdminPassword']").parent().css({'display': 'inline-block'});
|
||||
}
|
||||
if (checked == false) {
|
||||
$("select[id*='-domain']").removeAttr("disabled");
|
||||
$("label[for*='ad_user']").parent().css({'display': 'none'});
|
||||
$("label[for*='ad_password']").parent().css({'display': 'none'});
|
||||
$("label[for*='domainAdminUserName']").parent().css({'display': 'none'});
|
||||
$("label[for*='domainAdminPassword']").parent().css({'display': 'none'});
|
||||
}
|
||||
}
|
||||
|
||||
$("input[id*='external_ad']").change(check_preconfigured_ad);
|
||||
$("input[id*='externalAD']").change(check_preconfigured_ad);
|
||||
check_preconfigured_ad();
|
||||
});
|
|
@ -1,23 +1,14 @@
|
|||
/**
|
||||
* Created with PyCharm.
|
||||
* User: tsufiev
|
||||
* Date: 12.08.13
|
||||
* Time: 12:29
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
$(function() {
|
||||
function check_mixed_mode(){
|
||||
var checked = $("input[id*='mixed_mode']").prop('checked')
|
||||
var checked = $("input[id*='mixedModeAuth']").prop('checked')
|
||||
if ( checked === true) {
|
||||
$("label[for*='password_field']").parent().css(
|
||||
{'display': 'inline-block'});
|
||||
$("label[for*='saPassword']").parent().css({'display': 'inline-block'});
|
||||
} else if (checked === false) {
|
||||
$("label[for*='password_field']").parent().css(
|
||||
{'display': 'none'});
|
||||
$("label[for*='saPassword']").parent().css({'display': 'none'});
|
||||
}
|
||||
}
|
||||
|
||||
$("input[id*='mixed_mode']").change(check_mixed_mode);
|
||||
$("input[id*='mixedModeAuth']").change(check_mixed_mode);
|
||||
check_mixed_mode();
|
||||
$(".checkbox").css({'float': 'left', 'width': 'auto', 'margin-right': '10px'})
|
||||
});
|
||||
|
|
|
@ -1,10 +1,3 @@
|
|||
/**
|
||||
* Created with PyCharm.
|
||||
* User: tsufiev
|
||||
* Date: 12.08.13
|
||||
* Time: 11:34
|
||||
* To change this template use File | Settings | File Templates.
|
||||
*/
|
||||
$(function() {
|
||||
function main_check(div, parameter1, parameter2, text){
|
||||
var msg = "<span class='help-inline'>" + gettext(text) + '</span>'
|
||||
|
|
|
@ -28,297 +28,26 @@
|
|||
</td></tr></table>
|
||||
</div>
|
||||
<div class="right">
|
||||
{% if wizard.steps.index == 0 %}
|
||||
{% if wizard.steps.index > 0 %}
|
||||
<h3> {% blocktrans %} {{ service_name }} Service{% endblocktrans %} </h3>
|
||||
{% for title, description in field_descriptions %}
|
||||
<p>
|
||||
{% if title %}
|
||||
<strong>{% blocktrans %}{{ title }}:{% endblocktrans %}</strong>
|
||||
{% endif %}
|
||||
{% blocktrans %}{{ description }}{% endblocktrans %}
|
||||
</p>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>
|
||||
{% blocktrans %} <strong> The Active Directory Service </strong>
|
||||
includes one primary and optionally a few secondary Domain
|
||||
Controllers, with DNS service {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %} <strong> The Internet Information Service </strong>
|
||||
sets up an IIS server and joins it into an existing domain {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %} <strong> The ASP.NET Application Service </strong>
|
||||
installs custom application onto one IIS Web Server {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %} <strong> The IIS Farm Service </strong>
|
||||
sets up a load-balanced set of IIS servers {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %} <strong> The ASP.NET Farm Service </strong>
|
||||
installs a custom application on a load-balanced array of IIS servers {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %} <strong> The MS SQL Service </strong>
|
||||
installs an instance of Microsoft SQL Server{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
{% blocktrans %} <strong> The MS SQL Failover Cluster </strong>
|
||||
installs Microsoft SQL Failover Cluster Server{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<i>{% blocktrans %} If you want your services to work with AD you
|
||||
should create The Active Directory Service first {% endblocktrans %}
|
||||
</i>
|
||||
</p>
|
||||
{% for slug, description in service_descriptions %}
|
||||
<p class="switchable" id="{{ slug }}">
|
||||
{% autoescape off %} {% blocktrans %}
|
||||
{{ description }}
|
||||
{% endblocktrans %} {% endautoescape %}</p>
|
||||
{% endfor %}
|
||||
<p>{% blocktrans %} If you want your services to work with AD you should create The Active Directory Service first {% endblocktrans %}</p>
|
||||
{% endif %}
|
||||
{% if wizard.steps.index == 1 %}
|
||||
<h3> {% blocktrans %} {{ type }} Service{% endblocktrans %} </h3>
|
||||
{% if type == 'Active Directory' %}
|
||||
<p>
|
||||
<strong>{% blocktrans %} Domain Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Enter a desired name for a new domain. This name should fit in standard Windows domain name requirements: it should contain only A-Z, a-z, 0-9, and (-) and should not start or end with a dash.
|
||||
Single-level domain is not appropriate. Period characters are allowed only when they are used to delimit the components of domain style names. DNS server will be automatically set up on each of the Domain Controller instances. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Instance Count: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} You can create several Active Directory instances by setting instance number larger than one. One primary Domain Controller and a few secondary DCs will be created. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %}Account Name: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Specify name for domain account. The default name is Administrator.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Passwords: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Windows requires strong password for service administration. Your password should have at least one letter in each register, a number and a special character. Password length should be a minimum of 7 characters. {% endblocktrans %}
|
||||
{% blocktrans %} Once you forget your password you won't be able to operate the service until recovery password would be entered. So it's better for Recovery and Administrator password to be different. {% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% elif type == 'ASP.NET Application' %}
|
||||
{% blocktrans %} ASP.NET application will be installed onto one IISWeb Server {% endblocktrans %}
|
||||
<p>
|
||||
<strong>{% blocktrans %} Service Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and underline are allowed. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Administrator Password: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Windows requires strong password for service administration. Your password should have at least one letter in each register, a number and a special character. Password length should be a minimum of 7 characters. {% endblocktrans %}
|
||||
{% blocktrans %} Once you forget your password you won't be able to operate the service. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Active Directory Domain: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Service can be joined to the Active Directory domain. If you want to create an AD domain create the AD Service first. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Git repository {% endblocktrans %}</strong>
|
||||
{% blocktrans %} URL of a git repository with the application you want to deploy. {% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% elif type == 'ASP.NET Farm' %}
|
||||
<p>
|
||||
{% blocktrans %} The ASP.NET application will be installed on a number of IIS Web Servers, and load balancing will be configured. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Service Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and underline are allowed. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Administrator Password: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Windows requires strong password for service administration. Your password should have at least one letter in each register, a number and a special character.
|
||||
Password length should be a minimum of 7 characters. Once you forget your password you won't be able to operate the service. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Instance Count: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Several instances with ASP.NET Application Service can be created. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Load Balancer port {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Specify port number where Load Balancer will be running {% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% elif type == 'IIS' %}
|
||||
<p>{% blocktrans %} Standalone IIS Server {% endblocktrans %}</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Service Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and underline are allowed. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Administrator Password: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Windows requires strong password for service administration. Your password should have at least one letter in each register, a number and a special character.
|
||||
Password length should be a minimum of 7 characters. {% endblocktrans %}
|
||||
{% blocktrans %} Once you forget your password you won't be able to operate the service. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Active Directory Domain: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Service can be joined to the Active Directory domain. If you want to create an AD domain create the AD Service first. {% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% elif type == 'IIS Farm' %}
|
||||
<p>{% blocktrans %} A load-balanced array of IIS servers {% endblocktrans %}</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Service Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and underline are allowed. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Administrator Password: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Windows requires strong password for service administration. Your password should have at least one letter in each register, a number and a special character.
|
||||
Password length should be a minimum of 7 characters. Once you forget your password you won't be able to operate the service. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Active Directory Domain: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Service can be joined to the Active Directory domain. If you want to create an AD domain create the AD Service first. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Instance Count: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Several instances with IIS Service can be created at one time. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Load Balancer port {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Specify port number where Load Balancer will be running {% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% elif type == 'Microsoft SQL Server'%}
|
||||
<p>
|
||||
<strong>{% blocktrans %} Service Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Enter a desired name for a service. Just A-Z, a-z, 0-9, dash and underline are allowed. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Administrator Password: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Windows requires strong password for service
|
||||
administration. Your password should have at least one
|
||||
letter in each register, a number and a special character.
|
||||
Password length should be a minimum of 7 characters. Once
|
||||
you forget your password you won't be able to operate the
|
||||
service. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Active Directory Domain: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Service can be joined to the Active Directory
|
||||
domain. If you want to create an AD domain create the AD Service first. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Mixed-mode Authentication: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Mixed authentication mode allows the use of Windows credentials but supplements them with local SQL
|
||||
Server user accounts that the administrator may create and maintain within SQL Server.
|
||||
If this mode is on SA password is required{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} SA Password: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Set system administrator password for the MS SQL Server.{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% elif type == 'Microsoft SQL Server Cluster' %}
|
||||
<p>
|
||||
<strong>{% blocktrans %} Service Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Enter a desired name for a service. Just A-Z,
|
||||
a-z, 0-9, dash and underline are
|
||||
allowed. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Administrator Password: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Windows requires strong password for service
|
||||
administration. Your password should have at least one
|
||||
letter in each register, a number and a special character.
|
||||
Password length should be a minimum of 7 characters. Once
|
||||
you forget your password you won't be able to operate the
|
||||
service. {% endblocktrans %}
|
||||
</p>
|
||||
<p><strong>{% blocktrans %} Active Directory is configured by the System Administrator: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} AD domain must be specified for the MS SQL Server Cluster. <i>This checkbox should be enabled only in case
|
||||
your environment has scripts with valid rules that will join created service to the existent domain automatically. </i>
|
||||
Also AD User (administrator account) and AD Password should be entered to have access to the existing Active Directory Domain.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Active Directory Domain: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Service can be joined to the Active Directory
|
||||
domain. If you want to create an AD domain create the AD
|
||||
Service first. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Mixed-mode Authentication: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Mixed authentication mode allows the use of
|
||||
Windows credentials but supplements them with local SQL
|
||||
Server user accounts that the administrator may create and
|
||||
maintain within SQL Server.
|
||||
If this mode is on SA password is
|
||||
required{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} SA Password: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Set system administrator password for the MS
|
||||
SQL Server.{% endblocktrans %}
|
||||
</p>
|
||||
|
||||
{% endif %}
|
||||
<p>
|
||||
<strong>{% blocktrans %} Hostname template {% endblocktrans %}</strong>
|
||||
{% blocktrans %} For your convenience all instance hostnames can be named in the same way. Enter a name and use # character for incrementation. For example, host# turns into host1, host2, etc. Please follow Windows hostname restrictions. {% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if wizard.steps.current == 'mssql_ag_configuration' %}
|
||||
<h3> {% blocktrans %} MS SQL Failover Cluster {% endblocktrans %} </h3>
|
||||
<p>
|
||||
<strong>{% blocktrans %}Cluster Static IP: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Specify a valid IPv4 fixed IP.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %}Cluster Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %}Specify a name of a cluster. Just A-Z, a-z, 0-9, dash and underline are allowed.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %}Availability Group Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %}Specify a name of an AG. Just A-Z, a-z, 0-9, dash and underline are allowed.
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %}Availability Group Listener Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Specify a name of an AG Listener . Just A-Z, a-z, 0-9, dash and underline are allowed.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %}Availability Group Listener IP: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Specify a valid IPv4 fixed IP.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %}SQL User Name: {% endblocktrans %}</strong>
|
||||
{% blocktrans %}User name that will be created to manage cluster instances.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %}SQL User Password: {% endblocktrans %}</strong>
|
||||
{% blocktrans %}User password that will be created to manage cluster instances. {% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Instance Count: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Microsoft SQL Failover Cluster includes up to 5 instances.{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
{% if wizard.steps.current == 'mssql_datagrid' %}
|
||||
<h3> {% blocktrans %} MS SQL Failover Cluster {% endblocktrans %} </h3>
|
||||
<p>
|
||||
<strong>{% blocktrans %}Nodes: {% endblocktrans %}</strong>
|
||||
{% blocktrans %}Configure cluster instances. Cluster node quantity can be set with 'Add' and 'Remove' buttons.
|
||||
Configure Sync mode by enabling corresponding checkbox. All other nodes will be in Async mode. Just 2 nodes are allowed to be Sync.
|
||||
Also one Master node need to be selected. SQL Failover cluster has limit of 5 instances.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %}Database list: {% endblocktrans %}</strong>
|
||||
{% blocktrans %} Specify names for new databases which will be created as part of service installation.
|
||||
Here should come comma-separated list of database names, where each name has the following syntax: first symbol
|
||||
should be latin letter or underscore; subsequent symbols can be latin letter, numeric, underscore,
|
||||
at sign, number sign or dollar sign.{% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% if wizard.steps.current == 'instance_configuration' %}
|
||||
<h3>{% trans "Instance Configuration" %}:</h3>
|
||||
<p>{% blocktrans %}Specify some instance parameters on which service would be created.{% endblocktrans %}</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Instance flavor: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Select registered in Openstack flavor. Consider that service performance depends on this parameter.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Instance image: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Select valid image for a service. Image should already be prepared and registered in glance.{% endblocktrans %}
|
||||
</p>
|
||||
<p>
|
||||
<strong>{% blocktrans %} Availability zone: {% endblocktrans %} </strong>
|
||||
{% blocktrans %} Select availability zone where service would be installed. {% endblocktrans %}
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
@ -336,6 +65,15 @@
|
|||
return selected;
|
||||
}
|
||||
|
||||
$('#id_service_choice-service').change(function(event) {
|
||||
var selected = get_selection(event.target);
|
||||
if ( selected ) {
|
||||
$('p.switchable:not(#'+selected+')').css('display', 'none');
|
||||
$('p.switchable#'+selected).css('display', 'block');
|
||||
}
|
||||
});
|
||||
$('#id_service_choice-service').change();
|
||||
|
||||
$(".btn-wizard").click(function() {
|
||||
var step = get_selection($('#id_service_choice-service'));
|
||||
if ( step ) {
|
||||
|
|
Loading…
Reference in New Issue