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:
Timur Sufiev 2013-07-05 14:55:15 +04:00
parent 5bc5102057
commit 9c9b9d7778
22 changed files with 1592 additions and 1151 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
*~
*.orig
*.pyc
*.swp
.environment_version

View File

@ -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]

View File

@ -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

View File

@ -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'

View File

@ -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())

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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()]

View File

@ -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(),

View File

@ -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

View File

@ -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 = (

View File

@ -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;

View File

@ -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();
});

View File

@ -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'})
});

View File

@ -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>'

View File

@ -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 ) {