Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib.py @ 49abbd3e

History | View | Annotate | Download (169.1 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2008 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
"""Module implementing the master-side code."""
23

    
24
# pylint: disable-msg=W0613,W0201
25

    
26
import os
27
import os.path
28
import sha
29
import time
30
import tempfile
31
import re
32
import platform
33

    
34
from ganeti import rpc
35
from ganeti import ssh
36
from ganeti import logger
37
from ganeti import utils
38
from ganeti import errors
39
from ganeti import hypervisor
40
from ganeti import config
41
from ganeti import constants
42
from ganeti import objects
43
from ganeti import opcodes
44
from ganeti import ssconf
45
from ganeti import serializer
46

    
47

    
48
class LogicalUnit(object):
49
  """Logical Unit base class.
50

51
  Subclasses must follow these rules:
52
    - implement CheckPrereq which also fills in the opcode instance
53
      with all the fields (even if as None)
54
    - implement Exec
55
    - implement BuildHooksEnv
56
    - redefine HPATH and HTYPE
57
    - optionally redefine their run requirements:
58
        REQ_MASTER: the LU needs to run on the master node
59
        REQ_WSSTORE: the LU needs a writable SimpleStore
60

61
  Note that all commands require root permissions.
62

63
  """
64
  HPATH = None
65
  HTYPE = None
66
  _OP_REQP = []
67
  REQ_MASTER = True
68
  REQ_WSSTORE = False
69

    
70
  def __init__(self, processor, op, cfg, sstore):
71
    """Constructor for LogicalUnit.
72

73
    This needs to be overriden in derived classes in order to check op
74
    validity.
75

76
    """
77
    self.proc = processor
78
    self.op = op
79
    self.cfg = cfg
80
    self.sstore = sstore
81
    self.__ssh = None
82

    
83
    for attr_name in self._OP_REQP:
84
      attr_val = getattr(op, attr_name, None)
85
      if attr_val is None:
86
        raise errors.OpPrereqError("Required parameter '%s' missing" %
87
                                   attr_name)
88

    
89
    if not cfg.IsCluster():
90
      raise errors.OpPrereqError("Cluster not initialized yet,"
91
                                 " use 'gnt-cluster init' first.")
92
    if self.REQ_MASTER:
93
      master = sstore.GetMasterNode()
94
      if master != utils.HostInfo().name:
95
        raise errors.OpPrereqError("Commands must be run on the master"
96
                                   " node %s" % master)
97

    
98
  def __GetSSH(self):
99
    """Returns the SshRunner object
100

101
    """
102
    if not self.__ssh:
103
      self.__ssh = ssh.SshRunner(self.sstore)
104
    return self.__ssh
105

    
106
  ssh = property(fget=__GetSSH)
107

    
108
  def CheckPrereq(self):
109
    """Check prerequisites for this LU.
110

111
    This method should check that the prerequisites for the execution
112
    of this LU are fulfilled. It can do internode communication, but
113
    it should be idempotent - no cluster or system changes are
114
    allowed.
115

116
    The method should raise errors.OpPrereqError in case something is
117
    not fulfilled. Its return value is ignored.
118

119
    This method should also update all the parameters of the opcode to
120
    their canonical form; e.g. a short node name must be fully
121
    expanded after this method has successfully completed (so that
122
    hooks, logging, etc. work correctly).
123

124
    """
125
    raise NotImplementedError
126

    
127
  def Exec(self, feedback_fn):
128
    """Execute the LU.
129

130
    This method should implement the actual work. It should raise
131
    errors.OpExecError for failures that are somewhat dealt with in
132
    code, or expected.
133

134
    """
135
    raise NotImplementedError
136

    
137
  def BuildHooksEnv(self):
138
    """Build hooks environment for this LU.
139

140
    This method should return a three-node tuple consisting of: a dict
141
    containing the environment that will be used for running the
142
    specific hook for this LU, a list of node names on which the hook
143
    should run before the execution, and a list of node names on which
144
    the hook should run after the execution.
145

146
    The keys of the dict must not have 'GANETI_' prefixed as this will
147
    be handled in the hooks runner. Also note additional keys will be
148
    added by the hooks runner. If the LU doesn't define any
149
    environment, an empty dict (and not None) should be returned.
150

151
    No nodes should be returned as an empty list (and not None).
152

153
    Note that if the HPATH for a LU class is None, this function will
154
    not be called.
155

156
    """
157
    raise NotImplementedError
158

    
159
  def HooksCallBack(self, phase, hook_results, feedback_fn, lu_result):
160
    """Notify the LU about the results of its hooks.
161

162
    This method is called every time a hooks phase is executed, and notifies
163
    the Logical Unit about the hooks' result. The LU can then use it to alter
164
    its result based on the hooks.  By default the method does nothing and the
165
    previous result is passed back unchanged but any LU can define it if it
166
    wants to use the local cluster hook-scripts somehow.
167

168
    Args:
169
      phase: the hooks phase that has just been run
170
      hooks_results: the results of the multi-node hooks rpc call
171
      feedback_fn: function to send feedback back to the caller
172
      lu_result: the previous result this LU had, or None in the PRE phase.
173

174
    """
175
    return lu_result
176

    
177

    
178
class NoHooksLU(LogicalUnit):
179
  """Simple LU which runs no hooks.
180

181
  This LU is intended as a parent for other LogicalUnits which will
182
  run no hooks, in order to reduce duplicate code.
183

184
  """
185
  HPATH = None
186
  HTYPE = None
187

    
188

    
189
def _GetWantedNodes(lu, nodes):
190
  """Returns list of checked and expanded node names.
191

192
  Args:
193
    nodes: List of nodes (strings) or None for all
194

195
  """
196
  if not isinstance(nodes, list):
197
    raise errors.OpPrereqError("Invalid argument type 'nodes'")
198

    
199
  if nodes:
200
    wanted = []
201

    
202
    for name in nodes:
203
      node = lu.cfg.ExpandNodeName(name)
204
      if node is None:
205
        raise errors.OpPrereqError("No such node name '%s'" % name)
206
      wanted.append(node)
207

    
208
  else:
209
    wanted = lu.cfg.GetNodeList()
210
  return utils.NiceSort(wanted)
211

    
212

    
213
def _GetWantedInstances(lu, instances):
214
  """Returns list of checked and expanded instance names.
215

216
  Args:
217
    instances: List of instances (strings) or None for all
218

219
  """
220
  if not isinstance(instances, list):
221
    raise errors.OpPrereqError("Invalid argument type 'instances'")
222

    
223
  if instances:
224
    wanted = []
225

    
226
    for name in instances:
227
      instance = lu.cfg.ExpandInstanceName(name)
228
      if instance is None:
229
        raise errors.OpPrereqError("No such instance name '%s'" % name)
230
      wanted.append(instance)
231

    
232
  else:
233
    wanted = lu.cfg.GetInstanceList()
234
  return utils.NiceSort(wanted)
235

    
236

    
237
def _CheckOutputFields(static, dynamic, selected):
238
  """Checks whether all selected fields are valid.
239

240
  Args:
241
    static: Static fields
242
    dynamic: Dynamic fields
243

244
  """
245
  static_fields = frozenset(static)
246
  dynamic_fields = frozenset(dynamic)
247

    
248
  all_fields = static_fields | dynamic_fields
249

    
250
  if not all_fields.issuperset(selected):
251
    raise errors.OpPrereqError("Unknown output fields selected: %s"
252
                               % ",".join(frozenset(selected).
253
                                          difference(all_fields)))
254

    
255

    
256
def _BuildInstanceHookEnv(name, primary_node, secondary_nodes, os_type, status,
257
                          memory, vcpus, nics):
258
  """Builds instance related env variables for hooks from single variables.
259

260
  Args:
261
    secondary_nodes: List of secondary nodes as strings
262
  """
263
  env = {
264
    "OP_TARGET": name,
265
    "INSTANCE_NAME": name,
266
    "INSTANCE_PRIMARY": primary_node,
267
    "INSTANCE_SECONDARIES": " ".join(secondary_nodes),
268
    "INSTANCE_OS_TYPE": os_type,
269
    "INSTANCE_STATUS": status,
270
    "INSTANCE_MEMORY": memory,
271
    "INSTANCE_VCPUS": vcpus,
272
  }
273

    
274
  if nics:
275
    nic_count = len(nics)
276
    for idx, (ip, bridge, mac) in enumerate(nics):
277
      if ip is None:
278
        ip = ""
279
      env["INSTANCE_NIC%d_IP" % idx] = ip
280
      env["INSTANCE_NIC%d_BRIDGE" % idx] = bridge
281
      env["INSTANCE_NIC%d_HWADDR" % idx] = mac
282
  else:
283
    nic_count = 0
284

    
285
  env["INSTANCE_NIC_COUNT"] = nic_count
286

    
287
  return env
288

    
289

    
290
def _BuildInstanceHookEnvByObject(instance, override=None):
291
  """Builds instance related env variables for hooks from an object.
292

293
  Args:
294
    instance: objects.Instance object of instance
295
    override: dict of values to override
296
  """
297
  args = {
298
    'name': instance.name,
299
    'primary_node': instance.primary_node,
300
    'secondary_nodes': instance.secondary_nodes,
301
    'os_type': instance.os,
302
    'status': instance.os,
303
    'memory': instance.memory,
304
    'vcpus': instance.vcpus,
305
    'nics': [(nic.ip, nic.bridge, nic.mac) for nic in instance.nics],
306
  }
307
  if override:
308
    args.update(override)
309
  return _BuildInstanceHookEnv(**args)
310

    
311

    
312
def _CheckInstanceBridgesExist(instance):
313
  """Check that the brigdes needed by an instance exist.
314

315
  """
316
  # check bridges existance
317
  brlist = [nic.bridge for nic in instance.nics]
318
  if not rpc.call_bridges_exist(instance.primary_node, brlist):
319
    raise errors.OpPrereqError("one or more target bridges %s does not"
320
                               " exist on destination node '%s'" %
321
                               (brlist, instance.primary_node))
322

    
323

    
324
class LUDestroyCluster(NoHooksLU):
325
  """Logical unit for destroying the cluster.
326

327
  """
328
  _OP_REQP = []
329

    
330
  def CheckPrereq(self):
331
    """Check prerequisites.
332

333
    This checks whether the cluster is empty.
334

335
    Any errors are signalled by raising errors.OpPrereqError.
336

337
    """
338
    master = self.sstore.GetMasterNode()
339

    
340
    nodelist = self.cfg.GetNodeList()
341
    if len(nodelist) != 1 or nodelist[0] != master:
342
      raise errors.OpPrereqError("There are still %d node(s) in"
343
                                 " this cluster." % (len(nodelist) - 1))
344
    instancelist = self.cfg.GetInstanceList()
345
    if instancelist:
346
      raise errors.OpPrereqError("There are still %d instance(s) in"
347
                                 " this cluster." % len(instancelist))
348

    
349
  def Exec(self, feedback_fn):
350
    """Destroys the cluster.
351

352
    """
353
    master = self.sstore.GetMasterNode()
354
    if not rpc.call_node_stop_master(master):
355
      raise errors.OpExecError("Could not disable the master role")
356
    priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
357
    utils.CreateBackup(priv_key)
358
    utils.CreateBackup(pub_key)
359
    rpc.call_node_leave_cluster(master)
360

    
361

    
362
class LUVerifyCluster(LogicalUnit):
363
  """Verifies the cluster status.
364

365
  """
366
  HPATH = "cluster-verify"
367
  HTYPE = constants.HTYPE_CLUSTER
368
  _OP_REQP = ["skip_checks"]
369

    
370
  def _VerifyNode(self, node, file_list, local_cksum, vglist, node_result,
371
                  remote_version, feedback_fn):
372
    """Run multiple tests against a node.
373

374
    Test list:
375
      - compares ganeti version
376
      - checks vg existance and size > 20G
377
      - checks config file checksum
378
      - checks ssh to other nodes
379

380
    Args:
381
      node: name of the node to check
382
      file_list: required list of files
383
      local_cksum: dictionary of local files and their checksums
384

385
    """
386
    # compares ganeti version
387
    local_version = constants.PROTOCOL_VERSION
388
    if not remote_version:
389
      feedback_fn("  - ERROR: connection to %s failed" % (node))
390
      return True
391

    
392
    if local_version != remote_version:
393
      feedback_fn("  - ERROR: sw version mismatch: master %s, node(%s) %s" %
394
                      (local_version, node, remote_version))
395
      return True
396

    
397
    # checks vg existance and size > 20G
398

    
399
    bad = False
400
    if not vglist:
401
      feedback_fn("  - ERROR: unable to check volume groups on node %s." %
402
                      (node,))
403
      bad = True
404
    else:
405
      vgstatus = utils.CheckVolumeGroupSize(vglist, self.cfg.GetVGName(),
406
                                            constants.MIN_VG_SIZE)
407
      if vgstatus:
408
        feedback_fn("  - ERROR: %s on node %s" % (vgstatus, node))
409
        bad = True
410

    
411
    # checks config file checksum
412
    # checks ssh to any
413

    
414
    if 'filelist' not in node_result:
415
      bad = True
416
      feedback_fn("  - ERROR: node hasn't returned file checksum data")
417
    else:
418
      remote_cksum = node_result['filelist']
419
      for file_name in file_list:
420
        if file_name not in remote_cksum:
421
          bad = True
422
          feedback_fn("  - ERROR: file '%s' missing" % file_name)
423
        elif remote_cksum[file_name] != local_cksum[file_name]:
424
          bad = True
425
          feedback_fn("  - ERROR: file '%s' has wrong checksum" % file_name)
426

    
427
    if 'nodelist' not in node_result:
428
      bad = True
429
      feedback_fn("  - ERROR: node hasn't returned node ssh connectivity data")
430
    else:
431
      if node_result['nodelist']:
432
        bad = True
433
        for node in node_result['nodelist']:
434
          feedback_fn("  - ERROR: ssh communication with node '%s': %s" %
435
                          (node, node_result['nodelist'][node]))
436
    if 'node-net-test' not in node_result:
437
      bad = True
438
      feedback_fn("  - ERROR: node hasn't returned node tcp connectivity data")
439
    else:
440
      if node_result['node-net-test']:
441
        bad = True
442
        nlist = utils.NiceSort(node_result['node-net-test'].keys())
443
        for node in nlist:
444
          feedback_fn("  - ERROR: tcp communication with node '%s': %s" %
445
                          (node, node_result['node-net-test'][node]))
446

    
447
    hyp_result = node_result.get('hypervisor', None)
448
    if hyp_result is not None:
449
      feedback_fn("  - ERROR: hypervisor verify failure: '%s'" % hyp_result)
450
    return bad
451

    
452
  def _VerifyInstance(self, instance, instanceconfig, node_vol_is,
453
                      node_instance, feedback_fn):
454
    """Verify an instance.
455

456
    This function checks to see if the required block devices are
457
    available on the instance's node.
458

459
    """
460
    bad = False
461

    
462
    node_current = instanceconfig.primary_node
463

    
464
    node_vol_should = {}
465
    instanceconfig.MapLVsByNode(node_vol_should)
466

    
467
    for node in node_vol_should:
468
      for volume in node_vol_should[node]:
469
        if node not in node_vol_is or volume not in node_vol_is[node]:
470
          feedback_fn("  - ERROR: volume %s missing on node %s" %
471
                          (volume, node))
472
          bad = True
473

    
474
    if not instanceconfig.status == 'down':
475
      if (node_current not in node_instance or
476
          not instance in node_instance[node_current]):
477
        feedback_fn("  - ERROR: instance %s not running on node %s" %
478
                        (instance, node_current))
479
        bad = True
480

    
481
    for node in node_instance:
482
      if (not node == node_current):
483
        if instance in node_instance[node]:
484
          feedback_fn("  - ERROR: instance %s should not run on node %s" %
485
                          (instance, node))
486
          bad = True
487

    
488
    return bad
489

    
490
  def _VerifyOrphanVolumes(self, node_vol_should, node_vol_is, feedback_fn):
491
    """Verify if there are any unknown volumes in the cluster.
492

493
    The .os, .swap and backup volumes are ignored. All other volumes are
494
    reported as unknown.
495

496
    """
497
    bad = False
498

    
499
    for node in node_vol_is:
500
      for volume in node_vol_is[node]:
501
        if node not in node_vol_should or volume not in node_vol_should[node]:
502
          feedback_fn("  - ERROR: volume %s on node %s should not exist" %
503
                      (volume, node))
504
          bad = True
505
    return bad
506

    
507
  def _VerifyOrphanInstances(self, instancelist, node_instance, feedback_fn):
508
    """Verify the list of running instances.
509

510
    This checks what instances are running but unknown to the cluster.
511

512
    """
513
    bad = False
514
    for node in node_instance:
515
      for runninginstance in node_instance[node]:
516
        if runninginstance not in instancelist:
517
          feedback_fn("  - ERROR: instance %s on node %s should not exist" %
518
                          (runninginstance, node))
519
          bad = True
520
    return bad
521

    
522
  def _VerifyNPlusOneMemory(self, node_info, instance_cfg, feedback_fn):
523
    """Verify N+1 Memory Resilience.
524

525
    Check that if one single node dies we can still start all the instances it
526
    was primary for.
527

528
    """
529
    bad = False
530

    
531
    for node, nodeinfo in node_info.iteritems():
532
      # This code checks that every node which is now listed as secondary has
533
      # enough memory to host all instances it is supposed to should a single
534
      # other node in the cluster fail.
535
      # FIXME: not ready for failover to an arbitrary node
536
      # FIXME: does not support file-backed instances
537
      # WARNING: we currently take into account down instances as well as up
538
      # ones, considering that even if they're down someone might want to start
539
      # them even in the event of a node failure.
540
      for prinode, instances in nodeinfo['sinst-by-pnode'].iteritems():
541
        needed_mem = 0
542
        for instance in instances:
543
          needed_mem += instance_cfg[instance].memory
544
        if nodeinfo['mfree'] < needed_mem:
545
          feedback_fn("  - ERROR: not enough memory on node %s to accomodate"
546
                      " failovers should node %s fail" % (node, prinode))
547
          bad = True
548
    return bad
549

    
550
  def CheckPrereq(self):
551
    """Check prerequisites.
552

553
    Transform the list of checks we're going to skip into a set and check that
554
    all its members are valid.
555

556
    """
557
    self.skip_set = frozenset(self.op.skip_checks)
558
    if not constants.VERIFY_OPTIONAL_CHECKS.issuperset(self.skip_set):
559
      raise errors.OpPrereqError("Invalid checks to be skipped specified")
560

    
561
  def BuildHooksEnv(self):
562
    """Build hooks env.
563

564
    Cluster-Verify hooks just rone in the post phase and their failure makes
565
    the output be logged in the verify output and the verification to fail.
566

567
    """
568
    all_nodes = self.cfg.GetNodeList()
569
    # TODO: populate the environment with useful information for verify hooks
570
    env = {}
571
    return env, [], all_nodes
572

    
573
  def Exec(self, feedback_fn):
574
    """Verify integrity of cluster, performing various test on nodes.
575

576
    """
577
    bad = False
578
    feedback_fn("* Verifying global settings")
579
    for msg in self.cfg.VerifyConfig():
580
      feedback_fn("  - ERROR: %s" % msg)
581

    
582
    vg_name = self.cfg.GetVGName()
583
    nodelist = utils.NiceSort(self.cfg.GetNodeList())
584
    nodeinfo = [self.cfg.GetNodeInfo(nname) for nname in nodelist]
585
    instancelist = utils.NiceSort(self.cfg.GetInstanceList())
586
    i_non_redundant = [] # Non redundant instances
587
    node_volume = {}
588
    node_instance = {}
589
    node_info = {}
590
    instance_cfg = {}
591

    
592
    # FIXME: verify OS list
593
    # do local checksums
594
    file_names = list(self.sstore.GetFileList())
595
    file_names.append(constants.SSL_CERT_FILE)
596
    file_names.append(constants.CLUSTER_CONF_FILE)
597
    local_checksums = utils.FingerprintFiles(file_names)
598

    
599
    feedback_fn("* Gathering data (%d nodes)" % len(nodelist))
600
    all_volumeinfo = rpc.call_volume_list(nodelist, vg_name)
601
    all_instanceinfo = rpc.call_instance_list(nodelist)
602
    all_vglist = rpc.call_vg_list(nodelist)
603
    node_verify_param = {
604
      'filelist': file_names,
605
      'nodelist': nodelist,
606
      'hypervisor': None,
607
      'node-net-test': [(node.name, node.primary_ip, node.secondary_ip)
608
                        for node in nodeinfo]
609
      }
610
    all_nvinfo = rpc.call_node_verify(nodelist, node_verify_param)
611
    all_rversion = rpc.call_version(nodelist)
612
    all_ninfo = rpc.call_node_info(nodelist, self.cfg.GetVGName())
613

    
614
    for node in nodelist:
615
      feedback_fn("* Verifying node %s" % node)
616
      result = self._VerifyNode(node, file_names, local_checksums,
617
                                all_vglist[node], all_nvinfo[node],
618
                                all_rversion[node], feedback_fn)
619
      bad = bad or result
620

    
621
      # node_volume
622
      volumeinfo = all_volumeinfo[node]
623

    
624
      if isinstance(volumeinfo, basestring):
625
        feedback_fn("  - ERROR: LVM problem on node %s: %s" %
626
                    (node, volumeinfo[-400:].encode('string_escape')))
627
        bad = True
628
        node_volume[node] = {}
629
      elif not isinstance(volumeinfo, dict):
630
        feedback_fn("  - ERROR: connection to %s failed" % (node,))
631
        bad = True
632
        continue
633
      else:
634
        node_volume[node] = volumeinfo
635

    
636
      # node_instance
637
      nodeinstance = all_instanceinfo[node]
638
      if type(nodeinstance) != list:
639
        feedback_fn("  - ERROR: connection to %s failed" % (node,))
640
        bad = True
641
        continue
642

    
643
      node_instance[node] = nodeinstance
644

    
645
      # node_info
646
      nodeinfo = all_ninfo[node]
647
      if not isinstance(nodeinfo, dict):
648
        feedback_fn("  - ERROR: connection to %s failed" % (node,))
649
        bad = True
650
        continue
651

    
652
      try:
653
        node_info[node] = {
654
          "mfree": int(nodeinfo['memory_free']),
655
          "dfree": int(nodeinfo['vg_free']),
656
          "pinst": [],
657
          "sinst": [],
658
          # dictionary holding all instances this node is secondary for,
659
          # grouped by their primary node. Each key is a cluster node, and each
660
          # value is a list of instances which have the key as primary and the
661
          # current node as secondary.  this is handy to calculate N+1 memory
662
          # availability if you can only failover from a primary to its
663
          # secondary.
664
          "sinst-by-pnode": {},
665
        }
666
      except ValueError:
667
        feedback_fn("  - ERROR: invalid value returned from node %s" % (node,))
668
        bad = True
669
        continue
670

    
671
    node_vol_should = {}
672

    
673
    for instance in instancelist:
674
      feedback_fn("* Verifying instance %s" % instance)
675
      inst_config = self.cfg.GetInstanceInfo(instance)
676
      result =  self._VerifyInstance(instance, inst_config, node_volume,
677
                                     node_instance, feedback_fn)
678
      bad = bad or result
679

    
680
      inst_config.MapLVsByNode(node_vol_should)
681

    
682
      instance_cfg[instance] = inst_config
683

    
684
      pnode = inst_config.primary_node
685
      if pnode in node_info:
686
        node_info[pnode]['pinst'].append(instance)
687
      else:
688
        feedback_fn("  - ERROR: instance %s, connection to primary node"
689
                    " %s failed" % (instance, pnode))
690
        bad = True
691

    
692
      # If the instance is non-redundant we cannot survive losing its primary
693
      # node, so we are not N+1 compliant. On the other hand we have no disk
694
      # templates with more than one secondary so that situation is not well
695
      # supported either.
696
      # FIXME: does not support file-backed instances
697
      if len(inst_config.secondary_nodes) == 0:
698
        i_non_redundant.append(instance)
699
      elif len(inst_config.secondary_nodes) > 1:
700
        feedback_fn("  - WARNING: multiple secondaries for instance %s"
701
                    % instance)
702

    
703
      for snode in inst_config.secondary_nodes:
704
        if snode in node_info:
705
          node_info[snode]['sinst'].append(instance)
706
          if pnode not in node_info[snode]['sinst-by-pnode']:
707
            node_info[snode]['sinst-by-pnode'][pnode] = []
708
          node_info[snode]['sinst-by-pnode'][pnode].append(instance)
709
        else:
710
          feedback_fn("  - ERROR: instance %s, connection to secondary node"
711
                      " %s failed" % (instance, snode))
712

    
713
    feedback_fn("* Verifying orphan volumes")
714
    result = self._VerifyOrphanVolumes(node_vol_should, node_volume,
715
                                       feedback_fn)
716
    bad = bad or result
717

    
718
    feedback_fn("* Verifying remaining instances")
719
    result = self._VerifyOrphanInstances(instancelist, node_instance,
720
                                         feedback_fn)
721
    bad = bad or result
722

    
723
    if constants.VERIFY_NPLUSONE_MEM not in self.skip_set:
724
      feedback_fn("* Verifying N+1 Memory redundancy")
725
      result = self._VerifyNPlusOneMemory(node_info, instance_cfg, feedback_fn)
726
      bad = bad or result
727

    
728
    feedback_fn("* Other Notes")
729
    if i_non_redundant:
730
      feedback_fn("  - NOTICE: %d non-redundant instance(s) found."
731
                  % len(i_non_redundant))
732

    
733
    return int(bad)
734

    
735
  def HooksCallBack(self, phase, hooks_results, feedback_fn, lu_result):
736
    """Analize the post-hooks' result, handle it, and send some
737
    nicely-formatted feedback back to the user.
738

739
    Args:
740
      phase: the hooks phase that has just been run
741
      hooks_results: the results of the multi-node hooks rpc call
742
      feedback_fn: function to send feedback back to the caller
743
      lu_result: previous Exec result
744

745
    """
746
    # We only really run POST phase hooks, and are only interested in their results
747
    if phase == constants.HOOKS_PHASE_POST:
748
      # Used to change hooks' output to proper indentation
749
      indent_re = re.compile('^', re.M)
750
      feedback_fn("* Hooks Results")
751
      if not hooks_results:
752
        feedback_fn("  - ERROR: general communication failure")
753
        lu_result = 1
754
      else:
755
        for node_name in hooks_results:
756
          show_node_header = True
757
          res = hooks_results[node_name]
758
          if res is False or not isinstance(res, list):
759
            feedback_fn("    Communication failure")
760
            lu_result = 1
761
            continue
762
          for script, hkr, output in res:
763
            if hkr == constants.HKR_FAIL:
764
              # The node header is only shown once, if there are
765
              # failing hooks on that node
766
              if show_node_header:
767
                feedback_fn("  Node %s:" % node_name)
768
                show_node_header = False
769
              feedback_fn("    ERROR: Script %s failed, output:" % script)
770
              output = indent_re.sub('      ', output)
771
              feedback_fn("%s" % output)
772
              lu_result = 1
773

    
774
      return lu_result
775

    
776

    
777
class LUVerifyDisks(NoHooksLU):
778
  """Verifies the cluster disks status.
779

780
  """
781
  _OP_REQP = []
782

    
783
  def CheckPrereq(self):
784
    """Check prerequisites.
785

786
    This has no prerequisites.
787

788
    """
789
    pass
790

    
791
  def Exec(self, feedback_fn):
792
    """Verify integrity of cluster disks.
793

794
    """
795
    result = res_nodes, res_nlvm, res_instances, res_missing = [], {}, [], {}
796

    
797
    vg_name = self.cfg.GetVGName()
798
    nodes = utils.NiceSort(self.cfg.GetNodeList())
799
    instances = [self.cfg.GetInstanceInfo(name)
800
                 for name in self.cfg.GetInstanceList()]
801

    
802
    nv_dict = {}
803
    for inst in instances:
804
      inst_lvs = {}
805
      if (inst.status != "up" or
806
          inst.disk_template not in constants.DTS_NET_MIRROR):
807
        continue
808
      inst.MapLVsByNode(inst_lvs)
809
      # transform { iname: {node: [vol,],},} to {(node, vol): iname}
810
      for node, vol_list in inst_lvs.iteritems():
811
        for vol in vol_list:
812
          nv_dict[(node, vol)] = inst
813

    
814
    if not nv_dict:
815
      return result
816

    
817
    node_lvs = rpc.call_volume_list(nodes, vg_name)
818

    
819
    to_act = set()
820
    for node in nodes:
821
      # node_volume
822
      lvs = node_lvs[node]
823

    
824
      if isinstance(lvs, basestring):
825
        logger.Info("error enumerating LVs on node %s: %s" % (node, lvs))
826
        res_nlvm[node] = lvs
827
      elif not isinstance(lvs, dict):
828
        logger.Info("connection to node %s failed or invalid data returned" %
829
                    (node,))
830
        res_nodes.append(node)
831
        continue
832

    
833
      for lv_name, (_, lv_inactive, lv_online) in lvs.iteritems():
834
        inst = nv_dict.pop((node, lv_name), None)
835
        if (not lv_online and inst is not None
836
            and inst.name not in res_instances):
837
          res_instances.append(inst.name)
838

    
839
    # any leftover items in nv_dict are missing LVs, let's arrange the
840
    # data better
841
    for key, inst in nv_dict.iteritems():
842
      if inst.name not in res_missing:
843
        res_missing[inst.name] = []
844
      res_missing[inst.name].append(key)
845

    
846
    return result
847

    
848

    
849
class LURenameCluster(LogicalUnit):
850
  """Rename the cluster.
851

852
  """
853
  HPATH = "cluster-rename"
854
  HTYPE = constants.HTYPE_CLUSTER
855
  _OP_REQP = ["name"]
856
  REQ_WSSTORE = True
857

    
858
  def BuildHooksEnv(self):
859
    """Build hooks env.
860

861
    """
862
    env = {
863
      "OP_TARGET": self.sstore.GetClusterName(),
864
      "NEW_NAME": self.op.name,
865
      }
866
    mn = self.sstore.GetMasterNode()
867
    return env, [mn], [mn]
868

    
869
  def CheckPrereq(self):
870
    """Verify that the passed name is a valid one.
871

872
    """
873
    hostname = utils.HostInfo(self.op.name)
874

    
875
    new_name = hostname.name
876
    self.ip = new_ip = hostname.ip
877
    old_name = self.sstore.GetClusterName()
878
    old_ip = self.sstore.GetMasterIP()
879
    if new_name == old_name and new_ip == old_ip:
880
      raise errors.OpPrereqError("Neither the name nor the IP address of the"
881
                                 " cluster has changed")
882
    if new_ip != old_ip:
883
      result = utils.RunCmd(["fping", "-q", new_ip])
884
      if not result.failed:
885
        raise errors.OpPrereqError("The given cluster IP address (%s) is"
886
                                   " reachable on the network. Aborting." %
887
                                   new_ip)
888

    
889
    self.op.name = new_name
890

    
891
  def Exec(self, feedback_fn):
892
    """Rename the cluster.
893

894
    """
895
    clustername = self.op.name
896
    ip = self.ip
897
    ss = self.sstore
898

    
899
    # shutdown the master IP
900
    master = ss.GetMasterNode()
901
    if not rpc.call_node_stop_master(master):
902
      raise errors.OpExecError("Could not disable the master role")
903

    
904
    try:
905
      # modify the sstore
906
      ss.SetKey(ss.SS_MASTER_IP, ip)
907
      ss.SetKey(ss.SS_CLUSTER_NAME, clustername)
908

    
909
      # Distribute updated ss config to all nodes
910
      myself = self.cfg.GetNodeInfo(master)
911
      dist_nodes = self.cfg.GetNodeList()
912
      if myself.name in dist_nodes:
913
        dist_nodes.remove(myself.name)
914

    
915
      logger.Debug("Copying updated ssconf data to all nodes")
916
      for keyname in [ss.SS_CLUSTER_NAME, ss.SS_MASTER_IP]:
917
        fname = ss.KeyToFilename(keyname)
918
        result = rpc.call_upload_file(dist_nodes, fname)
919
        for to_node in dist_nodes:
920
          if not result[to_node]:
921
            logger.Error("copy of file %s to node %s failed" %
922
                         (fname, to_node))
923
    finally:
924
      if not rpc.call_node_start_master(master):
925
        logger.Error("Could not re-enable the master role on the master,"
926
                     " please restart manually.")
927

    
928

    
929
def _RecursiveCheckIfLVMBased(disk):
930
  """Check if the given disk or its children are lvm-based.
931

932
  Args:
933
    disk: ganeti.objects.Disk object
934

935
  Returns:
936
    boolean indicating whether a LD_LV dev_type was found or not
937

938
  """
939
  if disk.children:
940
    for chdisk in disk.children:
941
      if _RecursiveCheckIfLVMBased(chdisk):
942
        return True
943
  return disk.dev_type == constants.LD_LV
944

    
945

    
946
class LUSetClusterParams(LogicalUnit):
947
  """Change the parameters of the cluster.
948

949
  """
950
  HPATH = "cluster-modify"
951
  HTYPE = constants.HTYPE_CLUSTER
952
  _OP_REQP = []
953

    
954
  def BuildHooksEnv(self):
955
    """Build hooks env.
956

957
    """
958
    env = {
959
      "OP_TARGET": self.sstore.GetClusterName(),
960
      "NEW_VG_NAME": self.op.vg_name,
961
      }
962
    mn = self.sstore.GetMasterNode()
963
    return env, [mn], [mn]
964

    
965
  def CheckPrereq(self):
966
    """Check prerequisites.
967

968
    This checks whether the given params don't conflict and
969
    if the given volume group is valid.
970

971
    """
972
    if not self.op.vg_name:
973
      instances = [self.cfg.GetInstanceInfo(name)
974
                   for name in self.cfg.GetInstanceList()]
975
      for inst in instances:
976
        for disk in inst.disks:
977
          if _RecursiveCheckIfLVMBased(disk):
978
            raise errors.OpPrereqError("Cannot disable lvm storage while"
979
                                       " lvm-based instances exist")
980

    
981
    # if vg_name not None, checks given volume group on all nodes
982
    if self.op.vg_name:
983
      node_list = self.cfg.GetNodeList()
984
      vglist = rpc.call_vg_list(node_list)
985
      for node in node_list:
986
        vgstatus = utils.CheckVolumeGroupSize(vglist[node], self.op.vg_name,
987
                                              constants.MIN_VG_SIZE)
988
        if vgstatus:
989
          raise errors.OpPrereqError("Error on node '%s': %s" %
990
                                     (node, vgstatus))
991

    
992
  def Exec(self, feedback_fn):
993
    """Change the parameters of the cluster.
994

995
    """
996
    if self.op.vg_name != self.cfg.GetVGName():
997
      self.cfg.SetVGName(self.op.vg_name)
998
    else:
999
      feedback_fn("Cluster LVM configuration already in desired"
1000
                  " state, not changing")
1001

    
1002

    
1003
def _WaitForSync(cfgw, instance, proc, oneshot=False, unlock=False):
1004
  """Sleep and poll for an instance's disk to sync.
1005

1006
  """
1007
  if not instance.disks:
1008
    return True
1009

    
1010
  if not oneshot:
1011
    proc.LogInfo("Waiting for instance %s to sync disks." % instance.name)
1012

    
1013
  node = instance.primary_node
1014

    
1015
  for dev in instance.disks:
1016
    cfgw.SetDiskID(dev, node)
1017

    
1018
  retries = 0
1019
  while True:
1020
    max_time = 0
1021
    done = True
1022
    cumul_degraded = False
1023
    rstats = rpc.call_blockdev_getmirrorstatus(node, instance.disks)
1024
    if not rstats:
1025
      proc.LogWarning("Can't get any data from node %s" % node)
1026
      retries += 1
1027
      if retries >= 10:
1028
        raise errors.RemoteError("Can't contact node %s for mirror data,"
1029
                                 " aborting." % node)
1030
      time.sleep(6)
1031
      continue
1032
    retries = 0
1033
    for i in range(len(rstats)):
1034
      mstat = rstats[i]
1035
      if mstat is None:
1036
        proc.LogWarning("Can't compute data for node %s/%s" %
1037
                        (node, instance.disks[i].iv_name))
1038
        continue
1039
      # we ignore the ldisk parameter
1040
      perc_done, est_time, is_degraded, _ = mstat
1041
      cumul_degraded = cumul_degraded or (is_degraded and perc_done is None)
1042
      if perc_done is not None:
1043
        done = False
1044
        if est_time is not None:
1045
          rem_time = "%d estimated seconds remaining" % est_time
1046
          max_time = est_time
1047
        else:
1048
          rem_time = "no time estimate"
1049
        proc.LogInfo("- device %s: %5.2f%% done, %s" %
1050
                     (instance.disks[i].iv_name, perc_done, rem_time))
1051
    if done or oneshot:
1052
      break
1053

    
1054
    if unlock:
1055
      #utils.Unlock('cmd')
1056
      pass
1057
    try:
1058
      time.sleep(min(60, max_time))
1059
    finally:
1060
      if unlock:
1061
        #utils.Lock('cmd')
1062
        pass
1063

    
1064
  if done:
1065
    proc.LogInfo("Instance %s's disks are in sync." % instance.name)
1066
  return not cumul_degraded
1067

    
1068

    
1069
def _CheckDiskConsistency(cfgw, dev, node, on_primary, ldisk=False):
1070
  """Check that mirrors are not degraded.
1071

1072
  The ldisk parameter, if True, will change the test from the
1073
  is_degraded attribute (which represents overall non-ok status for
1074
  the device(s)) to the ldisk (representing the local storage status).
1075

1076
  """
1077
  cfgw.SetDiskID(dev, node)
1078
  if ldisk:
1079
    idx = 6
1080
  else:
1081
    idx = 5
1082

    
1083
  result = True
1084
  if on_primary or dev.AssembleOnSecondary():
1085
    rstats = rpc.call_blockdev_find(node, dev)
1086
    if not rstats:
1087
      logger.ToStderr("Node %s: Disk degraded, not found or node down" % node)
1088
      result = False
1089
    else:
1090
      result = result and (not rstats[idx])
1091
  if dev.children:
1092
    for child in dev.children:
1093
      result = result and _CheckDiskConsistency(cfgw, child, node, on_primary)
1094

    
1095
  return result
1096

    
1097

    
1098
class LUDiagnoseOS(NoHooksLU):
1099
  """Logical unit for OS diagnose/query.
1100

1101
  """
1102
  _OP_REQP = ["output_fields", "names"]
1103

    
1104
  def CheckPrereq(self):
1105
    """Check prerequisites.
1106

1107
    This always succeeds, since this is a pure query LU.
1108

1109
    """
1110
    if self.op.names:
1111
      raise errors.OpPrereqError("Selective OS query not supported")
1112

    
1113
    self.dynamic_fields = frozenset(["name", "valid", "node_status"])
1114
    _CheckOutputFields(static=[],
1115
                       dynamic=self.dynamic_fields,
1116
                       selected=self.op.output_fields)
1117

    
1118
  @staticmethod
1119
  def _DiagnoseByOS(node_list, rlist):
1120
    """Remaps a per-node return list into an a per-os per-node dictionary
1121

1122
      Args:
1123
        node_list: a list with the names of all nodes
1124
        rlist: a map with node names as keys and OS objects as values
1125

1126
      Returns:
1127
        map: a map with osnames as keys and as value another map, with
1128
             nodes as
1129
             keys and list of OS objects as values
1130
             e.g. {"debian-etch": {"node1": [<object>,...],
1131
                                   "node2": [<object>,]}
1132
                  }
1133

1134
    """
1135
    all_os = {}
1136
    for node_name, nr in rlist.iteritems():
1137
      if not nr:
1138
        continue
1139
      for os_obj in nr:
1140
        if os_obj.name not in all_os:
1141
          # build a list of nodes for this os containing empty lists
1142
          # for each node in node_list
1143
          all_os[os_obj.name] = {}
1144
          for nname in node_list:
1145
            all_os[os_obj.name][nname] = []
1146
        all_os[os_obj.name][node_name].append(os_obj)
1147
    return all_os
1148

    
1149
  def Exec(self, feedback_fn):
1150
    """Compute the list of OSes.
1151

1152
    """
1153
    node_list = self.cfg.GetNodeList()
1154
    node_data = rpc.call_os_diagnose(node_list)
1155
    if node_data == False:
1156
      raise errors.OpExecError("Can't gather the list of OSes")
1157
    pol = self._DiagnoseByOS(node_list, node_data)
1158
    output = []
1159
    for os_name, os_data in pol.iteritems():
1160
      row = []
1161
      for field in self.op.output_fields:
1162
        if field == "name":
1163
          val = os_name
1164
        elif field == "valid":
1165
          val = utils.all([osl and osl[0] for osl in os_data.values()])
1166
        elif field == "node_status":
1167
          val = {}
1168
          for node_name, nos_list in os_data.iteritems():
1169
            val[node_name] = [(v.status, v.path) for v in nos_list]
1170
        else:
1171
          raise errors.ParameterError(field)
1172
        row.append(val)
1173
      output.append(row)
1174

    
1175
    return output
1176

    
1177

    
1178
class LURemoveNode(LogicalUnit):
1179
  """Logical unit for removing a node.
1180

1181
  """
1182
  HPATH = "node-remove"
1183
  HTYPE = constants.HTYPE_NODE
1184
  _OP_REQP = ["node_name"]
1185

    
1186
  def BuildHooksEnv(self):
1187
    """Build hooks env.
1188

1189
    This doesn't run on the target node in the pre phase as a failed
1190
    node would then be impossible to remove.
1191

1192
    """
1193
    env = {
1194
      "OP_TARGET": self.op.node_name,
1195
      "NODE_NAME": self.op.node_name,
1196
      }
1197
    all_nodes = self.cfg.GetNodeList()
1198
    all_nodes.remove(self.op.node_name)
1199
    return env, all_nodes, all_nodes
1200

    
1201
  def CheckPrereq(self):
1202
    """Check prerequisites.
1203

1204
    This checks:
1205
     - the node exists in the configuration
1206
     - it does not have primary or secondary instances
1207
     - it's not the master
1208

1209
    Any errors are signalled by raising errors.OpPrereqError.
1210

1211
    """
1212
    node = self.cfg.GetNodeInfo(self.cfg.ExpandNodeName(self.op.node_name))
1213
    if node is None:
1214
      raise errors.OpPrereqError, ("Node '%s' is unknown." % self.op.node_name)
1215

    
1216
    instance_list = self.cfg.GetInstanceList()
1217

    
1218
    masternode = self.sstore.GetMasterNode()
1219
    if node.name == masternode:
1220
      raise errors.OpPrereqError("Node is the master node,"
1221
                                 " you need to failover first.")
1222

    
1223
    for instance_name in instance_list:
1224
      instance = self.cfg.GetInstanceInfo(instance_name)
1225
      if node.name == instance.primary_node:
1226
        raise errors.OpPrereqError("Instance %s still running on the node,"
1227
                                   " please remove first." % instance_name)
1228
      if node.name in instance.secondary_nodes:
1229
        raise errors.OpPrereqError("Instance %s has node as a secondary,"
1230
                                   " please remove first." % instance_name)
1231
    self.op.node_name = node.name
1232
    self.node = node
1233

    
1234
  def Exec(self, feedback_fn):
1235
    """Removes the node from the cluster.
1236

1237
    """
1238
    node = self.node
1239
    logger.Info("stopping the node daemon and removing configs from node %s" %
1240
                node.name)
1241

    
1242
    rpc.call_node_leave_cluster(node.name)
1243

    
1244
    self.ssh.Run(node.name, 'root', "%s stop" % constants.NODE_INITD_SCRIPT)
1245

    
1246
    logger.Info("Removing node %s from config" % node.name)
1247

    
1248
    self.cfg.RemoveNode(node.name)
1249

    
1250
    utils.RemoveHostFromEtcHosts(node.name)
1251

    
1252

    
1253
class LUQueryNodes(NoHooksLU):
1254
  """Logical unit for querying nodes.
1255

1256
  """
1257
  _OP_REQP = ["output_fields", "names"]
1258

    
1259
  def CheckPrereq(self):
1260
    """Check prerequisites.
1261

1262
    This checks that the fields required are valid output fields.
1263

1264
    """
1265
    self.dynamic_fields = frozenset([
1266
      "dtotal", "dfree",
1267
      "mtotal", "mnode", "mfree",
1268
      "bootid",
1269
      "ctotal",
1270
      ])
1271

    
1272
    _CheckOutputFields(static=["name", "pinst_cnt", "sinst_cnt",
1273
                               "pinst_list", "sinst_list",
1274
                               "pip", "sip", "tags"],
1275
                       dynamic=self.dynamic_fields,
1276
                       selected=self.op.output_fields)
1277

    
1278
    self.wanted = _GetWantedNodes(self, self.op.names)
1279

    
1280
  def Exec(self, feedback_fn):
1281
    """Computes the list of nodes and their attributes.
1282

1283
    """
1284
    nodenames = self.wanted
1285
    nodelist = [self.cfg.GetNodeInfo(name) for name in nodenames]
1286

    
1287
    # begin data gathering
1288

    
1289
    if self.dynamic_fields.intersection(self.op.output_fields):
1290
      live_data = {}
1291
      node_data = rpc.call_node_info(nodenames, self.cfg.GetVGName())
1292
      for name in nodenames:
1293
        nodeinfo = node_data.get(name, None)
1294
        if nodeinfo:
1295
          live_data[name] = {
1296
            "mtotal": utils.TryConvert(int, nodeinfo['memory_total']),
1297
            "mnode": utils.TryConvert(int, nodeinfo['memory_dom0']),
1298
            "mfree": utils.TryConvert(int, nodeinfo['memory_free']),
1299
            "dtotal": utils.TryConvert(int, nodeinfo['vg_size']),
1300
            "dfree": utils.TryConvert(int, nodeinfo['vg_free']),
1301
            "ctotal": utils.TryConvert(int, nodeinfo['cpu_total']),
1302
            "bootid": nodeinfo['bootid'],
1303
            }
1304
        else:
1305
          live_data[name] = {}
1306
    else:
1307
      live_data = dict.fromkeys(nodenames, {})
1308

    
1309
    node_to_primary = dict([(name, set()) for name in nodenames])
1310
    node_to_secondary = dict([(name, set()) for name in nodenames])
1311

    
1312
    inst_fields = frozenset(("pinst_cnt", "pinst_list",
1313
                             "sinst_cnt", "sinst_list"))
1314
    if inst_fields & frozenset(self.op.output_fields):
1315
      instancelist = self.cfg.GetInstanceList()
1316

    
1317
      for instance_name in instancelist:
1318
        inst = self.cfg.GetInstanceInfo(instance_name)
1319
        if inst.primary_node in node_to_primary:
1320
          node_to_primary[inst.primary_node].add(inst.name)
1321
        for secnode in inst.secondary_nodes:
1322
          if secnode in node_to_secondary:
1323
            node_to_secondary[secnode].add(inst.name)
1324

    
1325
    # end data gathering
1326

    
1327
    output = []
1328
    for node in nodelist:
1329
      node_output = []
1330
      for field in self.op.output_fields:
1331
        if field == "name":
1332
          val = node.name
1333
        elif field == "pinst_list":
1334
          val = list(node_to_primary[node.name])
1335
        elif field == "sinst_list":
1336
          val = list(node_to_secondary[node.name])
1337
        elif field == "pinst_cnt":
1338
          val = len(node_to_primary[node.name])
1339
        elif field == "sinst_cnt":
1340
          val = len(node_to_secondary[node.name])
1341
        elif field == "pip":
1342
          val = node.primary_ip
1343
        elif field == "sip":
1344
          val = node.secondary_ip
1345
        elif field == "tags":
1346
          val = list(node.GetTags())
1347
        elif field in self.dynamic_fields:
1348
          val = live_data[node.name].get(field, None)
1349
        else:
1350
          raise errors.ParameterError(field)
1351
        node_output.append(val)
1352
      output.append(node_output)
1353

    
1354
    return output
1355

    
1356

    
1357
class LUQueryNodeVolumes(NoHooksLU):
1358
  """Logical unit for getting volumes on node(s).
1359

1360
  """
1361
  _OP_REQP = ["nodes", "output_fields"]
1362

    
1363
  def CheckPrereq(self):
1364
    """Check prerequisites.
1365

1366
    This checks that the fields required are valid output fields.
1367

1368
    """
1369
    self.nodes = _GetWantedNodes(self, self.op.nodes)
1370

    
1371
    _CheckOutputFields(static=["node"],
1372
                       dynamic=["phys", "vg", "name", "size", "instance"],
1373
                       selected=self.op.output_fields)
1374

    
1375

    
1376
  def Exec(self, feedback_fn):
1377
    """Computes the list of nodes and their attributes.
1378

1379
    """
1380
    nodenames = self.nodes
1381
    volumes = rpc.call_node_volumes(nodenames)
1382

    
1383
    ilist = [self.cfg.GetInstanceInfo(iname) for iname
1384
             in self.cfg.GetInstanceList()]
1385

    
1386
    lv_by_node = dict([(inst, inst.MapLVsByNode()) for inst in ilist])
1387

    
1388
    output = []
1389
    for node in nodenames:
1390
      if node not in volumes or not volumes[node]:
1391
        continue
1392

    
1393
      node_vols = volumes[node][:]
1394
      node_vols.sort(key=lambda vol: vol['dev'])
1395

    
1396
      for vol in node_vols:
1397
        node_output = []
1398
        for field in self.op.output_fields:
1399
          if field == "node":
1400
            val = node
1401
          elif field == "phys":
1402
            val = vol['dev']
1403
          elif field == "vg":
1404
            val = vol['vg']
1405
          elif field == "name":
1406
            val = vol['name']
1407
          elif field == "size":
1408
            val = int(float(vol['size']))
1409
          elif field == "instance":
1410
            for inst in ilist:
1411
              if node not in lv_by_node[inst]:
1412
                continue
1413
              if vol['name'] in lv_by_node[inst][node]:
1414
                val = inst.name
1415
                break
1416
            else:
1417
              val = '-'
1418
          else:
1419
            raise errors.ParameterError(field)
1420
          node_output.append(str(val))
1421

    
1422
        output.append(node_output)
1423

    
1424
    return output
1425

    
1426

    
1427
class LUAddNode(LogicalUnit):
1428
  """Logical unit for adding node to the cluster.
1429

1430
  """
1431
  HPATH = "node-add"
1432
  HTYPE = constants.HTYPE_NODE
1433
  _OP_REQP = ["node_name"]
1434

    
1435
  def BuildHooksEnv(self):
1436
    """Build hooks env.
1437

1438
    This will run on all nodes before, and on all nodes + the new node after.
1439

1440
    """
1441
    env = {
1442
      "OP_TARGET": self.op.node_name,
1443
      "NODE_NAME": self.op.node_name,
1444
      "NODE_PIP": self.op.primary_ip,
1445
      "NODE_SIP": self.op.secondary_ip,
1446
      }
1447
    nodes_0 = self.cfg.GetNodeList()
1448
    nodes_1 = nodes_0 + [self.op.node_name, ]
1449
    return env, nodes_0, nodes_1
1450

    
1451
  def CheckPrereq(self):
1452
    """Check prerequisites.
1453

1454
    This checks:
1455
     - the new node is not already in the config
1456
     - it is resolvable
1457
     - its parameters (single/dual homed) matches the cluster
1458

1459
    Any errors are signalled by raising errors.OpPrereqError.
1460

1461
    """
1462
    node_name = self.op.node_name
1463
    cfg = self.cfg
1464

    
1465
    dns_data = utils.HostInfo(node_name)
1466

    
1467
    node = dns_data.name
1468
    primary_ip = self.op.primary_ip = dns_data.ip
1469
    secondary_ip = getattr(self.op, "secondary_ip", None)
1470
    if secondary_ip is None:
1471
      secondary_ip = primary_ip
1472
    if not utils.IsValidIP(secondary_ip):
1473
      raise errors.OpPrereqError("Invalid secondary IP given")
1474
    self.op.secondary_ip = secondary_ip
1475

    
1476
    node_list = cfg.GetNodeList()
1477
    if not self.op.readd and node in node_list:
1478
      raise errors.OpPrereqError("Node %s is already in the configuration" %
1479
                                 node)
1480
    elif self.op.readd and node not in node_list:
1481
      raise errors.OpPrereqError("Node %s is not in the configuration" % node)
1482

    
1483
    for existing_node_name in node_list:
1484
      existing_node = cfg.GetNodeInfo(existing_node_name)
1485

    
1486
      if self.op.readd and node == existing_node_name:
1487
        if (existing_node.primary_ip != primary_ip or
1488
            existing_node.secondary_ip != secondary_ip):
1489
          raise errors.OpPrereqError("Readded node doesn't have the same IP"
1490
                                     " address configuration as before")
1491
        continue
1492

    
1493
      if (existing_node.primary_ip == primary_ip or
1494
          existing_node.secondary_ip == primary_ip or
1495
          existing_node.primary_ip == secondary_ip or
1496
          existing_node.secondary_ip == secondary_ip):
1497
        raise errors.OpPrereqError("New node ip address(es) conflict with"
1498
                                   " existing node %s" % existing_node.name)
1499

    
1500
    # check that the type of the node (single versus dual homed) is the
1501
    # same as for the master
1502
    myself = cfg.GetNodeInfo(self.sstore.GetMasterNode())
1503
    master_singlehomed = myself.secondary_ip == myself.primary_ip
1504
    newbie_singlehomed = secondary_ip == primary_ip
1505
    if master_singlehomed != newbie_singlehomed:
1506
      if master_singlehomed:
1507
        raise errors.OpPrereqError("The master has no private ip but the"
1508
                                   " new node has one")
1509
      else:
1510
        raise errors.OpPrereqError("The master has a private ip but the"
1511
                                   " new node doesn't have one")
1512

    
1513
    # checks reachablity
1514
    if not utils.TcpPing(primary_ip, constants.DEFAULT_NODED_PORT):
1515
      raise errors.OpPrereqError("Node not reachable by ping")
1516

    
1517
    if not newbie_singlehomed:
1518
      # check reachability from my secondary ip to newbie's secondary ip
1519
      if not utils.TcpPing(secondary_ip, constants.DEFAULT_NODED_PORT,
1520
                           source=myself.secondary_ip):
1521
        raise errors.OpPrereqError("Node secondary ip not reachable by TCP"
1522
                                   " based ping to noded port")
1523

    
1524
    self.new_node = objects.Node(name=node,
1525
                                 primary_ip=primary_ip,
1526
                                 secondary_ip=secondary_ip)
1527

    
1528
  def Exec(self, feedback_fn):
1529
    """Adds the new node to the cluster.
1530

1531
    """
1532
    new_node = self.new_node
1533
    node = new_node.name
1534

    
1535
    # set up inter-node password and certificate and restarts the node daemon
1536
    gntpass = self.sstore.GetNodeDaemonPassword()
1537
    if not re.match('^[a-zA-Z0-9.]{1,64}$', gntpass):
1538
      raise errors.OpExecError("ganeti password corruption detected")
1539
    f = open(constants.SSL_CERT_FILE)
1540
    try:
1541
      gntpem = f.read(8192)
1542
    finally:
1543
      f.close()
1544
    # in the base64 pem encoding, neither '!' nor '.' are valid chars,
1545
    # so we use this to detect an invalid certificate; as long as the
1546
    # cert doesn't contain this, the here-document will be correctly
1547
    # parsed by the shell sequence below
1548
    if re.search('^!EOF\.', gntpem, re.MULTILINE):
1549
      raise errors.OpExecError("invalid PEM encoding in the SSL certificate")
1550
    if not gntpem.endswith("\n"):
1551
      raise errors.OpExecError("PEM must end with newline")
1552
    logger.Info("copy cluster pass to %s and starting the node daemon" % node)
1553

    
1554
    # and then connect with ssh to set password and start ganeti-noded
1555
    # note that all the below variables are sanitized at this point,
1556
    # either by being constants or by the checks above
1557
    ss = self.sstore
1558
    mycommand = ("umask 077 && "
1559
                 "echo '%s' > '%s' && "
1560
                 "cat > '%s' << '!EOF.' && \n"
1561
                 "%s!EOF.\n%s restart" %
1562
                 (gntpass, ss.KeyToFilename(ss.SS_NODED_PASS),
1563
                  constants.SSL_CERT_FILE, gntpem,
1564
                  constants.NODE_INITD_SCRIPT))
1565

    
1566
    result = self.ssh.Run(node, 'root', mycommand, batch=False, ask_key=True)
1567
    if result.failed:
1568
      raise errors.OpExecError("Remote command on node %s, error: %s,"
1569
                               " output: %s" %
1570
                               (node, result.fail_reason, result.output))
1571

    
1572
    # check connectivity
1573
    time.sleep(4)
1574

    
1575
    result = rpc.call_version([node])[node]
1576
    if result:
1577
      if constants.PROTOCOL_VERSION == result:
1578
        logger.Info("communication to node %s fine, sw version %s match" %
1579
                    (node, result))
1580
      else:
1581
        raise errors.OpExecError("Version mismatch master version %s,"
1582
                                 " node version %s" %
1583
                                 (constants.PROTOCOL_VERSION, result))
1584
    else:
1585
      raise errors.OpExecError("Cannot get version from the new node")
1586

    
1587
    # setup ssh on node
1588
    logger.Info("copy ssh key to node %s" % node)
1589
    priv_key, pub_key, _ = ssh.GetUserFiles(constants.GANETI_RUNAS)
1590
    keyarray = []
1591
    keyfiles = [constants.SSH_HOST_DSA_PRIV, constants.SSH_HOST_DSA_PUB,
1592
                constants.SSH_HOST_RSA_PRIV, constants.SSH_HOST_RSA_PUB,
1593
                priv_key, pub_key]
1594

    
1595
    for i in keyfiles:
1596
      f = open(i, 'r')
1597
      try:
1598
        keyarray.append(f.read())
1599
      finally:
1600
        f.close()
1601

    
1602
    result = rpc.call_node_add(node, keyarray[0], keyarray[1], keyarray[2],
1603
                               keyarray[3], keyarray[4], keyarray[5])
1604

    
1605
    if not result:
1606
      raise errors.OpExecError("Cannot transfer ssh keys to the new node")
1607

    
1608
    # Add node to our /etc/hosts, and add key to known_hosts
1609
    utils.AddHostToEtcHosts(new_node.name)
1610

    
1611
    if new_node.secondary_ip != new_node.primary_ip:
1612
      if not rpc.call_node_tcp_ping(new_node.name,
1613
                                    constants.LOCALHOST_IP_ADDRESS,
1614
                                    new_node.secondary_ip,
1615
                                    constants.DEFAULT_NODED_PORT,
1616
                                    10, False):
1617
        raise errors.OpExecError("Node claims it doesn't have the secondary ip"
1618
                                 " you gave (%s). Please fix and re-run this"
1619
                                 " command." % new_node.secondary_ip)
1620

    
1621
    success, msg = self.ssh.VerifyNodeHostname(node)
1622
    if not success:
1623
      raise errors.OpExecError("Node '%s' claims it has a different hostname"
1624
                               " than the one the resolver gives: %s."
1625
                               " Please fix and re-run this command." %
1626
                               (node, msg))
1627

    
1628
    # Distribute updated /etc/hosts and known_hosts to all nodes,
1629
    # including the node just added
1630
    myself = self.cfg.GetNodeInfo(self.sstore.GetMasterNode())
1631
    dist_nodes = self.cfg.GetNodeList()
1632
    if not self.op.readd:
1633
      dist_nodes.append(node)
1634
    if myself.name in dist_nodes:
1635
      dist_nodes.remove(myself.name)
1636

    
1637
    logger.Debug("Copying hosts and known_hosts to all nodes")
1638
    for fname in (constants.ETC_HOSTS, constants.SSH_KNOWN_HOSTS_FILE):
1639
      result = rpc.call_upload_file(dist_nodes, fname)
1640
      for to_node in dist_nodes:
1641
        if not result[to_node]:
1642
          logger.Error("copy of file %s to node %s failed" %
1643
                       (fname, to_node))
1644

    
1645
    to_copy = ss.GetFileList()
1646
    if self.sstore.GetHypervisorType() == constants.HT_XEN_HVM31:
1647
      to_copy.append(constants.VNC_PASSWORD_FILE)
1648
    for fname in to_copy:
1649
      if not self.ssh.CopyFileToNode(node, fname):
1650
        logger.Error("could not copy file %s to node %s" % (fname, node))
1651

    
1652
    if not self.op.readd:
1653
      logger.Info("adding node %s to cluster.conf" % node)
1654
      self.cfg.AddNode(new_node)
1655

    
1656

    
1657
class LUMasterFailover(LogicalUnit):
1658
  """Failover the master node to the current node.
1659

1660
  This is a special LU in that it must run on a non-master node.
1661

1662
  """
1663
  HPATH = "master-failover"
1664
  HTYPE = constants.HTYPE_CLUSTER
1665
  REQ_MASTER = False
1666
  REQ_WSSTORE = True
1667
  _OP_REQP = []
1668

    
1669
  def BuildHooksEnv(self):
1670
    """Build hooks env.
1671

1672
    This will run on the new master only in the pre phase, and on all
1673
    the nodes in the post phase.
1674

1675
    """
1676
    env = {
1677
      "OP_TARGET": self.new_master,
1678
      "NEW_MASTER": self.new_master,
1679
      "OLD_MASTER": self.old_master,
1680
      }
1681
    return env, [self.new_master], self.cfg.GetNodeList()
1682

    
1683
  def CheckPrereq(self):
1684
    """Check prerequisites.
1685

1686
    This checks that we are not already the master.
1687

1688
    """
1689
    self.new_master = utils.HostInfo().name
1690
    self.old_master = self.sstore.GetMasterNode()
1691

    
1692
    if self.old_master == self.new_master:
1693
      raise errors.OpPrereqError("This commands must be run on the node"
1694
                                 " where you want the new master to be."
1695
                                 " %s is already the master" %
1696
                                 self.old_master)
1697

    
1698
  def Exec(self, feedback_fn):
1699
    """Failover the master node.
1700

1701
    This command, when run on a non-master node, will cause the current
1702
    master to cease being master, and the non-master to become new
1703
    master.
1704

1705
    """
1706
    #TODO: do not rely on gethostname returning the FQDN
1707
    logger.Info("setting master to %s, old master: %s" %
1708
                (self.new_master, self.old_master))
1709

    
1710
    if not rpc.call_node_stop_master(self.old_master):
1711
      logger.Error("could disable the master role on the old master"
1712
                   " %s, please disable manually" % self.old_master)
1713

    
1714
    ss = self.sstore
1715
    ss.SetKey(ss.SS_MASTER_NODE, self.new_master)
1716
    if not rpc.call_upload_file(self.cfg.GetNodeList(),
1717
                                ss.KeyToFilename(ss.SS_MASTER_NODE)):
1718
      logger.Error("could not distribute the new simple store master file"
1719
                   " to the other nodes, please check.")
1720

    
1721
    if not rpc.call_node_start_master(self.new_master):
1722
      logger.Error("could not start the master role on the new master"
1723
                   " %s, please check" % self.new_master)
1724
      feedback_fn("Error in activating the master IP on the new master,"
1725
                  " please fix manually.")
1726

    
1727

    
1728

    
1729
class LUQueryClusterInfo(NoHooksLU):
1730
  """Query cluster configuration.
1731

1732
  """
1733
  _OP_REQP = []
1734
  REQ_MASTER = False
1735

    
1736
  def CheckPrereq(self):
1737
    """No prerequsites needed for this LU.
1738

1739
    """
1740
    pass
1741

    
1742
  def Exec(self, feedback_fn):
1743
    """Return cluster config.
1744

1745
    """
1746
    result = {
1747
      "name": self.sstore.GetClusterName(),
1748
      "software_version": constants.RELEASE_VERSION,
1749
      "protocol_version": constants.PROTOCOL_VERSION,
1750
      "config_version": constants.CONFIG_VERSION,
1751
      "os_api_version": constants.OS_API_VERSION,
1752
      "export_version": constants.EXPORT_VERSION,
1753
      "master": self.sstore.GetMasterNode(),
1754
      "architecture": (platform.architecture()[0], platform.machine()),
1755
      "hypervisor_type": self.sstore.GetHypervisorType(),
1756
      }
1757

    
1758
    return result
1759

    
1760

    
1761
class LUDumpClusterConfig(NoHooksLU):
1762
  """Return a text-representation of the cluster-config.
1763

1764
  """
1765
  _OP_REQP = []
1766

    
1767
  def CheckPrereq(self):
1768
    """No prerequisites.
1769

1770
    """
1771
    pass
1772

    
1773
  def Exec(self, feedback_fn):
1774
    """Dump a representation of the cluster config to the standard output.
1775

1776
    """
1777
    return self.cfg.DumpConfig()
1778

    
1779

    
1780
class LUActivateInstanceDisks(NoHooksLU):
1781
  """Bring up an instance's disks.
1782

1783
  """
1784
  _OP_REQP = ["instance_name"]
1785

    
1786
  def CheckPrereq(self):
1787
    """Check prerequisites.
1788

1789
    This checks that the instance is in the cluster.
1790

1791
    """
1792
    instance = self.cfg.GetInstanceInfo(
1793
      self.cfg.ExpandInstanceName(self.op.instance_name))
1794
    if instance is None:
1795
      raise errors.OpPrereqError("Instance '%s' not known" %
1796
                                 self.op.instance_name)
1797
    self.instance = instance
1798

    
1799

    
1800
  def Exec(self, feedback_fn):
1801
    """Activate the disks.
1802

1803
    """
1804
    disks_ok, disks_info = _AssembleInstanceDisks(self.instance, self.cfg)
1805
    if not disks_ok:
1806
      raise errors.OpExecError("Cannot activate block devices")
1807

    
1808
    return disks_info
1809

    
1810

    
1811
def _AssembleInstanceDisks(instance, cfg, ignore_secondaries=False):
1812
  """Prepare the block devices for an instance.
1813

1814
  This sets up the block devices on all nodes.
1815

1816
  Args:
1817
    instance: a ganeti.objects.Instance object
1818
    ignore_secondaries: if true, errors on secondary nodes won't result
1819
                        in an error return from the function
1820

1821
  Returns:
1822
    false if the operation failed
1823
    list of (host, instance_visible_name, node_visible_name) if the operation
1824
         suceeded with the mapping from node devices to instance devices
1825
  """
1826
  device_info = []
1827
  disks_ok = True
1828
  iname = instance.name
1829
  # With the two passes mechanism we try to reduce the window of
1830
  # opportunity for the race condition of switching DRBD to primary
1831
  # before handshaking occured, but we do not eliminate it
1832

    
1833
  # The proper fix would be to wait (with some limits) until the
1834
  # connection has been made and drbd transitions from WFConnection
1835
  # into any other network-connected state (Connected, SyncTarget,
1836
  # SyncSource, etc.)
1837

    
1838
  # 1st pass, assemble on all nodes in secondary mode
1839
  for inst_disk in instance.disks:
1840
    for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
1841
      cfg.SetDiskID(node_disk, node)
1842
      result = rpc.call_blockdev_assemble(node, node_disk, iname, False)
1843
      if not result:
1844
        logger.Error("could not prepare block device %s on node %s"
1845
                     " (is_primary=False, pass=1)" % (inst_disk.iv_name, node))
1846
        if not ignore_secondaries:
1847
          disks_ok = False
1848

    
1849
  # FIXME: race condition on drbd migration to primary
1850

    
1851
  # 2nd pass, do only the primary node
1852
  for inst_disk in instance.disks:
1853
    for node, node_disk in inst_disk.ComputeNodeTree(instance.primary_node):
1854
      if node != instance.primary_node:
1855
        continue
1856
      cfg.SetDiskID(node_disk, node)
1857
      result = rpc.call_blockdev_assemble(node, node_disk, iname, True)
1858
      if not result:
1859
        logger.Error("could not prepare block device %s on node %s"
1860
                     " (is_primary=True, pass=2)" % (inst_disk.iv_name, node))
1861
        disks_ok = False
1862
    device_info.append((instance.primary_node, inst_disk.iv_name, result))
1863

    
1864
  # leave the disks configured for the primary node
1865
  # this is a workaround that would be fixed better by
1866
  # improving the logical/physical id handling
1867
  for disk in instance.disks:
1868
    cfg.SetDiskID(disk, instance.primary_node)
1869

    
1870
  return disks_ok, device_info
1871

    
1872

    
1873
def _StartInstanceDisks(cfg, instance, force):
1874
  """Start the disks of an instance.
1875

1876
  """
1877
  disks_ok, dummy = _AssembleInstanceDisks(instance, cfg,
1878
                                           ignore_secondaries=force)
1879
  if not disks_ok:
1880
    _ShutdownInstanceDisks(instance, cfg)
1881
    if force is not None and not force:
1882
      logger.Error("If the message above refers to a secondary node,"
1883
                   " you can retry the operation using '--force'.")
1884
    raise errors.OpExecError("Disk consistency error")
1885

    
1886

    
1887
class LUDeactivateInstanceDisks(NoHooksLU):
1888
  """Shutdown an instance's disks.
1889

1890
  """
1891
  _OP_REQP = ["instance_name"]
1892

    
1893
  def CheckPrereq(self):
1894
    """Check prerequisites.
1895

1896
    This checks that the instance is in the cluster.
1897

1898
    """
1899
    instance = self.cfg.GetInstanceInfo(
1900
      self.cfg.ExpandInstanceName(self.op.instance_name))
1901
    if instance is None:
1902
      raise errors.OpPrereqError("Instance '%s' not known" %
1903
                                 self.op.instance_name)
1904
    self.instance = instance
1905

    
1906
  def Exec(self, feedback_fn):
1907
    """Deactivate the disks
1908

1909
    """
1910
    instance = self.instance
1911
    ins_l = rpc.call_instance_list([instance.primary_node])
1912
    ins_l = ins_l[instance.primary_node]
1913
    if not type(ins_l) is list:
1914
      raise errors.OpExecError("Can't contact node '%s'" %
1915
                               instance.primary_node)
1916

    
1917
    if self.instance.name in ins_l:
1918
      raise errors.OpExecError("Instance is running, can't shutdown"
1919
                               " block devices.")
1920

    
1921
    _ShutdownInstanceDisks(instance, self.cfg)
1922

    
1923

    
1924
def _ShutdownInstanceDisks(instance, cfg, ignore_primary=False):
1925
  """Shutdown block devices of an instance.
1926

1927
  This does the shutdown on all nodes of the instance.
1928

1929
  If the ignore_primary is false, errors on the primary node are
1930
  ignored.
1931

1932
  """
1933
  result = True
1934
  for disk in instance.disks:
1935
    for node, top_disk in disk.ComputeNodeTree(instance.primary_node):
1936
      cfg.SetDiskID(top_disk, node)
1937
      if not rpc.call_blockdev_shutdown(node, top_disk):
1938
        logger.Error("could not shutdown block device %s on node %s" %
1939
                     (disk.iv_name, node))
1940
        if not ignore_primary or node != instance.primary_node:
1941
          result = False
1942
  return result
1943

    
1944

    
1945
def _CheckNodeFreeMemory(cfg, node, reason, requested):
1946
  """Checks if a node has enough free memory.
1947

1948
  This function check if a given node has the needed amount of free
1949
  memory. In case the node has less memory or we cannot get the
1950
  information from the node, this function raise an OpPrereqError
1951
  exception.
1952

1953
  Args:
1954
    - cfg: a ConfigWriter instance
1955
    - node: the node name
1956
    - reason: string to use in the error message
1957
    - requested: the amount of memory in MiB
1958

1959
  """
1960
  nodeinfo = rpc.call_node_info([node], cfg.GetVGName())
1961
  if not nodeinfo or not isinstance(nodeinfo, dict):
1962
    raise errors.OpPrereqError("Could not contact node %s for resource"
1963
                             " information" % (node,))
1964

    
1965
  free_mem = nodeinfo[node].get('memory_free')
1966
  if not isinstance(free_mem, int):
1967
    raise errors.OpPrereqError("Can't compute free memory on node %s, result"
1968
                             " was '%s'" % (node, free_mem))
1969
  if requested > free_mem:
1970
    raise errors.OpPrereqError("Not enough memory on node %s for %s:"
1971
                             " needed %s MiB, available %s MiB" %
1972
                             (node, reason, requested, free_mem))
1973

    
1974

    
1975
class LUStartupInstance(LogicalUnit):
1976
  """Starts an instance.
1977

1978
  """
1979
  HPATH = "instance-start"
1980
  HTYPE = constants.HTYPE_INSTANCE
1981
  _OP_REQP = ["instance_name", "force"]
1982

    
1983
  def BuildHooksEnv(self):
1984
    """Build hooks env.
1985

1986
    This runs on master, primary and secondary nodes of the instance.
1987

1988
    """
1989
    env = {
1990
      "FORCE": self.op.force,
1991
      }
1992
    env.update(_BuildInstanceHookEnvByObject(self.instance))
1993
    nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
1994
          list(self.instance.secondary_nodes))
