Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ bd315bfa

History | View | Annotate | Download (57.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{ganeti.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
      "vcpus": "VCPUs",
224
      "be/auto_balance": "Auto_balance",
225
      "disk.count": "Disks", "disk.sizes": "Disk_sizes",
226
      "nic.count": "NICs", "nic.ips": "NIC_IPs",
227
      "nic.modes": "NIC_modes", "nic.links": "NIC_links",
228
      "nic.bridges": "NIC_bridges", "nic.macs": "NIC_MACs",
229
      }
230
  else:
231
    headers = None
232

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

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

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

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

    
276
  return 0
277

    
278

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

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

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

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

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

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

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

    
349
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES)
350
  utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
351

    
352
  op = opcodes.OpCreateInstance(instance_name=instance,
353
                                disks=disks,
354
                                disk_template=opts.disk_template,
355
                                nics=nics,
356
                                mode=constants.INSTANCE_CREATE,
357
                                os_type=opts.os, pnode=pnode,
358
                                snode=snode,
359
                                start=opts.start, ip_check=opts.ip_check,
360
                                wait_for_sync=opts.wait_for_sync,
361
                                hypervisor=hypervisor,
362
                                hvparams=hvparams,
363
                                beparams=opts.beparams,
364
                                iallocator=opts.iallocator,
365
                                file_storage_dir=opts.file_storage_dir,
366
                                file_driver=opts.file_driver,
367
                                )
368

    
369
  SubmitOrSend(op, opts)
370
  return 0
371

    
372

    
373
def BatchCreate(opts, args):
374
  """Create instances using a definition file.
375

    
376
  This function reads a json file with instances defined
377
  in the form::
378

    
379
    {"instance-name":{
380
      "disk_size": [20480],
381
      "template": "drbd",
382
      "backend": {
383
        "memory": 512,
384
        "vcpus": 1 },
385
      "os": "debootstrap",
386
      "primary_node": "firstnode",
387
      "secondary_node": "secondnode",
388
      "iallocator": "dumb"}
389
    }
390

    
391
  Note that I{primary_node} and I{secondary_node} have precedence over
392
  I{iallocator}.
393

    
394
  @param opts: the command line options selected by the user
395
  @type args: list
396
  @param args: should contain one element, the json filename
397
  @rtype: int
398
  @return: the desired exit code
399

    
400
  """
401
  _DEFAULT_SPECS = {"disk_size": [20 * 1024],
402
                    "backend": {},
403
                    "iallocator": None,
404
                    "primary_node": None,
405
                    "secondary_node": None,
406
                    "nics": None,
407
                    "start": True,
408
                    "ip_check": True,
409
                    "hypervisor": None,
410
                    "hvparams": {},
411
                    "file_storage_dir": None,
412
                    "file_driver": 'loop'}
413

    
414
  def _PopulateWithDefaults(spec):
415
    """Returns a new hash combined with default values."""
416
    mydict = _DEFAULT_SPECS.copy()
417
    mydict.update(spec)
418
    return mydict
419

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

    
437
    if (spec['hvparams'] and
438
        not isinstance(spec['hvparams'], dict)):
439
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
440

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

    
450
  jex = JobExecutor()
451

    
452
  # Iterate over the instances and do:
453
  #  * Populate the specs with default value
454
  #  * Validate the instance specs
455
  i_names = utils.NiceSort(instance_data.keys())
456
  for name in i_names:
457
    specs = instance_data[name]
458
    specs = _PopulateWithDefaults(specs)
459
    _Validate(specs)
460

    
461
    hypervisor = specs['hypervisor']
462
    hvparams = specs['hvparams']
463

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

    
474
    utils.ForceDictType(specs['backend'], constants.BES_PARAMETER_TYPES)
475
    utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES)
476

    
477
    tmp_nics = []
478
    for field in ('ip', 'mac', 'mode', 'link', 'bridge'):
479
      if field in specs:
480
        if not tmp_nics:
481
          tmp_nics.append({})
482
        tmp_nics[0][field] = specs[field]
483

    
484
    if specs['nics'] is not None and tmp_nics:
485
      raise errors.OpPrereqError("'nics' list incompatible with using"
486
                                 " individual nic fields as well")
487
    elif specs['nics'] is not None:
488
      tmp_nics = specs['nics']
489
    elif not tmp_nics:
490
      tmp_nics = [{}]
491

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

    
510
    jex.QueueJob(name, op)
511
  # we never want to wait, just show the submitted job IDs
512
  jex.WaitOrShow(False)
513

    
514
  return 0
515

    
516

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

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

    
527
  """
528
  # first, compute the desired name list
529
  if opts.multi_mode is None:
530
    opts.multi_mode = _SHUTDOWN_INSTANCES
531

    
532
  inames = _ExpandMultiNames(opts.multi_mode, args)
533
  if not inames:
534
    raise errors.OpPrereqError("Selection filter does not match any instances")
535

    
536
  # second, if requested, ask for an OS
537
  if opts.select_os is True:
538
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
539
    result = SubmitOpCode(op)
540

    
541
    if not result:
542
      ToStdout("Can't get the OS list")
543
      return 1
544

    
545
    ToStdout("Available OS templates:")
546
    number = 0
547
    choices = []
548
    for entry in result:
549
      ToStdout("%3s: %s", number, entry[0])
550
      choices.append(("%s" % number, entry[0], entry[0]))
551
      number = number + 1
552

    
553
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
554
    selected = AskUser("Enter OS template number (or x to abort):",
555
                       choices)
556

    
557
    if selected == 'exit':
558
      ToStderr("User aborted reinstall, exiting")
559
      return 1
560

    
561
    os_name = selected
562
  else:
563
    os_name = opts.os
564

    
565
  # third, get confirmation: multi-reinstall requires --force-multi
566
  # *and* --force, single-reinstall just --force
567
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
568
  if multi_on:
569
    warn_msg = "Note: this will remove *all* data for the below instances!\n"
570
    if not ((opts.force_multi and opts.force) or
571
            _ConfirmOperation(inames, "reinstall", extra=warn_msg)):
572
      return 1
573
  else:
574
    if not opts.force:
575
      usertext = ("This will reinstall the instance %s and remove"
576
                  " all data. Continue?") % inames[0]
577
      if not AskUser(usertext):
578
        return 1
579

    
580
  jex = JobExecutor(verbose=multi_on)
581
  for instance_name in inames:
582
    op = opcodes.OpReinstallInstance(instance_name=instance_name,
583
                                     os_type=os_name)
584
    jex.QueueJob(instance_name, op)
585

    
586
  jex.WaitOrShow(not opts.submit_only)
587
  return 0
588

    
589

    
590
def RemoveInstance(opts, args):
591
  """Remove an instance.
