Statistics
| Branch: | Tag: | Revision:

root / lib / cmdlib.py @ d489ca4f

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
      result = utils.RunCmd(["fping", "-q", new_ip])
884
      if not result.failed:
885
        raise errors.OpPrereqError("The given cluster IP address (%s) is"
886
                                   " reachable on the network. Aborting." %
887
                                   new_ip)
888

    
889
    self.op.name = new_name
890

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

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

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

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

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

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

    
928

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

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

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

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

    
945

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

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

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

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

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

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

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

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

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

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

    
1002

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

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

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

    
1013
  node = instance.primary_node
1014

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

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

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

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

    
1068

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

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

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

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

    
1095
  return result
1096

    
1097

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

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

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

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

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

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

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

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

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

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

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

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

    
1175
    return output
1176

    
1177

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

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

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

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

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

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

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

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

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

    
1216
    instance_list = self.cfg.GetInstanceList()
1217

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

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

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

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

    
1242
    rpc.call_node_leave_cluster(node.name)
1243

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

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

    
1248
    utils.RemoveHostFromEtcHosts(node.name)
1249

    
1250

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

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

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

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

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

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

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

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

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

    
1285
    # begin data gathering
1286

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

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

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

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

    
1323
    # end data gathering
1324

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

    
1352
    return output
1353

    
1354

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

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

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

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

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

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

    
1373

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

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

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

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

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

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

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

    
1420
        output.append(node_output)
1421

    
1422
    return output
1423

    
1424

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

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

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

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

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

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

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

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

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

    
1463
    dns_data = utils.HostInfo(node_name)
1464

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1643
    to_copy = ss.GetFileList()
1644
    if self.sstore.GetHypervisorType() == constants.HT_XEN_HVM31:
1645
      to_copy.append(constants.VNC_PASSWORD_FILE)
1646
    for fname in to_copy:
1647
      if not self.ssh.CopyFileToNode(node, fname):
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
      command = ["fping", "-q", name_info.ip]
2288
      result = utils.RunCmd(command)
2289
      if not result.failed:
2290
        raise errors.OpPrereqError("IP %s of instance %s already in use" %
2291
                                   (name_info.ip, new_name))
2292

    
2293

    
2294
  def Exec(self, feedback_fn):
2295
    """Reinstall the instance.
2296

2297
    """
2298
    inst = self.instance
2299
    old_name = inst.name
2300

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

    
2304
    self.cfg.RenameInstance(inst.name, self.op.new_name)
2305

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

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

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

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

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

    
2339

    
2340
class LURemoveInstance(LogicalUnit):
2341
  """Remove an instance.
2342

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

    
2348
  def BuildHooksEnv(self):
2349
    """Build hooks env.
2350

2351
    This runs on master, primary and secondary nodes of the instance.
2352

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

    
2358
  def CheckPrereq(self):
2359
    """Check prerequisites.
2360

2361
    This checks that the instance is in the cluster.
2362

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

    
2371
  def Exec(self, feedback_fn):
2372
    """Remove the instance.
2373

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

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

    
2386
    logger.Info("removing block devices for instance %s" % instance.name)
2387

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

    
2394
    logger.Info("removing instance %s out of cluster config" % instance.name)
2395

    
2396
    self.cfg.RemoveInstance(instance.name)
2397

    
2398

    
2399
class LUQueryInstances(NoHooksLU):
2400
  """Logical unit for querying instances.
2401

2402
  """
2403
  _OP_REQP = ["output_fields", "names"]
2404

    
2405
  def CheckPrereq(self):
2406
    """Check prerequisites.
2407

2408
    This checks that the fields required are valid output fields.
2409

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

    
2419
    self.wanted = _GetWantedInstances(self, self.op.names)
2420

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

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

    
2429
    # begin data gathering
2430

    
2431
    nodes = frozenset([inst.primary_node for inst in instance_list])
2432

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

    
2447
    # end data gathering
2448

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

    
2515
    return output
2516

    
2517

    
2518
class LUFailoverInstance(LogicalUnit):
2519
  """Failover an instance.
2520

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

    
2526
  def BuildHooksEnv(self):
