cmdlib: Extract instance operation functionality
[ganeti-local] / 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)