Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ b59252fe

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
      "bridge": "Bridge",
205
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
206
      "disk_usage": "DiskUsage",
207
      "status": "Status", "tags": "Tags",
208
      "network_port": "Network_port",
209
      "hv/kernel_path": "Kernel_path",
210
      "hv/initrd_path": "Initrd_path",
211
      "hv/boot_order": "HVM_boot_order",
212
      "hv/acpi": "HVM_ACPI",
213
      "hv/pae": "HVM_PAE",
214
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
215
      "hv/nic_type": "HVM_NIC_type",
216
      "hv/disk_type": "HVM_Disk_type",
217
      "hv/vnc_bind_address": "VNC_bind_address",
218
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
219
      "hvparams": "Hypervisor_parameters",
220
      "be/memory": "Configured_memory",
221
      "be/vcpus": "VCPUs",
222
      "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.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
227
      }
228
  else:
229
    headers = None
230

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

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

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

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

    
274
  return 0
275

    
276

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

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

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

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

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

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

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

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

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

    
361
  SubmitOrSend(op, opts)
362
  return 0
363

    
364

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

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

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

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

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

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

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

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

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

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

    
444
  jex = JobExecutor()
445

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

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

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

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

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

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

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

    
495
  return 0
496

    
497

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

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

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

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

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

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

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

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

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

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

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

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

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

    
723

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

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

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

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

    
758

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

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

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

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

    
789

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

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

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

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

    
829

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

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

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

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

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

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

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

    
861

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

    
865
  The migrate is done without shutdown.
866

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

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

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

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

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

    
899

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

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

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

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

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

    
925

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

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

    
944
  return data
945

    
946

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

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

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

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

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

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

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

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

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

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

    
1057

    
1058
def _FormatList(buf, data, indent_level):
1059
  """Formats a list of data at a given indent level.
1060

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

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

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

    
1086
def ShowInstanceConfig(opts, args):
1087
  """Compute instance run-time status.
1088

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

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

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

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

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

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

    
1169
    for idx, device in enumerate(instance["disks"]):
1170
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1171

    
1172
  ToStdout(buf.getvalue().rstrip('\n'))
1173
  return retcode
1174

    
1175

    
1176
def SetInstanceParams(opts, args):
1177
  """Modifies an instance.
1178

    
1179
  All parameters take effect only at the next restart of the instance.
1180

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

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

    
1193
  for param in opts.beparams:
1194
    if isinstance(opts.beparams[param], basestring):
1195
      if opts.beparams[param].lower() == "default":
1196
        opts.beparams[param] = constants.VALUE_DEFAULT
1197

    
1198
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1199
                      allowed_values=[constants.VALUE_DEFAULT])
1200

    
1201
  for param in opts.hypervisor:
1202
    if isinstance(opts.hypervisor[param], basestring):
1203
      if opts.hypervisor[param].lower() == "default":
1204
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1205

    
1206
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1207
                      allowed_values=[constants.VALUE_DEFAULT])
1208

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

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

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

    
1234
  # even if here we process the result, we allow submit only
1235
  result = SubmitOrSend(op, opts)
1236

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

    
1245

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

    
1250
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1251
                    metavar="<os>")
1252

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

    
1259
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1260
                             help="Filter by nodes (primary only)",
1261
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1262

    
1263
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1264
                             help="Filter by nodes (secondary only)",
1265
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1266

    
1267
m_node_opt = make_option("--node", dest="multi_mode",
1268
                         help="Filter by nodes (primary and secondary)",
1269
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1270

    
1271
m_clust_opt = make_option("--all", dest="multi_mode",
1272
                          help="Select all instances in the cluster",
1273
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1274

    
1275
m_inst_opt = make_option("--instance", dest="multi_mode",
1276
                         help="Filter by instance name [default]",
1277
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1278

    
1279

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

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

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

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

    
1541
if __name__ == '__main__':
1542
  sys.exit(GenericMain(commands, aliases=aliases,
1543
                       override={"tag_type": constants.TAG_INSTANCE}))