592

    
593
  @param opts: the command line options selected by the user
594
  @type args: list
595
  @param args: should contain only one element, the name of
596
      the instance to be removed
597
  @rtype: int
598
  @return: the desired exit code
599

    
600
  """
601
  instance_name = args[0]
602
  force = opts.force
603
  cl = GetClient()
604

    
605
  if not force:
606
    _EnsureInstancesExist(cl, [instance_name])
607

    
608
    usertext = ("This will remove the volumes of the instance %s"
609
                " (including mirrors), thus removing all the data"
610
                " of the instance. Continue?") % instance_name
611
    if not AskUser(usertext):
612
      return 1
613

    
614
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
615
                                ignore_failures=opts.ignore_failures)
616
  SubmitOrSend(op, opts, cl=cl)
617
  return 0
618

    
619

    
620
def RenameInstance(opts, args):
621
  """Rename an instance.
622

    
623
  @param opts: the command line options selected by the user
624
  @type args: list
625
  @param args: should contain two elements, the old and the
626
      new instance names
627
  @rtype: int
628
  @return: the desired exit code
629

    
630
  """
631
  op = opcodes.OpRenameInstance(instance_name=args[0],
632
                                new_name=args[1],
633
                                ignore_ip=opts.ignore_ip)
634
  SubmitOrSend(op, opts)
635
  return 0
636

    
637

    
638
def ActivateDisks(opts, args):
639
  """Activate an instance's disks.
640

    
641
  This serves two purposes:
642
    - it allows (as long as the instance is not running)
643
      mounting the disks and modifying them from the node
644
    - it repairs inactive secondary drbds
645

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

    
652
  """
653
  instance_name = args[0]
654
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name,
655
                                       ignore_size=opts.ignore_size)
656
  disks_info = SubmitOrSend(op, opts)
657
  for host, iname, nname in disks_info:
658
    ToStdout("%s:%s:%s", host, iname, nname)
659
  return 0
660

    
661

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

    
665
  This function takes the instance name, looks for its primary node
666
  and the tries to shutdown its block devices on that node.
667

    
668
  @param opts: the command line options selected by the user
669
  @type args: list
670
  @param args: should contain only one element, the instance name
671
  @rtype: int
672
  @return: the desired exit code
673

    
674
  """
675
  instance_name = args[0]
676
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
677
  SubmitOrSend(op, opts)
678
  return 0
679

    
680

    
681
def RecreateDisks(opts, args):
682
  """Recreate an instance's disks.
683

    
684
  @param opts: the command line options selected by the user
685
  @type args: list
686
  @param args: should contain only one element, the instance name
687
  @rtype: int
688
  @return: the desired exit code
689

    
690
  """
691
  instance_name = args[0]
692
  if opts.disks:
693
    try:
694
      opts.disks = [int(v) for v in opts.disks.split(",")]
695
    except (ValueError, TypeError), err:
696
      ToStderr("Invalid disks value: %s" % str(err))
697
      return 1
698
  else:
699
    opts.disks = []
700

    
701
  op = opcodes.OpRecreateInstanceDisks(instance_name=instance_name,
702
                                       disks=opts.disks)
703
  SubmitOrSend(op, opts)
704
  return 0
705

    
706

    
707
def GrowDisk(opts, args):
708
  """Grow an instance's disks.
709

    
710
  @param opts: the command line options selected by the user
711
  @type args: list
712
  @param args: should contain two elements, the instance name
713
      whose disks we grow and the disk name, e.g. I{sda}
714
  @rtype: int
715
  @return: the desired exit code
716

    
717
  """
718
  instance = args[0]
719
  disk = args[1]
720
  try:
721
    disk = int(disk)
722
  except ValueError, err:
723
    raise errors.OpPrereqError("Invalid disk index: %s" % str(err))
724
  amount = utils.ParseUnit(args[2])
725
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
726
                          wait_for_sync=opts.wait_for_sync)
727
  SubmitOrSend(op, opts)
728
  return 0
729

    
730

    
731
def StartupInstance(opts, args):
732
  """Startup instances.
733

    
734
  Depending on the options given, this will start one or more
735
  instances.
736

    
737
  @param opts: the command line options selected by the user
738
  @type args: list
739
  @param args: the instance or node names based on which we
740
      create the final selection (in conjunction with the
741
      opts argument)
742
  @rtype: int
743
  @return: the desired exit code
744

    
745
  """
746
  cl = GetClient()
747
  if opts.multi_mode is None:
748
    opts.multi_mode = _SHUTDOWN_INSTANCES
749
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
750
  if not inames:
751
    raise errors.OpPrereqError("Selection filter does not match any instances")
752
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
753
  if not (opts.force_multi or not multi_on
754
          or _ConfirmOperation(inames, "startup")):
755
    return 1
756
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
757
  for name in inames:
758
    op = opcodes.OpStartupInstance(instance_name=name,
759
                                   force=opts.force)
760
    # do not add these parameters to the opcode unless they're defined
761
    if opts.hvparams:
762
      op.hvparams = opts.hvparams
763
    if opts.beparams:
764
      op.beparams = opts.beparams
765
    jex.QueueJob(name, op)
766
  jex.WaitOrShow(not opts.submit_only)
767
  return 0
768

    
769

    
770
def RebootInstance(opts, args):
771
  """Reboot instance(s).
772

    
773
  Depending on the parameters given, this will reboot one or more
774
  instances.
775

    
776
  @param opts: the command line options selected by the user
777
  @type args: list
778
  @param args: the instance or node names based on which we
779
      create the final selection (in conjunction with the
780
      opts argument)
781
  @rtype: int
782
  @return: the desired exit code
783

    
784
  """
785
  cl = GetClient()
786
  if opts.multi_mode is None:
787
    opts.multi_mode = _SHUTDOWN_INSTANCES
788
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
789
  if not inames:
790
    raise errors.OpPrereqError("Selection filter does not match any instances")
791
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
792
  if not (opts.force_multi or not multi_on
793
          or _ConfirmOperation(inames, "reboot")):
794
    return 1
795
  jex = JobExecutor(verbose=multi_on, cl=cl)
796
  for name in inames:
797
    op = opcodes.OpRebootInstance(instance_name=name,
798
                                  reboot_type=opts.reboot_type,
799
                                  ignore_secondaries=opts.ignore_secondaries)
800
    jex.QueueJob(name, op)
801
  jex.WaitOrShow(not opts.submit_only)
802
  return 0
803

    
804

    
805
def ShutdownInstance(opts, args):
806
  """Shutdown an instance.
807

    
808
  @param opts: the command line options selected by the user
809
  @type args: list
810
  @param args: the instance or node names based on which we
811
      create the final selection (in conjunction with the
812
      opts argument)
813
  @rtype: int
814
  @return: the desired exit code
815

    
816
  """
817
  cl = GetClient()
818
  if opts.multi_mode is None:
819
    opts.multi_mode = _SHUTDOWN_INSTANCES
820
  inames = _ExpandMultiNames(opts.multi_mode, args, client=cl)
821
  if not inames:
822
    raise errors.OpPrereqError("Selection filter does not match any instances")
823
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
824
  if not (opts.force_multi or not multi_on
825
          or _ConfirmOperation(inames, "shutdown")):
826
    return 1
827

    
828
  jex = cli.JobExecutor(verbose=multi_on, cl=cl)
829
  for name in inames:
830
    op = opcodes.OpShutdownInstance(instance_name=name)
831
    jex.QueueJob(name, op)
832
  jex.WaitOrShow(not opts.submit_only)
833
  return 0
834

    
835

    
836
def ReplaceDisks(opts, args):
837
  """Replace the disks of an instance
838

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

    
845
  """
846
  instance_name = args[0]
847
  new_2ndary = opts.new_secondary
848
  iallocator = opts.iallocator
849
  if opts.disks is None:
850
    disks = []
851
  else:
852
    try:
853
      disks = [int(i) for i in opts.disks.split(",")]
854
    except ValueError, err:
855
      raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err))
856
  cnt = [opts.on_primary, opts.on_secondary, opts.auto,
857
         new_2ndary is not None, iallocator is not None].count(True)
858
  if cnt != 1:
859
    raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -i"
860
                               " options must be passed")
861
  elif opts.on_primary:
862
    mode = constants.REPLACE_DISK_PRI
863
  elif opts.on_secondary:
864
    mode = constants.REPLACE_DISK_SEC
865
  elif opts.auto:
866
    mode = constants.REPLACE_DISK_AUTO
867
    if disks:
868
      raise errors.OpPrereqError("Cannot specify disks when using automatic"
869
                                 " mode")
870
  elif new_2ndary is not None or iallocator is not None:
871
    # replace secondary
872
    mode = constants.REPLACE_DISK_CHG
873

    
874
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
875
                              remote_node=new_2ndary, mode=mode,
876
                              iallocator=iallocator)
877
  SubmitOrSend(op, opts)
878
  return 0
879

    
880

    
881
def FailoverInstance(opts, args):
882
  """Failover an instance.
883

    
884
  The failover is done by shutting it down on its present node and
885
  starting it on the secondary.
886

    
887
  @param opts: the command line options selected by the user
888
  @type args: list
889
  @param args: should contain only one element, the instance name
890
  @rtype: int
891
  @return: the desired exit code
892

    
893
  """
894
  cl = GetClient()
895
  instance_name = args[0]
896
  force = opts.force
897

    
898
  if not force:
899
    _EnsureInstancesExist(cl, [instance_name])
900

    
901
    usertext = ("Failover will happen to image %s."
902
                " This requires a shutdown of the instance. Continue?" %
903
                (instance_name,))
904
    if not AskUser(usertext):
905
      return 1
906

    
907
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
908
                                  ignore_consistency=opts.ignore_consistency)
909
  SubmitOrSend(op, opts, cl=cl)
910
  return 0
911

    
912

    
913
def MigrateInstance(opts, args):
914
  """Migrate an instance.
915

    
916
  The migrate is done without shutdown.
917

    
918
  @param opts: the command line options selected by the user
919
  @type args: list
920
  @param args: should contain only one element, the instance name
921
  @rtype: int
922
  @return: the desired exit code
923

    
924
  """
925
  cl = GetClient()
926
  instance_name = args[0]
927
  force = opts.force
928

    
929
  if not force:
930
    _EnsureInstancesExist(cl, [instance_name])
931

    
932
    if opts.cleanup:
933
      usertext = ("Instance %s will be recovered from a failed migration."
934
                  " Note that the migration procedure (including cleanup)" %
935
                  (instance_name,))
