Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 6605411d

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

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

    
35

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

    
42

    
43
_VALUE_TRUE = "true"
44

    
45
_LIST_DEF_FIELDS = [
46
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
47
  ]
48

    
49

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

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

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

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

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

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

    
103
  else:
104
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
105

    
106
  return inames
107

    
108

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

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

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

    
119
  Returns: boolean depending on user's confirmation.
120

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

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

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

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

    
143

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

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

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

    
164
  return result_path
165

    
166

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

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

    
178
  output = GetClient().QueryInstances([], selected_fields)
179

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

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

    
214
  numfields = ["be/memory", "oper_ram", "sda_size", "sdb_size", "be/vcpus",
215
               "serial_no"]
216

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

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

    
252
  for line in data:
253
    logger.ToStdout(line)
254

    
255
  return 0
256

    
257

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

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

    
270
  """
271
  instance = args[0]
272

    
273
  (pnode, snode) = SplitNodeOption(opts.node)
274

    
275
  hypervisor = None
276
  hvparams = {}
277
  if opts.hypervisor:
278
    hypervisor, hvparams = opts.hypervisor
279

    
280
  bep = ValidateBeParams(opts.beparams)
281

    
282
##  kernel_path = _TransformPath(opts.kernel_path)
283
##  initrd_path = _TransformPath(opts.initrd_path)
284

    
285
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
286
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
287

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

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

    
312
  SubmitOrSend(op, opts)
313
  return 0
314

    
315

    
316
def ReinstallInstance(opts, args):
317
  """Reinstall an instance.
318

    
319
  Args:
320
    opts - class with options as members
321
    args - list containing a single element, the instance name
322

    
323
  """
324
  instance_name = args[0]
325

    
326
  if opts.select_os is True:
327
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
328
    result = SubmitOpCode(op)
329

    
330
    if not result:
331
      logger.ToStdout("Can't get the OS list")
332
      return 1
333

    
334
    logger.ToStdout("Available OS templates:")
335
    number = 0
336
    choices = []
337
    for entry in result:
338
      logger.ToStdout("%3s: %s" % (number, entry[0]))
339
      choices.append(("%s" % number, entry[0], entry[0]))
340
      number = number + 1
341

    
342
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
343
    selected = AskUser("Enter OS template name or number (or x to abort):",
344
                       choices)
345

    
346
    if selected == 'exit':
347
      logger.ToStdout("User aborted reinstall, exiting")
348
      return 1
349

    
350
    os = selected
351
  else:
352
    os = opts.os
353

    
354
  if not opts.force:
355
    usertext = ("This will reinstall the instance %s and remove"
356
                " all data. Continue?") % instance_name
357
    if not AskUser(usertext):
358
      return 1
359

    
360
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
361
                                   os_type=os)
362
  SubmitOrSend(op, opts)
363

    
364
  return 0
365

    
366

    
367
def RemoveInstance(opts, args):
368
  """Remove an instance.
369

    
370
  Args:
371
    opts - class with options as members
372
    args - list containing a single element, the instance name
373

    
374
  """
375
  instance_name = args[0]
376
  force = opts.force
377

    
378
  if not force:
379
    usertext = ("This will remove the volumes of the instance %s"
380
                " (including mirrors), thus removing all the data"
381
                " of the instance. Continue?") % instance_name
382
    if not AskUser(usertext):
383
      return 1
384

    
385
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
386
                                ignore_failures=opts.ignore_failures)
387
  SubmitOrSend(op, opts)
388
  return 0
389

    
390

    
391
def RenameInstance(opts, args):
392
  """Rename an instance.
393

    
394
  Args:
395
    opts - class with options as members
396
    args - list containing two elements, the instance name and the new name
397

    
398
  """
399
  op = opcodes.OpRenameInstance(instance_name=args[0],
400
                                new_name=args[1],
401
                                ignore_ip=opts.ignore_ip)
402
  SubmitOrSend(op, opts)
403
  return 0
404

    
405

    
406
def ActivateDisks(opts, args):
407
  """Activate an instance's disks.
408

    
409
  This serves two purposes:
410
    - it allows one (as long as the instance is not running) to mount
411
    the disks and modify them from the node