1995
    return env, nl, nl
1996

    
1997
  def CheckPrereq(self):
1998
    """Check prerequisites.
1999

2000
    This checks that the instance is in the cluster.
2001

2002
    """
2003
    instance = self.cfg.GetInstanceInfo(
2004
      self.cfg.ExpandInstanceName(self.op.instance_name))
2005
    if instance is None:
2006
      raise errors.OpPrereqError("Instance '%s' not known" %
2007
                                 self.op.instance_name)
2008

    
2009
    # check bridges existance
2010
    _CheckInstanceBridgesExist(instance)
2011

    
2012
    _CheckNodeFreeMemory(self.cfg, instance.primary_node,
2013
                         "starting instance %s" % instance.name,
2014
                         instance.memory)
2015

    
2016
    self.instance = instance
2017
    self.op.instance_name = instance.name
2018

    
2019
  def Exec(self, feedback_fn):
2020
    """Start the instance.
2021

2022
    """
2023
    instance = self.instance
2024
    force = self.op.force
2025
    extra_args = getattr(self.op, "extra_args", "")
2026

    
2027
    self.cfg.MarkInstanceUp(instance.name)
2028

    
2029
    node_current = instance.primary_node
2030

    
2031
    _StartInstanceDisks(self.cfg, instance, force)
2032

    
2033
    if not rpc.call_instance_start(node_current, instance, extra_args):
2034
      _ShutdownInstanceDisks(instance, self.cfg)
2035
      raise errors.OpExecError("Could not start instance")
2036

    
2037

    
2038
class LURebootInstance(LogicalUnit):
2039
  """Reboot an instance.
2040

2041
  """
2042
  HPATH = "instance-reboot"
2043
  HTYPE = constants.HTYPE_INSTANCE
2044
  _OP_REQP = ["instance_name", "ignore_secondaries", "reboot_type"]
2045

    
2046
  def BuildHooksEnv(self):
2047
    """Build hooks env.
2048

2049
    This runs on master, primary and secondary nodes of the instance.
2050

2051
    """
2052
    env = {
2053
      "IGNORE_SECONDARIES": self.op.ignore_secondaries,
2054
      }
2055
    env.update(_BuildInstanceHookEnvByObject(self.instance))
2056
    nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
2057
          list(self.instance.secondary_nodes))
2058
    return env, nl, nl
2059

    
2060
  def CheckPrereq(self):
2061
    """Check prerequisites.
2062

2063
    This checks that the instance is in the cluster.
2064

2065
    """
2066
    instance = self.cfg.GetInstanceInfo(
2067
      self.cfg.ExpandInstanceName(self.op.instance_name))
2068
    if instance is None:
2069
      raise errors.OpPrereqError("Instance '%s' not known" %
2070
                                 self.op.instance_name)
2071

    
2072
    # check bridges existance
2073
    _CheckInstanceBridgesExist(instance)
2074

    
2075
    self.instance = instance
2076
    self.op.instance_name = instance.name
2077

    
2078
  def Exec(self, feedback_fn):
2079
    """Reboot the instance.
2080

2081
    """
2082
    instance = self.instance
2083
    ignore_secondaries = self.op.ignore_secondaries
2084
    reboot_type = self.op.reboot_type
2085
    extra_args = getattr(self.op, "extra_args", "")
2086

    
2087
    node_current = instance.primary_node
2088

    
2089
    if reboot_type not in [constants.INSTANCE_REBOOT_SOFT,
2090
                           constants.INSTANCE_REBOOT_HARD,
2091
                           constants.INSTANCE_REBOOT_FULL]:
2092
      raise errors.ParameterError("reboot type not in [%s, %s, %s]" %
2093
                                  (constants.INSTANCE_REBOOT_SOFT,
2094
                                   constants.INSTANCE_REBOOT_HARD,
2095
                                   constants.INSTANCE_REBOOT_FULL))
2096

    
2097
    if reboot_type in [constants.INSTANCE_REBOOT_SOFT,
2098
                       constants.INSTANCE_REBOOT_HARD]:
2099
      if not rpc.call_instance_reboot(node_current, instance,
2100
                                      reboot_type, extra_args):
2101
        raise errors.OpExecError("Could not reboot instance")
2102
    else:
2103
      if not rpc.call_instance_shutdown(node_current, instance):
2104
        raise errors.OpExecError("could not shutdown instance for full reboot")
2105
      _ShutdownInstanceDisks(instance, self.cfg)
2106
      _StartInstanceDisks(self.cfg, instance, ignore_secondaries)
2107
      if not rpc.call_instance_start(node_current, instance, extra_args):
2108
        _ShutdownInstanceDisks(instance, self.cfg)
2109
        raise errors.OpExecError("Could not start instance for full reboot")
2110

    
2111
    self.cfg.MarkInstanceUp(instance.name)
2112

    
2113

    
2114
class LUShutdownInstance(LogicalUnit):
2115
  """Shutdown an instance.
2116

2117
  """
2118
  HPATH = "instance-stop"
2119
  HTYPE = constants.HTYPE_INSTANCE
2120
  _OP_REQP = ["instance_name"]
2121

    
2122
  def BuildHooksEnv(self):
2123
    """Build hooks env.
2124

2125
    This runs on master, primary and secondary nodes of the instance.
2126

2127
    """
2128
    env = _BuildInstanceHookEnvByObject(self.instance)
2129
    nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
2130
          list(self.instance.secondary_nodes))
2131
    return env, nl, nl
2132

    
2133
  def CheckPrereq(self):
2134
    """Check prerequisites.
2135

2136
    This checks that the instance is in the cluster.
2137

2138
    """
2139
    instance = self.cfg.GetInstanceInfo(
2140
      self.cfg.ExpandInstanceName(self.op.instance_name))
2141
    if instance is None:
2142
      raise errors.OpPrereqError("Instance '%s' not known" %
2143
                                 self.op.instance_name)
2144
    self.instance = instance
2145

    
2146
  def Exec(self, feedback_fn):
2147
    """Shutdown the instance.
2148

2149
    """
2150
    instance = self.instance
2151
    node_current = instance.primary_node
2152
    self.cfg.MarkInstanceDown(instance.name)
2153
    if not rpc.call_instance_shutdown(node_current, instance):
2154
      logger.Error("could not shutdown instance")
2155

    
2156
    _ShutdownInstanceDisks(instance, self.cfg)
2157

    
2158

    
2159
class LUReinstallInstance(LogicalUnit):
2160
  """Reinstall an instance.
2161

2162
  """
2163
  HPATH = "instance-reinstall"
2164
  HTYPE = constants.HTYPE_INSTANCE
2165
  _OP_REQP = ["instance_name"]
2166

    
2167
  def BuildHooksEnv(self):
2168
    """Build hooks env.
2169

2170
    This runs on master, primary and secondary nodes of the instance.
2171

2172
    """
2173
    env = _BuildInstanceHookEnvByObject(self.instance)
2174
    nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
2175
          list(self.instance.secondary_nodes))
2176
    return env, nl, nl
2177

    
2178
  def CheckPrereq(self):
2179
    """Check prerequisites.
2180

2181
    This checks that the instance is in the cluster and is not running.
2182

2183
    """
2184
    instance = self.cfg.GetInstanceInfo(
2185
      self.cfg.ExpandInstanceName(self.op.instance_name))
2186
    if instance is None:
2187
      raise errors.OpPrereqError("Instance '%s' not known" %
2188
                                 self.op.instance_name)
2189
    if instance.disk_template == constants.DT_DISKLESS:
2190
      raise errors.OpPrereqError("Instance '%s' has no disks" %
2191
                                 self.op.instance_name)
2192
    if instance.status != "down":
2193
      raise errors.OpPrereqError("Instance '%s' is marked to be up" %
2194
                                 self.op.instance_name)
2195
    remote_info = rpc.call_instance_info(instance.primary_node, instance.name)
2196
    if remote_info:
2197
      raise errors.OpPrereqError("Instance '%s' is running on the node %s" %
2198
                                 (self.op.instance_name,
2199
                                  instance.primary_node))
2200

    
2201
    self.op.os_type = getattr(self.op, "os_type", None)
2202
    if self.op.os_type is not None:
2203
      # OS verification
2204
      pnode = self.cfg.GetNodeInfo(
2205
        self.cfg.ExpandNodeName(instance.primary_node))
2206
      if pnode is None:
2207
        raise errors.OpPrereqError("Primary node '%s' is unknown" %
2208
                                   self.op.pnode)
2209
      os_obj = rpc.call_os_get(pnode.name, self.op.os_type)
2210
      if not os_obj:
2211
        raise errors.OpPrereqError("OS '%s' not in supported OS list for"
2212
                                   " primary node"  % self.op.os_type)
2213

    
2214
    self.instance = instance
2215

    
2216
  def Exec(self, feedback_fn):
2217
    """Reinstall the instance.
2218

2219
    """
2220
    inst = self.instance
2221

    
2222
    if self.op.os_type is not None:
2223
      feedback_fn("Changing OS to '%s'..." % self.op.os_type)
2224
      inst.os = self.op.os_type
2225
      self.cfg.AddInstance(inst)
2226

    
2227
    _StartInstanceDisks(self.cfg, inst, None)
2228
    try:
2229
      feedback_fn("Running the instance OS create scripts...")
2230
      if not rpc.call_instance_os_add(inst.primary_node, inst, "sda", "sdb"):
2231
        raise errors.OpExecError("Could not install OS for instance %s"
2232
                                 " on node %s" %
2233
                                 (inst.name, inst.primary_node))
2234
    finally:
2235
      _ShutdownInstanceDisks(inst, self.cfg)
2236

    
2237

    
2238
class LURenameInstance(LogicalUnit):
2239
  """Rename an instance.
2240

2241
  """
2242
  HPATH = "instance-rename"
2243
  HTYPE = constants.HTYPE_INSTANCE
2244
  _OP_REQP = ["instance_name", "new_name"]
2245

    
2246
  def BuildHooksEnv(self):
2247
    """Build hooks env.
2248

2249
    This runs on master, primary and secondary nodes of the instance.
2250

2251
    """
2252
    env = _BuildInstanceHookEnvByObject(self.instance)
2253
    env["INSTANCE_NEW_NAME"] = self.op.new_name
2254
    nl = ([self.sstore.GetMasterNode(), self.instance.primary_node] +
2255
          list(self.instance.secondary_nodes))
2256
    return env, nl, nl
2257

    
2258
  def CheckPrereq(self):
2259
    """Check prerequisites.
2260

2261
    This checks that the instance is in the cluster and is not running.
2262

2263
    """
2264
    instance = self.cfg.GetInstanceInfo(
2265
      self.cfg.ExpandInstanceName(self.op.instance_name))
2266
    if instance is None:
2267
      raise errors.OpPrereqError("Instance '%s' not known" %
2268
                                 self.op.instance_name)
2269
    if instance.status != "down":
2270
      raise errors.OpPrereqError("Instance '%s' is marked to be up" %
2271
                                 self.op.instance_name)
2272
    remote_info = rpc.call_instance_info(instance.primary_node, instance.name)
2273
    if remote_info:
2274
      raise errors.OpPrereqError("Instance '%s' is running on the node %s" %
2275
                                 (self.op.instance_name,
2276
                                  instance.primary_node))
2277
    self.instance = instance
2278

    
2279
    # new name verification
2280
    name_info = utils.HostInfo(self.op.new_name)
2281

    
2282
    self.op.new_name = new_name = name_info.name
2283
    instance_list = self.cfg.GetInstanceList()
2284
    if new_name in instance_list:
2285
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
2286
                                 new_name)
2287

    
2288
    if not getattr(self.op, "ignore_ip", False):
2289
      command = ["fping", "-q", name_info.ip]
2290
      result = utils.RunCmd(command)
2291
      if not result.failed:
2292
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
2293
                                   (name_info.ip, new_name))
2294

    
2295

    
2296
  def Exec(self, feedback_fn):
2297
    """Reinstall the instance.
2298

2299
    """
2300
    inst = self.instance
2301
    old_name = inst.name
2302

    
2303
    if inst.disk_template == constants.DT_FILE:
2304
      old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
2305

    
2306
    self.cfg.RenameInstance(inst.name, self.op.new_name)
2307

    
2308
    # re-read the instance from the configuration after rename
2309
    inst = self.cfg.GetInstanceInfo(self.op.new_name)
2310

    
2311
    if inst.disk_template == constants.DT_FILE:
2312
      new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
2313
      result = rpc.call_file_storage_dir_rename(inst.primary_node,
2314
                                                old_file_storage_dir,
2315
                                                new_file_storage_dir)
2316

    
2317
      if not result:
2318
        raise errors.OpExecError("Could not connect to node '%s' to rename"
2319
                                 " directory '%s' to '%s' (but the instance"
2320
                                 " has been renamed in Ganeti)" % (
2321
                                 inst.primary_node, old_file_storage_dir,
2322
                                 new_file_storage_dir))
2323

    
2324
      if not result[0]:
2325
        raise errors.OpExecError("Could not rename directory '%s' to '%s'"
2326
                                 " (but the instance has been renamed in"
2327
                                 " Ganeti)" % (old_file_storage_dir,
2328
                                               new_file_storage_dir))
2329

    
2330
    _StartInstanceDisks(self.cfg, inst, None)
2331
    try:
2332
      if not rpc.call_instance_run_rename(inst.primary_node, inst, old_name,
2333
                                          "sda", "sdb"):
2334
        msg = ("Could run OS rename script for instance %s on node %s (but the"
2335
               " instance has been renamed in Ganeti)" %
2336
               (inst.name, inst.primary_node))
2337
        logger.Error(msg)
2338
    finally:
2339
      _ShutdownInstanceDisks(inst, self.cfg)
2340

    
2341

    
2342
class LURemoveInstance(LogicalUnit):
2343
  """Remove an instance.
2344

2345
  """
2346
  HPATH = "instance-remove"
2347
  HTYPE = constants.HTYPE_INSTANCE
2348
  _OP_REQP = ["instance_name", "ignore_failures"]
2349

    
2350
  def BuildHooksEnv(self):
2351
    """Build hooks env.
2352

2353
    This runs on master, primary and secondary nodes of the instance.
2354

2355
    """
2356
    env = _BuildInstanceHookEnvByObject(self.instance)
2357
    nl = [self.sstore.GetMasterNode()]
2358
    return env, nl, nl
2359

    
2360
  def CheckPrereq(self):
2361
    """Check prerequisites.
2362

2363
    This checks that the instance is in the cluster.
2364

2365
    """
2366
    instance = self.cfg.GetInstanceInfo(
2367
      self.cfg.ExpandInstanceName(self.op.instance_name))
2368
    if instance is None:
2369
      raise errors.OpPrereqError("Instance '%s' not known" %
2370
                                 self.op.instance_name)
2371
    self.instance = instance
2372

    
2373
  def Exec(self, feedback_fn):
2374
    """Remove the instance.
2375

2376
    """
2377
    instance = self.instance
2378
    logger.Info("shutting down instance %s on node %s" %
2379
                (instance.name, instance.primary_node))
2380

    
2381
    if not rpc.call_instance_shutdown(instance.primary_node, instance):
2382
      if self.op.ignore_failures:
2383
        feedback_fn("Warning: can't shutdown instance")
2384
      else:
2385
        raise errors.OpExecError("Could not shutdown instance %s on node %s" %
2386
                                 (instance.name, instance.primary_node))
2387

    
2388
    logger.Info("removing block devices for instance %s" % instance.name)
2389

    
2390
    if not _RemoveDisks(instance, self.cfg):
2391
      if self.op.ignore_failures:
2392
        feedback_fn("Warning: can't remove instance's disks")
2393
      else:
2394
        raise errors.OpExecError("Can't remove instance's disks")
2395

    
2396
    logger.Info("removing instance %s out of cluster config" % instance.name)
2397

    
2398
    self.cfg.RemoveInstance(instance.name)
2399

    
2400

    
2401
class LUQueryInstances(NoHooksLU):
2402
  """Logical unit for querying instances.
2403

2404
  """
2405
  _OP_REQP = ["output_fields", "names"]
2406

    
2407
  def CheckPrereq(self):
2408
    """Check prerequisites.
2409

2410
    This checks that the fields required are valid output fields.
2411

2412
    """
2413
    self.dynamic_fields = frozenset(["oper_state", "oper_ram", "status"])
2414
    _CheckOutputFields(static=["name", "os", "pnode", "snodes",
2415
                               "admin_state", "admin_ram",
2416
                               "disk_template", "ip", "mac", "bridge",
2417
                               "sda_size", "sdb_size", "vcpus", "tags"],
2418
                       dynamic=self.dynamic_fields,
2419
                       selected=self.op.output_fields)
2420

    
2421
    self.wanted = _GetWantedInstances(self, self.op.names)
2422

    
2423
  def Exec(self, feedback_fn):
2424
    """Computes the list of nodes and their attributes.
2425

2426
    """
2427
    instance_names = self.wanted
2428
    instance_list = [self.cfg.GetInstanceInfo(iname) for iname
2429
                     in instance_names]
2430

    
2431
    # begin data gathering
2432

    
2433
    nodes = frozenset([inst.primary_node for inst in instance_list])
2434

    
2435
    bad_nodes = []
2436
    if self.dynamic_fields.intersection(self.op.output_fields):
2437
      live_data = {}
2438
      node_data = rpc.call_all_instances_info(nodes)
2439
      for name in nodes:
2440
        result = node_data[name]
2441
        if result:
2442
          live_data.update(result)
2443
        elif result == False:
2444
          bad_nodes.append(name)
2445
        # else no instance is alive
2446
    else:
2447
      live_data = dict([(name, {}) for name in instance_names])
2448

    
2449
    # end data gathering
2450

    
2451
    output = []
2452
    for instance in instance_list:
2453
      iout = []
2454
      for field in self.op.output_fields:
2455
        if field == "name":
2456
          val = instance.name
2457
        elif field == "os":
2458
          val = instance.os
2459
        elif field == "pnode":
2460
          val = instance.primary_node
2461
        elif field == "snodes":
2462
          val = list(instance.secondary_nodes)
2463
        elif field == "admin_state":
2464
          val = (instance.status != "down")
2465
        elif field == "oper_state":
2466
          if instance.primary_node in bad_nodes:
2467
            val = None
2468
          else:
2469
            val = bool(live_data.get(instance.name))
2470
        elif field == "status":
2471
          if instance.primary_node in bad_nodes:
2472
            val = "ERROR_nodedown"
2473
          else:
2474
            running = bool(live_data.get(instance.name))
2475
            if running:
2476
              if instance.status != "down":
2477
                val = "running"
2478
              else:
2479
                val = "ERROR_up"
2480
            else:
2481
              if instance.status != "down":
2482
                val = "ERROR_down"
2483
              else:
2484
                val = "ADMIN_down"
2485
        elif field == "admin_ram":
2486
          val = instance.memory
2487
        elif field == "oper_ram":
2488
          if instance.primary_node in bad_nodes:
2489
            val = None
2490
          elif instance.name in live_data:
2491
            val = live_data[instance.name].get("memory", "?")
2492
          else:
2493
            val = "-"
2494
        elif field == "disk_template":
2495
          val = instance.disk_template
2496
        elif field == "ip":
2497
          val = instance.nics[0].ip
2498
        elif field == "bridge":
2499
          val = instance.nics[0].bridge
2500
        elif field == "mac":
2501
          val = instance.nics[0].mac
2502
        elif field == "sda_size" or field == "sdb_size":
2503
          disk = instance.FindDisk(field[:3])
2504
          if disk is None:
2505
            val = None
2506
          else:
2507
            val = disk.size
2508
        elif field == "vcpus":
2509
          val = instance.vcpus
2510
        elif field == "tags":
2511
          val = list(instance.GetTags())
2512
        else:
2513
          raise errors.ParameterError(field)
2514
        iout.append(val)
2515
      output.append(iout)
2516

    
2517
    return output
2518

    
2519

    
2520
class LUFailoverInstance(LogicalUnit):
2521
  """Failover an instance.
2522

2523
  """
2524
  HPATH = "instance-failover"
2525
  HTYPE = constants.HTYPE_INSTANCE
2526
  _OP_REQP = ["instance_name", "ignore_consistency"]
2527

    
2528
  def BuildHooksEnv(self):
2529
    """Build hooks env.
2530

2531
    This runs on master, primary and secondary nodes of the instance.
2532

2533
    """
2534
    env = {
2535
      "IGNORE_CONSISTENCY": self.op.ignore_consistency,
2536
      }
2537
    env.update(_BuildInstanceHookEnvByObject(self.instance))
2538
    nl = [self.sstore.GetMasterNode()] + list(self.instance.secondary_nodes)
2539
    return env, nl, nl
2540

    
2541
  def CheckPrereq(self):
2542
    """Check prerequisites.
2543

2544
    This checks that the instance is in the cluster.
2545

2546
    """
2547
    instance = self.cfg.GetInstanceInfo(
2548
      self.cfg.ExpandInstanceName(self.op.instance_name))
2549
    if instance is None:
2550
      raise errors.OpPrereqError("Instance '%s' not known" %
2551
                                 self.op.instance_name)
2552

    
2553
    if instance.disk_template not in constants.DTS_NET_MIRROR:
2554
      raise errors.OpPrereqError("Instance's disk layout is not"
2555
                                 " network mirrored, cannot failover.")
2556

    
2557
    secondary_nodes = instance.secondary_nodes
2558
    if not secondary_nodes:
2559
      raise errors.ProgrammerError("no secondary node but using "
2560
                                   "a mirrored disk template")
2561

    
2562
    target_node = secondary_nodes[0]
2563
    # check memory requirements on the secondary node
2564
    _CheckNodeFreeMemory(self.cfg, target_node, "failing over instance %s" %
2565
                         instance.name, instance.memory)
2566

    
2567
    # check bridge existance
2568
    brlist = [nic.bridge for nic in instance.nics]
2569
    if not rpc.call_bridges_exist(target_node, brlist):
2570
      raise errors.OpPrereqError("One or more target bridges %s does not"
2571
                                 " exist on destination node '%s'" %
2572
                                 (brlist, target_node))
2573

    
2574
    self.instance = instance
2575

    
2576
  def Exec(self, feedback_fn):
2577
    """Failover an instance.
2578

2579
    The failover is done by shutting it down on its present node and
2580
    starting it on the secondary.
2581

2582
    """
2583
    instance = self.instance
2584

    
2585
    source_node = instance.primary_node
2586
    target_node = instance.secondary_nodes[0]
2587

    
2588
    feedback_fn("* checking disk consistency between source and target")
2589
    for dev in instance.disks:
2590
      # for drbd, these are drbd over lvm
2591
      if not _CheckDiskConsistency(self.cfg, dev, target_node, False):
2592
        if instance.status == "up" and not self.op.ignore_consistency:
2593
          raise errors.OpExecError("Disk %s is degraded on target node,"
2594
                                   " aborting failover." % dev.iv_name)
2595

    
2596
    feedback_fn("* shutting down instance on source node")
2597
    logger.Info("Shutting down instance %s on node %s" %
2598
                (instance.name, source_node))
2599

    
2600
    if not rpc.call_instance_shutdown(source_node, instance):
2601
      if self.op.ignore_consistency:
2602
        logger.Error("Could not shutdown instance %s on node %s. Proceeding"
2603
                     " anyway. Please make sure node %s is down"  %
2604
                     (instance.name, source_node, source_node))
2605
      else:
2606
        raise errors.OpExecError("Could not shutdown instance %s on node %s" %
2607
                                 (instance.name, source_node))
2608

    
2609
    feedback_fn("* deactivating the instance's disks on source node")
2610
    if not _ShutdownInstanceDisks(instance, self.cfg, ignore_primary=True):
2611
      raise errors.OpExecError("Can't shut down the instance's disks.")
2612

    
2613
    instance.primary_node = target_node
2614
    # distribute new instance config to the other nodes
2615
    self.cfg.Update(instance)
2616

    
2617
    # Only start the instance if it's marked as up
2618
    if instance.status == "up":
2619
      feedback_fn("* activating the instance's disks on target node")
2620
      logger.Info("Starting instance %s on node %s" %
2621
                  (instance.name, target_node))
2622

    
2623
      disks_ok, dummy = _AssembleInstanceDisks(instance, self.cfg,
2624
                                               ignore_secondaries=True)
2625
      if not disks_ok:
2626
        _ShutdownInstanceDisks(instance, self.cfg)
2627
        raise errors.OpExecError("Can't activate the instance's disks")
2628

    
2629
      feedback_fn("* starting the instance on the target node")
2630
      if not rpc.call_instance_start(target_node, instance, None):
2631
        _ShutdownInstanceDisks(instance, self.cfg)
2632
        raise errors.OpExecError("Could not start instance %s on node %s." %
2633
                                 (instance.name, target_node))
2634

    
2635

    
2636
def _CreateBlockDevOnPrimary(cfg, node, instance, device, info):
2637
  """Create a tree of block devices on the primary node.
2638

2639
  This always creates all devices.
2640

2641
  """
2642
  if device.children:
2643
    for child in device.children:
2644
      if not _CreateBlockDevOnPrimary(cfg, node, instance, child, info):
2645
        return False
2646

    
2647
  cfg.SetDiskID(device, node)
2648
  new_id = rpc.call_blockdev_create(node, device, device.size,
2649
                                    instance.name, True, info)
2650
  if not new_id:
2651
    return False
2652
  if device.physical_id is None:
2653
    device.physical_id = new_id
2654
  return True
2655

    
2656

    
2657
def _CreateBlockDevOnSecondary(cfg, node, instance, device, force, info):
2658
  """Create a tree of block devices on a secondary node.
2659

2660
  If this device type has to be created on secondaries, create it and
2661
  all its children.
2662

2663
  If not, just recurse to children keeping the same 'force' value.
2664

2665
  """
2666
  if device.CreateOnSecondary():
2667
    force = True
2668
  if device.children:
2669
    for child in device.children:
2670
      if not _CreateBlockDevOnSecondary(cfg, node, instance,
2671
                                        child, force, info):
2672
        return False
2673

    
2674
  if not force:
2675
    return True
2676
  cfg.SetDiskID(device, node)
2677
  new_id = rpc.call_blockdev_create(node, device, device.size,
2678
                                    instance.name, False, info)
2679
  if not new_id:
2680
    return False
2681
  if device.physical_id is None:
2682
    device.physical_id = new_id
2683
  return True
2684

    
2685

    
2686
def _GenerateUniqueNames(cfg, exts):
2687
  """Generate a suitable LV name.
2688

2689
  This will generate a logical volume name for the given instance.
2690

2691
  """
2692
  results = []
2693
  for val in exts:
2694
    new_id = cfg.GenerateUniqueID()
2695
    results.append("%s%s" % (new_id, val))
2696
  return results
2697

    
2698

    
2699
def _GenerateDRBD8Branch(cfg, primary, secondary, size, names, iv_name):
2700
  """Generate a drbd8 device complete with its children.
2701

2702
  """
2703
  port = cfg.AllocatePort()
2704
  vgname = cfg.GetVGName()
2705
  dev_data = objects.Disk(dev_type=constants.LD_LV, size=size,
2706
                          logical_id=(vgname, names[0]))
2707
  dev_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
2708
                          logical_id=(vgname, names[1]))
2709
  drbd_dev = objects.Disk(dev_type=constants.LD_DRBD8, size=size,
2710
                          logical_id = (primary, secondary, port),
2711
                          children = [dev_data, dev_meta],
2712
                          iv_name=iv_name)
2713
  return drbd_dev
2714

    
2715

    
2716
def _GenerateDiskTemplate(cfg, template_name,
2717
                          instance_name, primary_node,
2718
                          secondary_nodes, disk_sz, swap_sz,
2719
                          file_storage_dir, file_driver):
2720
  """Generate the entire disk layout for a given template type.
2721

2722
  """
2723
  #TODO: compute space requirements
2724

    
2725
  vgname = cfg.GetVGName()
2726
  if template_name == constants.DT_DISKLESS:
2727
    disks = []
2728
  elif template_name == constants.DT_PLAIN:
2729
    if len(secondary_nodes) != 0:
2730
      raise errors.ProgrammerError("Wrong template configuration")
2731

    
2732
    names = _GenerateUniqueNames(cfg, [".sda", ".sdb"])
2733
    sda_dev = objects.Disk(dev_type=constants.LD_LV, size=disk_sz,
2734
                           logical_id=(vgname, names[0]),
2735
                           iv_name = "sda")
2736
    sdb_dev = objects.Disk(dev_type=constants.LD_LV, size=swap_sz,
2737
                           logical_id=(vgname, names[1]),
2738
                           iv_name = "sdb")
2739
    disks = [sda_dev, sdb_dev]
2740
  elif template_name == constants.DT_DRBD8:
2741
    if len(secondary_nodes) != 1:
2742
      raise errors.ProgrammerError("Wrong template configuration")
2743
    remote_node = secondary_nodes[0]
2744
    names = _GenerateUniqueNames(cfg, [".sda_data", ".sda_meta",
2745
                                       ".sdb_data", ".sdb_meta"])
2746
    drbd_sda_dev = _GenerateDRBD8Branch(cfg, primary_node, remote_node,
2747
                                         disk_sz, names[0:2], "sda")
2748
    drbd_sdb_dev = _GenerateDRBD8Branch(cfg, primary_node, remote_node,
2749
                                         swap_sz, names[2:4], "sdb")
2750
    disks = [drbd_sda_dev, drbd_sdb_dev]
2751
  elif template_name == constants.DT_FILE:
2752
    if len(secondary_nodes) != 0:
2753
      raise errors.ProgrammerError("Wrong template configuration")
2754

    
2755
    file_sda_dev = objects.Disk(dev_type=constants.LD_FILE, size=disk_sz,
2756
                                iv_name="sda", logical_id=(file_driver,
2757
                                "%s/sda" % file_storage_dir))
2758
    file_sdb_dev = objects.Disk(dev_type=constants.LD_FILE, size=swap_sz,
2759
                                iv_name="sdb", logical_id=(file_driver,
2760
                                "%s/sdb" % file_storage_dir))
2761
    disks = [file_sda_dev, file_sdb_dev]
2762
  else:
2763
    raise errors.ProgrammerError("Invalid disk template '%s'" % template_name)
2764
  return disks
2765

    
2766

    
2767
def _GetInstanceInfoText(instance):
2768
  """Compute that text that should be added to the disk's metadata.
2769

2770
  """
2771
  return "originstname+%s" % instance.name
2772

    
2773

    
2774
def _CreateDisks(cfg, instance):
2775
  """Create all disks for an instance.
2776

2777
  This abstracts away some work from AddInstance.
2778

2779
  Args:
2780
    instance: the instance object
2781

2782
  Returns:
2783
    True or False showing the success of the creation process
2784

2785
  """
2786
  info = _GetInstanceInfoText(instance)
2787

    
2788
  if instance.disk_template == constants.DT_FILE:
2789
    file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
2790
    result = rpc.call_file_storage_dir_create(instance.primary_node,
2791
                                              file_storage_dir)
2792

    
2793
    if not result:
2794
      logger.Error("Could not connect to node '%s'" % instance.primary_node)
2795
      return False
2796

    
2797
    if not result[0]:
2798
      logger.Error("failed to create directory '%s'" % file_storage_dir)
2799
      return False
2800

    
2801
  for device in instance.disks:
2802
    logger.Info("creating volume %s for instance %s" %
2803
                (device.iv_name, instance.name))
2804
    #HARDCODE
2805
    for secondary_node in instance.secondary_nodes:
2806
      if not _CreateBlockDevOnSecondary(cfg, secondary_node, instance,
2807
                                        device, False, info):
2808
        logger.Error("failed to create volume %s (%s) on secondary node %s!" %
2809
                     (device.iv_name, device, secondary_node))
2810
        return False
2811
    #HARDCODE
2812
    if not _CreateBlockDevOnPrimary(cfg, instance.primary_node,
2813
                                    instance, device, info):
2814
      logger.Error("failed to create volume %s on primary!" %
2815
                   device.iv_name)
2816
      return False
2817

    
2818
  return True
2819

    
2820

    
2821
def _RemoveDisks(instance, cfg):
2822
  """Remove all disks for an instance.
2823

2824
  This abstracts away some work from `AddInstance()` and
2825
  `RemoveInstance()`. Note that in case some of the devices couldn't
2826
  be removed, the removal will continue with the other ones (compare
2827
  with `_CreateDisks()`).
2828

2829
  Args:
2830
    instance: the instance object
2831

2832
  Returns:
2833
    True or False showing the success of the removal proces
2834

2835
  """
2836
  logger.Info("removing block devices for instance %s" % instance.name)
2837

    
2838
  result = True
2839
  for device in instance.disks:
2840
    for node, disk in device.ComputeNodeTree(instance.primary_node):
2841
      cfg.SetDiskID(disk, node)
2842
      if not rpc.call_blockdev_remove(node, disk):
2843
        logger.Error("could not remove block device %s on node %s,"
2844
                     " continuing anyway" %
2845
                     (device.iv_name, node))
2846
        result = False
2847

    
2848
  if instance.disk_template == constants.DT_FILE:
2849
    file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
2850
    if not rpc.call_file_storage_dir_remove(instance.primary_node,
2851
                                            file_storage_dir):
2852
      logger.Error("could not remove directory '%s'" % file_storage_dir)
2853
      result = False
2854

    
2855
  return result
2856

    
2857

    
2858
def _ComputeDiskSize(disk_template, disk_size, swap_size):
2859
  """Compute disk size requirements in the volume group
2860

2861
  This is currently hard-coded for the two-drive layout.
2862

2863
  """
2864
  # Required free disk space as a function of disk and swap space
2865
  req_size_dict = {
2866
    constants.DT_DISKLESS: None,
2867
    constants.DT_PLAIN: disk_size + swap_size,
2868
    # 256 MB are added for drbd metadata, 128MB for each drbd device
2869
    constants.DT_DRBD8: disk_size + swap_size + 256,
2870
    constants.DT_FILE: None,
2871
  }
2872

    
2873
  if disk_template not in req_size_dict:
2874
    raise errors.ProgrammerError("Disk template '%s' size requirement"
2875
                                 " is unknown" %  disk_template)
2876

    
2877
  return req_size_dict[disk_template]
2878

    
2879

    
2880
class LUCreateInstance(LogicalUnit):
2881
  """Create an instance.
2882

2883
  """
2884
  HPATH = "instance-add"
2885
  HTYPE = constants.HTYPE_INSTANCE
2886
  _OP_REQP = ["instance_name", "mem_size", "disk_size",
2887
              "disk_template", "swap_size", "mode", "start", "vcpus",
2888
              "wait_for_sync", "ip_check", "mac"]
2889

    
2890
  def _RunAllocator(self):
2891
    """Run the allocator based on input opcode.
2892

2893
    """
2894
    disks = [{"size": self.op.disk_size, "mode": "w"},
2895
             {"size": self.op.swap_size, "mode": "w"}]
2896
    nics = [{"mac": self.op.mac, "ip": getattr(self.op, "ip", None),
2897
             "bridge": self.op.bridge}]
2898
    ial = IAllocator(self.cfg, self.sstore,
2899
                     mode=constants.IALLOCATOR_MODE_ALLOC,
2900
                     name=self.op.instance_name,
2901
                     disk_template=self.op.disk_template,
2902
                     tags=[],
2903
                     os=self.op.os_type,
2904
                     vcpus=self.op.vcpus,
2905
                     mem_size=self.op.mem_size,
2906
                     disks=disks,
2907
                     nics=nics,
2908
                     )
2909

    
2910
    ial.Run(self.op.iallocator)
2911

    
2912
    if not ial.success:
2913
      raise errors.OpPrereqError("Can't compute nodes using"
2914
                                 " iallocator '%s': %s" % (self.op.iallocator,
2915
                                                           ial.info))
2916
    if len(ial.nodes) != ial.required_nodes:
2917
      raise errors.OpPrereqError("iallocator '%s' returned invalid number"
2918
                                 " of nodes (%s), required %s" %
2919
                                 (len(ial.nodes), ial.required_nodes))
2920
    self.op.pnode = ial.nodes[0]
2921
    logger.ToStdout("Selected nodes for the instance: %s" %
2922
                    (", ".join(ial.nodes),))
2923
    logger.Info("Selected nodes for instance %s via iallocator %s: %s" %
2924
                (self.op.instance_name, self.op.iallocator, ial.nodes))
2925
    if ial.required_nodes == 2:
2926
      self.op.snode = ial.nodes[1]
2927

    
2928
  def BuildHooksEnv(self):
2929
    """Build hooks env.
2930

2931
    This runs on master, primary and secondary nodes of the instance.
2932

2933
    """
2934
    env = {
2935
      "INSTANCE_DISK_TEMPLATE": self.op.disk_template,
2936
      "INSTANCE_DISK_SIZE": self.op.disk_size,
2937
      "INSTANCE_SWAP_SIZE": self.op.swap_size,
2938
      "INSTANCE_ADD_MODE": self.op.mode,
2939
      }
2940
    if self.op.mode == constants.INSTANCE_IMPORT:
2941
      env["INSTANCE_SRC_NODE"] = self.op.src_node
2942
      env["INSTANCE_SRC_PATH"] = self.op.src_path
2943
      env["INSTANCE_SRC_IMAGE"] = self.src_image
2944

    
2945
    env.update(_BuildInstanceHookEnv(name=self.op.instance_name,
2946
      primary_node=self.op.pnode,
2947
      secondary_nodes=self.secondaries,
2948
      status=self.instance_status,
2949
      os_type=self.op.os_type,
2950
      memory=self.op.mem_size,
2951
      vcpus=self.op.vcpus,
2952
      nics=[(self.inst_ip, self.op.bridge, self.op.mac)],
2953
    ))
2954

    
2955
    nl = ([self.sstore.GetMasterNode(), self.op.pnode] +
2956
          self.secondaries)
2957
    return env, nl, nl
2958

    
2959

    
2960
  def CheckPrereq(self):
2961
    """Check prerequisites.
2962

2963
    """
2964
    # set optional parameters to none if they don't exist
2965
    for attr in ["kernel_path", "initrd_path", "hvm_boot_order", "pnode",
2966
                 "iallocator", "hvm_acpi", "hvm_pae", "hvm_cdrom_image_path",
2967
                 "vnc_bind_address"]:
2968
      if not hasattr(self.op, attr):
2969
        setattr(self.op, attr, None)
2970

    
2971
    if self.op.mode not in (constants.INSTANCE_CREATE,
2972
                            constants.INSTANCE_IMPORT):
2973
      raise errors.OpPrereqError("Invalid instance creation mode '%s'" %
2974
                                 self.op.mode)
2975

    
2976
    if (not self.cfg.GetVGName() and
2977
        self.op.disk_template not in constants.DTS_NOT_LVM):
2978
      raise errors.OpPrereqError("Cluster does not support lvm-based"
2979
                                 " instances")
2980

    
2981
    if self.op.mode == constants.INSTANCE_IMPORT:
2982
      src_node = getattr(self.op, "src_node", None)
2983
      src_path = getattr(self.op, "src_path", None)
2984
      if src_node is None or src_path is None:
2985
        raise errors.OpPrereqError("Importing an instance requires source"
2986
                                   " node and path options")
2987
      src_node_full = self.cfg.ExpandNodeName(src_node)
2988
      if src_node_full is None:
2989
        raise errors.OpPrereqError("Unknown source node '%s'" % src_node)
2990
      self.op.src_node = src_node = src_node_full
2991

    
2992
      if not os.path.isabs(src_path):
2993
        raise errors.OpPrereqError("The source path must be absolute")
2994

    
2995
      export_info = rpc.call_export_info(src_node, src_path)
2996

    
2997
      if not export_info:
2998
        raise errors.OpPrereqError("No export found in dir %s" % src_path)
2999

    
3000
      if not export_info.has_section(constants.INISECT_EXP):
3001
        raise errors.ProgrammerError("Corrupted export config")
3002

    
3003
      ei_version = export_info.get(constants.INISECT_EXP, 'version')
3004
      if (int(ei_version) != constants.EXPORT_VERSION):
3005
        raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
3006
                                   (ei_version, constants.EXPORT_VERSION))
3007

    
3008
      if int(export_info.get(constants.INISECT_INS, 'disk_count')) > 1:
3009
        raise errors.OpPrereqError("Can't import instance with more than"
3010
                                   " one data disk")
3011

    
3012
      # FIXME: are the old os-es, disk sizes, etc. useful?
3013
      self.op.os_type = export_info.get(constants.INISECT_EXP, 'os')
3014
      diskimage = os.path.join(src_path, export_info.get(constants.INISECT_INS,
3015
                                                         'disk0_dump'))
3016
      self.src_image = diskimage
3017
    else: # INSTANCE_CREATE
3018
      if getattr(self.op, "os_type", None) is None:
3019
        raise errors.OpPrereqError("No guest OS specified")
3020

    
3021
    #### instance parameters check
3022

    
3023
    # disk template and mirror node verification
3024
    if self.op.disk_template not in constants.DISK_TEMPLATES:
3025
      raise errors.OpPrereqError("Invalid disk template name")
3026

    
3027
    # instance name verification
3028
    hostname1 = utils.HostInfo(self.op.instance_name)
3029

    
3030
    self.op.instance_name = instance_name = hostname1.name
3031
    instance_list = self.cfg.GetInstanceList()
3032
    if instance_name in instance_list:
3033
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
3034
                                 instance_name)
3035

    
3036
    # ip validity checks
3037
    ip = getattr(self.op, "ip", None)
3038
    if ip is None or ip.lower() == "none":
3039
      inst_ip = None
3040
    elif ip.lower() == "auto":
3041
      inst_ip = hostname1.ip
3042
    else:
3043
      if not utils.IsValidIP(ip):
3044
        raise errors.OpPrereqError("given IP address '%s' doesn't look"
3045
                                   " like a valid IP" % ip)
3046
      inst_ip = ip
3047
    self.inst_ip = self.op.ip = inst_ip
3048

    
3049
    if self.op.start and not self.op.ip_check:
3050
      raise errors.OpPrereqError("Cannot ignore IP address conflicts when"
3051
                                 " adding an instance in start mode")
3052

    
3053
    if self.op.ip_check:
3054
      if utils.TcpPing(hostname1.ip, constants.DEFAULT_NODED_PORT):
3055
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
3056
                                   (hostname1.ip, instance_name))
3057

    
3058
    # MAC address verification
3059
    if self.op.mac != "auto":
3060
      if not utils.IsValidMac(self.op.mac.lower()):
3061
        raise errors.OpPrereqError("invalid MAC address specified: %s" %
3062
                                   self.op.mac)
3063

    
3064
    # bridge verification
3065
    bridge = getattr(self.op, "bridge", None)
3066
    if bridge is None:
3067
      self.op.bridge = self.cfg.GetDefBridge()
3068
    else:
3069
      self.op.bridge = bridge
3070

    
3071
    # boot order verification
3072
    if self.op.hvm_boot_order is not None:
3073
      if len(self.op.hvm_boot_order.strip("acdn")) != 0:
3074
        raise errors.OpPrereqError("invalid boot order specified,"
3075
                                   " must be one or more of [acdn]")
3076
    # file storage checks
3077
    if (self.op.file_driver and
3078
        not self.op.file_driver in constants.FILE_DRIVER):
3079
      raise errors.OpPrereqError("Invalid file driver name '%s'" %
3080
                                 self.op.file_driver)
3081

    
3082
    if self.op.file_storage_dir and os.path.isabs(self.op.file_storage_dir):
3083
      raise errors.OpPrereqError("File storage directory not a relative"
3084
                                 " path")
3085
    #### allocator run
3086

    
3087
    if [self.op.iallocator, self.op.pnode].count(None) != 1:
3088
      raise errors.OpPrereqError("One and only one of iallocator and primary"
3089
                                 " node must be given")
3090

    
3091
    if self.op.iallocator is not None:
3092
      self._RunAllocator()
3093

    
3094
    #### node related checks
3095

    
3096
    # check primary node
3097
    pnode = self.cfg.GetNodeInfo(self.cfg.ExpandNodeName(self.op.pnode))
3098
    if pnode is None:
3099
      raise errors.OpPrereqError("Primary node '%s' is unknown" %
3100
                                 self.op.pnode)
3101
    self.op.pnode = pnode.name
3102
    self.pnode = pnode
3103
    self.secondaries = []
3104

    
3105
    # mirror node verification
3106
    if self.op.disk_template in constants.DTS_NET_MIRROR:
3107
      if getattr(self.op, "snode", None) is None:
3108
        raise errors.OpPrereqError("The networked disk templates need"
3109
                                   " a mirror node")
3110

    
3111
      snode_name = self.cfg.ExpandNodeName(self.op.snode)
3112
      if snode_name is None:
3113
        raise errors.OpPrereqError("Unknown secondary node '%s'" %
3114
                                   self.op.snode)
3115
      elif snode_name == pnode.name:
3116
        raise errors.OpPrereqError("The secondary node cannot be"
3117
                                   " the primary node.")
3118
      self.secondaries.append(snode_name)
3119

    
3120
    req_size = _ComputeDiskSize(self.op.disk_template,
3121
                                self.op.disk_size, self.op.swap_size)
3122

    
3123
    # Check lv size requirements
3124
    if req_size is not None:
3125
      nodenames = [pnode.name] + self.secondaries
3126
      nodeinfo = rpc.call_node_info(nodenames, self.cfg.GetVGName())
3127
      for node in nodenames:
3128
        info = nodeinfo.get(node, None)
3129
        if not info:
3130
          raise errors.OpPrereqError("Cannot get current information"
3131
                                     " from node '%s'" % node)
3132
        vg_free = info.get('vg_free', None)
3133
        if not isinstance(vg_free, int):
3134
          raise errors.OpPrereqError("Can't compute free disk space on"
3135
                                     " node %s" % node)
3136
        if req_size > info['vg_free']:
3137
          raise errors.OpPrereqError("Not enough disk space on target node %s."
3138
                                     " %d MB available, %d MB required" %
3139
                                     (node, info['vg_free'], req_size))
3140

    
3141
    # os verification
3142
    os_obj = rpc.call_os_get(pnode.name, self.op.os_type)
3143
    if not os_obj:
3144
      raise errors.OpPrereqError("OS '%s' not in supported os list for"
3145
                                 " primary node"  % self.op.os_type)
3146

    
3147
    if self.op.kernel_path == constants.VALUE_NONE:
3148
      raise errors.OpPrereqError("Can't set instance kernel to none")
3149

    
3150

    
3151
    # bridge check on primary node
3152
    if not rpc.call_bridges_exist(self.pnode.name, [self.op.bridge]):
3153
      raise errors.OpPrereqError("target bridge '%s' does not exist on"
3154
                                 " destination node '%s'" %
3155
                                 (self.op.bridge, pnode.name))
3156

    
3157
    # memory check on primary node
3158
    if self.op.start:
3159
      _CheckNodeFreeMemory(self.cfg, self.pnode.name,
3160
                           "creating instance %s" % self.op.instance_name,
3161
                           self.op.mem_size)
3162

    
3163
    # hvm_cdrom_image_path verification
3164
    if self.op.hvm_cdrom_image_path is not None:
3165
      if not os.path.isabs(self.op.hvm_cdrom_image_path):
3166
        raise errors.OpPrereqError("The path to the HVM CDROM image must"
3167
                                   " be an absolute path or None, not %s" %
3168
                                   self.op.hvm_cdrom_image_path)
3169
      if not os.path.isfile(self.op.hvm_cdrom_image_path):
3170
        raise errors.OpPrereqError("The HVM CDROM image must either be a"
3171
                                   " regular file or a symlink pointing to"
3172
                                   " an existing regular file, not %s" %
3173
                                   self.op.hvm_cdrom_image_path)
3174

    
3175
    # vnc_bind_address verification
3176
    if self.op.vnc_bind_address is not None:
3177
      if not utils.IsValidIP(self.op.vnc_bind_address):
3178
        raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
3179
                                   " like a valid IP address" %
3180
                                   self.op.vnc_bind_address)
3181

    
3182
    if self.op.start:
3183
      self.instance_status = 'up'
3184
    else:
3185
      self.instance_status = 'down'
3186

    
3187
  def Exec(self, feedback_fn):
3188
    """Create and add the instance to the cluster.
3189

3190
    """
3191
    instance = self.op.instance_name
3192
    pnode_name = self.pnode.name
3193

    
3194
    if self.op.mac == "auto":
3195
      mac_address = self.cfg.GenerateMAC()
3196
    else:
3197
      mac_address = self.op.mac
3198

    
3199
    nic = objects.NIC(bridge=self.op.bridge, mac=mac_address)
3200
    if self.inst_ip is not None:
3201
      nic.ip = self.inst_ip
3202

    
3203
    ht_kind = self.sstore.GetHypervisorType()
3204
    if ht_kind in constants.HTS_REQ_PORT:
3205
      network_port = self.cfg.AllocatePort()
3206
    else:
3207
      network_port = None
3208

    
3209
    if self.op.vnc_bind_address is None:
3210
      self.op.vnc_bind_address = constants.VNC_DEFAULT_BIND_ADDRESS
3211

    
3212
    # this is needed because os.path.join does not accept None arguments
3213
    if self.op.file_storage_dir is None:
3214
      string_file_storage_dir = ""
3215
    else:
3216
      string_file_storage_dir = self.op.file_storage_dir
3217

    
3218
    # build the full file storage dir path
3219
    file_storage_dir = os.path.normpath(os.path.join(
3220
                                        self.sstore.GetFileStorageDir(),
3221
                                        string_file_storage_dir, instance))
3222

    
3223

    
3224
    disks = _GenerateDiskTemplate(self.cfg,
3225
                                  self.op.disk_template,
3226
                                  instance, pnode_name,
3227
                                  self.secondaries, self.op.disk_size,
3228
                                  self.op.swap_size,
3229
                                  file_storage_dir,
3230
                                  self.op.file_driver)
3231

    
3232
    iobj = objects.Instance(name=instance, os=self.op.os_type,
3233
                            primary_node=pnode_name,
3234
                            memory=self.op.mem_size,
3235
                            vcpus=self.op.vcpus,
3236
                            nics=[nic], disks=disks,
3237
                            disk_template=self.op.disk_template,
3238
                            status=self.instance_status,
3239
                            network_port=network_port,
3240
                            kernel_path=self.op.kernel_path,
3241
                            initrd_path=self.op.initrd_path,
3242
                            hvm_boot_order=self.op.hvm_boot_order,
3243
                            hvm_acpi=self.op.hvm_acpi,
3244
                            hvm_pae=self.op.hvm_pae,
3245
                            hvm_cdrom_image_path=self.op.hvm_cdrom_image_path,
3246
                            vnc_bind_address=self.op.vnc_bind_address,
3247
                            )
3248

    
3249
    feedback_fn("* creating instance disks...")
3250
    if not _CreateDisks(self.cfg, iobj):
3251
      _RemoveDisks(iobj, self.cfg)
3252
      raise errors.OpExecError("Device creation failed, reverting...")
3253

    
3254
    feedback_fn("adding instance %s to cluster config" % instance)
3255

    
3256
    self.cfg.AddInstance(iobj)
3257

    
3258
    if self.op.wait_for_sync:
3259
      disk_abort = not _WaitForSync(self.cfg, iobj, self.proc)
3260
    elif iobj.disk_template in constants.DTS_NET_MIRROR:
3261
      # make sure the disks are not degraded (still sync-ing is ok)
3262
      time.sleep(15)
3263
      feedback_fn("* checking mirrors status")
3264
      disk_abort = not _WaitForSync(self.cfg, iobj, self.proc, oneshot=True)
3265
    else:
3266
      disk_abort = False
3267

    
3268
    if disk_abort:
3269
      _RemoveDisks(iobj, self.cfg)
3270
      self.cfg.RemoveInstance(iobj.name)
3271
      raise errors.OpExecError("There are some degraded disks for"
3272
                               " this instance")
3273

    
3274
    feedback_fn("creating os for instance %s on node %s" %
3275
                (instance, pnode_name))
3276

    
3277
    if iobj.disk_template != constants.DT_DISKLESS:
3278
      if self.op.mode == constants.INSTANCE_CREATE:
3279
        feedback_fn("* running the instance OS create scripts...")
3280
        if not rpc.call_instance_os_add(pnode_name, iobj, "sda", "sdb"):
3281
          raise errors.OpExecError("could not add os for instance %s"
3282
                                   " on node %s" %
3283
                                   (instance, pnode_name))
3284

    
3285
      elif self.op.mode == constants.INSTANCE_IMPORT:
3286
        feedback_fn("* running the instance OS import scripts...")
3287
        src_node = self.op.src_node
3288
        src_image = self.src_image
3289
        if not rpc.call_instance_os_import(pnode_name, iobj, "sda", "sdb",
3290
                                                src_node, src_image):
3291
          raise errors.OpExecError("Could not import os for instance"
3292
                                   " %s on node %s" %
3293
                                   (instance, pnode_name))
3294
      else:
3295
        # also checked in the prereq part
3296
        raise errors.ProgrammerError("Unknown OS initialization mode '%s'"
3297
                                     % self.op.mode)
3298

    
3299
    if self.op.start:
3300
      logger.Info("starting instance %s on node %s" % (instance, pnode_name))
3301
      feedback_fn("* starting instance...")
3302
      if not rpc.call_instance_start(pnode_name, iobj, None):
3303
        raise errors.OpExecError("Could not start instance")
3304

    
3305

    
3306
class LUConnectConsole(NoHooksLU):
3307
  """Connect to an instance's console.
3308

3309
  This is somewhat special in that it returns the command line that
3310
  you need to run on the master node in order to connect to the
3311
  console.
3312

3313
  """
3314
  _OP_REQP = ["instance_name"]
3315

    
3316
  def CheckPrereq(self):
3317
    """Check prerequisites.
3318

3319
    This checks that the instance is in the cluster.
3320

3321
    """
3322
    instance = self.cfg.GetInstanceInfo(
3323
      self.cfg.ExpandInstanceName(self.op.instance_name))
3324
    if instance is None:
3325
      raise errors.OpPrereqError("Instance '%s' not known" %
3326
                                 self.op.instance_name)
3327
    self.instance = instance
3328

    
3329
  def Exec(self, feedback_fn):
3330
    """Connect to the console of an instance
3331

3332
    """
3333
    instance = self.instance
3334
    node = instance.primary_node
3335

    
3336
    node_insts = rpc.call_instance_list([node])[node]
3337
    if node_insts is False:
3338
      raise errors.OpExecError("Can't connect to node %s." % node)
3339

    
3340
    if instance.name not in node_insts:
3341
      raise errors.OpExecError("Instance %s is not running." % instance.name)
3342

    
3343
    logger.Debug("connecting to console of %s on %s" % (instance.name, node))
3344

    
3345
    hyper = hypervisor.GetHypervisor()
3346
    console_cmd = hyper.GetShellCommandForConsole(instance)
3347

    
3348
    # build ssh cmdline
3349
    return self.ssh.BuildCmd(node, "root", console_cmd, batch=True, tty=True)
3350

    
3351

    
3352
class LUReplaceDisks(LogicalUnit):
3353
  """Replace the disks of an instance.
3354

3355
  """
3356
  HPATH = "mirrors-replace"
3357
  HTYPE = constants.HTYPE_INSTANCE
3358
  _OP_REQP = ["instance_name", "mode", "disks"]
3359

    
3360
  def _RunAllocator(self):
3361
    """Compute a new secondary node using an IAllocator.
3362

3363
    """
3364
    ial = IAllocator(self.cfg, self.sstore,
3365
                     mode=constants.IALLOCATOR_MODE_RELOC,
3366
                     name=self.op.instance_name,
3367
                     relocate_from=[self.sec_node])
3368

    
3369
    ial.Run(self.op.iallocator)
3370

    
3371
    if not ial.success:
3372
      raise errors.OpPrereqError("Can't compute nodes using"
3373
                                 " iallocator '%s': %s" % (self.op.iallocator,
3374
                                                           ial.info))
3375
    if len(ial.nodes) != ial.required_nodes:
3376
      raise errors.OpPrereqError("iallocator '%s' returned invalid number"
3377
                                 " of nodes (%s), required %s" %
3378
                                 (len(ial.nodes), ial.required_nodes))
3379
    self.op.remote_node = ial.nodes[0]
3380
    logger.ToStdout("Selected new secondary for the instance: %s" %
3381
                    self.op.remote_node)
3382

    
3383
  def BuildHooksEnv(self):
3384
    """Build hooks env.
3385

3386
    This runs on the master, the primary and all the secondaries.
3387

3388
    """
3389
    env = {
3390
      "MODE": self.op.mode,
3391
      "NEW_SECONDARY": self.op.remote_node,
3392
      "OLD_SECONDARY": self.instance.secondary_nodes[0],
3393
      }
3394
    env.update(_BuildInstanceHookEnvByObject(self.instance))
3395
    nl = [
3396
      self.sstore.GetMasterNode(),
3397
      self.instance.primary_node,
3398
      ]
3399
    if self.op.remote_node is not None:
3400
      nl.append(self.op.remote_node)
3401
    return env, nl, nl
3402

    
3403
  def CheckPrereq(self):
3404
    """Check prerequisites.
3405

3406
    This checks that the instance is in the cluster.
3407

3408
    """
3409
    if not hasattr(self.op, "remote_node"):
3410
      self.op.remote_node = None
3411

    
3412
    instance = self.cfg.GetInstanceInfo(
3413
      self.cfg.ExpandInstanceName(self.op.instance_name))
3414
    if instance is None:
3415
      raise errors.OpPrereqError("Instance '%s' not known" %
3416
                                 self.op.instance_name)
3417
    self.instance = instance
3418
    self.op.instance_name = instance.name
3419

    
3420
    if instance.disk_template not in constants.DTS_NET_MIRROR:
3421
      raise errors.OpPrereqError("Instance's disk layout is not"
3422
                                 " network mirrored.")
3423

    
3424
    if len(instance.secondary_nodes) != 1:
3425
      raise errors.OpPrereqError("The instance has a strange layout,"
3426
                                 " expected one secondary but found %d" %
3427
                                 len(instance.secondary_nodes))
3428

    
3429
    self.sec_node = instance.secondary_nodes[0]
3430

    
3431
    ia_name = getattr(self.op, "iallocator", None)
3432
    if ia_name is not None:
3433
      if self.op.remote_node is not None:
3434
        raise errors.OpPrereqError("Give either the iallocator or the new"
3435
                                   " secondary, not both")
3436
      self.op.remote_node = self._RunAllocator()
3437

    
3438
    remote_node = self.op.remote_node
3439
    if remote_node is not None:
3440
      remote_node = self.cfg.ExpandNodeName(remote_node)
3441
      if remote_node is None:
3442
        raise errors.OpPrereqError("Node '%s' not known" %
3443
                                   self.op.remote_node)
3444
      self.remote_node_info = self.cfg.GetNodeInfo(remote_node)
3445
    else:
3446
      self.remote_node_info = None
3447
    if remote_node == instance.primary_node:
3448
      raise errors.OpPrereqError("The specified node is the primary node of"
3449
                                 " the instance.")
3450
    elif remote_node == self.sec_node:
3451
      if self.op.mode == constants.REPLACE_DISK_SEC:
3452
        # this is for DRBD8, where we can't execute the same mode of
3453
        # replacement as for drbd7 (no different port allocated)
3454
        raise errors.OpPrereqError("Same secondary given, cannot execute"
3455
                                   " replacement")
3456
    if instance.disk_template == constants.DT_DRBD8:
3457
      if (self.op.mode == constants.REPLACE_DISK_ALL and
3458
          remote_node is not None):
3459
        # switch to replace secondary mode
3460
        self.op.mode = constants.REPLACE_DISK_SEC
3461

    
3462
      if self.op.mode == constants.REPLACE_DISK_ALL:
3463
        raise errors.OpPrereqError("Template 'drbd' only allows primary or"
3464
                                   " secondary disk replacement, not"
3465
                                   " both at once")
3466
      elif self.op.mode == constants.REPLACE_DISK_PRI:
3467
        if remote_node is not None:
3468
          raise errors.OpPrereqError("Template 'drbd' does not allow changing"
3469
                                     " the secondary while doing a primary"
3470
                                     " node disk replacement")
3471
        self.tgt_node = instance.primary_node
3472
        self.oth_node = instance.secondary_nodes[0]
3473
      elif self.op.mode == constants.REPLACE_DISK_SEC:
3474
        self.new_node = remote_node # this can be None, in which case
3475
                                    # we don't change the secondary
3476
        self.tgt_node = instance.secondary_nodes[0]
3477
        self.oth_node = instance.primary_node
3478
      else:
3479
        raise errors.ProgrammerError("Unhandled disk replace mode")
3480

    
3481
    for name in self.op.disks:
3482
      if instance.FindDisk(name) is None:
3483
        raise errors.OpPrereqError("Disk '%s' not found for instance '%s'" %
3484
                                   (name, instance.name))
3485
    self.op.remote_node = remote_node
3486

    
3487
  def _ExecD8DiskOnly(self, feedback_fn):
3488
    """Replace a disk on the primary or secondary for dbrd8.
3489

3490
    The algorithm for replace is quite complicated:
3491
      - for each disk to be replaced:
3492
        - create new LVs on the target node with unique names
3493
        - detach old LVs from the drbd device
3494
        - rename old LVs to name_replaced.<time_t>
3495
        - rename new LVs to old LVs
3496
        - attach the new LVs (with the old names now) to the drbd device
3497
      - wait for sync across all devices
3498
      - for each modified disk:
3499
        - remove old LVs (which have the name name_replaces.<time_t>)
3500

3501
    Failures are not very well handled.
3502

3503
    """
3504
    steps_total = 6
3505
    warning, info = (self.proc.LogWarning, self.proc.LogInfo)
3506
    instance = self.instance
3507
    iv_names = {}
3508
    vgname = self.cfg.GetVGName()
3509
    # start of work
3510
    cfg = self.cfg
3511
    tgt_node = self.tgt_node
3512
    oth_node = self.oth_node
3513

    
3514
    # Step: check device activation
3515
    self.proc.LogStep(1, steps_total, "check device existence")
3516
    info("checking volume groups")
3517
    my_vg = cfg.GetVGName()
3518
    results = rpc.call_vg_list([oth_node, tgt_node])
3519
    if not results:
3520
      raise errors.OpExecError("Can't list volume groups on the nodes")
3521
    for node in oth_node, tgt_node:
3522
      res = results.get(node, False)
3523
      if not res or my_vg not in res:
3524
        raise errors.OpExecError("Volume group '%s' not found on %s" %
3525
                                 (my_vg, node))
3526
    for dev in instance.disks:
3527
      if not dev.iv_name in self.op.disks:
3528
        continue
3529
      for node in tgt_node, oth_node:
3530
        info("checking %s on %s" % (dev.iv_name, node))
3531
        cfg.SetDiskID(dev, node)
3532
        if not rpc.call_blockdev_find(node, dev):
3533
          raise errors.OpExecError("Can't find device %s on node %s" %
3534
                                   (dev.iv_name, node))
3535

    
3536
    # Step: check other node consistency
3537
    self.proc.LogStep(2, steps_total, "check peer consistency")
3538
    for dev in instance.disks:
3539
      if not dev.iv_name in self.op.disks:
3540
        continue
3541
      info("checking %s consistency on %s" % (dev.iv_name, oth_node))
3542
      if not _CheckDiskConsistency(self.cfg, dev, oth_node,
3543
                                   oth_node==instance.primary_node):
3544
        raise errors.OpExecError("Peer node (%s) has degraded storage, unsafe"
3545
                                 " to replace disks on this node (%s)" %
3546
                                 (oth_node, tgt_node))
3547

    
3548
    # Step: create new storage
3549
    self.proc.LogStep(3, steps_total, "allocate new storage")
3550
    for dev in instance.disks:
3551
      if not dev.iv_name in self.op.disks:
3552
        continue
3553
      size = dev.size
3554
      cfg.SetDiskID(dev, tgt_node)
3555
      lv_names = [".%s_%s" % (dev.iv_name, suf) for suf in ["data", "meta"]]
3556
      names = _GenerateUniqueNames(cfg, lv_names)
3557
      lv_data = objects.Disk(dev_type=constants.LD_LV, size=size,
3558
                             logical_id=(vgname, names[0]))
3559
      lv_meta = objects.Disk(dev_type=constants.LD_LV, size=128,
3560
                             logical_id=(vgname, names[1]))
3561
      new_lvs = [lv_data, lv_meta]
3562
      old_lvs = dev.children
3563
      iv_names[dev.iv_name] = (dev, old_lvs, new_lvs)
3564
      info("creating new local storage on %s for %s" %
3565
           (tgt_node, dev.iv_name))
3566
      # since we *always* want to create this LV, we use the
3567
      # _Create...OnPrimary (which forces the creation), even if we
3568
      # are talking about the secondary node
3569
      for new_lv in new_lvs:
3570
        if not _CreateBlockDevOnPrimary(cfg, tgt_node, instance, new_lv,
3571
                                        _GetInstanceInfoText(instance)):
3572
          raise errors.OpExecError("Failed to create new LV named '%s' on"
3573
                                   " node '%s'" %
3574
                                   (new_lv.logical_id[1], tgt_node))
3575

    
3576
    # Step: for each lv, detach+rename*2+attach
3577
    self.proc.LogStep(4, steps_total, "change drbd configuration")
3578
    for dev, old_lvs, new_lvs in iv_names.itervalues():
3579
      info("detaching %s drbd from local storage" % dev.iv_name)
3580
      if not rpc.call_blockdev_removechildren(tgt_node, dev, old_lvs):
3581
        raise errors.OpExecError("Can't detach drbd from local storage on node"
3582
                                 " %s for device %s" % (tgt_node, dev.iv_name))
3583
      #dev.children = []
3584
      #cfg.Update(instance)
3585

    
3586
      # ok, we created the new LVs, so now we know we have the needed
3587
      # storage; as such, we proceed on the target node to rename
3588
      # old_lv to _old, and new_lv to old_lv; note that we rename LVs
3589
      # using the assumption that logical_id == physical_id (which in
3590
      # turn is the unique_id on that node)
3591

    
3592
      # FIXME(iustin): use a better name for the replaced LVs
3593
      temp_suffix = int(time.time())
3594
      ren_fn = lambda d, suff: (d.physical_id[0],
3595
                                d.physical_id[1] + "_replaced-%s" % suff)
3596
      # build the rename list based on what LVs exist on the node
3597
      rlist = []
3598
      for to_ren in old_lvs:
3599
        find_res = rpc.call_blockdev_find(tgt_node, to_ren)
3600
        if find_res is not None: # device exists
3601
          rlist.append((to_ren, ren_fn(to_ren, temp_suffix)))
3602

    
3603
      info("renaming the old LVs on the target node")
3604
      if not rpc.call_blockdev_rename(tgt_node, rlist):
3605
        raise errors.OpExecError("Can't rename old LVs on node %s" % tgt_node)
3606
      # now we rename the new LVs to the old LVs
3607
      info("renaming the new LVs on the target node")
3608
      rlist = [(new, old.physical_id) for old, new in zip(old_lvs, new_lvs)]
3609
      if not rpc.call_blockdev_rename(tgt_node, rlist):
3610
        raise errors.OpExecError("Can't rename new LVs on node %s" % tgt_node)
3611

    
3612
      for old, new in zip(old_lvs, new_lvs):
3613
        new.logical_id = old.logical_id
3614
        cfg.SetDiskID(new, tgt_node)
3615

    
3616
      for disk in old_lvs:
3617
        disk.logical_id = ren_fn(disk, temp_suffix)
3618
        cfg.SetDiskID(disk, tgt_node)
3619

    
3620
      # now that the new lvs have the old name, we can add them to the device
3621
      info("adding new mirror component on %s" % tgt_node)
3622
      if not rpc.call_blockdev_addchildren(tgt_node, dev, new_lvs):
3623
        for new_lv in new_lvs:
3624
          if not rpc.call_blockdev_remove(tgt_node, new_lv):
3625
            warning("Can't rollback device %s", hint="manually cleanup unused"
3626
                    " logical volumes")
3627
        raise errors.OpExecError("Can't add local storage to drbd")
3628

    
3629
      dev.children = new_lvs
3630
      cfg.Update(instance)
3631

    
3632
    # Step: wait for sync
3633

    
3634
    # this can fail as the old devices are degraded and _WaitForSync
3635
    # does a combined result over all disks, so we don't check its
3636
    # return value
3637
    self.proc.LogStep(5, steps_total, "sync devices")
3638
    _WaitForSync(cfg, instance, self.proc, unlock=True)
3639

    
3640
    # so check manually all the devices
3641
    for name, (dev, old_lvs, new_lvs) in iv_names.iteritems():
3642
      cfg.SetDiskID(dev, instance.primary_node)
3643
      is_degr = rpc.call_blockdev_find(instance.primary_node, dev)[5]
3644
      if is_degr:
3645
        raise errors.OpExecError("DRBD device %s is degraded!" % name)
3646

    
3647
    # Step: remove old storage
3648
    self.proc.LogStep(6, steps_total, "removing old storage")
3649
    for name, (dev, old_lvs, new_lvs) in iv_names.iteritems():
3650
      info("remove logical volumes for %s" % name)
3651
      for lv in old_lvs:
3652
        cfg.SetDiskID(lv, tgt_node)
3653
        if not rpc.call_blockdev_remove(tgt_node, lv):
3654
          warning("Can't remove old LV", hint="manually remove unused LVs")
3655
          continue
3656

    
3657
  def _ExecD8Secondary(self, feedback_fn):
3658
    """Replace the secondary node for drbd8.
3659

3660
    The algorithm for replace is quite complicated:
3661
      - for all disks of the instance:
3662
        - create new LVs on the new node with same names
3663
        - shutdown the drbd device on the old secondary
3664
        - disconnect the drbd network on the primary
3665
        - create the drbd device on the new secondary
3666
        - network attach the drbd on the primary, using an artifice:
3667
          the drbd code for Attach() will connect to the network if it
3668
          finds a device which is connected to the good local disks but
3669
          not network enabled
3670
      - wait for sync across all devices
3671
      - remove all disks from the old secondary
3672

3673
    Failures are not very well handled.
3674

3675
    """
3676
    steps_total = 6
3677
    warning, info = (self.proc.LogWarning, self.proc.LogInfo)
3678
    instance = self.instance
3679
    iv_names = {}
3680
    vgname = self.cfg.GetVGName()
3681
    # start of work
3682
    cfg = self.cfg
3683
    old_node = self.tgt_node
3684
    new_node = self.new_node
3685
    pri_node = instance.primary_node
3686

    
3687
    # Step: check device activation
3688
    self.proc.LogStep(1, steps_total, "check device existence")
3689
    info("checking volume groups")
3690
    my_vg = cfg.GetVGName()
3691
    results = rpc.call_vg_list([pri_node, new_node])
3692
    if not results:
3693
      raise errors.OpExecError("Can't list volume groups on the nodes")
3694
    for node in pri_node, new_node:
3695
      res = results.get(node, False)
3696
      if not res or my_vg not in res:
3697
        raise errors.OpExecError("Volume group '%s' not found on %s" %
3698
                                 (my_vg, node))
3699
    for dev in instance.disks:
3700
      if not dev.iv_name in self.op.disks:
3701
        continue
3702
      info("checking %s on %s" % (dev.iv_name, pri_node))
3703
      cfg.SetDiskID(dev, pri_node)
3704
      if not rpc.call_blockdev_find(pri_node, dev):
3705
        raise errors.OpExecError("Can't find device %s on node %s" %
3706
                                 (dev.iv_name, pri_node))
3707

    
3708
    # Step: check other node consistency
3709
    self.proc.LogStep(2, steps_total, "check peer consistency")
3710
    for dev in instance.disks:
3711
      if not dev.iv_name in self.op.disks:
3712
        continue
3713
      info("checking %s consistency on %s" % (dev.iv_name, pri_node))
3714
      if not _CheckDiskConsistency(self.cfg, dev, pri_node, True, ldisk=True):
3715
        raise errors.OpExecError("Primary node (%s) has degraded storage,"
3716
                                 " unsafe to replace the secondary" %
3717
                                 pri_node)
3718

    
3719
    # Step: create new storage
3720
    self.proc.LogStep(3, steps_total, "allocate new storage")
3721
    for dev in instance.disks:
3722
      size = dev.size
3723
      info("adding new local storage on %s for %s" % (new_node, dev.iv_name))
3724
      # since we *always* want to create this LV, we use the
3725
      # _Create...OnPrimary (which forces the creation), even if we
3726
      # are talking about the secondary node
3727
      for new_lv in dev.children:
3728
        if not _CreateBlockDevOnPrimary(cfg, new_node, instance, new_lv,
3729
                                        _GetInstanceInfoText(instance)):
3730
          raise errors.OpExecError("Failed to create new LV named '%s' on"
3731
                                   " node '%s'" %
3732
                                   (new_lv.logical_id[1], new_node))
3733

    
3734
      iv_names[dev.iv_name] = (dev, dev.children)
3735

    
3736
    self.proc.LogStep(4, steps_total, "changing drbd configuration")
3737
    for dev in instance.disks:
3738
      size = dev.size
3739
      info("activating a new drbd on %s for %s" % (new_node, dev.iv_name))
3740
      # create new devices on new_node
3741
      new_drbd = objects.Disk(dev_type=constants.LD_DRBD8,
3742
                              logical_id=(pri_node, new_node,
3743
                                          dev.logical_id[2]),
3744
                              children=dev.children)
3745
      if not _CreateBlockDevOnSecondary(cfg, new_node, instance,
3746
                                        new_drbd, False,
3747
                                      _GetInstanceInfoText(instance)):
3748
        raise errors.OpExecError("Failed to create new DRBD on"
3749
                                 " node '%s'" % new_node)
3750

    
3751
    for dev in instance.disks:
3752
      # we have new devices, shutdown the drbd on the old secondary
3753
      info("shutting down drbd for %s on old node" % dev.iv_name)
3754
      cfg.SetDiskID(dev, old_node)
3755
      if not rpc.call_blockdev_shutdown(old_node, dev):
3756
        warning("Failed to shutdown drbd for %s on old node" % dev.iv_name,
3757
                hint="Please cleanup this device manually as soon as possible")
3758

    
3759
    info("detaching primary drbds from the network (=> standalone)")
3760
    done = 0
3761
    for dev in instance.disks:
3762
      cfg.SetDiskID(dev, pri_node)
3763
      # set the physical (unique in bdev terms) id to None, meaning