936
    else:
937
      usertext = ("Instance %s will be migrated. Note that migration" %
938
                  (instance_name,))
939
    usertext += (" is **experimental** in this version."
940
                " This might impact the instance if anything goes wrong."
941
                " Continue?")
942
    if not AskUser(usertext):
943
      return 1
944

    
945
  op = opcodes.OpMigrateInstance(instance_name=instance_name, live=opts.live,
946
                                 cleanup=opts.cleanup)
947
  SubmitOpCode(op, cl=cl)
948
  return 0
949

    
950

    
951
def ConnectToInstanceConsole(opts, args):
952
  """Connect to the console of an instance.
953

    
954
  @param opts: the command line options selected by the user
955
  @type args: list
956
  @param args: should contain only one element, the instance name
957
  @rtype: int
958
  @return: the desired exit code
959

    
960
  """
961
  instance_name = args[0]
962

    
963
  op = opcodes.OpConnectConsole(instance_name=instance_name)
964
  cmd = SubmitOpCode(op)
965

    
966
  if opts.show_command:
967
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
968
  else:
969
    try:
970
      os.execvp(cmd[0], cmd)
971
    finally:
972
      ToStderr("Can't run console command %s with arguments:\n'%s'",
973
               cmd[0], " ".join(cmd))
974
      os._exit(1)
975

    
976

    
977
def _FormatLogicalID(dev_type, logical_id):
978
  """Formats the logical_id of a disk.
979

    
980
  """
981
  if dev_type == constants.LD_DRBD8:
982
    node_a, node_b, port, minor_a, minor_b, key = logical_id
983
    data = [
984
      ("nodeA", "%s, minor=%s" % (node_a, minor_a)),
985
      ("nodeB", "%s, minor=%s" % (node_b, minor_b)),
986
      ("port", port),
987
      ("auth key", key),
988
      ]
989
  elif dev_type == constants.LD_LV:
990
    vg_name, lv_name = logical_id
991
    data = ["%s/%s" % (vg_name, lv_name)]
992
  else:
993
    data = [str(logical_id)]
994

    
995
  return data
996

    
997

    
998
def _FormatBlockDevInfo(idx, top_level, dev, static):
999
  """Show block device information.
1000

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

    
1004
  @type idx: int
1005
  @param idx: the index of the current disk
1006
  @type top_level: boolean
1007
  @param top_level: if this a top-level disk?
1008
  @type dev: dict
1009
  @param dev: dictionary with disk information
1010
  @type static: boolean
1011
  @param static: wheter the device information doesn't contain
1012
      runtime information but only static data
1013
  @return: a list of either strings, tuples or lists
1014
      (which should be formatted at a higher indent level)
1015

    
1016
  """
1017
  def helper(dtype, status):
1018
    """Format one line for physical device status.
1019

    
1020
    @type dtype: str
1021
    @param dtype: a constant from the L{constants.LDS_BLOCK} set
1022
    @type status: tuple
1023
    @param status: a tuple as returned from L{backend.FindBlockDevice}
1024
    @return: the string representing the status
1025

    
1026
    """
1027
    if not status:
1028
      return "not active"
1029
    txt = ""
1030
    (path, major, minor, syncp, estt, degr, ldisk_status) = status
1031
    if major is None:
1032
      major_string = "N/A"
1033
    else:
1034
      major_string = str(major)
1035

    
1036
    if minor is None:
1037
      minor_string = "N/A"
1038
    else:
1039
      minor_string = str(minor)
1040

    
1041
    txt += ("%s (%s:%s)" % (path, major_string, minor_string))
1042
    if dtype in (constants.LD_DRBD8, ):
1043
      if syncp is not None:
1044
        sync_text = "*RECOVERING* %5.2f%%," % syncp
1045
        if estt:
1046
          sync_text += " ETA %ds" % estt
1047
        else:
1048
          sync_text += " ETA unknown"
1049
      else:
1050
        sync_text = "in sync"
1051
      if degr:
1052
        degr_text = "*DEGRADED*"
1053
      else:
1054
        degr_text = "ok"
1055
      if ldisk_status == constants.LDS_FAULTY:
1056
        ldisk_text = " *MISSING DISK*"
1057
      elif ldisk_status == constants.LDS_UNKNOWN:
1058
        ldisk_text = " *UNCERTAIN STATE*"
1059
      else:
1060
        ldisk_text = ""
1061
      txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
1062
    elif dtype == constants.LD_LV:
1063
      if ldisk_status == constants.LDS_FAULTY:
1064
        ldisk_text = " *FAILED* (failed drive?)"
1065
      else:
1066
        ldisk_text = ""
1067
      txt += ldisk_text
1068
    return txt
1069

    
1070
  # the header
1071
  if top_level:
1072
    if dev["iv_name"] is not None:
1073
      txt = dev["iv_name"]
1074
    else:
1075
      txt = "disk %d" % idx
1076
  else:
1077
    txt = "child %d" % idx
1078
  if isinstance(dev["size"], int):
1079
    nice_size = utils.FormatUnit(dev["size"], "h")
1080
  else:
1081
    nice_size = dev["size"]
1082
  d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)]
1083
  data = []
1084
  if top_level:
1085
    data.append(("access mode", dev["mode"]))
1086
  if dev["logical_id"] is not None:
1087
    try:
1088
      l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"])
1089
    except ValueError:
1090
      l_id = [str(dev["logical_id"])]
1091
    if len(l_id) == 1:
1092
      data.append(("logical_id", l_id[0]))
1093
    else:
1094
      data.extend(l_id)
1095
  elif dev["physical_id"] is not None:
1096
    data.append("physical_id:")
1097
    data.append([dev["physical_id"]])
1098
  if not static:
