Move default template for tables out of template into table base class
[ganeti_webmgr.git] / ganeti_web / views / general.py
bloba399b19b3c3d36e38c0d1964a48e55dae1e1c6ed
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,
16 # USA.
18 from itertools import chain, izip, repeat
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.db.models import Q, Count
24 from django.http import HttpResponse, HttpResponseForbidden
25 from django.shortcuts import render_to_response, get_object_or_404
26 from django.template import RequestContext
27 from django.utils import simplejson as json
28 from django.views.generic.base import TemplateView
30 from object_permissions import get_users_any
32 from ganeti_web.backend.queries import vm_qs_for_admins
33 from ganeti_web.middleware import Http403
34 from ganeti_web.models import Cluster, VirtualMachine, Job, GanetiError, \
35 ClusterUser, Profile, Organization, SSHKey
36 from ganeti_web.views import render_404
37 from ganeti_web.views.generic import NO_PRIVS
38 from django.utils.translation import ugettext as _
39 from ganeti_web.constants import VERSION
42 class AboutView(TemplateView):
44 template_name = "ganeti/about.html"
46 def render_to_response(self, context, **kwargs):
47 context["version"] = VERSION
48 return super(AboutView, self).render_to_response(context, **kwargs)
51 def merge_errors(errors, jobs):
52 """
53 Merge iterables of errors and jobs together.
55 The resulting list contains tuples of (bool, object) where the first
56 member indicates whether the object is a ``GanetiError`` or ``Job``.
57 """
59 def keyfunc(x):
60 """
61 Either the "finished" or "timestamp" attribute.
62 """
64 return getattr(x[1], "finished", getattr(x[1], "timestamp", 0))
66 i = chain(izip(repeat(True), errors), izip(repeat(False), jobs))
67 return list(sorted(i, key=keyfunc))
70 USED_NOTHING = dict(disk=0, ram=0, virtual_cpus=0)
73 @login_required
74 def get_errors(request):
75 """ Returns all errors that have ever been generated for clusters/vms
76 and then sends them to the errors page.
77 """
78 user = request.user
80 if user.is_superuser:
81 clusters = Cluster.objects.all()
82 else:
83 clusters = user.get_objects_all_perms(Cluster, ['admin', ])
84 admin = user.is_superuser or clusters
86 # Get all of the PKs from VMs that this user may administer.
87 vms = vm_qs_for_admins(user).values("pk")
89 # build list of job errors. Include jobs from any vm the user has access
90 # to
91 # If the user has admin on any cluster then those clusters and it's objects
92 # must be included too.
94 # XXX all jobs have the cluster listed, filtering by cluster includes jobs
95 # for both the cluster itself and any of its VMs or Nodes
96 error_clause = Q(status='error')
97 vm_type = ContentType.objects.get_for_model(VirtualMachine)
98 select_clause = Q(content_type=vm_type, object_id__in=vms)
99 if admin:
100 select_clause |= Q(cluster__in=clusters)
101 job_errors = Job.objects.filter(error_clause & select_clause)
103 # Build the list of job errors. Include jobs from any VMs for which the
104 # user has access.
105 qs = GanetiError.objects
106 ganeti_errors = qs.get_errors(obj=vms)
107 # If the user is an admin on any cluster, then include administrated
108 # clusters and related objects.
109 if admin:
110 ganeti_errors |= qs.get_errors(obj=clusters)
112 # merge error lists
113 errors = merge_errors(ganeti_errors, job_errors)
115 return render_to_response("ganeti/errors.html",
117 'admin': admin,
118 'cluster_list': clusters,
119 'user': request.user,
120 'errors': errors,
122 context_instance=RequestContext(request))
125 def get_used_resources(cluster_user):
126 """ help function for querying resources used for a given cluster_user """
127 resources = {}
128 owned_vms = cluster_user.virtual_machines.all()
129 used = cluster_user.used_resources()
130 clusters = cluster_user.permissable.get_objects_any_perms(Cluster)
131 quotas = Cluster.get_quotas(clusters, cluster_user)
133 for cluster, quota in quotas.items():
134 resources[cluster] = {
135 "used": used.pop(cluster.id)
136 if cluster.id in used else USED_NOTHING,
137 "set": quota
139 resources[cluster]["total"] = owned_vms.filter(cluster=cluster).count()
140 resources[cluster]["running"] = owned_vms \
141 .filter(cluster=cluster, status="running").count()
143 # add any clusters that have used resources
144 # but no perms (and thus no quota)
145 # since we know they don't have a custom quota just add the default quota
146 if used:
147 for cluster in Cluster.objects.filter(pk__in=used):
148 resources[cluster] = {"used": used[cluster.id],
149 "set": cluster.get_default_quota()}
150 resources[cluster]["total"] = owned_vms \
151 .filter(cluster=cluster).count()
152 resources[cluster]["running"] = owned_vms \
153 .filter(cluster=cluster, status="running").count()
155 return resources
158 def get_vm_counts(clusters):
160 Helper for getting the list of orphaned/ready to import/missing VMs.
162 @param clusters the list of clusters, for which numbers of VM are counted.
163 May be None, if update is set.
165 format_key = 'cluster_admin_%d'
166 orphaned = import_ready = missing = 0
168 # update the values that were not cached
169 if clusters.exists():
170 annotated = VirtualMachine.objects \
171 .filter(cluster__in=clusters,
172 owner=None).order_by().values("cluster__pk") \
173 .annotate(orphaned=Count("id"))
175 result = {}
176 for i in annotated:
177 result[format_key % i["cluster__pk"]] = {"orphaned": i["orphaned"]}
178 orphaned += i["orphaned"]
179 for cluster in clusters:
180 key = format_key % cluster.pk
182 if key not in result:
183 result[key] = {"orphaned": 0}
185 result[key]["import_ready"] = len(cluster.missing_in_db)
186 result[key]["missing"] = len(cluster.missing_in_ganeti)
188 import_ready += result[key]["import_ready"]
189 missing += result[key]["missing"]
191 return orphaned, import_ready, missing
194 @login_required
195 def overview(request, rest=False):
197 Status page
199 user = request.user
201 if user.is_superuser:
202 clusters = Cluster.objects.all()
203 else:
204 clusters = user.get_objects_any_perms(Cluster,
205 ['admin', 'create_vm', ])
206 admin = user.is_superuser or clusters
208 #orphaned, ready to import, missing
209 if admin:
210 # build list of admin tasks for this user's clusters
211 orphaned, import_ready, missing = get_vm_counts(clusters)
212 else:
213 orphaned = import_ready = missing = 0
215 # Get all of the PKs from VMs that this user may administer.
216 vms = vm_qs_for_admins(user).values("pk")
218 # get vm summary - running and totals need to be done as separate queries
219 # and then merged into a single list
220 vms_running = vms.filter(status='running') \
221 .order_by() \
222 .values('cluster__hostname', 'cluster__slug') \
223 .annotate(running=Count('pk'))
224 vms_total = vms.order_by()\
225 .values('cluster__hostname', 'cluster__slug') \
226 .annotate(total=Count('pk'))
227 vm_summary = {}
228 for cluster in vms_total:
229 vm_summary[cluster.pop('cluster__hostname')] = cluster
230 for cluster in vms_running:
231 vm_summary[cluster['cluster__hostname']]['running'] = \
232 cluster['running']
234 # get list of personas for the user: All groups, plus the user.
235 # include the user only if it owns a vm or has
236 # perms on at least one cluster
237 profile = user.get_profile()
238 personas = list(Organization.objects.filter(group__user=user))
239 if profile.virtual_machines.count() \
240 or user.has_any_perms(Cluster, ['admin', 'create_vm'], groups=False) \
241 or not personas:
242 personas.insert(0, profile)
244 # get resources used per cluster from the first persona in the list
245 resources = get_used_resources(personas[0])
247 create_vm = user.has_perm('create_vm', clusters)
249 if rest:
250 return clusters
251 else:
252 return render_to_response("ganeti/overview.html", {
253 'admin': admin,
254 'cluster_list': clusters,
255 'create_vm': create_vm,
256 'user': request.user,
257 'orphaned': orphaned,
258 'import_ready': import_ready,
259 'missing': missing,
260 'resources': resources,
261 'vm_summary': vm_summary,
262 'personas': personas,
264 context_instance=RequestContext(request),
268 @login_required
269 def used_resources(request, rest=False):
270 """ view for returning used resources for a given cluster user """
271 try:
272 cluster_user_id = request.GET['id']
273 except KeyError:
274 return render_404(request, 'requested user was not found')
275 cu = get_object_or_404(ClusterUser, pk=cluster_user_id)
277 # must be a super user, the user in question, or a member of the group
278 user = request.user
279 if not user.is_superuser:
280 user_type = ContentType.objects.get_for_model(Profile)
281 if cu.real_type_id == user_type.pk:
282 if not Profile.objects.filter(clusteruser_ptr=cu.pk, user=user) \
283 .exists():
284 raise Http403(_('You are not authorized to view this page'))
285 else:
286 if not Organization.objects.filter(clusteruser_ptr=cu.pk,
287 group__user=user).exists():
288 raise Http403(_('You are not authorized to view this page'))
290 resources = get_used_resources(cu.cast())
291 if rest:
292 return resources
293 else:
294 return render_to_response("ganeti/overview/used_resources.html", {
295 'resources': resources
296 }, context_instance=RequestContext(request))
299 @login_required
300 def clear_ganeti_error(request, pk):
302 Clear a single error message
304 user = request.user
305 error = get_object_or_404(GanetiError, pk=pk)
306 obj = error.obj
308 # if not a superuser, check permissions on the object itself
309 if not user.is_superuser:
310 if isinstance(obj, (Cluster,)) and not user.has_perm('admin', obj):
311 raise Http403(NO_PRIVS)
312 elif isinstance(obj, (VirtualMachine,)):
313 # object is a virtual machine, check perms on VM and on Cluster
314 if not (obj.owner_id == user.get_profile().pk or
315 user.has_perm('admin', obj.cluster)):
316 raise Http403(NO_PRIVS)
318 # clear the error
319 GanetiError.objects.filter(pk=error.pk).update(cleared=True)
321 return HttpResponse('1', mimetype='application/json')
324 def ssh_keys(request, api_key):
325 """ Lists all keys for all clusters managed by GWM """
327 Show all ssh keys which belong to users, who have any perms on the cluster
329 if settings.WEB_MGR_API_KEY != api_key:
330 return HttpResponseForbidden(_("You're not allowed to view keys."))
332 users = set()
333 for cluster in Cluster.objects.all():
334 users = users.union(set(get_users_any(cluster)
335 .values_list("id", flat=True)))
336 for vm in VirtualMachine.objects.all():
337 users = users.union(set(get_users_any(vm)
338 .values_list('id', flat=True)))
340 keys = SSHKey.objects \
341 .filter(Q(user__in=users) | Q(user__is_superuser=True)) \
342 .values_list('key', 'user__username')\
343 .order_by('user__username')
345 keys_list = list(keys)
346 return HttpResponse(json.dumps(keys_list), mimetype="application/json")