Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 638c6349

History | View | Annotate | Download (54.6 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
                              False)
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, extra=""):
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%s"
137
         "Do you want to continue?" % (text, count, extra))
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 _EnsureInstancesExist(client, names):
158
  """Check for and ensure the given instance names exist.
159

    
160
  This function will raise an OpPrereqError in case they don't
161
  exist. Otherwise it will exit cleanly.
162

    
163
  @type client: L{luxi.Client}
164
  @param client: the client to use for the query
165
  @type names: list
166
  @param names: the list of instance names to query
167
  @raise errors.OpPrereqError: in case any instance is missing
168

    
169
  """
170
  # TODO: change LUQueryInstances to that it actually returns None
171
  # instead of raising an exception, or devise a better mechanism
172
  result = client.QueryInstances(names, ["name"], False)
173
  for orig_name, row in zip(names, result):
174
    if row[0] is None:
175
      raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name)
176

    
177

    
178
def ListInstances(opts, args):
179
  """List instances and their properties.
180

    
181
  @param opts: the command line options selected by the user
182
  @type args: list
183
  @param args: should be an empty list
184
  @rtype: int
185
  @return: the desired exit code
186

    
187
  """
188
  if opts.output is None:
189
    selected_fields = _LIST_DEF_FIELDS
190
  elif opts.output.startswith("+"):
191
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
192
  else:
193
    selected_fields = opts.output.split(",")
194

    
195
  output = GetClient().QueryInstances(args, selected_fields, opts.do_locking)
196

    
197
  if not opts.no_headers:
198
    headers = {
199
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
200
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
201
      "oper_state": "Running",
202
      "oper_ram": "Memory", "disk_template": "Disk_template",
203
      "ip": "IP_address", "mac": "MAC_address",
204
      "nic_mode": "NIC_Mode", "nic_link": "NIC_Link",
205
      "bridge": "Bridge",
206
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
207
      "disk_usage": "DiskUsage",
208
      "status": "Status", "tags": "Tags",
209
      "network_port": "Network_port",
210
      "hv/kernel_path": "Kernel_path",
211
      "hv/initrd_path": "Initrd_path",
212
      "hv/boot_order": "HVM_boot_order",
213
      "hv/acpi": "HVM_ACPI",
214
      "hv/pae": "HVM_PAE",
215
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
216
      "hv/nic_type": "HVM_NIC_type",
217
      "hv/disk_type": "HVM_Disk_type",
218
      "hv/vnc_bind_address": "VNC_bind_address",
219
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
220
      "hvparams": "Hypervisor_parameters",
221
      "be/memory": "Configured_memory",
222
      "be/vcpus": "VCPUs",
223
      "be/auto_balance": "Auto_balance",
224
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
225
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
226
      "nic.modes": "NIC_modes", "nic.links": "NIC_links",
227
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
228
      }
229
  else:
230
    headers = None
231

    
232
  unitfields = ["be/memory", "oper_ram", "sd(a|b)_size", "disk\.size/.*"]
233
  numfields = ["be/memory", "oper_ram", "sd(a|b)_size", "be/vcpus",
234
               "serial_no", "(disk|nic)\.count", "disk\.size/.*"]
235

    
236
  list_type_fields = ("tags", "disk.sizes", "nic.macs", "nic.ips",
237
                      "nic.modes", "nic.links", "nic.bridges")
238
  # change raw values to nicer strings
239
  for row in output:
240
    for idx, field in enumerate(selected_fields):
241
      val = row[idx]
242
      if field == "snodes":
243
        val = ",".join(val) or "-"
244
      elif field == "admin_state":
245
        if val:
246
          val = "yes"
247
        else:
248
          val = "no"
249
      elif field == "oper_state":
250
        if val is None:
251
          val = "(node down)"
252
        elif val: # True
253
          val = "running"
254
        else:
255
          val = "stopped"
256
      elif field == "oper_ram":
257
        if val is None:
258
          val = "(node down)"
259
      elif field == "sda_size" or field == "sdb_size":
260
        if val is None:
261
          val = "N/A"
262
      elif field in list_type_fields:
263
        val = ",".join(str(item) for item in val)
264
      elif val is None:
265
        val = "-"