1099
    data.append(("on primary", helper(dev["dev_type"], dev["pstatus"])))
1100
  if dev["sstatus"] and not static:
1101
    data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"])))
1102

    
1103
  if dev["children"]:
1104
    data.append("child devices:")
1105
    for c_idx, child in enumerate(dev["children"]):
1106
      data.append(_FormatBlockDevInfo(c_idx, False, child, static))
1107
  d1.append(data)
1108
  return d1
1109

    
1110

    
1111
def _FormatList(buf, data, indent_level):
1112
  """Formats a list of data at a given indent level.
1113

    
1114
  If the element of the list is:
1115
    - a string, it is simply formatted as is
1116
    - a tuple, it will be split into key, value and the all the
1117
      values in a list will be aligned all at the same start column
1118
    - a list, will be recursively formatted
1119

    
1120
  @type buf: StringIO
1121
  @param buf: the buffer into which we write the output
1122
  @param data: the list to format
1123
  @type indent_level: int
1124
  @param indent_level: the indent level to format at
1125

    
1126
  """
1127
  max_tlen = max([len(elem[0]) for elem in data
1128
                 if isinstance(elem, tuple)] or [0])
1129
  for elem in data:
1130
    if isinstance(elem, basestring):
1131
      buf.write("%*s%s\n" % (2*indent_level, "", elem))
1132
    elif isinstance(elem, tuple):
1133
      key, value = elem
1134
      spacer = "%*s" % (max_tlen - len(key), "")
1135
      buf.write("%*s%s:%s %s\n" % (2*indent_level, "", key, spacer, value))
1136
    elif isinstance(elem, list):
1137
      _FormatList(buf, elem, indent_level+1)
1138

    
1139

    
1140
def ShowInstanceConfig(opts, args):
1141
  """Compute instance run-time status.
1142

    
1143
  @param opts: the command line options selected by the user
1144
  @type args: list
1145
  @param args: either an empty list, and then we query all
1146
      instances, or should contain a list of instance names
1147
  @rtype: int
1148
  @return: the desired exit code
1149

    
1150
  """
1151
  if not args and not opts.show_all:
1152
    ToStderr("No instance selected."
1153
             " Please pass in --all if you want to query all instances.\n"
1154
             "Note that this can take a long time on a big cluster.")
1155
    return 1
1156
  elif args and opts.show_all:
1157
    ToStderr("Cannot use --all if you specify instance names.")
1158
    return 1
1159

    
1160
  retcode = 0
1161
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
1162
  result = SubmitOpCode(op)
1163
  if not result:
1164
    ToStdout("No instances.")
1165
    return 1
1166

    
1167
  buf = StringIO()
1168
  retcode = 0
1169
  for instance_name in result:
1170
    instance = result[instance_name]
1171
    buf.write("Instance name: %s\n" % instance["name"])
1172
    buf.write("State: configured to be %s" % instance["config_state"])
1173
    if not opts.static:
1174
      buf.write(", actual state is %s" % instance["run_state"])
1175
    buf.write("\n")
1176
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
1177
    ##          instance["auto_balance"])
1178
    buf.write("  Nodes:\n")
1179
    buf.write("    - primary: %s\n" % instance["pnode"])
1180
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
1181
    buf.write("  Operating system: %s\n" % instance["os"])
1182
    if instance.has_key("network_port"):
1183
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
1184
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
1185

    
1186
    # custom VNC console information
1187
    vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS,
1188
                                                 None)
1189
    if vnc_bind_address:
1190
      port = instance["network_port"]
1191
      display = int(port) - constants.VNC_BASE_PORT
1192
      if display > 0 and vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
1193
        vnc_console_port = "%s:%s (display %s)" % (instance["pnode"],
1194
                                                   port,
1195
                                                   display)
1196
      elif display > 0 and utils.IsValidIP(vnc_bind_address):
1197
        vnc_console_port = ("%s:%s (node %s) (display %s)" %
1198
                             (vnc_bind_address, port,
1199
                              instance["pnode"], display))
1200
      else:
1201
        # vnc bind address is a file
1202
        vnc_console_port = "%s:%s" % (instance["pnode"],
1203
                                      vnc_bind_address)
1204
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
1205

    
1206
    for key in instance["hv_actual"]:
1207
      if key in instance["hv_instance"]:
1208
        val = instance["hv_instance"][key]
1209
      else:
1210
        val = "default (%s)" % instance["hv_actual"][key]
1211
      buf.write("    - %s: %s\n" % (key, val))
1212
    buf.write("  Hardware:\n")
1213
    buf.write("    - VCPUs: %d\n" %
1214
              instance["be_actual"][constants.BE_VCPUS])
1215
    buf.write("    - memory: %dMiB\n" %
1216
              instance["be_actual"][constants.BE_MEMORY])
1217
    buf.write("    - NICs:\n")
1218
    for idx, (mac, ip, mode, link) in enumerate(instance["nics"]):
1219
      buf.write("      - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" %
1220
                (idx, mac, ip, mode, link))
1221
    buf.write("  Disks:\n")
1222

    
1223
    for idx, device in enumerate(instance["disks"]):
1224
      _FormatList(buf, _FormatBlockDevInfo(idx, True, device, opts.static), 2)
1225

    
1226
  ToStdout(buf.getvalue().rstrip('\n'))
1227
  return retcode
1228

    
1229

    
1230
def SetInstanceParams(opts, args):
1231
  """Modifies an instance.
1232

    
1233
  All parameters take effect only at the next restart of the instance.
1234

    
1235
  @param opts: the command line options selected by the user
1236
  @type args: list
1237
  @param args: should contain only one element, the instance name
1238
  @rtype: int
1239
  @return: the desired exit code
1240

    
1241
  """
1242
  if not (opts.nics or opts.disks or
1243
          opts.hypervisor or opts.beparams):
