Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance_operation.py @ 8ef418bb

History | View | Annotate | Download (15.8 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 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
    cluster = self.cfg.GetClusterInfo()
102
    # extra hvparams
103
    if self.op.hvparams:
104
      # check hypervisor parameter syntax (locally)
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(
131
          instance.primary_node, instance.name, instance.hypervisor,
132
          cluster.hvparams[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
    cluster = self.cfg.GetClusterInfo()
396
    remote_info = self.rpc.call_instance_info(
397
        instance.primary_node, instance.name, instance.hypervisor,
398
        cluster.hvparams[instance.hypervisor])
399
    remote_info.Raise("Error checking node %s" % instance.primary_node)
400
    instance_running = bool(remote_info.payload)
401

    
402
    node_current = instance.primary_node
403

    
404
    if instance_running and reboot_type in [constants.INSTANCE_REBOOT_SOFT,
405
                                            constants.INSTANCE_REBOOT_HARD]:
406
      for disk in instance.disks:
407
        self.cfg.SetDiskID(disk, node_current)
408
      result = self.rpc.call_instance_reboot(node_current, instance,
409
                                             reboot_type,
410
                                             self.op.shutdown_timeout, reason)
411
      result.Raise("Could not reboot instance")
412
    else:
413
      if instance_running:
414
        result = self.rpc.call_instance_shutdown(node_current, instance,
415
                                                 self.op.shutdown_timeout,
416
                                                 reason)
417
        result.Raise("Could not shutdown instance for full reboot")
418
        ShutdownInstanceDisks(self, instance)
419
      else:
420
        self.LogInfo("Instance %s was already stopped, starting now",
421
                     instance.name)
422
      StartInstanceDisks(self, instance, ignore_secondaries)
423
      result = self.rpc.call_instance_start(node_current,
424
                                            (instance, None, None), False,
425
                                            reason)
426
      msg = result.fail_msg
427
      if msg:
428
        ShutdownInstanceDisks(self, instance)
429
        raise errors.OpExecError("Could not start instance for"
430
                                 " full reboot: %s" % msg)
431

    
432
    self.cfg.MarkInstanceUp(instance.name)
433

    
434

    
435
def GetInstanceConsole(cluster, instance):
436
  """Returns console information for an instance.
437

438
  @type cluster: L{objects.Cluster}
439
  @type instance: L{objects.Instance}
440
  @rtype: dict
441

442
  """
443
  hyper = hypervisor.GetHypervisor(instance.hypervisor)
444
  # beparams and hvparams are passed separately, to avoid editing the
445
  # instance and then saving the defaults in the instance itself.
446
  hvparams = cluster.FillHV(instance)
447
  beparams = cluster.FillBE(instance)
448
  console = hyper.GetInstanceConsole(instance, hvparams, beparams)
449

    
450
  assert console.instance == instance.name
451
  assert console.Validate()
452

    
453
  return console.ToDict()
454

    
455

    
456
class LUInstanceConsole(NoHooksLU):
457
  """Connect to an instance's console.
458

459
  This is somewhat special in that it returns the command line that
460
  you need to run on the master node in order to connect to the
461
  console.
462

463
  """
464
  REQ_BGL = False
465

    
466
  def ExpandNames(self):
467
    self.share_locks = ShareAll()
468
    self._ExpandAndLockInstance()
469

    
470
  def CheckPrereq(self):
471
    """Check prerequisites.
472

473
    This checks that the instance is in the cluster.
474

475
    """
476
    self.instance = self.cfg.GetInstanceInfo(self.op.instance_name)
477
    assert self.instance is not None, \
478
      "Cannot retrieve locked instance %s" % self.op.instance_name
479
    CheckNodeOnline(self, self.instance.primary_node)
480

    
481
  def Exec(self, feedback_fn):
482
    """Connect to the console of an instance
483

484
    """
485
    instance = self.instance
486
    node = instance.primary_node
487

    
488
    cluster_hvparams = self.cfg.GetClusterInfo().hvparams
489
    node_insts = self.rpc.call_instance_list([node],
490
                                             [instance.hypervisor],
491
                                             cluster_hvparams)[node]
492
    node_insts.Raise("Can't get node information from %s" % node)
493

    
494
    if instance.name not in node_insts.payload:
495
      if instance.admin_state == constants.ADMINST_UP:
496
        state = constants.INSTST_ERRORDOWN
497
      elif instance.admin_state == constants.ADMINST_DOWN:
498
        state = constants.INSTST_ADMINDOWN
499
      else:
500
        state = constants.INSTST_ADMINOFFLINE
501
      raise errors.OpExecError("Instance %s is not running (state %s)" %
502
                               (instance.name, state))
503

    
504
    logging.debug("Connecting to console of %s on %s", instance.name, node)
505

    
506
    return GetInstanceConsole(self.cfg.GetClusterInfo(), instance)