Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 61be6ba4

History | View | Annotate | Download (43.1 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
import sys
23
import os
24
import itertools
25
import simplejson
26
from optparse import make_option
27
from cStringIO import StringIO
28

    
29
from ganeti.cli import *
30
from ganeti import cli
31
from ganeti import opcodes
32
from ganeti import logger
33
from ganeti import constants
34
from ganeti import utils
35
from ganeti import errors
36

    
37

    
38
_SHUTDOWN_CLUSTER = "cluster"
39
_SHUTDOWN_NODES_BOTH = "nodes"
40
_SHUTDOWN_NODES_PRI = "nodes-pri"
41
_SHUTDOWN_NODES_SEC = "nodes-sec"
42
_SHUTDOWN_INSTANCES = "instances"
43

    
44

    
45
_VALUE_TRUE = "true"
46

    
47
_LIST_DEF_FIELDS = [
48
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
49
  ]
50

    
51

    
52
def _ExpandMultiNames(mode, names):
53
  """Expand the given names using the passed mode.
54

    
55
  Args:
56
    - mode, which can be one of _SHUTDOWN_CLUSTER, _SHUTDOWN_NODES_BOTH,
57
      _SHUTDOWN_NODES_PRI, _SHUTDOWN_NODES_SEC or _SHUTDOWN_INSTANCES
58
    - names, which is a list of names; for cluster, it must be empty,
59
      and for node and instance it must be a list of valid item
60
      names (short names are valid as usual, e.g. node1 instead of
61
      node1.example.com)
62

    
63
  For _SHUTDOWN_CLUSTER, all instances will be returned. For
64
  _SHUTDOWN_NODES_PRI/SEC, all instances having those nodes as
65
  primary/secondary will be shutdown. For _SHUTDOWN_NODES_BOTH, all
66
  instances having those nodes as either primary or secondary will be
67
  returned. For _SHUTDOWN_INSTANCES, the given instances will be
68
  returned.
69

    
70
  """
71
  if mode == _SHUTDOWN_CLUSTER:
72
    if names:
73
      raise errors.OpPrereqError("Cluster filter mode takes no arguments")
74
    client = GetClient()
75
    idata = client.QueryInstances([], ["name"])
76
    inames = [row[0] for row in idata]
77

    
78
  elif mode in (_SHUTDOWN_NODES_BOTH,
79
                _SHUTDOWN_NODES_PRI,
80
                _SHUTDOWN_NODES_SEC):
81
    if not names:
82
      raise errors.OpPrereqError("No node names passed")
83
    client = GetClient()
84
    ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"])
85
    ipri = [row[1] for row in ndata]
86
    pri_names = list(itertools.chain(*ipri))
87
    isec = [row[2] for row in ndata]
88
    sec_names = list(itertools.chain(*isec))
89
    if mode == _SHUTDOWN_NODES_BOTH:
90
      inames = pri_names + sec_names
91
    elif mode == _SHUTDOWN_NODES_PRI:
92
      inames = pri_names
93
    elif mode == _SHUTDOWN_NODES_SEC:
94
      inames = sec_names
95
    else:
96
      raise errors.ProgrammerError("Unhandled shutdown type")
97

    
98
  elif mode == _SHUTDOWN_INSTANCES:
99
    if not names:
100
      raise errors.OpPrereqError("No instance names passed")
101
    client = GetClient()
102
    idata = client.QueryInstances(names, ["name"])
103
    inames = [row[0] for row in idata]
104

    
105
  else:
106
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
107

    
108
  return inames
109

    
110

    
111
def _ConfirmOperation(inames, text):
112
  """Ask the user to confirm an operation on a list of instances.
113

    
114
  This function is used to request confirmation for doing an operation
115
  on a given list of instances.
116

    
117
  The inames argument is what the selection algorithm computed, and
118
  the text argument is the operation we should tell the user to
119
  confirm (e.g. 'shutdown' or 'startup').
120

    
121
  Returns: boolean depending on user's confirmation.
122

    
123
  """
124
  count = len(inames)
125
  msg = ("The %s will operate on %d instances.\n"
126
         "Do you want to continue?" % (text, count))
127
  affected = ("\nAffected instances:\n" +
128
              "\n".join(["  %s" % name for name in inames]))
129

    
130
  choices = [('y', True, 'Yes, execute the %s' % text),
131
             ('n', False, 'No, abort the %s' % text)]
132

    
133
  if count > 20:
134
    choices.insert(1, ('v', 'v', 'View the list of affected instances'))
135
    ask = msg
136
  else:
137
    ask = msg + affected
138

    
139
  choice = AskUser(ask, choices)
140
  if choice == 'v':
141
    choices.pop(1)
142
    choice = AskUser(msg + affected, choices)
143
  return choice
144

    
145

    
146
def _TransformPath(user_input):
147
  """Transform a user path into a canonical value.
148

    
149
  This function transforms the a path passed as textual information
150
  into the constants that the LU code expects.
151

    
152
  """
153
  if user_input:
154
    if user_input.lower() == "default":
155
      result_path = constants.VALUE_DEFAULT
156
    elif user_input.lower() == "none":
157
      result_path = constants.VALUE_NONE
158
    else:
159
      if not os.path.isabs(user_input):
160
        raise errors.OpPrereqError("Path '%s' is not an absolute filename" %
161
                                   user_input)
162
      result_path = user_input
163
  else:
164
    result_path = constants.VALUE_DEFAULT
165

    
166
  return result_path
167

    
168

    
169
def ListInstances(opts, args):
170
  """List instances and their properties.
171

    
172
  """
173
  if opts.output is None:
174
    selected_fields = _LIST_DEF_FIELDS
175
  elif opts.output.startswith("+"):
176
    selected_fields = _LIST_DEF_FIELDS + opts.output[1:].split(",")
177
  else:
178
    selected_fields = opts.output.split(",")
179

    
180
  output = GetClient().QueryInstances([], selected_fields)
181

    
182
  if not opts.no_headers:
183
    headers = {
184
      "name": "Instance", "os": "OS", "pnode": "Primary_node",
185
      "snodes": "Secondary_Nodes", "admin_state": "Autostart",
186
      "oper_state": "Running",
187
      "oper_ram": "Memory", "disk_template": "Disk_template",
188
      "ip": "IP_address", "mac": "MAC_address",
189
      "bridge": "Bridge",
190
      "sda_size": "Disk/0", "sdb_size": "Disk/1",
191
      "status": "Status", "tags": "Tags",
192
      "network_port": "Network_port",
193
      "hv/kernel_path": "Kernel_path",
194
      "hv/initrd_path": "Initrd_path",
195
      "hv/boot_order": "HVM_boot_order",
196
      "hv/acpi": "HVM_ACPI",
197
      "hv/pae": "HVM_PAE",
198
      "hv/cdrom_image_path": "HVM_CDROM_image_path",
199
      "hv/nic_type": "HVM_NIC_type",
200
      "hv/disk_type": "HVM_Disk_type",
201
      "hv/vnc_bind_address": "VNC_bind_address",
202
      "serial_no": "SerialNo", "hypervisor": "Hypervisor",
203
      "hvparams": "Hypervisor_parameters",
204
      "be/memory": "Configured_memory",
205
      "be/vcpus": "VCPUs",
206
      "be/auto_balance": "Auto_balance",
207
      }
208
  else:
209
    headers = None
210

    
211
  if opts.human_readable:
212
    unitfields = ["be/memory", "oper_ram", "sda_size", "sdb_size"]
213
  else:
214
    unitfields = None
215

    
216
  numfields = ["be/memory", "oper_ram", "sda_size", "sdb_size", "be/vcpus",
217
               "serial_no"]
218

    
219
  list_type_fields = ("tags",)
220
  # change raw values to nicer strings
221
  for row in output:
222
    for idx, field in enumerate(selected_fields):
223
      val = row[idx]
224
      if field == "snodes":
225
        val = ",".join(val) or "-"
226
      elif field == "admin_state":
227
        if val:
228
          val = "yes"
229
        else:
230
          val = "no"
231
      elif field == "oper_state":
232
        if val is None:
233
          val = "(node down)"
234
        elif val: # True
235
          val = "running"
236
        else:
237
          val = "stopped"
238
      elif field == "oper_ram":
239
        if val is None:
240
          val = "(node down)"
241
      elif field == "sda_size" or field == "sdb_size":
242
        if val is None:
243
          val = "N/A"
244
      elif field in list_type_fields:
245
        val = ",".join(val)
246
      elif val is None:
247
        val = "-"
248
      row[idx] = str(val)
249

    
250
  data = GenerateTable(separator=opts.separator, headers=headers,
251
                       fields=selected_fields, unitfields=unitfields,
252
                       numfields=numfields, data=output)
253

    
254
  for line in data:
255
    logger.ToStdout(line)
256

    
257
  return 0
258

    
259

    
260
def AddInstance(opts, args):
261
  """Add an instance to the cluster.
262

    
263
  Args:
264
    opts - class with options as members
265
    args - list with a single element, the instance name
266
  Opts used:
267
    mem - amount of memory to allocate to instance (MiB)
268
    size - amount of disk space to allocate to instance (MiB)
269
    os - which OS to run on instance
270
    node - node to run new instance on
271

    
272
  """
273
  instance = args[0]
274

    
275
  (pnode, snode) = SplitNodeOption(opts.node)
276

    
277
  hypervisor = None
278
  hvparams = {}
279
  if opts.hypervisor:
280
    hypervisor, hvparams = opts.hypervisor
281

    
282
  ValidateBeParams(opts.beparams)
283

    
284
##  kernel_path = _TransformPath(opts.kernel_path)
285
##  initrd_path = _TransformPath(opts.initrd_path)
286

    
287
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
288
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
289

    
290
##  if ((opts.hvm_cdrom_image_path is not None) and
291
##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
292
##    hvm_cdrom_image_path = None
293
##  else:
294
##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
295

    
296
  op = opcodes.OpCreateInstance(instance_name=instance,
297
                                disk_size=opts.size, swap_size=opts.swap,
298
                                disk_template=opts.disk_template,
299
                                mode=constants.INSTANCE_CREATE,
300
                                os_type=opts.os, pnode=pnode,
301
                                snode=snode,
302
                                ip=opts.ip, bridge=opts.bridge,
303
                                start=opts.start, ip_check=opts.ip_check,
304
                                wait_for_sync=opts.wait_for_sync,
305
                                mac=opts.mac,
306
                                hypervisor=hypervisor,
307
                                hvparams=hvparams,
308
                                beparams=opts.beparams,
309
                                iallocator=opts.iallocator,
310
                                file_storage_dir=opts.file_storage_dir,
311
                                file_driver=opts.file_driver,
312
                                )
313

    
314
  SubmitOrSend(op, opts)
315
  return 0
316

    
317

    
318
def BatchCreate(opts, args):
319
  """Create instances on a batched base.
320

    
321
  This function reads a json with instances defined in the form:
322

    
323
  {"instance-name": {"disk_size": 25,
324
                     "swap_size": 1024,
325
                     "template": "drbd",
326
                     "backend": { "memory": 512,
327
                                  "vcpus": 1 },
328
                     "os": "etch-image",
329
                     "primary_node": "firstnode",
330
                     "secondary_node": "secondnode",
331
                     "iallocator": "dumb"}}
332

    
333
  primary_node and secondary_node has precedence over iallocator.
334

    
335
  Args:
336
    opts: The parsed command line options
337
    args: Argument passed to the command in our case the json file
338

    
339
  """
340
  _DEFAULT_SPECS = {"disk_size": 20 * 1024,
341
                    "swap_size": 4 * 1024,
342
                    "backend": {},
343
                    "iallocator": None,
344
                    "primary_node": None,
345
                    "secondary_node": None,
346
                    "ip": 'none',
347
                    "mac": 'auto',
348
                    "bridge": None,
349
                    "start": True,
350
                    "ip_check": True,
351
                    "hypervisor": None,
352
                    "file_storage_dir": None,
353
                    "file_driver": 'loop'}
354

    
355
  def _PopulateWithDefaults(spec):
356
    """Returns a new hash combined with default values."""
357
    dict = _DEFAULT_SPECS.copy()
358
    dict.update(spec)
359
    return dict
360

    
361
  def _Validate(spec):
362
    """Validate the instance specs."""
363
    # Validate fields required under any circumstances
364
    for required_field in ('os', 'template'):
365
      if required_field not in spec:
366
        raise errors.OpPrereqError('Required field "%s" is missing.' %
367
                                   required_field)
368
    # Validate special fields
369
    if spec['primary_node'] is not None:
370
      if (spec['template'] in constants.DTS_NET_MIRROR and
371
          spec['secondary_node'] is None):
372
        raise errors.OpPrereqError('Template requires secondary node, but'
373
                                   ' there was no secondary provided.')
374
    elif spec['iallocator'] is None:
375
      raise errors.OpPrereqError('You have to provide at least a primary_node'
376
                                 ' or an iallocator.')
377

    
378
    if (spec['hypervisor'] and
379
        not isinstance(spec['hypervisor'], dict)):
380
      raise errors.OpPrereqError('Hypervisor parameters must be a dict.')
381

    
382
  json_filename = args[0]
383
  fd = open(json_filename, 'r')
384
  try:
385
    instance_data = simplejson.load(fd)
386
  finally:
387
    fd.close()
388

    
389
  # Iterate over the instances and do:
390
  #  * Populate the specs with default value
391
  #  * Validate the instance specs
392
  for (name, specs) in instance_data.iteritems():
393
    specs = _PopulateWithDefaults(specs)
394
    _Validate(specs)
395

    
396
    hypervisor = None
397
    hvparams = {}
398
    if specs['hypervisor']:
399
      hypervisor, hvparams = specs['hypervisor'].iteritems()
400

    
401
    op = opcodes.OpCreateInstance(instance_name=name,
402
                                  disk_size=specs['disk_size'],
403
                                  swap_size=specs['swap_size'],
404
                                  disk_template=specs['template'],
405
                                  mode=constants.INSTANCE_CREATE,
406
                                  os_type=specs['os'],
407
                                  pnode=specs['primary_node'],
408
                                  snode=specs['secondary_node'],
409
                                  ip=specs['ip'], bridge=specs['bridge'],
410
                                  start=specs['start'],
411
                                  ip_check=specs['ip_check'],
412
                                  wait_for_sync=True,
413
                                  mac=specs['mac'],
414
                                  iallocator=specs['iallocator'],
415
                                  hypervisor=hypervisor,
416
                                  hvparams=hvparams,
417
                                  beparams=specs['backend'],
418
                                  file_storage_dir=specs['file_storage_dir'],
419
                                  file_driver=specs['file_driver'])
420

    
421
    print '%s: %s' % (name, cli.SendJob([op]))
422

    
423
  return 0
424

    
425

    
426
def ReinstallInstance(opts, args):
427
  """Reinstall an instance.
428

    
429
  Args:
430
    opts - class with options as members
431
    args - list containing a single element, the instance name
432

    
433
  """
434
  instance_name = args[0]
435

    
436
  if opts.select_os is True:
437
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
438
    result = SubmitOpCode(op)
439

    
440
    if not result:
441
      logger.ToStdout("Can't get the OS list")
442
      return 1
443

    
444
    logger.ToStdout("Available OS templates:")
445
    number = 0
446
    choices = []
447
    for entry in result:
448
      logger.ToStdout("%3s: %s" % (number, entry[0]))
449
      choices.append(("%s" % number, entry[0], entry[0]))
450
      number = number + 1
451

    
452
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
453
    selected = AskUser("Enter OS template name or number (or x to abort):",
454
                       choices)
455

    
456
    if selected == 'exit':
457
      logger.ToStdout("User aborted reinstall, exiting")
458
      return 1
459

    
460
    os = selected
461
  else:
462
    os = opts.os
463

    
464
  if not opts.force:
465
    usertext = ("This will reinstall the instance %s and remove"
466
                " all data. Continue?") % instance_name
467
    if not AskUser(usertext):
468
      return 1
469

    
470
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
471
                                   os_type=os)
