root / lib / cmdlib / instance_query.py @ b54ecf12
History | View | Annotate | Download (15 kB)
1 |
#
|
---|---|
2 |
#
|
3 |
|
4 |
# Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013 Google Inc.
|
5 |
#
|
6 |
# This program is free software; you can redistribute it and/or modify
|
7 |
# it under the terms of the GNU General Public License as published by
|
8 |
# the Free Software Foundation; either version 2 of the License, or
|
9 |
# (at your option) any later version.
|
10 |
#
|
11 |
# This program is distributed in the hope that it will be useful, but
|
12 |
# WITHOUT ANY WARRANTY; without even the implied warranty of
|
13 |
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
14 |
# General Public License for more details.
|
15 |
#
|
16 |
# You should have received a copy of the GNU General Public License
|
17 |
# along with this program; if not, write to the Free Software
|
18 |
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
|
19 |
# 02110-1301, USA.
|
20 |
|
21 |
|
22 |
"""Logical units for querying instances."""
|
23 |
|
24 |
import itertools |
25 |
import logging |
26 |
import operator |
27 |
|
28 |
from ganeti import compat |
29 |
from ganeti import constants |
30 |
from ganeti import locking |
31 |
from ganeti import qlang |
32 |
from ganeti import query |
33 |
from ganeti.cmdlib.base import QueryBase, NoHooksLU |
34 |
from ganeti.cmdlib.common import ShareAll, GetWantedInstances, \ |
35 |
CheckInstanceNodeGroups, CheckInstancesNodeGroups, AnnotateDiskParams |
36 |
from ganeti.cmdlib.instance_operation import GetInstanceConsole |
37 |
from ganeti.cmdlib.instance_utils import NICListToTuple |
38 |
|
39 |
import ganeti.masterd.instance |
40 |
|
41 |
|
42 |
class InstanceQuery(QueryBase): |
43 |
FIELDS = query.INSTANCE_FIELDS |
44 |
|
45 |
def ExpandNames(self, lu): |
46 |
lu.needed_locks = {} |
47 |
lu.share_locks = ShareAll() |
48 |
|
49 |
if self.names: |
50 |
self.wanted = GetWantedInstances(lu, self.names) |
51 |
else:
|
52 |
self.wanted = locking.ALL_SET
|
53 |
|
54 |
self.do_locking = (self.use_locking and |
55 |
query.IQ_LIVE in self.requested_data) |
56 |
if self.do_locking: |
57 |
lu.needed_locks[locking.LEVEL_INSTANCE] = self.wanted
|
58 |
lu.needed_locks[locking.LEVEL_NODEGROUP] = [] |
59 |
lu.needed_locks[locking.LEVEL_NODE] = [] |
60 |
lu.needed_locks[locking.LEVEL_NETWORK] = [] |
61 |
lu.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE |
62 |
|
63 |
self.do_grouplocks = (self.do_locking and |
64 |
query.IQ_NODES in self.requested_data) |
65 |
|
66 |
def DeclareLocks(self, lu, level): |
67 |
if self.do_locking: |
68 |
if level == locking.LEVEL_NODEGROUP and self.do_grouplocks: |
69 |
assert not lu.needed_locks[locking.LEVEL_NODEGROUP] |
70 |
|
71 |
# Lock all groups used by instances optimistically; this requires going
|
72 |
# via the node before it's locked, requiring verification later on
|
73 |
lu.needed_locks[locking.LEVEL_NODEGROUP] = \ |
74 |
set(group_uuid
|
75 |
for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE) |
76 |
for group_uuid in lu.cfg.GetInstanceNodeGroups(instance_name)) |
77 |
elif level == locking.LEVEL_NODE:
|
78 |
lu._LockInstancesNodes() # pylint: disable=W0212
|
79 |
|
80 |
elif level == locking.LEVEL_NETWORK:
|
81 |
lu.needed_locks[locking.LEVEL_NETWORK] = \ |
82 |
frozenset(net_uuid
|
83 |
for instance_name in lu.owned_locks(locking.LEVEL_INSTANCE) |
84 |
for net_uuid in lu.cfg.GetInstanceNetworks(instance_name)) |
85 |
|
86 |
@staticmethod
|
87 |
def _CheckGroupLocks(lu): |
88 |
owned_instances = frozenset(lu.owned_locks(locking.LEVEL_INSTANCE))
|
89 |
owned_groups = frozenset(lu.owned_locks(locking.LEVEL_NODEGROUP))
|
90 |
|
91 |
# Check if node groups for locked instances are still correct
|
92 |
for instance_name in owned_instances: |
93 |
CheckInstanceNodeGroups(lu.cfg, instance_name, owned_groups) |
94 |
|
95 |
def _GetQueryData(self, lu): |
96 |
"""Computes the list of instances and their attributes.
|
97 |
|
98 |
"""
|
99 |
if self.do_grouplocks: |
100 |
self._CheckGroupLocks(lu)
|
101 |
|
102 |
cluster = lu.cfg.GetClusterInfo() |
103 |
all_info = lu.cfg.GetAllInstancesInfo() |
104 |
|
105 |
instance_names = self._GetNames(lu, all_info.keys(), locking.LEVEL_INSTANCE)
|
106 |
|
107 |
instance_list = [all_info[name] for name in instance_names] |
108 |
nodes = frozenset(itertools.chain(*(inst.all_nodes
|
109 |
for inst in instance_list))) |
110 |
hv_list = list(set([inst.hypervisor for inst in instance_list])) |
111 |
bad_nodes = [] |
112 |
offline_nodes = [] |
113 |
wrongnode_inst = set()
|
114 |
|
115 |
# Gather data as requested
|
116 |
if self.requested_data & set([query.IQ_LIVE, query.IQ_CONSOLE]): |
117 |
live_data = {} |
118 |
node_data = lu.rpc.call_all_instances_info(nodes, hv_list) |
119 |
for name in nodes: |
120 |
result = node_data[name] |
121 |
if result.offline:
|
122 |
# offline nodes will be in both lists
|
123 |
assert result.fail_msg
|
124 |
offline_nodes.append(name) |
125 |
if result.fail_msg:
|
126 |
bad_nodes.append(name) |
127 |
elif result.payload:
|
128 |
for inst in result.payload: |
129 |
if inst in all_info: |
130 |
if all_info[inst].primary_node == name:
|
131 |
live_data.update(result.payload) |
132 |
else:
|
133 |
wrongnode_inst.add(inst) |
134 |
else:
|
135 |
# orphan instance; we don't list it here as we don't
|
136 |
# handle this case yet in the output of instance listing
|
137 |
logging.warning("Orphan instance '%s' found on node %s",
|
138 |
inst, name) |
139 |
# else no instance is alive
|
140 |
else:
|
141 |
live_data = {} |
142 |
|
143 |
if query.IQ_DISKUSAGE in self.requested_data: |
144 |
gmi = ganeti.masterd.instance |
145 |
disk_usage = dict((inst.name,
|
146 |
gmi.ComputeDiskSize(inst.disk_template, |
147 |
[{constants.IDISK_SIZE: disk.size} |
148 |
for disk in inst.disks])) |
149 |
for inst in instance_list) |
150 |
else:
|
151 |
disk_usage = None
|
152 |
|
153 |
if query.IQ_CONSOLE in self.requested_data: |
154 |
consinfo = {} |
155 |
for inst in instance_list: |
156 |
if inst.name in live_data: |
157 |
# Instance is running
|
158 |
consinfo[inst.name] = GetInstanceConsole(cluster, inst) |
159 |
else:
|
160 |
consinfo[inst.name] = None
|
161 |
assert set(consinfo.keys()) == set(instance_names) |
162 |
else:
|
163 |
consinfo = None
|
164 |
|
165 |
if query.IQ_NODES in self.requested_data: |
166 |
node_names = set(itertools.chain(*map(operator.attrgetter("all_nodes"), |
167 |
instance_list))) |
168 |
nodes = dict(lu.cfg.GetMultiNodeInfo(node_names))
|
169 |
groups = dict((uuid, lu.cfg.GetNodeGroup(uuid))
|
170 |
for uuid in set(map(operator.attrgetter("group"), |
171 |
nodes.values()))) |
172 |
else:
|
173 |
nodes = None
|
174 |
groups = None
|
175 |
|
176 |
if query.IQ_NETWORKS in self.requested_data: |
177 |
net_uuids = itertools.chain(*(lu.cfg.GetInstanceNetworks(i.name) |
178 |
for i in instance_list)) |
179 |
networks = dict((uuid, lu.cfg.GetNetwork(uuid)) for uuid in net_uuids) |
180 |
else:
|
181 |
networks = None
|
182 |
|
183 |
return query.InstanceQueryData(instance_list, lu.cfg.GetClusterInfo(),
|
184 |
disk_usage, offline_nodes, bad_nodes, |
185 |
live_data, wrongnode_inst, consinfo, |
186 |
nodes, groups, networks) |
187 |
|
188 |
|
189 |
class LUInstanceQuery(NoHooksLU): |
190 |
"""Logical unit for querying instances.
|
191 |
|
192 |
"""
|
193 |
# pylint: disable=W0142
|
194 |
REQ_BGL = False
|
195 |
|
196 |
def CheckArguments(self): |
197 |
self.iq = InstanceQuery(qlang.MakeSimpleFilter("name", self.op.names), |
198 |
self.op.output_fields, self.op.use_locking) |
199 |
|
200 |
def ExpandNames(self): |
201 |
self.iq.ExpandNames(self) |
202 |
|
203 |
def DeclareLocks(self, level): |
204 |
self.iq.DeclareLocks(self, level) |
205 |
|
206 |
def Exec(self, feedback_fn): |
207 |
return self.iq.OldStyleQuery(self) |
208 |
|
209 |
|
210 |
class LUInstanceQueryData(NoHooksLU): |
211 |
"""Query runtime instance data.
|
212 |
|
213 |
"""
|
214 |
REQ_BGL = False
|
215 |
|
216 |
def ExpandNames(self): |
217 |
self.needed_locks = {}
|
218 |
|
219 |
# Use locking if requested or when non-static information is wanted
|
220 |
if not (self.op.static or self.op.use_locking): |
221 |
self.LogWarning("Non-static data requested, locks need to be acquired") |
222 |
self.op.use_locking = True |
223 |
|
224 |
if self.op.instances or not self.op.use_locking: |
225 |
# Expand instance names right here
|
226 |
self.wanted_names = GetWantedInstances(self, self.op.instances) |
227 |
else:
|
228 |
# Will use acquired locks
|
229 |
self.wanted_names = None |
230 |
|
231 |
if self.op.use_locking: |
232 |
self.share_locks = ShareAll()
|
233 |
|
234 |
if self.wanted_names is None: |
235 |
self.needed_locks[locking.LEVEL_INSTANCE] = locking.ALL_SET
|
236 |
else:
|
237 |
self.needed_locks[locking.LEVEL_INSTANCE] = self.wanted_names |
238 |
|
239 |
self.needed_locks[locking.LEVEL_NODEGROUP] = []
|
240 |
self.needed_locks[locking.LEVEL_NODE] = []
|
241 |
self.needed_locks[locking.LEVEL_NETWORK] = []
|
242 |
self.recalculate_locks[locking.LEVEL_NODE] = constants.LOCKS_REPLACE
|
243 |
|
244 |
def DeclareLocks(self, level): |
245 |
if self.op.use_locking: |
246 |
owned_instances = self.owned_locks(locking.LEVEL_INSTANCE)
|
247 |
if level == locking.LEVEL_NODEGROUP:
|
248 |
|
249 |
# Lock all groups used by instances optimistically; this requires going
|
250 |
# via the node before it's locked, requiring verification later on
|
251 |
self.needed_locks[locking.LEVEL_NODEGROUP] = \
|
252 |
frozenset(group_uuid
|
253 |
for instance_name in owned_instances |
254 |
for group_uuid in |
255 |
self.cfg.GetInstanceNodeGroups(instance_name))
|
256 |
|
257 |
elif level == locking.LEVEL_NODE:
|
258 |
self._LockInstancesNodes()
|
259 |
|
260 |
elif level == locking.LEVEL_NETWORK:
|
261 |
self.needed_locks[locking.LEVEL_NETWORK] = \
|
262 |
frozenset(net_uuid
|
263 |
for instance_name in owned_instances |
264 |
for net_uuid in |
265 |
self.cfg.GetInstanceNetworks(instance_name))
|
266 |
|
267 |
def CheckPrereq(self): |
268 |
"""Check prerequisites.
|
269 |
|
270 |
This only checks the optional instance list against the existing names.
|
271 |
|
272 |
"""
|
273 |
owned_instances = frozenset(self.owned_locks(locking.LEVEL_INSTANCE)) |
274 |
owned_groups = frozenset(self.owned_locks(locking.LEVEL_NODEGROUP)) |
275 |
owned_nodes = frozenset(self.owned_locks(locking.LEVEL_NODE)) |
276 |
owned_networks = frozenset(self.owned_locks(locking.LEVEL_NETWORK)) |
277 |
|
278 |
if self.wanted_names is None: |
279 |
assert self.op.use_locking, "Locking was not used" |
280 |
self.wanted_names = owned_instances
|
281 |
|
282 |
instances = dict(self.cfg.GetMultiInstanceInfo(self.wanted_names)) |
283 |
|
284 |
if self.op.use_locking: |
285 |
CheckInstancesNodeGroups(self.cfg, instances, owned_groups, owned_nodes,
|
286 |
None)
|
287 |
else:
|
288 |
assert not (owned_instances or owned_groups or |
289 |
owned_nodes or owned_networks)
|
290 |
|
291 |
self.wanted_instances = instances.values()
|
292 |
|
293 |
def _ComputeBlockdevStatus(self, node, instance, dev): |
294 |
"""Returns the status of a block device
|
295 |
|
296 |
"""
|
297 |
if self.op.static or not node: |
298 |
return None |
299 |
|
300 |
self.cfg.SetDiskID(dev, node)
|
301 |
|
302 |
result = self.rpc.call_blockdev_find(node, dev)
|
303 |
if result.offline:
|
304 |
return None |
305 |
|
306 |
result.Raise("Can't compute disk status for %s" % instance.name)
|
307 |
|
308 |
status = result.payload |
309 |
if status is None: |
310 |
return None |
311 |
|
312 |
return (status.dev_path, status.major, status.minor,
|
313 |
status.sync_percent, status.estimated_time, |
314 |
status.is_degraded, status.ldisk_status) |
315 |
|
316 |
def _ComputeDiskStatus(self, instance, snode, dev): |
317 |
"""Compute block device status.
|
318 |
|
319 |
"""
|
320 |
(anno_dev,) = AnnotateDiskParams(instance, [dev], self.cfg)
|
321 |
|
322 |
return self._ComputeDiskStatusInner(instance, snode, anno_dev) |
323 |
|
324 |
def _ComputeDiskStatusInner(self, instance, snode, dev): |
325 |
"""Compute block device status.
|
326 |
|
327 |
@attention: The device has to be annotated already.
|
328 |
|
329 |
"""
|
330 |
if dev.dev_type in constants.LDS_DRBD: |
331 |
# we change the snode then (otherwise we use the one passed in)
|
332 |
if dev.logical_id[0] == instance.primary_node: |
333 |
snode = dev.logical_id[1]
|
334 |
else:
|
335 |
snode = dev.logical_id[0]
|
336 |
|
337 |
dev_pstatus = self._ComputeBlockdevStatus(instance.primary_node,
|
338 |
instance, dev) |
339 |
dev_sstatus = self._ComputeBlockdevStatus(snode, instance, dev)
|
340 |
|
341 |
if dev.children:
|
342 |
dev_children = map(compat.partial(self._ComputeDiskStatusInner, |
343 |
instance, snode), |
344 |
dev.children) |
345 |
else:
|
346 |
dev_children = [] |
347 |
|
348 |
return {
|
349 |
"iv_name": dev.iv_name,
|
350 |
"dev_type": dev.dev_type,
|
351 |
"logical_id": dev.logical_id,
|
352 |
"physical_id": dev.physical_id,
|
353 |
"pstatus": dev_pstatus,
|
354 |
"sstatus": dev_sstatus,
|
355 |
"children": dev_children,
|
356 |
"mode": dev.mode,
|
357 |
"size": dev.size,
|
358 |
"name": dev.name,
|
359 |
"uuid": dev.uuid,
|
360 |
} |
361 |
|
362 |
def Exec(self, feedback_fn): |
363 |
"""Gather and return data"""
|
364 |
result = {} |
365 |
|
366 |
cluster = self.cfg.GetClusterInfo()
|
367 |
|
368 |
node_names = itertools.chain(*(i.all_nodes for i in self.wanted_instances)) |
369 |
nodes = dict(self.cfg.GetMultiNodeInfo(node_names)) |
370 |
|
371 |
groups = dict(self.cfg.GetMultiNodeGroupInfo(node.group |
372 |
for node in nodes.values())) |
373 |
|
374 |
group2name_fn = lambda uuid: groups[uuid].name
|
375 |
for instance in self.wanted_instances: |
376 |
pnode = nodes[instance.primary_node] |
377 |
|
378 |
if self.op.static or pnode.offline: |
379 |
remote_state = None
|
380 |
if pnode.offline:
|
381 |
self.LogWarning("Primary node %s is marked offline, returning static" |
382 |
" information only for instance %s" %
|
383 |
(pnode.name, instance.name)) |
384 |
else:
|
385 |
remote_info = self.rpc.call_instance_info(instance.primary_node,
|
386 |
instance.name, |
387 |
instance.hypervisor) |
388 |
remote_info.Raise("Error checking node %s" % instance.primary_node)
|
389 |
remote_info = remote_info.payload |
390 |
if remote_info and "state" in remote_info: |
391 |
remote_state = "up"
|
392 |
else:
|
393 |
if instance.admin_state == constants.ADMINST_UP:
|
394 |
remote_state = "down"
|
395 |
else:
|
396 |
remote_state = instance.admin_state |
397 |
|
398 |
disks = map(compat.partial(self._ComputeDiskStatus, instance, None), |
399 |
instance.disks) |
400 |
|
401 |
snodes_group_uuids = [nodes[snode_name].group |
402 |
for snode_name in instance.secondary_nodes] |
403 |
|
404 |
result[instance.name] = { |
405 |
"name": instance.name,
|
406 |
"config_state": instance.admin_state,
|
407 |
"run_state": remote_state,
|
408 |
"pnode": instance.primary_node,
|
409 |
"pnode_group_uuid": pnode.group,
|
410 |
"pnode_group_name": group2name_fn(pnode.group),
|
411 |
"snodes": instance.secondary_nodes,
|
412 |
"snodes_group_uuids": snodes_group_uuids,
|
413 |
"snodes_group_names": map(group2name_fn, snodes_group_uuids), |
414 |
"os": instance.os,
|
415 |
# this happens to be the same format used for hooks
|
416 |
"nics": NICListToTuple(self, instance.nics), |
417 |
"disk_template": instance.disk_template,
|
418 |
"disks": disks,
|
419 |
"hypervisor": instance.hypervisor,
|
420 |
"network_port": instance.network_port,
|
421 |
"hv_instance": instance.hvparams,
|
422 |
"hv_actual": cluster.FillHV(instance, skip_globals=True), |
423 |
"be_instance": instance.beparams,
|
424 |
"be_actual": cluster.FillBE(instance),
|
425 |
"os_instance": instance.osparams,
|
426 |
"os_actual": cluster.SimpleFillOS(instance.os, instance.osparams),
|
427 |
"serial_no": instance.serial_no,
|
428 |
"mtime": instance.mtime,
|
429 |
"ctime": instance.ctime,
|
430 |
"uuid": instance.uuid,
|
431 |
} |
432 |
|
433 |
return result
|