1244
    ToStderr("Please give at least one of the parameters.")
1245
    return 1
1246

    
1247
  for param in opts.beparams:
1248
    if isinstance(opts.beparams[param], basestring):
1249
      if opts.beparams[param].lower() == "default":
1250
        opts.beparams[param] = constants.VALUE_DEFAULT
1251

    
1252
  utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES,
1253
                      allowed_values=[constants.VALUE_DEFAULT])
1254

    
1255
  for param in opts.hypervisor:
1256
    if isinstance(opts.hypervisor[param], basestring):
1257
      if opts.hypervisor[param].lower() == "default":
1258
        opts.hypervisor[param] = constants.VALUE_DEFAULT
1259

    
1260
  utils.ForceDictType(opts.hypervisor, constants.HVS_PARAMETER_TYPES,
1261
                      allowed_values=[constants.VALUE_DEFAULT])
1262

    
1263
  for idx, (nic_op, nic_dict) in enumerate(opts.nics):
1264
    try:
1265
      nic_op = int(nic_op)
1266
      opts.nics[idx] = (nic_op, nic_dict)
1267
    except ValueError:
1268
      pass
1269

    
1270
  for idx, (disk_op, disk_dict) in enumerate(opts.disks):
1271
    try:
1272
      disk_op = int(disk_op)
1273
      opts.disks[idx] = (disk_op, disk_dict)
1274
    except ValueError:
1275
      pass
1276
    if disk_op == constants.DDM_ADD:
1277
      if 'size' not in disk_dict:
1278
        raise errors.OpPrereqError("Missing required parameter 'size'")
1279
      disk_dict['size'] = utils.ParseUnit(disk_dict['size'])
1280

    
1281
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
1282
                                   nics=opts.nics,
1283
                                   disks=opts.disks,
1284
                                   hvparams=opts.hypervisor,
1285
                                   beparams=opts.beparams,
1286
                                   force=opts.force)
1287

    
1288
  # even if here we process the result, we allow submit only
1289
  result = SubmitOrSend(op, opts)
1290

    
1291
  if result:
1292
    ToStdout("Modified instance %s", args[0])
1293
    for param, data in result:
1294
      ToStdout(" - %-5s -> %s", param, data)
1295
    ToStdout("Please don't forget that these parameters take effect"
1296
             " only at the next start of the instance.")
1297
  return 0
1298

    
1299

    
1300
# options used in more than one cmd
1301
node_opt = make_option("-n", "--node", dest="node", help="Target node",
1302
                       metavar="<node>")
1303

    
1304
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
1305
                    metavar="<os>")
1306

    
1307
# multi-instance selection options
1308
m_force_multi = make_option("--force-multiple", dest="force_multi",
1309
                            help="Do not ask for confirmation when more than"
1310
                            " one instance is affected",
1311
                            action="store_true", default=False)
