Additional optional Environment data to heat template selection

Change-Id: I4e0d32e7e032d5c0ec93595c14134e780fa21d66
Implements: blueprint heat-environment-file
This commit is contained in:
Jordan OMara 2014-01-30 16:43:00 -05:00
parent eabc890792
commit e59043d3bf
3 changed files with 285 additions and 48 deletions

View File

@ -53,6 +53,23 @@ def exception_to_validation_msg(e):
return match.group(1)
def create_upload_form_attributes(prefix, input_type, name):
"""Creates attribute dicts for the switchable upload form
:type prefix: str
:param prefix: prefix (environment, template) of field
:type input_type: str
:param input_type: field type (file, raw, url)
:type name: str
:param name: translated text label to display to user
:rtype: dict
:return: an attribute set to pass to form build
"""
attributes = {'class': 'switched', 'data-switch-on': prefix + 'source'}
attributes['data-' + prefix + 'source-' + input_type] = name
return attributes
class TemplateForm(forms.SelfHandlingForm):
class Meta:
@ -60,34 +77,79 @@ class TemplateForm(forms.SelfHandlingForm):
help_text = _('From here you can select a template to launch '
'a stack.')
choices = [('url', _('URL')),
('file', _('File')),
('raw', _('Direct Input'))]
attributes = {'class': 'switchable', 'data-slug': 'templatesource'}
template_source = forms.ChoiceField(label=_('Template Source'),
choices=[('url', _('URL')),
('file', _('File')),
('raw', _('Direct Input'))],
widget=forms.Select(attrs={
'class': 'switchable',
'data-slug': 'source'}))
choices=choices,
widget=forms.Select(attrs=attributes))
attributes = create_upload_form_attributes(
'template',
'file',
_('Template File'))
template_upload = forms.FileField(
label=_('Template File'),
help_text=_('A local template to upload.'),
widget=forms.FileInput(attrs={'class': 'switched',
'data-switch-on': 'source',
'data-source-file': _('Template File')}),
widget=forms.FileInput(attrs=attributes),
required=False)
attributes = create_upload_form_attributes(
'template',
'url',
_('Template URL'))
template_url = forms.URLField(
label=_('Template URL'),
help_text=_('An external (HTTP) URL to load the template from.'),
widget=forms.TextInput(attrs={'class': 'switched',
'data-switch-on': 'source',
'data-source-url': _('Template URL')}),
widget=forms.TextInput(attrs=attributes),
required=False)
attributes = create_upload_form_attributes(
'template',
'raw',
_('Template Data'))
template_data = forms.CharField(
label=_('Template Data'),
help_text=_('The raw contents of the template.'),
widget=forms.widgets.Textarea(attrs={
'class': 'switched',
'data-switch-on': 'source',
'data-source-raw': _('Template Data')}),
widget=forms.widgets.Textarea(attrs=attributes),
required=False)
attributes = {'data-slug': 'envsource', 'class': 'switchable'}
environment_source = forms.ChoiceField(
label=_('Environment Source'),
choices=choices,
widget=forms.Select(attrs=attributes),
required=False)
attributes = create_upload_form_attributes(
'env',
'file',
_('Environment File'))
environment_upload = forms.FileField(
label=_('Environment File'),
help_text=_('A local environment to upload.'),
widget=forms.FileInput(attrs=attributes),
required=False)
attributes = create_upload_form_attributes(
'env',
'url',
_('Environment URL'))
environment_url = forms.URLField(
label=_('Environment URL'),
help_text=_('An external (HTTP) URL to load the environment from.'),
widget=forms.TextInput(attrs=attributes),
required=False)
attributes = create_upload_form_attributes(
'env',
'raw',
_('Environment Data'))
environment_data = forms.CharField(
label=_('Environment Data'),
help_text=_('The raw contents of the environment file.'),
widget=forms.widgets.Textarea(attrs=attributes),
required=False)
def __init__(self, *args, **kwargs):
@ -96,35 +158,13 @@ class TemplateForm(forms.SelfHandlingForm):
def clean(self):
cleaned = super(TemplateForm, self).clean()
template_url = cleaned.get('template_url')
template_data = cleaned.get('template_data')
files = self.request.FILES
has_upload = 'template_upload' in files
# Uploaded file handler
if has_upload and not template_url:
log_template_name = self.request.FILES['template_upload'].name
LOG.info('got upload %s' % log_template_name)
tpl = self.request.FILES['template_upload'].read()
if tpl.startswith('{'):
try:
json.loads(tpl)
except Exception as e:
msg = _('There was a problem parsing the template: %s') % e
raise forms.ValidationError(msg)
cleaned['template_data'] = tpl
# URL handler
elif template_url and (has_upload or template_data):
msg = _('Please specify a template using only one source method.')
raise forms.ValidationError(msg)
# Check for raw template input
elif not template_url and not template_data:
msg = _('You must specify a template via one of the '
'available sources.')
raise forms.ValidationError(msg)
self.clean_uploaded_files('template', _('template'), cleaned, files)
self.clean_uploaded_files('environment',
_('environment'),
cleaned,
files)
# Validate the template and get back the params.
kwargs = {}
@ -145,8 +185,62 @@ class TemplateForm(forms.SelfHandlingForm):
return cleaned
def clean_uploaded_files(self, prefix, field_label, cleaned, files):
"""Cleans Template & Environment data from form upload.
Does some of the crunchy bits for processing uploads vs raw
data depending on what the user specified. Identical process
for environment data & template data.
:type prefix: str
:param prefix: prefix (environment, template) of field
:type field_label: str
:param field_label: translated prefix str for messages
:type input_type: dict
:param prefix: existing cleaned fields from form
:rtype: dict
:return: cleaned dict including environment & template data
"""
upload_str = prefix + "_upload"
data_str = prefix + "_data"
url = cleaned.get(prefix + '_url')
data = cleaned.get(prefix + '_data')
has_upload = upload_str in files
# Uploaded file handler
if has_upload and not url:
log_template_name = files[upload_str].name
LOG.info('got upload %s' % log_template_name)
tpl = files[upload_str].read()
if tpl.startswith('{'):
try:
json.loads(tpl)
except Exception as e:
msg = _('There was a problem parsing the'
' %(prefix)s: %(error)s')
msg = msg % {'prefix': prefix, 'error': e}
raise forms.ValidationError(msg)
cleaned[data_str] = tpl
# URL handler
elif url and (has_upload or data):
msg = _('Please specify a %s using only one source method.')
msg = msg % field_label
raise forms.ValidationError(msg)
elif prefix == 'template':
# Check for raw template input - blank environment allowed
if not url and not data:
msg = _('You must specify a template via one of the '
'available sources.')
raise forms.ValidationError(msg)
def create_kwargs(self, data):
kwargs = {'parameters': data['template_validate'],
'environment_data': data['environment_data'],
'environment_url': data['environment_url'],
'template_data': data['template_data'],
'template_url': data['template_url']}
if data.get('stack_id'):
@ -190,6 +284,12 @@ class CreateStackForm(forms.SelfHandlingForm):
template_url = forms.CharField(
widget=forms.widgets.HiddenInput,
required=False)
environment_data = forms.CharField(
widget=forms.widgets.HiddenInput,
required=False)
environment_url = forms.CharField(
widget=forms.widgets.HiddenInput,
required=False)
parameters = forms.CharField(
widget=forms.widgets.HiddenInput,
required=True)
@ -283,6 +383,11 @@ class CreateStackForm(forms.SelfHandlingForm):
else:
fields['template_url'] = data.get('template_url')
if data.get('environment_data'):
fields['environment'] = data.get('environment_data')
elif data.get('environment_url'):
fields['environment_url'] = data.get('environment_url')
try:
api.heat.stack_create(self.request, **fields)
messages.success(request, _("Stack creation started."))