2527
    """Build hooks env.
2528

2529
    This runs on master, primary and secondary nodes of the instance.
2530

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

    
2539
  def CheckPrereq(self):
2540
    """Check prerequisites.
2541

2542
    This checks that the instance is in the cluster.
2543

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

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

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

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

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

    
2572
    self.instance = instance
2573

    
2574
  def Exec(self, feedback_fn):
2575
    """Failover an instance.
2576

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

2580
    """
2581
    instance = self.instance
2582

    
2583
    source_node = instance.primary_node
2584
    target_node = instance.secondary_nodes[0]
2585

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

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

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

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

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

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

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

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

    
2633

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

2637
  This always creates all devices.
2638

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

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

    
2654

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

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

2661
  If not, just recurse to children keeping the same 'force' value.
2662

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

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

    
2683

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

2687
  This will generate a logical volume name for the given instance.
2688

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

    
2696

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

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

    
2713

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

2720
  """
2721
  #TODO: compute space requirements
2722

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

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

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

    
2764

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

2768
  """
2769
  return "originstname+%s" % instance.name
2770

    
2771

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

2775
  This abstracts away some work from AddInstance.
2776

2777
  Args:
2778
    instance: the instance object
2779

2780
  Returns:
2781
    True or False showing the success of the creation process
2782

2783
  """
2784
  info = _GetInstanceInfoText(instance)
2785

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

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

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

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

    
2816
  return True
2817

    
2818

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

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

2827
  Args:
2828
    instance: the instance object
2829

2830
  Returns:
2831
    True or False showing the success of the removal proces
2832

2833
  """
2834
  logger.Info("removing block devices for instance %s" % instance.name)
2835

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

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

    
2853
  return result
2854

    
2855

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

2859
  This is currently hard-coded for the two-drive layout.
2860

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

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

    
2875
  return req_size_dict[disk_template]
2876

    
2877

    
2878
class LUCreateInstance(LogicalUnit):
2879
  """Create an instance.
2880

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

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

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

    
2908
    ial.Run(self.op.iallocator)
2909

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

    
2926
  def BuildHooksEnv(self):
2927
    """Build hooks env.
2928

2929
    This runs on master, primary and secondary nodes of the instance.
2930

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

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

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

    
2957

    
2958
  def CheckPrereq(self):
2959
    """Check prerequisites.
2960

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

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

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

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

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

    
2993
      export_info = rpc.call_export_info(src_node, src_path)
2994

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

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

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

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

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

    
3019
    #### instance parameters check
3020

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

    
3025
    # instance name verification
3026
    hostname1 = utils.HostInfo(self.op.instance_name)
3027

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

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

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

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

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

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

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

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

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

    
3089
    if self.op.iallocator is not None:
3090
      self._RunAllocator()
3091

    
3092
    #### node related checks
3093

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

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

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

    
3118
    req_size = _ComputeDiskSize(self.op.disk_template,
3119
                                self.op.disk_size, self.op.swap_size)
3120

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

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

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

    
3148

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

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

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

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

    
3180
    if self.op.start:
3181
      self.instance_status = 'up'
3182
    else:
3183
      self.instance_status = 'down'
3184

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

3188
    """
3189
    instance = self.op.instance_name
3190
    pnode_name = self.pnode.name
3191

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

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

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

    
3207
    if self.op.vnc_bind_address is None:
3208
      self.op.vnc_bind_address = constants.VNC_DEFAULT_BIND_ADDRESS
3209

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

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

    
3221

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

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

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

    
3252
    feedback_fn("adding instance %s to cluster config" % instance)
3253

    
3254
    self.cfg.AddInstance(iobj)
3255

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

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

    
3272
    feedback_fn("creating os for instance %s on node %s" %
3273
                (instance, pnode_name))
3274

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

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

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

    
3303

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

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

3311
  """
3312
  _OP_REQP = ["instance_name"]
3313

    
3314
  def CheckPrereq(self):
3315
    """Check prerequisites.
3316

3317
    This checks that the instance is in the cluster.
3318

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

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

3330
    """
3331
    instance = self.instance
3332
    node = instance.primary_node
3333

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

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

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

    
3343
    hyper = hypervisor.GetHypervisor()
3344
    console_cmd = hyper.GetShellCommandForConsole(instance)
3345

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

    
3349

    
3350
class LUReplaceDisks(LogicalUnit):
3351
  """Replace the disks of an instance.
