Add more information on deploying; fix format.
[ganeti_webmgr.git] / ganeti_web / views / node.py
blob6c7376cce5a00d7a483632df1cdc3d1b2c877a45
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.db.models.query_utils import Q
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 PagedListView)
42 def get_node_and_cluster_or_404(cluster_slug, host):
43 """
44 Utility function for querying Node and Cluster in a single query
45 rather than 2 separate calls to get_object_or_404.
46 """
47 query = Node.objects \
48 .filter(cluster__slug=cluster_slug, hostname=host) \
49 .select_related('cluster')
50 if len(query):
51 return query[0], query[0].cluster
52 raise Http404('Node does not exist')
55 class NodeDetailView(LoginRequiredMixin, DetailView):
57 template_name = "ganeti/node/detail.html"
59 def get_object(self, queryset=None):
60 self.node, self.cluster = get_node_and_cluster_or_404(
61 self.kwargs["cluster_slug"], self.kwargs["host"])
62 return self.node
64 def get_context_data(self, **kwargs):
65 user = self.request.user
66 admin = user.is_superuser or user.has_perm('admin', self.cluster)
67 modify = admin or user.has_perm('migrate', self.cluster)
68 readonly = not (admin or modify)
70 return {
71 "cluster": self.cluster,
72 "node_count": self.cluster.nodes.all().count(),
73 "node": self.node,
74 "admin": admin,
75 "modify": modify,
76 "readonly": readonly,
80 class NodePrimaryListView(LoginRequiredMixin, PagedListView):
81 """
82 Renders a list of primary VirtualMachines on the given node.
83 """
85 template_name = "ganeti/virtual_machine/table.html"
87 def get_queryset(self):
88 self.node, self.cluster = get_node_and_cluster_or_404(
89 self.kwargs["cluster_slug"], self.kwargs["host"])
91 user = self.request.user
92 if not (user.is_superuser or
93 user.has_any_perms(self.cluster, ["admin", "migrate"])):
94 raise Http403(NO_PRIVS)
96 return self.node.primary_vms.all()
98 def get_context_data(self, **kwargs):
99 kwargs.update({
100 "tableID": "table_primary",
101 "primary_node": True,
102 "node": self.node,
104 return kwargs
107 class NodeSecondaryListView(LoginRequiredMixin, PagedListView):
109 Renders a list of secondary VirtualMachines on the given node.
112 template_name = "ganeti/virtual_machine/table.html"
114 def get_queryset(self):
115 self.node, self.cluster = get_node_and_cluster_or_404(
116 self.kwargs["cluster_slug"], self.kwargs["host"])
118 user = self.request.user
119 if not (user.is_superuser or
120 user.has_any_perms(self.cluster, ["admin", "migrate"])):
121 raise Http403(NO_PRIVS)
123 return self.node.secondary_vms.all()
125 def get_context_data(self, **kwargs):
126 kwargs.update({
127 "tableID": "table_secondary",
128 "secondary_node": True,
129 "node": self.node,
131 return kwargs
134 @login_required
135 def object_log(request, cluster_slug, host, rest=False):
137 Display object log for this node
139 node, cluster = get_node_and_cluster_or_404(cluster_slug, host)
141 user = request.user
142 if not (user.is_superuser or user.has_any_perms(cluster,
143 ['admin', 'migrate'])):
144 if not rest:
145 raise Http403(NO_PRIVS)
146 else:
147 return {'error': NO_PRIVS}
149 return list_for_object(request, node, rest)
152 @login_required
153 def role(request, cluster_slug, host):
155 view used for setting node role
157 node, cluster = get_node_and_cluster_or_404(cluster_slug, host)
159 user = request.user
160 if not (user.is_superuser or user.has_any_perms(cluster,
161 ['admin', 'migrate'])):
162 raise Http403(NO_PRIVS)
164 if request.method == 'POST':
165 form = RoleForm(request.POST)
166 if form.is_valid():
167 try:
168 job = node.set_role(form.cleaned_data['role'])
169 job.refresh()
170 msg = job.info
172 # log information
173 log_action('NODE_ROLE_CHANGE', user, node)
174 return HttpResponse(json.dumps(msg),
175 mimetype='application/json')
176 except GanetiApiError, e:
177 content = json.dumps({'__all__': [str(e)]})
178 else:
179 # error in form return ajax response
180 content = json.dumps(form.errors)
181 return HttpResponse(content, mimetype='application/json')
183 elif node.role == 'M':
184 # XXX master isn't a possible choice for changing role
185 form = RoleForm()
187 else:
188 data = {'role': constants.ROLE_MAP[node.role]}
189 form = RoleForm(data)
191 return render_to_response('ganeti/node/role.html',
192 {'form': form, 'node': node, 'cluster': cluster},
193 context_instance=RequestContext(request))
196 @login_required
197 def migrate(request, cluster_slug, host):
199 view used for initiating a Node Migrate job
201 node, cluster = get_node_and_cluster_or_404(cluster_slug, host)
203 user = request.user
204 if not (user.is_superuser or user.has_any_perms(cluster,
205 ['admin', 'migrate'])):
206 raise Http403(NO_PRIVS)
208 if request.method == 'POST':
209 form = MigrateForm(request.POST)
210 if form.is_valid():
211 try:
212 job = node.migrate(form.cleaned_data['mode'])
213 job.refresh()
214 msg = job.info
216 # log information
217 log_action('NODE_MIGRATE', user, node)
219 return HttpResponse(json.dumps(msg),
220 mimetype='application/json')
221 except GanetiApiError, e:
222 content = json.dumps({'__all__': [str(e)]})
223 else:
224 # error in form return ajax response
225 content = json.dumps(form.errors)
226 return HttpResponse(content, mimetype='application/json')
228 else:
229 form = MigrateForm()
231 return render_to_response('ganeti/node/migrate.html',
232 {'form': form, 'node': node, 'cluster': cluster},
233 context_instance=RequestContext(request))
236 @login_required
237 def evacuate(request, cluster_slug, host):
239 view used for initiating a node evacuate job
241 node, cluster = get_node_and_cluster_or_404(cluster_slug, host)
243 user = request.user
244 if not (user.is_superuser or user.has_any_perms(cluster,
245 ['admin', 'migrate'])):
246 raise Http403(NO_PRIVS)
248 if request.method == 'POST':
249 form = EvacuateForm(cluster, node, request.POST)
250 if form.is_valid():
251 try:
252 data = form.cleaned_data
253 evacuate_node = data['node']
254 iallocator_hostname = data['iallocator_hostname']
255 job = node.evacuate(iallocator_hostname, evacuate_node)
256 job.refresh()
257 msg = job.info
259 # log information
260 log_action('NODE_EVACUATE', user, node, job)
262 return HttpResponse(json.dumps(msg),
263 mimetype='application/json')
264 except GanetiApiError, e:
265 content = json.dumps({'__all__': [str(e)]})
266 else:
267 # error in form return ajax response
268 content = json.dumps(form.errors)
269 return HttpResponse(content, mimetype='application/json')
271 else:
272 form = EvacuateForm(cluster, node)
274 return render_to_response('ganeti/node/evacuate.html',
275 {'form': form, 'node': node, 'cluster': cluster},
276 context_instance=RequestContext(request))
279 @login_required
280 def job_status(request, id, rest=False):
282 Return a list of basic info for running jobs.
284 ct = ContentType.objects.get_for_model(Node)
285 jobs = Job.objects.filter(status__in=("error", "running", "waiting"),
286 content_type=ct,
287 object_id=id).order_by('job_id')
288 jobs = [j.info for j in jobs]
290 if rest:
291 return jobs
292 else:
293 return HttpResponse(json.dumps(jobs), mimetype='application/json')