412
    - it repairs inactive secondary drbds
413

    
414
  """
415
  instance_name = args[0]
416
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
417
  disks_info = SubmitOrSend(op, opts)
418
  for host, iname, nname in disks_info:
419
    print "%s:%s:%s" % (host, iname, nname)
420
  return 0
421

    
422

    
423
def DeactivateDisks(opts, args):
424
  """Command-line interface for _ShutdownInstanceBlockDevices.
425

    
426
  This function takes the instance name, looks for its primary node
427
  and the tries to shutdown its block devices on that node.
428

    
429
  """
430
  instance_name = args[0]
431
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
432
  SubmitOrSend(op, opts)
433
  return 0
434

    
435

    
436
def GrowDisk(opts, args):
437
  """Command-line interface for _ShutdownInstanceBlockDevices.
438

    
439
  This function takes the instance name, looks for its primary node
440
  and the tries to shutdown its block devices on that node.
441

    
442
  """
443
  instance = args[0]
444
  disk = args[1]
445
  amount = utils.ParseUnit(args[2])
446
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount,
447
                          wait_for_sync=opts.wait_for_sync)
448
  SubmitOrSend(op, opts)
449
  return 0
450

    
451

    
452
def StartupInstance(opts, args):
453
  """Startup an instance.
454

    
455
  Args:
456
    opts - class with options as members
457
    args - list containing a single element, the instance name
458

    
459
  """
460
  if opts.multi_mode is None:
461
    opts.multi_mode = _SHUTDOWN_INSTANCES
462
  inames = _ExpandMultiNames(opts.multi_mode, args)
463
  if not inames:
464
    raise errors.OpPrereqError("Selection filter does not match any instances")
465
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
466
  if not (opts.force_multi or not multi_on
467
          or _ConfirmOperation(inames, "startup")):
468
    return 1
469
  for name in inames:
470
    op = opcodes.OpStartupInstance(instance_name=name,
471
                                   force=opts.force,
472
                                   extra_args=opts.extra_args)
473
    if multi_on:
474
      logger.ToStdout("Starting up %s" % name)
475
    try:
476
      SubmitOrSend(op, opts)
477
    except JobSubmittedException, err:
478
      _, txt = FormatError(err)
479
      logger.ToStdout("%s" % txt)
480
  return 0
481

    
482

    
483
def RebootInstance(opts, args):
484
  """Reboot an instance
485

    
486
  Args:
487
    opts - class with options as members
488
    args - list containing a single element, the instance name
489

    
490
  """
491
  if opts.multi_mode is None:
492
    opts.multi_mode = _SHUTDOWN_INSTANCES
493
  inames = _ExpandMultiNames(opts.multi_mode, args)
494
  if not inames:
495
    raise errors.OpPrereqError("Selection filter does not match any instances")
496
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
497
  if not (opts.force_multi or not multi_on
498
          or _ConfirmOperation(inames, "reboot")):
499
    return 1
500
  for name in inames:
501
    op = opcodes.OpRebootInstance(instance_name=name,
502
                                  reboot_type=opts.reboot_type,
503
                                  ignore_secondaries=opts.ignore_secondaries)
504

    
505
    SubmitOrSend(op, opts)
506
  return 0
507

    
508

    
509
def ShutdownInstance(opts, args):
510
  """Shutdown an instance.
511

    
512
  Args:
513
    opts - class with options as members
514
    args - list containing a single element, the instance name
515

    
516
  """
517
  if opts.multi_mode is None:
518
    opts.multi_mode = _SHUTDOWN_INSTANCES
519
  inames = _ExpandMultiNames(opts.multi_mode, args)
520
  if not inames:
521
    raise errors.OpPrereqError("Selection filter does not match any instances")
522
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
523
  if not (opts.force_multi or not multi_on
524
          or _ConfirmOperation(inames, "shutdown")):
525
    return 1
526
  for name in inames:
527
    op = opcodes.OpShutdownInstance(instance_name=name)
528
    if multi_on:
529
      logger.ToStdout("Shutting down %s" % name)
530
    try:
531
      SubmitOrSend(op, opts)
532
    except JobSubmittedException, err:
533
      _, txt = FormatError(err)
534
      logger.ToStdout("%s" % txt)
535
  return 0
536

    
537

    
538
def ReplaceDisks(opts, args):
539
  """Replace the disks of an instance
