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 object_log
.views
import list_for_object
38 from object_permissions
import get_users_any
39 from object_permissions
.signals
import (view_add_user
, view_edit_user
,
41 from object_permissions
.views
.permissions
import view_users
, view_permissions
43 from object_log
.models
import LogItem
44 log_action
= LogItem
.objects
.log_action
46 from ganeti_web
.backend
.queries
import vm_qs_for_users
47 from ganeti_web
.caps
import has_shutdown_timeout
, has_balloonmem
48 from ganeti_web
.forms
.virtual_machine
import (KvmModifyVirtualMachineForm
,
49 PvmModifyVirtualMachineForm
,
50 HvmModifyVirtualMachineForm
,
51 ModifyConfirmForm
, MigrateForm
,
52 RenameForm
, ChangeOwnerForm
,
54 from ganeti_web
.middleware
import Http403
55 from ganeti_web
.models
import Cluster
, Job
, SSHKey
, Node
, VirtualMachine
56 from ganeti_web
.templatetags
.webmgr_tags
import render_storage
57 from ganeti_web
.util
.client
import GanetiApiError
58 from ganeti_web
.utilities
import (cluster_os_list
, compare
, os_prettify
,
60 from ganeti_web
.views
.generic
import (NO_PRIVS
, LoginRequiredMixin
,
64 #XXX No more need for tastypie dependency for 0.8
65 class HttpAccepted(HttpResponse
):
67 Take from tastypie.http
69 In 0.9 when we reorganize the RestAPI, change this back
75 def get_vm_and_cluster_or_404(cluster_slug
, instance
):
77 Utility function for querying VirtualMachine and Cluster in a single query
78 rather than 2 separate calls to get_object_or_404.
80 query
= VirtualMachine
.objects \
81 .filter(cluster__slug
=cluster_slug
, hostname
=instance
) \
82 .select_related('cluster')
84 return query
[0], query
[0].cluster
85 raise Http404('Virtual Machine does not exist')
88 class VMListView(LoginRequiredMixin
, PagedListView
):
90 View for displaying a list of VirtualMachines.
92 template_name
= "ganeti/virtual_machine/list.html"
94 def get_queryset(self
):
95 qs
= vm_qs_for_users(self
.request
.user
)
96 return qs
.select_related()
98 def get_context_data(self
, **kwargs
):
99 user
= self
.request
.user
100 context
= super(VMListView
, self
) \
101 .get_context_data(object_list
=kwargs
["object_list"])
102 context
["can_create"] = (user
.is_superuser
or
103 user
.has_any_perms(Cluster
, ["create_vm"]))
105 if "order_by" in self
.request
.GET
:
106 context
["order"] = self
.request
.GET
["order_by"]
108 context
["order"] = "hostname"
113 class VMListTableView(VMListView
):
115 View for displaying the virtual machine table.
117 This is used for ajax calls to reload the table, usually because of a page
120 It's very similar to the ``VMListView``, but has some additional filters
121 which can be applied.
124 def get_queryset(self
):
125 qs
= super(VMListTableView
, self
).get_queryset()
127 if "cluster_slug" in self
.kwargs
:
128 self
.cluster
= Cluster
.objects \
129 .get(slug
=self
.kwargs
["cluster_slug"])
130 qs
= qs
.filter(cluster
=self
.cluster
)
134 # filter the vms by primary node if applicable
135 if "primary_node" in self
.kwargs
:
136 inner
= Node
.objects
.filter(hostname
=self
.kwargs
["primary_node"])
137 qs
= qs
.filter(primary_node
=inner
)
139 # filter the vms by secondary node if applicable
140 if "secondary_node" in self
.kwargs
:
141 inner
= Node
.objects
.filter(hostname
=self
.kwargs
["secondary_node"])
142 qs
= qs
.filter(secondary_node
=inner
)
147 class VMDeleteView(LoginRequiredMixin
, DeleteView
):
152 template_name
= "ganeti/virtual_machine/delete.html"
154 def get_context_data(self
, **kwargs
):
155 kwargs
["vm"] = self
.vm
156 kwargs
["cluster"] = self
.cluster
157 return super(VMDeleteView
, self
).get_context_data(**kwargs
)
159 def get_object(self
):
160 user
= self
.request
.user
161 vm
, cluster
= get_vm_and_cluster_or_404(self
.kwargs
["cluster_slug"],
162 self
.kwargs
["instance"])
165 user
.has_any_perms(vm
, ["remove", "admin"]) or
166 user
.has_perm("admin", cluster
)):
167 raise Http403(NO_PRIVS
)
170 self
.cluster
= cluster
173 def get_success_url(self
):
174 return reverse('instance-detail', args
=[self
.kwargs
["cluster_slug"],
177 def delete(self
, *args
, **kwargs
):
178 vm
= self
.get_object()
180 return HttpResponseRedirect(self
.get_success_url())
182 def _destroy(self
, instance
):
184 Actually destroy a VM.
186 Well, kind of. We won't destroy it immediately if it still exists in
187 Ganeti; in that case, we'll only mark it for later removal and start
188 the Ganeti job to destroy it.
191 # Check that the VM still exists in Ganeti. If it doesn't, then just
195 except GanetiApiError
, e
:
201 # Clear any old jobs for this VM.
202 ct
= ContentType
.objects
.get_for_model(VirtualMachine
)
203 Job
.objects
.filter(content_type
=ct
, object_id
=instance
.id).delete()
205 # Create the deletion job.
206 job_id
= instance
.rapi
.DeleteInstance(instance
.hostname
)
207 job
= Job
.objects
.create(job_id
=job_id
, obj
=instance
,
208 cluster_id
=instance
.cluster_id
)
210 # Mark the VM as pending deletion. Also disable its cache.
211 instance
.last_job
= job
212 instance
.ignore_cache
= True
213 instance
.pending_delete
= True
217 @require_http_methods(["GET", "POST"])
219 def reinstall(request
, cluster_slug
, instance
):
225 instance
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
228 # XXX Reinstalling is somewhat similar to
229 # deleting in that you destroy data,
230 # so use that for now.
233 user
.has_any_perms(instance
, ["remove", "admin"]) or
234 user
.has_perm("admin", cluster
)):
235 raise Http403(NO_PRIVS
)
237 if request
.method
== 'GET':
238 return render_to_response(
239 "ganeti/virtual_machine/reinstall.html",
240 {'vm': instance
, 'oschoices': cluster_os_list(cluster
),
241 'current_os': instance
.operating_system
,
243 context_instance
=RequestContext(request
),
246 elif request
.method
== 'POST':
248 if "os" in request
.POST
:
249 os
= request
.POST
["os"]
251 os
= instance
.operating_system
253 # XXX no_startup=True prevents quota circumventions.
254 # possible future solution would be a checkbox
255 # asking whether they want to start up, and check
256 # quota here if they do (would also involve
257 # checking whether this VM is already running and subtracting that)
259 job_id
= instance
.rapi \
260 .ReinstallInstance(instance
.hostname
, os
=os
, no_startup
=True)
261 job
= Job
.objects
.create(job_id
=job_id
, obj
=instance
, cluster
=cluster
)
262 VirtualMachine
.objects \
263 .filter(id=instance
.id).update(last_job
=job
, ignore_cache
=True)
266 log_action('VM_REINSTALL', user
, instance
, job
)
268 return HttpResponseRedirect(
269 reverse('instance-detail', args
=[cluster
.slug
, instance
.hostname
]))
276 template
="ganeti/virtual_machine/novnc.html"):
277 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
278 cluster__slug
=cluster_slug
)
280 if not (user
.is_superuser
281 or user
.has_any_perms(vm
, ['admin', 'power'])
282 or user
.has_perm('admin', vm
.cluster
)):
283 return HttpResponseForbidden(_('You do not have permission '
286 return render_to_response(template
,
287 {'cluster_slug': cluster_slug
,
290 context_instance
=RequestContext(request
), )
295 def vnc_proxy(request
, cluster_slug
, instance
):
296 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
297 cluster__slug
=cluster_slug
)
299 if not (user
.is_superuser
300 or user
.has_any_perms(vm
, ['admin', 'power'])
301 or user
.has_perm('admin', vm
.cluster
)):
302 return HttpResponseForbidden(_('You do not have permission '
305 use_tls
= bool(request
.POST
.get("tls"))
306 result
= json
.dumps(vm
.setup_vnc_forwarding(tls
=use_tls
))
308 return HttpResponse(result
, mimetype
="application/json")
313 def shutdown(request
, cluster_slug
, instance
):
314 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
315 cluster__slug
=cluster_slug
)
318 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'power']) or
319 user
.has_perm('admin', vm
.cluster
)):
320 msg
= _('You do not have permission to shut down this virtual machine')
328 # log information about stopping the machine
329 log_action('VM_STOP', user
, vm
, job
)
330 except GanetiApiError
, e
:
331 msg
= {'__all__': [str(e
)]}
333 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
338 def shutdown_now(request
, cluster_slug
, instance
):
339 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
340 cluster__slug
=cluster_slug
)
343 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'power']) or
344 user
.has_perm('admin', vm
.cluster
)):
345 msg
= _('You do not have permission to shut down this virtual machine')
349 job
= vm
.shutdown(timeout
=0)
353 # log information about stopping the machine
354 log_action('VM_STOP', user
, vm
, job
)
355 except GanetiApiError
, e
:
356 msg
= {'__all__': [str(e
)]}
358 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
363 def startup(request
, cluster_slug
, instance
, rest
=False):
364 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
365 cluster__slug
=cluster_slug
)
367 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'power']) or
368 user
.has_perm('admin', vm
.cluster
)):
369 msg
= _('You do not have permission to start up this virtual machine')
371 return {"msg": msg
, "code": 403}
375 # superusers bypass quota checks
376 if not user
.is_superuser
and vm
.owner
:
378 quota
= vm
.cluster
.get_quota(vm
.owner
)
379 if any(quota
.values()):
380 used
= vm
.owner
.used_resources(vm
.cluster
, only_running
=True)
382 if quota
['ram'] is not None \
383 and (used
['ram'] + vm
.ram
) > quota
['ram']:
384 msg
= _('Owner does not have enough RAM remaining on '
385 'this cluster to start the virtual machine.')
387 return {"msg": msg
, "code": 500}
389 return HttpResponse(json
.dumps([0, msg
]),
390 mimetype
='application/json')
392 if quota
['virtual_cpus'] and \
393 (used
['virtual_cpus'] + vm
.virtual_cpus
) \
394 > quota
['virtual_cpus']:
395 msg
= _('Owner does not have enough Virtual CPUs remaining '
396 'on this cluster to start the virtual machine.')
398 return {"msg": msg
, "code": 500}
400 return HttpResponse(json
.dumps([0, msg
]),
401 mimetype
='application/json')
408 # log information about starting up the machine
409 log_action('VM_START', user
, vm
, job
)
410 except GanetiApiError
, e
:
411 msg
= {'__all__': [str(e
)]}
413 return {"msg": msg
, "code": 200}
415 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
419 def migrate(request
, cluster_slug
, instance
):
421 view used for initiating a Node Migrate job
423 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
426 if not (user
.is_superuser
or
427 user
.has_any_perms(cluster
, ['admin', 'migrate'])):
428 raise Http403(NO_PRIVS
)
430 if request
.method
== 'POST':
431 form
= MigrateForm(request
.POST
)
434 job
= vm
.migrate(form
.cleaned_data
['mode'])
436 content
= json
.dumps(job
.info
)
439 log_action('VM_MIGRATE', user
, vm
, job
)
440 except GanetiApiError
, e
:
441 content
= json
.dumps({'__all__': [str(e
)]})
443 # error in form return ajax response
444 content
= json
.dumps(form
.errors
)
445 return HttpResponse(content
, mimetype
='application/json')
450 return render_to_response('ganeti/virtual_machine/migrate.html',
451 {'form': form
, 'vm': vm
, 'cluster': cluster
},
452 context_instance
=RequestContext(request
))
456 def replace_disks(request
, cluster_slug
, instance
):
458 view used for initiating a Replace Disks job
460 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
462 if not (user
.is_superuser
or
463 user
.has_any_perms(cluster
, ['admin', 'replace_disks'])):
464 raise Http403(NO_PRIVS
)
466 if request
.method
== 'POST':
467 form
= ReplaceDisksForm(vm
, request
.POST
)
472 content
= json
.dumps(job
.info
)
475 log_action('VM_REPLACE_DISKS', user
, vm
, job
)
476 except GanetiApiError
, e
:
477 content
= json
.dumps({'__all__': [str(e
)]})
479 # error in form return ajax response
480 content
= json
.dumps(form
.errors
)
481 return HttpResponse(content
, mimetype
='application/json')
484 form
= ReplaceDisksForm(vm
)
486 return render_to_response('ganeti/virtual_machine/replace_disks.html',
487 {'form': form
, 'vm': vm
, 'cluster': cluster
},
488 context_instance
=RequestContext(request
))
493 def reboot(request
, cluster_slug
, instance
, rest
=False):
494 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
495 cluster__slug
=cluster_slug
)
497 if not (user
.is_superuser
or
498 user
.has_any_perms(vm
, ['admin', 'power']) or
499 user
.has_perm('admin', vm
.cluster
)):
502 return HttpResponseForbidden()
504 raise Http403(_('You do not have permission to '
505 'reboot this virtual machine'))
512 # log information about restarting the machine
513 log_action('VM_REBOOT', user
, vm
, job
)
514 except GanetiApiError
, e
:
515 msg
= {'__all__': [str(e
)]}
517 return HttpAccepted()
519 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
522 def ssh_keys(request
, cluster_slug
, instance
, api_key
):
524 Show all ssh keys which belong to users, who are specified vm's admin
526 if settings
.WEB_MGR_API_KEY
!= api_key
:
527 return HttpResponseForbidden(_("You're not allowed to view keys."))
529 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
530 cluster__slug
=cluster_slug
)
532 users
= get_users_any(vm
, ["admin", ]).values_list("id", flat
=True)
533 keys
= SSHKey
.objects \
534 .filter(Q(user__in
=users
) |
Q(user__is_superuser
=True)) \
535 .values_list('key', 'user__username') \
536 .order_by('user__username')
538 keys_list
= list(keys
)
539 return HttpResponse(json
.dumps(keys_list
), mimetype
="application/json")
543 def detail(request
, cluster_slug
, instance
, rest
=False):
545 Display details of virtual machine.
547 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
550 cluster_admin
= user
.is_superuser
or user
.has_perm('admin', cluster
)
552 if not cluster_admin
:
553 perms
= user
.get_perms(vm
)
555 if cluster_admin
or 'admin' in perms
:
564 remove
= 'remove' in perms
565 power
= 'power' in perms
566 modify
= 'modify' in perms
567 tags
= 'tags' in perms
568 migrate
= 'migrate' in perms
570 if not (admin
or power
or remove
or modify
or tags
): # TODO REST
571 raise Http403(_('You do not have permission to view '
572 'this virtual machines\'s details'))
578 'cluster_admin': cluster_admin
,
583 "has_immediate_shutdown": has_shutdown_timeout(cluster
),
586 # check job for pending jobs that should be rendered with a different
587 # detail template. This allows us to reduce the chance that users will do
588 # something strange like rebooting a VM that is being deleted or is not
590 if vm
.pending_delete
:
591 template
= 'ganeti/virtual_machine/delete_status.html'
593 template
= 'ganeti/virtual_machine/create_status.html'
595 context
['job'] = vm
.last_job
597 ct
= ContentType
.objects
.get_for_model(vm
)
598 jobs_loc
= Job
.objects
.order_by('-finished') \
599 .filter(content_type
=ct
, object_id
=vm
.pk
)
600 if jobs_loc
.count() > 0:
601 context
['job'] = jobs_loc
[0]
603 context
['job'] = None
605 template
= 'ganeti/virtual_machine/detail.html'
610 return render_to_response(template
, context
,
611 context_instance
=RequestContext(request
), )
615 def users(request
, cluster_slug
, instance
, rest
=False):
617 Display all of the Users of a VirtualMachine
619 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
622 if not (user
.is_superuser
or user
.has_perm('admin', vm
) or
623 user
.has_perm('admin', cluster
)):
625 return {'msg': NO_PRIVS
, 'code': 403}
627 raise Http403(NO_PRIVS
)
629 url
= reverse('vm-permissions', args
=[cluster
.slug
, vm
.hostname
])
630 return view_users(request
, vm
, url
, rest
=rest
)
634 def permissions(request
, cluster_slug
, instance
, user_id
=None, group_id
=None):
636 Update a users permissions.
638 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
,
639 cluster__slug
=cluster_slug
)
642 if not (user
.is_superuser
or user
.has_perm('admin', vm
) or
643 user
.has_perm('admin', vm
.cluster
)):
644 raise Http403(NO_PRIVS
)
646 url
= reverse('vm-permissions', args
=[cluster_slug
, vm
.hostname
])
647 return view_permissions(request
, vm
, url
, user_id
, group_id
)
651 def object_log(request
, cluster_slug
, instance
, rest
=False):
653 Display all of the Users of a VirtualMachine
655 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
656 vm
= get_object_or_404(VirtualMachine
, hostname
=instance
)
659 if not (user
.is_superuser
or user
.has_perm('admin', vm
) or
660 user
.has_perm('admin', cluster
)):
661 raise Http403(NO_PRIVS
)
664 return list_for_object(request
, vm
, True)
666 return list_for_object(request
, vm
)
670 def modify(request
, cluster_slug
, instance
):
671 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
674 if not (user
.is_superuser
675 or user
.has_any_perms(vm
, ['admin', 'modify'])
676 or user
.has_perm('admin', cluster
)):
678 'You do not have permissions to edit this virtual machine')
680 hv
= get_hypervisor(vm
)
682 hv_form
= KvmModifyVirtualMachineForm
683 template
= 'ganeti/virtual_machine/edit_kvm.html'
684 elif hv
== 'xen-pvm':
685 hv_form
= PvmModifyVirtualMachineForm
686 template
= 'ganeti/virtual_machine/edit_pvm.html'
687 elif hv
== 'xen-hvm':
688 hv_form
= HvmModifyVirtualMachineForm
689 template
= 'ganeti/virtual_machine/edit_hvm.html'
692 template
= 'ganeti/virtual_machine/edit_base.html'
693 # XXX no matter what, we're gonna call hv_form() and die. Let's do it
694 # louder than usual. >:3
695 msg
= "Hey, guys, implementation error in views/vm.py:modify"
696 raise RuntimeError(msg
)
698 if request
.method
== 'POST':
700 form
= hv_form(vm
, request
.POST
)
702 form
.owner
= vm
.owner
704 form
.cluster
= cluster
706 data
= form
.cleaned_data
707 request
.session
['edit_form'] = data
708 request
.session
['edit_vm'] = vm
.id
709 return HttpResponseRedirect(
710 reverse('instance-modify-confirm',
714 elif request
.method
== 'GET':
715 if 'edit_form' in request
.session \
716 and vm
.id == request
.session
['edit_vm']:
717 form
= hv_form(vm
, request
.session
['edit_form'])
721 return render_to_response(
726 'balloon': has_balloonmem(cluster
)
728 context_instance
=RequestContext(request
),
732 # XXX mother, did it need to be so long?
734 def modify_confirm(request
, cluster_slug
, instance
):
735 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
737 hv
= get_hypervisor(vm
)
739 hv_form
= KvmModifyVirtualMachineForm
740 elif hv
== 'xen-pvm':
741 hv_form
= PvmModifyVirtualMachineForm
742 elif hv
== 'xen-hvm':
743 hv_form
= HvmModifyVirtualMachineForm
746 # XXX no matter what, we're gonna call hv_form() and die. Let's do it
747 # louder than usual. >:3
748 msg
= "Hey, guys, implementation error in views/vm.py:modify_confirm"
749 raise RuntimeError(msg
)
752 power
= user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'power'])
753 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'modify'])
754 or user
.has_perm('admin', cluster
)):
756 _('You do not have permissions to edit this virtual machine'))
758 if request
.method
== "POST":
759 if 'edit' in request
.POST
:
760 return HttpResponseRedirect(
761 reverse("instance-modify",
762 args
=[cluster
.slug
, vm
.hostname
]))
763 elif 'reboot' in request
.POST
or 'save' in request
.POST
:
764 form
= ModifyConfirmForm(request
.POST
)
765 form
.session
= request
.session
766 form
.owner
= vm
.owner
768 form
.cluster
= cluster
772 data
= form
.cleaned_data
773 rapi_dict
= data
['rapi_dict']
774 nics
= rapi_dict
.pop('nics')
775 beparams
['vcpus'] = rapi_dict
.pop('vcpus')
776 if has_balloonmem(cluster
):
777 beparams
['maxmem'] = rapi_dict
.pop('maxmem')
778 beparams
['minmem'] = rapi_dict
.pop('minmem')
780 beparams
['memroy'] = rapi_dict
.pop('memory')
781 os_name
= rapi_dict
.pop('os')
782 job_id
= cluster
.rapi
.ModifyInstance(
788 # Create job and update message on virtual machine detail page
789 job
= Job
.objects
.create(job_id
=job_id
,
792 VirtualMachine
.objects \
793 .filter(id=vm
.id).update(last_job
=job
, ignore_cache
=True)
794 # log information about modifying this instance
795 log_action('EDIT', user
, vm
)
796 if 'reboot' in request
.POST
and vm
.info
['status'] == 'running':
800 log_action('VM_REBOOT', user
, vm
, job
)
803 _("Sorry, but you do not have permission "
804 "to reboot this machine."))
806 # Redirect to instance-detail
807 return HttpResponseRedirect(
808 reverse("instance-detail",
809 args
=[cluster
.slug
, vm
.hostname
]))
811 elif 'cancel' in request
.POST
:
812 # Remove session variables.
813 if 'edit_form' in request
.session
:
814 del request
.session
['edit_form']
815 # Redirect to instance-detail
816 return HttpResponseRedirect(
817 reverse("instance-detail", args
=[cluster
.slug
, vm
.hostname
]))
819 elif request
.method
== "GET":
820 form
= ModifyConfirmForm()
822 session
= request
.session
824 if not 'edit_form' in request
.session
:
825 return HttpResponseBadRequest('Incorrect Session Data')
827 data
= session
['edit_form']
829 hvparams
= info
['hvparams']
832 vcpus
=info
['beparams']['vcpus'],
835 if has_balloonmem(cluster
):
836 old_set
['maxmem'] = info
['beparams']['maxmem']
837 old_set
['minmem'] = info
['beparams']['minmem']
839 old_set
['memory'] = info
['beparams']['memory']
840 nic_count
= len(info
['nic.links'])
841 for i
in xrange(nic_count
):
842 old_set
['nic_link_%s' % i
] = info
['nic.links'][i
]
843 old_set
['nic_mac_%s' % i
] = info
['nic.macs'][i
]
845 # Add hvparams to the old_set
846 old_set
.update(hvparams
)
849 fields
= hv_form(vm
, data
).fields
850 for key
in data
.keys():
851 if key
in ['memory', 'maxmem', 'minmem']:
852 diff
= compare(render_storage(old_set
[key
]),
853 render_storage(data
[key
]))
855 oses
= os_prettify([old_set
[key
], data
[key
]])
858 XXX - Special case for a cluster with two different types of
859 optgroups (i.e. Image, Debootstrap).
860 The elements at 00 and 10:
862 The elements at 010 and 110:
863 Tuple containing the OS Name and OS value.
864 The elements at 0101 and 1101:
865 String containing the OS Name
867 oses
[0][1][0] = list(oses
[0][1][0])
868 oses
[1][1][0] = list(oses
[1][1][0])
869 oses
[0][1][0][1] = '%s (%s)' % (oses
[0][1][0][1], oses
[0][0])
870 oses
[1][1][0][1] = '%s (%s)' % (oses
[1][1][0][1], oses
[1][0])
871 oses
= oses
[0][1] + oses
[1][1]
872 diff
= compare(oses
[0][1], oses
[1][1])
875 diff
= compare(oses
[0][1], oses
[1][1])
876 #diff = compare(oses[0][1], oses[1][1])
877 if key
in ['nic_count', 'nic_count_original']:
879 elif key
not in old_set
.keys():
881 instance_diff
[fields
[key
].label
] = _('Added')
883 diff
= compare(old_set
[key
], data
[key
])
886 label
= fields
[key
].label
887 instance_diff
[label
] = diff
889 # remove mac if it has not changed
890 for i
in xrange(nic_count
):
891 if fields
['nic_mac_%s' % i
].label
not in instance_diff
:
892 del data
['nic_mac_%s' % i
]
894 # Repopulate form with changed values
895 form
.fields
['rapi_dict'] = CharField(widget
=HiddenInput
,
896 initial
=json
.dumps(data
))
898 return render_to_response(
899 'ganeti/virtual_machine/edit_confirm.html', {
903 'instance_diff': instance_diff
,
906 context_instance
=RequestContext(request
),
911 def rename(request
, cluster_slug
, instance
, rest
=False, extracted_params
=None):
913 Rename an existing instance
915 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
918 if not (user
.is_superuser
or user
.has_any_perms(vm
, ['admin', 'modify'])
919 or user
.has_perm('admin', cluster
)):
921 _('You do not have permissions to edit this virtual machine'))
923 if request
.method
== 'POST':
924 form
= RenameForm(vm
, request
.POST
)
926 if rest
and extracted_params
is not None:
927 if all(k
in extracted_params
928 for k
in ("hostname", "ip_check", "name_check")):
929 hostname
= extracted_params
['hostname']
930 ip_check
= extracted_params
['ip_check']
931 name_check
= extracted_params
['name_check']
934 return HttpResponseBadRequest()
937 data
= form
.cleaned_data
938 hostname
= data
['hostname']
939 ip_check
= data
['ip_check']
940 name_check
= data
['name_check']
942 if form
.is_valid() or params_ok
:
944 # In order for rename to work correctly, the vm must first be
948 log_action('VM_STOP', user
, vm
, job1
)
950 job_id
= vm
.rapi
.RenameInstance(vm
.hostname
, hostname
,
951 ip_check
, name_check
)
952 job
= Job
.objects
.create(job_id
=job_id
,
955 VirtualMachine
.objects
.filter(pk
=vm
.pk
) \
956 .update(hostname
=hostname
, last_job
=job
, ignore_cache
=True)
958 # slip the new hostname to the log action
959 vm
.newname
= hostname
961 # log information about creating the machine
962 log_action('VM_RENAME', user
, vm
, job
)
965 return HttpResponseRedirect(
966 reverse('instance-detail',
967 args
=[cluster
.slug
, hostname
]))
969 return HttpAccepted()
971 except GanetiApiError
, e
:
972 msg
= 'Error renaming virtual machine: %s' % e
973 form
._errors
["cluster"] = form
.error_class([msg
])
975 return HttpResponse(400, content
=msg
)
977 elif request
.method
== 'GET':
978 form
= RenameForm(vm
)
980 return render_to_response(
981 'ganeti/virtual_machine/rename.html',
986 context_instance
=RequestContext(request
), )
990 def reparent(request
, cluster_slug
, instance
):
992 update a virtual machine to have a new owner
994 vm
, cluster
= get_vm_and_cluster_or_404(cluster_slug
, instance
)
997 if not (user
.is_superuser
or user
.has_perm('admin', cluster
)):
999 _('You do not have permissions to change the owner '
1000 'of this virtual machine'))
1002 if request
.method
== 'POST':
1003 form
= ChangeOwnerForm(request
.POST
)
1005 data
= form
.cleaned_data
1006 vm
.owner
= data
['owner']
1007 vm
.save(force_update
=True)
1009 # log information about creating the machine
1010 log_action('VM_MODIFY', user
, vm
)
1012 return HttpResponseRedirect(
1013 reverse('instance-detail', args
=[cluster_slug
, instance
]))
1016 form
= ChangeOwnerForm()
1018 return render_to_response(
1019 'ganeti/virtual_machine/reparent.html', {
1024 context_instance
=RequestContext(request
),
1029 def job_status(request
, id, rest
=False):
1031 Return a list of basic info for running jobs.
1033 ct
= ContentType
.objects
.get_for_model(VirtualMachine
)
1034 jobs
= Job
.objects
.filter(status__in
=("error", "running", "waiting"),
1036 object_id
=id).order_by('job_id')
1037 jobs
= [j
.info
for j
in jobs
]
1042 return HttpResponse(json
.dumps(jobs
), mimetype
='application/json')
1045 def recv_user_add(sender
, editor
, user
, obj
, **kwargs
):
1047 receiver for object_permissions.signals.view_add_user, Logs action
1049 log_action('ADD_USER', editor
, obj
, user
)
1052 def recv_user_remove(sender
, editor
, user
, obj
, **kwargs
):
1054 receiver for object_permissions.signals.view_remove_user, Logs action
1056 log_action('REMOVE_USER', editor
, obj
, user
)
1059 def recv_perm_edit(sender
, editor
, user
, obj
, **kwargs
):
1061 receiver for object_permissions.signals.view_edit_user, Logs action
1063 log_action('MODIFY_PERMS', editor
, obj
, user
)
1066 view_add_user
.connect(recv_user_add
, sender
=VirtualMachine
)
1067 view_remove_user
.connect(recv_user_remove
, sender
=VirtualMachine
)
1068 view_edit_user
.connect(recv_perm_edit
, sender
=VirtualMachine
)