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 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
.core
.exceptions
import PermissionDenied
24 from django
.db
.models
import Q
, Count
25 from django
.http
import HttpResponse
, HttpResponseForbidden
26 from django
.shortcuts
import render_to_response
, get_object_or_404
27 from django
.template
import RequestContext
28 from django
.utils
import simplejson
as json
29 from django
.views
.generic
.base
import TemplateView
31 from object_permissions
import get_users_any
33 from ganeti_web
.backend
.queries
import vm_qs_for_admins
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
):
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``.
61 Either the "finished" or "timestamp" attribute.
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)
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.
81 clusters
= Cluster
.objects
.all()
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
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
)
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
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.
110 ganeti_errors |
= qs
.get_errors(obj
=clusters
)
113 errors
= merge_errors(ganeti_errors
, job_errors
)
115 return render_to_response("ganeti/errors.html",
118 'cluster_list': clusters
,
119 'user': request
.user
,
122 context_instance
=RequestContext(request
))
125 def get_used_resources(cluster_user
):
126 """ help function for querying resources used for a given cluster_user """
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
,
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
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()
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"))
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
195 def overview(request
, rest
=False):
201 # Get all clusters a user is an administrator of (admin/create_vm perms)
202 if user
.is_superuser
:
203 clusters
= Cluster
.objects
.all()
205 clusters
= user
.get_objects_any_perms(Cluster
,
206 ['admin', 'create_vm', ])
207 admin
= user
.is_superuser
or clusters
209 #orphaned, ready to import, missing
211 # build list of admin tasks for this user's clusters
212 orphaned
, import_ready
, missing
= get_vm_counts(clusters
)
214 orphaned
= import_ready
= missing
= 0
216 # Get all of the PKs from VMs that this user may administer.
217 vms
= vm_qs_for_admins(user
).values("pk")
219 # build list of job errors. Include jobs from any vm the user has access
220 # to if the user has admin on any cluster then those clusters and it's
221 # objects must be included too.
223 # XXX all jobs have the cluster listed, filtering by cluster includes jobs
224 # for both the cluster itself and any of its VMs or Nodes
225 error_clause
= Q(status
='error')
226 vm_type
= ContentType
.objects
.get_for_model(VirtualMachine
)
227 select_clause
= Q(content_type
=vm_type
, object_id__in
=vms
)
229 select_clause |
= Q(cluster__in
=clusters
)
230 job_errors
= Job
.objects
.filter(error_clause
& select_clause
) \
231 .order_by("-finished")[:5]
233 # Build the list of job errors. Include jobs from any VMs for which the
235 qs
= GanetiError
.objects
.filter(cleared
=False)
236 ganeti_errors
= qs
.get_errors(obj
=vms
)
237 # If the user is an admin on any cluster, then include administrated
238 # clusters and related objects.
240 ganeti_errors |
= qs
.get_errors(obj
=clusters
)
243 errors
= merge_errors(ganeti_errors
, job_errors
)
245 # get vm summary - running and totals need to be done as separate queries
246 # and then merged into a single list
247 vms_running
= (vms
.filter(status
='running')
249 .values('cluster__hostname', 'cluster__slug')
250 .annotate(running
=Count('pk')))
251 vms_total
= (vms
.order_by()
252 .values('cluster__hostname', 'cluster__slug')
253 .annotate(total
=Count('pk')))
255 for cluster
in vms_total
:
256 name
= cluster
.pop('cluster__hostname')
257 vm_summary
[name
] = cluster
258 for cluster
in vms_running
:
259 name
= cluster
['cluster__hostname']
260 vm_summary
[name
]['running'] = cluster
['running']
262 # get list of personas for the user: All groups, plus the user.
263 # include the user if they own a vm or have perms on at least one cluster
264 profile
= user
.get_profile()
265 personas
= list(Organization
.objects
.filter(group__user
=user
))
266 owns_vm
= profile
.virtual_machines
.count()
267 has_perms
= user
.has_any_perms(Cluster
, ['admin', 'create_vm'], False)
268 if (owns_vm
or has_perms
or not personas
):
269 personas
.insert(0, profile
)
271 # get resources used per cluster from the first persona in the list
272 resources
= get_used_resources(personas
[0])
279 'cluster_list': clusters
,
280 'user': request
.user
,
282 'orphaned': orphaned
,
283 'import_ready': import_ready
,
285 'resources': resources
,
286 'vm_summary': vm_summary
,
287 'personas': personas
,
289 return render_to_response("ganeti/overview.html", context
,
290 context_instance
=RequestContext(request
))
294 def used_resources(request
, rest
=False):
295 """ view for returning used resources for a given cluster user """
297 cluster_user_id
= request
.GET
['id']
299 return render_404(request
, 'requested user was not found')
300 cu
= get_object_or_404(ClusterUser
, pk
=cluster_user_id
)
302 # must be a super user, the user in question, or a member of the group
304 if not user
.is_superuser
:
305 user_type
= ContentType
.objects
.get_for_model(Profile
)
306 if cu
.real_type_id
== user_type
.pk
:
307 if not Profile
.objects
.filter(clusteruser_ptr
=cu
.pk
, user
=user
) \
309 raise PermissionDenied(_('You are not authorized to view this page'))
311 if not Organization
.objects
.filter(clusteruser_ptr
=cu
.pk
,
312 group__user
=user
).exists():
313 raise PermissionDenied(_('You are not authorized to view this page'))
315 resources
= get_used_resources(cu
.cast())
319 return render_to_response("ganeti/overview/used_resources.html", {
320 'resources': resources
321 }, context_instance
=RequestContext(request
))
325 def clear_ganeti_error(request
, pk
):
327 Clear a single error message
330 error
= get_object_or_404(GanetiError
, pk
=pk
)
333 # if not a superuser, check permissions on the object itself
334 if not user
.is_superuser
:
335 if isinstance(obj
, (Cluster
,)) and not user
.has_perm('admin', obj
):
336 raise PermissionDenied(NO_PRIVS
)
337 elif isinstance(obj
, (VirtualMachine
,)):
338 # object is a virtual machine, check perms on VM and on Cluster
339 if not (obj
.owner_id
== user
.get_profile().pk
or
340 user
.has_perm('admin', obj
.cluster
)):
341 raise PermissionDenied(NO_PRIVS
)
344 GanetiError
.objects
.filter(pk
=error
.pk
).update(cleared
=True)
346 return HttpResponse('1', mimetype
='application/json')
349 def ssh_keys(request
, api_key
):
350 """ Lists all keys for all clusters managed by GWM """
352 Show all ssh keys which belong to users, who have any perms on the cluster
354 if settings
.WEB_MGR_API_KEY
!= api_key
:
355 return HttpResponseForbidden(_("You're not allowed to view keys."))
358 for cluster
in Cluster
.objects
.all():
359 users
= users
.union(set(get_users_any(cluster
)
360 .values_list("id", flat
=True)))
361 for vm
in VirtualMachine
.objects
.all():
362 users
= users
.union(set(get_users_any(vm
)
363 .values_list('id', flat
=True)))
365 keys
= SSHKey
.objects \
366 .filter(Q(user__in
=users
) |
Q(user__is_superuser
=True)) \
367 .values_list('key', 'user__username')\
368 .order_by('user__username')
370 keys_list
= list(keys
)
371 return HttpResponse(json
.dumps(keys_list
), mimetype
="application/json")