266
      row[idx] = str(val)
267

    
268
  data = GenerateTable(separator=opts.separator, headers=headers,
269
                       fields=selected_fields, unitfields=unitfields,
270
                       numfields=numfields, data=output, units=opts.units)
271

    
272
  for line in data:
273
    ToStdout(line)
274

    
275
  return 0
276

    
277

    
278
def AddInstance(opts, args):
279
  """Add an instance to the cluster.
280

    
281
  @param opts: the command line options selected by the user
282
  @type args: list
283
  @param args: should contain only one element, the new instance name
284
  @rtype: int
285
  @return: the desired exit code
286

    
287
  """
288
  instance = args[0]
289

    
290
  (pnode, snode) = SplitNodeOption(opts.node)
291

    
292
  hypervisor = None
293
  hvparams = {}
294
  if opts.hypervisor:
295
    hypervisor, hvparams = opts.hypervisor
296

    
297
  if opts.nics:
298
    try:
299
      nic_max = max(int(nidx[0])+1 for nidx in opts.nics)
300
    except ValueError, err:
301
      raise errors.OpPrereqError("Invalid NIC index passed: %s" % str(err))
302
    nics = [{}] * nic_max
303
    for nidx, ndict in opts.nics:
304
      nidx = int(nidx)
305
      nics[nidx] = ndict
306
  elif opts.no_nics:
307
    # no nics
308
    nics = []
309
  else:
310
    # default of one nic, all auto
311
    nics = [{}]
312

    
313
  if opts.disk_template == constants.DT_DISKLESS:
314
    if opts.disks or opts.sd_size is not None:
315
      raise errors.OpPrereqError("Diskless instance but disk"
316
                                 " information passed")
317
    disks = []
318
  else:
319
    if not opts.disks and not opts.sd_size:
320
      raise errors.OpPrereqError("No disk information specified")
321
    if opts.disks and opts.sd_size is not None:
322
      raise errors.OpPrereqError("Please use either the '--disk' or"
323
                                 " '-s' option")
324
    if opts.sd_size is not None:
325
      opts.disks = [(0, {"size": opts.sd_size})]
326
    try:
327
      disk_max = max(int(didx[0])+1 for didx in opts.disks)
328
    except ValueError, err:
329
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
330
    disks = [{}] * disk_max
331
    for didx, ddict in opts.disks:
332
      didx = int(didx)
333
      if "size" not in ddict:
334
        raise errors.OpPrereqError("Missing size for disk %d" % didx)
335
      try:
336
        ddict["size"] = utils.ParseUnit(ddict["size"])
337
      except ValueError, err:
338
        raise errors.OpPrereqError("Invalid disk size for disk %d: %s" %
339
                                   (didx, err))
340
      disks[didx] = ddict
341

    
342
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
343
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
344

    
345
  op = opcodes.OpCreateInstance(instance_name=instance,
346
                                disks=disks,
347
                                disk_template=opts.disk_template,
348
                                nics=nics,
349
                                mode=constants.INSTANCE_CREATE,
350
                                os_type=opts.os, pnode=pnode,
351
                                snode=snode,
352
                                start=opts.start, ip_check=opts.ip_check,
353
                                wait_for_sync=opts.wait_for_sync,
354
                                hypervisor=hypervisor,
355
                                hvparams=hvparams,
356
                                beparams=opts.beparams,
357
                                iallocator=opts.iallocator,
358
                                file_storage_dir=opts.file_storage_dir,
359
                                file_driver=opts.file_driver,
360
                                )
361

    
362
  SubmitOrSend(op, opts)
363
  return 0
364

    
365

    
366
def BatchCreate(opts, args):
367
  """Create instances using a definition file.
368

    
369
  This function reads a json file with instances defined
370
  in the form::
371

    
372
    {"instance-name":{
373
      "disk_size": [20480],
374
      "template": "drbd",
375
      "backend": {
376
        "memory": 512,
377
        "vcpus": 1 },
378
      "os": "debootstrap",
379
      "primary_node": "firstnode",
380
      "secondary_node": "secondnode",
381
      "iallocator": "dumb"}
382
    }
383

    
384
  Note that I{primary_node} and I{secondary_node} have precedence over
385
  I{iallocator}.
386

    
387
  @param opts: the command line options selected by the user
388
  @type args: list
389
  @param args: should contain one element, the json filename
390
  @rtype: int
391
  @return: the desired exit code
392

    
393
  """
