Ticket #2733 - Cluster: Redist config button
[ganeti_webmgr.git] / ganeti / views / cluster.py
bloba82e9c22b2d3d8998c1ddb275e3f99a6e49d48f2
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
35 from object_permissions import signals as op_signals
37 from object_log.models import LogItem
38 from object_log.views import list_for_object
39 from util.client import GanetiApiError
41 log_action = LogItem.objects.log_action
43 from ganeti.models import Cluster, ClusterUser, Profile, SSHKey
44 from ganeti.views import render_403, render_404
45 from ganeti.views.virtual_machine import render_vms
46 from ganeti.fields import DataVolumeField
47 from django.utils.translation import ugettext as _
49 # Regex for a resolvable hostname
50 FQDN_RE = r'^[\w]+(\.[\w]+)*$'
53 @login_required
54 def detail(request, cluster_slug):
55 """
56 Display details of a cluster
57 """
58 cluster = get_object_or_404(Cluster, slug=cluster_slug)
59 user = request.user
60 admin = True if user.is_superuser else user.has_perm('admin', cluster)
61 if not admin:
62 return render_403(request, _("You do not have sufficient privileges"))
64 return render_to_response("cluster/detail.html", {
65 'cluster':cluster,
66 'admin':admin
68 context_instance=RequestContext(request),
72 @login_required
73 def nodes(request, cluster_slug):
74 """
75 Display all nodes in a cluster
76 """
77 cluster = get_object_or_404(Cluster, slug=cluster_slug)
78 user = request.user
79 if not (user.is_superuser or user.has_perm('admin', cluster)):
80 return render_403(request, _("You do not have sufficient privileges"))
82 return render_to_response("node/table.html", \
83 {'cluster': cluster, 'nodes':cluster.nodes.all()}, \
84 context_instance=RequestContext(request),
88 @login_required
89 def virtual_machines(request, cluster_slug):
90 """
91 Display all virtual machines in a cluster. Filtered by access the user
92 has permissions for
93 """
94 cluster = get_object_or_404(Cluster, slug=cluster_slug)
95 user = request.user
96 admin = True if user.is_superuser else user.has_perm('admin', cluster)
97 if not admin:
98 return render_403(request, _("You do not have sufficient privileges"))
100 vms = cluster.virtual_machines.select_related('cluster').all()
101 vms = render_vms(request, vms)
103 return render_to_response("virtual_machine/table.html", \
104 {'cluster': cluster, 'vms':vms}, \
105 context_instance=RequestContext(request))
108 @login_required
109 def edit(request, cluster_slug=None):
111 Edit a cluster
113 if cluster_slug:
114 cluster = get_object_or_404(Cluster, slug=cluster_slug)
115 else:
116 cluster = None
118 user = request.user
119 if not (user.is_superuser or (cluster and user.has_perm('admin', cluster))):
120 return render_403(request, _("You do not have sufficient privileges"))
122 if request.method == 'POST':
123 form = EditClusterForm(request.POST, instance=cluster)
124 if form.is_valid():
125 cluster = form.save()
126 # TODO Create post signal to import
127 # virtual machines on edit of cluster
128 if cluster.info is None:
129 try:
130 cluster.sync_nodes()
131 cluster.sync_virtual_machines()
132 except GanetiApiError:
133 # ganeti errors here are silently discarded. It's
134 # valid to enter bad info. A user might be adding
135 # info for an offline cluster.
136 pass
138 log_action('EDIT', user, cluster)
140 return HttpResponseRedirect(reverse('cluster-detail', \
141 args=[cluster.slug]))
143 elif request.method == 'DELETE':
144 cluster.delete()
145 return HttpResponse('1', mimetype='application/json')
147 else:
148 form = EditClusterForm(instance=cluster)
150 return render_to_response("cluster/edit.html", {
151 'form' : form,
152 'cluster': cluster,
154 context_instance=RequestContext(request),
158 @login_required
159 def list_(request):
161 List all clusters
163 user = request.user
164 if user.is_superuser:
165 cluster_list = Cluster.objects.all()
166 else:
167 cluster_list = user.get_objects_all_perms(Cluster, ['admin',])
168 return render_to_response("cluster/list.html", {
169 'cluster_list': cluster_list,
170 'user': request.user,
172 context_instance=RequestContext(request),
176 @login_required
177 def users(request, cluster_slug):
179 Display all of the Users of a Cluster
181 cluster = get_object_or_404(Cluster, slug=cluster_slug)
183 user = request.user
184 if not (user.is_superuser or user.has_perm('admin', cluster)):
185 return render_403(request, _("You do not have sufficient privileges"))
187 url = reverse('cluster-permissions', args=[cluster.slug])
188 return view_users(request, cluster, url, template='cluster/users.html')
191 @login_required
192 def permissions(request, cluster_slug, user_id=None, group_id=None):
194 Update a users permissions. This wraps object_permissions.view_permissions()
195 with our custom permissions checks.
197 cluster = get_object_or_404(Cluster, slug=cluster_slug)
198 user = request.user
199 if not (user.is_superuser or user.has_perm('admin', cluster)):
200 return render_403(request, "You do not have sufficient privileges")
202 url = reverse('cluster-permissions', args=[cluster.slug])
203 return view_permissions(request, cluster, url, user_id, group_id,
204 user_template='cluster/user_row.html',
205 group_template='cluster/group_row.html')
208 @login_required
209 def redistribute_config(request, cluster_slug):
211 Redistribute master-node config to all cluster's other nodes.
213 cluster = get_object_or_404(Cluster, slug=cluster_slug)
215 user = request.user
216 if not (user.is_superuser or user.has_perm('admin', cluster)):
217 return render_403(request, "You do not have sufficient privileges")
219 if request.method == 'POST':
220 try:
221 job = cluster.redistribute_config()
222 job.load_info()
223 msg = job.info
225 log_action('CLUSTER_REDISTRIBUTE', user, cluster, job)
226 except GanetiApiError, e:
227 msg = {'__all__':[str(e)]}
228 return HttpResponse(json.dumps(msg), mimetype='application/json')
229 return HttpResponseNotAllowed(['POST'])
232 def ssh_keys(request, cluster_slug, api_key):
234 Show all ssh keys which belong to users, who have any perms on the cluster
236 if settings.WEB_MGR_API_KEY != api_key:
237 return HttpResponseForbidden(_("You're not allowed to view keys."))
239 cluster = get_object_or_404(Cluster, slug=cluster_slug)
241 users = set(get_users_any(cluster).values_list("id", flat=True))
242 for vm in cluster.virtual_machines.all():
243 users = users.union(set(get_users_any(vm).values_list('id', flat=True)))
245 keys = SSHKey.objects \
246 .filter(Q(user__in=users) | Q(user__is_superuser=True)) \
247 .values_list('key','user__username') \
248 .order_by('user__username')
250 keys_list = list(keys)
251 return HttpResponse(json.dumps(keys_list), mimetype="application/json")
254 class QuotaForm(forms.Form):
256 Form for editing user quota on a cluster
258 input = forms.TextInput(attrs={'size':5})
260 user = forms.ModelChoiceField(queryset=ClusterUser.objects.all(), \
261 widget=forms.HiddenInput)
262 ram = DataVolumeField(label='Memory', required=False, min_value=0)
263 virtual_cpus = forms.IntegerField(label='Virtual CPUs', required=False, \
264 min_value=0, widget=input)
265 disk = DataVolumeField(label='Disk Space', required=False, min_value=0)
266 delete = forms.BooleanField(required=False, widget=forms.HiddenInput)
268 @login_required
269 def quota(request, cluster_slug, user_id):
271 Updates quota for a user
273 cluster = get_object_or_404(Cluster, slug=cluster_slug)
274 user = request.user
275 if not (user.is_superuser or user.has_perm('admin', cluster)):
276 return render_403(request, _("You do not have sufficient privileges"))
278 if request.method == 'POST':
279 form = QuotaForm(request.POST)
280 if form.is_valid():
281 data = form.cleaned_data
282 cluster_user = data['user']
283 if data['delete']:
284 cluster.set_quota(cluster_user)
285 else:
286 quota = cluster.get_quota()
287 same = data['virtual_cpus'] == quota['virtual_cpus'] \
288 and data['disk']==quota['disk'] \
289 and data['ram']==quota['ram']
290 if same:
291 # same as default, set quota to default.
292 cluster.set_quota(cluster_user)
293 else:
294 cluster.set_quota(cluster_user, data)
296 # return updated html
297 cluster_user = cluster_user.cast()
298 url = reverse('cluster-permissions', args=[cluster.slug])
299 if isinstance(cluster_user, (Profile,)):
300 return render_to_response("cluster/user_row.html",
301 {'object':cluster, 'user':cluster_user.user, 'url':url})
302 else:
303 return render_to_response("cluster/group_row.html",
304 {'object':cluster, 'group':cluster_user.group, \
305 'url':url})
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 op_signals.view_add_user.connect(recv_user_add, sender=Cluster)
409 op_signals.view_remove_user.connect(recv_user_remove, sender=Cluster)
410 op_signals.view_edit_user.connect(recv_perm_edit, sender=Cluster)