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,
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_web
.models
import Cluster
, ClusterUser
, Profile
, SSHKey
45 from ganeti_web
.views
import render_403
, render_404
46 from ganeti_web
.views
.virtual_machine
import render_vms
47 from ganeti_web
.fields
import DataVolumeField
48 from django
.utils
.translation
import ugettext
as _
52 def detail(request
, cluster_slug
):
54 Display details of a cluster
56 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
58 admin
= True if user
.is_superuser
else user
.has_perm('admin', cluster
)
60 return render_403(request
, _("You do not have sufficient privileges"))
62 return render_to_response("cluster/detail.html", {
66 context_instance
=RequestContext(request
),
71 def nodes(request
, cluster_slug
):
73 Display all nodes in a cluster
75 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
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
),
87 def virtual_machines(request
, cluster_slug
):
89 Display all virtual machines in a cluster. Filtered by access the user
92 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
94 admin
= True if user
.is_superuser
else user
.has_perm('admin', cluster
)
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
))
107 def edit(request
, cluster_slug
=None):
112 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
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
)
123 cluster
= form
.save()
124 # TODO Create post signal to import
125 # virtual machines on edit of cluster
126 if cluster
.info
is None:
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.
136 log_action('EDIT', user
, cluster
)
138 return HttpResponseRedirect(reverse('cluster-detail', \
139 args
=[cluster
.slug
]))
141 elif request
.method
== 'DELETE':
143 return HttpResponse('1', mimetype
='application/json')
146 form
= EditClusterForm(instance
=cluster
)
148 return render_to_response("cluster/edit.html", {
152 context_instance
=RequestContext(request
),
162 if user
.is_superuser
:
163 cluster_list
= Cluster
.objects
.all()
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
),
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
)
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')
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
)
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')
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
)
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':
219 job
= cluster
.redistribute_config()
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
)
268 def quota(request
, cluster_slug
, user_id
):
270 Updates quota for a user
272 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
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
)
280 data
= form
.cleaned_data
281 cluster_user
= data
['user']
283 cluster
.set_quota(cluster_user
)
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']
290 # same as default, set quota to default.
291 cluster
.set_quota(cluster_user
)
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
))
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')
312 cluster_user
= get_object_or_404(ClusterUser
, id=user_id
)
313 quota
= cluster
.get_quota(cluster_user
)
314 data
= {'user':user_id
}
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
):
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)
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
345 msg
= _('Enter a hostname')
346 self
._errors
['hostname'] = self
.error_class([msg
])
350 if 'password' in data
: del data
['password']
351 msg
= _('Enter a password')
352 self
._errors
['password'] = self
.error_class([msg
])
355 msg
= _('Enter a username')
356 self
._errors
['username'] = self
.error_class([msg
])
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']
371 def object_log(request
, cluster_slug
):
372 """ displays object log for this cluster """
373 cluster
= get_object_or_404(Cluster
, slug
=cluster_slug
)
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()
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
)