394
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
395
                    "backend": {},
396
                    "iallocator": None,
397
                    "primary_node": None,
398
                    "secondary_node": None,
399
                    "ip": 'none',
400
                    "mac": 'auto',
401
                    "bridge": None,
402
                    "start": True,
403
                    "ip_check": True,
404
                    "hypervisor": None,
405
                    "hvparams": {},
406
                    "file_storage_dir": None,
407
                    "file_driver": 'loop'}
408

    
409
  def _PopulateWithDefaults(spec):
410
    """Returns a new hash combined with default values."""
411
    mydict = _DEFAULT_SPECS.copy()
412
    mydict.update(spec)
413
    return mydict
414

    
415
  def _Validate(spec):
416
    """Validate the instance specs."""
417
    # Validate fields required under any circumstances
418
    for required_field in ('os', 'template'):
419
      if required_field not in spec:
420
        raise errors.OpPrereqError('Required field "%s" is missing.' %
421
                                   required_field)
422
    # Validate special fields
423
    if spec['primary_node'] is not None:
424
      if (spec['template'] in constants.DTS_NET_MIRROR and
425
          spec['secondary_node'] is None):
426
        raise errors.OpPrereqError('Template requires secondary node, but'
427
                                   ' there was no secondary provided.')
428
    elif spec['iallocator'] is None:
429
      raise errors.OpPrereqError('You have to provide at least a primary_node'
430
                                 ' or an iallocator.')
431

    
432
    if (spec['hvparams'] and
433
        not isinstance(spec['hvparams'], dict)):
434
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
435

    
436
  json_filename = args[0]
437
  try:
438
    fd = open(json_filename, 'r')
439
    instance_data = simplejson.load(fd)
440
    fd.close()
441
  except Exception, err:
442
    ToStderr("Can't parse the instance definition file: %s" % str(err))
443
    return 1
444

    
445
  jex = JobExecutor()
446

    
447
  # Iterate over the instances and do:
448
  #  * Populate the specs with default value
449
  #  * Validate the instance specs
450
  i_names = utils.NiceSort(instance_data.keys())
451
  for name in i_names:
452
    specs = instance_data[name]
453
    specs = _PopulateWithDefaults(specs)
454
    _Validate(specs)
455

    
456
    hypervisor = specs['hypervisor']
457
    hvparams = specs['hvparams']
458

    
459
    disks = []
460
    for elem in specs['disk_size']:
461
      try:
462
        size = utils.ParseUnit(elem)
463
      except ValueError, err:
464
        raise errors.OpPrereqError("Invalid disk size '%s' for"
465
                                   " instance %s: %s" %
466
                                   (elem, name, err))
467
      disks.append({"size": size})
468

    
469
    nic0 = {'ip': specs['ip'], 'bridge': specs['bridge'], 'mac': specs['mac']}
470

    
471
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
472
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
473

    
474
    op = opcodes.OpCreateInstance(instance_name=name,
475
                                  disks=disks,
476
                                  disk_template=specs['template'],
477
                                  mode=constants.INSTANCE_CREATE,
478
                                  os_type=specs['os'],
479
                                  pnode=specs['primary_node'],
480
                                  snode=specs['secondary_node'],
481
                                  nics=[nic0],
482
                                  start=specs['start'],
483
                                  ip_check=specs['ip_check'],
484
                                  wait_for_sync=True,
485
                                  iallocator=specs['iallocator'],
486
                                  hypervisor=hypervisor,
487
                                  hvparams=hvparams,
488
                                  beparams=specs['backend'],
489
                                  file_storage_dir=specs['file_storage_dir'],
490
                                  file_driver=specs['file_driver'])
491

    
492
    jex.QueueJob(name, op)
493
  # we never want to wait, just show the submitted job IDs
494
  jex.WaitOrShow(False)
495

    
496
  return 0