1312

    
1313
m_pri_node_opt = make_option("--primary", dest="multi_mode",
1314
                             help="Filter by nodes (primary only)",
1315
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
1316

    
1317
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
1318
                             help="Filter by nodes (secondary only)",
1319
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
1320

    
1321
m_node_opt = make_option("--node", dest="multi_mode",
1322
                         help="Filter by nodes (primary and secondary)",
1323
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
1324

    
1325
m_clust_opt = make_option("--all", dest="multi_mode",
1326
                          help="Select all instances in the cluster",
1327
                          const=_SHUTDOWN_CLUSTER, action="store_const")
1328

    
1329
m_inst_opt = make_option("--instance", dest="multi_mode",
1330
                         help="Filter by instance name [default]",
1331
                         const=_SHUTDOWN_INSTANCES, action="store_const")
1332

    
1333

    
1334
# this is defined separately due to readability only
1335
add_opts = [
1336
  DEBUG_OPT,
1337
  make_option("-n", "--node", dest="node",
1338
              help="Target node and optional secondary node",
1339
              metavar="<pnode>[:<snode>]"),
1340
  os_opt,
1341
  keyval_option("-B", "--backend", dest="beparams",
1342
                type="keyval", default={},
1343
                help="Backend parameters"),
1344
  make_option("-t", "--disk-template", dest="disk_template",
1345
              help="Custom disk setup (diskless, file, plain or drbd)",
1346
              default=None, metavar="TEMPL"),
1347
  cli_option("-s", "--os-size", dest="sd_size", help="Disk size for a"
1348
             " single-disk configuration, when not using the --disk option,"
1349
             " in MiB unless a suffix is used",
1350
             default=None, type="unit", metavar="<size>"),
1351
  ikv_option("--disk", help="Disk information",
1352
             default=[], dest="disks",
1353
             action="append",
1354
             type="identkeyval"),
1355
  ikv_option("--net", help="NIC information",
1356
             default=[], dest="nics",
1357
             action="append",
1358
             type="identkeyval"),
1359
  make_option("--no-nics", default=False, action="store_true",
1360
              help="Do not create any network cards for the instance"),
1361
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
1362
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
1363
  make_option("--no-start", dest="start", default=True,
1364
              action="store_false", help="Don't start the instance after"
1365
              " creation"),
1366
  make_option("--no-ip-check", dest="ip_check", default=True,
1367
              action="store_false", help="Don't check that the instance's IP"
1368
              " is alive (only valid with --no-start)"),
1369
  make_option("--file-storage-dir", dest="file_storage_dir",
1370
              help="Relative path under default cluster-wide file storage dir"
1371
              " to store file-based disks", default=None,
1372
              metavar="<DIR>"),
1373
  make_option("--file-driver", dest="file_driver", help="Driver to use"
1374
              " for image files", default="loop", metavar="<DRIVER>"),
1375
  make_option("-I", "--iallocator", metavar="<NAME>",
1376
              help="Select nodes for the instance automatically using the"
1377
              " <NAME> iallocator plugin", default=None, type="string"),
1378
  ikv_option("-H", "--hypervisor", dest="hypervisor",
1379
              help="Hypervisor and hypervisor options, in the format"
1380
              " hypervisor:option=value,option=value,...", default=None,
1381
              type="identkeyval"),
1382
  SUBMIT_OPT,
1383
  ]
1384

    
1385
commands = {
1386
  'add': (AddInstance, ARGS_ONE, add_opts,
1387
          "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1388
          "Creates and adds a new instance to the cluster"),
1389
  'batch-create': (BatchCreate, ARGS_ONE,
1390
                   [DEBUG_OPT],
1391
                   "<instances_file.json>",
1392
                   "Create a bunch of instances based on specs in the file."),
1393
  'console': (ConnectToInstanceConsole, ARGS_ONE,
1394
              [DEBUG_OPT,
1395
               make_option("--show-cmd", dest="show_command",
1396
                           action="store_true", default=False,
1397
                           help=("Show command instead of executing it"))],
1398
              "[--show-cmd] <instance>",
1399
              "Opens a console on the specified instance"),
1400
  'failover': (FailoverInstance, ARGS_ONE,
1401
               [DEBUG_OPT, FORCE_OPT,
1402
                make_option("--ignore-consistency", dest="ignore_consistency",
1403
                            action="store_true", default=False,
1404
                            help="Ignore the consistency of the disks on"
1405
                            " the secondary"),
1406
                SUBMIT_OPT,
1407
                ],
1408
               "[-f] <instance>",
1409
               "Stops the instance and starts it on the backup node, using"
1410
               " the remote mirror (only for instances of type drbd)"),
1411
  'migrate': (MigrateInstance, ARGS_ONE,
1412
               [DEBUG_OPT, FORCE_OPT,
1413
                make_option("--non-live", dest="live",
1414
                            default=True, action="store_false",
1415
                            help="Do a non-live migration (this usually means"
1416
                            " freeze the instance, save the state,"
1417
                            " transfer and only then resume running on the"
1418
                            " secondary node)"),
1419
                make_option("--cleanup", dest="cleanup",
1420
                            default=False, action="store_true",
1421
                            help="Instead of performing the migration, try to"
1422
                            " recover from a failed cleanup. This is safe"
1423
                            " to run even if the instance is healthy, but it"
1424
                            " will create extra replication traffic and "
1425
                            " disrupt briefly the replication (like during the"
1426
                            " migration"),
1427
                ],
1428
               "[-f] <instance>",
1429
               "Migrate instance to its secondary node"
1430
               " (only for instances of type drbd)"),
1431
  'info': (ShowInstanceConfig, ARGS_ANY,
1432
           [DEBUG_OPT,
1433
            make_option("-s", "--static", dest="static",
1434
                        action="store_true", default=False,
1435
                        help="Only show configuration data, not runtime data"),
1436
            make_option("--all", dest="show_all",
1437
                        default=False, action="store_true",
1438
                        help="Show info on all instances on the cluster."
1439
                        " This can take a long time to run, use wisely."),
1440
            ], "[-s] {--all | <instance>...}",
1441
           "Show information on the specified instance(s)"),
1442
  'list': (ListInstances, ARGS_ANY,
1443
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT],
1444
           "[<instance>...]",
1445
           "Lists the instances and their status. The available fields are"
1446
           " (see the man page for details): status, oper_state, oper_ram,"
1447
           " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1448
           " ip, mac, mode, link, sda_size, sdb_size, vcpus, serial_no,"
1449
           " hypervisor."
1450
           " The default field"
1451
           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1452
           ),
1453
  'reinstall': (ReinstallInstance, ARGS_ANY,
1454
                [DEBUG_OPT, FORCE_OPT, os_opt,
1455
                 m_force_multi,
1456
                 m_node_opt, m_pri_node_opt, m_sec_node_opt,
1457
                 m_clust_opt, m_inst_opt,
1458
                 make_option("--select-os", dest="select_os",
1459
                             action="store_true", default=False,
1460
                             help="Interactive OS reinstall, lists available"
1461
                             " OS templates for selection"),
1462
                 SUBMIT_OPT,
1463
                 ],
1464
                "[-f] <instance>", "Reinstall a stopped instance"),
1465
  'remove': (RemoveInstance, ARGS_ONE,
1466
             [DEBUG_OPT, FORCE_OPT,
1467
              make_option("--ignore-failures", dest="ignore_failures",
1468
                          action="store_true", default=False,
1469
                          help=("Remove the instance from the cluster even"
1470
                                " if there are failures during the removal"
1471
                                " process (shutdown, disk removal, etc.)")),
1472
              SUBMIT_OPT,
1473
              ],
1474
             "[-f] <instance>", "Shuts down the instance and removes it"),
1475
  'rename': (RenameInstance, ARGS_FIXED(2),
1476
             [DEBUG_OPT,
1477
              make_option("--no-ip-check", dest="ignore_ip",
1478
                          help="Do not check that the IP of the new name"
1479
                          " is alive",
1480
                          default=False, action="store_true"),
1481
              SUBMIT_OPT,
1482
              ],
1483
             "<instance> <new_name>", "Rename the instance"),
1484
  'replace-disks': (ReplaceDisks, ARGS_ONE,
1485
                    [DEBUG_OPT,
1486
                     make_option("-n", "--new-secondary", dest="new_secondary",
1487
                                 help=("New secondary node (for secondary"
1488
                                       " node change)"), metavar="NODE",
1489
                                 default=None),
1490
                     make_option("-p", "--on-primary", dest="on_primary",
1491
                                 default=False, action="store_true",
1492
                                 help=("Replace the disk(s) on the primary"
1493
                                       " node (only for the drbd template)")),
1494
                     make_option("-s", "--on-secondary", dest="on_secondary",
1495
                                 default=False, action="store_true",
1496
                                 help=("Replace the disk(s) on the secondary"
1497
                                       " node (only for the drbd template)")),
1498
                     make_option("-a", "--auto", dest="auto",
1499
                                 default=False, action="store_true",
1500
                                 help=("Automatically replace faulty disks"
1501
                                       " (only for the drbd template)")),
1502
                     make_option("--disks", dest="disks", default=None,
1503
                                 help="Comma-separated list of disks"
1504
                                 " indices to replace (e.g. 0,2) (optional,"
1505
                                 " defaults to all disks)"),
1506
                     make_option("-I", "--iallocator", metavar="<NAME>",
1507
                                 help="Select new secondary for the instance"
1508
                                 " automatically using the"
1509
                                 " <NAME> iallocator plugin (enables"
1510
                                 " secondary node replacement)",
1511
                                 default=None, type="string"),
1512
                     SUBMIT_OPT,
1513
                     ],
1514
                    "[-s|-p|-n NODE|-I NAME] <instance>",
1515
                    "Replaces all disks for the instance"),
1516
  'modify': (SetInstanceParams, ARGS_ONE,
1517
             [DEBUG_OPT, FORCE_OPT,
1518
              keyval_option("-H", "--hypervisor", type="keyval",
1519
                            default={}, dest="hypervisor",
1520
                            help="Change hypervisor parameters"),
1521
              keyval_option("-B", "--backend", type="keyval",
1522
                            default={}, dest="beparams",
1523
                            help="Change backend parameters"),
1524
              ikv_option("--disk", help="Disk changes",
1525
                         default=[], dest="disks",
1526
                         action="append",
1527
                         type="identkeyval"),
1528
              ikv_option("--net", help="NIC changes",
1529
                         default=[], dest="nics",
1530
                         action="append",
1531
                         type="identkeyval"),
1532
              SUBMIT_OPT,
1533
              ],
1534
             "<instance>", "Alters the parameters of an instance"),
1535
  'shutdown': (ShutdownInstance, ARGS_ANY,
1536
               [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1537
                m_clust_opt, m_inst_opt, m_force_multi,
1538
                SUBMIT_OPT,
1539
                ],
1540
               "<instance>", "Stops an instance"),
1541
  'startup': (StartupInstance, ARGS_ANY,
1542
              [DEBUG_OPT, FORCE_OPT, m_force_multi,
1543
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1544
               m_clust_opt, m_inst_opt,
1545
               SUBMIT_OPT,
1546
               keyval_option("-H", "--hypervisor", type="keyval",
1547
                             default={}, dest="hvparams",
1548
                             help="Temporary hypervisor parameters"),
1549
               keyval_option("-B", "--backend", type="keyval",
1550
                             default={}, dest="beparams",
1551
                             help="Temporary backend parameters"),
1552
               ],
1553
              "<instance>", "Starts an instance"),
1554

    
1555
  'reboot': (RebootInstance, ARGS_ANY,
1556
              [DEBUG_OPT, m_force_multi,
1557
               make_option("-t", "--type", dest="reboot_type",
1558
                           help="Type of reboot: soft/hard/full",
1559
                           default=constants.INSTANCE_REBOOT_HARD,
1560
                           type="string", metavar="<REBOOT>"),
1561
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1562
                           default=False, action="store_true",
1563
                           help="Ignore errors from secondaries"),
1564
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1565
               m_clust_opt, m_inst_opt,
1566
               SUBMIT_OPT,
1567
               ],
1568
            "<instance>", "Reboots an instance"),
1569
  'activate-disks': (ActivateDisks, ARGS_ONE,
1570
                     [DEBUG_OPT, SUBMIT_OPT,
1571
                      make_option("--ignore-size", dest="ignore_size",
1572
                                  default=False, action="store_true",
1573
                                  help="Ignore current recorded size"
1574
                                  " (useful for forcing activation when"
1575
                                  " the recorded size is wrong)"),
1576
                      ],
1577
                     "<instance>",
1578
                     "Activate an instance's disks"),
1579
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1580
                       "<instance>",
1581
                       "Deactivate an instance's disks"),