472
  SubmitOrSend(op, opts)
473

    
474
  return 0
475

    
476

    
477
def RemoveInstance(opts, args):
478
  """Remove an instance.
479

    
480
  Args:
481
    opts - class with options as members
482
    args - list containing a single element, the instance name
483

    
484
  """
485
  instance_name = args[0]
486
  force = opts.force
487

    
488
  if not force:
489
    usertext = ("This will remove the volumes of the instance %s"
490
                " (including mirrors), thus removing all the data"
491
                " of the instance. Continue?") % instance_name
492
    if not AskUser(usertext):
493
      return 1
494

    
495
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
496
                                ignore_failures=opts.ignore_failures)
497
  SubmitOrSend(op, opts)
498
  return 0
499

    
500

    
501
def RenameInstance(opts, args):
502
  """Rename an instance.
503

    
504
  Args:
505
    opts - class with options as members
506
    args - list containing two elements, the instance name and the new name
507

    
508
  """
509
  op = opcodes.OpRenameInstance(instance_name=args[0],
510
                                new_name=args[1],
511
                                ignore_ip=opts.ignore_ip)
512
  SubmitOrSend(op, opts)
513
  return 0
514

    
515

    
516
def ActivateDisks(opts, args):
517
  """Activate an instance's disks.
518

    
519
  This serves two purposes:
520
    - it allows one (as long as the instance is not running) to mount
521
    the disks and modify them from the node
522
    - it repairs inactive secondary drbds
523

    
524
  """
