Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib / instance_operation.py @ 06c2fb4a

History | View | Annotate | Download (17.7 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
from ganeti.cmdlib.instance import GetItemFromContainer
46

    
47

    
48
class LUInstanceStartup(LogicalUnit):
49
  """Starts an instance.
50

51
  """
52
  HPATH = "instance-start"
53
  HTYPE = constants.HTYPE_INSTANCE
54
  REQ_BGL = False
55

    
56
  def CheckArguments(self):
57
    # extra beparams
58
    if self.op.beparams:
59
      # fill the beparams dict
60
      objects.UpgradeBeParams(self.op.beparams)
61
      utils.ForceDictType(self.op.beparams, constants.BES_PARAMETER_TYPES)
62

    
63
  def ExpandNames(self):
64
    self._ExpandAndLockInstance()
65
    self.recalculate_locks[locking.LEVEL_NODE_RES] = constants.LOCKS_REPLACE
66

    
67
  def DeclareLocks(self, level):
68
    if level == locking.LEVEL_NODE_RES:
69
      self._LockInstancesNodes(primary_only=True, level=locking.LEVEL_NODE_RES)
70

    
71
  def BuildHooksEnv(self):
72
    """Build hooks env.
73

74
    This runs on master, primary and secondary nodes of the instance.
75

76
    """
77
    env = {
78
      "FORCE": self.op.force,
79
      }
80

    
81
    env.update(BuildInstanceHookEnvByObject(self, self.instance))
82

    
83
    return env
84

    
85
  def BuildHooksNodes(self):
86
    """Build hooks nodes.
87

88
    """
89
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
90
    return (nl, nl)
91

    
92
  def CheckPrereq(self):
93
    """Check prerequisites.
94

95
    This checks that the instance is in the cluster.
96

97
    """
98
    self.instance = instance = self.cfg.GetInstanceInfo(self.op.instance_name)
99
    assert self.instance is not None, \
100
      "Cannot retrieve locked instance %s" % self.op.instance_name
101

    
102
    # extra hvparams
103
    if self.op.hvparams:
104
      # check hypervisor parameter syntax (locally)
105
      cluster = self.cfg.GetClusterInfo()
106
      utils.ForceDictType(self.op.hvparams, constants.HVS_PARAMETER_TYPES)
107
      filled_hvp = cluster.FillHV(instance)
108
      filled_hvp.update(self.op.hvparams)
109
      hv_type = hypervisor.GetHypervisorClass(instance.hypervisor)
110
      hv_type.CheckParameterSyntax(filled_hvp)
111
      CheckHVParams(self, instance.all_nodes, instance.hypervisor, filled_hvp)
112

    
113
    CheckInstanceState(self, instance, INSTANCE_ONLINE)
114

    
115
    self.primary_offline = self.cfg.GetNodeInfo(instance.primary_node).offline
116

    
117
    if self.primary_offline and self.op.ignore_offline_nodes:
118
      self.LogWarning("Ignoring offline primary node")
119

    
120
      if self.op.hvparams or self.op.beparams:
121
        self.LogWarning("Overridden parameters are ignored")
122
    else:
123
      CheckNodeOnline(self, instance.primary_node)
124

    
125
      bep = self.cfg.GetClusterInfo().FillBE(instance)
126
      bep.update(self.op.beparams)
127

    
128
      # check bridges existence
129
      CheckInstanceBridgesExist(self, instance)
130

    
131
      remote_info = self.rpc.call_instance_info(instance.primary_node,
132
                                                instance.name,
133
                                                instance.hypervisor)
134
      remote_info.Raise("Error checking node %s" % instance.primary_node,
135
                        prereq=True, ecode=errors.ECODE_ENVIRON)
136
      if not remote_info.payload: # not running already
137
        CheckNodeFreeMemory(self, instance.primary_node,
138
                            "starting instance %s" % instance.name,
139
                            bep[constants.BE_MINMEM], 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
    node_insts = self.rpc.call_instance_list([node],
488
                                             [instance.hypervisor])[node]
489
    node_insts.Raise("Can't get node information from %s" % node)
490

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

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

    
503
    return GetInstanceConsole(self.cfg.GetClusterInfo(), instance)
504

    
505

    
506
class LUInstanceSnapshot(LogicalUnit):
507
  """Take a snapshot of the instance.
508

509
  """
510
  HPATH = "instance-snapshot"
511
  HTYPE = constants.HTYPE_INSTANCE
512
  REQ_BGL = False
513

    
514
  def ExpandNames(self):
515
    self._ExpandAndLockInstance()
516

    
517
  def BuildHooksEnv(self):
518
    """Build hooks env.
519

520
    This runs on master, primary and secondary nodes of the instance.
521

522
    """
523
    return BuildInstanceHookEnvByObject(self, self.instance)
524

    
525
  def BuildHooksNodes(self):
526
    """Build hooks nodes.
527

528
    """
529
    nl = [self.cfg.GetMasterNode()] + list(self.instance.all_nodes)
530
    return (nl, nl)
531

    
532
  def CheckPrereq(self):
533
    """Check prerequisites.
534

535
    This checks that the instance is in the cluster and is not running.
536

537
    """
538
    instance = self.cfg.GetInstanceInfo(self.op.instance_name)
539
    assert instance is not None, \
540
      "Cannot retrieve locked instance %s" % self.op.instance_name
541
    CheckNodeOnline(self, instance.primary_node, "Instance primary node"
542
                    " offline, cannot snapshot")
543

    
544
    self.snapshots = []
545
    for ident, params in self.op.disks:
546
      idx, disk = GetItemFromContainer(ident, 'disk', instance.disks)
547
      snapshot_name = params.get("snapshot_name", None)
548
      if not snapshot_name:
549
        raise errors.OpPrereqError("No snapshot_name passed for disk %s", ident)
550
      self.snapshots.append((idx, disk, snapshot_name))
551

    
552
    self.instance = instance
553

    
554
  def Exec(self, feedback_fn):
555
    """Take a snapshot of the instance the instance.
556

557
    """
558
    inst = self.instance
559
    node_uuid = inst.primary_node
560
    for idx, disk, snapshot_name in self.snapshots:
561
      self.cfg.SetDiskID(disk, node_uuid)
562
      feedback_fn("Taking a snapshot of instance...")
563
      result = self.rpc.call_blockdev_snapshot(node_uuid,
564
                                               (disk, inst),
565
                                               snapshot_name)
566
      result.Raise("Could not take a snapshot for instance %s disk/%d %s"
567
                   " on node %s" % (inst, idx, disk.uuid, inst.primary_node))