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
.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
):
45 Utility function for querying Node and Cluster in a single query
46 rather than 2 separate calls to get_object_or_404.
48 query
= Node
.objects \
49 .filter(cluster__slug
=cluster_slug
, hostname
=host
) \
50 .select_related('cluster')
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"])
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
)
72 "cluster": self
.cluster
,
73 "node_count": self
.cluster
.nodes
.all().count(),
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"
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.
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"]))
101 raise Http403(NO_PRIVS
)
103 self
.ajax_args
= [self
.cluster
.slug
, self
.node
.hostname
]
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
)
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',
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
)
149 if not (user
.is_superuser
or user
.has_any_perms(cluster
,
150 ['admin', 'migrate'])):
152 raise Http403(NO_PRIVS
)
154 return {'error': NO_PRIVS
}
156 return list_for_object(request
, node
, rest
)
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
)
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
)
175 job
= node
.set_role(form
.cleaned_data
['role'])
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
)]})
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
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
))
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
)
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
)
219 job
= node
.migrate(form
.cleaned_data
['mode'])
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
)]})
231 # error in form return ajax response
232 content
= json
.dumps(form
.errors
)
233 return HttpResponse(content
, mimetype
='application/json')
238 return render_to_response('ganeti/node/migrate.html',
239 {'form': form
, 'node': node
, 'cluster': cluster
},
240 context_instance
=RequestContext(request
))
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
)
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
)
259 data
= form
.cleaned_data
260 evacuate_node
= data
['node']
261 iallocator_hostname
= data
['iallocator_hostname']
262 job
= node
.evacuate(iallocator_hostname
, evacuate_node
)
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
)]})
274 # error in form return ajax response
275 content
= json
.dumps(form
.errors
)
276 return HttpResponse(content
, mimetype
='application/json')
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
))
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"),
294 object_id
=id).order_by('job_id')
295 jobs
= [j
.info
for j
in jobs
]
300 return HttpResponse(json
.dumps(jobs
), mimetype
='application/json')