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
.auth
.models
import User
23 from django
.contrib
.contenttypes
.models
import ContentType
24 from django
.core
.urlresolvers
import reverse
25 from django
.db
.models
import Q
, Sum
26 from django
.http
import (HttpResponse
, HttpResponseRedirect
,
27 HttpResponseForbidden
)
28 from django
.shortcuts
import get_object_or_404
, render_to_response
, redirect
29 from django
.template
import RequestContext
30 from django
.utils
import simplejson
as json
31 from django
.utils
.translation
import ugettext
as _
32 from django
.views
.decorators
.http
import require_POST
33 from django
.views
.generic
.detail
import DetailView
34 from django
.views
.generic
.list import ListView
36 from object_permissions
import get_users_any
37 from object_permissions
import signals
as op_signals
38 from object_permissions
.views
.permissions
import view_users
, view_permissions
40 from object_log
.models
import LogItem
41 from object_log
.views
import list_for_object
43 log_action
= LogItem
.objects
.log_action
45 from ganeti_web
.util
.client
import GanetiApiError
46 from ganeti_web
.middleware
import Http403
47 from ganeti_web
.models
import (Cluster
, ClusterUser
, Profile
, SSHKey
,
49 from ganeti_web
.views
import render_404
50 from ganeti_web
.forms
.cluster
import EditClusterForm
, QuotaForm
51 from ganeti_web
.views
.generic
import (NO_PRIVS
, LoginRequiredMixin
,
55 class ClusterDetailView(LoginRequiredMixin
, DetailView
):
57 template_name
= "ganeti/cluster/detail.html"
59 def get_object(self
, queryset
=None):
60 return get_object_or_404(Cluster
, slug
=self
.kwargs
["cluster_slug"])
62 def get_context_data(self
, **kwargs
):
63 cluster
= kwargs
["object"]
64 user
= self
.request
.user
65 admin
= user
.is_superuser
or user
.has_perm("admin", cluster
)
70 "readonly": not admin
,
74 class ClusterListView(LoginRequiredMixin
, PagedListView
):
76 template_name
= "ganeti/cluster/list.html"
78 def get_queryset(self
):
79 if self
.request
.user
.is_superuser
:
80 qs
= Cluster
.objects
.all()
82 perms
= ['admin', 'migrate', 'export', 'replace_disks', 'tags']
83 qs
= self
.request
.user
.get_objects_any_perms(Cluster
, perms
)
86 super(ClusterListView
, self
).get_queryset()
87 return qs
.select_related()
89 def get_context_data(self
, **kwargs
):
90 user
= self
.request
.user
91 context
= super(ClusterListView
, self
).get_context_data(
92 object_list
=kwargs
["object_list"])
93 context
["can_create"] = (user
.is_superuser
or
94 user
.has_perm("admin", Cluster
))
96 if "order_by" in self
.request
.GET
:
97 context
["order"] = self
.request
.GET
["order_by"]
99 context
["order"] = "id"
104 class ClusterVMListView(LoginRequiredMixin
, PagedListView
):
106 template_name
= "ganeti/virtual_machine/table.html"
108 def get_queryset(self
):
109 self
.cluster
= get_object_or_404(Cluster
,
110 slug
=self
.kwargs
["cluster_slug"])
111 user
= self
.request
.user
112 admin
= user
.is_superuser
or user
.has_perm("admin", self
.cluster
)
114 raise Http403(NO_PRIVS
)
116 return self
.cluster
.virtual_machines
.select_related("cluster").all()
118 def get_context_data(self
, **kwargs
):
119 kwargs
["cluster"] = self
.cluster
123 class ClusterJobListView(LoginRequiredMixin
, PagedListView
):
125 template_name
= "ganeti/cluster/jobs.html"
127 def get_queryset(self
):
128 self
.cluster
= get_object_or_404(Cluster
,
129 slug
=self
.kwargs
["cluster_slug"])
131 user
= self
.request
.user
132 admin
= user
.is_superuser
or user
.has_perm("admin", self
.cluster
) or \
133 user
.has_perm("create_vm", self
.cluster
)
136 raise Http403(NO_PRIVS
)
138 qs
= Job
.objects
.filter(cluster
=self
.cluster
.id)
139 if "order_by" in self
.request
.GET
:
140 qs
= qs
.order_by(self
.request
.GET
['order_by'])
143 super(ClusterJobListView
, self
).get_queryset()
145 return qs
.select_related()
147 def get_context_data(self
, **kwargs
):
148 context
= super(ClusterJobListView
, self
).get_context_data(
149 object_list
=kwargs
["object_list"])
150 context
["cluster"] = self
.cluster
152 if "order_by" in self
.request
.GET
:
153 context
["order"] = self
.request
.GET
["order_by"]
155 context
["order"] = "id"
161 def nodes(request
, cluster_slug
):
163 Display all nodes in a cluster
165 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
167 if not (user
.is_superuser
or user
.has_perm('admin', cluster
)):
168 raise Http403(NO_PRIVS
)
170 # query allocated CPUS for all nodes in this list. Must be done here to
171 # avoid querying Node.allocated_cpus for each node in the list. Repackage
172 # list so it is easier to retrieve the values in the template
173 values
= VirtualMachine
.objects \
174 .filter(cluster
=cluster
, status
='running') \
175 .exclude(virtual_cpus
=-1) \
177 .values('primary_node') \
178 .annotate(cpus
=Sum('virtual_cpus'))
180 nodes
= cluster
.nodes
.all()
182 cpus
[d
['primary_node']] = d
['cpus']
184 # Include nodes that do not have any virtual machines on them.
186 if node
.pk
not in cpus
:
189 return render_to_response("ganeti/node/table.html",
194 context_instance
=RequestContext(request
),
199 def edit(request
, cluster_slug
=None):
204 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
209 if not (user
.is_superuser
or (cluster
and user
.has_perm(
211 raise Http403(NO_PRIVS
)
213 if request
.method
== 'POST':
214 form
= EditClusterForm(request
.POST
, instance
=cluster
)
216 cluster
= form
.save()
217 # TODO Create post signal to import
218 # virtual machines on edit of cluster
219 if cluster
.info
is None:
222 cluster
.sync_virtual_machines()
223 except GanetiApiError
:
224 # ganeti errors here are silently discarded. It's
225 # valid to enter bad info. A user might be adding
226 # info for an offline cluster.
229 log_action('EDIT' if cluster_slug
else 'CREATE', user
, cluster
)
231 return HttpResponseRedirect(reverse('cluster-detail',
232 args
=[cluster
.slug
]))
234 elif request
.method
== 'DELETE':
236 return HttpResponse('1', mimetype
='application/json')
239 form
= EditClusterForm(instance
=cluster
)
241 return render_to_response("ganeti/cluster/edit.html", {
245 context_instance
=RequestContext(request
),
250 def refresh(request
, cluster_slug
):
252 Display a notice to the user that we are refreshing
253 the cluster data, then redirect them back to the
254 cluster details page.
257 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
258 cluster
.sync_nodes(remove
=True)
259 cluster
.sync_virtual_machines(remove
=True)
261 url
= reverse('cluster-detail', args
=[cluster
.slug
])
266 def users(request
, cluster_slug
):
268 Display all of the Users of a Cluster
270 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
273 if not (user
.is_superuser
or user
.has_perm('admin', cluster
)):
274 raise Http403(NO_PRIVS
)
276 url
= reverse('cluster-permissions', args
=[cluster
.slug
])
277 return view_users(request
, cluster
, url
,
278 template
='ganeti/cluster/users.html')
282 def permissions(request
, cluster_slug
, user_id
=None, group_id
=None):
284 Update a users permissions.
285 This wraps object_permissions.view_permissions()
286 with our custom permissions checks.
288 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
290 if not (user
.is_superuser
or user
.has_perm('admin', cluster
)):
291 raise Http403(NO_PRIVS
)
293 url
= reverse('cluster-permissions', args
=[cluster
.slug
])
294 return view_permissions(request
, cluster
, url
, user_id
, group_id
,
295 user_template
='ganeti/cluster/user_row.html',
296 group_template
='ganeti/cluster/group_row.html')
301 def redistribute_config(request
, cluster_slug
):
303 Redistribute master-node config to all cluster's other nodes.
305 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
308 if not (user
.is_superuser
or user
.has_perm('admin', cluster
)):
309 raise Http403(NO_PRIVS
)
312 job
= cluster
.redistribute_config()
316 log_action('CLUSTER_REDISTRIBUTE', user
, cluster
, job
)
317 except GanetiApiError
, e
:
318 msg
= {'__all__': [str(e
)]}
319 return HttpResponse(json
.dumps(msg
), mimetype
='application/json')
322 def ssh_keys(request
, cluster_slug
, api_key
):
324 Show all ssh keys which belong to users, who have any perms on the cluster
326 if settings
.WEB_MGR_API_KEY
!= api_key
:
327 return HttpResponseForbidden(_("You're not allowed to view keys."))
329 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
331 users
= set(get_users_any(cluster
).values_list("id", flat
=True))
332 for vm
in cluster
.virtual_machines
.all():
333 users
= users
.union(set(get_users_any(vm
)
334 .values_list('id', flat
=True)))
336 keys
= SSHKey
.objects \
337 .filter(Q(user__in
=users
) |
Q(user__is_superuser
=True)) \
338 .values_list('key', 'user__username') \
339 .order_by('user__username')
341 keys_list
= list(keys
)
342 return HttpResponse(json
.dumps(keys_list
), mimetype
="application/json")
346 def quota(request
, cluster_slug
, user_id
):
348 Updates quota for a user
350 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
352 if not (user
.is_superuser
or user
.has_perm('admin', cluster
)):
353 raise Http403(NO_PRIVS
)
355 if request
.method
== 'POST':
356 form
= QuotaForm(request
.POST
)
358 data
= form
.cleaned_data
359 cluster_user
= data
['user']
361 cluster
.set_quota(cluster_user
, None)
363 cluster
.set_quota(cluster_user
, data
)
365 # return updated html
366 cluster_user
= cluster_user
.cast()
367 url
= reverse('cluster-permissions', args
=[cluster
.slug
])
368 if isinstance(cluster_user
, (Profile
,)):
369 return render_to_response(
370 "ganeti/cluster/user_row.html",
371 {'object': cluster
, 'user_detail': cluster_user
.user
,
373 context_instance
=RequestContext(request
))
375 return render_to_response(
376 "ganeti/cluster/group_row.html",
377 {'object': cluster
, 'group': cluster_user
.group
,
379 context_instance
=RequestContext(request
))
381 # error in form return ajax response
382 content
= json
.dumps(form
.errors
)
383 return HttpResponse(content
, mimetype
='application/json')
386 cluster_user
= get_object_or_404(ClusterUser
, id=user_id
)
387 quota
= cluster
.get_quota(cluster_user
)
388 data
= {'user': user_id
}
392 return render_404(request
, _('User was not found'))
394 form
= QuotaForm(data
)
395 return render_to_response("ganeti/cluster/quota.html",
396 {'form': form
, 'cluster': cluster
,
398 context_instance
=RequestContext(request
))
402 def job_status(request
, id, rest
=False):
404 Return a list of basic info for running jobs.
407 ct
= ContentType
.objects
.get_for_model(Cluster
)
408 jobs
= Job
.objects
.filter(status__in
=("error", "running", "waiting"),
410 object_id
=id).order_by('job_id')
411 jobs
= [j
.info
for j
in jobs
]
416 return HttpResponse(json
.dumps(jobs
), mimetype
='application/json')
420 def object_log(request
, cluster_slug
):
421 """ displays object log for this cluster """
422 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
424 if not (user
.is_superuser
or user
.has_perm('admin', cluster
)):
425 raise Http403(NO_PRIVS
)
426 return list_for_object(request
, cluster
)
429 def recv_user_add(sender
, editor
, user
, obj
, **kwargs
):
431 receiver for object_permissions.signals.view_add_user, Logs action
433 log_action('ADD_USER', editor
, obj
, user
)
436 def recv_user_remove(sender
, editor
, user
, obj
, **kwargs
):
438 receiver for object_permissions.signals.view_remove_user, Logs action
440 log_action('REMOVE_USER', editor
, obj
, user
)
442 # remove custom quota user may have had.
443 if isinstance(user
, (User
,)):
444 cluster_user
= user
.get_profile()
446 cluster_user
= user
.organization
447 cluster_user
.quotas
.filter(cluster
=obj
).delete()
450 def recv_perm_edit(sender
, editor
, user
, obj
, **kwargs
):
452 receiver for object_permissions.signals.view_edit_user, Logs action
454 log_action('MODIFY_PERMS', editor
, obj
, user
)
457 op_signals
.view_add_user
.connect(recv_user_add
, sender
=Cluster
)
458 op_signals
.view_remove_user
.connect(recv_user_remove
, sender
=Cluster
)
459 op_signals
.view_edit_user
.connect(recv_perm_edit
, sender
=Cluster
)