525
  instance_name = args[0]
526
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
527
  disks_info = SubmitOrSend(op, opts)
528
  for host, iname, nname in disks_info:
529
    print "%s:%s:%s" % (host, iname, nname)
530
  return 0
531

    
532

    
533
def DeactivateDisks(opts, args):
534
  """Command-line interface for _ShutdownInstanceBlockDevices.
535

    
536
  This function takes the instance name, looks for its primary node
537
  and the tries to shutdown its block devices on that node.
538

    
539
  """
540
  instance_name = args[0]
541
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
542
  SubmitOrSend(op, opts)
543
  return 0
544

    
545

    
546
def GrowDisk(opts, args):
547
  """Command-line interface for _ShutdownInstanceBlockDevices.
548

    
549
  This function takes the instance name, looks for its primary node
550
  and the tries to shutdown its block devices on that node.
551

    
552
  """
553
  instance = args[0]
554
  disk = args[1]
555
  amount = utils.ParseUnit(args[2])
556
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
557
                          wait_for_sync=opts.wait_for_sync)
558
  SubmitOrSend(op, opts)
559
  return 0
560

    
561

    
562
def StartupInstance(opts, args):
563
  """Startup an instance.
564

    
565
  Args:
566
    opts - class with options as members
567
    args - list containing a single element, the instance name
568

    
569
  """
