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
,
37 from ganeti_web
.backend
.templates
import template_to_instance
38 from ganeti_web
.caps
import has_cdrom2
, has_balloonmem
, has_sharedfile
39 from ganeti_web
.constants
import (EMPTY_CHOICE_FIELD
, HV_DISK_TEMPLATES
,
40 HV_NIC_MODES
, KVM_CHOICES
, HV_USB_MICE
,
41 HV_SECURITY_MODELS
, KVM_FLAGS
,
42 HV_DISK_CACHES
, MODE_CHOICES
, HVM_CHOICES
,
43 VM_HELP
, VM_CREATE_HELP
, VM_RENAME_HELP
,
44 KVM_BOOT_ORDER
, HVM_BOOT_ORDER
)
45 from ganeti_web
.fields
import DataVolumeField
, MACAddressField
46 from ganeti_web
.models
import (Cluster
, ClusterUser
, Node
,
47 VirtualMachineTemplate
, VirtualMachine
)
48 from ganeti_web
.utilities
import (cluster_default_info
, cluster_os_list
,
49 get_hypervisor
, hv_prettify
)
50 from ganeti_web
.util
.client
import (REPLACE_DISK_AUTO
, REPLACE_DISK_PRI
,
51 REPLACE_DISK_CHG
, REPLACE_DISK_SECONDARY
)
52 from ganeti_web
.views
.generic
import LoginRequiredMixin
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
):
678 self
.cluster
= cluster
680 qs
= owner_qs_for_cluster(cluster
)
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 def _configure_for_cluster(self
, cluster
):
938 self
.cluster
= cluster
940 qs
= Node
.objects
.filter(cluster
=cluster
)
941 self
.fields
["pnode"].queryset
= qs
942 self
.fields
["snode"].queryset
= qs
944 def _configure_for_template(self
, template
):
948 self
.fields
["ip_check"].initial
= template
.ip_check
949 self
.fields
["name_check"].initial
= template
.name_check
950 self
.fields
["pnode"].initial
= template
.pnode
951 self
.fields
["snode"].initial
= template
.snode
953 def _configure_for_iallocator(self
):
954 del self
.fields
["pnode"]
955 del self
.fields
["snode"]
956 self
.use_iallocator
= True
958 def _configure_for_disk_template(self
, template
):
959 # If its not drdb, we dont use the secondary node.
960 # If we're using the iallocator then this field
961 # will already be deleted.
962 if template
!= "drbd":
963 del self
.fields
["snode"]
967 # Ganeti will error on VM creation if an IP address check is requested
968 # but a name check is not.
969 data
= self
.cleaned_data
970 if (data
.get("ip_check") and not data
.get("name_check")):
971 msg
= ["Cannot perform IP check without name check"]
972 self
._errors
["ip_check"] = self
.error_class(msg
)
974 if not self
.use_iallocator
and data
.get('pnode') == data
.get('snode'):
975 raise forms
.ValidationError("The secondary node cannot be the "
981 class VMWizardPVMForm(Form
):
982 kernel_path
= CharField(label
=_("Kernel path"), max_length
=255)
983 root_path
= CharField(label
=_("Root path"), max_length
=255)
985 def _configure_for_cluster(self
, cluster
):
989 self
.cluster
= cluster
990 params
= cluster
.info
["hvparams"]["xen-pvm"]
992 self
.fields
["kernel_path"].initial
= params
["kernel_path"]
993 self
.fields
["root_path"].initial
= params
["root_path"]
995 def _configure_for_template(self
, template
):
999 self
.fields
["kernel_path"].initial
= template
.kernel_path
1000 self
.fields
["root_path"].initial
= template
.root_path
1003 class VMWizardHVMForm(Form
):
1004 boot_order
= ChoiceField(label
=_("Preferred boot device"),
1005 required
=False, choices
=HVM_BOOT_ORDER
,
1006 help_text
=_(VM_CREATE_HELP
['boot_order']))
1007 cdrom_image_path
= CharField(label
=_("CD-ROM image path"), max_length
=512,
1010 VM_CREATE_HELP
['cdrom_image_path']))
1011 disk_type
= ChoiceField(label
=_("Disk type"),
1012 choices
=HVM_CHOICES
["disk_type"],
1013 help_text
=_(VM_CREATE_HELP
['disk_type']))
1014 nic_type
= ChoiceField(label
=_("NIC type"),
1015 choices
=HVM_CHOICES
["nic_type"],
1016 help_text
=_(VM_CREATE_HELP
['nic_type']))
1018 def _configure_for_cluster(self
, cluster
):
1022 self
.cluster
= cluster
1023 params
= cluster
.info
["hvparams"]["xen-pvm"]
1025 self
.fields
["boot_order"].initial
= params
["boot_order"]
1026 self
.fields
["disk_type"].initial
= params
["disk_type"]
1027 self
.fields
["nic_type"].initial
= params
["nic_type"]
1029 def _configure_for_template(self
, template
):
1033 self
.fields
["boot_order"].initial
= template
.boot_order
1034 self
.fields
["cdrom_image_path"].initial
= template
.cdrom_image_path
1035 self
.fields
["disk_type"].initial
= template
.disk_type
1036 self
.fields
["nic_type"].initial
= template
.nic_type
1039 class VMWizardKVMForm(Form
):
1040 kernel_path
= CharField(label
=_("Kernel path"), max_length
=255,
1042 help_text
=_(VM_CREATE_HELP
['kernel_path']))
1043 root_path
= CharField(label
=_("Root path"), max_length
=255,
1044 help_text
=_(VM_CREATE_HELP
['root_path']))
1045 serial_console
= BooleanField(label
=_("Enable serial console"),
1048 VM_CREATE_HELP
['serial_console']))
1049 boot_order
= ChoiceField(label
=_("Preferred boot device"),
1050 required
=False, choices
=KVM_BOOT_ORDER
,
1051 help_text
=_(VM_CREATE_HELP
['boot_order']))
1052 cdrom_image_path
= CharField(label
=_("CD-ROM image path"), max_length
=512,
1055 VM_CREATE_HELP
['cdrom_image_path']))
1056 cdrom2_image_path
= CharField(label
=_("Second CD-ROM image path"),
1057 max_length
=512, required
=False,
1059 VM_CREATE_HELP
['cdrom2_image_path']))
1060 disk_type
= ChoiceField(label
=_("Disk type"),
1061 choices
=KVM_CHOICES
["disk_type"],
1062 help_text
=_(VM_CREATE_HELP
['disk_type']))
1063 nic_type
= ChoiceField(label
=_("NIC type"),
1064 choices
=KVM_CHOICES
["nic_type"],
1065 help_text
=_(VM_CREATE_HELP
['nic_type']))
1067 def _configure_for_cluster(self
, cluster
):
1071 self
.cluster
= cluster
1072 params
= cluster
.info
["hvparams"]["kvm"]
1074 self
.fields
["boot_order"].initial
= params
["boot_order"]
1075 self
.fields
["disk_type"].initial
= params
["disk_type"]
1076 self
.fields
["kernel_path"].initial
= params
["kernel_path"]
1077 self
.fields
["nic_type"].initial
= params
["nic_type"]
1078 self
.fields
["root_path"].initial
= params
["root_path"]
1079 self
.fields
["serial_console"].initial
= params
["serial_console"]
1081 # Remove cdrom2 if the cluster doesn't have it; see #11655.
1082 if not has_cdrom2(cluster
):
1083 del self
.fields
["cdrom2_image_path"]
1085 def _configure_for_template(self
, template
):
1089 self
.fields
["kernel_path"].initial
= template
.kernel_path
1090 self
.fields
["root_path"].initial
= template
.root_path
1091 self
.fields
["serial_console"].initial
= template
.serial_console
1092 self
.fields
["boot_order"].initial
= template
.boot_order
1093 self
.fields
["cdrom_image_path"].initial
= template
.cdrom_image_path
1094 self
.fields
["cdrom2_image_path"].initial
= template
.cdrom2_image_path
1095 self
.fields
["disk_type"].initial
= template
.disk_type
1096 self
.fields
["nic_type"].initial
= template
.nic_type
1099 data
= super(VMWizardKVMForm
, self
).clean()
1101 # Force cdrom disk type to IDE; see #9297.
1102 data
['cdrom_disk_type'] = 'ide'
1104 # If booting from CD-ROM, require the first CD-ROM image to be
1106 if (data
.get("boot_order") == "cdrom" and
1107 not data
.get("cdrom_image_path")):
1108 msg
= u
"%s." % _("Image path required if boot device is CD-ROM")
1109 self
._errors
["cdrom_image_path"] = self
.error_class([msg
])
1114 class VMWizardView(LoginRequiredMixin
, CookieWizardView
):
1115 template_name
= "ganeti/forms/vm_wizard.html"
1118 # value, display value
1119 # value corresponds to VMWizardOwnerForm's fields
1120 ('template_name', 'Template'),
1121 ('hostname', 'Virtual Machine'),
1124 def _get_vm_or_template(self
):
1125 """Returns items that were not checked in step0"""
1126 data
= self
.get_cleaned_data_for_step('0')
1128 options
= [option
[0] for option
in self
.OPTIONS
]
1129 choices
= data
.get('choices', None)
1130 # which boxes weren't checked
1131 unchecked
= set(options
) - set(choices
)
1136 def _get_template(self
):
1137 name
= self
.kwargs
.get("template")
1139 return VirtualMachineTemplate
.objects
.get(template_name
=name
)
1142 def _get_cluster(self
):
1143 data
= self
.get_cleaned_data_for_step("0")
1145 return data
["cluster"]
1149 data
= self
.get_cleaned_data_for_step("2")
1154 def _get_disk_template(self
):
1155 data
= self
.get_cleaned_data_for_step("2")
1157 return data
["disk_template"]
1160 def _get_iallocator(self
):
1161 data
= self
.get_cleaned_data_for_step("2")
1163 # This one is different because the iallocator might not exist.
1164 return data
.get("iallocator", False)
1167 def get_form(self
, step
=None, data
=None, files
=None):
1168 s
= int(self
.steps
.current
) if step
is None else int(step
)
1169 initial
= self
.get_form_initial(s
)
1172 form
= VMWizardClusterForm(data
=data
, options
=self
.OPTIONS
,
1174 form
._configure
_for
_user
(self
.request
.user
)
1175 # XXX this should somehow become totally invalid if the user
1176 # doesn't have perms on the template.
1178 form
= VMWizardOwnerForm(data
=data
)
1179 form
._configure
_for
_cluster
(self
._get
_cluster
())
1180 form
._configure
_for
_template
(self
._get
_template
(),
1181 choices
=self
._get
_vm
_or
_template
())
1183 form
= VMWizardBasicsForm(data
=data
)
1184 form
._configure
_for
_cluster
(self
._get
_cluster
())
1185 form
._configure
_for
_template
(self
._get
_template
())
1187 form
= VMWizardAdvancedForm(data
=data
)
1188 form
._configure
_for
_cluster
(self
._get
_cluster
())
1189 form
._configure
_for
_template
(self
._get
_template
())
1190 using_iallocator
= self
._get
_iallocator
()
1191 # Autoallocation means we dont need to configure the disk template
1192 if using_iallocator
:
1193 form
._configure
_for
_iallocator
()
1195 form
._configure
_for
_disk
_template
(self
._get
_disk
_template
())
1197 cluster
= self
._get
_cluster
()
1203 form
= VMWizardKVMForm(data
=data
)
1204 elif hv
== "xen-pvm":
1205 form
= VMWizardPVMForm(data
=data
)
1206 elif hv
== "xen-hvm":
1207 form
= VMWizardHVMForm(data
=data
)
1210 form
._configure
_for
_cluster
(cluster
)
1211 form
._configure
_for
_template
(self
._get
_template
())
1215 form
= super(VMWizardView
, self
).get_form(step
, data
, files
)
1219 def get_context_data(self
, form
, **kwargs
):
1220 context
= super(VMWizardView
, self
).get_context_data(form
=form
,
1223 "cluster_form": self
.get_cleaned_data_for_step("0"),
1224 "owner_form": self
.get_cleaned_data_for_step("1"),
1225 "basics_form": self
.get_cleaned_data_for_step("2"),
1226 "advanced_form": self
.get_cleaned_data_for_step("3"),
1227 "hv_form": self
.get_cleaned_data_for_step("4"),
1229 context
["summary"] = summary
1233 def done(self
, forms
, template
=None, **kwargs
):
1235 Create a template. Optionally, bind a template to a VM instance
1236 created from the template. Optionally, name the template and save it.
1237 One or both of those is done depending on what the user has requested.
1240 # Hack: accepting kwargs in order to be able to work in several
1243 if template
is None:
1244 template
= VirtualMachineTemplate()
1246 template
= self
._get
_template
()
1248 user
= self
.request
.user
1250 cluster
= forms
[0].cleaned_data
["cluster"]
1251 owner
= forms
[1].cleaned_data
["owner"]
1253 template_name
= forms
[1].cleaned_data
["template_name"]
1254 hostname
= forms
[1].cleaned_data
["hostname"]
1256 # choice_data are the options that were not checked
1257 # if unchecked, than we should make sure that this is not submitted.
1258 # this fixes cases where the user checked a box in the beginning, put
1259 # data into the input, and went back and unchecked that box later.
1260 unchecked_options
= self
._get
_vm
_or
_template
()
1261 for unchecked
in unchecked_options
:
1262 if 'template_name' == unchecked
:
1264 if 'hostname' == unchecked
:
1267 template
.cluster
= cluster
1268 template
.memory
= forms
[2].cleaned_data
["memory"]
1269 if has_balloonmem(cluster
):
1270 template
.minmem
= forms
[2].cleaned_data
["minram"]
1271 template
.vcpus
= forms
[2].cleaned_data
["vcpus"]
1272 template
.disk_template
= forms
[2].cleaned_data
["disk_template"]
1274 template
.disks
= forms
[2].cleaned_data
["disks"]
1276 nics
= forms
[2].cleaned_data
["nics"]
1279 nics
= [{"link": "br0", "mode": "bridged"}]
1280 template
.nics
= nics
1282 template
.hypervisor
= forms
[2].cleaned_data
["hv"]
1284 template
.os
= forms
[2].cleaned_data
["os"]
1285 template
.no_install
= forms
[2].cleaned_data
["no_install"]
1286 template
.iallocator
= forms
[2].cleaned_data
["iallocator"]
1287 template
.ip_check
= forms
[3].cleaned_data
["ip_check"]
1288 template
.name_check
= forms
[3].cleaned_data
["name_check"]
1289 template
.no_start
= forms
[3].cleaned_data
["no_start"]
1291 if not template
.iallocator
:
1292 template
.pnode
= forms
[3].cleaned_data
["pnode"].hostname
1293 if "snode" in forms
[3].cleaned_data
:
1294 template
.snode
= forms
[3].cleaned_data
["snode"].hostname
1296 hvparams
= forms
[4].cleaned_data
1298 template
.boot_order
= hvparams
.get("boot_order")
1299 template
.cdrom2_image_path
= hvparams
.get("cdrom2_image_path")
1300 template
.cdrom_image_path
= hvparams
.get("cdrom_image_path")
1301 template
.kernel_path
= hvparams
.get("kernel_path")
1302 template
.root_path
= hvparams
.get("root_path")
1303 template
.serial_console
= hvparams
.get("serial_console")
1304 template
.nic_type
= hvparams
.get('nic_type')
1305 template
.disk_type
= hvparams
.get('disk_type')
1307 template
.set_name(template_name
)
1308 # only save the template to the database if its not temporary
1309 if not template
.temporary
:
1313 vm
= template_to_instance(template
, hostname
, owner
)
1314 log_action('CREATE', user
, vm
)
1315 return HttpResponseRedirect(reverse('instance-detail',
1319 return HttpResponseRedirect(reverse("template-detail",
1324 def vm_wizard(*args
, **kwargs
):
1326 VMWizardClusterForm
,
1329 VMWizardAdvancedForm
,
1332 initial
= kwargs
.get('initial_dict', None)
1333 return VMWizardView
.as_view(forms
, initial_dict
=initial
)