Commit d1030b90 by Karsa Zoltán István

add cloudinit navbar and save options

parent 9d86cfb8
......@@ -569,9 +569,6 @@ class TemplateForm(forms.ModelForm):
self.fields['raw_data'].validators.append(domain_validator)
self.fields['ci_user_data'].validators.append(user_data_validator)
self.fields['ci_meta_data'].validators.append(meta_data_validator)
def clean_owner(self):
if self.instance.pk is not None:
return User.objects.get(pk=self.instance.owner.pk)
......@@ -1537,25 +1534,23 @@ class RawDataForm(forms.ModelForm):
class CIDataForm(forms.ModelForm):
ci_meta_data = forms.CharField(disabled=True, help_text='meta-data',
validators=[meta_data_validator],
ci_meta_data = forms.CharField(help_text='meta-data',
widget=forms.Textarea(attrs={'rows': 5}),
required=False)
ci_user_data = forms.CharField(disabled=True, help_text='user-data',
validators=[user_data_validator],
widget=forms.Textarea(attrs={'rows': 5}),
ci_user_data = forms.CharField(help_text='user-data',
widget=forms.Textarea(attrs={'rows': 12}),
required=False)
def clean(self):
data = self.cleaned_data
meta_data_validator(self.instance, data['ci_meta_data'])
user_data_validator(self.instance, data['ci_user_data'])
return super().clean()
class Meta:
model = Instance
fields = ('ci_meta_data', 'ci_user_data',)
@property
def helper(self):
helper = FormHelper()
helper.form_show_labels = False
return helper
class GroupPermissionForm(forms.ModelForm):
permissions = forms.ModelMultipleChoiceField(
......
var Websock_native; // not sure
$(function() {
/* */
$('#vm-details-cidata-save').click(function(e) {
$.ajax({
type: 'POST',
url: $(this).parents("form").prop('action'),
data: $('#resource-cidata-form').serialize(),
success: function(data, textStatus, xhr) {
if(data.success) {
$('a[href="#activity"]').trigger("click");
} else {
addMessage(data.messages.join("<br />"), "danger");
}
$("#vm-details-resources-save i").removeClass('fa-refresh fa-spin').addClass("fa-floppy-o");
},
error: function(xhr, textStatus, error) {
$("#vm-details-resources-save i").removeClass('fa-refresh fa-spin').addClass("fa-floppy-o");
if (xhr.status == 500) {
addMessage("500 Internal Server Error", "danger");
} else {
addMessage(xhr.status + " Unknown Error", "danger");
}
}
});
e.preventDefault()
})
/* save resources */
$('#vm-details-resources-save').click(function(e) {
var error = false;
......
......@@ -9,17 +9,17 @@
<div class="card card-body">
<b> {% trans "Use Jinja2 template syntax, values: " %}</b>
<ul>
<li>{% trans "<strong>hostname</strong> - vm hostname" %}</li>
<li>{% trans "<strong>sysuser</strong> - vm system default user (cloud)" %}</li>
<li>{% trans "<strong>password</strong> - default user sha512 password" %}</li>
<li>{% trans "<strong>owner</strong> - vm owner django username" %}</li>
<li>{% trans "<strong>acl.allusers</strong> - get all associated user's username to vm (list)" %}</li>
<li>{% trans "<strong>acl.operators</strong> - get all associated operator level user's username to vm (list)" %}</li>
<li>{% trans "<strong>acl.users</strong> - get all associated operator user user's username to vm (list)" %}</li>
<li>{% trans "<strong>net.ipv4</strong> - default host ip address" %}</li>
<li>{% trans "<strong>net.ipv6</strong> - default host ipv6 address" %}</li>
<li>{% trans "<strong>net.[vlan_name].ipv4/ipv6</strong> - associated vlan ([vlan_name]) host ip addresses, example: when vm associated to vlan 'public' and vlan 'internal', we can get these ips: {{net.public.ipv4}} and {{net.internal.ipv4}}" %}</li>
<li>{% trans "<strong>ci.rndstr(len: int)</strong> - function: make random string with 'len' charachters lenght" %}</li>
<li>{% trans "<code>hostname</code> - vm hostname" %}</li>
<li>{% trans "<code>sysuser</code> - vm system default user (cloud)" %}</li>
<li>{% trans "<code>password</code> - default user sha512 password" %}</li>
<li>{% trans "<code>owner</code> - vm owner django username" %}</li>
<li>{% trans "<code>acl.allusers</code> - get all associated user's username to vm (list)" %}</li>
<li>{% trans "<code>acl.operators</code> - get all associated operator level user's username to vm (list)" %}</li>
<li>{% trans "<code>acl.users</code> - get all associated user level user's username to vm (list)" %}</li>
<li>{% trans "<code>net.ipv4</code> - default host ip address" %}</li>
<li>{% trans "<code>net.ipv6</code> - default host ipv6 address" %}</li>
<li>{% trans "<code>net.vlans</code> - associated vlans: list of objects (name: vlan name, ipv4/ipv6: host ip in vlan)" %}</li>
<li>{% trans "<code>ci.rndstr(len: int)</code> - function: make random string with 'len' charachters lenght" %}</li>
</ul>
<b>Example:</b>
<pre>{% verbatim %}
......
......@@ -28,6 +28,9 @@
{{ form.system|as_crispy_field }}
<hr/>
{% include "dashboard/_ci-data-help.html" %}
<div class="alert alert-warning">
{% trans "Template validition only works on a real virtual machine" %}
</div>
{{ form.cloud_init|as_crispy_field }}
{{ form.ci_meta_data|as_crispy_field }}
{{ form.ci_user_data|as_crispy_field }}
......
......@@ -54,6 +54,9 @@
{{ form.has_agent|as_crispy_field }}
<hr/>
{% include "dashboard/_ci-data-help.html" %}
<div class="alert alert-warning">
{% trans "Template validition only works on a real virtual machine" %}
</div>
{{ form.cloud_init|as_crispy_field }}
{{ form.ci_meta_data|as_crispy_field }}
{{ form.ci_user_data|as_crispy_field }}
......
......@@ -227,6 +227,11 @@
{% trans "Network" %}</a>
</li>
<li>
<a href="#cloudinit" data-toggle="pill" data-target="#_cloudinit" class="text-center">
<i class="fa fa-cloud-upload fa-2x"></i><br>
{% trans "Cloud-init" %}</a>
</li>
<li>
<a href="#activity" data-toggle="pill" data-target="#_activity" class="text-center"
data-activity-url="{% url "dashboard.views.vm-activity-list" instance.pk %}">
<i class="fa fa-clock-o fa-2x"></i><br>
......@@ -244,6 +249,8 @@
<hr class="js-hidden"/>
<div class="not-tab-pane" id="_network">{% include "dashboard/vm-detail/network.html" %}</div>
<hr class="js-hidden"/>
<div class="not-tab-pane" id="_cloudinit">{% include "dashboard/vm-detail/cloudinit.html" %}</div>
<hr class="js-hidden"/>
<div class="not-tab-pane" id="_activity">{% include "dashboard/vm-detail/activity.html" %}</div>
<hr class="js-hidden"/>
</div>
......
{% load i18n %}
{% load sizefieldtags %}
{% load crispy_forms_tags %}
{% if instance.cloud_init %}
{% include "dashboard/_ci-data-help.html" %}
<div class="row">
<div class="col-sm-12">
<h3>
{% trans "Cloud-init" %}
</h3>
<form method="POST" action="{{ op.cloudinit_change.get_url }}" id="resource-cidata-form">
{% csrf_token %}
{{ ci_data.ci_meta_data|as_crispy_field }}
{{ ci_data.ci_user_data|as_crispy_field }}
<button type="submit" class="btn btn-success btn-sm change-resources-button"
id="vm-details-cidata-save" data-vm="{{ instance.pk }}"
{% if not save_resources_enabled %}disabled{% endif %}>
<i class="fa fa-floppy-o"></i> {% trans "Validate & Save" %}
</button>
</form>
</div>
</div>
{% else %}
<p>Cloud-init is inactive</p>
{% endif %}
\ No newline at end of file
......@@ -79,20 +79,6 @@
</div>
{% endif %}
<hr />
{% if instance.cloud_init %}
<div class="row">
<div class="col-sm-12">
<h3>
{% trans "Cloud-init" %}
</h3>
{% crispy ci_data %}
</div>
</div>
{% endif %}
{% if user.is_superuser %}
<hr/>
......
......@@ -116,7 +116,8 @@ urlpatterns = [
name='dashboard.views.vm-raw-data'),
url(r'^vm/(?P<pk>\d+)/toggle_tutorial/$', toggle_template_tutorial,
name='dashboard.views.vm-toggle-tutorial'),
#url(r'^vm/(?P<pk>\d+)/cloud_init/$', CIDataUpdate.as_view(),
# name='dashboard.views.cloud-init'),
url(r'^node/list/$', NodeList.as_view(), name='dashboard.views.node-list'),
url(r'^node/(?P<pk>\d+)/$', NodeDetailView.as_view(),
name='dashboard.views.node-detail'),
......
......@@ -17,6 +17,7 @@
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext_lazy as _
import jinja2
from lxml import etree as ET
import logging
......@@ -54,9 +55,9 @@ def domain_validator(value):
raise ValidationError(e.message)
def meta_data_validator(value):
def meta_data_validator(instance, value):
try:
Instance.validate_ci_data(value)
instance.validate_ci_data(value)
except yaml.YAMLError as exc:
if hasattr(exc, 'problem_mark'):
if exc.context != None:
......@@ -68,11 +69,13 @@ def meta_data_validator(value):
str(exc.problem) + '\nPlease correct data and retry.')
else:
raise ValidationError("Something went wrong while parsing yaml file")
except jinja2.exceptions.TemplateError as exc:
raise ValidationError(exc.message)
def user_data_validator(value):
def user_data_validator(instance, value):
try:
Instance.validate_ci_data(value)
instance.validate_ci_data(value)
except yaml.YAMLError as exc:
if hasattr(exc, 'problem_mark'):
if exc.context != None:
......@@ -84,6 +87,8 @@ def user_data_validator(value):
str(exc.problem) + '\nPlease correct data and retry.')
else:
raise ValidationError("Something went wrong while parsing yaml file")
except jinja2.exceptions.TemplateError as exc:
raise ValidationError(exc.message)
def connect_command_template_validator(value):
......
......@@ -550,6 +550,37 @@ class VmSaveView(FormOperationMixin, VmOperationView):
val['clone'] = True
return val
class CIDataUpdate(VmOperationView):
op = 'cloudinit_change'
icon = "cloud-upload"
show_in_toolbar = False
wait_for_result = 0.5
def post(self, request, extra=None, *args, **kwargs):
if extra is None:
extra = {}
instance = get_object_or_404(Instance, pk=kwargs['pk'])
form = CIDataForm(request.POST, instance=instance)
if not form.is_valid():
for f in form.errors:
messages.error(request, "<strong>%s</strong>: %s" % (
f, form.errors[f].as_text()
))
if request.is_ajax(): # this is not too nice
store = messages.get_messages(request)
store.used = True
return JsonResponse({'success': False,
'messages': [str(m) for m in store]})
else:
return HttpResponseRedirect(instance.get_absolute_url() +
"#cloudinit")
else:
extra = form.cleaned_data
return super(CIDataUpdate, self).post(request, extra,
*args, **kwargs)
class VmResourcesChangeView(VmOperationView):
op = 'resources_change'
......@@ -805,6 +836,7 @@ vm_ops = OrderedDict([
('add_port', VmPortAddView),
('renew', VmRenewView),
('resources_change', VmResourcesChangeView),
('cloudinit_change', CIDataUpdate),
('password_reset', VmOperationView.factory(
op='password_reset', icon='unlock', effect='warning',
show_in_toolbar=False, wait_for_result=0.5, with_reload=True)),
......
......@@ -266,6 +266,19 @@ class AclTemplate:
self.users = list(k['username'] for k in self.user_levels if k['level'] == 'user')
class NetTemplate:
class Host:
def __init__(self, net):
self.ipv4 = str(net.host.ipv4)
self.ipv6 = str(net.host.ipv6)
self.name = str(net.vlan.name)
def __init__(self, instance):
self.vlans = list(NetTemplate.Host(net) for net in instance.interface_set.all() if net.host)
self.ipv4 = str(instance.ipv4)
self.ipv6 = str(instance.ipv6)
class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
TimeStampedModel):
......@@ -387,15 +400,10 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
"sysuser": "cloud",
"hostname": self.short_hostname,
"password": sha512_crypt.hash(self.pw),
"net.ipv4": str(self.ipv4),
"net.ipv6": str(self.ipv6),
"owner": str(self.owner.username)
"owner": str(self.owner.username),
"net": NetTemplate(self),
"acl": AclTemplate(self)
}
for net in self.interface_set.all():
if net.host:
datas["net.%s.ipv4" % net.vlan.name] = str(net.host.ipv4)
datas["net.%s.ipv6" % net.vlan.name] = str(net.host.ipv6)
datas['acl'] = AclTemplate(self)
return datas
@property
......@@ -412,10 +420,11 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
template = jinja2.Template(data, trim_blocks=True, lstrip_blocks=True)
return template.render(ci_datas)
@classmethod
def validate_ci_data(cls, meta_data):
# data = re.sub(r'\{\{([a-zA-Z_.0-9()%]+)\}\}', 'ci-data', meta_data)
# yaml.dump(yaml.load(data, Loader=yaml.Loader))
def validate_ci_data(self, data):
ci_datas = self.get_ci_data_dict()
template = jinja2.Template(data, trim_blocks=True, lstrip_blocks=True)
data = template.render(ci_datas)
yaml.dump(yaml.load(data, Loader=yaml.Loader))
return True
def _update_status(self):
......
......@@ -1451,6 +1451,46 @@ class RecoverOperation(InstanceOperation):
@register_operation
class CIDataChangeOperation(InstanceOperation):
id = 'cloudinit_change'
name = _("cloud-init change")
description = _("Change cloud-init data of a stopped virtual machine.")
acl_level = "owner"
required_perms = ('vm.change_resources',)
accept_states = ('STOPPED', 'PENDING')
def _operation(self, user, activity,
ci_meta_data, ci_user_data,
with_shutdown=False, task=None):
logger.debug('save cloud-init: %s \n %s', ci_meta_data, ci_user_data)
if self.instance.status == 'RUNNING' and not with_shutdown:
raise Instance.WrongStateError(self.instance)
try:
self.instance.shutdown(parent_activity=activity, task=task)
except Instance.WrongStateError:
pass
self.instance._update_status()
self.instance.ci_meta_data = ci_meta_data
self.instance.ci_user_data = ci_user_data
self.instance.full_clean()
self.instance.save()
init_disk = None
try:
init_disk = self.instance.disks.get(ci_disk=True)
except:
logger.debug('init-disk')
if self.instance.cloud_init and init_disk != None:
self.instance.remove_disk(parent_activity=activity, disk=init_disk)
return create_readable(ugettext_noop("Meta- and user-data saved. Please redeploy the vm!"))
@register_operation
class ResourcesOperation(InstanceOperation):
id = 'resources_change'
name = _("resources change")
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment