Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib.py @ b5602d15

History | View | Annotate | Download (169 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
      if utils.TcpPing(new_ip, constants.DEFAULT_NODED_PORT):
884
        raise errors.OpPrereqError("The given cluster IP address (%s) is"
885
                                   " reachable on the network. Aborting." %
886
                                   new_ip)
887

    
888
    self.op.name = new_name
889

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

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

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

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

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

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

    
927

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

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

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

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

    
944

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

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

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

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

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

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

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

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

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

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

    
1001

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

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

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

    
1012
  node = instance.primary_node
1013

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

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

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

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

    
1067

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

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

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

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

    
1094
  return result
1095

    
1096

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

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

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

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

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

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

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

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

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

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

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

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

    
1174
    return output
1175

    
1176

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

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

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

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

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

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

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

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

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

    
1215
    instance_list = self.cfg.GetInstanceList()
1216

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

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

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

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

    
1241
    rpc.call_node_leave_cluster(node.name)
1242

    
1243
    logger.Info("Removing node %s from config" % node.name)
1244

    
1245
    self.cfg.RemoveNode(node.name)
1246

    
1247
    utils.RemoveHostFromEtcHosts(node.name)
1248

    
1249

    
1250
class LUQueryNodes(NoHooksLU):
1251
  """Logical unit for querying nodes.
1252

1253
  """
1254
  _OP_REQP = ["output_fields", "names"]
1255

    
1256
  def CheckPrereq(self):
1257
    """Check prerequisites.
1258

1259
    This checks that the fields required are valid output fields.
1260

1261
    """
1262
    self.dynamic_fields = frozenset([
1263
      "dtotal", "dfree",
1264
      "mtotal", "mnode", "mfree",
1265
      "bootid",
1266
      "ctotal",
1267
      ])
1268

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

    
1275
    self.wanted = _GetWantedNodes(self, self.op.names)
1276

    
1277
  def Exec(self, feedback_fn):
1278
    """Computes the list of nodes and their attributes.
1279

1280
    """
1281
    nodenames = self.wanted
1282
    nodelist = [self.cfg.GetNodeInfo(name) for name in nodenames]
1283

    
1284
    # begin data gathering
1285

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

    
1306
    node_to_primary = dict([(name, set()) for name in nodenames])
1307
    node_to_secondary = dict([(name, set()) for name in nodenames])
1308

    
1309
    inst_fields = frozenset(("pinst_cnt", "pinst_list",
1310
                             "sinst_cnt", "sinst_list"))
1311
    if inst_fields & frozenset(self.op.output_fields):
1312
      instancelist = self.cfg.GetInstanceList()
1313

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

    
1322
    # end data gathering
1323

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

    
1351
    return output
1352

    
1353

    
1354
class LUQueryNodeVolumes(NoHooksLU):
1355
  """Logical unit for getting volumes on node(s).
1356

1357
  """
1358
  _OP_REQP = ["nodes", "output_fields"]
1359

    
1360
  def CheckPrereq(self):
1361
    """Check prerequisites.
1362

1363
    This checks that the fields required are valid output fields.
1364

1365
    """
1366
    self.nodes = _GetWantedNodes(self, self.op.nodes)
1367

    
1368
    _CheckOutputFields(static=["node"],
1369
                       dynamic=["phys", "vg", "name", "size", "instance"],
1370
                       selected=self.op.output_fields)
1371

    
1372

    
1373
  def Exec(self, feedback_fn):
1374
    """Computes the list of nodes and their attributes.
1375

1376
    """
1377
    nodenames = self.nodes
1378
    volumes = rpc.call_node_volumes(nodenames)
1379

    
1380
    ilist = [self.cfg.GetInstanceInfo(iname) for iname
1381
             in self.cfg.GetInstanceList()]
1382

    
1383
    lv_by_node = dict([(inst, inst.MapLVsByNode()) for inst in ilist])
1384

    
1385
    output = []
1386
    for node in nodenames:
1387
      if node not in volumes or not volumes[node]:
1388
        continue
1389

    
1390
      node_vols = volumes[node][:]
1391
      node_vols.sort(key=lambda vol: vol['dev'])
1392

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

    
1419
        output.append(node_output)
1420

    
1421
    return output
1422

    
1423

    
1424
class LUAddNode(LogicalUnit):
1425
  """Logical unit for adding node to the cluster.
1426

1427
  """
1428
  HPATH = "node-add"
1429
  HTYPE = constants.HTYPE_NODE
1430
  _OP_REQP = ["node_name"]
1431

    
1432
  def BuildHooksEnv(self):
1433
    """Build hooks env.
1434

1435
    This will run on all nodes before, and on all nodes + the new node after.
1436

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

    
1448
  def CheckPrereq(self):
1449
    """Check prerequisites.
1450

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

1456
    Any errors are signalled by raising errors.OpPrereqError.
1457

1458
    """
1459
    node_name = self.op.node_name
1460
    cfg = self.cfg
1461

    
1462
    dns_data = utils.HostInfo(node_name)
1463

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

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

    
1480
    for existing_node_name in node_list:
1481
      existing_node = cfg.GetNodeInfo(existing_node_name)
1482

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

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

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

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

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

    
1521
    self.new_node = objects.Node(name=node,
1522
                                 primary_ip=primary_ip,
1523
                                 secondary_ip=secondary_ip)
1524

    
1525
  def Exec(self, feedback_fn):
1526
    """Adds the new node to the cluster.
1527

1528
    """
1529
    new_node = self.new_node
1530
    node = new_node.name
1531

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

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

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

    
1569
    # check connectivity
1570
    time.sleep(4)
1571

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

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

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

    
1599
    result = rpc.call_node_add(node, keyarray[0], keyarray[1], keyarray[2],
1600
                               keyarray[3], keyarray[4], keyarray[5])
1601

    
1602
    if not result:
1603
      raise errors.OpExecError("Cannot transfer ssh keys to the new node")
1604

    
1605
    # Add node to our /etc/hosts, and add key to known_hosts
1606
    utils.AddHostToEtcHosts(new_node.name)
1607

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

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

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

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

    
1642
    to_copy = ss.GetFileList()
1643
    if self.sstore.GetHypervisorType() == constants.HT_XEN_HVM31:
1644
      to_copy.append(constants.VNC_PASSWORD_FILE)
1645
    for fname in to_copy:
1646
      result = rpc.call_upload_file([node], fname)
1647
      if not result[node]:
1648
        logger.Error("could not copy file %s to node %s" % (fname, node))
1649

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

    
1654

    
1655
class LUMasterFailover(LogicalUnit):
1656
  """Failover the master node to the current node.
1657

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

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

    
1667
  def BuildHooksEnv(self):
1668
    """Build hooks env.
1669

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

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

    
1681
  def CheckPrereq(self):
1682
    """Check prerequisites.
1683

1684
    This checks that we are not already the master.
1685

1686
    """
1687
    self.new_master = utils.HostInfo().name
1688
    self.old_master = self.sstore.GetMasterNode()
1689

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

    
1696
  def Exec(self, feedback_fn):
1697
    """Failover the master node.
1698

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

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

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

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

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

    
1725

    
1726

    
1727
class LUQueryClusterInfo(NoHooksLU):
1728
  """Query cluster configuration.
1729

1730
  """
1731
  _OP_REQP = []
1732
  REQ_MASTER = False
1733

    
1734
  def CheckPrereq(self):
1735
    """No prerequsites needed for this LU.
1736

1737
    """
1738
    pass
1739

    
1740
  def Exec(self, feedback_fn):
1741
    """Return cluster config.
1742

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

    
1756
    return result
1757

    
1758

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

1762
  """
1763
  _OP_REQP = []
1764

    
1765
  def CheckPrereq(self):
1766
    """No prerequisites.
1767

1768
    """
1769
    pass
1770

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

1774
    """
1775
    return self.cfg.DumpConfig()
1776

    
1777

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

1781
  """
1782
  _OP_REQP = ["instance_name"]
1783

    
1784
  def CheckPrereq(self):
1785
    """Check prerequisites.
1786

1787
    This checks that the instance is in the cluster.
1788

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

    
1797

    
1798
  def Exec(self, feedback_fn):
1799
    """Activate the disks.
1800

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

    
1806
    return disks_info
1807

    
1808

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

1812
  This sets up the block devices on all nodes.
1813

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

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

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

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

    
1847
  # FIXME: race condition on drbd migration to primary
1848

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

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

    
1868
  return disks_ok, device_info
1869

    
1870

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

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

    
1884

    
1885
class LUDeactivateInstanceDisks(NoHooksLU):
1886
  """Shutdown an instance's disks.
1887

1888
  """
1889
  _OP_REQP = ["instance_name"]
1890

    
1891
  def CheckPrereq(self):
1892
    """Check prerequisites.
1893

1894
    This checks that the instance is in the cluster.
1895

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

    
1904
  def Exec(self, feedback_fn):
1905
    """Deactivate the disks
1906

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

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

    
1919
    _ShutdownInstanceDisks(instance, self.cfg)
1920

    
1921

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

1925
  This does the shutdown on all nodes of the instance.
1926

1927
  If the ignore_primary is false, errors on the primary node are
1928
  ignored.
1929

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

    
1942

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

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

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

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

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

    
1972

    
1973
class LUStartupInstance(LogicalUnit):
1974
  """Starts an instance.
1975

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

    
1981
  def BuildHooksEnv(self):
1982
    """Build hooks env.
1983

1984
    This runs on master, primary and secondary nodes of the instance.
1985

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

    
1995
  def CheckPrereq(self):
1996
    """Check prerequisites.
1997

1998
    This checks that the instance is in the cluster.
1999

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

    
2007
    # check bridges existance
2008
    _CheckInstanceBridgesExist(instance)
2009

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

    
2014
    self.instance = instance
2015
    self.op.instance_name = instance.name
2016

    
2017
  def Exec(self, feedback_fn):
2018
    """Start the instance.
2019

2020
    """
2021
    instance = self.instance
2022
    force = self.op.force
2023
    extra_args = getattr(self.op, "extra_args", "")
2024

    
2025
    self.cfg.MarkInstanceUp(instance.name)
2026

    
2027
    node_current = instance.primary_node
2028

    
2029
    _StartInstanceDisks(self.cfg, instance, force)
2030

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

    
2035

    
2036
class LURebootInstance(LogicalUnit):
2037
  """Reboot an instance.
2038

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

    
2044
  def BuildHooksEnv(self):
2045
    """Build hooks env.
2046

2047
    This runs on master, primary and secondary nodes of the instance.
2048

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

    
2058
  def CheckPrereq(self):
2059
    """Check prerequisites.
2060

2061
    This checks that the instance is in the cluster.
2062

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

    
2070
    # check bridges existance
2071
    _CheckInstanceBridgesExist(instance)
2072

    
2073
    self.instance = instance
2074
    self.op.instance_name = instance.name
2075

    
2076
  def Exec(self, feedback_fn):
2077
    """Reboot the instance.
2078

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

    
2085
    node_current = instance.primary_node
2086

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

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

    
2109
    self.cfg.MarkInstanceUp(instance.name)
2110

    
2111

    
2112
class LUShutdownInstance(LogicalUnit):
2113
  """Shutdown an instance.
2114

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

    
2120
  def BuildHooksEnv(self):
2121
    """Build hooks env.
2122

2123
    This runs on master, primary and secondary nodes of the instance.
2124

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

    
2131
  def CheckPrereq(self):
2132
    """Check prerequisites.
2133

2134
    This checks that the instance is in the cluster.
2135

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

    
2144
  def Exec(self, feedback_fn):
2145
    """Shutdown the instance.
2146

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

    
2154
    _ShutdownInstanceDisks(instance, self.cfg)
2155

    
2156

    
2157
class LUReinstallInstance(LogicalUnit):
2158
  """Reinstall an instance.
2159

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

    
2165
  def BuildHooksEnv(self):
2166
    """Build hooks env.
2167

2168
    This runs on master, primary and secondary nodes of the instance.
2169

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

    
2176
  def CheckPrereq(self):
2177
    """Check prerequisites.
2178

2179
    This checks that the instance is in the cluster and is not running.
2180

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

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

    
2212
    self.instance = instance
2213

    
2214
  def Exec(self, feedback_fn):
2215
    """Reinstall the instance.
2216

2217
    """
2218
    inst = self.instance
2219

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

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

    
2235

    
2236
class LURenameInstance(LogicalUnit):
2237
  """Rename an instance.
2238

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

    
2244
  def BuildHooksEnv(self):
2245
    """Build hooks env.
2246

2247
    This runs on master, primary and secondary nodes of the instance.
2248

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

    
2256
  def CheckPrereq(self):
2257
    """Check prerequisites.
2258

2259
    This checks that the instance is in the cluster and is not running.
2260

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

    
2277
    # new name verification
2278
    name_info = utils.HostInfo(self.op.new_name)
2279

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

    
2286
    if not getattr(self.op, "ignore_ip", False):
2287
      if utils.TcpPing(name_info.ip, constants.DEFAULT_NODED_PORT):
2288
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
2289
                                   (name_info.ip, new_name))
2290

    
2291

    
2292
  def Exec(self, feedback_fn):
2293
    """Reinstall the instance.
2294

2295
    """
2296
    inst = self.instance
2297
    old_name = inst.name
2298

    
2299
    if inst.disk_template == constants.DT_FILE:
2300
      old_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
2301

    
2302
    self.cfg.RenameInstance(inst.name, self.op.new_name)
2303

    
2304
    # re-read the instance from the configuration after rename
2305
    inst = self.cfg.GetInstanceInfo(self.op.new_name)
2306

    
2307
    if inst.disk_template == constants.DT_FILE:
2308
      new_file_storage_dir = os.path.dirname(inst.disks[0].logical_id[1])
2309
      result = rpc.call_file_storage_dir_rename(inst.primary_node,
2310
                                                old_file_storage_dir,
2311
                                                new_file_storage_dir)
2312

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

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

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

    
2337

    
2338
class LURemoveInstance(LogicalUnit):
2339
  """Remove an instance.
2340

2341
  """
2342
  HPATH = "instance-remove"
2343
  HTYPE = constants.HTYPE_INSTANCE
2344
  _OP_REQP = ["instance_name", "ignore_failures"]
2345

    
2346
  def BuildHooksEnv(self):
2347
    """Build hooks env.
2348

2349
    This runs on master, primary and secondary nodes of the instance.
2350

2351
    """
2352
    env = _BuildInstanceHookEnvByObject(self.instance)
2353
    nl = [self.sstore.GetMasterNode()]
2354
    return env, nl, nl
2355

    
2356
  def CheckPrereq(self):
2357
    """Check prerequisites.
2358

2359
    This checks that the instance is in the cluster.
2360

2361
    """
2362
    instance = self.cfg.GetInstanceInfo(
2363
      self.cfg.ExpandInstanceName(self.op.instance_name))
2364
    if instance is None:
2365
      raise errors.OpPrereqError("Instance '%s' not known" %
2366
                                 self.op.instance_name)
2367
    self.instance = instance
2368

    
2369
  def Exec(self, feedback_fn):
2370
    """Remove the instance.
2371

2372
    """
2373
    instance = self.instance
2374
    logger.Info("shutting down instance %s on node %s" %
2375
                (instance.name, instance.primary_node))
2376

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

    
2384
    logger.Info("removing block devices for instance %s" % instance.name)
2385

    
2386
    if not _RemoveDisks(instance, self.cfg):
2387
      if self.op.ignore_failures:
2388
        feedback_fn("Warning: can't remove instance's disks")
2389
      else:
2390
        raise errors.OpExecError("Can't remove instance's disks")
2391

    
2392
    logger.Info("removing instance %s out of cluster config" % instance.name)
2393

    
2394
    self.cfg.RemoveInstance(instance.name)
2395

    
2396

    
2397
class LUQueryInstances(NoHooksLU):
2398
  """Logical unit for querying instances.
2399

2400
  """
2401
  _OP_REQP = ["output_fields", "names"]
2402

    
2403
  def CheckPrereq(self):
2404
    """Check prerequisites.
2405

2406
    This checks that the fields required are valid output fields.
2407

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

    
2417
    self.wanted = _GetWantedInstances(self, self.op.names)
2418

    
2419
  def Exec(self, feedback_fn):
2420
    """Computes the list of nodes and their attributes.
2421

2422
    """
2423
    instance_names = self.wanted
2424
    instance_list = [self.cfg.GetInstanceInfo(iname) for iname
2425
                     in instance_names]
2426

    
2427
    # begin data gathering
2428

    
2429
    nodes = frozenset([inst.primary_node for inst in instance_list])
2430

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

    
2445
    # end data gathering
2446

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

    
2513
    return output
2514

    
2515

    
2516
class LUFailoverInstance(LogicalUnit):
2517
  """Failover an instance.
2518

2519
  """
2520
  HPATH = "instance-failover"
2521
  HTYPE = constants.HTYPE_INSTANCE
2522
  _OP_REQP = ["instance_name", "ignore_consistency"]
2523

    
2524
  def BuildHooksEnv(self):
2525
    """Build hooks env.
2526

2527
    This runs on master, primary and secondary nodes of the instance.
2528

2529
    """
2530
    env = {
2531
      "IGNORE_CONSISTENCY": self.op.ignore_consistency,
2532
      }
2533
    env.update(_BuildInstanceHookEnvByObject(self.instance))
2534
    nl = [self.sstore.GetMasterNode()] + list(self.instance.secondary_nodes)
2535
    return env, nl, nl
2536

    
2537
  def CheckPrereq(self):
2538
    """Check prerequisites.
2539

2540
    This checks that the instance is in the cluster.
2541

2542
    """
2543
    instance = self.cfg.GetInstanceInfo(
2544
      self.cfg.ExpandInstanceName(self.op.instance_name))
2545
    if instance is None:
2546
      raise errors.OpPrereqError("Instance '%s' not known" %
2547
                                 self.op.instance_name)
2548

    
2549
    if instance.disk_template not in constants.DTS_NET_MIRROR:
2550
      raise errors.OpPrereqError("Instance's disk layout is not"
2551
                                 " network mirrored, cannot failover.")
2552

    
2553
    secondary_nodes = instance.secondary_nodes
2554
    if not secondary_nodes:
2555
      raise errors.ProgrammerError("no secondary node but using "
2556
                                   "a mirrored disk template")
2557

    
2558
    target_node = secondary_nodes[0]
2559
    # check memory requirements on the secondary node
2560
    _CheckNodeFreeMemory(self.cfg, target_node, "failing over instance %s" %
2561
                         instance.name, instance.memory)
2562

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

    
2570
    self.instance = instance
2571

    
2572
  def Exec(self, feedback_fn):
2573
    """Failover an instance.
2574

2575
    The failover is done by shutting it down on its present node and
2576
    starting it on the secondary.
2577

2578
    """
2579
    instance = self.instance
2580

    
2581
    source_node = instance.primary_node
2582
    target_node = instance.secondary_nodes[0]
2583

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

    
2592
    feedback_fn("* shutting down instance on source node")
2593
    logger.Info("Shutting down instance %s on node %s" %
2594
                (instance.name, source_node))
2595

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

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

    
2609
    instance.primary_node = target_node
2610
    # distribute new instance config to the other nodes
2611
    self.cfg.Update(instance)
2612

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

    
2619
      disks_ok, dummy = _AssembleInstanceDisks(instance, self.cfg,
2620
                                               ignore_secondaries=True)
2621
      if not disks_ok:
2622
        _ShutdownInstanceDisks(instance, self.cfg)
2623
        raise errors.OpExecError("Can't activate the instance's disks")
2624

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

    
2631

    
2632
def _CreateBlockDevOnPrimary(cfg, node, instance, device, info):
2633
  """Create a tree of block devices on the primary node.
2634

2635
  This always creates all devices.
2636

2637
  """
2638
  if device.children:
2639
    for child in device.children:
2640
      if not _CreateBlockDevOnPrimary(cfg, node, instance, child, info):
2641
        return False
2642

    
2643
  cfg.SetDiskID(device, node)
2644
  new_id = rpc.call_blockdev_create(node, device, device.size,
2645
                                    instance.name, True, info)
2646
  if not new_id:
2647
    return False
2648
  if device.physical_id is None:
2649
    device.physical_id = new_id
2650
  return True
2651

    
2652

    
2653
def _CreateBlockDevOnSecondary(cfg, node, instance, device, force, info):
2654
  """Create a tree of block devices on a secondary node.
2655

2656
  If this device type has to be created on secondaries, create it and
2657
  all its children.
2658

2659
  If not, just recurse to children keeping the same 'force' value.
2660

2661
  """
2662
  if device.CreateOnSecondary():
2663
    force = True
2664
  if device.children:
2665
    for child in device.children:
2666
      if not _CreateBlockDevOnSecondary(cfg, node, instance,
2667
                                        child, force, info):
2668
        return False
2669

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

    
2681

    
2682
def _GenerateUniqueNames(cfg, exts):
2683
  """Generate a suitable LV name.
2684

2685
  This will generate a logical volume name for the given instance.
2686

2687
  """
2688
  results = []
2689
  for val in exts:
2690
    new_id = cfg.GenerateUniqueID()
2691
    results.append("%s%s" % (new_id, val))
2692
  return results
2693

    
2694

    
2695
def _GenerateDRBD8Branch(cfg, primary, secondary, size, names, iv_name):
2696
  """Generate a drbd8 device complete with its children.
2697

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

    
2711

    
2712
def _GenerateDiskTemplate(cfg, template_name,
2713
                          instance_name, primary_node,
2714
                          secondary_nodes, disk_sz, swap_sz,
2715
                          file_storage_dir, file_driver):
2716
  """Generate the entire disk layout for a given template type.
2717

2718
  """
2719
  #TODO: compute space requirements
2720

    
2721
  vgname = cfg.GetVGName()
2722
  if template_name == constants.DT_DISKLESS:
2723
    disks = []
2724
  elif template_name == constants.DT_PLAIN:
2725
    if len(secondary_nodes) != 0:
2726
      raise errors.ProgrammerError("Wrong template configuration")
2727

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

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

    
2762

    
2763
def _GetInstanceInfoText(instance):
2764
  """Compute that text that should be added to the disk's metadata.
2765

2766
  """
2767
  return "originstname+%s" % instance.name
2768

    
2769

    
2770
def _CreateDisks(cfg, instance):
2771
  """Create all disks for an instance.
2772

2773
  This abstracts away some work from AddInstance.
2774

2775
  Args:
2776
    instance: the instance object
2777

2778
  Returns:
2779
    True or False showing the success of the creation process
2780

2781
  """
2782
  info = _GetInstanceInfoText(instance)
2783

    
2784
  if instance.disk_template == constants.DT_FILE:
2785
    file_storage_dir = os.path.dirname(instance.disks[0].logical_id[1])
2786
    result = rpc.call_file_storage_dir_create(instance.primary_node,
2787
                                              file_storage_dir)
2788

    
2789
    if not result:
2790
      logger.Error("Could not connect to node '%s'" % instance.primary_node)
2791
      return False
2792

    
2793
    if not result[0]:
2794
      logger.Error("failed to create directory '%s'" % file_storage_dir)
2795
      return False
2796

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

    
2814
  return True
2815

    
2816

    
2817
def _RemoveDisks(instance, cfg):
2818
  """Remove all disks for an instance.
2819

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

2825
  Args:
2826
    instance: the instance object
2827

2828
  Returns:
2829
    True or False showing the success of the removal proces
2830

2831
  """
2832
  logger.Info("removing block devices for instance %s" % instance.name)
2833

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

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

    
2851
  return result
2852

    
2853

    
2854
def _ComputeDiskSize(disk_template, disk_size, swap_size):
2855
  """Compute disk size requirements in the volume group
2856

2857
  This is currently hard-coded for the two-drive layout.
2858

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

    
2869
  if disk_template not in req_size_dict:
2870
    raise errors.ProgrammerError("Disk template '%s' size requirement"
2871
                                 " is unknown" %  disk_template)
2872

    
2873
  return req_size_dict[disk_template]
2874

    
2875

    
2876
class LUCreateInstance(LogicalUnit):
2877
  """Create an instance.
2878

2879
  """
2880
  HPATH = "instance-add"
2881
  HTYPE = constants.HTYPE_INSTANCE
2882
  _OP_REQP = ["instance_name", "mem_size", "disk_size",
2883
              "disk_template", "swap_size", "mode", "start", "vcpus",
2884
              "wait_for_sync", "ip_check", "mac"]
2885

    
2886
  def _RunAllocator(self):
2887
    """Run the allocator based on input opcode.
2888

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

    
2906
    ial.Run(self.op.iallocator)
2907

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

    
2924
  def BuildHooksEnv(self):
2925
    """Build hooks env.
2926

2927
    This runs on master, primary and secondary nodes of the instance.
2928

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

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

    
2951
    nl = ([self.sstore.GetMasterNode(), self.op.pnode] +
2952
          self.secondaries)
2953
    return env, nl, nl
2954

    
2955

    
2956
  def CheckPrereq(self):
2957
    """Check prerequisites.
2958

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

    
2967
    if self.op.mode not in (constants.INSTANCE_CREATE,
2968
                            constants.INSTANCE_IMPORT):
2969
      raise errors.OpPrereqError("Invalid instance creation mode '%s'" %
2970
                                 self.op.mode)
2971

    
2972
    if (not self.cfg.GetVGName() and
2973
        self.op.disk_template not in constants.DTS_NOT_LVM):
2974
      raise errors.OpPrereqError("Cluster does not support lvm-based"
2975
                                 " instances")
2976

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

    
2988
      if not os.path.isabs(src_path):
2989
        raise errors.OpPrereqError("The source path must be absolute")
2990

    
2991
      export_info = rpc.call_export_info(src_node, src_path)
2992

    
2993
      if not export_info:
2994
        raise errors.OpPrereqError("No export found in dir %s" % src_path)
2995

    
2996
      if not export_info.has_section(constants.INISECT_EXP):
2997
        raise errors.ProgrammerError("Corrupted export config")
2998

    
2999
      ei_version = export_info.get(constants.INISECT_EXP, 'version')
3000
      if (int(ei_version) != constants.EXPORT_VERSION):
3001
        raise errors.OpPrereqError("Wrong export version %s (wanted %d)" %
3002
                                   (ei_version, constants.EXPORT_VERSION))
3003

    
3004
      if int(export_info.get(constants.INISECT_INS, 'disk_count')) > 1:
3005
        raise errors.OpPrereqError("Can't import instance with more than"
3006
                                   " one data disk")
3007

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

    
3017
    #### instance parameters check
3018

    
3019
    # disk template and mirror node verification
3020
    if self.op.disk_template not in constants.DISK_TEMPLATES:
3021
      raise errors.OpPrereqError("Invalid disk template name")
3022

    
3023
    # instance name verification
3024
    hostname1 = utils.HostInfo(self.op.instance_name)
3025

    
3026
    self.op.instance_name = instance_name = hostname1.name
3027
    instance_list = self.cfg.GetInstanceList()
3028
    if instance_name in instance_list:
3029
      raise errors.OpPrereqError("Instance '%s' is already in the cluster" %
3030
                                 instance_name)
3031

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

    
3045
    if self.op.start and not self.op.ip_check:
3046
      raise errors.OpPrereqError("Cannot ignore IP address conflicts when"
3047
                                 " adding an instance in start mode")
3048

    
3049
    if self.op.ip_check:
3050
      if utils.TcpPing(hostname1.ip, constants.DEFAULT_NODED_PORT):
3051
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
3052
                                   (hostname1.ip, instance_name))
3053

    
3054
    # MAC address verification
3055
    if self.op.mac != "auto":
3056
      if not utils.IsValidMac(self.op.mac.lower()):
3057
        raise errors.OpPrereqError("invalid MAC address specified: %s" %
3058
                                   self.op.mac)
3059

    
3060
    # bridge verification
3061
    bridge = getattr(self.op, "bridge", None)
3062
    if bridge is None:
3063
      self.op.bridge = self.cfg.GetDefBridge()
3064
    else:
3065
      self.op.bridge = bridge
3066

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

    
3078
    if self.op.file_storage_dir and os.path.isabs(self.op.file_storage_dir):
3079
      raise errors.OpPrereqError("File storage directory not a relative"
3080
                                 " path")
3081
    #### allocator run
3082

    
3083
    if [self.op.iallocator, self.op.pnode].count(None) != 1:
3084
      raise errors.OpPrereqError("One and only one of iallocator and primary"
3085
                                 " node must be given")
3086

    
3087
    if self.op.iallocator is not None:
3088
      self._RunAllocator()
3089

    
3090
    #### node related checks
3091

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

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

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

    
3116
    req_size = _ComputeDiskSize(self.op.disk_template,
3117
                                self.op.disk_size, self.op.swap_size)
3118

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

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

    
3143
    if self.op.kernel_path == constants.VALUE_NONE:
3144
      raise errors.OpPrereqError("Can't set instance kernel to none")
3145

    
3146

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

    
3153
    # memory check on primary node
3154
    if self.op.start:
3155
      _CheckNodeFreeMemory(self.cfg, self.pnode.name,
3156
                           "creating instance %s" % self.op.instance_name,
3157
                           self.op.mem_size)
3158

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

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

    
3178
    if self.op.start:
3179
      self.instance_status = 'up'
3180
    else:
3181
      self.instance_status = 'down'
3182

    
3183
  def Exec(self, feedback_fn):
3184
    """Create and add the instance to the cluster.
3185

3186
    """
3187
    instance = self.op.instance_name
3188
    pnode_name = self.pnode.name
3189

    
3190
    if self.op.mac == "auto":
3191
      mac_address = self.cfg.GenerateMAC()
3192
    else:
3193
      mac_address = self.op.mac
3194

    
3195
    nic = objects.NIC(bridge=self.op.bridge, mac=mac_address)
3196
    if self.inst_ip is not None:
3197
      nic.ip = self.inst_ip
3198

    
3199
    ht_kind = self.sstore.GetHypervisorType()
3200
    if ht_kind in constants.HTS_REQ_PORT:
3201
      network_port = self.cfg.AllocatePort()
3202
    else:
3203
      network_port = None
3204

    
3205
    if self.op.vnc_bind_address is None:
3206
      self.op.vnc_bind_address = constants.VNC_DEFAULT_BIND_ADDRESS
3207

    
3208
    # this is needed because os.path.join does not accept None arguments
3209
    if self.op.file_storage_dir is None:
3210
      string_file_storage_dir = ""
3211
    else:
3212
      string_file_storage_dir = self.op.file_storage_dir
3213

    
3214
    # build the full file storage dir path
3215
    file_storage_dir = os.path.normpath(os.path.join(
3216
                                        self.sstore.GetFileStorageDir(),
3217
                                        string_file_storage_dir, instance))
3218

    
3219

    
3220
    disks = _GenerateDiskTemplate(self.cfg,
3221
                                  self.op.disk_template,
3222
                                  instance, pnode_name,
3223
                                  self.secondaries, self.op.disk_size,
3224
                                  self.op.swap_size,
3225
                                  file_storage_dir,
3226
                                  self.op.file_driver)
3227

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

    
3245
    feedback_fn("* creating instance disks...")
3246
    if not _CreateDisks(self.cfg, iobj):
3247
      _RemoveDisks(iobj, self.cfg)
3248
      raise errors.OpExecError("Device creation failed, reverting...")
3249

    
3250
    feedback_fn("adding instance %s to cluster config" % instance)
3251

    
3252
    self.cfg.AddInstance(iobj)
3253

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

    
3264
    if disk_abort:
3265
      _RemoveDisks(iobj, self.cfg)
3266
      self.cfg.RemoveInstance(iobj.name)
3267
      raise errors.OpExecError("There are some degraded disks for"
3268
                               " this instance")
3269

    
3270
    feedback_fn("creating os for instance %s on node %s" %
3271
                (instance, pnode_name))
3272

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

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

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

    
3301

    
3302
class LUConnectConsole(NoHooksLU):
3303
  """Connect to an instance's console.
3304

3305
  This is somewhat special in that it returns the command line that
3306
  you need to run on the master node in order to connect to the
3307
  console.
3308

3309
  """
3310
  _OP_REQP = ["instance_name"]
3311

    
3312
  def CheckPrereq(self):
3313
    """Check prerequisites.
3314

3315
    This checks that the instance is in the cluster.
3316

3317
    """
3318
    instance = self.cfg.GetInstanceInfo(
3319
      self.cfg.ExpandInstanceName(self.op.instance_name))
3320
    if instance is None:
3321
      raise errors.OpPrereqError("Instance '%s' not known" %
3322
                                 self.op.instance_name)
3323
    self.instance = instance
3324

    
3325
  def Exec(self, feedback_fn):
3326
    """Connect to the console of an instance
3327

3328
    """
3329
    instance = self.instance
3330
    node = instance.primary_node
3331

    
3332
    node_insts = rpc.call_instance_list([node])[node]
3333
    if node_insts is False:
3334
      raise errors.OpExecError("Can't connect to node %s." % node)
3335

    
3336
    if instance.name not in node_insts:
3337
      raise errors.OpExecError("Instance %s is not running." % instance.name)
3338

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

    
3341
    hyper = hypervisor.GetHypervisor()
3342
    console_cmd = hyper.GetShellCommandForConsole(instance)
3343

    
3344
    # build ssh cmdline
3345
    return self.ssh.BuildCmd(node, "root", console_cmd, batch=True, tty=True)
3346

    
3347

    
3348
class LUReplaceDisks(LogicalUnit):
3349
  """Replace the disks of an instance.
3350

3351
  """
3352
  HPATH = "mirrors-replace"
3353
  HTYPE = constants.HTYPE_INSTANCE
3354
  _OP_REQP = ["instance_name", "mode", "disks"]
3355

    
3356
  def _RunAllocator(self):
3357
    """Compute a new secondary node using an IAllocator.
3358

3359
    """
3360
    ial = IAllocator(self.cfg, self.sstore,
3361
                     mode=constants.IALLOCATOR_MODE_RELOC,
3362
                     name=self.op.instance_name,
3363
                     relocate_from=[self.sec_node])
3364

    
3365
    ial.Run(self.op.iallocator)
3366

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

    
3379
  def BuildHooksEnv(self):
3380
    """Build hooks env.
3381

3382
    This runs on the master, the primary and all the secondaries.
3383

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

    
3399
  def CheckPrereq(self):
3400
    """Check prerequisites.
3401

3402
    This checks that the instance is in the cluster.
3403

3404
    """
3405
    if not hasattr(self.op, "remote_node"):
3406
      self.op.remote_node = None
3407

    
3408
    instance = self.cfg.GetInstanceInfo(
3409
      self.cfg.ExpandInstanceName(self.op.instance_name))
3410
    if instance is None:
3411
      raise errors.OpPrereqError("Instance '%s' not known" %
3412
                                 self.op.instance_name)
3413
    self.instance = instance
3414
    self.op.instance_name = instance.name
3415

    
3416
    if instance.disk_template not in constants.DTS_NET_MIRROR:
3417
      raise errors.OpPrereqError("Instance's disk layout is not"
3418
                                 " network mirrored.")
3419

    
3420
    if len(instance.secondary_nodes) != 1:
3421
      raise errors.OpPrereqError("The instance has a strange layout,"
3422
                                 " expected one secondary but found %d" %
3423
                                 len(instance.secondary_nodes))
3424

    
3425
    self.sec_node = instance.secondary_nodes[0]
3426

    
3427
    ia_name = getattr(self.op, "iallocator", None)
3428
    if ia_name is not None:
3429
      if self.op.remote_node is not None:
3430
        raise errors.OpPrereqError("Give either the iallocator or the new"
3431
                                   " secondary, not both")
3432
      self.op.remote_node = self._RunAllocator()
3433

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

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

    
3477
    for name in self.op.disks:
3478
      if instance.FindDisk(name) is None:
3479
        raise errors.OpPrereqError("Disk '%s' not found for instance '%s'" %
3480
                                   (name, instance.name))
3481
    self.op.remote_node = remote_node
3482

    
3483
  def _ExecD8DiskOnly(self, feedback_fn):
3484
    """Replace a disk on the primary or secondary for dbrd8.
3485

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

3497
    Failures are not very well handled.
3498

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

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

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

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

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

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

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

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

    
3608
      for old, new in zip(old_lvs, new_lvs):
3609
        new.logical_id = old.logical_id
3610
        cfg.SetDiskID(new, tgt_node)
3611

    
3612
      for disk in old_lvs:
3613
        disk.logical_id = ren_fn(disk, temp_suffix)
3614
        cfg.SetDiskID(disk, tgt_node)
3615

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

    
3625
      dev.children = new_lvs
3626
      cfg.Update(instance)
3627

    
3628
    # Step: wait for sync
3629

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

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

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

    
3653
  def _ExecD8Secondary(self, feedback_fn):
3654
    """Replace the secondary node for drbd8.
3655

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

3669
    Failures are not very well handled.
3670

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

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

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

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

    
3730
      iv_names[dev.iv_name] = (dev, dev.children)
3731

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

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

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

    
3770
    if not done:
3771
      # no detaches succeeded (very unlikely)
3772
      raise errors.OpExecError("Can't detach at least one DRBD from old node")
3773

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

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

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

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

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

    
3817
  def Exec(self, feedback_fn):
3818
    """Execute disk replacement.
3819

3820
    This dispatches the disk replacement to the appropriate handler.
3821

3822
    """
3823
    instance = self.instance
3824

    
3825
    # Activate the instance disks if we're replacing them on a down instance
3826
    if instance.status == "down":
3827
      op = opcodes.OpActivateInstanceDisks(instance_name=instance.name)
3828
      self.proc.ChainOpCode(op)
3829

    
3830
    if instance.disk_template == constants.DT_DRBD8:
3831
      if self.op.remote_node is None:
3832
        fn = self._ExecD8DiskOnly
3833
      else:
3834
        fn = self._ExecD8Secondary
3835
    else:
3836
      raise errors.ProgrammerError("Unhandled disk replacement case")
3837

    
3838
    ret = fn(feedback_fn)
3839

    
3840
    # Deactivate the instance disks if we're replacing them on a down instance
3841
    if instance.status == "down":
3842
      op = opcodes.OpDeactivateInstanceDisks(instance_name=instance.name)
3843
      self.proc.ChainOpCode(op)
3844

    
3845
    return ret
3846

    
3847

    
3848
class LUGrowDisk(LogicalUnit):
3849
  """Grow a disk of an instance.
3850

3851
  """
3852
  HPATH = "disk-grow"
3853
  HTYPE = constants.HTYPE_INSTANCE
3854
  _OP_REQP = ["instance_name", "disk", "amount"]
3855

    
3856
  def BuildHooksEnv(self):
3857
    """Build hooks env.
3858

3859
    This runs on the master, the primary and all the secondaries.
3860

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

    
3873
  def CheckPrereq(self):
3874
    """Check prerequisites.
3875

3876
    This checks that the instance is in the cluster.
3877

3878
    """
3879
    instance = self.cfg.GetInstanceInfo(
3880
      self.cfg.ExpandInstanceName(self.op.instance_name))
3881
    if instance is None:
3882
      raise errors.OpPrereqError("Instance '%s' not known" %
3883
                                 self.op.instance_name)
3884
    self.instance = instance
3885
    self.op.instance_name = instance.name
3886

    
3887
    if instance.disk_template not in (constants.DT_PLAIN, constants.DT_DRBD8):
3888
      raise errors.OpPrereqError("Instance's disk layout does not support"
3889
                                 " growing.")
3890

    
3891
    if instance.FindDisk(self.op.disk) is None:
3892
      raise errors.OpPrereqError("Disk '%s' not found for instance '%s'" %
3893
                                 (self.op.disk, instance.name))
3894

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

    
3911
  def Exec(self, feedback_fn):
3912
    """Execute disk grow.
3913

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

    
3929

    
3930
class LUQueryInstanceData(NoHooksLU):
3931
  """Query runtime instance data.
3932

3933
  """
3934
  _OP_REQP = ["instances"]
3935

    
3936
  def CheckPrereq(self):
3937
    """Check prerequisites.
3938

3939
    This only checks the optional instance list against the existing names.
3940

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

    
3957

    
3958
  def _ComputeDiskStatus(self, instance, snode, dev):
3959
    """Compute block device status.
3960

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

    
3971
    if snode:
3972
      self.cfg.SetDiskID(dev, snode)
3973
      dev_sstatus = rpc.call_blockdev_find(snode, dev)
3974
    else:
3975
      dev_sstatus = None
3976

    
3977
    if dev.children:
3978
      dev_children = [self._ComputeDiskStatus(instance, snode, child)
3979
                      for child in dev.children]
3980
    else:
3981
      dev_children = []
3982

    
3983
    data = {
3984
      "iv_name": dev.iv_name,
3985
      "dev_type": dev.dev_type,
3986
      "logical_id": dev.logical_id,
3987
      "physical_id": dev.physical_id,
3988
      "pstatus": dev_pstatus,
3989
      "sstatus": dev_sstatus,
3990
      "children": dev_children,
3991
      }
3992

    
3993
    return data
3994

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

    
4010
      disks = [self._ComputeDiskStatus(instance, None, device)
4011
               for device in instance.disks]
4012

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

    
4026
      htkind = self.sstore.GetHypervisorType()
4027
      if htkind == constants.HT_XEN_PVM30:
4028
        idict["kernel_path"] = instance.kernel_path
4029
        idict["initrd_path"] = instance.initrd_path
4030

    
4031
      if htkind == constants.HT_XEN_HVM31:
4032
        idict["hvm_boot_order"] = instance.hvm_boot_order
4033
        idict["hvm_acpi"] = instance.hvm_acpi
4034
        idict["hvm_pae"] = instance.hvm_pae
4035
        idict["hvm_cdrom_image_path"] = instance.hvm_cdrom_image_path
4036

    
4037
      if htkind in constants.HTS_REQ_PORT:
4038
        idict["vnc_bind_address"] = instance.vnc_bind_address
4039
        idict["network_port"] = instance.network_port
4040

    
4041
      result[instance.name] = idict
4042

    
4043
    return result
4044

    
4045

    
4046
class LUSetInstanceParams(LogicalUnit):
4047
  """Modifies an instances's parameters.
4048

4049
  """
4050
  HPATH = "instance-modify"
4051
  HTYPE = constants.HTYPE_INSTANCE
4052
  _OP_REQP = ["instance_name"]
4053

    
4054
  def BuildHooksEnv(self):
4055
    """Build hooks env.
4056

4057
    This runs on the master, primary and secondaries.
4058

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

    
4084
  def CheckPrereq(self):
4085
    """Check prerequisites.
4086

4087
    This only checks the instance list against the existing names.
4088

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

    
4135
    if self.kernel_path is not None:
4136
      self.do_kernel_path = True
4137
      if self.kernel_path == constants.VALUE_NONE:
4138
        raise errors.OpPrereqError("Can't set instance to no kernel")
4139

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

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

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

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

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

    
4184
    instance = self.cfg.GetInstanceInfo(
4185
      self.cfg.ExpandInstanceName(self.op.instance_name))
4186
    if instance is None:
4187
      raise errors.OpPrereqError("No such instance name '%s'" %
4188
                                 self.op.instance_name)
4189
    self.op.instance_name = instance.name
4190
    self.instance = instance
4191
    return
4192

    
4193
  def Exec(self, feedback_fn):
4194
    """Modifies an instance.
4195

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

    
4240
    self.cfg.AddInstance(instance)
4241

    
4242
    return result
4243

    
4244

    
4245
class LUQueryExports(NoHooksLU):
4246
  """Query the exports list
4247

4248
  """
4249
  _OP_REQP = []
4250

    
4251
  def CheckPrereq(self):
4252
    """Check that the nodelist contains only existing nodes.
4253

4254
    """
4255
    self.nodes = _GetWantedNodes(self, getattr(self.op, "nodes", None))
4256

    
4257
  def Exec(self, feedback_fn):
4258
    """Compute the list of all the exported system images.
4259

4260
    Returns:
4261
      a dictionary with the structure node->(export-list)
4262
      where export-list is a list of the instances exported on
4263
      that node.
4264

4265
    """
4266
    return rpc.call_export_list(self.nodes)
4267

    
4268

    
4269
class LUExportInstance(LogicalUnit):
4270
  """Export an instance to an image in the cluster.
4271

4272
  """
4273
  HPATH = "instance-export"
4274
  HTYPE = constants.HTYPE_INSTANCE
4275
  _OP_REQP = ["instance_name", "target_node", "shutdown"]
4276

    
4277
  def BuildHooksEnv(self):
4278
    """Build hooks env.
4279

4280
    This will run on the master, primary node and target node.
4281

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

    
4292
  def CheckPrereq(self):
4293
    """Check prerequisites.
4294

4295
    This checks that the instance and node names are valid.
4296

4297
    """
4298
    instance_name = self.cfg.ExpandInstanceName(self.op.instance_name)
4299
    self.instance = self.cfg.GetInstanceInfo(instance_name)
4300
    if self.instance is None:
4301
      raise errors.OpPrereqError("Instance '%s' not found" %
4302
                                 self.op.instance_name)
4303

    
4304
    # node verification
4305
    dst_node_short = self.cfg.ExpandNodeName(self.op.target_node)
4306
    self.dst_node = self.cfg.GetNodeInfo(dst_node_short)
4307

    
4308
    if self.dst_node is None:
4309
      raise errors.OpPrereqError("Destination node '%s' is unknown." %
4310
                                 self.op.target_node)
4311
    self.op.target_node = self.dst_node.name
4312

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

    
4319
  def Exec(self, feedback_fn):
4320
    """Export an instance to an image in the cluster.
4321

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

    
4332
    vgname = self.cfg.GetVGName()
4333

    
4334
    snap_disks = []
4335

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

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

    
4352
    finally:
4353
      if self.op.shutdown and instance.status == "up":
4354
        if not rpc.call_instance_start(src_node, instance, None):
4355
          _ShutdownInstanceDisks(instance, self.cfg)
4356
          raise errors.OpExecError("Could not start instance")
4357

    
4358
    # TODO: check for size
4359

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

    
4368
    if not rpc.call_finalize_export(dst_node.name, instance, snap_disks):
4369
      logger.Error("could not finalize export for instance %s on node %s" %
4370
                   (instance.name, dst_node.name))
4371

    
4372
    nodelist = self.cfg.GetNodeList()
4373
    nodelist.remove(dst_node.name)
4374

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

    
4387

    
4388
class LURemoveExport(NoHooksLU):
4389
  """Remove exports related to the named instance.
4390

4391
  """
4392
  _OP_REQP = ["instance_name"]
4393

    
4394
  def CheckPrereq(self):
4395
    """Check prerequisites.
4396
    """
4397
    pass
4398

    
4399
  def Exec(self, feedback_fn):
4400
    """Remove any export.
4401

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

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

    
4421
    if fqdn_warn and not found:
4422
      feedback_fn("Export not found. If trying to remove an export belonging"
4423
                  " to a deleted instance please use its Fully Qualified"
4424
                  " Domain Name.")
4425

    
4426

    
4427
class TagsLU(NoHooksLU):
4428
  """Generic tags LU.
4429

4430
  This is an abstract class which is the parent of all the other tags LUs.
4431

4432
  """
4433
  def CheckPrereq(self):
4434
    """Check prerequisites.
4435

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

    
4457

    
4458
class LUGetTags(TagsLU):
4459
  """Returns the tags of a given object.
4460

4461
  """
4462
  _OP_REQP = ["kind", "name"]
4463

    
4464
  def Exec(self, feedback_fn):
4465
    """Returns the tag list.
4466

4467
    """
4468
    return self.target.GetTags()
4469

    
4470

    
4471
class LUSearchTags(NoHooksLU):
4472
  """Searches the tags for a given pattern.
4473

4474
  """
4475
  _OP_REQP = ["pattern"]
4476

    
4477
  def CheckPrereq(self):
4478
    """Check prerequisites.
4479

4480
    This checks the pattern passed for validity by compiling it.
4481

4482
    """
4483
    try:
4484
      self.re = re.compile(self.op.pattern)
4485
    except re.error, err:
4486
      raise errors.OpPrereqError("Invalid search pattern '%s': %s" %
4487
                                 (self.op.pattern, err))
4488

    
4489
  def Exec(self, feedback_fn):
4490
    """Returns the tag list.
4491

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

    
4506

    
4507
class LUAddTags(TagsLU):
4508
  """Sets a tag on a given object.
4509

4510
  """
4511
  _OP_REQP = ["kind", "name", "tags"]
4512

    
4513
  def CheckPrereq(self):
4514
    """Check prerequisites.
4515

4516
    This checks the type and length of the tag name and value.
4517

4518
    """
4519
    TagsLU.CheckPrereq(self)
4520
    for tag in self.op.tags:
4521
      objects.TaggableObject.ValidateTag(tag)
4522

    
4523
  def Exec(self, feedback_fn):
4524
    """Sets the tag.
4525

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

    
4539

    
4540
class LUDelTags(TagsLU):
4541
  """Delete a list of tags from a given object.
4542

4543
  """
4544
  _OP_REQP = ["kind", "name", "tags"]
4545

    
4546
  def CheckPrereq(self):
4547
    """Check prerequisites.
4548

4549
    This checks that we have the given tag.
4550

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

    
4564
  def Exec(self, feedback_fn):
4565
    """Remove the tag from the object.
4566

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

    
4577
class LUTestDelay(NoHooksLU):
4578
  """Sleep for a specified amount of time.
4579

4580
  This LU sleeps on the master and/or nodes for a specified amoutn of
4581
  time.
4582

4583
  """
4584
  _OP_REQP = ["duration", "on_master", "on_nodes"]
4585

    
4586
  def CheckPrereq(self):
4587
    """Check prerequisites.
4588

4589
    This checks that we have a good list of nodes and/or the duration
4590
    is valid.
4591

4592
    """
4593

    
4594
    if self.op.on_nodes:
4595
      self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
4596

    
4597
  def Exec(self, feedback_fn):
4598
    """Do the actual sleep.
4599

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

    
4613

    
4614
class IAllocator(object):
4615
  """IAllocator framework.
4616

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

4626
  """
4627
  _ALLO_KEYS = [
4628
    "mem_size", "disks", "disk_template",
4629
    "os", "tags", "nics", "vcpus",
4630
    ]
4631
  _RELO_KEYS = [
4632
    "relocate_from",
4633
    ]
4634

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

    
4668
  def _ComputeClusterData(self):
4669
    """Compute the generic allocator input data.
4670

4671
    This is the data that is independent of the actual operation.
4672

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

    
4684
    i_list = [cfg.GetInstanceInfo(iname) for iname in cfg.GetInstanceList()]
4685

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

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

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

    
4748
    data["instances"] = instance_data
4749

    
4750
    self.in_data = data
4751

    
4752
  def _AddNewInstance(self):
4753
    """Add new instance data to allocator structure.
4754

4755
    This in combination with _AllocatorGetClusterData will create the
4756
    correct structure needed as input for the allocator.
4757

4758
    The checks for the completeness of the opcode must have already been
4759
    done.
4760

4761
    """
4762
    data = self.in_data
4763
    if len(self.disks) != 2:
4764
      raise errors.OpExecError("Only two-disk configurations supported")
4765

    
4766
    disk_space = _ComputeDiskSize(self.disk_template,
4767
                                  self.disks[0]["size"], self.disks[1]["size"])
4768

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

    
4788
  def _AddRelocateInstance(self):
4789
    """Add relocate instance data to allocator structure.
4790

4791
    This in combination with _IAllocatorGetClusterData will create the
4792
    correct structure needed as input for the allocator.
4793

4794
    The checks for the completeness of the opcode must have already been
4795
    done.
4796

4797
    """
4798
    instance = self.cfg.GetInstanceInfo(self.name)
4799
    if instance is None:
4800
      raise errors.ProgrammerError("Unknown instance '%s' passed to"
4801
                                   " IAllocator" % self.name)
4802

    
4803
    if instance.disk_template not in constants.DTS_NET_MIRROR:
4804
      raise errors.OpPrereqError("Can't relocate non-mirrored instances")
4805

    
4806
    if len(instance.secondary_nodes) != 1:
4807
      raise errors.OpPrereqError("Instance has not exactly one secondary node")
4808

    
4809
    self.required_nodes = 1
4810

    
4811
    disk_space = _ComputeDiskSize(instance.disk_template,
4812
                                  instance.disks[0].size,
4813
                                  instance.disks[1].size)
4814

    
4815
    request = {
4816
      "type": "relocate",
4817
      "name": self.name,
4818
      "disk_space_total": disk_space,
4819
      "required_nodes": self.required_nodes,
4820
      "relocate_from": self.relocate_from,
4821
      }
4822
    self.in_data["request"] = request
4823

    
4824
  def _BuildInputData(self):
4825
    """Build input data structures.
4826

4827
    """
4828
    self._ComputeClusterData()
4829

    
4830
    if self.mode == constants.IALLOCATOR_MODE_ALLOC:
4831
      self._AddNewInstance()
4832
    else:
4833
      self._AddRelocateInstance()
4834

    
4835
    self.in_text = serializer.Dump(self.in_data)
4836

    
4837
  def Run(self, name, validate=True, call_fn=rpc.call_iallocator_runner):
4838
    """Run an instance allocator and return the results.
4839

4840
    """
4841
    data = self.in_text
4842

    
4843
    result = call_fn(self.sstore.GetMasterNode(), name, self.in_text)
4844

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

    
4848
    rcode, stdout, stderr, fail = result
4849

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

    
4860
  def _ValidateResult(self):
4861
    """Process the allocator results.
4862

4863
    This will process and if successful save the result in
4864
    self.out_data and the other parameters.
4865

4866
    """
4867
    try:
4868
      rdict = serializer.Load(self.out_text)
4869
    except Exception, err:
4870
      raise errors.OpExecError("Can't parse iallocator results: %s" % str(err))
4871

    
4872
    if not isinstance(rdict, dict):
4873
      raise errors.OpExecError("Can't parse iallocator results: not a dict")
4874

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

    
4881
    if not isinstance(rdict["nodes"], list):
4882
      raise errors.OpExecError("Can't parse iallocator results: 'nodes' key"
4883
                               " is not a list")
4884
    self.out_data = rdict
4885

    
4886

    
4887
class LUTestAllocator(NoHooksLU):
4888
  """Run allocator tests.
4889

4890
  This LU runs the allocator tests
4891

4892
  """
4893
  _OP_REQP = ["direction", "mode", "name"]
4894

    
4895
  def CheckPrereq(self):
4896
    """Check prerequisites.
4897

4898
    This checks the opcode parameters depending on the director and mode test.
4899

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

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

    
4952
  def Exec(self, feedback_fn):
4953
    """Run the allocator test.
4954

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

    
4975
    if self.op.direction == constants.IALLOCATOR_DIR_IN:
4976
      result = ial.in_text
4977
    else:
4978
      ial.Run(self.op.allocator, validate=False)
4979
      result = ial.out_text
4980
    return result