Move default template for tables out of template into table base class
[ganeti_webmgr.git] / ganeti_web / views / node.py
blobb48fcd050578014face2995f822714193435a6de
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.
19 from django.contrib.auth.decorators import login_required
20 from django.contrib.contenttypes.models import ContentType
21 from django.core.urlresolvers import reverse
22 from django.http import HttpResponse, Http404
23 from django.shortcuts import render_to_response
24 from django.template import RequestContext
25 from django.utils import simplejson as json
26 from django.views.generic.detail import DetailView
28 from object_log.models import LogItem
29 from object_log.views import list_for_object
31 log_action = LogItem.objects.log_action
33 from ganeti_web.util.client import GanetiApiError
34 from ganeti_web import constants
35 from ganeti_web.forms.node import RoleForm, MigrateForm, EvacuateForm
36 from ganeti_web.middleware import Http403
37 from ganeti_web.models import Node, Job
38 from ganeti_web.views.generic import NO_PRIVS, LoginRequiredMixin
39 from ganeti_web.views.virtual_machine import BaseVMListView
40 from ganeti_web.views.tables import NodeVMTable
43 def get_node_and_cluster_or_404(cluster_slug, host):
44 """
45 Utility function for querying Node and Cluster in a single query
46 rather than 2 separate calls to get_object_or_404.
47 """
48 query = Node.objects \
49 .filter(cluster__slug=cluster_slug, hostname=host) \
50 .select_related('cluster')
51 if len(query):
52 return query[0], query[0].cluster
53 raise Http404('Node does not exist')
56 class NodeDetailView(LoginRequiredMixin, DetailView):
58 template_name = "ganeti/node/detail.html"
60 def get_object(self, queryset=None):
61 self.node, self.cluster = get_node_and_cluster_or_404(
62 self.kwargs["cluster_slug"], self.kwargs["host"])
63 return self.node
65 def get_context_data(self, **kwargs):
66 user = self.request.user
67 admin = user.is_superuser or user.has_perm('admin', self.cluster)
68 modify = admin or user.has_perm('migrate', self.cluster)
69 readonly = not (admin or modify)
71 return {
72 "cluster": self.cluster,
73 "node_count": self.cluster.nodes.all().count(),
74 "node": self.node,
75 "admin": admin,
76 "modify": modify,
77 "readonly": readonly,
81 # We can probably get away with a single view, and filter which vm's by url,
82 # rather than the foreign key, but this works.
84 class BaseNodeVMListView(BaseVMListView):
86 table_class = NodeVMTable
87 template_name = "ganeti/virtual_machine/list.html"
89 def get_node(self):
90 """
91 Helper method to query the database and retrieve the node, and cluster.
92 If the user has perms return the node otherwise return a 403 error.
93 """
94 self.node, self.cluster = get_node_and_cluster_or_404(
95 self.kwargs["cluster_slug"], self.kwargs["host"])
97 user = self.request.user
98 self.admin = (user.is_superuser or user.has_any_perms(self.cluster,
99 ["admin", "migrate"]))
100 if not self.admin:
101 raise Http403(NO_PRIVS)
103 self.ajax_args = [self.cluster.slug, self.node.hostname]
105 return self.node
108 class NodePrimaryListView(BaseNodeVMListView):
110 Renders a list of primary VirtualMachines on the given node.
113 def get_queryset(self):
114 node = self.get_node()
115 self.queryset = node.primary_vms.all()
116 return super(NodePrimaryListView, self).get_queryset()
118 def get_context_data(self, **kwargs):
119 context = super(BaseNodeVMListView, self).get_context_data(**kwargs)
120 context['ajax_url'] = reverse('node-primary-vms', args=self.ajax_args)
121 return context
124 class NodeSecondaryListView(BaseNodeVMListView):
126 Renders a list of secondary VirtualMachines on the given node.
129 def get_queryset(self):
130 node = self.get_node()
131 self.queryset = node.secondary_vms.all()
132 return super(NodeSecondaryListView, self).get_queryset()
134 def get_context_data(self, **kwargs):
135 context = super(BaseNodeVMListView, self).get_context_data(**kwargs)
136 context['ajax_url'] = reverse('node-secondary-vms',
137 args=self.ajax_args)
138 return context
141 @login_required
142 def object_log(request, cluster_slug, host, rest=False):
144 Display object log for this node
146 node, cluster = get_node_and_cluster_or_404(cluster_slug, host)
148 user = request.user
149 if not (user.is_superuser or user.has_any_perms(cluster,
150 ['admin', 'migrate'])):
151 if not rest:
152 raise Http403(NO_PRIVS)
153 else:
154 return {'error': NO_PRIVS}
156 return list_for_object(request, node, rest)
159 @login_required
160 def role(request, cluster_slug, host):
162 view used for setting node role
164 node, cluster = get_node_and_cluster_or_404(cluster_slug, host)
166 user = request.user
167 if not (user.is_superuser or user.has_any_perms(cluster,
168 ['admin', 'migrate'])):
169 raise Http403(NO_PRIVS)
171 if request.method == 'POST':
172 form = RoleForm(request.POST)
173 if form.is_valid():
174 try:
175 job = node.set_role(form.cleaned_data['role'])
176 job.refresh()
177 msg = job.info
179 # log information
180 log_action('NODE_ROLE_CHANGE', user, node)
181 return HttpResponse(json.dumps(msg),
182 mimetype='application/json')
183 except GanetiApiError, e:
184 content = json.dumps({'__all__': [str(e)]})
185 else:
186 # error in form return ajax response
187 content = json.dumps(form.errors)
188 return HttpResponse(content, mimetype='application/json')
190 elif node.role == 'M':
191 # XXX master isn't a possible choice for changing role
192 form = RoleForm()
194 else:
195 data = {'role': constants.ROLE_MAP[node.role]}
196 form = RoleForm(data)
198 return render_to_response('ganeti/node/role.html',
199 {'form': form, 'node': node, 'cluster': cluster},
200 context_instance=RequestContext(request))
203 @login_required
204 def migrate(request, cluster_slug, host):
206 view used for initiating a Node Migrate job
208 node, cluster = get_node_and_cluster_or_404(cluster_slug, host)
210 user = request.user
211 if not (user.is_superuser or user.has_any_perms(cluster,
212 ['admin', 'migrate'])):
213 raise Http403(NO_PRIVS)
215 if request.method == 'POST':
216 form = MigrateForm(request.POST)
217 if form.is_valid():
218 try:
219 job = node.migrate(form.cleaned_data['mode'])
220 job.refresh()
221 msg = job.info
223 # log information
224 log_action('NODE_MIGRATE', user, node)
226 return HttpResponse(json.dumps(msg),
227 mimetype='application/json')
228 except GanetiApiError, e:
229 content = json.dumps({'__all__': [str(e)]})
230 else:
231 # error in form return ajax response
232 content = json.dumps(form.errors)
233 return HttpResponse(content, mimetype='application/json')
235 else:
236 form = MigrateForm()
238 return render_to_response('ganeti/node/migrate.html',
239 {'form': form, 'node': node, 'cluster': cluster},
240 context_instance=RequestContext(request))
243 @login_required
244 def evacuate(request, cluster_slug, host):
246 view used for initiating a node evacuate job
248 node, cluster = get_node_and_cluster_or_404(cluster_slug, host)
250 user = request.user
251 if not (user.is_superuser or user.has_any_perms(cluster,
252 ['admin', 'migrate'])):
253 raise Http403(NO_PRIVS)
255 if request.method == 'POST':
256 form = EvacuateForm(cluster, node, request.POST)
257 if form.is_valid():
258 try:
259 data = form.cleaned_data
260 evacuate_node = data['node']
261 iallocator_hostname = data['iallocator_hostname']
262 job = node.evacuate(iallocator_hostname, evacuate_node)
263 job.refresh()
264 msg = job.info
266 # log information
267 log_action('NODE_EVACUATE', user, node, job)
269 return HttpResponse(json.dumps(msg),
270 mimetype='application/json')
271 except GanetiApiError, e:
272 content = json.dumps({'__all__': [str(e)]})
273 else:
274 # error in form return ajax response
275 content = json.dumps(form.errors)
276 return HttpResponse(content, mimetype='application/json')
278 else:
279 form = EvacuateForm(cluster, node)
281 return render_to_response('ganeti/node/evacuate.html',
282 {'form': form, 'node': node, 'cluster': cluster},
283 context_instance=RequestContext(request))
286 @login_required
287 def job_status(request, id, rest=False):
289 Return a list of basic info for running jobs.
291 ct = ContentType.objects.get_for_model(Node)
292 jobs = Job.objects.filter(status__in=("error", "running", "waiting"),
293 content_type=ct,
294 object_id=id).order_by('job_id')
295 jobs = [j.info for j in jobs]
297 if rest:
298 return jobs
299 else:
300 return HttpResponse(json.dumps(jobs), mimetype='application/json')