497

    
498

    
499
def ReinstallInstance(opts, args):
500
  """Reinstall an instance.
501

    
502
  @param opts: the command line options selected by the user
503
  @type args: list
504
  @param args: should contain only one element, the name of the
505
      instance to be reinstalled
506
  @rtype: int
507
  @return: the desired exit code
508

    
509
  """
510
  # first, compute the desired name list
511
  if opts.multi_mode is None:
512
    opts.multi_mode = _SHUTDOWN_INSTANCES
513

    
514
  inames = _ExpandMultiNames(opts.multi_mode, args)
515
  if not inames:
516
    raise errors.OpPrereqError("Selection filter does not match any instances")
517

    
518
  # second, if requested, ask for an OS
519
  if opts.select_os is True:
520
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
521
    result = SubmitOpCode(op)
522

    
523
    if not result:
524
      ToStdout("Can't get the OS list")
525
      return 1
526

    
527
    ToStdout("Available OS templates:")
528
    number = 0
529
    choices = []
530
    for entry in result:
531
      ToStdout("%3s: %s", number, entry[0])
532
      choices.append(("%s" % number, entry[0], entry[0]))
533
      number = number + 1
534

    
535
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
536
    selected = AskUser("Enter OS template number (or x to abort):",
537
                       choices)
538

    
539
    if selected == 'exit':
540
      ToStderr("User aborted reinstall, exiting")
541
      return 1
542

    
543
    os_name = selected
544
  else:
545
    os_name = opts.os
546

    
547
  # third, get confirmation: multi-reinstall requires --force-multi
548
  # *and* --force, single-reinstall just --force
549
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
550
  if multi_on:
551
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
552
    if not ((opts.force_multi and opts.force) or
553
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
554
      return 1
555
  else:
556
    if not opts.force:
557
      usertext = ("This will reinstall the instance %s and remove"
558
                  " all data. Continue?") % inames[0]
559
      if not AskUser(usertext):
560
        return 1
561

    
562
  jex = JobExecutor(verbose=multi_on)
563
  for instance_name in inames:
564
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
565
                                     os_type=os_name)
566
    jex.QueueJob(instance_name, op)
567

    
568
  jex.WaitOrShow(not opts.submit_only)
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
    # do not add these parameters to the opcode unless they're defined
716
    if opts.hvparams:
717
      op.hvparams = opts.hvparams
718
    if opts.beparams:
719
      op.beparams = opts.beparams
720
    jex.QueueJob(name, op)
721
  jex.WaitOrShow(not opts.submit_only)
722
  return 0
723

    
724

    
725
def RebootInstance(opts, args):
726
  """Reboot instance(s).
727

    
728
  Depending on the parameters given, this will reboot one or more
729
  instances.
730

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

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

    
759

    
760
def ShutdownInstance(opts, args):
761
  """Shutdown an instance.
762

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

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

    
783
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
784
  for name in inames:
785
    op = opcodes.OpShutdownInstance(instance_name=name)
786
    jex.QueueJob(name, op)
787
  jex.WaitOrShow(not opts.submit_only)
788
  return 0
789

    
790

    
791
def ReplaceDisks(opts, args):
792
  """Replace the disks of an instance
793

    
794
  @param opts: the command line options selected by the user
795
  @type args: list
796
  @param args: should contain only one element, the instance name
797
  @rtype: int
798
  @return: the desired exit code
799

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

    
824
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
825
                              remote_node=new_2ndary, mode=mode,
826
                              iallocator=iallocator)
827
  SubmitOrSend(op, opts)
828
  return 0
829

    
830

    
831
def FailoverInstance(opts, args):
832
  """Failover an instance.
833

    
834
  The failover is done by shutting it down on its present node and
835
  starting it on the secondary.
836

    
837
  @param opts: the command line options selected by the user
838
  @type args: list
839
  @param args: should contain only one element, the instance name
840
  @rtype: int
841
  @return: the desired exit code
842

    
843
  """
844
  cl = GetClient()
845
  instance_name = args[0]
846
  force = opts.force
847

    
848
  if not force:
849
    _EnsureInstancesExist(cl, [instance_name])
850

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

    
857
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
858
                                  ignore_consistency=opts.ignore_consistency)
859
  SubmitOrSend(op, opts, cl=cl)
860
  return 0
861

    
862

    
863
def MigrateInstance(opts, args):
864
  """Migrate an instance.
865

    
866
  The migrate is done without shutdown.
867

    
868
  @param opts: the command line options selected by the user
869
  @type args: list
870
  @param args: should contain only one element, the instance name
871
  @rtype: int
872
  @return: the desired exit code
873

    
874
  """
875
  cl = GetClient()
876
  instance_name = args[0]
877
  force = opts.force
878

    
879
  if not force:
880
    _EnsureInstancesExist(cl, [instance_name])
881

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

    
895
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
896
                                 cleanup=opts.cleanup)
897
  SubmitOpCode(op, cl=cl)
898
  return 0
899

    
900

    
901
def ConnectToInstanceConsole(opts, args):
902
  """Connect to the console of an instance.
903

    
904
  @param opts: the command line options selected by the user
905
  @type args: list
906
  @param args: should contain only one element, the instance name
907
  @rtype: int
908
  @return: the desired exit code
909

    
910
  """
911
  instance_name = args[0]
912

    
913
  op = opcodes.OpConnectConsole(instance_name=instance_name)
914
  cmd = SubmitOpCode(op)
915

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

    
926

    
927
def _FormatLogicalID(dev_type, logical_id):
928
  """Formats the logical_id of a disk.
929

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

    
945
  return data
946

    
947

    
948
def _FormatBlockDevInfo(idx, top_level, dev, static):
949
  """Show block device information.
950

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

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

    
966
  """
967
  def helper(dtype, status):
968
    """Format one line for physical device status.
969

    
970
    @type dtype: str
971
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
972
    @type status: tuple
973
    @param status: a tuple as returned from L{backend.FindBlockDevice}
974
    @return: the string representing the status
975

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

    
986
    if minor is None:
987
      minor_string = "N/A"
988
    else:
989
      minor_string = str(minor)
990

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

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

    
1047
  if dev["children"]:
1048
    data.append("child devices:")
1049
    for c_idx, child in enumerate(dev["children"]):
1050
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1051
  d1.append(data)
1052
  return d1
1053

    
1054

    
1055
def _FormatList(buf, data, indent_level):
1056
  """Formats a list of data at a given indent level.
1057

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

    
1064
  @type buf: StringIO
1065
  @param buf: the buffer into which we write the output
1066
  @param data: the list to format
1067
  @type indent_level: int
1068
  @param indent_level: the indent level to format at
1069

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

    
1083
def ShowInstanceConfig(opts, args):
1084
  """Compute instance run-time status.
1085

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

    
1093
  """
1094
  if not args and not opts.show_all:
1095
    ToStderr("No instance selected."
1096
             " Please pass in --all if you want to query all instances.\n"
1097
             "Note that this can take a long time on a big cluster.")
1098
    return 1
1099
  elif args and opts.show_all:
1100
    ToStderr("Cannot use --all if you specify instance names.")
1101
    return 1
1102

    
1103
  retcode = 0
1104
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1105
  result = SubmitOpCode(op)
1106
  if not result:
1107
    ToStdout("No instances.")
1108
    return 1
1109

    
1110
  buf = StringIO()
1111
  retcode = 0
1112
  for instance_name in result:
1113
    instance = result[instance_name]
1114
    buf.write("Instance name: %s\n" % instance["name"])
1115
    buf.write("State: configured to be %s" % instance["config_state"])
1116
    if not opts.static:
1117
      buf.write(", actual state is %s" % instance["run_state"])
1118
    buf.write("\n")
1119
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1120
    ##          instance["auto_balance"])
1121
    buf.write("  Nodes:\n")
1122
    buf.write("    - primary: %s\n" % instance["pnode"])
1123
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1124
    buf.write("  Operating system: %s\n" % instance["os"])
1125
    if instance.has_key("network_port"):
1126
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1127
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1128

    
1129
    # custom VNC console information
1130
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1131
                                                 None)
1132
    if vnc_bind_address:
1133
      port = instance["network_port"]
1134
      display = int(port) - constants.VNC_BASE_PORT
1135
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1136
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1137
                                                   port,
1138
                                                   display)
1139
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1140
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1141
                             (vnc_bind_address, port,
1142
                              instance["pnode"], display))
