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
.cache
import cache
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
.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
41 class AboutView(TemplateView
):
43 template_name
= "ganeti/about.html"
45 def render_to_response(self
, context
, **kwargs
):
46 context
["version"] = VERSION
47 return super(AboutView
, self
).render_to_response(context
, **kwargs
)
49 def merge_errors(errors
, jobs
):
51 Merge iterables of errors and jobs together.
53 The resulting list contains tuples of (bool, object) where the first
54 member indicates whether the object is a ``GanetiError`` or ``Job``.
59 Either the "finished" or "timestamp" attribute.
62 return getattr(x
[1], "finished", getattr(x
[1], "timestamp", 0))
64 i
= chain(izip(repeat(True), errors
), izip(repeat(False), jobs
))
65 return list(sorted(i
, key
=keyfunc
))
68 USED_NOTHING
= dict(disk
=0, ram
=0, virtual_cpus
=0)
71 def get_used_resources(cluster_user
):
72 """ help function for querying resources used for a given cluster_user """
74 owned_vms
= cluster_user
.virtual_machines
.all()
75 used
= cluster_user
.used_resources()
76 clusters
= cluster_user
.permissable
.get_objects_any_perms(Cluster
)
77 quotas
= Cluster
.get_quotas(clusters
, cluster_user
)
79 for cluster
, quota
in quotas
.items():
80 resources
[cluster
] = {
81 "used": used
.pop(cluster
.id) if cluster
.id in used
else USED_NOTHING
,
84 resources
[cluster
]["total"] = owned_vms
.filter(cluster
=cluster
).count()
85 resources
[cluster
]["running"] = owned_vms
.filter(cluster
=cluster
, \
86 status
="running").count()
88 # add any clusters that have used resources but no perms (and thus no quota)
89 # since we know they don't have a custom quota just add the default quota
91 for cluster
in Cluster
.objects
.filter(pk__in
=used
):
92 resources
[cluster
] = {"used":used
[cluster
.id],
93 "set":cluster
.get_default_quota()}
94 resources
[cluster
]["total"] = owned_vms
.filter(cluster
=cluster
).count()
95 resources
[cluster
]["running"] = owned_vms
.filter(cluster
=cluster
, \
96 status
="running").count()
101 def update_vm_counts(key
, data
):
103 Updates the cache for numbers of orphaned / ready to import / missing VMs.
105 If the cluster's data is not in cache it is ignored. This is only for
106 updating the cache with information we already have.
108 @param key - admin data key that is being updated: orphaned, ready_to_import,
110 @param data - dict of data stored by cluster.pk
112 format_key
= 'cluster_admin_%d'
113 keys
= [format_key
% k
for k
in data
.keys()]
114 cached
= cache
.get_many(keys
)
116 for k
, v
in data
.items():
118 values
= cached
[format_key
% k
]
123 cache
.set_many(cached
, 600)
126 def get_vm_counts(clusters
, timeout
=600):
128 Helper for getting the list of orphaned/ready to import/missing VMs.
131 This caches data under the keys: cluster_admin_<cluster_id>
133 @param clusters the list of clusters, for which numbers of VM are counted.
134 May be None, if update is set.
135 @param timeout specified timeout for cache, in seconds.
137 format_key
= 'cluster_admin_%d'
138 orphaned
= import_ready
= missing
= 0
139 cached
= cache
.get_many((format_key
% cluster
.pk
for cluster
in clusters
))
140 exclude
= [int(key
[14:]) for key
in cached
.keys()]
141 keys
= [k
for k
in clusters
.values_list('pk', flat
=True) if k
not in exclude
]
142 cluster_list
= Cluster
.objects
.filter(pk__in
=keys
)
144 # total the cached values first
145 for k
in cached
.values():
146 orphaned
+= k
["orphaned"]
147 import_ready
+= k
["import_ready"]
148 missing
+= k
["missing"]
150 # update the values that were not cached
151 if cluster_list
.count():
152 base
= VirtualMachine
.objects
.filter(cluster__in
=cluster_list
,
153 owner
=None).order_by()
154 annotated
= base
.values("cluster__pk").annotate(orphaned
=Count("id"))
158 result
[format_key
% i
["cluster__pk"]] = {"orphaned": i
["orphaned"]}
159 orphaned
+= i
["orphaned"]
160 for cluster
in cluster_list
:
161 key
= format_key
% cluster
.pk
163 if key
not in result
:
164 result
[key
] = {"orphaned": 0}
166 result
[key
]["import_ready"] = len(cluster
.missing_in_db
)
167 result
[key
]["missing"] = len(cluster
.missing_in_ganeti
)
169 import_ready
+= result
[key
]["import_ready"]
170 missing
+= result
[key
]["missing"]
172 # add all results into cache
173 cache
.set_many(result
, timeout
)
175 return orphaned
, import_ready
, missing
179 def overview(request
, rest
=False):
185 if user
.is_superuser
:
186 clusters
= Cluster
.objects
.all()
188 clusters
= user
.get_objects_all_perms(Cluster
, ['admin',])
189 admin
= user
.is_superuser
or clusters
191 #orphaned, ready to import, missing
193 # build list of admin tasks for this user's clusters
194 orphaned
, import_ready
, missing
= get_vm_counts(clusters
)
196 orphaned
= import_ready
= missing
= 0
198 if user
.is_superuser
:
199 vms
= VirtualMachine
.objects
.all()
201 # Get query containing any virtual machines the user has permissions for
202 vms
= user
.get_objects_any_perms(VirtualMachine
, groups
=True, cluster
=['admin']).values('pk')
204 # build list of job errors. Include jobs from any vm the user has access to
205 # If the user has admin on any cluster then those clusters and it's objects
206 # must be included too.
208 # XXX all jobs have the cluster listed, filtering by cluster includes jobs
209 # for both the cluster itself and any of its VMs or Nodes
210 error_clause
= Q(status
='error')
211 vm_type
= ContentType
.objects
.get_for_model(VirtualMachine
)
212 select_clause
= Q(content_type
=vm_type
, object_id__in
=vms
)
214 select_clause |
= Q(cluster__in
=clusters
)
215 job_errors
= Job
.objects
.filter(error_clause
& select_clause
) \
216 .order_by("-finished")[:5]
218 # Build the list of job errors. Include jobs from any VMs for which the
220 qs
= GanetiError
.objects
.filter(cleared
=False)
221 ganeti_errors
= qs
.get_errors(obj
=vms
)
222 # If the user is an admin on any cluster, then include administrated
223 # clusters and related objects.
225 ganeti_errors |
= qs
.get_errors(obj
=clusters
)
228 errors
= merge_errors(ganeti_errors
, job_errors
)
230 # get vm summary - running and totals need to be done as separate queries
231 # and then merged into a single list
232 vms_running
= vms
.filter(status
='running') \
234 .values('cluster__hostname','cluster__slug') \
235 .annotate(running
=Count('pk'))
236 vms_total
= vms
.order_by()\
237 .values('cluster__hostname','cluster__slug') \
238 .annotate(total
=Count('pk'))
240 for cluster
in vms_total
:
241 vm_summary
[cluster
.pop('cluster__hostname')] = cluster
242 for cluster
in vms_running
:
243 vm_summary
[cluster
['cluster__hostname']]['running'] = cluster
['running']
245 # get list of personas for the user: All groups, plus the user.
246 # include the user only if it owns a vm or has perms on at least one cluster
247 profile
= user
.get_profile()
248 personas
= list(Organization
.objects
.filter(group__user
=user
))
249 if profile
.virtual_machines
.count() \
250 or user
.has_any_perms(Cluster
, ['admin', 'create_vm'], groups
=False) \
252 personas
.insert(0, profile
)
254 # get resources used per cluster from the first persona in the list
255 resources
= get_used_resources(personas
[0])
260 return render_to_response("ganeti/overview.html", {
262 'cluster_list': clusters
,
263 'user': request
.user
,
265 'orphaned': orphaned
,
266 'import_ready': import_ready
,
268 'resources': resources
,
269 'vm_summary': vm_summary
,
270 'personas': personas
,
272 context_instance
=RequestContext(request
),
277 def used_resources(request
, rest
=False):
278 """ view for returning used resources for a given cluster user """
280 cluster_user_id
= request
.GET
['id']
282 return render_404(request
, 'requested user was not found')
283 cu
= get_object_or_404(ClusterUser
, pk
=cluster_user_id
)
285 # must be a super user, the user in question, or a member of the group
287 if not user
.is_superuser
:
288 user_type
= ContentType
.objects
.get_for_model(Profile
)
289 if cu
.real_type_id
== user_type
.pk
:
290 if not Profile
.objects
.filter(clusteruser_ptr
=cu
.pk
, user
=user
)\
292 raise Http403(_('You are not authorized to view this page'))
294 if not Organization
.objects
.filter(clusteruser_ptr
=cu
.pk
, \
295 group__user
=user
).exists():
296 raise Http403(_('You are not authorized to view this page'))
298 resources
= get_used_resources(cu
.cast())
302 return render_to_response("ganeti/overview/used_resources.html", {
303 'resources':resources
304 }, context_instance
=RequestContext(request
))
309 def clear_ganeti_error(request
, pk
):
311 Clear a single error message
314 error
= get_object_or_404(GanetiError
, pk
=pk
)
317 # if not a superuser, check permissions on the object itself
318 if not user
.is_superuser
:
319 if isinstance(obj
, (Cluster
,)) and not user
.has_perm('admin', obj
):
320 raise Http403(NO_PRIVS
)
321 elif isinstance(obj
, (VirtualMachine
,)):
322 # object is a virtual machine, check perms on VM and on Cluster
323 if not (obj
.owner_id
== user
.get_profile().pk
or \
324 user
.has_perm('admin', obj
.cluster
)):
325 raise Http403(NO_PRIVS
)
328 GanetiError
.objects
.filter(pk
=error
.pk
).update(cleared
=True)
330 return HttpResponse('1', mimetype
='application/json')
333 def ssh_keys(request
, api_key
):
334 """ Lists all keys for all clusters managed by GWM """
336 Show all ssh keys which belong to users, who have any perms on the cluster
338 if settings
.WEB_MGR_API_KEY
!= api_key
:
339 return HttpResponseForbidden(_("You're not allowed to view keys."))
342 for cluster
in Cluster
.objects
.all():
343 users
= users
.union(set(get_users_any(cluster
).values_list("id", flat
=True)))
344 for vm
in VirtualMachine
.objects
.all():
345 users
= users
.union(set(get_users_any(vm
).values_list('id', flat
=True)))
347 keys
= SSHKey
.objects \
348 .filter(Q(user__in
=users
) |
Q(user__is_superuser
=True)) \
349 .values_list('key','user__username')\
350 .order_by('user__username')
352 keys_list
= list(keys
)
353 return HttpResponse(json
.dumps(keys_list
), mimetype
="application/json")