3764
      # detach from network
3765
      dev.physical_id = (None,) * len(dev.physical_id)
3766
      # and 'find' the device, which will 'fix' it to match the
3767
      # standalone state
3768
      if rpc.call_blockdev_find(pri_node, dev):
3769
        done += 1
3770
      else:
3771
        warning("Failed to detach drbd %s from network, unusual case" %
3772
                dev.iv_name)
3773

    
3774
    if not done:
3775
      # no detaches succeeded (very unlikely)
3776
      raise errors.OpExecError("Can't detach at least one DRBD from old node")
3777

    
3778
    # if we managed to detach at least one, we update all the disks of
3779
    # the instance to point to the new secondary
3780
    info("updating instance configuration")
3781
    for dev in instance.disks:
3782
      dev.logical_id = (pri_node, new_node) + dev.logical_id[2:]
3783
      cfg.SetDiskID(dev, pri_node)
3784
    cfg.Update(instance)
3785

    
3786
    # and now perform the drbd attach
3787
    info("attaching primary drbds to new secondary (standalone => connected)")
3788
    failures = []
3789
    for dev in instance.disks:
3790
      info("attaching primary drbd for %s to new secondary node" % dev.iv_name)
3791
      # since the attach is smart, it's enough to 'find' the device,
3792
      # it will automatically activate the network, if the physical_id
3793
      # is correct
3794
      cfg.SetDiskID(dev, pri_node)
3795
      if not rpc.call_blockdev_find(pri_node, dev):
3796
        warning("can't attach drbd %s to new secondary!" % dev.iv_name,
3797
                "please do a gnt-instance info to see the status of disks")
3798

    
3799
    # this can fail as the old devices are degraded and _WaitForSync
3800
    # does a combined result over all disks, so we don't check its
3801
    # return value
3802
    self.proc.LogStep(5, steps_total, "sync devices")
3803
    _WaitForSync(cfg, instance, self.proc, unlock=True)
3804

    
3805
    # so check manually all the devices
3806
    for name, (dev, old_lvs) in iv_names.iteritems():
3807
      cfg.SetDiskID(dev, pri_node)
3808
      is_degr = rpc.call_blockdev_find(pri_node, dev)[5]
3809
      if is_degr:
3810
        raise errors.OpExecError("DRBD device %s is degraded!" % name)
3811

    
3812
    self.proc.LogStep(6, steps_total, "removing old storage")
3813
    for name, (dev, old_lvs) in iv_names.iteritems():
3814
      info("remove logical volumes for %s" % name)
3815
      for lv in old_lvs:
3816
        cfg.SetDiskID(lv, old_node)
3817
        if not rpc.call_blockdev_remove(old_node, lv):
3818
          warning("Can't remove LV on old secondary",
3819
                  hint="Cleanup stale volumes by hand")
3820

    
3821
  def Exec(self, feedback_fn):
3822
    """Execute disk replacement.
3823

3824
    This dispatches the disk replacement to the appropriate handler.
3825

3826
    """
3827
    instance = self.instance
3828

    
3829
    # Activate the instance disks if we're replacing them on a down instance
3830
    if instance.status == "down":
3831
      op = opcodes.OpActivateInstanceDisks(instance_name=instance.name)
3832
      self.proc.ChainOpCode(op)
3833

    
3834
    if instance.disk_template == constants.DT_DRBD8:
3835
      if self.op.remote_node is None:
3836
        fn = self._ExecD8DiskOnly
3837
      else:
3838
        fn = self._ExecD8Secondary
3839
    else:
3840
      raise errors.ProgrammerError("Unhandled disk replacement case")
3841

    
3842
    ret = fn(feedback_fn)
3843

    
3844
    # Deactivate the instance disks if we're replacing them on a down instance
3845
    if instance.status == "down":
3846
      op = opcodes.OpDeactivateInstanceDisks(instance_name=instance.name)
3847
      self.proc.ChainOpCode(op)
3848

    
3849
    return ret
3850

    
3851

    
3852
class LUGrowDisk(LogicalUnit):
3853
  """Grow a disk of an instance.
3854

3855
  """
3856
  HPATH = "disk-grow"
3857
  HTYPE = constants.HTYPE_INSTANCE
3858
  _OP_REQP = ["instance_name", "disk", "amount"]
3859

    
3860
  def BuildHooksEnv(self):
3861
    """Build hooks env.
3862

3863
    This runs on the master, the primary and all the secondaries.
3864

3865
    """
3866
    env = {
3867
      "DISK": self.op.disk,
3868
      "AMOUNT": self.op.amount,
3869
      }
3870
    env.update(_BuildInstanceHookEnvByObject(self.instance))
3871
    nl = [
3872
      self.sstore.GetMasterNode(),
3873
      self.instance.primary_node,
3874
      ]
3875
    return env, nl, nl
3876

    
3877
  def CheckPrereq(self):
3878
    """Check prerequisites.
3879

3880
    This checks that the instance is in the cluster.
3881

3882
    """
3883
    instance = self.cfg.GetInstanceInfo(
3884
      self.cfg.ExpandInstanceName(self.op.instance_name))
3885
    if instance is None:
3886
      raise errors.OpPrereqError("Instance '%s' not known" %
3887
                                 self.op.instance_name)
3888
    self.instance = instance
3889
    self.op.instance_name = instance.name
3890

    
3891
    if instance.disk_template not in (constants.DT_PLAIN, constants.DT_DRBD8):
3892
      raise errors.OpPrereqError("Instance's disk layout does not support"
3893
                                 " growing.")
3894

    
3895
    if instance.FindDisk(self.op.disk) is None:
3896
      raise errors.OpPrereqError("Disk '%s' not found for instance '%s'" %
3897
                                 (self.op.disk, instance.name))
3898

    
3899
    nodenames = [instance.primary_node] + list(instance.secondary_nodes)
3900
    nodeinfo = rpc.call_node_info(nodenames, self.cfg.GetVGName())
3901
    for node in nodenames:
3902
      info = nodeinfo.get(node, None)
3903
      if not info:
3904
        raise errors.OpPrereqError("Cannot get current information"
3905
                                   " from node '%s'" % node)
3906
      vg_free = info.get('vg_free', None)
3907
      if not isinstance(vg_free, int):
3908
        raise errors.OpPrereqError("Can't compute free disk space on"
3909
                                   " node %s" % node)
3910
      if self.op.amount > info['vg_free']:
3911
        raise errors.OpPrereqError("Not enough disk space on target node %s:"
3912
                                   " %d MiB available, %d MiB required" %
3913
                                   (node, info['vg_free'], self.op.amount))
3914

    
3915
  def Exec(self, feedback_fn):
3916
    """Execute disk grow.
3917

3918
    """
3919
    instance = self.instance
3920
    disk = instance.FindDisk(self.op.disk)
3921
    for node in (instance.secondary_nodes + (instance.primary_node,)):
3922
      self.cfg.SetDiskID(disk, node)
3923
      result = rpc.call_blockdev_grow(node, disk, self.op.amount)
3924
      if not result or not isinstance(result, tuple) or len(result) != 2:
3925
        raise errors.OpExecError("grow request failed to node %s" % node)
3926
      elif not result[0]:
3927
        raise errors.OpExecError("grow request failed to node %s: %s" %
3928
                                 (node, result[1]))
3929
    disk.RecordGrow(self.op.amount)
3930
    self.cfg.Update(instance)
3931
    return
3932

    
3933

    
3934
class LUQueryInstanceData(NoHooksLU):
3935
  """Query runtime instance data.
3936

3937
  """
3938
  _OP_REQP = ["instances"]
3939

    
3940
  def CheckPrereq(self):
3941
    """Check prerequisites.
3942

3943
    This only checks the optional instance list against the existing names.
3944

3945
    """
3946
    if not isinstance(self.op.instances, list):
3947
      raise errors.OpPrereqError("Invalid argument type 'instances'")
3948
    if self.op.instances:
3949
      self.wanted_instances = []
3950
      names = self.op.instances
3951
      for name in names:
3952
        instance = self.cfg.GetInstanceInfo(self.cfg.ExpandInstanceName(name))
3953
        if instance is None:
3954
          raise errors.OpPrereqError("No such instance name '%s'" % name)
3955
        self.wanted_instances.append(instance)
3956
    else:
3957
      self.wanted_instances = [self.cfg.GetInstanceInfo(name) for name
3958
                               in self.cfg.GetInstanceList()]
3959
    return
3960

    
3961

    
3962
  def _ComputeDiskStatus(self, instance, snode, dev):
3963
    """Compute block device status.
3964

3965
    """
3966
    self.cfg.SetDiskID(dev, instance.primary_node)
3967
    dev_pstatus = rpc.call_blockdev_find(instance.primary_node, dev)
3968
    if dev.dev_type in constants.LDS_DRBD:
3969
      # we change the snode then (otherwise we use the one passed in)
3970
      if dev.logical_id[0] == instance.primary_node:
3971
        snode = dev.logical_id[1]
3972
      else:
3973
        snode = dev.logical_id[0]
3974

    
3975
    if snode:
3976
      self.cfg.SetDiskID(dev, snode)
3977
      dev_sstatus = rpc.call_blockdev_find(snode, dev)
3978
    else:
3979
      dev_sstatus = None
3980

    
3981
    if dev.children:
3982
      dev_children = [self._ComputeDiskStatus(instance, snode, child)
3983
                      for child in dev.children]
3984
    else:
3985
      dev_children = []
3986

    
3987
    data = {
3988
      "iv_name": dev.iv_name,
3989
      "dev_type": dev.dev_type,
3990
      "logical_id": dev.logical_id,
3991
      "physical_id": dev.physical_id,
3992
      "pstatus": dev_pstatus,
3993
      "sstatus": dev_sstatus,
3994
      "children": dev_children,
3995
      }
3996

    
3997
    return data
3998

    
3999
  def Exec(self, feedback_fn):
4000
    """Gather and return data"""
4001
    result = {}
4002
    for instance in self.wanted_instances:
4003
      remote_info = rpc.call_instance_info(instance.primary_node,
4004
                                                instance.name)
4005
      if remote_info and "state" in remote_info:
4006
        remote_state = "up"
4007
      else:
4008
        remote_state = "down"
4009
      if instance.status == "down":
4010
        config_state = "down"
4011
      else:
4012
        config_state = "up"
4013

    
4014
      disks = [self._ComputeDiskStatus(instance, None, device)
4015
               for device in instance.disks]
4016

    
4017
      idict = {
4018
        "name": instance.name,
4019
        "config_state": config_state,
4020
        "run_state": remote_state,
4021
        "pnode": instance.primary_node,
4022
        "snodes": instance.secondary_nodes,
4023
        "os": instance.os,
4024
        "memory": instance.memory,
4025
        "nics": [(nic.mac, nic.ip, nic.bridge) for nic in instance.nics],
4026
        "disks": disks,
4027
        "vcpus": instance.vcpus,
4028
        }
4029

    
4030
      htkind = self.sstore.GetHypervisorType()
4031
      if htkind == constants.HT_XEN_PVM30:
4032
        idict["kernel_path"] = instance.kernel_path
4033
        idict["initrd_path"] = instance.initrd_path
4034

    
4035
      if htkind == constants.HT_XEN_HVM31:
4036
        idict["hvm_boot_order"] = instance.hvm_boot_order
4037
        idict["hvm_acpi"] = instance.hvm_acpi
4038
        idict["hvm_pae"] = instance.hvm_pae
4039
        idict["hvm_cdrom_image_path"] = instance.hvm_cdrom_image_path
4040

    
4041
      if htkind in constants.HTS_REQ_PORT:
4042
        idict["vnc_bind_address"] = instance.vnc_bind_address
4043
        idict["network_port"] = instance.network_port
4044

    
4045
      result[instance.name] = idict
4046

    
4047
    return result
4048

    
4049

    
4050
class LUSetInstanceParams(LogicalUnit):
4051
  """Modifies an instances's parameters.
4052

4053
  """
4054
  HPATH = "instance-modify"
4055
  HTYPE = constants.HTYPE_INSTANCE
4056
  _OP_REQP = ["instance_name"]
4057

    
4058
  def BuildHooksEnv(self):
4059
    """Build hooks env.
4060

4061
    This runs on the master, primary and secondaries.
4062

4063
    """
4064
    args = dict()
4065
    if self.mem:
4066
      args['memory'] = self.mem
4067
    if self.vcpus:
4068
      args['vcpus'] = self.vcpus
4069
    if self.do_ip or self.do_bridge or self.mac:
4070
      if self.do_ip:
4071
        ip = self.ip
4072
      else:
4073
        ip = self.instance.nics[0].ip
4074
      if self.bridge:
4075
        bridge = self.bridge
4076
      else:
4077
        bridge = self.instance.nics[0].bridge
4078
      if self.mac:
4079
        mac = self.mac
4080
      else:
4081
        mac = self.instance.nics[0].mac
4082
      args['nics'] = [(ip, bridge, mac)]
4083
    env = _BuildInstanceHookEnvByObject(self.instance, override=args)
4084
    nl = [self.sstore.GetMasterNode(),
4085
          self.instance.primary_node] + list(self.instance.secondary_nodes)
4086
    return env, nl, nl
4087

    
4088
  def CheckPrereq(self):
4089
    """Check prerequisites.
4090

4091
    This only checks the instance list against the existing names.
4092

4093
    """
4094
    self.mem = getattr(self.op, "mem", None)
4095
    self.vcpus = getattr(self.op, "vcpus", None)
4096
    self.ip = getattr(self.op, "ip", None)
4097
    self.mac = getattr(self.op, "mac", None)
4098
    self.bridge = getattr(self.op, "bridge", None)
4099
    self.kernel_path = getattr(self.op, "kernel_path", None)
4100
    self.initrd_path = getattr(self.op, "initrd_path", None)
4101
    self.hvm_boot_order = getattr(self.op, "hvm_boot_order", None)
4102
    self.hvm_acpi = getattr(self.op, "hvm_acpi", None)
4103
    self.hvm_pae = getattr(self.op, "hvm_pae", None)
4104
    self.hvm_cdrom_image_path = getattr(self.op, "hvm_cdrom_image_path", None)
4105
    self.vnc_bind_address = getattr(self.op, "vnc_bind_address", None)
4106
    all_parms = [self.mem, self.vcpus, self.ip, self.bridge, self.mac,
4107
                 self.kernel_path, self.initrd_path, self.hvm_boot_order,
4108
                 self.hvm_acpi, self.hvm_pae, self.hvm_cdrom_image_path,
4109
                 self.vnc_bind_address]
4110
    if all_parms.count(None) == len(all_parms):
4111
      raise errors.OpPrereqError("No changes submitted")
4112
    if self.mem is not None:
4113
      try:
4114
        self.mem = int(self.mem)
4115
      except ValueError, err:
4116
        raise errors.OpPrereqError("Invalid memory size: %s" % str(err))
4117
    if self.vcpus is not None:
4118
      try:
4119
        self.vcpus = int(self.vcpus)
4120
      except ValueError, err:
4121
        raise errors.OpPrereqError("Invalid vcpus number: %s" % str(err))
4122
    if self.ip is not None:
4123
      self.do_ip = True
4124
      if self.ip.lower() == "none":
4125
        self.ip = None
4126
      else:
4127
        if not utils.IsValidIP(self.ip):
4128
          raise errors.OpPrereqError("Invalid IP address '%s'." % self.ip)
4129
    else:
4130
      self.do_ip = False
4131
    self.do_bridge = (self.bridge is not None)
4132
    if self.mac is not None:
4133
      if self.cfg.IsMacInUse(self.mac):
4134
        raise errors.OpPrereqError('MAC address %s already in use in cluster' %
4135
                                   self.mac)
4136
      if not utils.IsValidMac(self.mac):
4137
        raise errors.OpPrereqError('Invalid MAC address %s' % self.mac)
4138

    
4139
    if self.kernel_path is not None:
4140
      self.do_kernel_path = True
4141
      if self.kernel_path == constants.VALUE_NONE:
4142
        raise errors.OpPrereqError("Can't set instance to no kernel")
4143

    
4144
      if self.kernel_path != constants.VALUE_DEFAULT:
4145
        if not os.path.isabs(self.kernel_path):
4146
          raise errors.OpPrereqError("The kernel path must be an absolute"
4147
                                    " filename")
4148
    else:
4149
      self.do_kernel_path = False
4150

    
4151
    if self.initrd_path is not None:
4152
      self.do_initrd_path = True
4153
      if self.initrd_path not in (constants.VALUE_NONE,
4154
                                  constants.VALUE_DEFAULT):
4155
        if not os.path.isabs(self.initrd_path):
4156
          raise errors.OpPrereqError("The initrd path must be an absolute"
4157
                                    " filename")
4158
    else:
4159
      self.do_initrd_path = False
4160

    
4161
    # boot order verification
4162
    if self.hvm_boot_order is not None:
4163
      if self.hvm_boot_order != constants.VALUE_DEFAULT:
4164
        if len(self.hvm_boot_order.strip("acdn")) != 0:
4165
          raise errors.OpPrereqError("invalid boot order specified,"
4166
                                     " must be one or more of [acdn]"
4167
                                     " or 'default'")
4168

    
4169
    # hvm_cdrom_image_path verification
4170
    if self.op.hvm_cdrom_image_path is not None:
4171
      if not os.path.isabs(self.op.hvm_cdrom_image_path):
4172
        raise errors.OpPrereqError("The path to the HVM CDROM image must"
4173
                                   " be an absolute path or None, not %s" %
4174
                                   self.op.hvm_cdrom_image_path)
4175
      if not os.path.isfile(self.op.hvm_cdrom_image_path):
4176
        raise errors.OpPrereqError("The HVM CDROM image must either be a"
4177
                                   " regular file or a symlink pointing to"
4178
                                   " an existing regular file, not %s" %
4179
                                   self.op.hvm_cdrom_image_path)
4180

    
4181
    # vnc_bind_address verification
4182
    if self.op.vnc_bind_address is not None:
4183
      if not utils.IsValidIP(self.op.vnc_bind_address):
4184
        raise errors.OpPrereqError("given VNC bind address '%s' doesn't look"
4185
                                   " like a valid IP address" %
4186
                                   self.op.vnc_bind_address)
4187

    
4188
    instance = self.cfg.GetInstanceInfo(
4189
      self.cfg.ExpandInstanceName(self.op.instance_name))
4190
    if instance is None:
4191
      raise errors.OpPrereqError("No such instance name '%s'" %
4192
                                 self.op.instance_name)
4193
    self.op.instance_name = instance.name
4194
    self.instance = instance
4195
    return
4196

    
4197
  def Exec(self, feedback_fn):
4198
    """Modifies an instance.
4199

4200
    All parameters take effect only at the next restart of the instance.
4201
    """
4202
    result = []
4203
    instance = self.instance
4204
    if self.mem:
4205
      instance.memory = self.mem
4206
      result.append(("mem", self.mem))
4207
    if self.vcpus:
4208
      instance.vcpus = self.vcpus
4209
      result.append(("vcpus",  self.vcpus))
4210
    if self.do_ip:
4211
      instance.nics[0].ip = self.ip
4212
      result.append(("ip", self.ip))
4213
    if self.bridge:
4214
      instance.nics[0].bridge = self.bridge
4215
      result.append(("bridge", self.bridge))
4216
    if self.mac:
4217
      instance.nics[0].mac = self.mac
4218
      result.append(("mac", self.mac))
4219
    if self.do_kernel_path:
4220
      instance.kernel_path = self.kernel_path
4221
      result.append(("kernel_path", self.kernel_path))
4222
    if self.do_initrd_path:
4223
      instance.initrd_path = self.initrd_path
4224
      result.append(("initrd_path", self.initrd_path))
4225
    if self.hvm_boot_order:
4226
      if self.hvm_boot_order == constants.VALUE_DEFAULT:
4227
        instance.hvm_boot_order = None
4228
      else:
4229
        instance.hvm_boot_order = self.hvm_boot_order
4230
      result.append(("hvm_boot_order", self.hvm_boot_order))
4231
    if self.hvm_acpi:
4232
      instance.hvm_acpi = self.hvm_acpi
4233
      result.append(("hvm_acpi", self.hvm_acpi))
4234
    if self.hvm_pae:
4235
      instance.hvm_pae = self.hvm_pae
4236
      result.append(("hvm_pae", self.hvm_pae))
4237
    if self.hvm_cdrom_image_path:
4238
      instance.hvm_cdrom_image_path = self.hvm_cdrom_image_path
4239
      result.append(("hvm_cdrom_image_path", self.hvm_cdrom_image_path))
4240
    if self.vnc_bind_address:
4241
      instance.vnc_bind_address = self.vnc_bind_address
4242
      result.append(("vnc_bind_address", self.vnc_bind_address))
4243

    
4244
    self.cfg.AddInstance(instance)
4245

    
4246
    return result
4247

    
4248

    
4249
class LUQueryExports(NoHooksLU):
4250
  """Query the exports list
4251

4252
  """
4253
  _OP_REQP = []
4254

    
4255
  def CheckPrereq(self):
4256
    """Check that the nodelist contains only existing nodes.
4257

4258
    """
4259
    self.nodes = _GetWantedNodes(self, getattr(self.op, "nodes", None))
4260

    
4261
  def Exec(self, feedback_fn):
4262
    """Compute the list of all the exported system images.
4263

4264
    Returns:
4265
      a dictionary with the structure node->(export-list)
4266
      where export-list is a list of the instances exported on
4267
      that node.
4268

4269
    """
4270
    return rpc.call_export_list(self.nodes)
4271

    
4272

    
4273
class LUExportInstance(LogicalUnit):
4274
  """Export an instance to an image in the cluster.
4275

4276
  """
4277
  HPATH = "instance-export"
4278
  HTYPE = constants.HTYPE_INSTANCE
4279
  _OP_REQP = ["instance_name", "target_node", "shutdown"]
4280

    
4281
  def BuildHooksEnv(self):
4282
    """Build hooks env.
4283

4284
    This will run on the master, primary node and target node.
4285

4286
    """
4287
    env = {
4288
      "EXPORT_NODE": self.op.target_node,
4289
      "EXPORT_DO_SHUTDOWN": self.op.shutdown,
4290
      }
4291
    env.update(_BuildInstanceHookEnvByObject(self.instance))
4292
    nl = [self.sstore.GetMasterNode(), self.instance.primary_node,
4293
          self.op.target_node]
4294
    return env, nl, nl
4295

    
4296
  def CheckPrereq(self):
4297
    """Check prerequisites.
4298

4299
    This checks that the instance and node names are valid.
4300

4301
    """
4302
    instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
4303
    self.instance = self.cfg.GetInstanceInfo(instance_name)
4304
    if self.instance is None:
4305
      raise errors.OpPrereqError("Instance '%s' not found" %
4306
                                 self.op.instance_name)
4307

    
4308
    # node verification
4309
    dst_node_short = self.cfg.ExpandNodeName(self.op.target_node)
4310
    self.dst_node = self.cfg.GetNodeInfo(dst_node_short)
4311

    
4312
    if self.dst_node is None:
4313
      raise errors.OpPrereqError("Destination node '%s' is unknown." %
4314
                                 self.op.target_node)
4315
    self.op.target_node = self.dst_node.name
4316

    
4317
    # instance disk type verification
4318
    for disk in self.instance.disks:
4319
      if disk.dev_type == constants.LD_FILE:
4320
        raise errors.OpPrereqError("Export not supported for instances with"
4321
                                   " file-based disks")
4322

    
4323
  def Exec(self, feedback_fn):
4324
    """Export an instance to an image in the cluster.
4325

4326
    """
4327
    instance = self.instance
4328
    dst_node = self.dst_node
4329
    src_node = instance.primary_node
4330
    if self.op.shutdown:
4331
      # shutdown the instance, but not the disks
4332
      if not rpc.call_instance_shutdown(src_node, instance):
4333
         raise errors.OpExecError("Could not shutdown instance %s on node %s" %
4334
                                  (instance.name, src_node))
4335

    
4336
    vgname = self.cfg.GetVGName()
4337

    
4338
    snap_disks = []
4339

    
4340
    try:
4341
      for disk in instance.disks:
4342
        if disk.iv_name == "sda":
4343
          # new_dev_name will be a snapshot of an lvm leaf of the one we passed
4344
          new_dev_name = rpc.call_blockdev_snapshot(src_node, disk)
4345

    
4346
          if not new_dev_name:
4347
            logger.Error("could not snapshot block device %s on node %s" %
4348
                         (disk.logical_id[1], src_node))
4349
          else:
4350
            new_dev = objects.Disk(dev_type=constants.LD_LV, size=disk.size,
4351
                                      logical_id=(vgname, new_dev_name),
4352
                                      physical_id=(vgname, new_dev_name),
4353
                                      iv_name=disk.iv_name)