3352

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

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

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

    
3367
    ial.Run(self.op.iallocator)
3368

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

    
3381
  def BuildHooksEnv(self):
3382
    """Build hooks env.
3383

3384
    This runs on the master, the primary and all the secondaries.
3385

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

    
3401
  def CheckPrereq(self):
3402
    """Check prerequisites.
3403

3404
    This checks that the instance is in the cluster.
3405

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

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

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

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

    
3427
    self.sec_node = instance.secondary_nodes[0]
3428

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

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

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

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

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

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

3499
    Failures are not very well handled.
3500

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

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

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

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

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

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

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

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

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

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

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

    
3627
      dev.children = new_lvs
3628
      cfg.Update(instance)
3629

    
3630
    # Step: wait for sync
3631

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

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

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

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

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

3671
    Failures are not very well handled.
3672

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

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

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

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

    
3732
      iv_names[dev.iv_name] = (dev, dev.children)
3733

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

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

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

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

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

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

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

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

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

    
3819
  def Exec(self, feedback_fn):
3820
    """Execute disk replacement.
3821

3822
    This dispatches the disk replacement to the appropriate handler.
3823

3824
    """
3825
    instance = self.instance
3826

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

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

    
3840
    ret = fn(feedback_fn)
3841

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

    
3847
    return ret
3848

    
3849

    
3850
class LUGrowDisk(LogicalUnit):
3851
  """Grow a disk of an instance.
3852

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

    
3858
  def BuildHooksEnv(self):
3859
    """Build hooks env.
3860

3861
    This runs on the master, the primary and all the secondaries.
3862

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

    
3875
  def CheckPrereq(self):
3876
    """Check prerequisites.
3877

3878
    This checks that the instance is in the cluster.
3879

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

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

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

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

    
3913
  def Exec(self, feedback_fn):
3914
    """Execute disk grow.
3915

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

    
3931

    
3932
class LUQueryInstanceData(NoHooksLU):
3933
  """Query runtime instance data.
3934

3935
  """
3936
  _OP_REQP = ["instances"]
3937

    
3938
  def CheckPrereq(self):
3939
    """Check prerequisites.
3940

3941
    This only checks the optional instance list against the existing names.
3942

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

    
3959

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

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

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

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

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

    
3995
    return data
3996

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

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

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

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

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

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

    
4043
      result[instance.name] = idict
4044

    
4045
    return result
4046

    
4047

    
4048
class LUSetInstanceParams(LogicalUnit):
4049
  """Modifies an instances's parameters.
4050

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

    
4056
  def BuildHooksEnv(self):
4057
    """Build hooks env.
4058

4059
    This runs on the master, primary and secondaries.
4060

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

    
4086
  def CheckPrereq(self):
4087
    """Check prerequisites.
4088

4089
    This only checks the instance list against the existing names.
4090

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

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

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

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

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

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

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

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

    
4195
  def Exec(self, feedback_fn):
4196
    """Modifies an instance.
4197

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

    
4242
    self.cfg.AddInstance(instance)
4243

    
4244
    return result
4245

    
4246

    
4247
class LUQueryExports(NoHooksLU):
4248
  """Query the exports list
4249

4250
  """
4251
  _OP_REQP = []
4252

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

4256
    """
4257
    self.nodes = _GetWantedNodes(self, getattr(self.op, "nodes", None))
4258

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

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

4267
    """
4268
    return rpc.call_export_list(self.nodes)
4269

    
4270

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

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

    
4279
  def BuildHooksEnv(self):
4280
    """Build hooks env.
4281

4282
    This will run on the master, primary node and target node.
4283

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

    
4294
  def CheckPrereq(self):
4295
    """Check prerequisites.
4296

4297
    This checks that the instance and node names are valid.
4298

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

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

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

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

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

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

    
4334
    vgname = self.cfg.GetVGName()
4335

    
4336
    snap_disks = []
4337

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

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

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

    
4360
    # TODO: check for size
4361

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

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

    
4374
    nodelist = self.cfg.GetNodeList()
4375
    nodelist.remove(dst_node.name)
4376

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

    
4389

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

4393
  """
