4129bb0897d8f7ae15b96217ee20d973448cd632
[ganeti-github.git] / lib / cmdlib / instance_operation.py
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 dealing with instance operations (start/stop/...).
23
24 Those operations have in common that they affect the operating system in a
25 running instance directly.
26
27 """
28
29 import logging
30
31 from ganeti import constants
32 from ganeti import errors
33 from ganeti import hypervisor
34 from ganeti import locking
35 from ganeti import objects
36 from ganeti import utils
37 from ganeti.cmdlib.base import LogicalUnit, NoHooksLU
38 from ganeti.cmdlib.common import INSTANCE_ONLINE, INSTANCE_DOWN, \
39 _CheckHVParams, _CheckInstanceState, _CheckNodeOnline, _ExpandNodeName, \
40 _GetUpdatedParams, _CheckOSParams, _ShareAll
41 from ganeti.cmdlib.instance_storage import _StartInstanceDisks, \
42 _ShutdownInstanceDisks
43 from ganeti.cmdlib.instance_utils import _BuildInstanceHookEnvByObject, \
44 _CheckInstanceBridgesExist, _CheckNodeFreeMemory, _CheckNodeHasOS
45
46
47 class LUInstanceStartup(LogicalUnit):
48 """Starts an instance.
49
50 """
51 HPATH = "instance-start"
52 HTYPE = constants.HTYPE_INSTANCE
53 REQ_BGL = False
54
55 def CheckArguments(self):
56 # extra beparams
57 if self.op.beparams:
58 # fill the beparams dict
59 objects.UpgradeBeParams(self.op.beparams)
60 utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
61
62 def ExpandNames(self):
63 self._ExpandAndLockInstance()
64 self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
65
66 def DeclareLocks(self, level):
67 if level == locking.LEVEL_NODE_RES:
68 self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES)
69
70 def BuildHooksEnv(self):
71 """Build hooks env.
72
73 This runs on master, primary and secondary nodes of the instance.
74
75 """
76 env = {
77 "FORCE": self.op.force,
78 }
79
80 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
81
82 return env
83
84 def BuildHooksNodes(self):
85 """Build hooks nodes.
86
87 """
88 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
89 return (nl, nl)
90
91 def CheckPrereq(self):
92 """Check prerequisites.
93
94 This checks that the instance is in the cluster.
95
96 """
97 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
98 assert self.instance is not None, \
99 "Cannot retrieve locked instance %s" % self.op.instance_name
100
101 # extra hvparams
102 if self.op.hvparams:
103 # check hypervisor parameter syntax (locally)
104 cluster = self.cfg.GetClusterInfo()
105 utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
106 filled_hvp = cluster.FillHV(instance)
107 filled_hvp.update(self.op.hvparams)
108 hv_type = hypervisor.GetHypervisorClass(instance.hypervisor)
109 hv_type.CheckParameterSyntax(filled_hvp)
110 _CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
111
112 _CheckInstanceState(self, instance, INSTANCE_ONLINE)
113
114 self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
115
116 if self.primary_offline and self.op.ignore_offline_nodes:
117 self.LogWarning("Ignoring offline primary node")
118
119 if self.op.hvparams or self.op.beparams:
120 self.LogWarning("Overridden parameters are ignored")
121 else:
122 _CheckNodeOnline(self, instance.primary_node)
123
124 bep = self.cfg.GetClusterInfo().FillBE(instance)
125 bep.update(self.op.beparams)
126
127 # check bridges existence
128 _CheckInstanceBridgesExist(self, instance)
129
130 remote_info = self.rpc.call_instance_info(instance.primary_node,
131 instance.name,
132 instance.hypervisor)
133 remote_info.Raise("Error checking node %s" % instance.primary_node,
134 prereq=True, ecode=errors.ECODE_ENVIRON)
135 if not remote_info.payload: # not running already
136 _CheckNodeFreeMemory(self, instance.primary_node,
137 "starting instance %s" % instance.name,
138 bep[constants.BE_MINMEM], instance.hypervisor)
139
140 def Exec(self, feedback_fn):
141 """Start the instance.
142
143 """
144 instance = self.instance
145 force = self.op.force
146 reason = self.op.reason
147
148 if not self.op.no_remember:
149 self.cfg.MarkInstanceUp(instance.name)
150
151 if self.primary_offline:
152 assert self.op.ignore_offline_nodes
153 self.LogInfo("Primary node offline, marked instance as started")
154 else:
155 node_current = instance.primary_node
156
157 _StartInstanceDisks(self, instance, force)
158
159 result = \
160 self.rpc.call_instance_start(node_current,
161 (instance, self.op.hvparams,
162 self.op.beparams),
163 self.op.startup_paused, reason)
164 msg = result.fail_msg
165 if msg:
166 _ShutdownInstanceDisks(self, instance)
167 raise errors.OpExecError("Could not start instance: %s" % msg)
168
169
170 class LUInstanceShutdown(LogicalUnit):
171 """Shutdown an instance.
172
173 """
174 HPATH = "instance-stop"
175 HTYPE = constants.HTYPE_INSTANCE
176 REQ_BGL = False
177
178 def ExpandNames(self):
179 self._ExpandAndLockInstance()
180
181 def BuildHooksEnv(self):
182 """Build hooks env.
183
184 This runs on master, primary and secondary nodes of the instance.
185
186 """
187 env = _BuildInstanceHookEnvByObject(self, self.instance)
188 env["TIMEOUT"] = self.op.timeout
189 return env
190
191 def BuildHooksNodes(self):
192 """Build hooks nodes.
193
194 """
195 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
196 return (nl, nl)
197
198 def CheckPrereq(self):
199 """Check prerequisites.
200
201 This checks that the instance is in the cluster.
202
203 """
204 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
205 assert self.instance is not None, \
206 "Cannot retrieve locked instance %s" % self.op.instance_name
207
208 if not self.op.force:
209 _CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
210 else:
211 self.LogWarning("Ignoring offline instance check")
212
213 self.primary_offline = \
214 self.cfg.GetNodeInfo(self.instance.primary_node).offline
215
216 if self.primary_offline and self.op.ignore_offline_nodes:
217 self.LogWarning("Ignoring offline primary node")
218 else:
219 _CheckNodeOnline(self, self.instance.primary_node)
220
221 def Exec(self, feedback_fn):
222 """Shutdown the instance.
223
224 """
225 instance = self.instance
226 node_current = instance.primary_node
227 timeout = self.op.timeout
228 reason = self.op.reason
229
230 # If the instance is offline we shouldn't mark it as down, as that
231 # resets the offline flag.
232 if not self.op.no_remember and instance.admin_state in INSTANCE_ONLINE:
233 self.cfg.MarkInstanceDown(instance.name)
234
235 if self.primary_offline:
236 assert self.op.ignore_offline_nodes
237 self.LogInfo("Primary node offline, marked instance as stopped")
238 else:
239 result = self.rpc.call_instance_shutdown(node_current, instance, timeout,
240 reason)
241 msg = result.fail_msg
242 if msg:
243 self.LogWarning("Could not shutdown instance: %s", msg)
244
245 _ShutdownInstanceDisks(self, instance)
246
247
248 class LUInstanceReinstall(LogicalUnit):
249 """Reinstall an instance.
250
251 """
252 HPATH = "instance-reinstall"
253 HTYPE = constants.HTYPE_INSTANCE
254 REQ_BGL = False
255
256 def ExpandNames(self):
257 self._ExpandAndLockInstance()
258
259 def BuildHooksEnv(self):
260 """Build hooks env.
261
262 This runs on master, primary and secondary nodes of the instance.
263
264 """
265 return _BuildInstanceHookEnvByObject(self, self.instance)
266
267 def BuildHooksNodes(self):
268 """Build hooks nodes.
269
270 """
271 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
272 return (nl, nl)
273
274 def CheckPrereq(self):
275 """Check prerequisites.
276
277 This checks that the instance is in the cluster and is not running.
278
279 """
280 instance = self.cfg.GetInstanceInfo(self.op.instance_name)
281 assert instance is not None, \
282 "Cannot retrieve locked instance %s" % self.op.instance_name
283 _CheckNodeOnline(self, instance.primary_node, "Instance primary node"
284 " offline, cannot reinstall")
285
286 if instance.disk_template == constants.DT_DISKLESS:
287 raise errors.OpPrereqError("Instance '%s' has no disks" %
288 self.op.instance_name,
289 errors.ECODE_INVAL)
290 _CheckInstanceState(self, instance, INSTANCE_DOWN, msg="cannot reinstall")
291
292 if self.op.os_type is not None:
293 # OS verification
294 pnode = _ExpandNodeName(self.cfg, instance.primary_node)
295 _CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
296 instance_os = self.op.os_type
297 else:
298 instance_os = instance.os
299
300 nodelist = list(instance.all_nodes)
301
302 if self.op.osparams:
303 i_osdict = _GetUpdatedParams(instance.osparams, self.op.osparams)
304 _CheckOSParams(self, True, nodelist, instance_os, i_osdict)
305 self.os_inst = i_osdict # the new dict (without defaults)
306 else:
307 self.os_inst = None
308
309 self.instance = instance
310
311 def Exec(self, feedback_fn):
312 """Reinstall the instance.
313
314 """
315 inst = self.instance
316
317 if self.op.os_type is not None:
318 feedback_fn("Changing OS to '%s'..." % self.op.os_type)
319 inst.os = self.op.os_type
320 # Write to configuration
321 self.cfg.Update(inst, feedback_fn)
322
323 _StartInstanceDisks(self, inst, None)
324 try:
325 feedback_fn("Running the instance OS create scripts...")
326 # FIXME: pass debug option from opcode to backend
327 result = self.rpc.call_instance_os_add(inst.primary_node,
328 (inst, self.os_inst), True,
329 self.op.debug_level)
330 result.Raise("Could not install OS for instance %s on node %s" %
331 (inst.name, inst.primary_node))
332 finally:
333 _ShutdownInstanceDisks(self, inst)
334
335
336 class LUInstanceReboot(LogicalUnit):
337 """Reboot an instance.
338
339 """
340 HPATH = "instance-reboot"
341 HTYPE = constants.HTYPE_INSTANCE
342 REQ_BGL = False
343
344 def ExpandNames(self):
345 self._ExpandAndLockInstance()
346
347 def BuildHooksEnv(self):
348 """Build hooks env.
349
350 This runs on master, primary and secondary nodes of the instance.
351
352 """
353 env = {
354 "IGNORE_SECONDARIES": self.op.ignore_secondaries,
355 "REBOOT_TYPE": self.op.reboot_type,
356 "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
357 }
358
359 env.update(_BuildInstanceHookEnvByObject(self, self.instance))
360
361 return env
362
363 def BuildHooksNodes(self):
364 """Build hooks nodes.
365
366 """
367 nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
368 return (nl, nl)
369
370 def CheckPrereq(self):
371 """Check prerequisites.
372
373 This checks that the instance is in the cluster.
374
375 """
376 self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
377 assert self.instance is not None, \
378 "Cannot retrieve locked instance %s" % self.op.instance_name
379 _CheckInstanceState(self, instance, INSTANCE_ONLINE)
380 _CheckNodeOnline(self, instance.primary_node)
381
382 # check bridges existence
383 _CheckInstanceBridgesExist(self, instance)
384
385 def Exec(self, feedback_fn):
386 """Reboot the instance.
387
388 """
389 instance = self.instance
390 ignore_secondaries = self.op.ignore_secondaries
391 reboot_type = self.op.reboot_type
392 reason = self.op.reason
393
394 remote_info = self.rpc.call_instance_info(instance.primary_node,
395 instance.name,
396 instance.hypervisor)
397 remote_info.Raise("Error checking node %s" % instance.primary_node)
398 instance_running = bool(remote_info.payload)
399
400 node_current = instance.primary_node
401
402 if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
403 constants.INSTANCE_REBOOT_HARD]:
404 for disk in instance.disks:
405 self.cfg.SetDiskID(disk, node_current)
406 result = self.rpc.call_instance_reboot(node_current, instance,
407 reboot_type,
408 self.op.shutdown_timeout, reason)
409 result.Raise("Could not reboot instance")
410 else:
411 if instance_running:
412 result = self.rpc.call_instance_shutdown(node_current, instance,
413 self.op.shutdown_timeout,
414 reason)
415 result.Raise("Could not shutdown instance for full reboot")
416 _ShutdownInstanceDisks(self, instance)
417 else:
418 self.LogInfo("Instance %s was already stopped, starting now",
419 instance.name)
420 _StartInstanceDisks(self, instance, ignore_secondaries)
421 result = self.rpc.call_instance_start(node_current,
422 (instance, None, None), False,
423 reason)
424 msg = result.fail_msg
425 if msg:
426 _ShutdownInstanceDisks(self, instance)
427 raise errors.OpExecError("Could not start instance for"
428 " full reboot: %s" % msg)
429
430 self.cfg.MarkInstanceUp(instance.name)
431
432
433 def _GetInstanceConsole(cluster, instance):
434 """Returns console information for an instance.
435
436 @type cluster: L{objects.Cluster}
437 @type instance: L{objects.Instance}
438 @rtype: dict
439
440 """
441 hyper = hypervisor.GetHypervisorClass(instance.hypervisor)
442 # beparams and hvparams are passed separately, to avoid editing the
443 # instance and then saving the defaults in the instance itself.
444 hvparams = cluster.FillHV(instance)
445 beparams = cluster.FillBE(instance)
446 console = hyper.GetInstanceConsole(instance, hvparams, beparams)
447
448 assert console.instance == instance.name
449 assert console.Validate()
450
451 return console.ToDict()
452
453
454 class LUInstanceConsole(NoHooksLU):
455 """Connect to an instance's console.
456
457 This is somewhat special in that it returns the command line that
458 you need to run on the master node in order to connect to the
459 console.
460
461 """
462 REQ_BGL = False
463
464 def ExpandNames(self):
465 self.share_locks = _ShareAll()
466 self._ExpandAndLockInstance()
467
468 def CheckPrereq(self):
469 """Check prerequisites.
470
471 This checks that the instance is in the cluster.
472
473 """
474 self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
475 assert self.instance is not None, \
476 "Cannot retrieve locked instance %s" % self.op.instance_name
477 _CheckNodeOnline(self, self.instance.primary_node)
478
479 def Exec(self, feedback_fn):
480 """Connect to the console of an instance
481
482 """
483 instance = self.instance
484 node = instance.primary_node
485
486 node_insts = self.rpc.call_instance_list([node],
487 [instance.hypervisor])[node]
488 node_insts.Raise("Can't get node information from %s" % node)
489
490 if instance.name not in node_insts.payload:
491 if instance.admin_state == constants.ADMINST_UP:
492 state = constants.INSTST_ERRORDOWN
493 elif instance.admin_state == constants.ADMINST_DOWN:
494 state = constants.INSTST_ADMINDOWN
495 else:
496 state = constants.INSTST_ADMINOFFLINE
497 raise errors.OpExecError("Instance %s is not running (state %s)" %
498 (instance.name, state))
499
500 logging.debug("Connecting to console of %s on %s", instance.name, node)
501
502 return _GetInstanceConsole(self.cfg.GetClusterInfo(), instance)