540

    
541
  Args:
542
    opts - class with options as members
543
    args - list with a single element, the instance name
544

    
545
  """
546
  instance_name = args[0]
547
  new_2ndary = opts.new_secondary
548
  iallocator = opts.iallocator
549
  if opts.disks is None:
550
    disks = ["sda", "sdb"]
551
  else:
552
    disks = opts.disks.split(",")
553
  if opts.on_primary == opts.on_secondary: # no -p or -s passed, or both passed
554
    mode = constants.REPLACE_DISK_ALL
555
  elif opts.on_primary: # only on primary:
556
    mode = constants.REPLACE_DISK_PRI
557
    if new_2ndary is not None or iallocator is not None:
558
      raise errors.OpPrereqError("Can't change secondary node on primary disk"
559
                                 " replacement")
560
  elif opts.on_secondary is not None or iallocator is not None:
561
    # only on secondary
562
    mode = constants.REPLACE_DISK_SEC
563

    
564
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
565
                              remote_node=new_2ndary, mode=mode,
566
                              iallocator=iallocator)
567
  SubmitOrSend(op, opts)
568
  return 0
569

    
570

    
571
def FailoverInstance(opts, args):
572
  """Failover an instance.
573

    
574
  The failover is done by shutting it down on its present node and
575
  starting it on the secondary.
576

    
577
  Args:
578
    opts - class with options as members
579
    args - list with a single element, the instance name
580
  Opts used:
581
    force - whether to failover without asking questions.
582

    
583
  """
584
  instance_name = args[0]
585
  force = opts.force
586

    
587
  if not force:
588
    usertext = ("Failover will happen to image %s."
589
                " This requires a shutdown of the instance. Continue?" %
590
                (instance_name,))
591
    if not AskUser(usertext):
592
      return 1
593

    
594
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
595
                                  ignore_consistency=opts.ignore_consistency)
596
  SubmitOrSend(op, opts)
597
  return 0
598

    
599

    
600
def ConnectToInstanceConsole(opts, args):
601
  """Connect to the console of an instance.
602

    
603
  Args:
604
    opts - class with options as members
605
    args - list with a single element, the instance name
606

    
607
  """
608
  instance_name = args[0]
609

    
610
  op = opcodes.OpConnectConsole(instance_name=instance_name)
611
  cmd = SubmitOpCode(op)
612

    
613
  if opts.show_command:
614
    print utils.ShellQuoteArgs(cmd)
615
  else:
616
    try:
617
      os.execvp(cmd[0], cmd)
618
    finally:
619
      sys.stderr.write("Can't run console command %s with arguments:\n'%s'" %
620
                       (cmd, " ".join(argv)))
621
      os._exit(1)
622

    
623

    
624
def _FormatBlockDevInfo(buf, dev, indent_level, static):
625
  """Show block device information.
626

    
627
  This is only used by ShowInstanceConfig(), but it's too big to be
628
  left for an inline definition.