570
  if opts.multi_mode is None:
571
    opts.multi_mode = _SHUTDOWN_INSTANCES
572
  inames = _ExpandMultiNames(opts.multi_mode, args)
573
  if not inames:
574
    raise errors.OpPrereqError("Selection filter does not match any instances")
575
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
576
  if not (opts.force_multi or not multi_on
577
          or _ConfirmOperation(inames, "startup")):
578
    return 1
579
  for name in inames:
580
    op = opcodes.OpStartupInstance(instance_name=name,
581
                                   force=opts.force,
582
                                   extra_args=opts.extra_args)
583
    if multi_on:
584
      logger.ToStdout("Starting up %s" % name)
585
    try:
586
      SubmitOrSend(op, opts)
587
    except JobSubmittedException, err:
588
      _, txt = FormatError(err)
589
      logger.ToStdout("%s" % txt)
590
  return 0
591

    
592

    
593
def RebootInstance(opts, args):
594
  """Reboot an instance
595

    
596
  Args:
597
    opts - class with options as members
598
    args - list containing a single element, the instance name
599

    
600
  """
601
  if opts.multi_mode is None:
602
    opts.multi_mode = _SHUTDOWN_INSTANCES
603
  inames = _ExpandMultiNames(opts.multi_mode, args)
604
  if not inames:
605
    raise errors.OpPrereqError("Selection filter does not match any instances")
606
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
607
  if not (opts.force_multi or not multi_on
608
          or _ConfirmOperation(inames, "reboot")):
609
    return 1
610
  for name in inames:
611
    op = opcodes.OpRebootInstance(instance_name=name,
612
                                  reboot_type=opts.reboot_type,
613
                                  ignore_secondaries=opts.ignore_secondaries)
614

    
615
    SubmitOrSend(op, opts)
616
  return 0
617

    
618

    
619
def ShutdownInstance(opts, args):
620
  """Shutdown an instance.
621

    
622
  Args:
623
    opts - class with options as members
624
    args - list containing a single element, the instance name
625

    
626
  """
627
  if opts.multi_mode is None:
628
    opts.multi_mode = _SHUTDOWN_INSTANCES
629
  inames = _ExpandMultiNames(opts.multi_mode, args)
630
  if not inames:
631
    raise errors.OpPrereqError("Selection filter does not match any instances")
632
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
633
  if not (opts.force_multi or not multi_on
634
          or _ConfirmOperation(inames, "shutdown")):
635
    return 1
636
  for name in inames:
637
    op = opcodes.OpShutdownInstance(instance_name=name)
638
    if multi_on:
639
      logger.ToStdout("Shutting down %s" % name)
640
    try:
641
      SubmitOrSend(op, opts)
642
    except JobSubmittedException, err:
643
      _, txt = FormatError(err)
644
      logger.ToStdout("%s" % txt)
645
  return 0
646

    
647

    
648
def ReplaceDisks(opts, args):
649
  """Replace the disks of an instance
650

    
651
  Args:
652
    opts - class with options as members
653
    args - list with a single element, the instance name
654

    
655
  """
