1 # Copyright (C) 2010 Oregon State University et al.
2 # Copyright (C) 2010 Greek Research and Technology Network
4 # This program is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU General Public License
6 # as published by the Free Software Foundation; either version 2
7 # of the License, or (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
20 from django
.conf
import settings
21 from django
.contrib
.auth
.decorators
import login_required
22 from django
.contrib
.contenttypes
.models
import ContentType
23 from django
.core
.urlresolvers
import reverse
24 from django
.db
.models
import Q
25 from django
.forms
import CharField
, HiddenInput
26 from django
.http
import (HttpResponse
, HttpResponseRedirect
,
27 HttpResponseForbidden
, HttpResponseBadRequest
,
29 from django
.shortcuts
import get_object_or_404
, render_to_response
30 from django
.template
import RequestContext
31 from django
.utils
import simplejson
as json
32 from django
.utils
.translation
import ugettext
as _
33 from django
.views
.decorators
.http
import require_http_methods
, require_POST
34 from django
.views
.generic
.edit
import DeleteView
36 from django_tables2
import SingleTableView
38 from object_log
.views
import list_for_object
39 from object_log
.models
import LogItem
40 log_action
= LogItem
.objects
.log_action
42 from object_permissions
import get_users_any
43 from object_permissions
.signals
import (view_add_user
, view_edit_user
,
45 from object_permissions
.views
.permissions
import view_users
, view_permissions
48 from ganeti_web
.backend
.queries
import vm_qs_for_users
49 from ganeti_web
.caps
import has_shutdown_timeout
, has_balloonmem
50 from ganeti_web
.forms
.virtual_machine
import (KvmModifyVirtualMachineForm
,
51 PvmModifyVirtualMachineForm
,
52 HvmModifyVirtualMachineForm
,
53 ModifyConfirmForm
, MigrateForm
,
54 RenameForm
, ChangeOwnerForm
,
56 from ganeti_web
.middleware
import Http403
57 from ganeti_web
.models
import Cluster
, Job
, SSHKey
, VirtualMachine
58 from ganeti_web
.templatetags
.webmgr_tags
import render_storage
59 from ganeti_web
.util
.client
import GanetiApiError
60 from ganeti_web
.utilities
import (cluster_os_list
, compare
, os_prettify
,
62 from ganeti_web
.views
.generic
import (NO_PRIVS
, LoginRequiredMixin
,
63 PaginationMixin
, GWMBaseView
)
65 from ganeti_web
.views
.tables
import BaseVMTable
68 #XXX No more need for tastypie dependency for 0.8
69 class HttpAccepted(HttpResponse
):
71 Take from tastypie.http
73 In 0.9 when we reorganize the RestAPI, change this back
79 def get_vm_and_cluster_or_404(cluster_slug
, instance
):
81 Utility function for querying VirtualMachine and Cluster in a single query
82 rather than 2 separate calls to get_object_or_404.
84 query
= VirtualMachine
.objects \
85 .filter(cluster__slug
=cluster_slug
, hostname
=instance
) \
86 .select_related('cluster')
88 return query
[0], query
[0].cluster
89 raise Http404('Virtual Machine does not exist')
92 class BaseVMListView(LoginRequiredMixin
, PaginationMixin
, GWMBaseView
,
95 A view for listing VirtualMachines. It does so using a custom table object
96 containing the logic for displaying the list.
98 model
= VirtualMachine
99 table_class
= BaseVMTable
100 template_name
= "ganeti/virtual_machine/list.html"
102 def get_template_names(self
):
103 if self
.request
.is_ajax():
104 template
= ['table.html'] # all we need is the table
106 template
= ['ganeti/virtual_machine/list.html']
110 class VMListView(BaseVMListView
):
111 def get_queryset(self
):
112 # queryset takes precedence over model
113 self
.queryset
= vm_qs_for_users(self
.request
.user
)
114 qs
= super(BaseVMListView
, self
).get_queryset()
117 def get_context_data(self
, **kwargs
):
118 context
= super(VMListView
, self
).get_context_data(**kwargs
)
119 context
["ajax_url"] = reverse("virtualmachine-list")
120 # check perms, and send them to the table for rendering
121 # pass in the Cluster Class to check all clusters
122 can_create
= self
.can_create(Cluster
)
123 context
["table"].can_create
= context
["create_vm"] = can_create
127 class VMDeleteView(LoginRequiredMixin
, DeleteView
):
132 template_name
= "ganeti/virtual_machine/delete.html"
134 def get_context_data(self
, **kwargs
):
135 kwargs
["vm"] = self
.vm
136 kwargs
["cluster"] = self
.cluster
137 return super(VMDeleteView
, self
).get_context_data(**kwargs
)
139 def get_object(self
):
140 user
= self
.request
.user
141 vm
, cluster
= get_vm_and_cluster_or_404(self
.kwargs
["cluster_slug"],
142 self
.kwargs
["instance"])
145 user
.has_any_perms(vm
, ["remove", "admin"]) or
146 user
.has_perm("admin", cluster
)):
147 raise Http403(NO_PRIVS
)
150 self
.cluster
= cluster
153 def get_success_url(self
):
154 return reverse('instance-detail', args
=[self
.kwargs
["cluster_slug"],
157 def delete(self
, *args
, **kwargs
):
158 vm
= self
.get_object()
160 return HttpResponseRedirect(self
.get_success_url())
162 def _destroy(self
, instance
):
164 Actually destroy a VM.
166 Well, kind of. We won't destroy it immediately if it still exists in
167 Ganeti; in that case, we'll only mark it for later removal and start
168 the Ganeti job to destroy it.
171 # Check that the VM still exists in Ganeti. If it doesn't, then just
175 except GanetiApiError
, e
:
181 # Clear any old jobs for this VM.
182 ct
= ContentType
.objects
.get_for_model(VirtualMachine
)
183 Job
.objects
.filter(content_type
=ct
, object_id
=instance
.id).delete()
185 # Create the deletion job.
186 job_id
= instance
.rapi
.DeleteInstance(instance
.hostname
)
187 job
= Job
.objects
.create(job_id
=job_id
, obj
=instance
,
188 cluster_id
=instance
.cluster_id
)
190 # Mark the VM as pending deletion. Also disable its cache.
191 instance
.last_job
= job
192 instance
.ignore_cache
= True
193 instance
.pending_delete
= True
197 @require_http_methods(["GET", "POST"])
199 def reinstall(request
, cluster_slug
, instance
):
205 instance
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
208 # XXX Reinstalling is somewhat similar to
209 # deleting in that you destroy data,
210 # so use that for now.
213 user
.has_any_perms(instance
, ["remove", "admin"]) or
214 user
.has_perm("admin", cluster
)):
215 raise Http403(NO_PRIVS
)
217 if request
.method
== 'GET':
218 return render_to_response(
219 "ganeti/virtual_machine/reinstall.html",
220 {'vm': instance
, 'oschoices': cluster_os_list(cluster
),
221 'current_os': instance
.operating_system
,
223 context_instance
=RequestContext(request
),
226 elif request
.method
== 'POST':
228 if "os" in request
.POST
:
229 os
= request
.POST
["os"]
231 os
= instance
.operating_system
233 # XXX no_startup=True prevents quota circumventions.
234 # possible future solution would be a checkbox
235 # asking whether they want to start up, and check
236 # quota here if they do (would also involve
237 # checking whether this VM is already running and subtracting that)
239 job_id
= instance
.rapi \
240 .ReinstallInstance(instance
.hostname
, os
=os
, no_startup
=True)
241 job
= Job
.objects
.create(job_id
=job_id
, obj
=instance
, cluster
=cluster
)
242 VirtualMachine
.objects \
243 .filter(id=instance
.id).update(last_job
=job
, ignore_cache
=True)
246 log_action('VM_REINSTALL', user
, instance
, job
)
248 return HttpResponseRedirect(
249 reverse('instance-detail', args
=[cluster
.slug
, instance
.hostname
]))
256 template
="ganeti/virtual_machine/novnc.html"):
257 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
258 cluster__slug
=cluster_slug
)
260 if not (user
.is_superuser
261 or user
.has_any_perms(vm
, ['admin', 'power'])
262 or user
.has_perm('admin', vm
.cluster
)):
263 return HttpResponseForbidden(_('You do not have permission '
266 return render_to_response(template
,
267 {'cluster_slug': cluster_slug
,
270 context_instance
=RequestContext(request
), )
275 def vnc_proxy(request
, cluster_slug
, instance
):
276 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
277 cluster__slug
=cluster_slug
)
279 if not (user
.is_superuser
280 or user
.has_any_perms(vm
, ['admin', 'power'])
281 or user
.has_perm('admin', vm
.cluster
)):
282 return HttpResponseForbidden(_('You do not have permission '
285 use_tls
= bool(request
.POST
.get("tls"))
286 result
= json
.dumps(vm
.setup_vnc_forwarding(tls
=use_tls
))
288 return HttpResponse(result
, mimetype
="application/json")
293 def shutdown(request
, cluster_slug
, instance
):
294 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
295 cluster__slug
=cluster_slug
)
298 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'power']) or
299 user
.has_perm('admin', vm
.cluster
)):
300 msg
= _('You do not have permission to shut down this virtual machine')
308 # log information about stopping the machine
309 log_action('VM_STOP', user
, vm
, job
)
310 except GanetiApiError
, e
:
311 msg
= {'__all__': [str(e
)]}
313 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
318 def shutdown_now(request
, cluster_slug
, instance
):
319 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
320 cluster__slug
=cluster_slug
)
323 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'power']) or
324 user
.has_perm('admin', vm
.cluster
)):
325 msg
= _('You do not have permission to shut down this virtual machine')
329 job
= vm
.shutdown(timeout
=0)
333 # log information about stopping the machine
334 log_action('VM_STOP', user
, vm
, job
)
335 except GanetiApiError
, e
:
336 msg
= {'__all__': [str(e
)]}
338 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
343 def startup(request
, cluster_slug
, instance
, rest
=False):
344 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
345 cluster__slug
=cluster_slug
)
347 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'power']) or
348 user
.has_perm('admin', vm
.cluster
)):
349 msg
= _('You do not have permission to start up this virtual machine')
351 return {"msg": msg
, "code": 403}
355 # superusers bypass quota checks
356 if not user
.is_superuser
and vm
.owner
:
358 quota
= vm
.cluster
.get_quota(vm
.owner
)
359 if any(quota
.values()):
360 used
= vm
.owner
.used_resources(vm
.cluster
, only_running
=True)
362 if quota
['ram'] is not None \
363 and (used
['ram'] + vm
.ram
) > quota
['ram']:
364 msg
= _('Owner does not have enough RAM remaining on '
365 'this cluster to start the virtual machine.')
367 return {"msg": msg
, "code": 500}
369 return HttpResponse(json
.dumps([0, msg
]),
370 mimetype
='application/json')
372 if quota
['virtual_cpus'] and \
373 (used
['virtual_cpus'] + vm
.virtual_cpus
) \
374 > quota
['virtual_cpus']:
375 msg
= _('Owner does not have enough Virtual CPUs remaining '
376 'on this cluster to start the virtual machine.')
378 return {"msg": msg
, "code": 500}
380 return HttpResponse(json
.dumps([0, msg
]),
381 mimetype
='application/json')
388 # log information about starting up the machine
389 log_action('VM_START', user
, vm
, job
)
390 except GanetiApiError
, e
:
391 msg
= {'__all__': [str(e
)]}
393 return {"msg": msg
, "code": 200}
395 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
399 def migrate(request
, cluster_slug
, instance
):
401 view used for initiating a Node Migrate job
403 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
406 if not (user
.is_superuser
or
407 user
.has_any_perms(cluster
, ['admin', 'migrate'])):
408 raise Http403(NO_PRIVS
)
410 if request
.method
== 'POST':
411 form
= MigrateForm(request
.POST
)
414 job
= vm
.migrate(form
.cleaned_data
['mode'])
416 content
= json
.dumps(job
.info
)
419 log_action('VM_MIGRATE', user
, vm
, job
)
420 except GanetiApiError
, e
:
421 content
= json
.dumps({'__all__': [str(e
)]})
423 # error in form return ajax response
424 content
= json
.dumps(form
.errors
)
425 return HttpResponse(content
, mimetype
='application/json')
430 return render_to_response('ganeti/virtual_machine/migrate.html',
431 {'form': form
, 'vm': vm
, 'cluster': cluster
},
432 context_instance
=RequestContext(request
))
436 def replace_disks(request
, cluster_slug
, instance
):
438 view used for initiating a Replace Disks job
440 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
442 if not (user
.is_superuser
or
443 user
.has_any_perms(cluster
, ['admin', 'replace_disks'])):
444 raise Http403(NO_PRIVS
)
446 if request
.method
== 'POST':
447 form
= ReplaceDisksForm(vm
, request
.POST
)
452 content
= json
.dumps(job
.info
)
455 log_action('VM_REPLACE_DISKS', user
, vm
, job
)
456 except GanetiApiError
, e
:
457 content
= json
.dumps({'__all__': [str(e
)]})
459 # error in form return ajax response
460 content
= json
.dumps(form
.errors
)
461 return HttpResponse(content
, mimetype
='application/json')
464 form
= ReplaceDisksForm(vm
)
466 return render_to_response('ganeti/virtual_machine/replace_disks.html',
467 {'form': form
, 'vm': vm
, 'cluster': cluster
},
468 context_instance
=RequestContext(request
))
473 def reboot(request
, cluster_slug
, instance
, rest
=False):
474 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
475 cluster__slug
=cluster_slug
)
477 if not (user
.is_superuser
or
478 user
.has_any_perms(vm
, ['admin', 'power']) or
479 user
.has_perm('admin', vm
.cluster
)):
482 return HttpResponseForbidden()
484 raise Http403(_('You do not have permission to '
485 'reboot this virtual machine'))
492 # log information about restarting the machine
493 log_action('VM_REBOOT', user
, vm
, job
)
494 except GanetiApiError
, e
:
495 msg
= {'__all__': [str(e
)]}
497 return HttpAccepted()
499 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
502 def ssh_keys(request
, cluster_slug
, instance
, api_key
):
504 Show all ssh keys which belong to users, who are specified vm's admin
506 if settings
.WEB_MGR_API_KEY
!= api_key
:
507 return HttpResponseForbidden(_("You're not allowed to view keys."))
509 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
510 cluster__slug
=cluster_slug
)
512 users
= get_users_any(vm
, ["admin", ]).values_list("id", flat
=True)
513 keys
= SSHKey
.objects \
514 .filter(Q(user__in
=users
) |
Q(user__is_superuser
=True)) \
515 .values_list('key', 'user__username') \
516 .order_by('user__username')
518 keys_list
= list(keys
)
519 return HttpResponse(json
.dumps(keys_list
), mimetype
="application/json")
523 def detail(request
, cluster_slug
, instance
, rest
=False):
525 Display details of virtual machine.
527 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
530 cluster_admin
= (user
.is_superuser
or
531 user
.has_any_perms(cluster
, perms
=['admin', 'create_vm']))
533 if not cluster_admin
:
534 perms
= user
.get_perms(vm
)
536 if cluster_admin
or 'admin' in perms
:
545 remove
= 'remove' in perms
546 power
= 'power' in perms
547 modify
= 'modify' in perms
548 tags
= 'tags' in perms
549 migrate
= 'migrate' in perms
551 if not (admin
or power
or remove
or modify
or tags
): # TODO REST
552 raise Http403(_('You do not have permission to view '
553 'this virtual machines\'s details'))
559 'cluster_admin': cluster_admin
,
564 "has_immediate_shutdown": has_shutdown_timeout(cluster
),
567 # check job for pending jobs that should be rendered with a different
568 # detail template. This allows us to reduce the chance that users will do
569 # something strange like rebooting a VM that is being deleted or is not
571 if vm
.pending_delete
:
572 template
= 'ganeti/virtual_machine/delete_status.html'
574 template
= 'ganeti/virtual_machine/create_status.html'
576 context
['job'] = vm
.last_job
578 ct
= ContentType
.objects
.get_for_model(vm
)
579 jobs_loc
= Job
.objects
.order_by('-finished') \
580 .filter(content_type
=ct
, object_id
=vm
.pk
)
581 if jobs_loc
.count() > 0:
582 context
['job'] = jobs_loc
[0]
584 context
['job'] = None
586 template
= 'ganeti/virtual_machine/detail.html'
591 return render_to_response(template
, context
,
592 context_instance
=RequestContext(request
), )
596 def users(request
, cluster_slug
, instance
, rest
=False):
598 Display all of the Users of a VirtualMachine
600 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
603 if not (user
.is_superuser
or user
.has_perm('admin', vm
) or
604 user
.has_perm('admin', cluster
)):
606 return {'msg': NO_PRIVS
, 'code': 403}
608 raise Http403(NO_PRIVS
)
610 url
= reverse('vm-permissions', args
=[cluster
.slug
, vm
.hostname
])
611 return view_users(request
, vm
, url
, rest
=rest
)
615 def permissions(request
, cluster_slug
, instance
, user_id
=None, group_id
=None):
617 Update a users permissions.
619 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
620 cluster__slug
=cluster_slug
)
623 if not (user
.is_superuser
or user
.has_perm('admin', vm
) or
624 user
.has_perm('admin', vm
.cluster
)):
625 raise Http403(NO_PRIVS
)
627 url
= reverse('vm-permissions', args
=[cluster_slug
, vm
.hostname
])
628 return view_permissions(request
, vm
, url
, user_id
, group_id
)
632 def object_log(request
, cluster_slug
, instance
, rest
=False):
634 Display all of the Users of a VirtualMachine
636 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
637 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
)
640 if not (user
.is_superuser
or user
.has_perm('admin', vm
) or
641 user
.has_perm('admin', cluster
)):
642 raise Http403(NO_PRIVS
)
645 return list_for_object(request
, vm
, True)
647 return list_for_object(request
, vm
)
651 def modify(request
, cluster_slug
, instance
):
652 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
655 if not (user
.is_superuser
656 or user
.has_any_perms(vm
, ['admin', 'modify'])
657 or user
.has_perm('admin', cluster
)):
659 'You do not have permissions to edit this virtual machine')
661 hv
= get_hypervisor(vm
)
663 hv_form
= KvmModifyVirtualMachineForm
664 template
= 'ganeti/virtual_machine/edit_kvm.html'
665 elif hv
== 'xen-pvm':
666 hv_form
= PvmModifyVirtualMachineForm
667 template
= 'ganeti/virtual_machine/edit_pvm.html'
668 elif hv
== 'xen-hvm':
669 hv_form
= HvmModifyVirtualMachineForm
670 template
= 'ganeti/virtual_machine/edit_hvm.html'
673 template
= 'ganeti/virtual_machine/edit_base.html'
674 # XXX no matter what, we're gonna call hv_form() and die. Let's do it
675 # louder than usual. >:3
676 msg
= "Hey, guys, implementation error in views/vm.py:modify"
677 raise RuntimeError(msg
)
679 if request
.method
== 'POST':
681 form
= hv_form(vm
, request
.POST
)
683 form
.owner
= vm
.owner
685 form
.cluster
= cluster
687 data
= form
.cleaned_data
688 request
.session
['edit_form'] = data
689 request
.session
['edit_vm'] = vm
.id
690 return HttpResponseRedirect(
691 reverse('instance-modify-confirm',
695 elif request
.method
== 'GET':
696 if 'edit_form' in request
.session \
697 and vm
.id == request
.session
['edit_vm']:
698 form
= hv_form(vm
, request
.session
['edit_form'])
702 return render_to_response(
707 'balloon': has_balloonmem(cluster
)
709 context_instance
=RequestContext(request
),
713 # XXX mother, did it need to be so long?
715 def modify_confirm(request
, cluster_slug
, instance
):
716 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
718 hv
= get_hypervisor(vm
)
720 hv_form
= KvmModifyVirtualMachineForm
721 elif hv
== 'xen-pvm':
722 hv_form
= PvmModifyVirtualMachineForm
723 elif hv
== 'xen-hvm':
724 hv_form
= HvmModifyVirtualMachineForm
727 # XXX no matter what, we're gonna call hv_form() and die. Let's do it
728 # louder than usual. >:3
729 msg
= "Hey, guys, implementation error in views/vm.py:modify_confirm"
730 raise RuntimeError(msg
)
733 power
= user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'power'])
734 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'modify'])
735 or user
.has_perm('admin', cluster
)):
737 _('You do not have permissions to edit this virtual machine'))
739 if request
.method
== "POST":
740 if 'edit' in request
.POST
:
741 return HttpResponseRedirect(
742 reverse("instance-modify",
743 args
=[cluster
.slug
, vm
.hostname
]))
744 elif 'reboot' in request
.POST
or 'save' in request
.POST
:
745 form
= ModifyConfirmForm(request
.POST
)
746 form
.session
= request
.session
747 form
.owner
= vm
.owner
749 form
.cluster
= cluster
753 data
= form
.cleaned_data
754 rapi_dict
= data
['rapi_dict']
755 nics
= rapi_dict
.pop('nics')
756 beparams
['vcpus'] = rapi_dict
.pop('vcpus')
757 if has_balloonmem(cluster
):
758 beparams
['maxmem'] = rapi_dict
.pop('maxmem')
759 beparams
['minmem'] = rapi_dict
.pop('minmem')
761 beparams
['memroy'] = rapi_dict
.pop('memory')
762 os_name
= rapi_dict
.pop('os')
763 job_id
= cluster
.rapi
.ModifyInstance(
769 # Create job and update message on virtual machine detail page
770 job
= Job
.objects
.create(job_id
=job_id
,
773 VirtualMachine
.objects \
774 .filter(id=vm
.id).update(last_job
=job
, ignore_cache
=True)
775 # log information about modifying this instance
776 log_action('EDIT', user
, vm
)
777 if 'reboot' in request
.POST
and vm
.info
['status'] == 'running':
781 log_action('VM_REBOOT', user
, vm
, job
)
784 _("Sorry, but you do not have permission "
785 "to reboot this machine."))
787 # Redirect to instance-detail
788 return HttpResponseRedirect(
789 reverse("instance-detail",
790 args
=[cluster
.slug
, vm
.hostname
]))
792 elif 'cancel' in request
.POST
:
793 # Remove session variables.
794 if 'edit_form' in request
.session
:
795 del request
.session
['edit_form']
796 # Redirect to instance-detail
797 return HttpResponseRedirect(
798 reverse("instance-detail", args
=[cluster
.slug
, vm
.hostname
]))
800 elif request
.method
== "GET":
801 form
= ModifyConfirmForm()
803 session
= request
.session
805 if not 'edit_form' in request
.session
:
806 return HttpResponseBadRequest('Incorrect Session Data')
808 data
= session
['edit_form']
810 hvparams
= info
['hvparams']
813 vcpus
=info
['beparams']['vcpus'],
816 if has_balloonmem(cluster
):
817 old_set
['maxmem'] = info
['beparams']['maxmem']
818 old_set
['minmem'] = info
['beparams']['minmem']
820 old_set
['memory'] = info
['beparams']['memory']
821 nic_count
= len(info
['nic.links'])
822 for i
in xrange(nic_count
):
823 old_set
['nic_link_%s' % i
] = info
['nic.links'][i
]
824 old_set
['nic_mac_%s' % i
] = info
['nic.macs'][i
]
826 # Add hvparams to the old_set
827 old_set
.update(hvparams
)
830 fields
= hv_form(vm
, data
).fields
831 for key
in data
.keys():
832 if key
in ['memory', 'maxmem', 'minmem']:
833 diff
= compare(render_storage(old_set
[key
]),
834 render_storage(data
[key
]))
836 oses
= os_prettify([old_set
[key
], data
[key
]])
839 XXX - Special case for a cluster with two different types of
840 optgroups (i.e. Image, Debootstrap).
841 The elements at 00 and 10:
843 The elements at 010 and 110:
844 Tuple containing the OS Name and OS value.
845 The elements at 0101 and 1101:
846 String containing the OS Name
848 oses
[0][1][0] = list(oses
[0][1][0])
849 oses
[1][1][0] = list(oses
[1][1][0])
850 oses
[0][1][0][1] = '%s (%s)' % (oses
[0][1][0][1], oses
[0][0])
851 oses
[1][1][0][1] = '%s (%s)' % (oses
[1][1][0][1], oses
[1][0])
852 oses
= oses
[0][1] + oses
[1][1]
853 diff
= compare(oses
[0][1], oses
[1][1])
856 diff
= compare(oses
[0][1], oses
[1][1])
857 #diff = compare(oses[0][1], oses[1][1])
858 if key
in ['nic_count', 'nic_count_original']:
860 elif key
not in old_set
.keys():
862 instance_diff
[fields
[key
].label
] = _('Added')
864 diff
= compare(old_set
[key
], data
[key
])
867 label
= fields
[key
].label
868 instance_diff
[label
] = diff
870 # remove mac if it has not changed
871 for i
in xrange(nic_count
):
872 if fields
['nic_mac_%s' % i
].label
not in instance_diff
:
873 del data
['nic_mac_%s' % i
]
875 # Repopulate form with changed values
876 form
.fields
['rapi_dict'] = CharField(widget
=HiddenInput
,
877 initial
=json
.dumps(data
))
879 return render_to_response(
880 'ganeti/virtual_machine/edit_confirm.html', {
884 'instance_diff': instance_diff
,
887 context_instance
=RequestContext(request
),
892 def rename(request
, cluster_slug
, instance
, rest
=False, extracted_params
=None):
894 Rename an existing instance
896 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
899 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'modify'])
900 or user
.has_perm('admin', cluster
)):
902 _('You do not have permissions to edit this virtual machine'))
904 if request
.method
== 'POST':
905 form
= RenameForm(vm
, request
.POST
)
907 if rest
and extracted_params
is not None:
908 if all(k
in extracted_params
909 for k
in ("hostname", "ip_check", "name_check")):
910 hostname
= extracted_params
['hostname']
911 ip_check
= extracted_params
['ip_check']
912 name_check
= extracted_params
['name_check']
915 return HttpResponseBadRequest()
918 data
= form
.cleaned_data
919 hostname
= data
['hostname']
920 ip_check
= data
['ip_check']
921 name_check
= data
['name_check']
923 if form
.is_valid() or params_ok
:
925 # In order for rename to work correctly, the vm must first be
929 log_action('VM_STOP', user
, vm
, job1
)
931 job_id
= vm
.rapi
.RenameInstance(vm
.hostname
, hostname
,
932 ip_check
, name_check
)
933 job
= Job
.objects
.create(job_id
=job_id
,
936 VirtualMachine
.objects
.filter(pk
=vm
.pk
) \
937 .update(hostname
=hostname
, last_job
=job
, ignore_cache
=True)
939 # slip the new hostname to the log action
940 vm
.newname
= hostname
942 # log information about creating the machine
943 log_action('VM_RENAME', user
, vm
, job
)
946 return HttpResponseRedirect(
947 reverse('instance-detail',
948 args
=[cluster
.slug
, hostname
]))
950 return HttpAccepted()
952 except GanetiApiError
, e
:
953 msg
= 'Error renaming virtual machine: %s' % e
954 form
._errors
["cluster"] = form
.error_class([msg
])
956 return HttpResponse(400, content
=msg
)
958 elif request
.method
== 'GET':
959 form
= RenameForm(vm
)
961 return render_to_response(
962 'ganeti/virtual_machine/rename.html',
967 context_instance
=RequestContext(request
), )
971 def reparent(request
, cluster_slug
, instance
):
973 update a virtual machine to have a new owner
975 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
978 if not (user
.is_superuser
or user
.has_perm('admin', cluster
)):
980 _('You do not have permissions to change the owner '
981 'of this virtual machine'))
983 if request
.method
== 'POST':
984 form
= ChangeOwnerForm(request
.POST
)
986 data
= form
.cleaned_data
987 vm
.owner
= data
['owner']
988 vm
.save(force_update
=True)
990 # log information about creating the machine
991 log_action('VM_MODIFY', user
, vm
)
993 return HttpResponseRedirect(
994 reverse('instance-detail', args
=[cluster_slug
, instance
]))
997 form
= ChangeOwnerForm()
999 return render_to_response(
1000 'ganeti/virtual_machine/reparent.html', {
1005 context_instance
=RequestContext(request
),
1010 def job_status(request
, id, rest
=False):
1012 Return a list of basic info for running jobs.
1014 ct
= ContentType
.objects
.get_for_model(VirtualMachine
)
1015 jobs
= Job
.objects
.filter(status__in
=("error", "running", "waiting"),
1017 object_id
=id).order_by('job_id')
1018 jobs
= [j
.info
for j
in jobs
]
1023 return HttpResponse(json
.dumps(jobs
), mimetype
='application/json')
1026 def recv_user_add(sender
, editor
, user
, obj
, **kwargs
):
1028 receiver for object_permissions.signals.view_add_user, Logs action
1030 log_action('ADD_USER', editor
, obj
, user
)
1033 def recv_user_remove(sender
, editor
, user
, obj
, **kwargs
):
1035 receiver for object_permissions.signals.view_remove_user, Logs action
1037 log_action('REMOVE_USER', editor
, obj
, user
)
1040 def recv_perm_edit(sender
, editor
, user
, obj
, **kwargs
):
1042 receiver for object_permissions.signals.view_edit_user, Logs action
1044 log_action('MODIFY_PERMS', editor
, obj
, user
)
1047 view_add_user
.connect(recv_user_add
, sender
=VirtualMachine
)
1048 view_remove_user
.connect(recv_user_remove
, sender
=VirtualMachine
)
1049 view_edit_user
.connect(recv_perm_edit
, sender
=VirtualMachine
)