1582
  'recreate-disks': (RecreateDisks, ARGS_ONE,
1583
                     [DEBUG_OPT, SUBMIT_OPT,
1584
                     make_option("--disks", dest="disks", default=None,
1585
                                 help="Comma-separated list of disks"
1586
                                 " indices to replace (e.g. 0,2) (optional,"
1587
                                 " defaults to all disks)"),
1588
                      ],
1589
                     "<instance>",
1590
                     "Recreate an instance's disks"),
1591
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1592
                [DEBUG_OPT, SUBMIT_OPT,
1593
                 make_option("--no-wait-for-sync",
1594
                             dest="wait_for_sync", default=True,
1595
                             action="store_false",
1596
                             help="Don't wait for sync (DANGEROUS!)"),
1597
                 ],
1598
                "<instance> <disk> <size>", "Grow an instance's disk"),
1599
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1600
                "<instance_name>", "List the tags of the given instance"),
1601
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1602
               "<instance_name> tag...", "Add tags to the given instance"),
1603
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1604
                  "<instance_name> tag...", "Remove tags from given instance"),
1605
  }
1606

    
1607
#: dictionary with aliases for commands
1608
aliases = {
1609
  'activate_block_devs': 'activate-disks',
1610
  'replace_disks': 'replace-disks',
1611
  'start': 'startup',
1612
  'stop': 'shutdown',
1613
  }
1614

    
1615
if __name__ == '__main__':
1616
  sys.exit(GenericMain(commands, aliases=aliases,
1617
                       override={"tag_type": constants.TAG_INSTANCE}))