656
  instance_name = args[0]
657
  new_2ndary = opts.new_secondary
658
  iallocator = opts.iallocator
659
  if opts.disks is None:
660
    disks = ["sda", "sdb"]
661
  else:
662
    disks = opts.disks.split(",")
663
  if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
664
    mode = constants.REPLACE_DISK_ALL
665
  elif opts.on_primary: # only on primary:
666
    mode = constants.REPLACE_DISK_PRI
667
    if new_2ndary is not None or iallocator is not None:
668
      raise errors.OpPrereqError("Can't change secondary node on primary disk"
669
                                 " replacement")
670
  elif opts.on_secondary is not None or iallocator is not None:
671
    # only on secondary
672
    mode = constants.REPLACE_DISK_SEC
673

    
674
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
675
                              remote_node=new_2ndary, mode=mode,
676
                              iallocator=iallocator)
677
  SubmitOrSend(op, opts)
678
  return 0
679

    
680

    
681
def FailoverInstance(opts, args):
682
  """Failover an instance.
683

    
684
  The failover is done by shutting it down on its present node and
685
  starting it on the secondary.
686

    
687
  Args:
688
    opts - class with options as members
689
    args - list with a single element, the instance name
690
  Opts used:
691
    force - whether to failover without asking questions.
692

    
693
  """
694
  instance_name = args[0]
695
  force = opts.force
696

    
697
  if not force:
698
    usertext = ("Failover will happen to image %s."
699
                " This requires a shutdown of the instance. Continue?" %
700
                (instance_name,))
701
    if not AskUser(usertext):
702
      return 1
703

    
704
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
705
                                  ignore_consistency=opts.ignore_consistency)
706
  SubmitOrSend(op, opts)
707
  return 0
708

    
709

    
710
def ConnectToInstanceConsole(opts, args):
711
  """Connect to the console of an instance.
712

    
713
  Args:
714
    opts - class with options as members
715
    args - list with a single element, the instance name
716

    
717
  """
718
  instance_name = args[0]
719

    
720
  op = opcodes.OpConnectConsole(instance_name=instance_name)
721
  cmd = SubmitOpCode(op)
722

    
723
  if opts.show_command:
724
    print utils.ShellQuoteArgs(cmd)
725
  else:
726
    try:
727
      os.execvp(cmd[0], cmd)
728
    finally:
729
      sys.stderr.write("Can't run console command %s with arguments:\n'%s'" %
730
                       (cmd, " ".join(argv)))
731
      os._exit(1)
732

    
733

    
734
def _FormatBlockDevInfo(buf, dev, indent_level, static):
735
  """Show block device information.
736

    
737
  This is only used by ShowInstanceConfig(), but it's too big to be
738
  left for an inline definition.
739

    
740
  """
741
  def helper(buf, dtype, status):
742
    """Format one line for physical device status."""
743
    if not status:
744
      buf.write("not active\n")
745
    else:
746
      (path, major, minor, syncp, estt, degr, ldisk) = status
747
      if major is None:
748
        major_string = "N/A"
749
      else:
750
        major_string = str(major)
751

    
752
      if minor is None:
753
        minor_string = "N/A"
754
      else:
755
        minor_string = str(minor)
756

    
757
      buf.write("%s (%s:%s)" % (path, major_string, minor_string))
758
      if dtype in (constants.LD_DRBD8, ):
759
        if syncp is not None:
760
          sync_text = "*RECOVERING* %5.2f%%," % syncp
761
          if estt:
762
            sync_text += " ETA %ds" % estt
763
          else:
764
            sync_text += " ETA unknown"
765
        else:
766
          sync_text = "in sync"
767
        if degr:
768
          degr_text = "*DEGRADED*"
769
        else:
770
          degr_text = "ok"
771
        if ldisk:
772
          ldisk_text = " *MISSING DISK*"
773
        else:
774
          ldisk_text = ""
775
        buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
776
      elif dtype == constants.LD_LV:
777
        if ldisk:
778
          ldisk_text = " *FAILED* (failed drive?)"
779
        else:
780
          ldisk_text = ""
781
        buf.write(ldisk_text)
782
      buf.write("\n")
783

    
784
  if dev["iv_name"] is not None:
785
    data = "  - %s, " % dev["iv_name"]
786
  else:
787
    data = "  - "
788
  data += "type: %s" % dev["dev_type"]
789
  if dev["logical_id"] is not None:
790
    data += ", logical_id: %s" % (dev["logical_id"],)
791
  elif dev["physical_id"] is not None:
792
    data += ", physical_id: %s" % (dev["physical_id"],)
793
  buf.write("%*s%s\n" % (2*indent_level, "", data))
794
  if not static:
795
    buf.write("%*s    primary:   " % (2*indent_level, ""))
796
    helper(buf, dev["dev_type"], dev["pstatus"])
797

    
798
  if dev["sstatus"] and not static:
799
    buf.write("%*s    secondary: " % (2*indent_level, ""))
800
    helper(buf, dev["dev_type"], dev["sstatus"])
801

    
802
  if dev["children"]:
803
    for child in dev["children"]:
804
      _FormatBlockDevInfo(buf, child, indent_level+1, static)
805

    
806

    
807
def ShowInstanceConfig(opts, args):
808
  """Compute instance run-time status.
809

    
810
  """
811
  retcode = 0
812
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
813
  result = SubmitOpCode(op)
814
  if not result:
815
    logger.ToStdout("No instances.")
816
    return 1
817

    
818
  buf = StringIO()
819
  retcode = 0