1143
      else:
1144
        # vnc bind address is a file
1145
        vnc_console_port = "%s:%s" % (instance["pnode"],
1146
                                      vnc_bind_address)
1147
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1148

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

    
1166
    for idx, device in enumerate(instance["disks"]):
1167
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1168

    
1169
  ToStdout(buf.getvalue().rstrip('\n'))
1170
  return retcode
1171

    
1172

    
1173
def SetInstanceParams(opts, args):
1174
  """Modifies an instance.
1175

    
1176
  All parameters take effect only at the next restart of the instance.
1177

    
1178
  @param opts: the command line options selected by the user
1179
  @type args: list
1180
  @param args: should contain only one element, the instance name
1181
  @rtype: int
1182
  @return: the desired exit code
1183

    
1184
  """
1185
  if not (opts.nics or opts.disks or
1186
          opts.hypervisor or opts.beparams):
1187
    ToStderr("Please give at least one of the parameters.")
1188
    return 1
1189

    
1190
  for param in opts.beparams:
1191
    if isinstance(opts.beparams[param], basestring):
1192
      if opts.beparams[param].lower() == "default":
1193
        opts.beparams[param] = constants.VALUE_DEFAULT
1194

    
1195
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1196
                      allowed_values=[constants.VALUE_DEFAULT])
1197

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

    
1203
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1204
                      allowed_values=[constants.VALUE_DEFAULT])
1205

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

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

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

    
1231
  # even if here we process the result, we allow submit only
1232
  result = SubmitOrSend(op, opts)
1233

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

    
1242

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

    
1247
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1248
                    metavar="<os>")
1249

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

    
1256
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1257
                             help="Filter by nodes (primary only)",
1258
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1259

    
1260
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1261
                             help="Filter by nodes (secondary only)",
1262
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1263

    
1264
m_node_opt = make_option("--node", dest="multi_mode",
1265
                         help="Filter by nodes (primary and secondary)",
1266
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1267

    
1268
m_clust_opt = make_option("--all", dest="multi_mode",
1269
                          help="Select all instances in the cluster",
1270
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1271

    
1272
m_inst_opt = make_option("--instance", dest="multi_mode",
1273
                         help="Filter by instance name [default]",
1274
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1275

    
1276

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

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

    
1494
  'reboot': (RebootInstance, ARGS_ANY,
1495
              [DEBUG_OPT, m_force_multi,
1496
               make_option("-t", "--type", dest="reboot_type",
1497
                           help="Type of reboot: soft/hard/full",
1498
                           default=constants.INSTANCE_REBOOT_HARD,
1499
                           type="string", metavar="<REBOOT>"),
1500
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1501
                           default=False, action="store_true",
1502
                           help="Ignore errors from secondaries"),
1503
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1504
               m_clust_opt, m_inst_opt,
1505
               SUBMIT_OPT,
1506
               ],
1507
            "<instance>", "Reboots an instance"),
1508
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1509
                     "<instance>",
1510
                     "Activate an instance's disks"),
1511
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1512
                       "<instance>",
1513
                       "Deactivate an instance's disks"),
1514
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1515
                [DEBUG_OPT, SUBMIT_OPT,
1516
                 make_option("--no-wait-for-sync",
1517
                             dest="wait_for_sync", default=True,
1518
                             action="store_false",
1519
                             help="Don't wait for sync (DANGEROUS!)"),
1520
                 ],
1521
                "<instance> <disk> <size>", "Grow an instance's disk"),
1522
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1523
                "<instance_name>", "List the tags of the given instance"),
1524
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1525
               "<instance_name> tag...", "Add tags to the given instance"),
1526
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1527
                  "<instance_name> tag...", "Remove tags from given instance"),
1528
  }
1529

    
1530
#: dictionary with aliases for commands
1531
aliases = {
1532
  'activate_block_devs': 'activate-disks',
1533
  'replace_disks': 'replace-disks',
1534
  'start': 'startup',
1535
  'stop': 'shutdown',
1536
  }
1537

    
1538
if __name__ == '__main__':
1539
  sys.exit(GenericMain(commands, aliases=aliases,
1540
                       override={"tag_type": constants.TAG_INSTANCE}))