Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ ec79568d

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
      "status": "Status", "tags": "Tags",
230
      "network_port": "Network_port",
231
      "hv/kernel_path": "Kernel_path",
232
      "hv/initrd_path": "Initrd_path",
233
      "hv/boot_order": "HVM_boot_order",
234
      "hv/acpi": "HVM_ACPI",
235
      "hv/pae": "HVM_PAE",
236
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
237
      "hv/nic_type": "HVM_NIC_type",
238
      "hv/disk_type": "HVM_Disk_type",
239
      "hv/vnc_bind_address": "VNC_bind_address",
240
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
241
      "hvparams": "Hypervisor_parameters",
242
      "be/memory": "Configured_memory",
243
      "be/vcpus": "VCPUs",
244
      "be/auto_balance": "Auto_balance",
245
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
246
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
247
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
248
      }
249
  else:
250
    headers = None
251

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

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

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

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

    
295
  return 0
296

    
297

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

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

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

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

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

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

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

    
357
  ValidateBeParams(opts.beparams)
358

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

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

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

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

    
388
  SubmitOrSend(op, opts)
389
  return 0
390

    
391

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
514
  return 0
515

    
516

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

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

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

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

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

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

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

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

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

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

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

    
568
  return 0
569

    
570

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

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

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

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

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

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

    
600

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

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

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

    
618

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

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

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

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

    
641

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

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

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

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

    
660

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

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

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

    
684

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

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

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

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

    
719

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

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

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

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

    
754

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

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

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

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

    
785

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

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

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

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

    
825

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

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

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

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

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

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

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

    
857

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

    
861
  The migrate is done without shutdown.
862

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

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

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

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

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

    
895

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

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

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

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

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

    
921

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

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

    
940
  return data
941

    
942

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

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

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

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

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

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

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

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

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

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

    
1049

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

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

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

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

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

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

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

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

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

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

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

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

    
1167

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

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

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

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

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

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

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

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

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

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

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

    
1238

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

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

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

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

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

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

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

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

    
1272

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

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

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

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

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