820
  for instance_name in result:
821
    instance = result[instance_name]
822
    buf.write("Instance name: %s\n" % instance["name"])
823
    buf.write("State: configured to be %s" % instance["config_state"])
824
    if not opts.static:
825
      buf.write(", actual state is %s" % instance["run_state"])
826
    buf.write("\n")
827
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
828
    ##          instance["auto_balance"])
829
    buf.write("  Nodes:\n")
830
    buf.write("    - primary: %s\n" % instance["pnode"])
831
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
832
    buf.write("  Operating system: %s\n" % instance["os"])
833
    if instance.has_key("network_port"):
834
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
835
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
836
    if instance["hypervisor"] == constants.HT_XEN_PVM:
837
      hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
838
                 (constants.HV_INITRD_PATH, "initrd path"))
839
    elif instance["hypervisor"] == constants.HT_XEN_HVM:
840
      hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
841
                 (constants.HV_ACPI, "ACPI"),
842
                 (constants.HV_PAE, "PAE"),
843
                 (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
844
                 (constants.HV_NIC_TYPE, "NIC type"),
845
                 (constants.HV_DISK_TYPE, "Disk type"),
846
                 (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
847
                 )
848
      # custom console information for HVM
849
      vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
850
      if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
851
        vnc_console_port = "%s:%s" % (instance["pnode"],
852
                                      instance["network_port"])
853
      elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
854
        vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
855
                                                 instance["network_port"],
856
                                                 instance["pnode"])
857
      else:
858
        vnc_console_port = "%s:%s" % (vnc_bind_address,
859
                                      instance["network_port"])
860
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
861

    
862
    else:
863
      # auto-handle other hypervisor types
864
      hvattrs = [(key, key) for key in instance["hv_actual"]]
865

    
866
    for key, desc in hvattrs:
867
      if key in instance["hv_instance"]:
868
        val = instance["hv_instance"][key]
869
      else:
870
        val = "default (%s)" % instance["hv_actual"][key]
871
      buf.write("    - %s: %s\n" % (desc, val))
872
    buf.write("  Hardware:\n")
873
    buf.write("    - VCPUs: %d\n" %
874
              instance["be_actual"][constants.BE_VCPUS])
875
    buf.write("    - memory: %dMiB\n" %
876
              instance["be_actual"][constants.BE_MEMORY])
877
    buf.write("    - NICs: %s\n" %
878
              ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
879
                         (mac, ip, bridge)
880
                         for mac, ip, bridge in instance["nics"]]))
881
    buf.write("  Block devices:\n")
882

    
883
    for device in instance["disks"]:
884
      _FormatBlockDevInfo(buf, device, 1, opts.static)
885

    
886
  logger.ToStdout(buf.getvalue().rstrip('\n'))
887
  return retcode
888

    
889

    
890
def SetInstanceParams(opts, args):
891
  """Modifies an instance.
892

    
893
  All parameters take effect only at the next restart of the instance.
894

    
895
  Args:
896
    opts - class with options as members
897
    args - list with a single element, the instance name
898
  Opts used:
899
    mac - the new MAC address of the instance
900

    
901
  """
902
  if not (opts.ip or opts.bridge or opts.mac or
903
          opts.hypervisor or opts.beparams):
904
    logger.ToStdout("Please give at least one of the parameters.")
905
    return 1
906

    
907
  if constants.BE_MEMORY in opts.beparams:
908
    opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
909
      opts.beparams[constants.BE_MEMORY])
910

    
911
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
912
                                   ip=opts.ip,
913
                                   bridge=opts.bridge, mac=opts.mac,
914
                                   hvparams=opts.hypervisor,
915
                                   beparams=opts.beparams,
916
                                   force=opts.force)
917

    
918
  # even if here we process the result, we allow submit only
919
  result = SubmitOrSend(op, opts)
920

    
921
  if result:
922
    logger.ToStdout("Modified instance %s" % args[0])
923
    for param, data in result:
924
      logger.ToStdout(" - %-5s -> %s" % (param, data))
925
    logger.ToStdout("Please don't forget that these parameters take effect"
926
                    " only at the next start of the instance.")
927
  return 0
928

    
929

    
930
# options used in more than one cmd
931
node_opt = make_option("-n", "--node", dest="node", help="Target node",
932
                       metavar="<node>")
933

    
934
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
935
                    metavar="<os>")
936

    
937
# multi-instance selection options
938
m_force_multi = make_option("--force-multiple", dest="force_multi",
939
                            help="Do not ask for confirmation when more than"
940
                            " one instance is affected",
941
                            action="store_true", default=False)
