Merge tag '0.10.2'
[ganeti_webmgr.git] / ganeti_web / forms / virtual_machine.py
blob9f91f0233509a2c724d08b2a89ad7341cac309db
1 # Copyright (C) 2010 Oregon State University et al.
3 # This program is free software; you can redistribute it and/or
4 # modify it under the terms of the GNU General Public License
5 # as published by the Free Software Foundation; either version 2
6 # of the License, or (at your option) any later version.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
16 # USA.
18 from django import forms
19 from django.contrib.formtools.wizard.views import CookieWizardView
20 from django.conf import settings
21 from django.core.urlresolvers import reverse
22 from django.core.validators import MaxValueValidator, MinValueValidator
23 from django.db.models import Q
24 from django.forms import (Form, BooleanField, CharField, ChoiceField,
25 IntegerField, ModelChoiceField, ValidationError,
26 MultipleChoiceField, CheckboxSelectMultiple)
27 from django.http import HttpResponseRedirect
28 # Per #6579, do not change this import without discussion.
29 from django.utils import simplejson as json
30 from django.utils.translation import ugettext_lazy as _
32 from object_log.models import LogItem
33 log_action = LogItem.objects.log_action
35 from ganeti_web.backend.queries import cluster_qs_for_user, owner_qs
36 from ganeti_web.backend.templates import template_to_instance
37 from ganeti_web.caps import has_cdrom2, has_balloonmem, has_sharedfile
38 from ganeti_web.constants import (EMPTY_CHOICE_FIELD, HV_DISK_TEMPLATES,
39 HV_NIC_MODES, KVM_CHOICES, HV_USB_MICE,
40 HV_SECURITY_MODELS, KVM_FLAGS,
41 HV_DISK_CACHES, MODE_CHOICES, HVM_CHOICES,
42 VM_HELP, VM_CREATE_HELP, VM_RENAME_HELP,
43 KVM_BOOT_ORDER, HVM_BOOT_ORDER)
44 from ganeti_web.fields import DataVolumeField, MACAddressField
45 from ganeti_web.models import (Cluster, ClusterUser, Node,
46 VirtualMachineTemplate, VirtualMachine)
47 from ganeti_web.utilities import (cluster_default_info, cluster_os_list,
48 get_hypervisor, hv_prettify)
49 from ganeti_web.util.client import (REPLACE_DISK_AUTO, REPLACE_DISK_PRI,
50 REPLACE_DISK_CHG, REPLACE_DISK_SECONDARY)
51 from ganeti_web.views.generic import (LoginRequiredMixin,
52 PermissionRequiredMixin)
54 username_or_mtime = Q(username='') | Q(mtime__isnull=True)
57 class VirtualMachineForm(forms.ModelForm):
58 """
59 Parent class that holds all vm clean methods
60 and shared form fields.
61 """
62 memory = DataVolumeField(label=_('Memory'), min_value=100)
63 minmem = DataVolumeField(label=_('Minimum RAM (MiB)'),
64 required=True, min_value=100)
65 maxmem = DataVolumeField(label=_('Maximum RAM (MiB)'),
66 required=True, min_value=100)
68 class Meta:
69 model = VirtualMachineTemplate
71 def create_disk_fields(self, count):
72 """
73 dynamically add fields for disks
74 """
75 self.disk_fields = range(count)
76 for i in range(count):
77 disk_size = DataVolumeField(min_value=100, required=True,
78 label=_("Disk/%s Size" % i))
79 self.fields['disk_size_%s' % i] = disk_size
81 def create_nic_fields(self, count, defaults=None):
82 """
83 dynamically add fields for nics
84 """
85 self.nic_fields = range(count)
86 for i in range(count):
87 nic_mode = forms.ChoiceField(label=_('NIC/%s Mode' % i),
88 choices=HV_NIC_MODES)
89 nic_link = forms.CharField(label=_('NIC/%s Link' % i),
90 max_length=255)
91 if defaults is not None:
92 nic_link.initial = defaults['nic_link']
93 self.fields['nic_mode_%s' % i] = nic_mode
94 self.fields['nic_link_%s' % i] = nic_link
96 def clean_hostname(self):
97 data = self.cleaned_data
98 hostname = data.get('hostname')
99 cluster = data.get('cluster')
100 if hostname and cluster:
101 # Verify that this hostname is not in use for this cluster. It can
102 # only be reused when recovering a VM that failed to deploy.
104 # Recoveries are only allowed when the user is the owner of the VM
105 try:
106 vm = VirtualMachine.objects.get(cluster=cluster,
107 hostname=hostname)
109 # detect vm that failed to deploy
110 if not vm.pending_delete and vm.template is not None:
111 current_owner = vm.owner.cast()
112 if current_owner == self.owner:
113 data['vm_recovery'] = vm
114 else:
115 msg = _("Owner cannot be changed when recovering a "
116 "failed deployment")
117 self._errors["owner"] = self.error_class([msg])
118 else:
119 raise ValidationError(_("Hostname is already in use for "
120 "this cluster"))
122 except VirtualMachine.DoesNotExist:
123 # doesn't exist, no further checks needed
124 pass
126 # Spaces in hostname will always break things.
127 if ' ' in hostname:
128 self._errors["hostname"] = self.error_class(
129 ["Hostname contains illegal character"])
130 return hostname
132 def clean_vcpus(self):
133 vcpus = self.cleaned_data.get("vcpus", None)
135 if vcpus is not None and vcpus < 1:
136 self._errors["vcpus"] = self.error_class(
137 ["At least one CPU must be present"])
138 else:
139 return vcpus
141 def clean_initrd_path(self):
142 data = self.cleaned_data['initrd_path']
143 if data and not data.startswith('/') and data != 'no_initrd_path':
144 msg = u"%s." % _('This field must start with a "/"')
145 self._errors['initrd_path'] = self.error_class([msg])
146 return data
148 def clean_security_domain(self):
149 data = self.cleaned_data['security_domain']
150 security_model = self.cleaned_data['security_model']
151 msg = None
153 if data and security_model != 'user':
154 msg = u'%s.' % _(
155 'This field can not be set if Security '
156 'Mode is not set to User')
157 elif security_model == 'user':
158 if not data:
159 msg = u'%s.' % _('This field is required')
160 elif not data[0].isalpha():
161 msg = u'%s.' % _('This field must being with '
162 'an alpha character')
164 if msg:
165 self._errors['security_domain'] = self.error_class([msg])
166 return data
168 def clean_vnc_x509_path(self):
169 data = self.cleaned_data['vnc_x509_path']
170 if data and not data.startswith('/'):
171 msg = u'%s,' % _('This field must start with a "/"')
172 self._errors['vnc_x509_path'] = self.error_class([msg])
173 return data
176 def check_quota_modify(form):
177 """ method for validating user is within their quota when modifying """
178 data = form.cleaned_data
179 cluster = form.cluster
180 owner = form.owner
181 vm = form.vm
183 # check quota
184 if owner is not None:
185 start = data['start']
186 quota = cluster.get_quota(owner)
187 if quota.values():
188 used = owner.used_resources(cluster, only_running=True)
190 if (start and quota['ram'] is not None and
191 (used['ram'] + data['memory']-vm.ram) > quota['ram']):
192 del data['memory']
193 q_msg = u"%s" % _("Owner does not have enough ram "
194 "remaining on this cluster. You must "
195 "reduce the amount of ram.")
196 form._errors["ram"] = form.error_class([q_msg])
198 if 'disk_size' in data and data['disk_size']:
199 if quota['disk'] and used['disk'] + data['disk_size'] > \
200 quota['disk']:
201 del data['disk_size']
202 q_msg = u"%s" % _("Owner does not have enough diskspace "
203 "remaining on this cluster.")
204 form._errors["disk_size"] = form.error_class([q_msg])
206 if (start and quota['virtual_cpus'] is not None and
207 (used['virtual_cpus'] + data['vcpus']
208 - vm.virtual_cpus) >
209 quota['virtual_cpus']):
210 del data['vcpus']
211 q_msg = u"%s" % _("Owner does not have enough virtual "
212 "cpus remaining on this cluster. You "
213 "must reduce the amount of virtual "
214 "cpus.")
215 form._errors["vcpus"] = form.error_class([q_msg])
218 class ModifyVirtualMachineForm(VirtualMachineForm):
220 Base modify class.
221 If hvparam_fields (itirable) set on child, then
222 each field on the form will be initialized to the
223 value in vm.info.hvparams
225 always_required = ('vcpus', 'memory')
226 empty_field = EMPTY_CHOICE_FIELD
228 nic_count = forms.IntegerField(initial=1, widget=forms.HiddenInput())
229 os = forms.ChoiceField(label=_('Operating System'), choices=[empty_field])
231 class Meta:
232 model = VirtualMachineTemplate
233 exclude = ('start', 'owner', 'cluster', 'hostname', 'name_check',
234 'iallocator', 'iallocator_hostname', 'disk_template',
235 'pnode', 'nics', 'snode', 'disk_size', 'nic_mode',
236 'template_name', 'hypervisor', 'disks', 'description',
237 'no_install', 'ip_check', 'temporary')
239 def __init__(self, vm, initial=None, *args, **kwargs):
240 super(VirtualMachineForm, self).__init__(initial, *args, **kwargs)
242 if has_balloonmem(vm.cluster):
243 self.always_required = ('vcpus', 'memory', 'minmem')
245 # Set owner on form
246 try:
247 self.owner
248 except AttributeError:
249 self.owner = vm.owner
251 # Setup os choices
252 os_list = cluster_os_list(vm.cluster)
253 self.fields['os'].choices = os_list
255 for field in self.always_required:
256 self.fields[field].required = True
257 # If the required property is set on a child class,
258 # require those form fields
259 try:
260 if self.required:
261 for field in self.required:
262 self.fields[field].required = True
263 except AttributeError:
264 pass
266 # Need to set initial values from vm.info as these are not saved
267 # per the vm model.
268 if vm.info:
269 info = vm.info
270 hvparam = info['hvparams']
271 # XXX Convert ram string since it comes out
272 # from ganeti as an int and the DataVolumeField does not like
273 # ints.
274 self.fields['vcpus'].initial = info['beparams']['vcpus']
275 if has_balloonmem(vm.cluster):
276 del self.fields['memory']
277 self.fields['minmem'].initial = info['beparams']['minmem']
278 self.fields['maxmem'].initial = info['beparams']['maxmem']
279 else:
280 del self.fields['minmem']
281 del self.fields['maxmem']
282 self.fields['memory'].initial = str(info['beparams']['memory'])
284 # always take the larger nic count. this ensures that if nics are
285 # being removed that they will be in the form as Nones
286 self.nics = len(info['nic.links'])
287 nic_count = int(initial.get('nic_count', 1)) if initial else 1
288 nic_count = self.nics if self.nics > nic_count else nic_count
289 self.fields['nic_count'].initial = nic_count
290 self.nic_fields = xrange(nic_count)
291 for i in xrange(nic_count):
292 link = forms.CharField(label=_('NIC/%s Link' % i),
293 max_length=255, required=True)
294 self.fields['nic_link_%s' % i] = link
295 mac = MACAddressField(label=_('NIC/%s Mac' % i), required=True)
296 self.fields['nic_mac_%s' % i] = mac
297 if i < self.nics:
298 mac.initial = info['nic.macs'][i]
299 link.initial = info['nic.links'][i]
301 self.fields['os'].initial = info['os']
303 try:
304 if self.hvparam_fields:
305 for field in self.hvparam_fields:
306 self.fields[field].initial = hvparam.get(field)
307 except AttributeError:
308 pass
310 def clean(self):
311 data = self.cleaned_data
312 kernel_path = data.get('kernel_path')
313 initrd_path = data.get('initrd_path')
315 # Make sure if initrd_path is set, kernel_path is aswell
316 if initrd_path and not kernel_path:
317 msg = u"%s." % _("Kernel Path must be specified along "
318 "with Initrd Path")
319 self._errors['kernel_path'] = self.error_class([msg])
320 self._errors['initrd_path'] = self.error_class([msg])
321 del data['initrd_path']
323 vnc_tls = data.get('vnc_tls')
324 vnc_x509_path = data.get('vnc_x509_path')
325 vnc_x509_verify = data.get('vnc_x509_verify')
327 if not vnc_tls and vnc_x509_path:
328 msg = u'%s.' % _("This field can not be set without "
329 "VNC TLS enabled")
330 self._errors['vnc_x509_path'] = self.error_class([msg])
331 if vnc_x509_verify and not vnc_x509_path:
332 msg = u'%s.' % _('This field is required')
333 self._errors['vnc_x509_path'] = self.error_class([msg])
335 if self.owner:
336 data['start'] = 'reboot' in self.data or self.vm.is_running
337 check_quota_modify(self)
338 del data['start']
340 for i in xrange(data['nic_count']):
341 mac_field = 'nic_mac_%s' % i
342 link_field = 'nic_link_%s' % i
343 mac = data[mac_field] if mac_field in data else None
344 link = data[link_field] if link_field in data else None
345 if mac and not link:
346 self._errors[link_field] = self.error_class([_('This field is'
347 ' required')])
348 elif link and not mac:
349 self._errors[mac_field] = self.error_class([_('This field is '
350 'required')])
351 data['nic_count_original'] = self.nics
353 return data
356 class HvmModifyVirtualMachineForm(ModifyVirtualMachineForm):
357 hvparam_fields = ('boot_order', 'cdrom_image_path', 'nic_type',
358 'disk_type', 'vnc_bind_address', 'acpi',
359 'use_localtime')
360 required = ('disk_type', 'boot_order', 'nic_type')
361 empty_field = EMPTY_CHOICE_FIELD
362 disk_types = HVM_CHOICES['disk_type']
363 nic_types = HVM_CHOICES['nic_type']
364 boot_devices = HVM_CHOICES['boot_order']
366 acpi = forms.BooleanField(label='ACPI', required=False)
367 use_localtime = forms.BooleanField(label='Use Localtime', required=False)
368 vnc_bind_address = forms.IPAddressField(label='VNC Bind Address',
369 required=False)
370 disk_type = forms.ChoiceField(label=_('Disk Type'), choices=disk_types)
371 nic_type = forms.ChoiceField(label=_('NIC Type'), choices=nic_types)
372 boot_order = forms.ChoiceField(label=_('Boot Device'),
373 choices=boot_devices)
375 class Meta(ModifyVirtualMachineForm.Meta):
376 exclude = ModifyVirtualMachineForm.Meta.exclude + \
377 ('kernel_path', 'root_path', 'kernel_args',
378 'serial_console', 'cdrom2_image_path')
380 def __init__(self, vm, *args, **kwargs):
381 super(HvmModifyVirtualMachineForm, self).__init__(vm, *args, **kwargs)
384 class PvmModifyVirtualMachineForm(ModifyVirtualMachineForm):
385 hvparam_fields = ('root_path', 'kernel_path', 'kernel_args',
386 'initrd_path')
388 initrd_path = forms.CharField(label='initrd Path', required=False)
389 kernel_args = forms.CharField(label='Kernel Args', required=False)
391 class Meta(ModifyVirtualMachineForm.Meta):
392 exclude = ModifyVirtualMachineForm.Meta.exclude + \
393 ('disk_type', 'nic_type', 'boot_order',
394 'cdrom_image_path', 'serial_console',
395 'cdrom2_image_path')
397 def __init__(self, vm, *args, **kwargs):
398 super(PvmModifyVirtualMachineForm, self).__init__(vm, *args, **kwargs)
401 class KvmModifyVirtualMachineForm(PvmModifyVirtualMachineForm,
402 HvmModifyVirtualMachineForm):
403 hvparam_fields = (
404 'acpi', 'disk_cache', 'initrd_path',
405 'kernel_args', 'kvm_flag', 'mem_path',
406 'migration_downtime', 'security_domain',
407 'security_model', 'usb_mouse', 'use_chroot',
408 'use_localtime', 'vnc_bind_address', 'vnc_tls',
409 'vnc_x509_path', 'vnc_x509_verify', 'disk_type',
410 'boot_order', 'nic_type', 'root_path',
411 'kernel_path', 'serial_console',
412 'cdrom_image_path',
413 'cdrom2_image_path',
415 disk_caches = HV_DISK_CACHES
416 kvm_flags = KVM_FLAGS
417 security_models = HV_SECURITY_MODELS
418 usb_mice = HV_USB_MICE
419 disk_types = KVM_CHOICES['disk_type']
420 nic_types = KVM_CHOICES['nic_type']
421 boot_devices = KVM_CHOICES['boot_order']
423 disk_cache = forms.ChoiceField(label='Disk Cache', required=False,
424 choices=disk_caches)
425 kvm_flag = forms.ChoiceField(label='KVM Flag', required=False,
426 choices=kvm_flags)
427 mem_path = forms.CharField(label='Mem Path', required=False)
428 migration_downtime = forms.IntegerField(label='Migration Downtime',
429 required=False)
430 security_model = forms.ChoiceField(label='Security Model',
431 required=False, choices=security_models)
432 security_domain = forms.CharField(label='Security Domain', required=False)
433 usb_mouse = forms.ChoiceField(label='USB Mouse', required=False,
434 choices=usb_mice)
435 use_chroot = forms.BooleanField(label='Use Chroot', required=False)
436 vnc_tls = forms.BooleanField(label='VNC TLS', required=False)
437 vnc_x509_path = forms.CharField(label='VNC x509 Path', required=False)
438 vnc_x509_verify = forms.BooleanField(label='VNC x509 Verify',
439 required=False)
441 class Meta(ModifyVirtualMachineForm.Meta):
442 pass
444 def __init__(self, vm, *args, **kwargs):
445 super(KvmModifyVirtualMachineForm, self).__init__(vm, *args, **kwargs)
446 self.fields['disk_type'].choices = self.disk_types
447 self.fields['nic_type'].choices = self.nic_types
448 self.fields['boot_order'].choices = self.boot_devices
451 class ModifyConfirmForm(forms.Form):
453 def clean(self):
454 raw = self.data['rapi_dict']
455 data = json.loads(raw)
457 cleaned = self.cleaned_data
458 cleaned['rapi_dict'] = data
460 # XXX copy properties into cleaned data so that check_quota_modify can
461 # be used
462 if data.get('maxmem'):
463 cleaned['maxmem'] = data['maxmem']
464 cleaned['minmem'] = data['minmem']
465 else:
466 cleaned['memory'] = data['memory']
467 cleaned['vcpus'] = data['vcpus']
468 cleaned['start'] = 'reboot' in data or self.vm.is_running
469 check_quota_modify(self)
471 # Build NICs dicts. Add changes for existing nics and mark new or
472 # removed nics
474 # XXX Ganeti only allows a single remove or add but this code will
475 # format properly for unlimited adds or removes in the hope that this
476 # limitation is removed sometime in the future.
477 nics = []
478 nic_count_original = data.pop('nic_count_original')
479 nic_count = data.pop('nic_count')
480 for i in xrange(nic_count):
481 nic = dict(link=data.pop('nic_link_%s' % i))
482 if 'nic_mac_%s' % i in data:
483 nic['mac'] = data.pop('nic_mac_%s' % i)
484 index = i if i < nic_count_original else 'add'
485 nics.append((index, nic))
486 for i in xrange(nic_count_original-nic_count):
487 nics.append(('remove', {}))
488 try:
489 del data['nic_mac_%s' % (nic_count+i)]
490 except KeyError:
491 pass
492 del data['nic_link_%s' % (nic_count+i)]
494 data['nics'] = nics
495 return cleaned
498 class MigrateForm(forms.Form):
499 """ Form used for migrating a Virtual Machine """
500 mode = forms.ChoiceField(choices=MODE_CHOICES)
501 cleanup = forms.BooleanField(initial=False, required=False,
502 label=_("Attempt recovery from failed "
503 "migration"))
506 class RenameForm(forms.Form):
507 """ form used for renaming a Virtual Machine """
508 hostname = forms.CharField(label=_('Instance Name'), max_length=255,
509 required=True)
510 ip_check = forms.BooleanField(initial=True, required=False,
511 label=_('IP Check'))
512 name_check = forms.BooleanField(initial=True, required=False,
513 label=_('DNS Name Check'))
515 def __init__(self, vm, *args, **kwargs):
516 self.vm = vm
517 super(RenameForm, self).__init__(*args, **kwargs)
519 def clean_hostname(self):
520 data = self.cleaned_data
521 hostname = data.get('hostname', None)
522 if hostname and hostname == self.vm.hostname:
523 raise ValidationError(_("The new hostname must be different than "
524 "the current hostname"))
525 return hostname
528 class ChangeOwnerForm(forms.Form):
529 """ Form used when modifying the owner of a virtual machine """
530 owner = forms.ModelChoiceField(queryset=ClusterUser.objects.all(),
531 label=_('Owner'))
534 class ReplaceDisksForm(forms.Form):
536 Form used when replacing disks for a virtual machine
538 empty_field = EMPTY_CHOICE_FIELD
540 MODE_CHOICES = (
541 (REPLACE_DISK_AUTO, _('Automatic')),
542 (REPLACE_DISK_PRI, _('Replace disks on primary')),
543 (REPLACE_DISK_SECONDARY, _('Replace disks secondary')),
544 (REPLACE_DISK_CHG, _('Replace secondary with new disk')),
547 mode = forms.ChoiceField(choices=MODE_CHOICES, label=_('Mode'))
548 disks = forms.MultipleChoiceField(label=_('Disks'), required=False)
549 node = forms.ChoiceField(label=_('Node'), choices=[empty_field],
550 required=False)
551 iallocator = forms.BooleanField(initial=False, label=_('Iallocator'),
552 required=False,
553 help_text=_(VM_CREATE_HELP['iallocator']))
555 def __init__(self, instance, *args, **kwargs):
556 super(ReplaceDisksForm, self).__init__(*args, **kwargs)
557 self.instance = instance
559 # set disk choices based on the instance
560 disk_choices = [(i, 'disk/%s' % i) for i, v in
561 enumerate(instance.info['disk.sizes'])]
562 self.fields['disks'].choices = disk_choices
564 # set choices based on the instances cluster
565 cluster = instance.cluster
566 nodelist = [str(h) for h in
567 cluster.nodes.values_list('hostname', flat=True)]
568 nodes = zip(nodelist, nodelist)
569 nodes.insert(0, self.empty_field)
570 self.fields['node'].choices = nodes
572 defaults = cluster_default_info(cluster, get_hypervisor(instance))
573 if defaults['iallocator'] != '':
574 self.fields['iallocator'].initial = True
575 self.fields['iallocator_hostname'] = forms.CharField(
576 initial=defaults['iallocator'],
577 required=False,
578 widget=forms.HiddenInput())
580 def clean(self):
581 data = self.cleaned_data
582 mode = data.get('mode')
583 if mode == REPLACE_DISK_CHG:
584 iallocator = data.get('iallocator')
585 node = data.get('node')
586 if not (iallocator or node):
587 msg = _('Node or iallocator is required when '
588 'replacing secondary with new disk')
589 self._errors['mode'] = self.error_class([msg])
591 elif iallocator and node:
592 msg = _('Choose either node or iallocator')
593 self._errors['mode'] = self.error_class([msg])
595 return data
597 def clean_disks(self):
598 """ format disks into a comma delimited string """
599 disks = self.cleaned_data.get('disks')
600 if disks is not None:
601 disks = ','.join(disks)
602 return disks
604 def clean_node(self):
605 node = self.cleaned_data.get('node')
606 return node if node else None
608 def save(self):
610 Start a replace disks job using the data in this form.
612 data = self.cleaned_data
613 mode = data['mode']
614 disks = data['disks']
615 node = data['node']
616 if data['iallocator']:
617 iallocator = data['iallocator_hostname']
618 else:
619 iallocator = None
620 return self.instance.replace_disks(mode, disks, node, iallocator)
623 class VMWizardClusterForm(Form):
624 cluster = ModelChoiceField(label=_('Cluster'),
625 queryset=Cluster.objects.all(),
626 empty_label=None)
628 class Media:
629 css = {
630 # I'm not quite sure if this is the proper way to use static
631 'all': ('/static/css/vm_wizard/cluster_form.css',)
634 def __init__(self, options=None, *args, **kwargs):
635 super(VMWizardClusterForm, self).__init__(*args, **kwargs)
636 if options:
637 self.fields['choices'] = MultipleChoiceField(
638 widget=CheckboxSelectMultiple,
639 choices=options,
640 initial=self.initial,
641 label=_('What would you '
642 'like to create?'),
643 help_text=_(VM_CREATE_HELP['choices']))
645 def _configure_for_user(self, user):
646 self.fields["cluster"].queryset = cluster_qs_for_user(user)
648 def clean_cluster(self):
650 Ensure that the cluster is available.
653 cluster = self.cleaned_data.get('cluster', None)
654 if not getattr(cluster, "info", None):
655 msg = _("This cluster is currently unavailable. Please check"
656 " for Errors on the cluster detail page.")
657 self._errors['cluster'] = self.error_class([msg])
659 return cluster
662 class VMWizardOwnerForm(Form):
663 owner = ModelChoiceField(label=_('Owner'),
664 queryset=ClusterUser.objects.all(),
665 empty_label=None,
666 help_text=_(VM_CREATE_HELP['owner']))
667 template_name = CharField(label=_("Template Name"), max_length=255,
668 required=False,
669 help_text=_(VM_HELP['template_name']))
670 hostname = CharField(label=_('Instance Name'), max_length=255,
671 required=False,
672 help_text=_(VM_CREATE_HELP['hostname']))
674 def _configure_for_cluster(self, cluster, user):
675 if not cluster:
676 return
678 self.cluster = cluster
680 qs = owner_qs(cluster, user)
681 self.fields["owner"].queryset = qs
683 def _configure_for_template(self, template, choices=None):
684 # for each option not checked on step 0
685 if choices:
686 for field in choices:
687 # Hide it
688 self.fields[field].widget = forms.HiddenInput()
690 if not template:
691 return
693 self.fields["template_name"].initial = template.template_name
695 def clean_hostname(self):
696 hostname = self.cleaned_data.get('hostname')
697 if hostname:
698 # Confirm that the hostname is not already in use.
699 try:
700 vm = VirtualMachine.objects.get(cluster=self.cluster,
701 hostname=hostname)
702 except VirtualMachine.DoesNotExist:
703 # Well, *I'm* convinced.
704 pass
705 else:
706 raise ValidationError(
707 _("Hostname is already in use for this cluster"))
709 # Spaces in hostname will always break things.
710 if ' ' in hostname:
711 self._errors["hostname"] = self.error_class(
712 ["Hostnames cannot contain spaces."])
713 return hostname
715 def clean(self):
716 if (not self.cleaned_data.get("template_name") and
717 not self.cleaned_data.get("hostname")):
718 raise ValidationError("What should be created?")
719 return self.cleaned_data
722 class VMWizardBasicsForm(Form):
723 hv = ChoiceField(label=_("Hypervisor"), choices=[],
724 help_text=_(VM_CREATE_HELP['hypervisor']))
725 os = ChoiceField(label=_('Operating System'), choices=[],
726 help_text=_(VM_CREATE_HELP['os']))
727 no_install = BooleanField(label=_('Do not install the OS'), required=False,
728 help_text=_(VM_CREATE_HELP['no_install']))
729 iallocator = BooleanField(label=_("Automatic Allocation"),
730 initial=True, required=False,
731 help_text=_(VM_CREATE_HELP['iallocator']))
732 vcpus = IntegerField(label=_("Virtual CPU Count"), initial=1, min_value=1,
733 help_text=_(VM_HELP['vcpus']))
734 minram = DataVolumeField(label=_('Minimum RAM (MiB)'),
735 help_text=_(VM_HELP['memory']))
736 memory = DataVolumeField(label=_('Maximum RAM (MiB)'),
737 help_text=_(VM_HELP['memory']))
738 disk_template = ChoiceField(label=_('Disk Template'),
739 choices=HV_DISK_TEMPLATES,
740 help_text=_(VM_CREATE_HELP['disk_template']))
742 def __init__(self, *args, **kwargs):
743 super(VMWizardBasicsForm, self).__init__(*args, **kwargs)
745 # Create disk and nic fields based on value in settings
746 disk_count = settings.MAX_DISKS_ADD
747 self.create_disk_fields(disk_count)
749 nic_count = settings.MAX_NICS_ADD
750 self.create_nic_fields(nic_count)
752 def create_disk_fields(self, count):
754 dynamically add fields for disks
756 for i in range(count):
757 disk_size = DataVolumeField(
758 label=_("Disk/%s Size (MB)" % i), required=False,
759 help_text=_(VM_CREATE_HELP['disk_size']))
761 disk_size.widget.attrs['class'] = 'multi disk'
762 disk_size.widget.attrs['data-group'] = i
763 self.fields['disk_size_%s' % i] = disk_size
765 def create_nic_fields(self, count):
767 dynamically add fields for nics
769 self.nic_fields = range(count)
770 for i in range(count):
771 nic_mode = forms.ChoiceField(
772 label=_('NIC/%s Mode' % i), choices=HV_NIC_MODES, initial='',
773 required=False, help_text=_(VM_CREATE_HELP['nic_mode']))
775 nic_link = forms.CharField(
776 label=_('NIC/%s Link' % i), max_length=255, initial='',
777 required=False, help_text=_(VM_HELP['nic_link']))
779 # used for front end
780 nic_mode.widget.attrs['class'] = 'multi nic mode'
781 nic_mode.widget.attrs['data-group'] = i
782 nic_link.widget.attrs['class'] = 'multi nic link'
783 nic_link.widget.attrs['data-group'] = i
785 self.fields['nic_mode_%s' % i] = nic_mode
786 self.fields['nic_link_%s' % i] = nic_link
788 def _configure_for_cluster(self, cluster):
789 if not cluster:
790 return
792 self.cluster = cluster
794 # Verify that the autoallocator isn't nothing
795 # If it is, remove the option.
796 default_iallocator = cluster.info['default_iallocator']
797 if not default_iallocator:
798 del self.fields['iallocator']
799 else:
800 label_extra = " (%s)" % default_iallocator
801 self.fields['iallocator'].label += label_extra
803 # Get a look at the list of available hypervisors, and set the initial
804 # hypervisor appropriately.
805 hvs = cluster.info["enabled_hypervisors"]
806 prettified = [hv_prettify(hv) for hv in hvs]
807 hv = cluster.info["default_hypervisor"]
808 self.fields["hv"].choices = zip(hvs, prettified)
809 self.fields["hv"].initial = hv
811 if not has_sharedfile(cluster):
812 self.fields["disk_template"].choices.remove((u'sharedfile',
813 u'Sharedfile'))
815 # Get the OS list.
816 self.fields["os"].choices = cluster_os_list(cluster)
818 # Set the default CPU count based on the backend parameters.
819 beparams = cluster.info["beparams"]["default"]
820 self.fields["vcpus"].initial = beparams["vcpus"]
822 # Check for memory based on ganeti version
823 if has_balloonmem(cluster):
824 self.fields["memory"].initial = beparams["maxmem"]
825 self.fields["minram"].initial = beparams["minmem"]
826 else:
827 self.fields["memory"].initial = beparams["memory"]
829 # If there are ipolicy limits in place, add validators for them.
830 if "ipolicy" in cluster.info:
831 if "max" in cluster.info["ipolicy"]:
832 # disk maximums
833 v = cluster.info["ipolicy"]["max"]["disk-size"]
834 for disk in xrange(settings.MAX_DISKS_ADD):
835 self.fields["disk_size_%s" % disk].validators.append(
836 MaxValueValidator(v))
837 # ram minimums
838 v = cluster.info["ipolicy"]["max"]["memory-size"]
839 self.fields["memory"].validators.append(MaxValueValidator(v))
840 if has_balloonmem(cluster):
841 self.fields["minram"].validators.append(
842 MaxValueValidator(v))
844 if "min" in cluster.info["ipolicy"]:
845 # disk minimums
846 v = cluster.info["ipolicy"]["min"]["disk-size"]
847 for disk in xrange(settings.MAX_DISKS_ADD):
848 disk_field = self.fields["disk_size_%s" % disk]
849 disk_field.validators.append(MinValueValidator(v))
850 # if its the first disk, add the min value as a default
851 if disk == 0:
852 disk_field.initial = v
853 # memory minimums
854 v = cluster.info["ipolicy"]["min"]["memory-size"]
855 self.fields["memory"].validators.append(MinValueValidator(v))
856 if has_balloonmem(cluster):
857 self.fields["minram"].validators.append(
858 MinValueValidator(v))
860 # configure cluster defaults for nics
861 nic_defaults = cluster.info['nicparams']['default']
862 self.fields['nic_mode_0'].initial = nic_defaults['mode']
863 self.fields['nic_link_0'].initial = nic_defaults['link']
865 def _configure_for_template(self, template):
866 if not template:
867 return
869 self.fields["os"].initial = template.os
870 self.fields["vcpus"].initial = template.vcpus
871 self.fields["memory"].initial = template.memory
872 if has_balloonmem(template.cluster):
873 self.fields["minram"].initial = template.minmem
874 self.fields["disk_template"].initial = template.disk_template
875 for num, disk in enumerate(template.disks):
876 self.fields["disk_size_%s" % num].initial = disk["size"]
877 for num, nic in enumerate(template.nics):
878 self.fields["nic_link_%s" % num].initial = nic['link']
879 self.fields["nic_mode_%s" % num].initial = nic['mode']
881 def clean(self):
882 data = self.cleaned_data
883 # get disk sizes after validation (after 1.5G -> 1500)
884 # and filter empty fields.
885 disks = []
886 for disk_num in xrange(settings.MAX_DISKS_ADD):
887 disk = data.get("disk_size_%s" % disk_num, None)
888 if disk:
889 disks.append(disk)
890 # if disks validated (no errors), but none of them contain data, then
891 # they were all left empty
892 if not disks and not self._errors:
893 msg = _("You need to add at least 1 disk!")
894 self._errors["disk_size_0"] = self.error_class([msg])
896 # Store disks as an array of dicts for use in template.
897 data["disks"] = [{"size": disk} for disk in disks]
899 nics = []
900 for nic in xrange(settings.MAX_NICS_ADD):
901 link = data.get('nic_link_%s' % nic, None)
902 mode = data.get('nic_mode_%s' % nic, None)
903 # if both the mode and link for a NIC are filled out, add it to the
904 # nic list.
905 if link and mode:
906 nics.append({'link': link, 'mode': mode})
907 elif link or mode:
908 raise ValidationError(_("Please input both a link and mode."))
910 data['nics'] = nics
912 if data.get('minram') > data.get('memory'):
913 msg = _("The minimum ram cannot be larger than the maximum ram.")
914 self._errors["minram"] = self.error_class([msg])
916 return data
919 class VMWizardAdvancedForm(Form):
920 ip_check = BooleanField(label=_('Verify IP'), initial=False,
921 required=False,
922 help_text=_(VM_RENAME_HELP['ip_check']))
923 name_check = BooleanField(label=_('Verify hostname through DNS'),
924 initial=False, required=False,
925 help_text=_(VM_RENAME_HELP['name_check']))
926 no_start = BooleanField(label=_('Do not boot the VM'), required=False)
927 pnode = ModelChoiceField(label=_("Primary Node"),
928 queryset=Node.objects.all(), empty_label=None,
929 help_text=_(VM_CREATE_HELP['pnode']))
930 snode = ModelChoiceField(label=_("Secondary Node"),
931 queryset=Node.objects.all(), empty_label=None,
932 help_text=_(VM_CREATE_HELP['snode']))
934 # By default unless the configure_for_iallocator method has run,
935 # we assume we're not using autoallocation
936 use_iallocator = False
938 def _configure_for_cluster(self, cluster):
939 if not cluster:
940 return
942 self.cluster = cluster
944 qs = Node.objects.filter(cluster=cluster)
945 self.fields["pnode"].queryset = qs
946 self.fields["snode"].queryset = qs
948 def _configure_for_template(self, template):
949 if not template:
950 return
952 self.fields["ip_check"].initial = template.ip_check
953 self.fields["name_check"].initial = template.name_check
954 self.fields["pnode"].initial = template.pnode
955 self.fields["snode"].initial = template.snode
957 def _configure_for_iallocator(self):
958 del self.fields["pnode"]
959 del self.fields["snode"]
960 self.use_iallocator = True
962 def _configure_for_disk_template(self, template):
963 # If its not drdb, we dont use the secondary node.
964 # If we're using the iallocator then this field
965 # will already be deleted.
966 if template != "drbd":
967 del self.fields["snode"]
970 def clean(self):
971 # Ganeti will error on VM creation if an IP address check is requested
972 # but a name check is not.
973 data = self.cleaned_data
974 if (data.get("ip_check") and not data.get("name_check")):
975 msg = ["Cannot perform IP check without name check"]
976 self._errors["ip_check"] = self.error_class(msg)
978 if not self.use_iallocator and data.get('pnode') == data.get('snode'):
979 raise forms.ValidationError("The secondary node cannot be the "
980 "primary node.")
982 return data
985 class VMWizardPVMForm(Form):
986 kernel_path = CharField(label=_("Kernel path"), max_length=255)
987 root_path = CharField(label=_("Root path"), max_length=255)
989 def _configure_for_cluster(self, cluster):
990 if not cluster:
991 return
993 self.cluster = cluster
994 params = cluster.info["hvparams"]["xen-pvm"]
996 self.fields["kernel_path"].initial = params["kernel_path"]
997 self.fields["root_path"].initial = params["root_path"]
999 def _configure_for_template(self, template):
1000 if not template:
1001 return
1003 self.fields["kernel_path"].initial = template.kernel_path
1004 self.fields["root_path"].initial = template.root_path
1007 class VMWizardHVMForm(Form):
1008 boot_order = ChoiceField(label=_("Preferred boot device"),
1009 required=False, choices=HVM_BOOT_ORDER,
1010 help_text=_(VM_CREATE_HELP['boot_order']))
1011 cdrom_image_path = CharField(label=_("CD-ROM image path"), max_length=512,
1012 required=False,
1013 help_text=_(
1014 VM_CREATE_HELP['cdrom_image_path']))
1015 disk_type = ChoiceField(label=_("Disk type"),
1016 choices=HVM_CHOICES["disk_type"],
1017 help_text=_(VM_CREATE_HELP['disk_type']))
1018 nic_type = ChoiceField(label=_("NIC type"),
1019 choices=HVM_CHOICES["nic_type"],
1020 help_text=_(VM_CREATE_HELP['nic_type']))
1022 def _configure_for_cluster(self, cluster):
1023 if not cluster:
1024 return
1026 self.cluster = cluster
1027 params = cluster.info["hvparams"]["xen-pvm"]
1029 self.fields["boot_order"].initial = params["boot_order"]
1030 self.fields["disk_type"].initial = params["disk_type"]
1031 self.fields["nic_type"].initial = params["nic_type"]
1033 def _configure_for_template(self, template):
1034 if not template:
1035 return
1037 self.fields["boot_order"].initial = template.boot_order
1038 self.fields["cdrom_image_path"].initial = template.cdrom_image_path
1039 self.fields["disk_type"].initial = template.disk_type
1040 self.fields["nic_type"].initial = template.nic_type
1043 class VMWizardKVMForm(Form):
1044 kernel_path = CharField(label=_("Kernel path"), max_length=255,
1045 required=False,
1046 help_text=_(VM_CREATE_HELP['kernel_path']))
1047 root_path = CharField(label=_("Root path"), max_length=255,
1048 help_text=_(VM_CREATE_HELP['root_path']))
1049 serial_console = BooleanField(label=_("Enable serial console"),
1050 required=False,
1051 help_text=_(
1052 VM_CREATE_HELP['serial_console']))
1053 boot_order = ChoiceField(label=_("Preferred boot device"),
1054 required=False, choices=KVM_BOOT_ORDER,
1055 help_text=_(VM_CREATE_HELP['boot_order']))
1056 cdrom_image_path = CharField(label=_("CD-ROM image path"), max_length=512,
1057 required=False,
1058 help_text=_(
1059 VM_CREATE_HELP['cdrom_image_path']))
1060 cdrom2_image_path = CharField(label=_("Second CD-ROM image path"),
1061 max_length=512, required=False,
1062 help_text=_(
1063 VM_CREATE_HELP['cdrom2_image_path']))
1064 disk_type = ChoiceField(label=_("Disk type"),
1065 choices=KVM_CHOICES["disk_type"],
1066 help_text=_(VM_CREATE_HELP['disk_type']))
1067 nic_type = ChoiceField(label=_("NIC type"),
1068 choices=KVM_CHOICES["nic_type"],
1069 help_text=_(VM_CREATE_HELP['nic_type']))
1071 def _configure_for_cluster(self, cluster):
1072 if not cluster:
1073 return
1075 self.cluster = cluster
1076 params = cluster.info["hvparams"]["kvm"]
1078 self.fields["boot_order"].initial = params["boot_order"]
1079 self.fields["disk_type"].initial = params["disk_type"]
1080 self.fields["kernel_path"].initial = params["kernel_path"]
1081 self.fields["nic_type"].initial = params["nic_type"]
1082 self.fields["root_path"].initial = params["root_path"]
1083 self.fields["serial_console"].initial = params["serial_console"]
1085 # Remove cdrom2 if the cluster doesn't have it; see #11655.
1086 if not has_cdrom2(cluster):
1087 del self.fields["cdrom2_image_path"]
1089 def _configure_for_template(self, template):
1090 if not template:
1091 return
1093 self.fields["kernel_path"].initial = template.kernel_path
1094 self.fields["root_path"].initial = template.root_path
1095 self.fields["serial_console"].initial = template.serial_console
1096 self.fields["boot_order"].initial = template.boot_order
1097 self.fields["cdrom_image_path"].initial = template.cdrom_image_path
1098 self.fields["cdrom2_image_path"].initial = template.cdrom2_image_path
1099 self.fields["disk_type"].initial = template.disk_type
1100 self.fields["nic_type"].initial = template.nic_type
1102 def clean(self):
1103 data = super(VMWizardKVMForm, self).clean()
1105 # Force cdrom disk type to IDE; see #9297.
1106 data['cdrom_disk_type'] = 'ide'
1108 # If booting from CD-ROM, require the first CD-ROM image to be
1109 # present.
1110 if (data.get("boot_order") == "cdrom" and
1111 not data.get("cdrom_image_path")):
1112 msg = u"%s." % _("Image path required if boot device is CD-ROM")
1113 self._errors["cdrom_image_path"] = self.error_class([msg])
1115 return data
1118 class VMWizardView(LoginRequiredMixin, PermissionRequiredMixin,
1119 CookieWizardView):
1120 template_name = "ganeti/forms/vm_wizard.html"
1121 permission_required = ["admin", "create_vm"]
1122 no_perms_msg = ("You do not have admin or create vm "
1123 "privledges to any clusters.")
1125 OPTIONS = (
1126 # value, display value
1127 # value corresponds to VMWizardOwnerForm's fields
1128 ('template_name', 'Template'),
1129 ('hostname', 'Virtual Machine'),
1132 def _get_vm_or_template(self):
1133 """Returns items that were not checked in step0"""
1134 data = self.get_cleaned_data_for_step('0')
1135 if data:
1136 options = [option[0] for option in self.OPTIONS]
1137 choices = data.get('choices', None)
1138 # which boxes weren't checked
1139 unchecked = set(options) - set(choices)
1140 return unchecked
1142 return None
1144 def _get_template(self):
1145 name = self.kwargs.get("template")
1146 if name:
1147 return VirtualMachineTemplate.objects.get(template_name=name)
1148 return None
1150 def _get_cluster(self):
1151 data = self.get_cleaned_data_for_step("0")
1152 if data:
1153 return data["cluster"]
1154 return None
1156 def _get_hv(self):
1157 data = self.get_cleaned_data_for_step("2")
1158 if data:
1159 return data["hv"]
1160 return None
1162 def _get_disk_template(self):
1163 data = self.get_cleaned_data_for_step("2")
1164 if data:
1165 return data["disk_template"]
1166 return None
1168 def _get_iallocator(self):
1169 data = self.get_cleaned_data_for_step("2")
1170 if data:
1171 # This one is different because the iallocator might not exist.
1172 return data.get("iallocator", False)
1173 return False
1175 def get_form(self, step=None, data=None, files=None):
1176 s = int(self.steps.current) if step is None else int(step)
1177 initial = self.get_form_initial(s)
1179 if s == 0:
1180 form = VMWizardClusterForm(data=data, options=self.OPTIONS,
1181 initial=initial)
1182 form._configure_for_user(self.request.user)
1183 # XXX this should somehow become totally invalid if the user
1184 # doesn't have perms on the template.
1185 elif s == 1:
1186 form = VMWizardOwnerForm(data=data)
1187 form._configure_for_cluster(self._get_cluster(),
1188 user=self.request.user)
1189 form._configure_for_template(self._get_template(),
1190 choices=self._get_vm_or_template())
1191 elif s == 2:
1192 form = VMWizardBasicsForm(data=data)
1193 form._configure_for_cluster(self._get_cluster())
1194 form._configure_for_template(self._get_template())
1195 elif s == 3:
1196 form = VMWizardAdvancedForm(data=data)
1197 form._configure_for_cluster(self._get_cluster())
1198 form._configure_for_template(self._get_template())
1199 using_iallocator = self._get_iallocator()
1200 # Autoallocation means we dont need to configure the disk template
1201 if using_iallocator:
1202 form._configure_for_iallocator()
1203 else:
1204 form._configure_for_disk_template(self._get_disk_template())
1205 elif s == 4:
1206 cluster = self._get_cluster()
1207 hv = self._get_hv()
1208 form = None
1210 if cluster and hv:
1211 if hv == "kvm":
1212 form = VMWizardKVMForm(data=data)
1213 elif hv == "xen-pvm":
1214 form = VMWizardPVMForm(data=data)
1215 elif hv == "xen-hvm":
1216 form = VMWizardHVMForm(data=data)
1218 if form:
1219 form._configure_for_cluster(cluster)
1220 form._configure_for_template(self._get_template())
1221 else:
1222 form = Form()
1223 else:
1224 form = super(VMWizardView, self).get_form(step, data, files)
1226 return form
1228 def get_context_data(self, form, **kwargs):
1229 context = super(VMWizardView, self).get_context_data(form=form,
1230 **kwargs)
1231 summary = {
1232 "cluster_form": self.get_cleaned_data_for_step("0"),
1233 "owner_form": self.get_cleaned_data_for_step("1"),
1234 "basics_form": self.get_cleaned_data_for_step("2"),
1235 "advanced_form": self.get_cleaned_data_for_step("3"),
1236 "hv_form": self.get_cleaned_data_for_step("4"),
1238 context["summary"] = summary
1240 return context
1242 def done(self, forms, template=None, **kwargs):
1244 Create a template. Optionally, bind a template to a VM instance
1245 created from the template. Optionally, name the template and save it.
1246 One or both of those is done depending on what the user has requested.
1249 # Hack: accepting kwargs in order to be able to work in several
1250 # different spots.
1252 if template is None:
1253 template = VirtualMachineTemplate()
1254 else:
1255 template = self._get_template()
1257 user = self.request.user
1259 cluster = forms[0].cleaned_data["cluster"]
1260 owner = forms[1].cleaned_data["owner"]
1262 template_name = forms[1].cleaned_data["template_name"]
1263 hostname = forms[1].cleaned_data["hostname"]
1265 # choice_data are the options that were not checked
1266 # if unchecked, than we should make sure that this is not submitted.
1267 # this fixes cases where the user checked a box in the beginning, put
1268 # data into the input, and went back and unchecked that box later.
1269 unchecked_options = self._get_vm_or_template()
1270 for unchecked in unchecked_options:
1271 if 'template_name' == unchecked:
1272 template_name = ''
1273 if 'hostname' == unchecked:
1274 hostname = ''
1276 template.cluster = cluster
1277 template.memory = forms[2].cleaned_data["memory"]
1278 if has_balloonmem(cluster):
1279 template.minmem = forms[2].cleaned_data["minram"]
1280 template.vcpus = forms[2].cleaned_data["vcpus"]
1281 template.disk_template = forms[2].cleaned_data["disk_template"]
1283 template.disks = forms[2].cleaned_data["disks"]
1285 nics = forms[2].cleaned_data["nics"]
1286 # default
1287 if not nics:
1288 nics = [{"link": "br0", "mode": "bridged"}]
1289 template.nics = nics
1291 template.hypervisor = forms[2].cleaned_data["hv"]
1293 template.os = forms[2].cleaned_data["os"]
1294 template.no_install = forms[2].cleaned_data["no_install"]
1295 template.iallocator = forms[2].cleaned_data["iallocator"]
1296 template.ip_check = forms[3].cleaned_data["ip_check"]
1297 template.name_check = forms[3].cleaned_data["name_check"]
1298 template.no_start = forms[3].cleaned_data["no_start"]
1300 if not template.iallocator:
1301 template.pnode = forms[3].cleaned_data["pnode"].hostname
1302 if "snode" in forms[3].cleaned_data:
1303 template.snode = forms[3].cleaned_data["snode"].hostname
1305 hvparams = forms[4].cleaned_data
1307 template.boot_order = hvparams.get("boot_order")
1308 template.cdrom2_image_path = hvparams.get("cdrom2_image_path")
1309 template.cdrom_image_path = hvparams.get("cdrom_image_path")
1310 template.kernel_path = hvparams.get("kernel_path")
1311 template.root_path = hvparams.get("root_path")
1312 template.serial_console = hvparams.get("serial_console")
1313 template.nic_type = hvparams.get('nic_type')
1314 template.disk_type = hvparams.get('disk_type')
1316 template.set_name(template_name)
1317 # only save the template to the database if its not temporary
1318 if not template.temporary:
1319 template.save()
1321 if hostname:
1322 vm = template_to_instance(template, hostname, owner)
1323 log_action('CREATE', user, vm)
1324 return HttpResponseRedirect(reverse('instance-detail',
1325 args=[cluster.slug,
1326 vm.hostname]))
1327 else:
1328 return HttpResponseRedirect(reverse("template-detail",
1329 args=[cluster.slug,
1330 template]))
1332 def has_perms(self, request, perms, obj=None):
1333 user = request.user
1334 # We use Cluster and dont use obj because we want to check if the user
1335 # has perms on ANY clusters
1336 return user.is_superuser or user.has_any_perms(Cluster, perms)
1339 def vm_wizard(*args, **kwargs):
1340 forms = (
1341 VMWizardClusterForm,
1342 VMWizardOwnerForm,
1343 VMWizardBasicsForm,
1344 VMWizardAdvancedForm,
1345 Form,
1347 initial = kwargs.get('initial_dict', None)
1348 return VMWizardView.as_view(forms, initial_dict=initial)