Heat Stack update view/form
2 Part view for updating Heat Templates. The first page allows you to select a new template for your stack. The second allows you to set new template parameters for your stack. Like the launch stack workflow, this is not a horizon workflow, but two separate forms. Implements: blueprint heat-stack-update Change-Id: I2854e9e4bb578be5187ef962808b93f11ac6b1f1
This commit is contained in:
parent
9f8a5eb349
commit
015aff2630
@ -64,10 +64,20 @@ def stack_get(request, stack_id):
|
||||
return heatclient(request).stacks.get(stack_id)
|
||||
|
||||
|
||||
def template_get(request, stack_id):
|
||||
return heatclient(request).stacks.template(stack_id)
|
||||
|
||||
|
||||
def stack_create(request, password=None, **kwargs):
|
||||
return heatclient(request, password).stacks.create(**kwargs)
|
||||
|
||||
|
||||
def stack_update(request, stack_id, **kwargs):
|
||||
if kwargs.get('password'):
|
||||
kwargs.pop('password')
|
||||
return heatclient(request).stacks.update(stack_id, **kwargs)
|
||||
|
||||
|
||||
def events_list(request, stack_name):
|
||||
return heatclient(request).events.list(stack_name)
|
||||
|
||||
|
@ -145,18 +145,39 @@ class TemplateForm(forms.SelfHandlingForm):
|
||||
|
||||
return cleaned
|
||||
|
||||
def handle(self, request, data):
|
||||
def create_kwargs(self, data):
|
||||
kwargs = {'parameters': data['template_validate'],
|
||||
'template_data': data['template_data'],
|
||||
'template_url': data['template_url']}
|
||||
if data.get('stack_id'):
|
||||
kwargs['stack_id'] = data['stack_id']
|
||||
return kwargs
|
||||
|
||||
def handle(self, request, data):
|
||||
kwargs = self.create_kwargs(data)
|
||||
# NOTE (gabriel): This is a bit of a hack, essentially rewriting this
|
||||
# request so that we can chain it as an input to the next view...
|
||||
# but hey, it totally works.
|
||||
request.method = 'GET'
|
||||
|
||||
return self.next_view.as_view()(request, **kwargs)
|
||||
|
||||
|
||||
class StackCreateForm(forms.SelfHandlingForm):
|
||||
class ChangeTemplateForm(TemplateForm):
|
||||
class Meta:
|
||||
name = _('Edit Template')
|
||||
help_text = _('From here you can select a new template to re-launch '
|
||||
'a stack.')
|
||||
stack_id = forms.CharField(label=_('Stack ID'),
|
||||
widget=forms.widgets.HiddenInput,
|
||||
required=True)
|
||||
stack_name = forms.CharField(label=_('Stack Name'),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}
|
||||
))
|
||||
|
||||
|
||||
class CreateStackForm(forms.SelfHandlingForm):
|
||||
|
||||
param_prefix = '__param_'
|
||||
|
||||
@ -193,11 +214,13 @@ class StackCreateForm(forms.SelfHandlingForm):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
parameters = kwargs.pop('parameters')
|
||||
super(StackCreateForm, self).__init__(*args, **kwargs)
|
||||
# special case: load template data from API, not passed in params
|
||||
if(kwargs.get('validate_me')):
|
||||
parameters = kwargs.pop('validate_me')
|
||||
super(CreateStackForm, self).__init__(*args, **kwargs)
|
||||
self._build_parameter_fields(parameters)
|
||||
|
||||
def _build_parameter_fields(self, template_validate):
|
||||
|
||||
self.fields['password'] = forms.CharField(
|
||||
label=_('Password for user "%s"') % self.request.user.username,
|
||||
help_text=_('This is required for operations to be performed '
|
||||
@ -267,3 +290,49 @@ class StackCreateForm(forms.SelfHandlingForm):
|
||||
except Exception as e:
|
||||
msg = exception_to_validation_msg(e)
|
||||
exceptions.handle(request, msg or _('Stack creation failed.'))
|
||||
|
||||
|
||||
class EditStackForm(CreateStackForm):
|
||||
|
||||
class Meta:
|
||||
name = _('Update Stack Parameters')
|
||||
|
||||
stack_id = forms.CharField(label=_('Stack ID'),
|
||||
widget=forms.widgets.HiddenInput,
|
||||
required=True)
|
||||
stack_name = forms.CharField(label=_('Stack Name'),
|
||||
widget=forms.TextInput(
|
||||
attrs={'readonly': 'readonly'}
|
||||
))
|
||||
|
||||
@sensitive_variables('password')
|
||||
def handle(self, request, data):
|
||||
prefix_length = len(self.param_prefix)
|
||||
params_list = [(k[prefix_length:], v) for (k, v) in data.iteritems()
|
||||
if k.startswith(self.param_prefix)]
|
||||
|
||||
stack_id = data.get('stack_id')
|
||||
fields = {
|
||||
'stack_name': data.get('stack_name'),
|
||||
'timeout_mins': data.get('timeout_mins'),
|
||||
'disable_rollback': not(data.get('enable_rollback')),
|
||||
'parameters': dict(params_list),
|
||||
'password': data.get('password')
|
||||
}
|
||||
|
||||
# if the user went directly to this form, resubmit the existing
|
||||
# template data. otherwise, submit what they had from the first form
|
||||
if data.get('template_data'):
|
||||
fields['template'] = data.get('template_data')
|
||||
elif data.get('template_url'):
|
||||
fields['template_url'] = data.get('template_url')
|
||||
elif data.get('parameters'):
|
||||
fields['template'] = data.get('parameters')
|
||||
|
||||
try:
|
||||
api.heat.stack_update(self.request, stack_id=stack_id, **fields)
|
||||
messages.success(request, _("Stack update started."))
|
||||
return True
|
||||
except Exception as e:
|
||||
msg = exception_to_validation_msg(e)
|
||||
exceptions.handle(request, msg or _('Stack update failed.'))
|
||||
|
@ -12,9 +12,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core import urlresolvers
|
||||
from django.http import Http404 # noqa
|
||||
from django.template.defaultfilters import timesince # noqa
|
||||
from django.template.defaultfilters import title # noqa
|
||||
from django.utils.http import urlencode # noqa
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import messages
|
||||
@ -34,6 +36,16 @@ class LaunchStack(tables.LinkAction):
|
||||
classes = ("btn-create", "ajax-modal")
|
||||
|
||||
|
||||
class ChangeStackTemplate(tables.LinkAction):
|
||||
name = "edit"
|
||||
verbose_name = _("Change Stack Template")
|
||||
url = "horizon:project:stacks:change_template"
|
||||
classes = ("ajax-modal", "btn-edit")
|
||||
|
||||
def get_link_url(self, stack):
|
||||
return urlresolvers.reverse(self.url, args=[stack.id])
|
||||
|
||||
|
||||
class DeleteStack(tables.BatchAction):
|
||||
name = "delete"
|
||||
action_present = _("Delete")
|
||||
@ -100,7 +112,8 @@ class StacksTable(tables.DataTable):
|
||||
status_columns = ["status", ]
|
||||
row_class = StacksUpdateRow
|
||||
table_actions = (LaunchStack, DeleteStack,)
|
||||
row_actions = (DeleteStack, )
|
||||
row_actions = (DeleteStack,
|
||||
ChangeStackTemplate)
|
||||
|
||||
|
||||
class EventsTable(tables.DataTable):
|
||||
|
@ -0,0 +1,28 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}select_template{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:stacks:change_template' stack.id%}{% endblock %}
|
||||
{% block form_attrs %}enctype="multipart/form-data"{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Select Template" %}{% endblock %}
|
||||
{% block modal_id %}select_template_modal{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "Use one of the available template source options to specify the template to be used in creating this stack." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Next" %}" />
|
||||
<a href="{% url 'horizon:project:stacks:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,26 @@
|
||||
{% extends "horizon/common/_modal_form.html" %}
|
||||
{% load i18n %}
|
||||
{% load url from future %}
|
||||
|
||||
{% block form_id %}update_stack{% endblock %}
|
||||
{% block form_action %}{% url 'horizon:project:stacks:edit_stack' stack.id %}{% endblock %}
|
||||
|
||||
{% block modal-header %}{% trans "Update Stack Parameters" %}{% endblock %}
|
||||
{% block modal_id %}update_stack_modal{% endblock %}
|
||||
|
||||
{% block modal-body %}
|
||||
<div class="left">
|
||||
<fieldset>
|
||||
{% include "horizon/common/_form_fields.html" %}
|
||||
</fieldset>
|
||||
</div>
|
||||
<div class="right">
|
||||
<h3>{% trans "Description" %}:</h3>
|
||||
<p>{% trans "Update a stack with the provided values. Please note that any encrypted parameters, such as passwords, will be reset to default if you don't change them here." %}</p>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block modal-footer %}
|
||||
<input class="btn btn-primary pull-right" type="submit" value="{% trans "Update" %}" />
|
||||
<a href="{% url 'horizon:project:stacks:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
|
||||
{% endblock %}
|
@ -0,0 +1,12 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Change Template" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Change Template") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/stacks/_change_template.html' %}
|
||||
{% endblock %}
|
||||
|
@ -0,0 +1,11 @@
|
||||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Update Stack Parameters" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=_("Update Stack") %}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block main %}
|
||||
{% include 'project/stacks/_update.html' %}
|
||||
{% endblock %}
|
@ -151,7 +151,76 @@ class StackTests(test.TestCase):
|
||||
"__param_DBPassword": "admin",
|
||||
"__param_DBRootPassword": "admin",
|
||||
"__param_DBName": "wordpress",
|
||||
'method': forms.StackCreateForm.__name__}
|
||||
'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):
|
||||
template = self.stack_templates.first()
|
||||
stack = self.stacks.first()
|
||||
|
||||
# GET to template form
|
||||
api.heat.stack_get(IsA(http.HttpRequest),
|
||||
stack.id).AndReturn(stack)
|
||||
# POST template form, validation
|
||||
api.heat.template_validate(IsA(http.HttpRequest),
|
||||
template=template.data) \
|
||||
.AndReturn(json.loads(template.validate))
|
||||
|
||||
# GET to edit form
|
||||
api.heat.stack_get(IsA(http.HttpRequest),
|
||||
stack.id).AndReturn(stack)
|
||||
api.heat.template_get(IsA(http.HttpRequest),
|
||||
stack.id) \
|
||||
.AndReturn(json.loads(template.validate))
|
||||
|
||||
# POST to edit form
|
||||
api.heat.stack_get(IsA(http.HttpRequest),
|
||||
stack.id).AndReturn(stack)
|
||||
|
||||
fields = {
|
||||
'stack_name': stack.stack_name,
|
||||
'disable_rollback': True,
|
||||
'timeout_mins': 61,
|
||||
'password': 'password',
|
||||
'template': IsA(unicode),
|
||||
'parameters': IsA(dict)
|
||||
}
|
||||
api.heat.stack_update(IsA(http.HttpRequest),
|
||||
stack_id=stack.id,
|
||||
**fields)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
url = reverse('horizon:project:stacks:change_template',
|
||||
args=[stack.id])
|
||||
res = self.client.get(url)
|
||||
self.assertTemplateUsed(res, 'project/stacks/change_template.html')
|
||||
|
||||
form_data = {'template_source': 'raw',
|
||||
'template_data': template.data,
|
||||
'method': forms.ChangeTemplateForm.__name__}
|
||||
res = self.client.post(url, form_data)
|
||||
|
||||
url = reverse('horizon:project:stacks:edit_stack',
|
||||
args=[stack.id, ])
|
||||
form_data = {'template_source': 'raw',
|
||||
'template_data': template.data,
|
||||
'password': 'password',
|
||||
'parameters': template.validate,
|
||||
'stack_name': stack.stack_name,
|
||||
'stack_id': stack.id,
|
||||
"timeout_mins": 61,
|
||||
"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.EditStackForm.__name__}
|
||||
res = self.client.post(url, form_data)
|
||||
self.assertRedirectsNoFollow(res, INDEX_URL)
|
||||
|
||||
@ -177,7 +246,7 @@ class StackTests(test.TestCase):
|
||||
"__param_DBPassword": "admin",
|
||||
"__param_DBRootPassword": "admin",
|
||||
"__param_DBName": "wordpress",
|
||||
'method': forms.StackCreateForm.__name__}
|
||||
'method': forms.CreateStackForm.__name__}
|
||||
|
||||
res = self.client.post(url, form_data)
|
||||
error = ('Name must start with a letter and may only contain letters, '
|
||||
|
@ -26,6 +26,10 @@ urlpatterns = patterns(
|
||||
url(r'^launch$', views.CreateStackView.as_view(), name='launch'),
|
||||
url(r'^stack/(?P<stack_id>[^/]+)/$',
|
||||
views.DetailView.as_view(), name='detail'),
|
||||
url(r'^(?P<stack_id>[^/]+)/change_template$',
|
||||
views.ChangeTemplateView.as_view(), name='change_template'),
|
||||
url(r'^(?P<stack_id>[^/]+)/edit_stack$',
|
||||
views.EditStackView.as_view(), name='edit_stack'),
|
||||
url(r'^stack/(?P<stack_id>[^/]+)/(?P<resource_name>[^/]+)/$',
|
||||
views.ResourceView.as_view(), name='resource'),
|
||||
url(r'^get_d3_data/(?P<stack_id>[^/]+)/$',
|
||||
|
@ -67,8 +67,41 @@ class SelectTemplateView(forms.ModalFormView):
|
||||
return kwargs
|
||||
|
||||
|
||||
class ChangeTemplateView(forms.ModalFormView):
|
||||
form_class = project_forms.ChangeTemplateForm
|
||||
template_name = 'project/stacks/change_template.html'
|
||||
success_url = reverse_lazy('horizon:project:stacks:edit_stack')
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(ChangeTemplateView, self).get_context_data(**kwargs)
|
||||
context['stack'] = self.get_object()
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self):
|
||||
stack_id = self.kwargs['stack_id']
|
||||
try:
|
||||
self._stack = api.heat.stack_get(self.request, stack_id)
|
||||
except Exception:
|
||||
msg = _("Unable to retrieve stack.")
|
||||
redirect = reverse('horizon:project:stacks:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._stack
|
||||
|
||||
def get_initial(self):
|
||||
stack = self.get_object()
|
||||
return {'stack_id': stack.id,
|
||||
'stack_name': stack.stack_name
|
||||
}
|
||||
|
||||
def get_form_kwargs(self):
|
||||
kwargs = super(ChangeTemplateView, self).get_form_kwargs()
|
||||
kwargs['next_view'] = EditStackView
|
||||
return kwargs
|
||||
|
||||
|
||||
class CreateStackView(forms.ModalFormView):
|
||||
form_class = project_forms.StackCreateForm
|
||||
form_class = project_forms.CreateStackForm
|
||||
template_name = 'project/stacks/create.html'
|
||||
success_url = reverse_lazy('horizon:project:stacks:index')
|
||||
|
||||
@ -92,6 +125,42 @@ class CreateStackView(forms.ModalFormView):
|
||||
return kwargs
|
||||
|
||||
|
||||
# edit stack parameters, coming from template selector
|
||||
class EditStackView(CreateStackView):
|
||||
form_class = project_forms.EditStackForm
|
||||
template_name = 'project/stacks/update.html'
|
||||
success_url = reverse_lazy('horizon:project:stacks:index')
|
||||
|
||||
def get_initial(self):
|
||||
initial = super(EditStackView, self).get_initial()
|
||||
|
||||
initial['stack'] = self.get_object()['stack']
|
||||
if initial['stack']:
|
||||
initial['stack_id'] = initial['stack'].id
|
||||
initial['stack_name'] = initial['stack'].stack_name
|
||||
|
||||
return initial
|
||||
|
||||
def get_context_data(self, **kwargs):
|
||||
context = super(EditStackView, self).get_context_data(**kwargs)
|
||||
context['stack'] = self.get_object()['stack']
|
||||
return context
|
||||
|
||||
@memoized.memoized_method
|
||||
def get_object(self):
|
||||
stack_id = self.kwargs['stack_id']
|
||||
try:
|
||||
stack = {}
|
||||
stack['stack'] = api.heat.stack_get(self.request, stack_id)
|
||||
stack['template'] = api.heat.template_get(self.request, stack_id)
|
||||
self._stack = stack
|
||||
except Exception:
|
||||
msg = _("Unable to retrieve stack.")
|
||||
redirect = reverse('horizon:project:stacks:index')
|
||||
exceptions.handle(self.request, msg, redirect=redirect)
|
||||
return self._stack
|
||||
|
||||
|
||||
class DetailView(tabs.TabView):
|
||||
tab_group_class = project_tabs.StackDetailTabs
|
||||
template_name = 'project/stacks/detail.html'
|
||||
|
@ -24,6 +24,35 @@ class HeatApiTests(test.APITestCase):
|
||||
heatclient.stacks = self.mox.CreateMockAnything()
|
||||
heatclient.stacks.list().AndReturn(iter(api_stacks))
|
||||
self.mox.ReplayAll()
|
||||
|
||||
stacks = api.heat.stacks_list(self.request)
|
||||
self.assertItemsEqual(stacks, api_stacks)
|
||||
|
||||
def test_template_get(self):
|
||||
api_stacks = self.stacks.list()
|
||||
stack_id = api_stacks[0].id
|
||||
mock_data_template = self.stack_templates.list()[0]
|
||||
|
||||
heatclient = self.stub_heatclient()
|
||||
heatclient.stacks = self.mox.CreateMockAnything()
|
||||
heatclient.stacks.template(stack_id).AndReturn(mock_data_template)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
template = api.heat.template_get(self.request, stack_id)
|
||||
self.assertEqual(template.data, mock_data_template.data)
|
||||
|
||||
def test_stack_update(self):
|
||||
api_stacks = self.stacks.list()
|
||||
stack = api_stacks[0]
|
||||
stack_id = stack.id
|
||||
|
||||
heatclient = self.stub_heatclient()
|
||||
heatclient.stacks = self.mox.CreateMockAnything()
|
||||
form_data = {'timeout_mins': 600}
|
||||
heatclient.stacks.update(stack_id, **form_data).AndReturn(stack)
|
||||
self.mox.ReplayAll()
|
||||
|
||||
returned_stack = api.heat.stack_update(self.request,
|
||||
stack_id,
|
||||
**form_data)
|
||||
from heatclient.v1 import stacks
|
||||
self.assertIsInstance(returned_stack, stacks.Stack)
|
||||
|
@ -326,6 +326,17 @@ def data(TEST):
|
||||
"05b4f39f-ea96-4d91-910c-e758c078a089",
|
||||
"rel": "self"
|
||||
}],
|
||||
"parameters": {
|
||||
'DBUsername': '******',
|
||||
'InstanceType': 'm1.small',
|
||||
'AWS::StackId':
|
||||
'arn:openstack:heat::2ce287:stacks/teststack/88553ec',
|
||||
'DBRootPassword': '******',
|
||||
'AWS::StackName': 'teststack',
|
||||
'DBPassword': '******',
|
||||
'AWS::Region': 'ap-southeast-1',
|
||||
'DBName': u'wordpress'
|
||||
},
|
||||
"stack_status_reason": "Stack successfully created",
|
||||
"stack_name": "stack-test",
|
||||
"creation_time": "2013-04-22T00:11:39Z",
|
||||
|
Loading…
Reference in New Issue
Block a user