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,
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
):
59 Parent class that holds all vm clean methods
60 and shared form fields.
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)
69 model
= VirtualMachineTemplate
71 def create_disk_fields(self
, count
):
73 dynamically add fields for disks
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):
83 dynamically add fields for nics
85 self
.nic_fields
= range(count
)
86 for i
in range(count
):
87 nic_mode
= forms
.ChoiceField(label
=_('NIC/%s Mode' % i
),
89 nic_link
= forms
.CharField(label
=_('NIC/%s Link' % i
),
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
106 vm
= VirtualMachine
.objects
.get(cluster
=cluster
,
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
115 msg
= _("Owner cannot be changed when recovering a "
117 self
._errors
["owner"] = self
.error_class([msg
])
119 raise ValidationError(_("Hostname is already in use for "
122 except VirtualMachine
.DoesNotExist
:
123 # doesn't exist, no further checks needed
126 # Spaces in hostname will always break things.
128 self
._errors
["hostname"] = self
.error_class(
129 ["Hostname contains illegal character"])
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"])
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
])
148 def clean_security_domain(self
):
149 data
= self
.cleaned_data
['security_domain']
150 security_model
= self
.cleaned_data
['security_model']
153 if data
and security_model
!= 'user':
155 'This field can not be set if Security '
156 'Mode is not set to User')
157 elif security_model
== 'user':
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')
165 self
._errors
['security_domain'] = self
.error_class([msg
])
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
])
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
184 if owner
is not None:
185 start
= data
['start']
186 quota
= cluster
.get_quota(owner
)
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']):
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'] > \
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']
209 quota
['virtual_cpus']):
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 "
215 form
._errors
["vcpus"] = form
.error_class([q_msg
])
218 class ModifyVirtualMachineForm(VirtualMachineForm
):
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
])
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')
248 except AttributeError:
249 self
.owner
= vm
.owner
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
261 for field
in self
.required
:
262 self
.fields
[field
].required
= True
263 except AttributeError:
266 # Need to set initial values from vm.info as these are not saved
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
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']
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
298 mac
.initial
= info
['nic.macs'][i
]
299 link
.initial
= info
['nic.links'][i
]
301 self
.fields
['os'].initial
= info
['os']
304 if self
.hvparam_fields
:
305 for field
in self
.hvparam_fields
:
306 self
.fields
[field
].initial
= hvparam
.get(field
)
307 except AttributeError:
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 "
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 "
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
])
336 data
['start'] = 'reboot' in self
.data
or self
.vm
.is_running
337 check_quota_modify(self
)
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
346 self
._errors
[link_field
] = self
.error_class([_('This field is'
348 elif link
and not mac
:
349 self
._errors
[mac_field
] = self
.error_class([_('This field is '
351 data
['nic_count_original'] = self
.nics
356 class HvmModifyVirtualMachineForm(ModifyVirtualMachineForm
):
357 hvparam_fields
= ('boot_order', 'cdrom_image_path', 'nic_type',
358 'disk_type', 'vnc_bind_address', 'acpi',
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',
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',
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',
397 def __init__(self
, vm
, *args
, **kwargs
):
398 super(PvmModifyVirtualMachineForm
, self
).__init
__(vm
, *args
, **kwargs
)
401 class KvmModifyVirtualMachineForm(PvmModifyVirtualMachineForm
,
402 HvmModifyVirtualMachineForm
):
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',
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,
425 kvm_flag
= forms
.ChoiceField(label
='KVM Flag', required
=False,
427 mem_path
= forms
.CharField(label
='Mem Path', required
=False)
428 migration_downtime
= forms
.IntegerField(label
='Migration Downtime',
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,
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',
441 class Meta(ModifyVirtualMachineForm
.Meta
):
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
):
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
462 if data
.get('maxmem'):
463 cleaned
['maxmem'] = data
['maxmem']
464 cleaned
['minmem'] = data
['minmem']
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
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.
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', {}))
489 del data
['nic_mac_%s' % (nic_count
+i
)]
492 del data
['nic_link_%s' % (nic_count
+i
)]
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 "
506 class RenameForm(forms
.Form
):
507 """ form used for renaming a Virtual Machine """
508 hostname
= forms
.CharField(label
=_('Instance Name'), max_length
=255,
510 ip_check
= forms
.BooleanField(initial
=True, required
=False,
512 name_check
= forms
.BooleanField(initial
=True, required
=False,
513 label
=_('DNS Name Check'))
515 def __init__(self
, vm
, *args
, **kwargs
):
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"))
528 class ChangeOwnerForm(forms
.Form
):
529 """ Form used when modifying the owner of a virtual machine """
530 owner
= forms
.ModelChoiceField(queryset
=ClusterUser
.objects
.all(),
534 class ReplaceDisksForm(forms
.Form
):
536 Form used when replacing disks for a virtual machine
538 empty_field
= EMPTY_CHOICE_FIELD
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
],
551 iallocator
= forms
.BooleanField(initial
=False, label
=_('Iallocator'),
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'],
578 widget
=forms
.HiddenInput())
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
])
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
)
604 def clean_node(self
):
605 node
= self
.cleaned_data
.get('node')
606 return node
if node
else None
610 Start a replace disks job using the data in this form.
612 data
= self
.cleaned_data
614 disks
= data
['disks']
616 if data
['iallocator']:
617 iallocator
= data
['iallocator_hostname']
620 return self
.instance
.replace_disks(mode
, disks
, node
, iallocator
)
623 class VMWizardClusterForm(Form
):
624 cluster
= ModelChoiceField(label
=_('Cluster'),
625 queryset
=Cluster
.objects
.all(),
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
)
637 self
.fields
['choices'] = MultipleChoiceField(
638 widget
=CheckboxSelectMultiple
,
640 initial
=self
.initial
,
641 label
=_('What would you '
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
])
662 class VMWizardOwnerForm(Form
):
663 owner
= ModelChoiceField(label
=_('Owner'),
664 queryset
=ClusterUser
.objects
.all(),
666 help_text
=_(VM_CREATE_HELP
['owner']))
667 template_name
= CharField(label
=_("Template Name"), max_length
=255,
669 help_text
=_(VM_HELP
['template_name']))
670 hostname
= CharField(label
=_('Instance Name'), max_length
=255,
672 help_text
=_(VM_CREATE_HELP
['hostname']))
674 def _configure_for_cluster(self
, cluster
, user
):
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
686 for field
in choices
:
688 self
.fields
[field
].widget
= forms
.HiddenInput()
693 self
.fields
["template_name"].initial
= template
.template_name
695 def clean_hostname(self
):
696 hostname
= self
.cleaned_data
.get('hostname')
698 # Confirm that the hostname is not already in use.
700 vm
= VirtualMachine
.objects
.get(cluster
=self
.cluster
,
702 except VirtualMachine
.DoesNotExist
:
703 # Well, *I'm* convinced.
706 raise ValidationError(
707 _("Hostname is already in use for this cluster"))
709 # Spaces in hostname will always break things.
711 self
._errors
["hostname"] = self
.error_class(
712 ["Hostnames cannot contain spaces."])
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']))
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
):
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']
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',
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"]
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"]:
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
))
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"]:
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
852 disk_field
.initial
= v
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
):
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']
882 data
= self
.cleaned_data
883 # get disk sizes after validation (after 1.5G -> 1500)
884 # and filter empty fields.
886 for disk_num
in xrange(settings
.MAX_DISKS_ADD
):
887 disk
= data
.get("disk_size_%s" % disk_num
, None)
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
]
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
906 nics
.append({'link': link
, 'mode': mode
})
908 raise ValidationError(_("Please input both a link and mode."))
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
])
919 class VMWizardAdvancedForm(Form
):
920 ip_check
= BooleanField(label
=_('Verify IP'), initial
=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
):
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
):
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"]
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 "
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
):
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
):
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,
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
):
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
):
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,
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"),
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,
1059 VM_CREATE_HELP
['cdrom_image_path']))
1060 cdrom2_image_path
= CharField(label
=_("Second CD-ROM image path"),
1061 max_length
=512, required
=False,
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
):
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
):
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
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
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
])
1118 class VMWizardView(LoginRequiredMixin
, PermissionRequiredMixin
,
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.")
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')
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
)
1144 def _get_template(self
):
1145 name
= self
.kwargs
.get("template")
1147 return VirtualMachineTemplate
.objects
.get(template_name
=name
)
1150 def _get_cluster(self
):
1151 data
= self
.get_cleaned_data_for_step("0")
1153 return data
["cluster"]
1157 data
= self
.get_cleaned_data_for_step("2")
1162 def _get_disk_template(self
):
1163 data
= self
.get_cleaned_data_for_step("2")
1165 return data
["disk_template"]
1168 def _get_iallocator(self
):
1169 data
= self
.get_cleaned_data_for_step("2")
1171 # This one is different because the iallocator might not exist.
1172 return data
.get("iallocator", 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
)
1180 form
= VMWizardClusterForm(data
=data
, options
=self
.OPTIONS
,
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.
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
())
1192 form
= VMWizardBasicsForm(data
=data
)
1193 form
._configure
_for
_cluster
(self
._get
_cluster
())
1194 form
._configure
_for
_template
(self
._get
_template
())
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
()
1204 form
._configure
_for
_disk
_template
(self
._get
_disk
_template
())
1206 cluster
= self
._get
_cluster
()
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
)
1219 form
._configure
_for
_cluster
(cluster
)
1220 form
._configure
_for
_template
(self
._get
_template
())
1224 form
= super(VMWizardView
, self
).get_form(step
, data
, files
)
1228 def get_context_data(self
, form
, **kwargs
):
1229 context
= super(VMWizardView
, self
).get_context_data(form
=form
,
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
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
1252 if template
is None:
1253 template
= VirtualMachineTemplate()
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
:
1273 if 'hostname' == unchecked
:
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"]
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
:
1322 vm
= template_to_instance(template
, hostname
, owner
)
1323 log_action('CREATE', user
, vm
)
1324 return HttpResponseRedirect(reverse('instance-detail',
1328 return HttpResponseRedirect(reverse("template-detail",
1332 def has_perms(self
, request
, perms
, obj
=None):
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
):
1341 VMWizardClusterForm
,
1344 VMWizardAdvancedForm
,
1347 initial
= kwargs
.get('initial_dict', None)
1348 return VMWizardView
.as_view(forms
, initial_dict
=initial
)