4394
  _OP_REQP = ["instance_name"]
4395

    
4396
  def CheckPrereq(self):
4397
    """Check prerequisites.
4398
    """
4399
    pass
4400

    
4401
  def Exec(self, feedback_fn):
4402
    """Remove any export.
4403

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

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

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

    
4428

    
4429
class TagsLU(NoHooksLU):
4430
  """Generic tags LU.
4431

4432
  This is an abstract class which is the parent of all the other tags LUs.
4433

4434
  """
4435
  def CheckPrereq(self):
4436
    """Check prerequisites.
4437

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

    
4459

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

4463
  """
4464
  _OP_REQP = ["kind", "name"]
4465

    
4466
  def Exec(self, feedback_fn):
4467
    """Returns the tag list.
4468

4469
    """
4470
    return self.target.GetTags()
4471

    
4472

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

4476
  """
4477
  _OP_REQP = ["pattern"]
4478

    
4479
  def CheckPrereq(self):
4480
    """Check prerequisites.
4481

4482
    This checks the pattern passed for validity by compiling it.
4483

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

    
4491
  def Exec(self, feedback_fn):
4492
    """Returns the tag list.
4493

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

    
4508

    
4509
class LUAddTags(TagsLU):
4510
  """Sets a tag on a given object.
4511

4512
  """
4513
  _OP_REQP = ["kind", "name", "tags"]
4514

    
4515
  def CheckPrereq(self):
4516
    """Check prerequisites.
4517

4518
    This checks the type and length of the tag name and value.
4519

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

    
4525
  def Exec(self, feedback_fn):
4526
    """Sets the tag.
4527

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

    
4541

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

4545
  """
4546
  _OP_REQP = ["kind", "name", "tags"]
4547

    
4548
  def CheckPrereq(self):
4549
    """Check prerequisites.
4550

4551
    This checks that we have the given tag.
4552

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

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

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

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

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

4585
  """
4586
  _OP_REQP = ["duration", "on_master", "on_nodes"]
4587

    
4588
  def CheckPrereq(self):
4589
    """Check prerequisites.
4590

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

4594
    """
4595

    
4596
    if self.op.on_nodes:
4597
      self.op.on_nodes = _GetWantedNodes(self, self.op.on_nodes)
4598

    
4599
  def Exec(self, feedback_fn):
4600
    """Do the actual sleep.
4601

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

    
4615

    
4616
class IAllocator(object):
4617
  """IAllocator framework.
4618

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

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

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

    
4670
  def _ComputeClusterData(self):
4671
    """Compute the generic allocator input data.
4672

4673
    This is the data that is independent of the actual operation.
4674

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

    
4686
    i_list = [cfg.GetInstanceInfo(iname) for iname in cfg.GetInstanceList()]
4687

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

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

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

    
4750
    data["instances"] = instance_data
4751

    
4752
    self.in_data = data
4753

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

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

4760
    The checks for the completeness of the opcode must have already been
4761
    done.
4762

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

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

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

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

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

4796
    The checks for the completeness of the opcode must have already been
4797
    done.
4798

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

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

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

    
4811
    self.required_nodes = 1
4812

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

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

    
4826
  def _BuildInputData(self):
4827
    """Build input data structures.
4828

4829
    """
4830
    self._ComputeClusterData()
4831

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

    
4837
    self.in_text = serializer.Dump(self.in_data)
4838

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

4842
    """
4843
    data = self.in_text
4844

    
4845
    result = call_fn(self.sstore.GetMasterNode(), name, self.in_text)
4846

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

    
4850
    rcode, stdout, stderr, fail = result
4851

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

    
4862
  def _ValidateResult(self):
4863
    """Process the allocator results.
4864

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

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

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

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

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

    
4888

    
4889
class LUTestAllocator(NoHooksLU):
4890
  """Run allocator tests.
4891

4892
  This LU runs the allocator tests
4893

4894
  """
4895
  _OP_REQP = ["direction", "mode", "name"]
4896

    
4897
  def CheckPrereq(self):
4898
    """Check prerequisites.
4899

4900
    This checks the opcode parameters depending on the director and mode test.
4901

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

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

    
4954
  def Exec(self, feedback_fn):
4955
    """Run the allocator test.
4956

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

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