View File

@ -14,6 +14,7 @@
import json
from django.core import exceptions
from django.core.urlresolvers import reverse
from django import http
@ -155,6 +156,60 @@ class StackTests(test.TestCase):
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.heat: ('stack_create', 'template_validate')})
def test_launch_stackwith_environment(self):
template = self.stack_templates.first()
environment = self.stack_environments.first()
stack = self.stacks.first()
api.heat.template_validate(IsA(http.HttpRequest),
template=template.data) \
.AndReturn(json.loads(template.validate))
api.heat.stack_create(IsA(http.HttpRequest),
stack_name=stack.stack_name,
timeout_mins=60,
disable_rollback=True,
template=template.data,
environment=environment.data,
parameters=IsA(dict),
password='password')
self.mox.ReplayAll()
url = reverse('horizon:project:stacks:select_template')
res = self.client.get(url)
self.assertTemplateUsed(res, 'project/stacks/select_template.html')
form_data = {'template_source': 'raw',
'template_data': template.data,
'environment_source': 'raw',
'environment_data': environment.data,
'method': forms.TemplateForm.__name__}
res = self.client.post(url, form_data)
self.assertTemplateUsed(res, 'project/stacks/create.html')
url = reverse('horizon:project:stacks:launch')
form_data = {'template_source': 'raw',
'template_data': template.data,
'environment_source': 'raw',
'environment_data': environment.data,
'password': 'password',
'parameters': template.validate,
'stack_name': stack.stack_name,
"timeout_mins": 60,
"disable_rollback": True,
"__param_DBUsername": "admin",
"__param_LinuxDistribution": "F17",
"__param_InstanceType": "m1.small",
"__param_KeyName": "test",
"__param_DBPassword": "admin",
"__param_DBRootPassword": "admin",
"__param_DBName": "wordpress",
'method': forms.CreateStackForm.__name__}
res = self.client.post(url, form_data)
self.assertRedirectsNoFollow(res, INDEX_URL)
@test.create_stubs({api.heat: ('stack_update', 'stack_get',
'template_get', 'template_validate')})
def test_edit_stack_template(self):
@ -258,6 +313,14 @@ class StackTests(test.TestCase):
class TemplateFormTests(test.TestCase):
class SimpleFile(object):
def __init__(self, name, data):
self.name = name
self.data = data
def read(self):
return self.data
def test_exception_to_validation(self):
json_error = """{
"code": 400,
@ -298,3 +361,67 @@ malformed or otherwise incorrect.
msg = forms.exception_to_validation_msg(json_error)
self.assertIsNone(msg)
def test_create_upload_form_attributes(self):
attrs = forms.create_upload_form_attributes(
'env', 'url', 'Environment')
self.assertEqual(attrs['data-envsource-url'], 'Environment')
def test_clean_file_upload_form_url(self):
kwargs = {'next_view': 'Launch Stack'}
t = forms.TemplateForm({}, **kwargs)
precleaned = {
'template_url': 'http://templateurl.com',
}
t.clean_uploaded_files('template', 'template', precleaned, {})
self.assertEqual(precleaned['template_url'], 'http://templateurl.com')
def test_clean_file_upload_form_multiple(self):
kwargs = {'next_view': 'Launch Stack'}
t = forms.TemplateForm({}, **kwargs)
precleaned = {
'template_url': 'http://templateurl.com',
'template_data': 'http://templateurl.com',
}
self.assertRaises(
exceptions.ValidationError,
t.clean_uploaded_files,
'template',
'template',
precleaned,
{})
def test_clean_file_upload_form_invalid_json(self):
kwargs = {'next_view': 'Launch Stack'}
t = forms.TemplateForm({}, **kwargs)
precleaned = {
'template_data': 'http://templateurl.com',
}
json_str = '{notvalidjson::::::json/////json'
files = {'template_upload':
self.SimpleFile('template_name', json_str)}
self.assertRaises(
exceptions.ValidationError,
t.clean_uploaded_files,
'template',
'template',
precleaned,
files)
def test_clean_file_upload_form_valid_data(self):
kwargs = {'next_view': 'Launch Stack'}
t = forms.TemplateForm({}, **kwargs)
precleaned = {
'template_data': 'http://templateurl.com',
}
json_str = '{"isvalid":"json"}'
files = {'template_upload':
self.SimpleFile('template_name', json_str)}
t.clean_uploaded_files('template', 'template', precleaned, files)
self.assertEqual(
json_str,
precleaned['template_data'])

View File

@ -107,14 +107,19 @@ class CreateStackView(forms.ModalFormView):
def get_initial(self):
initial = {}
if 'template_data' in self.kwargs:
initial['template_data'] = self.kwargs['template_data']
if 'template_url' in self.kwargs:
initial['template_url'] = self.kwargs['template_url']
self.load_kwargs(initial)
if 'parameters' in self.kwargs:
initial['parameters'] = json.dumps(self.kwargs['parameters'])
return initial
def load_kwargs(self, initial):
# load the "passed through" data from template form
for prefix in ('template', 'environment'):
for suffix in ('_data', '_url'):
key = prefix + suffix
if key in self.kwargs:
initial[key] = self.kwargs[key]
def get_form_kwargs(self):
kwargs = super(CreateStackView, self).get_form_kwargs()
if 'parameters' in self.kwargs: