Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 024e157f

History | View | Annotate | Download (53.5 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 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
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
import os
28
import itertools
29
import simplejson
30
from optparse import make_option
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import cli
35
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import utils
38
from ganeti import errors
39

    
40

    
41
_SHUTDOWN_CLUSTER = "cluster"
42
_SHUTDOWN_NODES_BOTH = "nodes"
43
_SHUTDOWN_NODES_PRI = "nodes-pri"
44
_SHUTDOWN_NODES_SEC = "nodes-sec"
45
_SHUTDOWN_INSTANCES = "instances"
46

    
47

    
48
_VALUE_TRUE = "true"
49

    
50
#: default list of options for L{ListInstances}
51
_LIST_DEF_FIELDS = [
52
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
53
  ]
54

    
55

    
56
def _ExpandMultiNames(mode, names, client=None):
57
  """Expand the given names using the passed mode.
58

    
59
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
60
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
61
  primary/secondary will be returned. For _SHUTDOWN_NODES_BOTH, all
62
  instances having those nodes as either primary or secondary will be
63
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
64
  returned.
65

    
66
  @param mode: one of L{_SHUTDOWN_CLUSTER}, L{_SHUTDOWN_NODES_BOTH},
67
      L{_SHUTDOWN_NODES_PRI}, L{_SHUTDOWN_NODES_SEC} or
68
      L{_SHUTDOWN_INSTANCES}
69
  @param names: a list of names; for cluster, it must be empty,
70
      and for node and instance it must be a list of valid item
71
      names (short names are valid as usual, e.g. node1 instead of
72
      node1.example.com)
73
  @rtype: list
74
  @return: the list of names after the expansion
75
  @raise errors.ProgrammerError: for unknown selection type
76
  @raise errors.OpPrereqError: for invalid input parameters
77

    
78
  """
79
  if client is None:
80
    client = GetClient()
81
  if mode == _SHUTDOWN_CLUSTER:
82
    if names:
83
      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
84
    idata = client.QueryInstances([], ["name"], False)
85
    inames = [row[0] for row in idata]
86

    
87
  elif mode in (_SHUTDOWN_NODES_BOTH,
88
                _SHUTDOWN_NODES_PRI,
89
                _SHUTDOWN_NODES_SEC):
90
    if not names:
91
      raise errors.OpPrereqError("No node names passed")
92
    ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"],
93
                              True)
94
    ipri = [row[1] for row in ndata]
95
    pri_names = list(itertools.chain(*ipri))
96
    isec = [row[2] for row in ndata]
97
    sec_names = list(itertools.chain(*isec))
98
    if mode == _SHUTDOWN_NODES_BOTH:
99
      inames = pri_names + sec_names
100
    elif mode == _SHUTDOWN_NODES_PRI:
101
      inames = pri_names
102
    elif mode == _SHUTDOWN_NODES_SEC:
103
      inames = sec_names
104
    else:
105
      raise errors.ProgrammerError("Unhandled shutdown type")
106

    
107
  elif mode == _SHUTDOWN_INSTANCES:
108
    if not names:
109
      raise errors.OpPrereqError("No instance names passed")
110
    idata = client.QueryInstances(names, ["name"], False)
111
    inames = [row[0] for row in idata]
112

    
113
  else:
114
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
115

    
116
  return inames
117

    
118

    
119
def _ConfirmOperation(inames, text):
120
  """Ask the user to confirm an operation on a list of instances.
121

    
122
  This function is used to request confirmation for doing an operation
123
  on a given list of instances.
124

    
125
  @type inames: list
126
  @param inames: the list of names that we display when
127
      we ask for confirmation
128
  @type text: str
129
  @param text: the operation that the user should confirm
130
      (e.g. I{shutdown} or I{startup})
131
  @rtype: boolean
132
  @return: True or False depending on user's confirmation.
133

    
134
  """
135
  count = len(inames)
136
  msg = ("The %s will operate on %d instances.\n"
137
         "Do you want to continue?" % (text, count))
138
  affected = ("\nAffected instances:\n" +
139
              "\n".join(["  %s" % name for name in inames]))
140

    
141
  choices = [('y', True, 'Yes, execute the %s' % text),
142
             ('n', False, 'No, abort the %s' % text)]
143

    
144
  if count > 20:
145
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
146
    ask = msg
147
  else:
148
    ask = msg + affected
149

    
150
  choice = AskUser(ask, choices)
151
  if choice == 'v':
152
    choices.pop(1)
153
    choice = AskUser(msg + affected, choices)
154
  return choice
155

    
156

    
157
def _TransformPath(user_input):
158
  """Transform a user path into a canonical value.
159

    
160
  This function transforms the a path passed as textual information
161
  into the constants that the LU code expects.
162

    
163
  """
164
  if user_input:
165
    if user_input.lower() == "default":
166
      result_path = constants.VALUE_DEFAULT
167
    elif user_input.lower() == "none":
168
      result_path = constants.VALUE_NONE
169
    else:
170
      if not os.path.isabs(user_input):
171
        raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
172
                                   user_input)
173
      result_path = user_input
174
  else:
175
    result_path = constants.VALUE_DEFAULT
176

    
177
  return result_path
178

    
179

    
180
def _EnsureInstancesExist(client, names):
181
  """Check for and ensure the given instance names exist.
182

    
183
  This function will raise an OpPrereqError in case they don't
184
  exist. Otherwise it will exit cleanly.
185

    
186
  @type client: L{luxi.Client}
187
  @param client: the client to use for the query
188
  @type names: list
189
  @param names: the list of instance names to query
190
  @raise errors.OpPrereqError: in case any instance is missing
191

    
192
  """
193
  # TODO: change LUQueryInstances to that it actually returns None
194
  # instead of raising an exception, or devise a better mechanism
195
  result = client.QueryInstances(names, ["name"], False)
196
  for orig_name, row in zip(names, result):
197
    if row[0] is None:
198
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
199

    
200

    
201
def ListInstances(opts, args):
202
  """List instances and their properties.
203

    
204
  @param opts: the command line options selected by the user
205
  @type args: list
206
  @param args: should be an empty list
207
  @rtype: int
208
  @return: the desired exit code
209

    
210
  """
211
  if opts.output is None:
212
    selected_fields = _LIST_DEF_FIELDS
213
  elif opts.output.startswith("+"):
214
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
215
  else:
216
    selected_fields = opts.output.split(",")
217

    
218
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
219

    
220
  if not opts.no_headers:
221
    headers = {
222
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
223
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
224
      "oper_state": "Running",
225
      "oper_ram": "Memory", "disk_template": "Disk_template",
226
      "ip": "IP_address", "mac": "MAC_address",
227
      "bridge": "Bridge",
228
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
229
      "disk_usage": "DiskUsage",
230
      "status": "Status", "tags": "Tags",
231
      "network_port": "Network_port",
232
      "hv/kernel_path": "Kernel_path",
233
      "hv/initrd_path": "Initrd_path",
234
      "hv/boot_order": "HVM_boot_order",
235
      "hv/acpi": "HVM_ACPI",
236
      "hv/pae": "HVM_PAE",
237
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
238
      "hv/nic_type": "HVM_NIC_type",
239
      "hv/disk_type": "HVM_Disk_type",
240
      "hv/vnc_bind_address": "VNC_bind_address",
241
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
242
      "hvparams": "Hypervisor_parameters",
243
      "be/memory": "Configured_memory",
244
      "be/vcpus": "VCPUs",
245
      "be/auto_balance": "Auto_balance",
246
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
247
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
248
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
249
      }
250
  else:
251
    headers = None
252

    
253
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
254
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
255
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
256

    
257
  list_type_fields = ("tags", "disk.sizes",
258
                      "nic.macs", "nic.ips", "nic.bridges")
259
  # change raw values to nicer strings
260
  for row in output:
261
    for idx, field in enumerate(selected_fields):
262
      val = row[idx]
263
      if field == "snodes":
264
        val = ",".join(val) or "-"
265
      elif field == "admin_state":
266
        if val:
267
          val = "yes"
268
        else:
269
          val = "no"
270
      elif field == "oper_state":
271
        if val is None:
272
          val = "(node down)"
273
        elif val: # True
274
          val = "running"
275
        else:
276
          val = "stopped"
277
      elif field == "oper_ram":
278
        if val is None:
279
          val = "(node down)"
280
      elif field == "sda_size" or field == "sdb_size":
281
        if val is None:
282
          val = "N/A"
283
      elif field in list_type_fields:
284
        val = ",".join(str(item) for item in val)
285
      elif val is None:
286
        val = "-"
287
      row[idx] = str(val)
288

    
289
  data = GenerateTable(separator=opts.separator, headers=headers,
290
                       fields=selected_fields, unitfields=unitfields,
291
                       numfields=numfields, data=output, units=opts.units)
292

    
293
  for line in data:
294
    ToStdout(line)
295

    
296
  return 0
297

    
298

    
299
def AddInstance(opts, args):
300
  """Add an instance to the cluster.
301

    
302
  @param opts: the command line options selected by the user
303
  @type args: list
304
  @param args: should contain only one element, the new instance name
305
  @rtype: int
306
  @return: the desired exit code
307

    
308
  """
309
  instance = args[0]
310

    
311
  (pnode, snode) = SplitNodeOption(opts.node)
312

    
313
  hypervisor = None
314
  hvparams = {}
315
  if opts.hypervisor:
316
    hypervisor, hvparams = opts.hypervisor
317

    
318
  if opts.nics:
319
    try:
320
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
321
    except ValueError, err:
322
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
323
    nics = [{}] * nic_max
324
    for nidx, ndict in opts.nics.items():
325
      nidx = int(nidx)
326
      nics[nidx] = ndict
327
  elif opts.no_nics:
328
    # no nics
329
    nics = []
330
  else:
331
    # default of one nic, all auto
332
    nics = [{}]
333

    
334
  if opts.disk_template == constants.DT_DISKLESS:
335
    if opts.disks:
336
      raise errors.OpPrereqError("Diskless instance but disk"
337
                                 " information passed")
338
    disks = []
339
  else:
340
    if not opts.disks:
341
      raise errors.OpPrereqError("No disk information specified")
342
    try:
343
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
344
    except ValueError, err:
345
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
346
    disks = [{}] * disk_max
347
    for didx, ddict in opts.disks:
348
      didx = int(didx)
349
      if "size" not in ddict:
350
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
351
      try:
352
        ddict["size"] = utils.ParseUnit(ddict["size"])
353
      except ValueError, err:
354
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
355
                                   (didx, err))
356
      disks[didx] = ddict
357

    
358
  ValidateBeParams(opts.beparams)
359

    
360
##  kernel_path = _TransformPath(opts.kernel_path)
361
##  initrd_path = _TransformPath(opts.initrd_path)
362

    
363
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
364
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
365

    
366
##  if ((opts.hvm_cdrom_image_path is not None) and
367
##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
368
##    hvm_cdrom_image_path = None
369
##  else:
370
##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
371

    
372
  op = opcodes.OpCreateInstance(instance_name=instance,
373
                                disks=disks,
374
                                disk_template=opts.disk_template,
375
                                nics=nics,
376
                                mode=constants.INSTANCE_CREATE,
377
                                os_type=opts.os, pnode=pnode,
378
                                snode=snode,
379
                                start=opts.start, ip_check=opts.ip_check,
380
                                wait_for_sync=opts.wait_for_sync,
381
                                hypervisor=hypervisor,
382
                                hvparams=hvparams,
383
                                beparams=opts.beparams,
384
                                iallocator=opts.iallocator,
385
                                file_storage_dir=opts.file_storage_dir,
386
                                file_driver=opts.file_driver,
387
                                )
388

    
389
  SubmitOrSend(op, opts)
390
  return 0
391

    
392

    
393
def BatchCreate(opts, args):
394
  """Create instances using a definition file.
395

    
396
  This function reads a json file with instances defined
397
  in the form::
398

    
399
    {"instance-name":{
400
      "disk_size": [20480],
401
      "template": "drbd",
402
      "backend": {
403
        "memory": 512,
404
        "vcpus": 1 },
405
      "os": "debootstrap",
406
      "primary_node": "firstnode",
407
      "secondary_node": "secondnode",
408
      "iallocator": "dumb"}
409
    }
410

    
411
  Note that I{primary_node} and I{secondary_node} have precedence over
412
  I{iallocator}.
413

    
414
  @param opts: the command line options selected by the user
415
  @type args: list
416
  @param args: should contain one element, the json filename
417
  @rtype: int
418
  @return: the desired exit code
419

    
420
  """
421
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
422
                    "backend": {},
423
                    "iallocator": None,
424
                    "primary_node": None,
425
                    "secondary_node": None,
426
                    "ip": 'none',
427
                    "mac": 'auto',
428
                    "bridge": None,
429
                    "start": True,
430
                    "ip_check": True,
431
                    "hypervisor": None,
432
                    "file_storage_dir": None,
433
                    "file_driver": 'loop'}
434

    
435
  def _PopulateWithDefaults(spec):
436
    """Returns a new hash combined with default values."""
437
    mydict = _DEFAULT_SPECS.copy()
438
    mydict.update(spec)
439
    return mydict
440

    
441
  def _Validate(spec):
442
    """Validate the instance specs."""
443
    # Validate fields required under any circumstances
444
    for required_field in ('os', 'template'):
445
      if required_field not in spec:
446
        raise errors.OpPrereqError('Required field "%s" is missing.' %
447
                                   required_field)
448
    # Validate special fields
449
    if spec['primary_node'] is not None:
450
      if (spec['template'] in constants.DTS_NET_MIRROR and
451
          spec['secondary_node'] is None):
452
        raise errors.OpPrereqError('Template requires secondary node, but'
453
                                   ' there was no secondary provided.')
454
    elif spec['iallocator'] is None:
455
      raise errors.OpPrereqError('You have to provide at least a primary_node'
456
                                 ' or an iallocator.')
457

    
458
    if (spec['hypervisor'] and
459
        not isinstance(spec['hypervisor'], dict)):
460
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
461

    
462
  json_filename = args[0]
463
  fd = open(json_filename, 'r')
464
  try:
465
    instance_data = simplejson.load(fd)
466
  finally:
467
    fd.close()
468

    
469
  # Iterate over the instances and do:
470
  #  * Populate the specs with default value
471
  #  * Validate the instance specs
472
  i_names = utils.NiceSort(instance_data.keys())
473
  for name in i_names:
474
    specs = instance_data[name]
475
    specs = _PopulateWithDefaults(specs)
476
    _Validate(specs)
477

    
478
    hypervisor = None
479
    hvparams = {}
480
    if specs['hypervisor']:
481
      hypervisor, hvparams = specs['hypervisor'].iteritems()
482

    
483
    disks = []
484
    for elem in specs['disk_size']:
485
      try:
486
        size = utils.ParseUnit(elem)
487
      except ValueError, err:
488
        raise errors.OpPrereqError("Invalid disk size '%s' for"
489
                                   " instance %s: %s" %
490
                                   (elem, name, err))
491
      disks.append({"size": size})
492

    
493
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
494

    
495
    op = opcodes.OpCreateInstance(instance_name=name,
496
                                  disks=disks,
497
                                  disk_template=specs['template'],
498
                                  mode=constants.INSTANCE_CREATE,
499
                                  os_type=specs['os'],
500
                                  pnode=specs['primary_node'],
501
                                  snode=specs['secondary_node'],
502
                                  nics=[nic0],
503
                                  start=specs['start'],
504
                                  ip_check=specs['ip_check'],
505
                                  wait_for_sync=True,
506
                                  iallocator=specs['iallocator'],
507
                                  hypervisor=hypervisor,
508
                                  hvparams=hvparams,
509
                                  beparams=specs['backend'],
510
                                  file_storage_dir=specs['file_storage_dir'],
511
                                  file_driver=specs['file_driver'])
512

    
513
    ToStdout("%s: %s", name, cli.SendJob([op]))
514

    
515
  return 0
516

    
517

    
518
def ReinstallInstance(opts, args):
519
  """Reinstall an instance.
520

    
521
  @param opts: the command line options selected by the user
522
  @type args: list
523
  @param args: should contain only one element, the name of the
524
      instance to be reinstalled
525
  @rtype: int
526
  @return: the desired exit code
527

    
528
  """
529
  instance_name = args[0]
530

    
531
  if opts.select_os is True:
532
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
533
    result = SubmitOpCode(op)
534

    
535
    if not result:
536
      ToStdout("Can't get the OS list")
537
      return 1
538

    
539
    ToStdout("Available OS templates:")
540
    number = 0
541
    choices = []
542
    for entry in result:
543
      ToStdout("%3s: %s", number, entry[0])
544
      choices.append(("%s" % number, entry[0], entry[0]))
545
      number = number + 1
546

    
547
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
548
    selected = AskUser("Enter OS template name or number (or x to abort):",
549
                       choices)
550

    
551
    if selected == 'exit':
552
      ToStdout("User aborted reinstall, exiting")
553
      return 1
554

    
555
    os_name = selected
556
  else:
557
    os_name = opts.os
558

    
559
  if not opts.force:
560
    usertext = ("This will reinstall the instance %s and remove"
561
                " all data. Continue?") % instance_name
562
    if not AskUser(usertext):
563
      return 1
564

    
565
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
566
                                   os_type=os_name)
567
  SubmitOrSend(op, opts)
568

    
569
  return 0
570

    
571

    
572
def RemoveInstance(opts, args):
573
  """Remove an instance.
574

    
575
  @param opts: the command line options selected by the user
576
  @type args: list
577
  @param args: should contain only one element, the name of
578
      the instance to be removed
579
  @rtype: int
580
  @return: the desired exit code
581

    
582
  """
583
  instance_name = args[0]
584
  force = opts.force
585
  cl = GetClient()
586

    
587
  if not force:
588
    _EnsureInstancesExist(cl, [instance_name])
589

    
590
    usertext = ("This will remove the volumes of the instance %s"
591
                " (including mirrors), thus removing all the data"
592
                " of the instance. Continue?") % instance_name
593
    if not AskUser(usertext):
594
      return 1
595

    
596
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
597
                                ignore_failures=opts.ignore_failures)
598
  SubmitOrSend(op, opts, cl=cl)
599
  return 0
600

    
601

    
602
def RenameInstance(opts, args):
603
  """Rename an instance.
604

    
605
  @param opts: the command line options selected by the user
606
  @type args: list
607
  @param args: should contain two elements, the old and the
608
      new instance names
609
  @rtype: int
610
  @return: the desired exit code
611

    
612
  """
613
  op = opcodes.OpRenameInstance(instance_name=args[0],
614
                                new_name=args[1],
615
                                ignore_ip=opts.ignore_ip)
616
  SubmitOrSend(op, opts)
617
  return 0
618

    
619

    
620
def ActivateDisks(opts, args):
621
  """Activate an instance's disks.
622

    
623
  This serves two purposes:
624
    - it allows (as long as the instance is not running)
625
      mounting the disks and modifying them from the node
626
    - it repairs inactive secondary drbds
627

    
628
  @param opts: the command line options selected by the user
629
  @type args: list
630
  @param args: should contain only one element, the instance name
631
  @rtype: int
632
  @return: the desired exit code
633

    
634
  """
635
  instance_name = args[0]
636
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
637
  disks_info = SubmitOrSend(op, opts)
638
  for host, iname, nname in disks_info:
639
    ToStdout("%s:%s:%s", host, iname, nname)
640
  return 0
641

    
642

    
643
def DeactivateDisks(opts, args):
644
  """Deactivate an instance's disks..
645

    
646
  This function takes the instance name, looks for its primary node
647
  and the tries to shutdown its block devices on that node.
648

    
649
  @param opts: the command line options selected by the user
650
  @type args: list
651
  @param args: should contain only one element, the instance name
652
  @rtype: int
653
  @return: the desired exit code
654

    
655
  """
656
  instance_name = args[0]
657
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
658
  SubmitOrSend(op, opts)
659
  return 0
660

    
661

    
662
def GrowDisk(opts, args):
663
  """Grow an instance's disks.
664

    
665
  @param opts: the command line options selected by the user
666
  @type args: list
667
  @param args: should contain two elements, the instance name
668
      whose disks we grow and the disk name, e.g. I{sda}
669
  @rtype: int
670
  @return: the desired exit code
671

    
672
  """
673
  instance = args[0]
674
  disk = args[1]
675
  try:
676
    disk = int(disk)
677
  except ValueError, err:
678
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
679
  amount = utils.ParseUnit(args[2])
680
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
681
                          wait_for_sync=opts.wait_for_sync)
682
  SubmitOrSend(op, opts)
683
  return 0
684

    
685

    
686
def StartupInstance(opts, args):
687
  """Startup instances.
688

    
689
  Depending on the options given, this will start one or more
690
  instances.
691

    
692
  @param opts: the command line options selected by the user
693
  @type args: list
694
  @param args: the instance or node names based on which we
695
      create the final selection (in conjunction with the
696
      opts argument)
697
  @rtype: int
698
  @return: the desired exit code
699

    
700
  """
701
  cl = GetClient()
702
  if opts.multi_mode is None:
703
    opts.multi_mode = _SHUTDOWN_INSTANCES
704
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
705
  if not inames:
706
    raise errors.OpPrereqError("Selection filter does not match any instances")
707
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
708
  if not (opts.force_multi or not multi_on
709
          or _ConfirmOperation(inames, "startup")):
710
    return 1
711
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
712
  for name in inames:
713
    op = opcodes.OpStartupInstance(instance_name=name,
714
                                   force=opts.force,
715
                                   extra_args=opts.extra_args)
716
    jex.QueueJob(name, op)
717
  jex.WaitOrShow(not opts.submit_only)
718
  return 0
719

    
720

    
721
def RebootInstance(opts, args):
722
  """Reboot instance(s).
723

    
724
  Depending on the parameters given, this will reboot one or more
725
  instances.
726

    
727
  @param opts: the command line options selected by the user
728
  @type args: list
729
  @param args: the instance or node names based on which we
730
      create the final selection (in conjunction with the
731
      opts argument)
732
  @rtype: int
733
  @return: the desired exit code
734

    
735
  """
736
  cl = GetClient()
737
  if opts.multi_mode is None:
738
    opts.multi_mode = _SHUTDOWN_INSTANCES
739
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
740
  if not inames:
741
    raise errors.OpPrereqError("Selection filter does not match any instances")
742
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
743
  if not (opts.force_multi or not multi_on
744
          or _ConfirmOperation(inames, "reboot")):
745
    return 1
746
  jex = JobExecutor(verbose=multi_on, cl=cl)
747
  for name in inames:
748
    op = opcodes.OpRebootInstance(instance_name=name,
749
                                  reboot_type=opts.reboot_type,
750
                                  ignore_secondaries=opts.ignore_secondaries)
751
    jex.QueueJob(name, op)
752
  jex.WaitOrShow(not opts.submit_only)
753
  return 0
754

    
755

    
756
def ShutdownInstance(opts, args):
757
  """Shutdown an instance.
758

    
759
  @param opts: the command line options selected by the user
760
  @type args: list
761
  @param args: the instance or node names based on which we
762
      create the final selection (in conjunction with the
763
      opts argument)
764
  @rtype: int
765
  @return: the desired exit code
766

    
767
  """
768
  cl = GetClient()
769
  if opts.multi_mode is None:
770
    opts.multi_mode = _SHUTDOWN_INSTANCES
771
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
772
  if not inames:
773
    raise errors.OpPrereqError("Selection filter does not match any instances")
774
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
775
  if not (opts.force_multi or not multi_on
776
          or _ConfirmOperation(inames, "shutdown")):
777
    return 1
778

    
779
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
780
  for name in inames:
781
    op = opcodes.OpShutdownInstance(instance_name=name)
782
    jex.QueueJob(name, op)
783
  jex.WaitOrShow(not opts.submit_only)
784
  return 0
785

    
786

    
787
def ReplaceDisks(opts, args):
788
  """Replace the disks of an instance
789

    
790
  @param opts: the command line options selected by the user
791
  @type args: list
792
  @param args: should contain only one element, the instance name
793
  @rtype: int
794
  @return: the desired exit code
795

    
796
  """
797
  instance_name = args[0]
798
  new_2ndary = opts.new_secondary
799
  iallocator = opts.iallocator
800
  if opts.disks is None:
801
    disks = []
802
  else:
803
    try:
804
      disks = [int(i) for i in opts.disks.split(",")]
805
    except ValueError, err:
806
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
807
  cnt = [opts.on_primary, opts.on_secondary,
808
         new_2ndary is not None, iallocator is not None].count(True)
809
  if cnt != 1:
810
    raise errors.OpPrereqError("One and only one of the -p, -s, -n and -i"
811
                               " options must be passed")
812
  elif opts.on_primary:
813
    mode = constants.REPLACE_DISK_PRI
814
  elif opts.on_secondary:
815
    mode = constants.REPLACE_DISK_SEC
816
  elif new_2ndary is not None or iallocator is not None:
817
    # replace secondary
818
    mode = constants.REPLACE_DISK_CHG
819

    
820
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
821
                              remote_node=new_2ndary, mode=mode,
822
                              iallocator=iallocator)
823
  SubmitOrSend(op, opts)
824
  return 0
825

    
826

    
827
def FailoverInstance(opts, args):
828
  """Failover an instance.
829

    
830
  The failover is done by shutting it down on its present node and
831
  starting it on the secondary.
832

    
833
  @param opts: the command line options selected by the user
834
  @type args: list
835
  @param args: should contain only one element, the instance name
836
  @rtype: int
837
  @return: the desired exit code
838

    
839
  """
840
  cl = GetClient()
841
  instance_name = args[0]
842
  force = opts.force
843

    
844
  if not force:
845
    _EnsureInstancesExist(cl, [instance_name])
846

    
847
    usertext = ("Failover will happen to image %s."
848
                " This requires a shutdown of the instance. Continue?" %
849
                (instance_name,))
850
    if not AskUser(usertext):
851
      return 1
852

    
853
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
854
                                  ignore_consistency=opts.ignore_consistency)
855
  SubmitOrSend(op, opts, cl=cl)
856
  return 0
857

    
858

    
859
def MigrateInstance(opts, args):
860
  """Migrate an instance.
861

    
862
  The migrate is done without shutdown.
863

    
864
  @param opts: the command line options selected by the user
865
  @type args: list
866
  @param args: should contain only one element, the instance name
867
  @rtype: int
868
  @return: the desired exit code
869

    
870
  """
871
  cl = GetClient()
872
  instance_name = args[0]
873
  force = opts.force
874

    
875
  if not force:
876
    _EnsureInstancesExist(cl, [instance_name])
877

    
878
    if opts.cleanup:
879
      usertext = ("Instance %s will be recovered from a failed migration."
880
                  " Note that the migration procedure (including cleanup)" %
881
                  (instance_name,))
882
    else:
883
      usertext = ("Instance %s will be migrated. Note that migration" %
884
                  (instance_name,))
885
    usertext += (" is **experimental** in this version."
886
                " This might impact the instance if anything goes wrong."
887
                " Continue?")
888
    if not AskUser(usertext):
889
      return 1
890

    
891
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
892
                                 cleanup=opts.cleanup)
893
  SubmitOpCode(op, cl=cl)
894
  return 0
895

    
896

    
897
def ConnectToInstanceConsole(opts, args):
898
  """Connect to the console of an instance.
899

    
900
  @param opts: the command line options selected by the user
901
  @type args: list
902
  @param args: should contain only one element, the instance name
903
  @rtype: int
904
  @return: the desired exit code
905

    
906
  """
907
  instance_name = args[0]
908

    
909
  op = opcodes.OpConnectConsole(instance_name=instance_name)
910
  cmd = SubmitOpCode(op)
911

    
912
  if opts.show_command:
913
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
914
  else:
915
    try:
916
      os.execvp(cmd[0], cmd)
917
    finally:
918
      ToStderr("Can't run console command %s with arguments:\n'%s'",
919
               cmd[0], " ".join(cmd))
920
      os._exit(1)
921

    
922

    
923
def _FormatLogicalID(dev_type, logical_id):
924
  """Formats the logical_id of a disk.
925

    
926
  """
927
  if dev_type == constants.LD_DRBD8:
928
    node_a, node_b, port, minor_a, minor_b, key = logical_id
929
    data = [
930
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
931
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
932
      ("port", port),
933
      ("auth key", key),
934
      ]
935
  elif dev_type == constants.LD_LV:
936
    vg_name, lv_name = logical_id
937
    data = ["%s/%s" % (vg_name, lv_name)]
938
  else:
939
    data = [str(logical_id)]
940

    
941
  return data
942

    
943

    
944
def _FormatBlockDevInfo(idx, top_level, dev, static):
945
  """Show block device information.
946

    
947
  This is only used by L{ShowInstanceConfig}, but it's too big to be
948
  left for an inline definition.
949

    
950
  @type idx: int
951
  @param idx: the index of the current disk
952
  @type top_level: boolean
953
  @param top_level: if this a top-level disk?
954
  @type dev: dict
955
  @param dev: dictionary with disk information
956
  @type static: boolean
957
  @param static: wheter the device information doesn't contain
958
      runtime information but only static data
959
  @return: a list of either strings, tuples or lists
960
      (which should be formatted at a higher indent level)
961

    
962
  """
963
  def helper(dtype, status):
964
    """Format one line for physical device status.
965

    
966
    @type dtype: str
967
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
968
    @type status: tuple
969
    @param status: a tuple as returned from L{backend.FindBlockDevice}
970
    @return: the string representing the status
971

    
972
    """
973
    if not status:
974
      return "not active"
975
    txt = ""
976
    (path, major, minor, syncp, estt, degr, ldisk) = status
977
    if major is None:
978
      major_string = "N/A"
979
    else:
980
      major_string = str(major)
981

    
982
    if minor is None:
983
      minor_string = "N/A"
984
    else:
985
      minor_string = str(minor)
986

    
987
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
988
    if dtype in (constants.LD_DRBD8, ):
989
      if syncp is not None:
990
        sync_text = "*RECOVERING* %5.2f%%," % syncp
991
        if estt:
992
          sync_text += " ETA %ds" % estt
993
        else:
994
          sync_text += " ETA unknown"
995
      else:
996
        sync_text = "in sync"
997
      if degr:
998
        degr_text = "*DEGRADED*"
999
      else:
1000
        degr_text = "ok"
1001
      if ldisk:
1002
        ldisk_text = " *MISSING DISK*"
1003
      else:
1004
        ldisk_text = ""
1005
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1006
    elif dtype == constants.LD_LV:
1007
      if ldisk:
1008
        ldisk_text = " *FAILED* (failed drive?)"
1009
      else:
1010
        ldisk_text = ""
1011
      txt += ldisk_text
1012
    return txt
1013

    
1014
  # the header
1015
  if top_level:
1016
    if dev["iv_name"] is not None:
1017
      txt = dev["iv_name"]
1018
    else:
1019
      txt = "disk %d" % idx
1020
  else:
1021
    txt = "child %d" % idx
1022
  d1 = ["- %s: %s" % (txt, dev["dev_type"])]
1023
  data = []
1024
  if top_level:
1025
    data.append(("access mode", dev["mode"]))
1026
  if dev["logical_id"] is not None:
1027
    try:
1028
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1029
    except ValueError:
1030
      l_id = [str(dev["logical_id"])]
1031
    if len(l_id) == 1:
1032
      data.append(("logical_id", l_id[0]))
1033
    else:
1034
      data.extend(l_id)
1035
  elif dev["physical_id"] is not None:
1036
    data.append("physical_id:")
1037
    data.append([dev["physical_id"]])
1038
  if not static:
1039
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1040
  if dev["sstatus"] and not static:
1041
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1042

    
1043
  if dev["children"]:
1044
    data.append("child devices:")
1045
    for c_idx, child in enumerate(dev["children"]):
1046
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1047
  d1.append(data)
1048
  return d1
1049

    
1050

    
1051
def _FormatList(buf, data, indent_level):
1052
  """Formats a list of data at a given indent level.
1053

    
1054
  If the element of the list is:
1055
    - a string, it is simply formatted as is
1056
    - a tuple, it will be split into key, value and the all the
1057
      values in a list will be aligned all at the same start column
1058
    - a list, will be recursively formatted
1059

    
1060
  @type buf: StringIO
1061
  @param buf: the buffer into which we write the output
1062
  @param data: the list to format
1063
  @type indent_level: int
1064
  @param indent_level: the indent level to format at
1065

    
1066
  """
1067
  max_tlen = max([len(elem[0]) for elem in data
1068
                 if isinstance(elem, tuple)] or [0])
1069
  for elem in data:
1070
    if isinstance(elem, basestring):
1071
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1072
    elif isinstance(elem, tuple):
1073
      key, value = elem
1074
      spacer = "%*s" % (max_tlen - len(key), "")
1075
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1076
    elif isinstance(elem, list):
1077
      _FormatList(buf, elem, indent_level+1)
1078

    
1079
def ShowInstanceConfig(opts, args):
1080
  """Compute instance run-time status.
1081

    
1082
  @param opts: the command line options selected by the user
1083
  @type args: list
1084
  @param args: either an empty list, and then we query all
1085
      instances, or should contain a list of instance names
1086
  @rtype: int
1087
  @return: the desired exit code
1088

    
1089
  """
1090
  retcode = 0
1091
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1092
  result = SubmitOpCode(op)
1093
  if not result:
1094
    ToStdout("No instances.")
1095
    return 1
1096

    
1097
  buf = StringIO()
1098
  retcode = 0
1099
  for instance_name in result:
1100
    instance = result[instance_name]
1101
    buf.write("Instance name: %s\n" % instance["name"])
1102
    buf.write("State: configured to be %s" % instance["config_state"])
1103
    if not opts.static:
1104
      buf.write(", actual state is %s" % instance["run_state"])
1105
    buf.write("\n")
1106
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1107
    ##          instance["auto_balance"])
1108
    buf.write("  Nodes:\n")
1109
    buf.write("    - primary: %s\n" % instance["pnode"])
1110
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1111
    buf.write("  Operating system: %s\n" % instance["os"])
1112
    if instance.has_key("network_port"):
1113
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1114
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1115
    if instance["hypervisor"] == constants.HT_XEN_PVM:
1116
      hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
1117
                 (constants.HV_INITRD_PATH, "initrd path"))
1118
    elif instance["hypervisor"] == constants.HT_XEN_HVM:
1119
      hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
1120
                 (constants.HV_ACPI, "ACPI"),
1121
                 (constants.HV_PAE, "PAE"),
1122
                 (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
1123
                 (constants.HV_NIC_TYPE, "NIC type"),
1124
                 (constants.HV_DISK_TYPE, "Disk type"),
1125
                 (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
1126
                 )
1127
      # custom console information for HVM
1128
      vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
1129
      if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1130
        vnc_console_port = "%s:%s" % (instance["pnode"],
1131
                                      instance["network_port"])
1132
      elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
1133
        vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
1134
                                                 instance["network_port"],
1135
                                                 instance["pnode"])
1136
      else:
1137
        vnc_console_port = "%s:%s" % (vnc_bind_address,
1138
                                      instance["network_port"])
1139
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1140

    
1141
    else:
1142
      # auto-handle other hypervisor types
1143
      hvattrs = [(key, key) for key in instance["hv_actual"]]
1144

    
1145
    for key, desc in hvattrs:
1146
      if key in instance["hv_instance"]:
1147
        val = instance["hv_instance"][key]
1148
      else:
1149
        val = "default (%s)" % instance["hv_actual"][key]
1150
      buf.write("    - %s: %s\n" % (desc, val))
1151
    buf.write("  Hardware:\n")
1152
    buf.write("    - VCPUs: %d\n" %
1153
              instance["be_actual"][constants.BE_VCPUS])
1154
    buf.write("    - memory: %dMiB\n" %
1155
              instance["be_actual"][constants.BE_MEMORY])
1156
    buf.write("    - NICs:\n")
1157
    for idx, (mac, ip, bridge) in enumerate(instance["nics"]):
1158
      buf.write("      - nic/%d: MAC: %s, IP: %s, bridge: %s\n" %
1159
                (idx, mac, ip, bridge))
1160
    buf.write("  Disks:\n")
1161

    
1162
    for idx, device in enumerate(instance["disks"]):
1163
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1164

    
1165
  ToStdout(buf.getvalue().rstrip('\n'))
1166
  return retcode
1167

    
1168

    
1169
def SetInstanceParams(opts, args):
1170
  """Modifies an instance.
1171

    
1172
  All parameters take effect only at the next restart of the instance.
1173

    
1174
  @param opts: the command line options selected by the user
1175
  @type args: list
1176
  @param args: should contain only one element, the instance name
1177
  @rtype: int
1178
  @return: the desired exit code
1179

    
1180
  """
1181
  if not (opts.nics or opts.disks or
1182
          opts.hypervisor or opts.beparams):
1183
    ToStderr("Please give at least one of the parameters.")
1184
    return 1
1185

    
1186
  for param in opts.beparams:
1187
    if isinstance(opts.beparams[param], basestring):
1188
      if opts.beparams[param].lower() == "default":
1189
        opts.beparams[param] = constants.VALUE_DEFAULT
1190
      elif opts.beparams[param].lower() == "none":
1191
        opts.beparams[param] = constants.VALUE_NONE
1192
      elif param == constants.BE_MEMORY:
1193
        opts.beparams[constants.BE_MEMORY] = \
1194
          utils.ParseUnit(opts.beparams[constants.BE_MEMORY])
1195

    
1196
  for param in opts.hypervisor:
1197
    if isinstance(opts.hypervisor[param], basestring):
1198
      if opts.hypervisor[param].lower() == "default":
1199
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1200
      elif opts.hypervisor[param].lower() == "none":
1201
        opts.hypervisor[param] = constants.VALUE_NONE
1202

    
1203
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1204
    try:
1205
      nic_op = int(nic_op)
1206
      opts.nics[idx] = (nic_op, nic_dict)
1207
    except ValueError:
1208
      pass
1209

    
1210
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1211
    try:
1212
      disk_op = int(disk_op)
1213
      opts.disks[idx] = (disk_op, disk_dict)
1214
    except ValueError:
1215
      pass
1216
    if disk_op == constants.DDM_ADD:
1217
      if 'size' not in disk_dict:
1218
        raise errors.OpPrereqError("Missing required parameter 'size'")
1219
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1220

    
1221
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1222
                                   nics=opts.nics,
1223
                                   disks=opts.disks,
1224
                                   hvparams=opts.hypervisor,
1225
                                   beparams=opts.beparams,
1226
                                   force=opts.force)
1227

    
1228
  # even if here we process the result, we allow submit only
1229
  result = SubmitOrSend(op, opts)
1230

    
1231
  if result:
1232
    ToStdout("Modified instance %s", args[0])
1233
    for param, data in result:
1234
      ToStdout(" - %-5s -> %s", param, data)
1235
    ToStdout("Please don't forget that these parameters take effect"
1236
             " only at the next start of the instance.")
1237
  return 0
1238

    
1239

    
1240
# options used in more than one cmd
1241
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1242
                       metavar="<node>")
1243

    
1244
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1245
                    metavar="<os>")
1246

    
1247
# multi-instance selection options
1248
m_force_multi = make_option("--force-multiple", dest="force_multi",
1249
                            help="Do not ask for confirmation when more than"
1250
                            " one instance is affected",
1251
                            action="store_true", default=False)
1252

    
1253
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1254
                             help="Filter by nodes (primary only)",
1255
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1256

    
1257
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1258
                             help="Filter by nodes (secondary only)",
1259
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1260

    
1261
m_node_opt = make_option("--node", dest="multi_mode",
1262
                         help="Filter by nodes (primary and secondary)",
1263
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1264

    
1265
m_clust_opt = make_option("--all", dest="multi_mode",
1266
                          help="Select all instances in the cluster",
1267
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1268

    
1269
m_inst_opt = make_option("--instance", dest="multi_mode",
1270
                         help="Filter by instance name [default]",
1271
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1272

    
1273

    
1274
# this is defined separately due to readability only
1275
add_opts = [
1276
  DEBUG_OPT,
1277
  make_option("-n", "--node", dest="node",
1278
              help="Target node and optional secondary node",
1279
              metavar="<pnode>[:<snode>]"),
1280
  os_opt,
1281
  keyval_option("-B", "--backend", dest="beparams",
1282
                type="keyval", default={},
1283
                help="Backend parameters"),
1284
  make_option("-t", "--disk-template", dest="disk_template",
1285
              help="Custom disk setup (diskless, file, plain or drbd)",
1286
              default=None, metavar="TEMPL"),
1287
  ikv_option("--disk", help="Disk information",
1288
             default=[], dest="disks",
1289
             action="append",
1290
             type="identkeyval"),
1291
  ikv_option("--net", help="NIC information",
1292
             default=[], dest="nics",
1293
             action="append",
1294
             type="identkeyval"),
1295
  make_option("--no-nics", default=False, action="store_true",
1296
              help="Do not create any network cards for the instance"),
1297
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1298
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1299
  make_option("--no-start", dest="start", default=True,
1300
              action="store_false", help="Don't start the instance after"
1301
              " creation"),
1302
  make_option("--no-ip-check", dest="ip_check", default=True,
1303
              action="store_false", help="Don't check that the instance's IP"
1304
              " is alive (only valid with --no-start)"),
1305
  make_option("--file-storage-dir", dest="file_storage_dir",
1306
              help="Relative path under default cluster-wide file storage dir"
1307
              " to store file-based disks", default=None,
1308
              metavar="<DIR>"),
1309
  make_option("--file-driver", dest="file_driver", help="Driver to use"
1310
              " for image files", default="loop", metavar="<DRIVER>"),
1311
  make_option("-I", "--iallocator", metavar="<NAME>",
1312
              help="Select nodes for the instance automatically using the"
1313
              " <NAME> iallocator plugin", default=None, type="string"),
1314
  ikv_option("-H", "--hypervisor", dest="hypervisor",
1315
              help="Hypervisor and hypervisor options, in the format"
1316
              " hypervisor:option=value,option=value,...", default=None,
1317
              type="identkeyval"),
1318
  SUBMIT_OPT,
1319
  ]
1320

    
1321
commands = {
1322
  'add': (AddInstance, ARGS_ONE, add_opts,
1323
          "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1324
          "Creates and adds a new instance to the cluster"),
1325
  'batch-create': (BatchCreate, ARGS_ONE,
1326
                   [DEBUG_OPT],
1327
                   "<instances_file.json>",
1328
                   "Create a bunch of instances based on specs in the file."),
1329
  'console': (ConnectToInstanceConsole, ARGS_ONE,
1330
              [DEBUG_OPT,
1331
               make_option("--show-cmd", dest="show_command",
1332
                           action="store_true", default=False,
1333
                           help=("Show command instead of executing it"))],
1334
              "[--show-cmd] <instance>",
1335
              "Opens a console on the specified instance"),
1336
  'failover': (FailoverInstance, ARGS_ONE,
1337
               [DEBUG_OPT, FORCE_OPT,
1338
                make_option("--ignore-consistency", dest="ignore_consistency",
1339
                            action="store_true", default=False,
1340
                            help="Ignore the consistency of the disks on"
1341
                            " the secondary"),
1342
                SUBMIT_OPT,
1343
                ],
1344
               "[-f] <instance>",
1345
               "Stops the instance and starts it on the backup node, using"
1346
               " the remote mirror (only for instances of type drbd)"),
1347
  'migrate': (MigrateInstance, ARGS_ONE,
1348
               [DEBUG_OPT, FORCE_OPT,
1349
                make_option("--non-live", dest="live",
1350
                            default=True, action="store_false",
1351
                            help="Do a non-live migration (this usually means"
1352
                            " freeze the instance, save the state,"
1353
                            " transfer and only then resume running on the"
1354
                            " secondary node)"),
1355
                make_option("--cleanup", dest="cleanup",
1356
                            default=False, action="store_true",
1357
                            help="Instead of performing the migration, try to"
1358
                            " recover from a failed cleanup. This is safe"
1359
                            " to run even if the instance is healthy, but it"
1360
                            " will create extra replication traffic and "
1361
                            " disrupt briefly the replication (like during the"
1362
                            " migration"),
1363
                ],
1364
               "[-f] <instance>",
1365
               "Migrate instance to its secondary node"
1366
               " (only for instances of type drbd)"),
1367
  'info': (ShowInstanceConfig, ARGS_ANY,
1368
           [DEBUG_OPT,
1369
            make_option("-s", "--static", dest="static",
1370
                        action="store_true", default=False,
1371
                        help="Only show configuration data, not runtime data"),
1372
            ], "[-s] [<instance>...]",
1373
           "Show information on the specified instance(s)"),
1374
  'list': (ListInstances, ARGS_ANY,
1375
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
1376
           "[<instance>...]",
1377
           "Lists the instances and their status. The available fields are"
1378
           " (see the man page for details): status, oper_state, oper_ram,"
1379
           " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1380
           " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1381
           " hypervisor."
1382
           " The default field"
1383
           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1384
           ),
1385
  'reinstall': (ReinstallInstance, ARGS_ONE,
1386
                [DEBUG_OPT, FORCE_OPT, os_opt,
1387
                 make_option("--select-os", dest="select_os",
1388
                             action="store_true", default=False,
1389
                             help="Interactive OS reinstall, lists available"
1390
                             " OS templates for selection"),
1391
                 SUBMIT_OPT,
1392
                 ],
1393
                "[-f] <instance>", "Reinstall a stopped instance"),
1394
  'remove': (RemoveInstance, ARGS_ONE,
1395
             [DEBUG_OPT, FORCE_OPT,
1396
              make_option("--ignore-failures", dest="ignore_failures",
1397
                          action="store_true", default=False,
1398
                          help=("Remove the instance from the cluster even"
1399
                                " if there are failures during the removal"
1400
                                " process (shutdown, disk removal, etc.)")),
1401
              SUBMIT_OPT,
1402
              ],
1403
             "[-f] <instance>", "Shuts down the instance and removes it"),
1404
  'rename': (RenameInstance, ARGS_FIXED(2),
1405
             [DEBUG_OPT,
1406
              make_option("--no-ip-check", dest="ignore_ip",
1407
                          help="Do not check that the IP of the new name"
1408
                          " is alive",
1409
                          default=False, action="store_true"),
1410
              SUBMIT_OPT,
1411
              ],
1412
             "<instance> <new_name>", "Rename the instance"),
1413
  'replace-disks': (ReplaceDisks, ARGS_ONE,
1414
                    [DEBUG_OPT,
1415
                     make_option("-n", "--new-secondary", dest="new_secondary",
1416
                                 help=("New secondary node (for secondary"
1417
                                       " node change)"), metavar="NODE",
1418
                                 default=None),
1419
                     make_option("-p", "--on-primary", dest="on_primary",
1420
                                 default=False, action="store_true",
1421
                                 help=("Replace the disk(s) on the primary"
1422
                                       " node (only for the drbd template)")),
1423
                     make_option("-s", "--on-secondary", dest="on_secondary",
1424
                                 default=False, action="store_true",
1425
                                 help=("Replace the disk(s) on the secondary"
1426
                                       " node (only for the drbd template)")),
1427
                     make_option("--disks", dest="disks", default=None,
1428
                                 help=("Comma-separated list of disks"
1429
                                       " to replace (e.g. sda) (optional,"
1430
                                       " defaults to all disks")),
1431
                     make_option("-i", "--iallocator", metavar="<NAME>",
1432
                                 help="Select new secondary for the instance"
1433
                                 " automatically using the"
1434
                                 " <NAME> iallocator plugin (enables"
1435
                                 " secondary node replacement)",
1436
                                 default=None, type="string"),
1437
                     SUBMIT_OPT,
1438
                     ],
1439
                    "[-s|-p|-n NODE] <instance>",
1440
                    "Replaces all disks for the instance"),
1441
  'modify': (SetInstanceParams, ARGS_ONE,
1442
             [DEBUG_OPT, FORCE_OPT,
1443
              keyval_option("-H", "--hypervisor", type="keyval",
1444
                            default={}, dest="hypervisor",
1445
                            help="Change hypervisor parameters"),
1446
              keyval_option("-B", "--backend", type="keyval",
1447
                            default={}, dest="beparams",
1448
                            help="Change backend parameters"),
1449
              ikv_option("--disk", help="Disk changes",
1450
                         default=[], dest="disks",
1451
                         action="append",
1452
                         type="identkeyval"),
1453
              ikv_option("--net", help="NIC changes",
1454
                         default=[], dest="nics",
1455
                         action="append",
1456
                         type="identkeyval"),
1457
              SUBMIT_OPT,
1458
              ],
1459
             "<instance>", "Alters the parameters of an instance"),
1460
  'shutdown': (ShutdownInstance, ARGS_ANY,
1461
               [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1462
                m_clust_opt, m_inst_opt, m_force_multi,
1463
                SUBMIT_OPT,
1464
                ],
1465
               "<instance>", "Stops an instance"),
1466
  'startup': (StartupInstance, ARGS_ANY,
1467
              [DEBUG_OPT, FORCE_OPT, m_force_multi,
1468
               make_option("-e", "--extra", dest="extra_args",
1469
                           help="Extra arguments for the instance's kernel",
1470
                           default=None, type="string", metavar="<PARAMS>"),
1471
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1472
               m_clust_opt, m_inst_opt,
1473
               SUBMIT_OPT,
1474
               ],
1475
            "<instance>", "Starts an instance"),
1476

    
1477
  'reboot': (RebootInstance, ARGS_ANY,
1478
              [DEBUG_OPT, m_force_multi,
1479
               make_option("-e", "--extra", dest="extra_args",
1480
                           help="Extra arguments for the instance's kernel",
1481
                           default=None, type="string", metavar="<PARAMS>"),
1482
               make_option("-t", "--type", dest="reboot_type",
1483
                           help="Type of reboot: soft/hard/full",
1484
                           default=constants.INSTANCE_REBOOT_HARD,
1485
                           type="string", metavar="<REBOOT>"),
1486
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1487
                           default=False, action="store_true",
1488
                           help="Ignore errors from secondaries"),
1489
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1490
               m_clust_opt, m_inst_opt,
1491
               SUBMIT_OPT,
1492
               ],
1493
            "<instance>", "Reboots an instance"),
1494
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1495
                     "<instance>",
1496
                     "Activate an instance's disks"),
1497
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1498
                       "<instance>",
1499
                       "Deactivate an instance's disks"),
1500
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1501
                [DEBUG_OPT, SUBMIT_OPT,
1502
                 make_option("--no-wait-for-sync",
1503
                             dest="wait_for_sync", default=True,
1504
                             action="store_false",
1505
                             help="Don't wait for sync (DANGEROUS!)"),
1506
                 ],
1507
                "<instance> <disk> <size>", "Grow an instance's disk"),
1508
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1509
                "<instance_name>", "List the tags of the given instance"),
1510
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1511
               "<instance_name> tag...", "Add tags to the given instance"),
1512
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1513
                  "<instance_name> tag...", "Remove tags from given instance"),
1514
  }
1515

    
1516
#: dictionary with aliases for commands
1517
aliases = {
1518
  'activate_block_devs': 'activate-disks',
1519
  'replace_disks': 'replace-disks',
1520
  'start': 'startup',
1521
  'stop': 'shutdown',
1522
  }
1523

    
1524
if __name__ == '__main__':
1525
  sys.exit(GenericMain(commands, aliases=aliases,
1526
                       override={"tag_type": constants.TAG_INSTANCE}))