629

    
630
  """
631
  def helper(buf, dtype, status):
632
    """Format one line for physical device status."""
633
    if not status:
634
      buf.write("not active\n")
635
    else:
636
      (path, major, minor, syncp, estt, degr, ldisk) = status
637
      if major is None:
638
        major_string = "N/A"
639
      else:
640
        major_string = str(major)
641

    
642
      if minor is None:
643
        minor_string = "N/A"
644
      else:
645
        minor_string = str(minor)
646

    
647
      buf.write("%s (%s:%s)" % (path, major_string, minor_string))
648
      if dtype in (constants.LD_DRBD8, ):
649
        if syncp is not None:
650
          sync_text = "*RECOVERING* %5.2f%%," % syncp
651
          if estt:
652
            sync_text += " ETA %ds" % estt
653
          else:
654
            sync_text += " ETA unknown"
655
        else:
656
          sync_text = "in sync"
657
        if degr:
658
          degr_text = "*DEGRADED*"
659
        else:
660
          degr_text = "ok"
661
        if ldisk:
662
          ldisk_text = " *MISSING DISK*"
663
        else:
664
          ldisk_text = ""
665
        buf.write(" %s, status %s%s" % (sync_text, degr_text, ldisk_text))
666
      elif dtype == constants.LD_LV:
667
        if ldisk:
668
          ldisk_text = " *FAILED* (failed drive?)"
669
        else:
670
          ldisk_text = ""
671
        buf.write(ldisk_text)
672
      buf.write("\n")
673

    
674
  if dev["iv_name"] is not None:
675
    data = "  - %s, " % dev["iv_name"]
676
  else:
677
    data = "  - "
678
  data += "type: %s" % dev["dev_type"]
679
  if dev["logical_id"] is not None:
680
    data += ", logical_id: %s" % (dev["logical_id"],)
681
  elif dev["physical_id"] is not None:
682
    data += ", physical_id: %s" % (dev["physical_id"],)
683
  buf.write("%*s%s\n" % (2*indent_level, "", data))
684
  if not static:
685
    buf.write("%*s    primary:   " % (2*indent_level, ""))
686
    helper(buf, dev["dev_type"], dev["pstatus"])
687

    
688
  if dev["sstatus"] and not static:
689
    buf.write("%*s    secondary: " % (2*indent_level, ""))
690
    helper(buf, dev["dev_type"], dev["sstatus"])
691

    
692
  if dev["children"]:
693
    for child in dev["children"]:
694
      _FormatBlockDevInfo(buf, child, indent_level+1, static)
695

    
696

    
697
def ShowInstanceConfig(opts, args):
698
  """Compute instance run-time status.
699

    
700
  """
701
  retcode = 0
702
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
703
  result = SubmitOpCode(op)
704
  if not result:
705
    logger.ToStdout("No instances.")
706
    return 1
707

    
708
  buf = StringIO()
709
  retcode = 0
710
  for instance_name in result:
711
    instance = result[instance_name]
712
    buf.write("Instance name: %s\n" % instance["name"])
713
    buf.write("State: configured to be %s" % instance["config_state"])
714
    if not opts.static:
715
      buf.write(", actual state is %s" % instance["run_state"])
716
    buf.write("\n")
717
    ##buf.write("Considered for memory checks in cluster verify: %s\n" %
718
    ##          instance["auto_balance"])
719
    buf.write("  Nodes:\n")
720
    buf.write("    - primary: %s\n" % instance["pnode"])
721
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
722
    buf.write("  Operating system: %s\n" % instance["os"])
723
    if instance.has_key("network_port"):
724
      buf.write("  Allocated network port: %s\n" % instance["network_port"])
725
    buf.write("  Hypervisor: %s\n" % instance["hypervisor"])
726
    if instance["hypervisor"] == constants.HT_XEN_PVM:
727
      hvattrs = ((constants.HV_KERNEL_PATH, "kernel path"),
728
                 (constants.HV_INITRD_PATH, "initrd path"))
729
    elif instance["hypervisor"] == constants.HT_XEN_HVM:
730
      hvattrs = ((constants.HV_BOOT_ORDER, "boot order"),
731
                 (constants.HV_ACPI, "ACPI"),
732
                 (constants.HV_PAE, "PAE"),
733
                 (constants.HV_CDROM_IMAGE_PATH, "virtual CDROM"),
734
                 (constants.HV_NIC_TYPE, "NIC type"),
735
                 (constants.HV_DISK_TYPE, "Disk type"),
736
                 (constants.HV_VNC_BIND_ADDRESS, "VNC bind address"),
737
                 )
738
      # custom console information for HVM
739
      vnc_bind_address = instance["hv_actual"][constants.HV_VNC_BIND_ADDRESS]
740
      if vnc_bind_address == constants.BIND_ADDRESS_GLOBAL:
741
        vnc_console_port = "%s:%s" % (instance["pnode"],
742
                                      instance["network_port"])
743
      elif vnc_bind_address == constants.LOCALHOST_IP_ADDRESS:
744
        vnc_console_port = "%s:%s on node %s" % (vnc_bind_address,
745
                                                 instance["network_port"],
746
                                                 instance["pnode"])
747
      else:
748
        vnc_console_port = "%s:%s" % (vnc_bind_address,
749
                                      instance["network_port"])
750
      buf.write("    - console connection: vnc to %s\n" % vnc_console_port)
751

    
752
    else:
753
      # auto-handle other hypervisor types
754
      hvattrs = [(key, key) for key in instance["hv_actual"]]
755

    
756
    for key, desc in hvattrs:
757
      if key in instance["hv_instance"]:
758
        val = instance["hv_instance"][key]
759
      else:
760
        val = "default (%s)" % instance["hv_actual"][key]
761
      buf.write("    - %s: %s\n" % (desc, val))
762
    buf.write("  Hardware:\n")
763
    buf.write("    - VCPUs: %d\n" %
764
              instance["be_actual"][constants.BE_VCPUS])
765
    buf.write("    - memory: %dMiB\n" %
766
              instance["be_actual"][constants.BE_MEMORY])
767
    buf.write("    - NICs: %s\n" %
768
              ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
769
                         (mac, ip, bridge)
770
                         for mac, ip, bridge in instance["nics"]]))
771
    buf.write("  Block devices:\n")
772

    
773
    for device in instance["disks"]:
774
      _FormatBlockDevInfo(buf, device, 1, opts.static)
775

    
776
  logger.ToStdout(buf.getvalue().rstrip('\n'))
777
  return retcode
778

    
779

    
780
def SetInstanceParams(opts, args):
781
  """Modifies an instance.
