Merge branch 'develop' into feature/search_autocomplete_haste
[ganeti_webmgr.git] / ganeti / views / cluster.py
blob3f8b05478445fd60b3c4c751f0494ed2ad6d8533
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,
17 # USA.
20 import json
22 from django import forms
23 from django.conf import settings
24 from django.contrib.auth.decorators import login_required
25 from django.contrib.auth.models import User
26 from django.core.urlresolvers import reverse
27 from django.db.models.query_utils import Q
28 from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden, HttpResponseNotAllowed
29 from django.shortcuts import get_object_or_404, render_to_response
30 from django.template import RequestContext
31 from django.template.defaultfilters import slugify
33 from object_permissions import get_users_any
34 from object_permissions.views.permissions import view_users, view_permissions
36 from muddle_users import signals as muddle_user_signals
38 from object_log.models import LogItem
39 from object_log.views import list_for_object
40 from util.client import GanetiApiError
42 log_action = LogItem.objects.log_action
44 from ganeti.models import Cluster, ClusterUser, Profile, SSHKey
45 from ganeti.views import render_403, render_404
46 from ganeti.views.virtual_machine import render_vms
47 from ganeti.fields import DataVolumeField
48 from django.utils.translation import ugettext as _
51 @login_required
52 def detail(request, cluster_slug):
53 """
54 Display details of a cluster
55 """
56 cluster = get_object_or_404(Cluster, slug=cluster_slug)
57 user = request.user
58 admin = True if user.is_superuser else user.has_perm('admin', cluster)
59 if not admin:
60 return render_403(request, _("You do not have sufficient privileges"))
62 return render_to_response("cluster/detail.html", {
63 'cluster':cluster,
64 'admin':admin
66 context_instance=RequestContext(request),
70 @login_required
71 def nodes(request, cluster_slug):
72 """
73 Display all nodes in a cluster
74 """
75 cluster = get_object_or_404(Cluster, slug=cluster_slug)
76 user = request.user
77 if not (user.is_superuser or user.has_perm('admin', cluster)):
78 return render_403(request, _("You do not have sufficient privileges"))
80 return render_to_response("node/table.html", \
81 {'cluster': cluster, 'nodes':cluster.nodes.all()}, \
82 context_instance=RequestContext(request),
86 @login_required
87 def virtual_machines(request, cluster_slug):
88 """
89 Display all virtual machines in a cluster. Filtered by access the user
90 has permissions for
91 """
92 cluster = get_object_or_404(Cluster, slug=cluster_slug)
93 user = request.user
94 admin = True if user.is_superuser else user.has_perm('admin', cluster)
95 if not admin:
96 return render_403(request, _("You do not have sufficient privileges"))
98 vms = cluster.virtual_machines.select_related('cluster').all()
99 vms = render_vms(request, vms)
101 return render_to_response("virtual_machine/table.html", \
102 {'cluster': cluster, 'vms':vms}, \
103 context_instance=RequestContext(request))
106 @login_required
107 def edit(request, cluster_slug=None):
109 Edit a cluster
111 if cluster_slug:
112 cluster = get_object_or_404(Cluster, slug=cluster_slug)
113 else:
114 cluster = None
116 user = request.user
117 if not (user.is_superuser or (cluster and user.has_perm('admin', cluster))):
118 return render_403(request, _("You do not have sufficient privileges"))
120 if request.method == 'POST':
121 form = EditClusterForm(request.POST, instance=cluster)
122 if form.is_valid():
123 cluster = form.save()
124 # TODO Create post signal to import
125 # virtual machines on edit of cluster
126 if cluster.info is None:
127 try:
128 cluster.sync_nodes()
129 cluster.sync_virtual_machines()
130 except GanetiApiError:
131 # ganeti errors here are silently discarded. It's
132 # valid to enter bad info. A user might be adding
133 # info for an offline cluster.
134 pass
136 log_action('EDIT', user, cluster)
138 return HttpResponseRedirect(reverse('cluster-detail', \
139 args=[cluster.slug]))
141 elif request.method == 'DELETE':
142 cluster.delete()
143 return HttpResponse('1', mimetype='application/json')
145 else:
146 form = EditClusterForm(instance=cluster)
148 return render_to_response("cluster/edit.html", {
149 'form' : form,
150 'cluster': cluster,
152 context_instance=RequestContext(request),
156 @login_required
157 def list_(request):
159 List all clusters
161 user = request.user
162 if user.is_superuser:
163 cluster_list = Cluster.objects.all()
164 else:
165 cluster_list = user.get_objects_all_perms(Cluster, ['admin',])
166 return render_to_response("cluster/list.html", {
167 'cluster_list': cluster_list,
168 'user': request.user,
170 context_instance=RequestContext(request),
174 @login_required
175 def users(request, cluster_slug):
177 Display all of the Users of a Cluster
179 cluster = get_object_or_404(Cluster, slug=cluster_slug)
181 user = request.user
182 if not (user.is_superuser or user.has_perm('admin', cluster)):
183 return render_403(request, _("You do not have sufficient privileges"))
185 url = reverse('cluster-permissions', args=[cluster.slug])
186 return view_users(request, cluster, url, template='cluster/users.html')
189 @login_required
190 def permissions(request, cluster_slug, user_id=None, group_id=None):
192 Update a users permissions. This wraps object_permissions.view_permissions()
193 with our custom permissions checks.
195 cluster = get_object_or_404(Cluster, slug=cluster_slug)
196 user = request.user
197 if not (user.is_superuser or user.has_perm('admin', cluster)):
198 return render_403(request, "You do not have sufficient privileges")
200 url = reverse('cluster-permissions', args=[cluster.slug])
201 return view_permissions(request, cluster, url, user_id, group_id,
202 user_template='cluster/user_row.html',
203 group_template='cluster/group_row.html')
206 @login_required
207 def redistribute_config(request, cluster_slug):
209 Redistribute master-node config to all cluster's other nodes.
211 cluster = get_object_or_404(Cluster, slug=cluster_slug)
213 user = request.user
214 if not (user.is_superuser or user.has_perm('admin', cluster)):
215 return render_403(request, "You do not have sufficient privileges")
217 if request.method == 'POST':
218 try:
219 job = cluster.redistribute_config()
220 job.load_info()
221 msg = job.info
223 log_action('CLUSTER_REDISTRIBUTE', user, cluster, job)
224 except GanetiApiError, e:
225 msg = {'__all__':[str(e)]}
226 return HttpResponse(json.dumps(msg), mimetype='application/json')
227 return HttpResponseNotAllowed(['POST'])
230 def ssh_keys(request, cluster_slug, api_key):
232 Show all ssh keys which belong to users, who have any perms on the cluster
234 if settings.WEB_MGR_API_KEY != api_key:
235 return HttpResponseForbidden(_("You're not allowed to view keys."))
237 cluster = get_object_or_404(Cluster, slug=cluster_slug)
239 users = set(get_users_any(cluster).values_list("id", flat=True))
240 for vm in cluster.virtual_machines.all():
241 users = users.union(set(get_users_any(vm).values_list('id', flat=True)))
243 keys = SSHKey.objects \
244 .filter(Q(user__in=users) | Q(user__is_superuser=True)) \
245 .values_list('key','user__username') \
246 .order_by('user__username')
248 keys_list = list(keys)
249 return HttpResponse(json.dumps(keys_list), mimetype="application/json")
252 class QuotaForm(forms.Form):
254 Form for editing user quota on a cluster
256 input = forms.TextInput(attrs={'size':5})
258 user = forms.ModelChoiceField(queryset=ClusterUser.objects.all(), \
259 widget=forms.HiddenInput)
260 ram = DataVolumeField(label='Memory', required=False, min_value=0)
261 virtual_cpus = forms.IntegerField(label='Virtual CPUs', required=False, \
262 min_value=0, widget=input)
263 disk = DataVolumeField(label='Disk Space', required=False, min_value=0)
264 delete = forms.BooleanField(required=False, widget=forms.HiddenInput)
267 @login_required
268 def quota(request, cluster_slug, user_id):
270 Updates quota for a user
272 cluster = get_object_or_404(Cluster, slug=cluster_slug)
273 user = request.user
274 if not (user.is_superuser or user.has_perm('admin', cluster)):
275 return render_403(request, _("You do not have sufficient privileges"))
277 if request.method == 'POST':
278 form = QuotaForm(request.POST)
279 if form.is_valid():
280 data = form.cleaned_data
281 cluster_user = data['user']
282 if data['delete']:
283 cluster.set_quota(cluster_user)
284 else:
285 quota = cluster.get_quota()
286 same = data['virtual_cpus'] == quota['virtual_cpus'] \
287 and data['disk']==quota['disk'] \
288 and data['ram']==quota['ram']
289 if same:
290 # same as default, set quota to default.
291 cluster.set_quota(cluster_user)
292 else:
293 cluster.set_quota(cluster_user, data)
295 # return updated html
296 cluster_user = cluster_user.cast()
297 url = reverse('cluster-permissions', args=[cluster.slug])
298 if isinstance(cluster_user, (Profile,)):
299 return render_to_response("cluster/user_row.html",
300 {'object':cluster, 'user_detail':cluster_user.user, 'url':url},
301 context_instance=RequestContext(request))
302 else:
303 return render_to_response("cluster/group_row.html",
304 {'object':cluster, 'group':cluster_user.group, 'url':url},
305 context_instance=RequestContext(request))
307 # error in form return ajax response
308 content = json.dumps(form.errors)
309 return HttpResponse(content, mimetype='application/json')
311 if user_id:
312 cluster_user = get_object_or_404(ClusterUser, id=user_id)
313 quota = cluster.get_quota(cluster_user)
314 data = {'user':user_id}
315 if quota:
316 data.update(quota)
317 else:
318 return render_404(request, _('User was not found'))
320 form = QuotaForm(data)
321 return render_to_response("cluster/quota.html", \
322 {'form':form, 'cluster':cluster, 'user_id':user_id}, \
323 context_instance=RequestContext(request))
326 class EditClusterForm(forms.ModelForm):
327 class Meta:
328 model = Cluster
329 widgets = {
330 'password' : forms.PasswordInput(),
333 ram = DataVolumeField(label=_('Memory'), required=False, min_value=0)
334 disk = DataVolumeField(label=_('Disk Space'), required=False, min_value=0)
336 def clean(self):
337 self.cleaned_data = super(EditClusterForm, self).clean()
338 data = self.cleaned_data
339 host = data.get('hostname', None)
340 user = data.get('username', None)
341 new = data.get('password', None)
343 # Automatically set the slug on cluster creation
344 if not host:
345 msg = _('Enter a hostname')
346 self._errors['hostname'] = self.error_class([msg])
348 if user:
349 if not new:
350 if 'password' in data: del data['password']
351 msg = _('Enter a password')
352 self._errors['password'] = self.error_class([msg])
354 elif new:
355 msg = _('Enter a username')
356 self._errors['username'] = self.error_class([msg])
358 if not new:
359 if 'password' in data: del data['password']
360 msg = _('Enter a password')
361 self._errors['password'] = self.error_class([msg])
363 if 'hostname' in data and data['hostname'] and 'slug' not in data:
364 data['slug'] = slugify(data['hostname'].split('.')[0])
365 del self._errors['slug']
367 return data
370 @login_required
371 def object_log(request, cluster_slug):
372 """ displays object log for this cluster """
373 cluster = get_object_or_404(Cluster, slug=cluster_slug)
374 user = request.user
375 if not (user.is_superuser or user.has_perm('admin', cluster)):
376 return render_403(request, _("You do not have sufficient privileges"))
377 return list_for_object(request, cluster)
380 def recv_user_add(sender, editor, user, obj, **kwargs):
382 receiver for object_permissions.signals.view_add_user, Logs action
384 log_action('ADD_USER', editor, obj, user)
387 def recv_user_remove(sender, editor, user, obj, **kwargs):
389 receiver for object_permissions.signals.view_remove_user, Logs action
391 log_action('REMOVE_USER', editor, obj, user)
393 # remove custom quota user may have had.
394 if isinstance(user, (User,)):
395 cluster_user = user.get_profile()
396 else:
397 cluster_user = user.organization
398 cluster_user.quotas.filter(cluster=obj).delete()
401 def recv_perm_edit(sender, editor, user, obj, **kwargs):
403 receiver for object_permissions.signals.view_edit_user, Logs action
405 log_action('MODIFY_PERMS', editor, obj, user)
408 muddle_user_signals.view_add_user.connect(recv_user_add, sender=Cluster)
409 muddle_user_signals.view_remove_user.connect(recv_user_remove, sender=Cluster)
410 muddle_user_signals.view_edit_user.connect(recv_perm_edit, sender=Cluster)