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,
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
,
42 def get_node_and_cluster_or_404(cluster_slug
, host
):
44 Utility function for querying Node and Cluster in a single query
45 rather than 2 separate calls to get_object_or_404.
47 query
= Node
.objects \
48 .filter(cluster__slug
=cluster_slug
, hostname
=host
) \
49 .select_related('cluster')
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"])
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
)
71 "cluster": self
.cluster
,
72 "node_count": self
.cluster
.nodes
.all().count(),
80 class NodePrimaryListView(LoginRequiredMixin
, PagedListView
):
82 Renders a list of primary VirtualMachines on the given node.
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
):
100 "tableID": "table_primary",
101 "primary_node": True,
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
):
127 "tableID": "table_secondary",
128 "secondary_node": True,
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
)
142 if not (user
.is_superuser
or user
.has_any_perms(cluster
,
143 ['admin', 'migrate'])):
145 raise Http403(NO_PRIVS
)
147 return {'error': NO_PRIVS
}
149 return list_for_object(request
, node
, rest
)
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
)
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
)
168 job
= node
.set_role(form
.cleaned_data
['role'])
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
)]})
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
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
))
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
)
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
)
212 job
= node
.migrate(form
.cleaned_data
['mode'])
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
)]})
224 # error in form return ajax response
225 content
= json
.dumps(form
.errors
)
226 return HttpResponse(content
, mimetype
='application/json')
231 return render_to_response('ganeti/node/migrate.html',
232 {'form': form
, 'node': node
, 'cluster': cluster
},
233 context_instance
=RequestContext(request
))
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
)
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
)
252 data
= form
.cleaned_data
253 evacuate_node
= data
['node']
254 iallocator_hostname
= data
['iallocator_hostname']
255 job
= node
.evacuate(iallocator_hostname
, evacuate_node
)
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
)]})
267 # error in form return ajax response
268 content
= json
.dumps(form
.errors
)
269 return HttpResponse(content
, mimetype
='application/json')
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
))
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"),
287 object_id
=id).order_by('job_id')
288 jobs
= [j
.info
for j
in jobs
]
293 return HttpResponse(json
.dumps(jobs
), mimetype
='application/json')