4354
            snap_disks.append(new_dev)
4355

    
4356
    finally:
4357
      if self.op.shutdown and instance.status == "up":
4358
        if not rpc.call_instance_start(src_node, instance, None):
4359
          _ShutdownInstanceDisks(instance, self.cfg)
4360
          raise errors.OpExecError("Could not start instance")
4361

    
4362
    # TODO: check for size
4363

    
4364
    for dev in snap_disks:
4365
      if not rpc.call_snapshot_export(src_node, dev, dst_node.name, instance):
4366
        logger.Error("could not export block device %s from node %s to node %s"
4367
                     % (dev.logical_id[1], src_node, dst_node.name))
4368
      if not rpc.call_blockdev_remove(src_node, dev):
4369
        logger.Error("could not remove snapshot block device %s from node %s" %
4370
                     (dev.logical_id[1], src_node))
4371

    
4372
    if not rpc.call_finalize_export(dst_node.name, instance, snap_disks):
4373
      logger.Error("could not finalize export for instance %s on node %s" %
4374
                   (instance.name, dst_node.name))
4375

    
4376
    nodelist = self.cfg.GetNodeList()
4377
    nodelist.remove(dst_node.name)
4378

    
4379
    # on one-node clusters nodelist will be empty after the removal
4380
    # if we proceed the backup would be removed because OpQueryExports
4381
    # substitutes an empty list with the full cluster node list.
4382
    if nodelist:
4383
      op = opcodes.OpQueryExports(nodes=nodelist)
4384
      exportlist = self.proc.ChainOpCode(op)
4385
      for node in exportlist:
4386
        if instance.name in exportlist[node]:
4387
          if not rpc.call_export_remove(node, instance.name):
4388
            logger.Error("could not remove older export for instance %s"
4389
                         " on node %s" % (instance.name, node))
4390

    
4391

    
4392
class LURemoveExport(NoHooksLU):
4393
  """Remove exports related to the named instance.
4394

4395
  """
4396
  _OP_REQP = ["instance_name"]
4397

    
4398
  def CheckPrereq(self):
4399
    """Check prerequisites.
4400
    """
4401
    pass
4402

    
4403
  def Exec(self, feedback_fn):
4404
    """Remove any export.
4405

4406
    """
4407
    instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
4408
    # If the instance was not found we'll try with the name that was passed in.
4409
    # This will only work if it was an FQDN, though.
4410
    fqdn_warn = False
4411
    if not instance_name:
4412
      fqdn_warn = True
4413
      instance_name = self.op.instance_name
4414

    
4415
    op = opcodes.OpQueryExports(nodes=[])
4416
    exportlist = self.proc.ChainOpCode(op)
4417
    found = False
4418
    for node in exportlist:
4419
      if instance_name in exportlist[node]:
4420
        found = True
4421
        if not rpc.call_export_remove(node, instance_name):
4422
          logger.Error("could not remove export for instance %s"
4423
                       " on node %s" % (instance_name, node))
4424

    
4425
    if fqdn_warn and not found:
4426
      feedback_fn("Export not found. If trying to remove an export belonging"
4427
                  " to a deleted instance please use its Fully Qualified"
4428
                  " Domain Name.")
4429

    
4430

    
4431
class TagsLU(NoHooksLU):
4432
  """Generic tags LU.
4433

4434
  This is an abstract class which is the parent of all the other tags LUs.
4435

4436
  """
4437
  def CheckPrereq(self):
4438
    """Check prerequisites.
4439

4440
    """
4441
    if self.op.kind == constants.TAG_CLUSTER:
4442
      self.target = self.cfg.GetClusterInfo()
4443
    elif self.op.kind == constants.TAG_NODE:
4444
      name = self.cfg.ExpandNodeName(self.op.name)
4445
      if name is None:
4446
        raise errors.OpPrereqError("Invalid node name (%s)" %
4447
                                   (self.op.name,))
4448
      self.op.name = name
4449
      self.target = self.cfg.GetNodeInfo(name)
4450
    elif self.op.kind == constants.TAG_INSTANCE:
4451
      name = self.cfg.ExpandInstanceName(self.op.name)
4452
      if name is None:
4453
        raise errors.OpPrereqError("Invalid instance name (%s)" %
4454
                                   (self.op.name,))
4455
      self.op.name = name
4456
      self.target = self.cfg.GetInstanceInfo(name)
4457
    else:
4458
      raise errors.OpPrereqError("Wrong tag type requested (%s)" %
4459
                                 str(self.op.kind))
4460

    
4461

    
4462
class LUGetTags(TagsLU):
4463
  """Returns the tags of a given object.
4464

4465
  """
4466
  _OP_REQP = ["kind", "name"]
4467

    
4468
  def Exec(self, feedback_fn):
4469
    """Returns the tag list.
4470

4471
    """
4472
    return self.target.GetTags()
4473

    
4474

    
4475
class LUSearchTags(NoHooksLU):
4476
  """Searches the tags for a given pattern.
4477

4478
  """
4479
  _OP_REQP = ["pattern"]
4480

    
4481
  def CheckPrereq(self):
4482
    """Check prerequisites.
4483

4484
    This checks the pattern passed for validity by compiling it.
4485

4486
    """
4487
    try:
4488
      self.re = re.compile(self.op.pattern)
4489
    except re.error, err:
4490
      raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
4491
                                 (self.op.pattern, err))
4492

    
4493
  def Exec(self, feedback_fn):
4494
    """Returns the tag list.
4495

4496
    """
4497
    cfg = self.cfg
4498
    tgts = [("/cluster", cfg.GetClusterInfo())]
4499
    ilist = [cfg.GetInstanceInfo(name) for name in cfg.GetInstanceList()]
4500
    tgts.extend([("/instances/%s" % i.name, i) for i in ilist])
4501
    nlist = [cfg.GetNodeInfo(name) for name in cfg.GetNodeList()]
4502
    tgts.extend([("/nodes/%s" % n.name, n) for n in nlist])
4503
    results = []
4504
    for path, target in tgts:
4505
      for tag in target.GetTags():
4506
        if self.re.search(tag):
4507
          results.append((path, tag))
4508
    return results
4509

    
4510

    
4511
class LUAddTags(TagsLU):
4512
  """Sets a tag on a given object.
4513

4514
  """
4515
  _OP_REQP = ["kind", "name", "tags"]
4516

    
4517
  def CheckPrereq(self):
4518
    """Check prerequisites.
4519

4520
    This checks the type and length of the tag name and value.
4521

4522
    """
4523
    TagsLU.CheckPrereq(self)
4524
    for tag in self.op.tags:
4525
      objects.TaggableObject.ValidateTag(tag)
4526

    
4527
  def Exec(self, feedback_fn):
4528
    """Sets the tag.
4529

4530
    """
4531
    try:
4532
      for tag in self.op.tags:
4533
        self.target.AddTag(tag)
4534
    except errors.TagError, err:
4535
      raise errors.OpExecError("Error while setting tag: %s" % str(err))
4536
    try:
4537
      self.cfg.Update(self.target)
4538
    except errors.ConfigurationError:
4539
      raise errors.OpRetryError("There has been a modification to the"
4540
                                " config file and the operation has been"
4541
                                " aborted. Please retry.")
4542

    
4543

    
4544
class LUDelTags(TagsLU):
4545
  """Delete a list of tags from a given object.
4546

4547
  """
4548
  _OP_REQP = ["kind", "name", "tags"]
4549

    
4550
  def CheckPrereq(self):
4551
    """Check prerequisites.
4552

4553
    This checks that we have the given tag.
4554

4555
    """
4556
    TagsLU.CheckPrereq(self)
4557
    for tag in self.op.tags:
4558
      objects.TaggableObject.ValidateTag(tag)
4559
    del_tags = frozenset(self.op.tags)
4560
    cur_tags = self.target.GetTags()
4561
    if not del_tags <= cur_tags:
4562
      diff_tags = del_tags - cur_tags
4563
      diff_names = ["'%s'" % tag for tag in diff_tags]
4564
      diff_names.sort()
4565
      raise errors.OpPrereqError("Tag(s) %s not found" %
4566
                                 (",".join(diff_names)))
4567

    
4568
  def Exec(self, feedback_fn):
4569
    """Remove the tag from the object.
4570

4571
    """
4572
    for tag in self.op.tags:
4573
      self.target.RemoveTag(tag)
4574
    try:
4575
      self.cfg.Update(self.target)
4576
    except errors.ConfigurationError:
4577
      raise errors.OpRetryError("There has been a modification to the"
4578
                                " config file and the operation has been"
4579
                                " aborted. Please retry.")
4580

    
4581
class LUTestDelay(NoHooksLU):
4582
  """Sleep for a specified amount of time.
4583

4584
  This LU sleeps on the master and/or nodes for a specified amoutn of
4585
  time.
4586

4587
  """
4588
  _OP_REQP = ["duration", "on_master", "on_nodes"]
4589

    
4590
  def CheckPrereq(self):
4591
    """Check prerequisites.
4592

4593
    This checks that we have a good list of nodes and/or the duration
4594
    is valid.
4595

4596
    """
4597

    
4598
    if self.op.on_nodes:
4599
      self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
4600

    
4601
  def Exec(self, feedback_fn):
4602
    """Do the actual sleep.
4603

4604
    """
4605
    if self.op.on_master:
4606
      if not utils.TestDelay(self.op.duration):
4607
        raise errors.OpExecError("Error during master delay test")
4608
    if self.op.on_nodes:
4609
      result = rpc.call_test_delay(self.op.on_nodes, self.op.duration)
4610
      if not result:
4611
        raise errors.OpExecError("Complete failure from rpc call")
4612
      for node, node_result in result.items():
4613
        if not node_result:
4614
          raise errors.OpExecError("Failure during rpc call to node %s,"
4615
                                   " result: %s" % (node, node_result))
4616

    
4617

    
4618
class IAllocator(object):
4619
  """IAllocator framework.
4620

4621
  An IAllocator instance has three sets of attributes:
4622
    - cfg/sstore that are needed to query the cluster
4623
    - input data (all members of the _KEYS class attribute are required)
4624
    - four buffer attributes (in|out_data|text), that represent the
4625
      input (to the external script) in text and data structure format,
4626
      and the output from it, again in two formats
4627
    - the result variables from the script (success, info, nodes) for
4628
      easy usage
4629

4630
  """
4631
  _ALLO_KEYS = [
4632
    "mem_size", "disks", "disk_template",
4633
    "os", "tags", "nics", "vcpus",
4634
    ]
4635
  _RELO_KEYS = [
4636
    "relocate_from",
4637
    ]
4638

    
4639
  def __init__(self, cfg, sstore, mode, name, **kwargs):
4640
    self.cfg = cfg
4641
    self.sstore = sstore
4642
    # init buffer variables
4643
    self.in_text = self.out_text = self.in_data = self.out_data = None
4644
    # init all input fields so that pylint is happy
4645
    self.mode = mode
4646
    self.name = name
4647
    self.mem_size = self.disks = self.disk_template = None
4648
    self.os = self.tags = self.nics = self.vcpus = None
4649
    self.relocate_from = None
4650
    # computed fields
4651
    self.required_nodes = None
4652
    # init result fields
4653
    self.success = self.info = self.nodes = None
4654
    if self.mode == constants.IALLOCATOR_MODE_ALLOC:
4655
      keyset = self._ALLO_KEYS
4656
    elif self.mode == constants.IALLOCATOR_MODE_RELOC:
4657
      keyset = self._RELO_KEYS
4658
    else:
4659
      raise errors.ProgrammerError("Unknown mode '%s' passed to the"
4660
                                   " IAllocator" % self.mode)
4661
    for key in kwargs:
4662
      if key not in keyset:
4663
        raise errors.ProgrammerError("Invalid input parameter '%s' to"
4664
                                     " IAllocator" % key)
4665
      setattr(self, key, kwargs[key])
4666
    for key in keyset:
4667
      if key not in kwargs:
4668
        raise errors.ProgrammerError("Missing input parameter '%s' to"
4669
                                     " IAllocator" % key)
4670
    self._BuildInputData()
4671

    
4672
  def _ComputeClusterData(self):
4673
    """Compute the generic allocator input data.
4674

4675
    This is the data that is independent of the actual operation.
4676

4677
    """
4678
    cfg = self.cfg
4679
    # cluster data
4680
    data = {
4681
      "version": 1,
4682
      "cluster_name": self.sstore.GetClusterName(),
4683
      "cluster_tags": list(cfg.GetClusterInfo().GetTags()),
4684
      "hypervisor_type": self.sstore.GetHypervisorType(),
4685
      # we don't have job IDs
4686
      }
4687

    
4688
    i_list = [cfg.GetInstanceInfo(iname) for iname in cfg.GetInstanceList()]
4689

    
4690
    # node data
4691
    node_results = {}
4692
    node_list = cfg.GetNodeList()
4693
    node_data = rpc.call_node_info(node_list, cfg.GetVGName())
4694
    for nname in node_list:
4695
      ninfo = cfg.GetNodeInfo(nname)
4696
      if nname not in node_data or not isinstance(node_data[nname], dict):
4697
        raise errors.OpExecError("Can't get data for node %s" % nname)
4698
      remote_info = node_data[nname]
4699
      for attr in ['memory_total', 'memory_free', 'memory_dom0',
4700
                   'vg_size', 'vg_free', 'cpu_total']:
4701
        if attr not in remote_info:
4702
          raise errors.OpExecError("Node '%s' didn't return attribute '%s'" %
4703
                                   (nname, attr))
4704
        try:
4705
          remote_info[attr] = int(remote_info[attr])
4706
        except ValueError, err:
4707
          raise errors.OpExecError("Node '%s' returned invalid value for '%s':"
4708
                                   " %s" % (nname, attr, str(err)))
4709
      # compute memory used by primary instances
4710
      i_p_mem = i_p_up_mem = 0
4711
      for iinfo in i_list:
4712
        if iinfo.primary_node == nname:
4713
          i_p_mem += iinfo.memory
4714
          if iinfo.status == "up":
4715
            i_p_up_mem += iinfo.memory
4716

    
4717
      # compute memory used by instances
4718
      pnr = {
4719
        "tags": list(ninfo.GetTags()),
4720
        "total_memory": remote_info['memory_total'],
4721
        "reserved_memory": remote_info['memory_dom0'],
4722
        "free_memory": remote_info['memory_free'],
4723
        "i_pri_memory": i_p_mem,
4724
        "i_pri_up_memory": i_p_up_mem,
4725
        "total_disk": remote_info['vg_size'],
4726
        "free_disk": remote_info['vg_free'],
4727
        "primary_ip": ninfo.primary_ip,
4728
        "secondary_ip": ninfo.secondary_ip,
4729
        "total_cpus": remote_info['cpu_total'],
4730
        }
4731
      node_results[nname] = pnr
4732
    data["nodes"] = node_results
4733

    
4734
    # instance data
4735
    instance_data = {}
4736
    for iinfo in i_list:
4737
      nic_data = [{"mac": n.mac, "ip": n.ip, "bridge": n.bridge}
4738
                  for n in iinfo.nics]
4739
      pir = {
4740
        "tags": list(iinfo.GetTags()),
4741
        "should_run": iinfo.status == "up",
4742
        "vcpus": iinfo.vcpus,
4743
        "memory": iinfo.memory,
4744
        "os": iinfo.os,
4745
        "nodes": [iinfo.primary_node] + list(iinfo.secondary_nodes),
4746
        "nics": nic_data,
4747
        "disks": [{"size": dsk.size, "mode": "w"} for dsk in iinfo.disks],
4748
        "disk_template": iinfo.disk_template,
4749
        }
4750
      instance_data[iinfo.name] = pir
4751

    
4752
    data["instances"] = instance_data
4753

    
4754
    self.in_data = data
4755

    
4756
  def _AddNewInstance(self):
4757
    """Add new instance data to allocator structure.
4758

4759
    This in combination with _AllocatorGetClusterData will create the
4760
    correct structure needed as input for the allocator.
4761

4762
    The checks for the completeness of the opcode must have already been
4763
    done.
4764

4765
    """
4766
    data = self.in_data
4767
    if len(self.disks) != 2:
4768
      raise errors.OpExecError("Only two-disk configurations supported")
4769

    
4770
    disk_space = _ComputeDiskSize(self.disk_template,
4771
                                  self.disks[0]["size"], self.disks[1]["size"])
4772

    
4773
    if self.disk_template in constants.DTS_NET_MIRROR:
4774
      self.required_nodes = 2
4775
    else:
4776
      self.required_nodes = 1
4777
    request = {
4778
      "type": "allocate",
4779
      "name": self.name,
4780
      "disk_template": self.disk_template,
4781
      "tags": self.tags,
4782
      "os": self.os,
4783
      "vcpus": self.vcpus,
4784
      "memory": self.mem_size,
4785
      "disks": self.disks,
4786
      "disk_space_total": disk_space,
4787
      "nics": self.nics,
4788
      "required_nodes": self.required_nodes,
4789
      }
4790
    data["request"] = request
4791

    
4792
  def _AddRelocateInstance(self):
4793
    """Add relocate instance data to allocator structure.
4794

4795
    This in combination with _IAllocatorGetClusterData will create the
4796
    correct structure needed as input for the allocator.
4797

4798
    The checks for the completeness of the opcode must have already been
4799
    done.
4800

4801
    """
4802
    instance = self.cfg.GetInstanceInfo(self.name)
4803
    if instance is None:
4804
      raise errors.ProgrammerError("Unknown instance '%s' passed to"
4805
                                   " IAllocator" % self.name)
4806

    
4807
    if instance.disk_template not in constants.DTS_NET_MIRROR:
4808
      raise errors.OpPrereqError("Can't relocate non-mirrored instances")
4809

    
4810
    if len(instance.secondary_nodes) != 1:
4811
      raise errors.OpPrereqError("Instance has not exactly one secondary node")
4812

    
4813
    self.required_nodes = 1
4814

    
4815
    disk_space = _ComputeDiskSize(instance.disk_template,
4816
                                  instance.disks[0].size,
4817
                                  instance.disks[1].size)
4818

    
4819
    request = {
4820
      "type": "relocate",
4821
      "name": self.name,
4822
      "disk_space_total": disk_space,
4823
      "required_nodes": self.required_nodes,
4824
      "relocate_from": self.relocate_from,
4825
      }
4826
    self.in_data["request"] = request
4827

    
4828
  def _BuildInputData(self):
4829
    """Build input data structures.
4830

4831
    """
4832
    self._ComputeClusterData()
4833

    
4834
    if self.mode == constants.IALLOCATOR_MODE_ALLOC:
4835
      self._AddNewInstance()
4836
    else:
4837
      self._AddRelocateInstance()
4838

    
4839
    self.in_text = serializer.Dump(self.in_data)
4840

    
4841
  def Run(self, name, validate=True, call_fn=rpc.call_iallocator_runner):
4842
    """Run an instance allocator and return the results.
4843

4844
    """
4845
    data = self.in_text
4846

    
4847
    result = call_fn(self.sstore.GetMasterNode(), name, self.in_text)
4848

    
4849
    if not isinstance(result, tuple) or len(result) != 4:
4850
      raise errors.OpExecError("Invalid result from master iallocator runner")
4851

    
4852
    rcode, stdout, stderr, fail = result
4853

    
4854
    if rcode == constants.IARUN_NOTFOUND:
4855
      raise errors.OpExecError("Can't find allocator '%s'" % name)
4856
    elif rcode == constants.IARUN_FAILURE:
4857
        raise errors.OpExecError("Instance allocator call failed: %s,"
4858
                                 " output: %s" %
4859
                                 (fail, stdout+stderr))
4860
    self.out_text = stdout
4861
    if validate:
4862
      self._ValidateResult()
4863

    
4864
  def _ValidateResult(self):
4865
    """Process the allocator results.
4866

4867
    This will process and if successful save the result in
4868
    self.out_data and the other parameters.
4869

4870
    """
4871
    try:
4872
      rdict = serializer.Load(self.out_text)
4873
    except Exception, err:
4874
      raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
4875

    
4876
    if not isinstance(rdict, dict):
4877
      raise errors.OpExecError("Can't parse iallocator results: not a dict")
4878

    
4879
    for key in "success", "info", "nodes":
4880
      if key not in rdict:
4881
        raise errors.OpExecError("Can't parse iallocator results:"
4882
                                 " missing key '%s'" % key)
4883
      setattr(self, key, rdict[key])
4884

    
4885
    if not isinstance(rdict["nodes"], list):
4886
      raise errors.OpExecError("Can't parse iallocator results: 'nodes' key"
4887
                               " is not a list")
4888
    self.out_data = rdict
4889

    
4890

    
4891
class LUTestAllocator(NoHooksLU):
4892
  """Run allocator tests.
4893

4894
  This LU runs the allocator tests
4895

4896
  """
4897
  _OP_REQP = ["direction", "mode", "name"]
4898

    
4899
  def CheckPrereq(self):
4900
    """Check prerequisites.
4901

4902
    This checks the opcode parameters depending on the director and mode test.
4903

4904
    """
4905
    if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
4906
      for attr in ["name", "mem_size", "disks", "disk_template",
4907
                   "os", "tags", "nics", "vcpus"]:
4908
        if not hasattr(self.op, attr):
4909
          raise errors.OpPrereqError("Missing attribute '%s' on opcode input" %
4910
                                     attr)
4911
      iname = self.cfg.ExpandInstanceName(self.op.name)
4912
      if iname is not None:
4913
        raise errors.OpPrereqError("Instance '%s' already in the cluster" %
4914
                                   iname)
4915
      if not isinstance(self.op.nics, list):
4916
        raise errors.OpPrereqError("Invalid parameter 'nics'")
4917
      for row in self.op.nics:
4918
        if (not isinstance(row, dict) or
4919
            "mac" not in row or
4920
            "ip" not in row or
4921
            "bridge" not in row):
4922
          raise errors.OpPrereqError("Invalid contents of the"
4923
                                     " 'nics' parameter")
4924
      if not isinstance(self.op.disks, list):
4925
        raise errors.OpPrereqError("Invalid parameter 'disks'")
4926
      if len(self.op.disks) != 2:
4927
        raise errors.OpPrereqError("Only two-disk configurations supported")
4928
      for row in self.op.disks:
4929
        if (not isinstance(row, dict) or
4930
            "size" not in row or
4931
            not isinstance(row["size"], int) or
4932
            "mode" not in row or
4933
            row["mode"] not in ['r', 'w']):
4934
          raise errors.OpPrereqError("Invalid contents of the"
4935
                                     " 'disks' parameter")
4936
    elif self.op.mode == constants.IALLOCATOR_MODE_RELOC:
4937
      if not hasattr(self.op, "name"):
4938
        raise errors.OpPrereqError("Missing attribute 'name' on opcode input")
4939
      fname = self.cfg.ExpandInstanceName(self.op.name)
4940
      if fname is None:
4941
        raise errors.OpPrereqError("Instance '%s' not found for relocation" %
4942
                                   self.op.name)
4943
      self.op.name = fname
4944
      self.relocate_from = self.cfg.GetInstanceInfo(fname).secondary_nodes
4945
    else:
4946
      raise errors.OpPrereqError("Invalid test allocator mode '%s'" %
4947
                                 self.op.mode)
4948

    
4949
    if self.op.direction == constants.IALLOCATOR_DIR_OUT:
4950
      if not hasattr(self.op, "allocator") or self.op.allocator is None:
4951
        raise errors.OpPrereqError("Missing allocator name")
4952
    elif self.op.direction != constants.IALLOCATOR_DIR_IN:
4953
      raise errors.OpPrereqError("Wrong allocator test '%s'" %
4954
                                 self.op.direction)
4955

    
4956
  def Exec(self, feedback_fn):
4957
    """Run the allocator test.
4958

4959
    """
4960
    if self.op.mode == constants.IALLOCATOR_MODE_ALLOC:
4961
      ial = IAllocator(self.cfg, self.sstore,
4962
                       mode=self.op.mode,
4963
                       name=self.op.name,
4964
                       mem_size=self.op.mem_size,
4965
                       disks=self.op.disks,
4966
                       disk_template=self.op.disk_template,
4967
                       os=self.op.os,
4968
                       tags=self.op.tags,
4969
                       nics=self.op.nics,
4970
                       vcpus=self.op.vcpus,
4971
                       )
4972
    else:
4973
      ial = IAllocator(self.cfg, self.sstore,
4974
                       mode=self.op.mode,
4975
                       name=self.op.name,
4976
                       relocate_from=list(self.relocate_from),
4977
                       )
4978

    
4979
    if self.op.direction == constants.IALLOCATOR_DIR_IN:
4980
      result = ial.in_text
4981
    else:
4982
      ial.Run(self.op.allocator, validate=False)
4983
      result = ial.out_text
4984
    return result