942

    
943
m_pri_node_opt = make_option("--primary", dest="multi_mode",
944
                             help="Filter by nodes (primary only)",
945
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
946

    
947
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
948
                             help="Filter by nodes (secondary only)",
949
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
950

    
951
m_node_opt = make_option("--node", dest="multi_mode",
952
                         help="Filter by nodes (primary and secondary)",
953
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
954

    
955
m_clust_opt = make_option("--all", dest="multi_mode",
956
                          help="Select all instances in the cluster",
957
                          const=_SHUTDOWN_CLUSTER, action="store_const")
958

    
959
m_inst_opt = make_option("--instance", dest="multi_mode",
960
                         help="Filter by instance name [default]",
961
                         const=_SHUTDOWN_INSTANCES, action="store_const")
962

    
963

    
964
# this is defined separately due to readability only
965
add_opts = [
966
  DEBUG_OPT,
967
  make_option("-n", "--node", dest="node",
968
              help="Target node and optional secondary node",
969
              metavar="<pnode>[:<snode>]"),
970
  cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
971
             " a suffix is used",
972
             default=20 * 1024, type="unit", metavar="<size>"),
973
  cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
974
             " suffix is used",
975
             default=4 * 1024, type="unit", metavar="<size>"),
976
  os_opt,
977
  keyval_option("-B", "--backend", dest="beparams",
978
                type="keyval", default={},
979
                help="Backend parameters"),
980
  make_option("-t", "--disk-template", dest="disk_template",
981
              help="Custom disk setup (diskless, file, plain or drbd)",
982
              default=None, metavar="TEMPL"),
983
  make_option("-i", "--ip", dest="ip",
984
              help="IP address ('none' [default], 'auto', or specify address)",
985
              default='none', type="string", metavar="<ADDRESS>"),
986
  make_option("--mac", dest="mac",
987
              help="MAC address ('auto' [default], or specify address)",
988
              default='auto', type="string", metavar="<MACADDRESS>"),
989
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
990
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
991
  make_option("-b", "--bridge", dest="bridge",
992
              help="Bridge to connect this instance to",
993
              default=None, metavar="<bridge>"),
994
  make_option("--no-start", dest="start", default=True,
995
              action="store_false", help="Don't start the instance after"
996
              " creation"),
997
  make_option("--no-ip-check", dest="ip_check", default=True,
998
              action="store_false", help="Don't check that the instance's IP"
999
              " is alive (only valid with --no-start)"),
1000
  make_option("--file-storage-dir", dest="file_storage_dir",
1001
              help="Relative path under default cluster-wide file storage dir"
1002
              " to store file-based disks", default=None,
1003
              metavar="<DIR>"),
1004
  make_option("--file-driver", dest="file_driver", help="Driver to use"
1005
              " for image files", default="loop", metavar="<DRIVER>"),
1006
  make_option("--iallocator", metavar="<NAME>",
1007
              help="Select nodes for the instance automatically using the"
1008
              " <NAME> iallocator plugin", default=None, type="string"),
1009
  ikv_option("-H", "--hypervisor", dest="hypervisor",
1010
              help="Hypervisor and hypervisor options, in the format"
1011
              " hypervisor:option=value,option=value,...", default=None,
1012
              type="identkeyval"),
1013
  SUBMIT_OPT,
1014
  ]
