Adjust all callers of RPC 'call_node_info'
[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(
137             self, instance.primary_node, "starting instance %s" % instance.name,
138             bep[constants.BE_MINMEM], instance.hypervisor,
139             self.cfg.GetClusterInfo().hvparams[instance.hypervisor])
140
141   def Exec(self, feedback_fn):
142     """Start the instance.
143
144     """
145     instance = self.instance
146     force = self.op.force
147     reason = self.op.reason
148
149     if not self.op.no_remember:
150       self.cfg.MarkInstanceUp(instance.name)
151
152     if self.primary_offline:
153       assert self.op.ignore_offline_nodes
154       self.LogInfo("Primary node offline, marked instance as started")
155     else:
156       node_current = instance.primary_node
157
158       StartInstanceDisks(self, instance, force)
159
160       result = \
161         self.rpc.call_instance_start(node_current,
162                                      (instance, self.op.hvparams,
163                                       self.op.beparams),
164                                      self.op.startup_paused, reason)
165       msg = result.fail_msg
166       if msg:
167         ShutdownInstanceDisks(self, instance)
168         raise errors.OpExecError("Could not start instance: %s" % msg)
169
170
171 class LUInstanceShutdown(LogicalUnit):
172   """Shutdown an instance.
173
174   """
175   HPATH = "instance-stop"
176   HTYPE = constants.HTYPE_INSTANCE
177   REQ_BGL = False
178
179   def ExpandNames(self):
180     self._ExpandAndLockInstance()
181
182   def BuildHooksEnv(self):
183     """Build hooks env.
184
185     This runs on master, primary and secondary nodes of the instance.
186
187     """
188     env = BuildInstanceHookEnvByObject(self, self.instance)
189     env["TIMEOUT"] = self.op.timeout
190     return env
191
192   def BuildHooksNodes(self):
193     """Build hooks nodes.
194
195     """
196     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
197     return (nl, nl)
198
199   def CheckPrereq(self):
200     """Check prerequisites.
201
202     This checks that the instance is in the cluster.
203
204     """
205     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
206     assert self.instance is not None, \
207       "Cannot retrieve locked instance %s" % self.op.instance_name
208
209     if not self.op.force:
210       CheckInstanceState(self, self.instance, INSTANCE_ONLINE)
211     else:
212       self.LogWarning("Ignoring offline instance check")
213
214     self.primary_offline = \
215       self.cfg.GetNodeInfo(self.instance.primary_node).offline
216
217     if self.primary_offline and self.op.ignore_offline_nodes:
218       self.LogWarning("Ignoring offline primary node")
219     else:
220       CheckNodeOnline(self, self.instance.primary_node)
221
222   def Exec(self, feedback_fn):
223     """Shutdown the instance.
224
225     """
226     instance = self.instance
227     node_current = instance.primary_node
228     timeout = self.op.timeout
229     reason = self.op.reason
230
231     # If the instance is offline we shouldn't mark it as down, as that
232     # resets the offline flag.
233     if not self.op.no_remember and instance.admin_state in INSTANCE_ONLINE:
234       self.cfg.MarkInstanceDown(instance.name)
235
236     if self.primary_offline:
237       assert self.op.ignore_offline_nodes
238       self.LogInfo("Primary node offline, marked instance as stopped")
239     else:
240       result = self.rpc.call_instance_shutdown(node_current, instance, timeout,
241                                                reason)
242       msg = result.fail_msg
243       if msg:
244         self.LogWarning("Could not shutdown instance: %s", msg)
245
246       ShutdownInstanceDisks(self, instance)
247
248
249 class LUInstanceReinstall(LogicalUnit):
250   """Reinstall an instance.
251
252   """
253   HPATH = "instance-reinstall"
254   HTYPE = constants.HTYPE_INSTANCE
255   REQ_BGL = False
256
257   def ExpandNames(self):
258     self._ExpandAndLockInstance()
259
260   def BuildHooksEnv(self):
261     """Build hooks env.
262
263     This runs on master, primary and secondary nodes of the instance.
264
265     """
266     return BuildInstanceHookEnvByObject(self, self.instance)
267
268   def BuildHooksNodes(self):
269     """Build hooks nodes.
270
271     """
272     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
273     return (nl, nl)
274
275   def CheckPrereq(self):
276     """Check prerequisites.
277
278     This checks that the instance is in the cluster and is not running.
279
280     """
281     instance = self.cfg.GetInstanceInfo(self.op.instance_name)
282     assert instance is not None, \
283       "Cannot retrieve locked instance %s" % self.op.instance_name
284     CheckNodeOnline(self, instance.primary_node, "Instance primary node"
285                     " offline, cannot reinstall")
286
287     if instance.disk_template == constants.DT_DISKLESS:
288       raise errors.OpPrereqError("Instance '%s' has no disks" %
289                                  self.op.instance_name,
290                                  errors.ECODE_INVAL)
291     CheckInstanceState(self, instance, INSTANCE_DOWN, msg="cannot reinstall")
292
293     if self.op.os_type is not None:
294       # OS verification
295       pnode = ExpandNodeName(self.cfg, instance.primary_node)
296       CheckNodeHasOS(self, pnode, self.op.os_type, self.op.force_variant)
297       instance_os = self.op.os_type
298     else:
299       instance_os = instance.os
300
301     nodelist = list(instance.all_nodes)
302
303     if self.op.osparams:
304       i_osdict = GetUpdatedParams(instance.osparams, self.op.osparams)
305       CheckOSParams(self, True, nodelist, instance_os, i_osdict)
306       self.os_inst = i_osdict # the new dict (without defaults)
307     else:
308       self.os_inst = None
309
310     self.instance = instance
311
312   def Exec(self, feedback_fn):
313     """Reinstall the instance.
314
315     """
316     inst = self.instance
317
318     if self.op.os_type is not None:
319       feedback_fn("Changing OS to '%s'..." % self.op.os_type)
320       inst.os = self.op.os_type
321       # Write to configuration
322       self.cfg.Update(inst, feedback_fn)
323
324     StartInstanceDisks(self, inst, None)
325     try:
326       feedback_fn("Running the instance OS create scripts...")
327       # FIXME: pass debug option from opcode to backend
328       result = self.rpc.call_instance_os_add(inst.primary_node,
329                                              (inst, self.os_inst), True,
330                                              self.op.debug_level)
331       result.Raise("Could not install OS for instance %s on node %s" %
332                    (inst.name, inst.primary_node))
333     finally:
334       ShutdownInstanceDisks(self, inst)
335
336
337 class LUInstanceReboot(LogicalUnit):
338   """Reboot an instance.
339
340   """
341   HPATH = "instance-reboot"
342   HTYPE = constants.HTYPE_INSTANCE
343   REQ_BGL = False
344
345   def ExpandNames(self):
346     self._ExpandAndLockInstance()
347
348   def BuildHooksEnv(self):
349     """Build hooks env.
350
351     This runs on master, primary and secondary nodes of the instance.
352
353     """
354     env = {
355       "IGNORE_SECONDARIES": self.op.ignore_secondaries,
356       "REBOOT_TYPE": self.op.reboot_type,
357       "SHUTDOWN_TIMEOUT": self.op.shutdown_timeout,
358       }
359
360     env.update(BuildInstanceHookEnvByObject(self, self.instance))
361
362     return env
363
364   def BuildHooksNodes(self):
365     """Build hooks nodes.
366
367     """
368     nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
369     return (nl, nl)
370
371   def CheckPrereq(self):
372     """Check prerequisites.
373
374     This checks that the instance is in the cluster.
375
376     """
377     self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
378     assert self.instance is not None, \
379       "Cannot retrieve locked instance %s" % self.op.instance_name
380     CheckInstanceState(self, instance, INSTANCE_ONLINE)
381     CheckNodeOnline(self, instance.primary_node)
382
383     # check bridges existence
384     CheckInstanceBridgesExist(self, instance)
385
386   def Exec(self, feedback_fn):
387     """Reboot the instance.
388
389     """
390     instance = self.instance
391     ignore_secondaries = self.op.ignore_secondaries
392     reboot_type = self.op.reboot_type
393     reason = self.op.reason
394
395     remote_info = self.rpc.call_instance_info(instance.primary_node,
396                                               instance.name,
397                                               instance.hypervisor)
398     remote_info.Raise("Error checking node %s" % instance.primary_node)
399     instance_running = bool(remote_info.payload)
400
401     node_current = instance.primary_node
402
403     if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
404                                             constants.INSTANCE_REBOOT_HARD]:
405       for disk in instance.disks:
406         self.cfg.SetDiskID(disk, node_current)
407       result = self.rpc.call_instance_reboot(node_current, instance,
408                                              reboot_type,
409                                              self.op.shutdown_timeout, reason)
410       result.Raise("Could not reboot instance")
411     else:
412       if instance_running:
413         result = self.rpc.call_instance_shutdown(node_current, instance,
414                                                  self.op.shutdown_timeout,
415                                                  reason)
416         result.Raise("Could not shutdown instance for full reboot")
417         ShutdownInstanceDisks(self, instance)
418       else:
419         self.LogInfo("Instance %s was already stopped, starting now",
420                      instance.name)
421       StartInstanceDisks(self, instance, ignore_secondaries)
422       result = self.rpc.call_instance_start(node_current,
423                                             (instance, None, None), False,
424                                             reason)
425       msg = result.fail_msg
426       if msg:
427         ShutdownInstanceDisks(self, instance)
428         raise errors.OpExecError("Could not start instance for"
429                                  " full reboot: %s" % msg)
430
431     self.cfg.MarkInstanceUp(instance.name)
432
433
434 def GetInstanceConsole(cluster, instance):
435   """Returns console information for an instance.
436
437   @type cluster: L{objects.Cluster}
438   @type instance: L{objects.Instance}
439   @rtype: dict
440
441   """
442   hyper = hypervisor.GetHypervisorClass(instance.hypervisor)
443   # beparams and hvparams are passed separately, to avoid editing the
444   # instance and then saving the defaults in the instance itself.
445   hvparams = cluster.FillHV(instance)
446   beparams = cluster.FillBE(instance)
447   console = hyper.GetInstanceConsole(instance, hvparams, beparams)
448
449   assert console.instance == instance.name
450   assert console.Validate()
451
452   return console.ToDict()
453
454
455 class LUInstanceConsole(NoHooksLU):
456   """Connect to an instance's console.
457
458   This is somewhat special in that it returns the command line that
459   you need to run on the master node in order to connect to the
460   console.
461
462   """
463   REQ_BGL = False
464
465   def ExpandNames(self):
466     self.share_locks = ShareAll()
467     self._ExpandAndLockInstance()
468
469   def CheckPrereq(self):
470     """Check prerequisites.
471
472     This checks that the instance is in the cluster.
473
474     """
475     self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
476     assert self.instance is not None, \
477       "Cannot retrieve locked instance %s" % self.op.instance_name
478     CheckNodeOnline(self, self.instance.primary_node)
479
480   def Exec(self, feedback_fn):
481     """Connect to the console of an instance
482
483     """
484     instance = self.instance
485     node = instance.primary_node
486
487     cluster_hvparams = self.cfg.GetClusterInfo().hvparams
488     node_insts = self.rpc.call_instance_list([node],
489                                              [instance.hypervisor],
490                                              cluster_hvparams)[node]
491     node_insts.Raise("Can't get node information from %s" % node)
492
493     if instance.name not in node_insts.payload:
494       if instance.admin_state == constants.ADMINST_UP:
495         state = constants.INSTST_ERRORDOWN
496       elif instance.admin_state == constants.ADMINST_DOWN:
497         state = constants.INSTST_ADMINDOWN
498       else:
499         state = constants.INSTST_ADMINOFFLINE
500       raise errors.OpExecError("Instance %s is not running (state %s)" %
501                                (instance.name, state))
502
503     logging.debug("Connecting to console of %s on %s", instance.name, node)
504
505     return GetInstanceConsole(self.cfg.GetClusterInfo(), instance)