782

    
783
  All parameters take effect only at the next restart of the instance.
784

    
785
  Args:
786
    opts - class with options as members
787
    args - list with a single element, the instance name
788
  Opts used:
789
    mac - the new MAC address of the instance
790

    
791
  """
792
  if not (opts.mem or opts.vcpus or opts.ip or opts.bridge or opts.mac or
793
          opts.hypervisor):
794
    logger.ToStdout("Please give at least one of the parameters.")
795
    return 1
796

    
797
  op = opcodes.OpSetInstanceParams(instance_name=args[0],
798
                                   ip=opts.ip,
799
                                   bridge=opts.bridge, mac=opts.mac,
800
                                   hvparams=opts.hypervisor,
801
                                   beparams=opts.beparams,
802
                                   force=opts.force)
803

    
804
  # even if here we process the result, we allow submit only
805
  result = SubmitOrSend(op, opts)
806

    
807
  if result:
808
    logger.ToStdout("Modified instance %s" % args[0])
809
    for param, data in result:
810
      logger.ToStdout(" - %-5s -> %s" % (param, data))
811
    logger.ToStdout("Please don't forget that these parameters take effect"
812
                    " only at the next start of the instance.")
813
  return 0
814

    
815

    
816
# options used in more than one cmd
817
node_opt = make_option("-n", "--node", dest="node", help="Target node",
818
                       metavar="<node>")
819

    
820
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
821
                    metavar="<os>")
822

    
823
# multi-instance selection options
824
m_force_multi = make_option("--force-multiple", dest="force_multi",
825
                            help="Do not ask for confirmation when more than"
826
                            " one instance is affected",
827
                            action="store_true", default=False)
828

    
829
m_pri_node_opt = make_option("--primary", dest="multi_mode",
830
                             help="Filter by nodes (primary only)",
831
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
832

    
833
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
834
                             help="Filter by nodes (secondary only)",
835
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
836

    
837
m_node_opt = make_option("--node", dest="multi_mode",
838
                         help="Filter by nodes (primary and secondary)",
839
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
840

    
841
m_clust_opt = make_option("--all", dest="multi_mode",
842
                          help="Select all instances in the cluster",
843
                          const=_SHUTDOWN_CLUSTER, action="store_const")
844

    
845
m_inst_opt = make_option("--instance", dest="multi_mode",
846
                         help="Filter by instance name [default]",
847
                         const=_SHUTDOWN_INSTANCES, action="store_const")
848

    
849

    
850
# this is defined separately due to readability only
851
add_opts = [
852
  DEBUG_OPT,
853
  make_option("-n", "--node", dest="node",
854
              help="Target node and optional secondary node",
855
              metavar="<pnode>[:<snode>]"),
856
  cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
857
             " a suffix is used",
858
             default=20 * 1024, type="unit", metavar="<size>"),
859
  cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
860
             " suffix is used",
861
             default=4 * 1024, type="unit", metavar="<size>"),
862
  os_opt,
863
  keyval_option("-B", "--backend", dest="beparams",
864
                type="keyval", default={},
865
                help="Backend parameters"),
866
  make_option("-t", "--disk-template", dest="disk_template",
867
              help="Custom disk setup (diskless, file, plain or drbd)",
868
              default=None, metavar="TEMPL"),
869
  make_option("-i", "--ip", dest="ip",
870
              help="IP address ('none' [default], 'auto', or specify address)",
871
              default='none', type="string", metavar="<ADDRESS>"),
872
  make_option("--mac", dest="mac",
873
              help="MAC address ('auto' [default], or specify address)",
874
              default='auto', type="string", metavar="<MACADDRESS>"),
875
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
876
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
877
  make_option("-b", "--bridge", dest="bridge",
878
              help="Bridge to connect this instance to",
879
              default=None, metavar="<bridge>"),
880
  make_option("--no-start", dest="start", default=True,
881
              action="store_false", help="Don't start the instance after"
882
              " creation"),
883
  make_option("--no-ip-check", dest="ip_check", default=True,
884
              action="store_false", help="Don't check that the instance's IP"
885
              " is alive (only valid with --no-start)"),
886
  make_option("--kernel", dest="kernel_path",
887
              help="Path to the instances' kernel (or 'default')",
888
              default=None,
889
              type="string", metavar="<FILENAME>"),
890
  make_option("--initrd", dest="initrd_path",
891
              help="Path to the instances' initrd (or 'none', or 'default')",
892
              default=None,
893
              type="string", metavar="<FILENAME>"),
894
  make_option("--hvm-boot-order", dest="hvm_boot_order",
895
              help="Boot device order for HVM (one or more of [acdn])",
896
              default=None, type="string", metavar="<BOOTORDER>"),
897
  make_option("--file-storage-dir", dest="file_storage_dir",
898
              help="Relative path under default cluster-wide file storage dir"
899
              " to store file-based disks", default=None,
900
              metavar="<DIR>"),
901
  make_option("--file-driver", dest="file_driver", help="Driver to use"
902
              " for image files", default="loop", metavar="<DRIVER>"),
903
  make_option("--iallocator", metavar="<NAME>",
904
              help="Select nodes for the instance automatically using the"
905
              " <NAME> iallocator plugin", default=None, type="string"),
906
  ikv_option("-H", "--hypervisor", dest="hypervisor",
907
              help="Hypervisor and hypervisor options, in the format"
908
              " hypervisor:option=value,option=value,...", default=None,
909
              type="identkeyval"),
910
  make_option("--vnc-bind-address", dest="vnc_bind_address",
911
              help="bind address for VNC (IP address)",
912
              default=None, type="string", metavar="<VNCADDRESS>"),
913
  SUBMIT_OPT,
914
  ]
915

    
916
commands = {
917
  'add': (AddInstance, ARGS_ONE, add_opts,
918
          "[...] -t disk-type -n node[:secondary-node] -o os-type <name>",
919
          "Creates and adds a new instance to the cluster"),
920
  'console': (ConnectToInstanceConsole, ARGS_ONE,
921
              [DEBUG_OPT,
922
               make_option("--show-cmd", dest="show_command",
923
                           action="store_true", default=False,
924
                           help=("Show command instead of executing it"))],
925
              "[--show-cmd] <instance>",
926
              "Opens a console on the specified instance"),
927
  'failover': (FailoverInstance, ARGS_ONE,
928
               [DEBUG_OPT, FORCE_OPT,
929
                make_option("--ignore-consistency", dest="ignore_consistency",
930
                            action="store_true", default=False,
931
                            help="Ignore the consistency of the disks on"
932
                            " the secondary"),
933
                SUBMIT_OPT,
934
                ],
935
               "[-f] <instance>",
936
               "Stops the instance and starts it on the backup node, using"
937
               " the remote mirror (only for instances of type drbd)"),
938
  'info': (ShowInstanceConfig, ARGS_ANY,
939
           [DEBUG_OPT,
940
            make_option("-s", "--static", dest="static",
941
                        action="store_true", default=False,
942
                        help="Only show configuration data, not runtime data"),
943
            ], "[-s] [<instance>...]",
944
           "Show information on the specified instance(s)"),
945
  'list': (ListInstances, ARGS_NONE,
946
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT], "",
947
           "Lists the instances and their status. The available fields are"
948
           " (see the man page for details): status, oper_state, oper_ram,"
949
           " name, os, pnode, snodes, admin_state, admin_ram, disk_template,"
950
           " ip, mac, bridge, sda_size, sdb_size, vcpus, serial_no,"
951
           " hypervisor."
952
           " The default field"
953
           " list is (in order): %s." % ", ".join(_LIST_DEF_FIELDS),
954
           ),
955
  'reinstall': (ReinstallInstance, ARGS_ONE,
956
                [DEBUG_OPT, FORCE_OPT, os_opt,
957
                 make_option("--select-os", dest="select_os",
958
                             action="store_true", default=False,
959
                             help="Interactive OS reinstall, lists available"
960
                             " OS templates for selection"),
961
                 SUBMIT_OPT,
962
                 ],
963
                "[-f] <instance>", "Reinstall a stopped instance"),
964
  'remove': (RemoveInstance, ARGS_ONE,
965
             [DEBUG_OPT, FORCE_OPT,
966
              make_option("--ignore-failures", dest="ignore_failures",
967
                          action="store_true", default=False,
968
                          help=("Remove the instance from the cluster even"
969
                                " if there are failures during the removal"
970
                                " process (shutdown, disk removal, etc.)")),
971
              SUBMIT_OPT,
972
              ],
973
             "[-f] <instance>", "Shuts down the instance and removes it"),
974
  'rename': (RenameInstance, ARGS_FIXED(2),
975
             [DEBUG_OPT,
976
              make_option("--no-ip-check", dest="ignore_ip",
977
                          help="Do not check that the IP of the new name"
978
                          " is alive",
979
                          default=False, action="store_true"),
980
              SUBMIT_OPT,
981
              ],
982
             "<instance> <new_name>", "Rename the instance"),
983
  'replace-disks': (ReplaceDisks, ARGS_ONE,
984
                    [DEBUG_OPT,
985
                     make_option("-n", "--new-secondary", dest="new_secondary",
986
                                 help=("New secondary node (for secondary"
987
                                       " node change)"), metavar="NODE"),
988
                     make_option("-p", "--on-primary", dest="on_primary",
989
                                 default=False, action="store_true",
990
                                 help=("Replace the disk(s) on the primary"
991
                                       " node (only for the drbd template)")),
992
                     make_option("-s", "--on-secondary", dest="on_secondary",
993
                                 default=False, action="store_true",
994
                                 help=("Replace the disk(s) on the secondary"
995
                                       " node (only for the drbd template)")),
996
                     make_option("--disks", dest="disks", default=None,
997
                                 help=("Comma-separated list of disks"
998
                                       " to replace (e.g. sda) (optional,"
999
                                       " defaults to all disks")),
1000
                     make_option("--iallocator", metavar="<NAME>",
1001
                                 help="Select new secondary for the instance"
1002
                                 " automatically using the"
1003
                                 " <NAME> iallocator plugin (enables"
1004
                                 " secondary node replacement)",
1005
                                 default=None, type="string"),
1006
                     SUBMIT_OPT,
1007
                     ],
1008
                    "[-s|-p|-n NODE] <instance>",
1009
                    "Replaces all disks for the instance"),
1010
  'modify': (SetInstanceParams, ARGS_ONE,
1011
             [DEBUG_OPT, FORCE_OPT,
1012
              cli_option("-m", "--memory", dest="mem",
1013
                         help="Memory size",
1014
                         default=None, type="unit", metavar="<mem>"),
1015
              make_option("-p", "--cpu", dest="vcpus",
1016
                          help="Number of virtual CPUs",
1017
                          default=None, type="int", metavar="<PROC>"),
1018
              make_option("-i", "--ip", dest="ip",
1019
                          help="IP address ('none' or numeric IP)",
1020
                          default=None, type="string", metavar="<ADDRESS>"),
1021
              make_option("-b", "--bridge", dest="bridge",
1022
                          help="Bridge to connect this instance to",
1023
                          default=None, type="string", metavar="<bridge>"),
1024
              make_option("--mac", dest="mac",
1025
                          help="MAC address", default=None,
1026
                          type="string", metavar="<MACADDRESS>"),
1027
              keyval_option("-H", "--hypervisor", type="keyval",
1028
                            default={}, dest="hypervisor",
1029
                            help="Change hypervisor parameters"),
1030
              SUBMIT_OPT,
1031
              ],
1032
             "<instance>", "Alters the parameters of an instance"),
1033
  'shutdown': (ShutdownInstance, ARGS_ANY,
1034
               [DEBUG_OPT, m_node_opt, m_pri_node_opt, m_sec_node_opt,
1035
                m_clust_opt, m_inst_opt, m_force_multi,
1036
                SUBMIT_OPT,
1037
                ],
1038
               "<instance>", "Stops an instance"),
1039
  'startup': (StartupInstance, ARGS_ANY,
1040
              [DEBUG_OPT, FORCE_OPT, m_force_multi,
1041
               make_option("-e", "--extra", dest="extra_args",
1042
                           help="Extra arguments for the instance's kernel",
1043
                           default=None, type="string", metavar="<PARAMS>"),
1044
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1045
               m_clust_opt, m_inst_opt,
1046
               SUBMIT_OPT,
1047
               ],
1048
            "<instance>", "Starts an instance"),
1049

    
1050
  'reboot': (RebootInstance, ARGS_ANY,
1051
              [DEBUG_OPT, m_force_multi,
1052
               make_option("-e", "--extra", dest="extra_args",
1053
                           help="Extra arguments for the instance's kernel",
1054
                           default=None, type="string", metavar="<PARAMS>"),
1055
               make_option("-t", "--type", dest="reboot_type",
1056
                           help="Type of reboot: soft/hard/full",
1057
                           default=constants.INSTANCE_REBOOT_HARD,
1058
                           type="string", metavar="<REBOOT>"),
1059
               make_option("--ignore-secondaries", dest="ignore_secondaries",
1060
                           default=False, action="store_true",
1061
                           help="Ignore errors from secondaries"),
1062
               m_node_opt, m_pri_node_opt, m_sec_node_opt,
1063
               m_clust_opt, m_inst_opt,
1064
               SUBMIT_OPT,
1065
               ],
1066
            "<instance>", "Reboots an instance"),
1067
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1068
                     "<instance>",
1069
                     "Activate an instance's disks"),
1070
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT, SUBMIT_OPT],
1071
                       "<instance>",
1072
                       "Deactivate an instance's disks"),
1073
  'grow-disk': (GrowDisk, ARGS_FIXED(3),
1074
                [DEBUG_OPT, SUBMIT_OPT,
1075
                 make_option("--no-wait-for-sync",
1076
                             dest="wait_for_sync", default=True,
1077
                             action="store_false",
1078
                             help="Don't wait for sync (DANGEROUS!)"),
1079
                 ],
1080
                "<instance> <disk> <size>", "Grow an instance's disk"),
1081
  'list-tags': (ListTags, ARGS_ONE, [DEBUG_OPT],
1082
                "<instance_name>", "List the tags of the given instance"),
1083
  'add-tags': (AddTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1084
               "<instance_name> tag...", "Add tags to the given instance"),
1085
  'remove-tags': (RemoveTags, ARGS_ATLEAST(1), [DEBUG_OPT, TAG_SRC_OPT],
1086
                  "<instance_name> tag...", "Remove tags from given instance"),
1087
  }
1088

    
1089
aliases = {
1090
  'activate_block_devs': 'activate-disks',
1091
  'replace_disks': 'replace-disks',
1092
  'start': 'startup',
1093
  'stop': 'shutdown',
1094
  }
1095

    
1096
if __name__ == '__main__':
1097
  sys.exit(GenericMain(commands, aliases=aliases,
1098
                       override={"tag_type": constants.TAG_INSTANCE}))