1015

    
1016
commands = {
1017
  'add': (AddInstance, ARGS_ONE, add_opts,
1018
          "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
1019
          "Creates and adds a new instance to the cluster"),
1020
  'batch-create': (BatchCreate, ARGS_ONE,
1021
                   [DEBUG_OPT],
1022
                   "<instances_file.json>",
1023
                   "Create a bunch of instances based on specs in the file."),
1024
  'console': (ConnectToInstanceConsole, ARGS_ONE,
1025
              [DEBUG_OPT,
1026
               make_option("--show-cmd", dest="show_command",
1027
                           action="store_true", default=False,
1028
                           help=("Show command instead of executing it"))],
1029
              "[--show-cmd] <instance>",
1030
              "Opens a console on the specified instance"),
1031
  'failover': (FailoverInstance, ARGS_ONE,
1032
               [DEBUG_OPT, FORCE_OPT,
1033
                make_option("--ignore-consistency", dest="ignore_consistency",
1034
                            action="store_true", default=False,
1035
                            help="Ignore the consistency of the disks on"
1036
                            " the secondary"),
1037
                SUBMIT_OPT,
1038
                ],
1039
               "[-f] <instance>",
1040
               "Stops the instance and starts it on the backup node, using"
1041
               " the remote mirror (only for instances of type drbd)"),
1042
  'info': (ShowInstanceConfig, ARGS_ANY,
1043
           [DEBUG_OPT,
1044
            make_option("-s", "--static", dest="static",
1045
                        action="store_true", default=False,
1046
                        help="Only show configuration data, not runtime data"),
1047
            ], "[-s] [<instance>...]",
1048
           "Show information on the specified instance(s)"),
1049
  'list': (ListInstances, ARGS_NONE,
1050
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
1051
           "Lists the instances and their status. The available fields are"
1052
           " (see the man page for details): status, oper_state, oper_ram,"
1053
           " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
1054
           " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
1055
           " hypervisor."
1056
           " The default field"
1057
           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
1058
           ),
1059
  'reinstall': (ReinstallInstance, ARGS_ONE,
1060
                [DEBUG_OPT, FORCE_OPT, os_opt,
1061
                 make_option("--select-os", dest="select_os",
1062
                             action="store_true", default=False,
1063
                             help="Interactive OS reinstall, lists available"
1064
                             " OS templates for selection"),
1065
                 SUBMIT_OPT,
1066
                 ],
1067
                "[-f] <instance>", "Reinstall a stopped instance"),
1068
  'remove': (RemoveInstance, ARGS_ONE,
1069
             [DEBUG_OPT, FORCE_OPT,
1070
              make_option("--ignore-failures", dest="ignore_failures",
1071
                          action="store_true", default=False,
1072
                          help=("Remove the instance from the cluster even"
1073
                                " if there are failures during the removal"
1074
                                " process (shutdown, disk removal, etc.)")),
1075
              SUBMIT_OPT,
1076
              ],
1077
             "[-f] <instance>", "Shuts down the instance and removes it"),
1078
  'rename': (RenameInstance, ARGS_FIXED(2),
1079
             [DEBUG_OPT,
1080
              make_option("--no-ip-check", dest="ignore_ip",
1081
                          help="Do not check that the IP of the new name"
1082
                          " is alive",
1083
                          default=False, action="store_true"),
1084
              SUBMIT_OPT,
1085
              ],
1086
             "<instance> <new_name>", "Rename the instance"),
1087
  'replace-disks': (ReplaceDisks, ARGS_ONE,
1088
                    [DEBUG_OPT,
1089
                     make_option("-n", "--new-secondary", dest="new_secondary",
1090
                                 help=("New secondary node (for secondary"
1091
                                       " node change)"), metavar="NODE"),
1092
                     make_option("-p", "--on-primary", dest="on_primary",
1093
                                 default=False, action="store_true",
1094
                                 help=("Replace the disk(s) on the primary"
1095
                                       " node (only for the drbd template)")),
1096
                     make_option("-s", "--on-secondary", dest="on_secondary",
1097
                                 default=False, action="store_true",
1098
                                 help=("Replace the disk(s) on the secondary"
1099
                                       " node (only for the drbd template)")),
1100
                     make_option("--disks", dest="disks", default=None,
1101
                                 help=("Comma-separated list of disks"
1102
                                       " to replace (e.g. sda) (optional,"
1103
                                       " defaults to all disks")),
1104
                     make_option("--iallocator", metavar="<NAME>",
1105
                                 help="Select new secondary for the instance"
1106
                                 " automatically using the"
1107
                                 " <NAME> iallocator plugin (enables"
1108
                                 " secondary node replacement)",
1109
                                 default=None, type="string"),
1110
                     SUBMIT_OPT,
1111
                     ],
1112
                    "[-s|-p|-n NODE] <instance>",
1113
                    "Replaces all disks for the instance"),
1114
  'modify': (SetInstanceParams, ARGS_ONE,
1115
             [DEBUG_OPT, FORCE_OPT,
1116
              make_option("-i", "--ip", dest="ip",
1117
                          help="IP address ('none' or numeric IP)",
1118
                          default=None, type="string", metavar="<ADDRESS>"),
1119
              make_option("-b", "--bridge", dest="bridge",
1120
                          help="Bridge to connect this instance to",
1121
                          default=None, type="string", metavar="<bridge>"),
1122
              make_option("--mac", dest="mac",
1123
                          help="MAC address", default=None,
1124
                          type="string", metavar="<MACADDRESS>"),
1125
              keyval_option("-H", "--hypervisor", type="keyval",
1126
                            default={}, dest="hypervisor",
1127
                            help="Change hypervisor parameters"),
1128
              keyval_option("-B", "--backend", type="keyval",
1129
                            default={}, dest="beparams",
1130
                            help="Change backend parameters"),
1131
              SUBMIT_OPT,
1132
              ],
1133
             "<instance>", "Alters the parameters of an instance"),
1134
  'shutdown': (ShutdownInstance, ARGS_ANY,
1135
               [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1136
                m_clust_opt, m_inst_opt, m_force_multi,
1137
                SUBMIT_OPT,
1138
                ],
1139
               "<instance>", "Stops an instance"),
1140
  'startup': (StartupInstance, ARGS_ANY,
1141
              [DEBUG_OPT, FORCE_OPT, m_force_multi,
1142
               make_option("-e", "--extra", dest="extra_args",
1143
                           help="Extra arguments for the instance's kernel",
1144
                           default=None, type="string", metavar="<PARAMS>"),
1145
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1146
               m_clust_opt, m_inst_opt,
1147
               SUBMIT_OPT,
1148
               ],
1149
            "<instance>", "Starts an instance"),
1150

    
1151
  'reboot': (RebootInstance, ARGS_ANY,
1152
              [DEBUG_OPT, m_force_multi,
1153
               make_option("-e", "--extra", dest="extra_args",
1154
                           help="Extra arguments for the instance's kernel",
1155
                           default=None, type="string", metavar="<PARAMS>"),
1156
               make_option("-t", "--type", dest="reboot_type",
1157
                           help="Type of reboot: soft/hard/full",
1158
                           default=constants.INSTANCE_REBOOT_HARD,
1159
                           type="string", metavar="<REBOOT>"),
1160
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1161
                           default=False, action="store_true",
1162
                           help="Ignore errors from secondaries"),
1163
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1164
               m_clust_opt, m_inst_opt,
1165
               SUBMIT_OPT,
1166
               ],
1167
            "<instance>", "Reboots an instance"),
1168
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1169
                     "<instance>",
1170
                     "Activate an instance's disks"),
1171
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1172
                       "<instance>",
1173
                       "Deactivate an instance's disks"),
1174
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1175
                [DEBUG_OPT, SUBMIT_OPT,
1176
                 make_option("--no-wait-for-sync",
1177
                             dest="wait_for_sync", default=True,
1178
                             action="store_false",
1179
                             help="Don't wait for sync (DANGEROUS!)"),
1180
                 ],
1181
                "<instance> <disk> <size>", "Grow an instance's disk"),
1182
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1183
                "<instance_name>", "List the tags of the given instance"),
1184
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1185
               "<instance_name> tag...", "Add tags to the given instance"),
1186
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1187
                  "<instance_name> tag...", "Remove tags from given instance"),
1188
  }
1189

    
1190
aliases = {
1191
  'activate_block_devs': 'activate-disks',
1192
  'replace_disks': 'replace-disks',
1193
  'start': 'startup',
1194
  'stop': 'shutdown',
1195
  }
1196

    
1197
if __name__ == '__main__':
1198
  sys.exit(GenericMain(commands, aliases=aliases,
1199
                       override={"tag_type": constants.TAG_INSTANCE}))