Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 57821cac

History | View | Annotate | Download (39.3 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", "admin_ram": "Configured_memory",
185
      "oper_ram": "Memory", "disk_template": "Disk_template",
186
      "ip": "IP_address", "mac": "MAC_address",
187
      "bridge": "Bridge", "vcpus": "VCPUs",
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
      }
203
  else:
204
    headers = None
205

    
206
  if opts.human_readable:
207
    unitfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size"]
208
  else:
209
    unitfields = None
210

    
211
  numfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size", "vcpus",
212
               "serial_no"]
213

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

    
245
  data = GenerateTable(separator=opts.separator, headers=headers,
246
                       fields=selected_fields, unitfields=unitfields,
247
                       numfields=numfields, data=output)
248

    
249
  for line in data:
250
    logger.ToStdout(line)
251

    
252
  return 0
253

    
254

    
255
def AddInstance(opts, args):
256
  """Add an instance to the cluster.
257

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

    
267
  """
268
  instance = args[0]
269

    
270
  (pnode, snode) = SplitNodeOption(opts.node)
271

    
272
  hypervisor = None
273
  hvparams = {}
274
  if opts.hypervisor:
275
    hypervisor, hvparams = opts.hypervisor
276

    
277
##  kernel_path = _TransformPath(opts.kernel_path)
278
##  initrd_path = _TransformPath(opts.initrd_path)
279

    
280
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
281
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
282

    
283
##  if ((opts.hvm_cdrom_image_path is not None) and
284
##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
285
##    hvm_cdrom_image_path = None
286
##  else:
287
##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
288

    
289
  op = opcodes.OpCreateInstance(instance_name=instance, mem_size=opts.mem,
290
                                disk_size=opts.size, swap_size=opts.swap,
291
                                disk_template=opts.disk_template,
292
                                mode=constants.INSTANCE_CREATE,
293
                                os_type=opts.os, pnode=pnode,
294
                                snode=snode, vcpus=opts.vcpus,
295
                                ip=opts.ip, bridge=opts.bridge,
296
                                start=opts.start, ip_check=opts.ip_check,
297
                                wait_for_sync=opts.wait_for_sync,
298
                                mac=opts.mac,
299
                                hypervisor=hypervisor,
300
                                hvparams=hvparams,
301
                                iallocator=opts.iallocator,
302
                                file_storage_dir=opts.file_storage_dir,
303
                                file_driver=opts.file_driver,
304
                                )
305

    
306
  SubmitOrSend(op, opts)
307
  return 0
308

    
309

    
310
def ReinstallInstance(opts, args):
311
  """Reinstall an instance.
312

    
313
  Args:
314
    opts - class with options as members
315
    args - list containing a single element, the instance name
316

    
317
  """
318
  instance_name = args[0]
319

    
320
  if opts.select_os is True:
321
    op = opcodes.OpDiagnoseOS(output_fields=["name", "valid"], names=[])
322
    result = SubmitOpCode(op)
323

    
324
    if not result:
325
      logger.ToStdout("Can't get the OS list")
326
      return 1
327

    
328
    logger.ToStdout("Available OS templates:")
329
    number = 0
330
    choices = []
331
    for entry in result:
332
      logger.ToStdout("%3s: %s" % (number, entry[0]))
333
      choices.append(("%s" % number, entry[0], entry[0]))
334
      number = number + 1
335

    
336
    choices.append(('x', 'exit', 'Exit gnt-instance reinstall'))
337
    selected = AskUser("Enter OS template name or number (or x to abort):",
338
                       choices)
339

    
340
    if selected == 'exit':
341
      logger.ToStdout("User aborted reinstall, exiting")
342
      return 1
343

    
344
    os = selected
345
  else:
346
    os = opts.os
347

    
348
  if not opts.force:
349
    usertext = ("This will reinstall the instance %s and remove"
350
                " all data. Continue?") % instance_name
351
    if not AskUser(usertext):
352
      return 1
353

    
354
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
355
                                   os_type=os)
356
  SubmitOrSend(op, opts)
357

    
358
  return 0
359

    
360

    
361
def RemoveInstance(opts, args):
362
  """Remove an instance.
363

    
364
  Args:
365
    opts - class with options as members
366
    args - list containing a single element, the instance name
367

    
368
  """
369
  instance_name = args[0]
370
  force = opts.force
371

    
372
  if not force:
373
    usertext = ("This will remove the volumes of the instance %s"
374
                " (including mirrors), thus removing all the data"
375
                " of the instance. Continue?") % instance_name
376
    if not AskUser(usertext):
377
      return 1
378

    
379
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
380
                                ignore_failures=opts.ignore_failures)
381
  SubmitOrSend(op, opts)
382
  return 0
383

    
384

    
385
def RenameInstance(opts, args):
386
  """Rename an instance.
387

    
388
  Args:
389
    opts - class with options as members
390
    args - list containing two elements, the instance name and the new name
391

    
392
  """
393
  op = opcodes.OpRenameInstance(instance_name=args[0],
394
                                new_name=args[1],
395
                                ignore_ip=opts.ignore_ip)
396
  SubmitOrSend(op, opts)
397
  return 0
398

    
399

    
400
def ActivateDisks(opts, args):
401
  """Activate an instance's disks.
402

    
403
  This serves two purposes:
404
    - it allows one (as long as the instance is not running) to mount
405
    the disks and modify them from the node
406
    - it repairs inactive secondary drbds
407

    
408
  """
409
  instance_name = args[0]
410
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
411
  disks_info = SubmitOrSend(op, opts)
412
  for host, iname, nname in disks_info:
413
    print "%s:%s:%s" % (host, iname, nname)
414
  return 0
415

    
416

    
417
def DeactivateDisks(opts, args):
418
  """Command-line interface for _ShutdownInstanceBlockDevices.
419

    
420
  This function takes the instance name, looks for its primary node
421
  and the tries to shutdown its block devices on that node.
422

    
423
  """
424
  instance_name = args[0]
425
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
426
  SubmitOrSend(op, opts)
427
  return 0
428

    
429

    
430
def GrowDisk(opts, args):
431
  """Command-line interface for _ShutdownInstanceBlockDevices.
432

    
433
  This function takes the instance name, looks for its primary node
434
  and the tries to shutdown its block devices on that node.
435

    
436
  """
437
  instance = args[0]
438
  disk = args[1]
439
  amount = utils.ParseUnit(args[2])
440
  op = opcodes.OpGrowDisk(instance_name=instance, disk=disk, amount=amount)
441
  SubmitOrSend(op, opts)
442
  return 0
443

    
444

    
445
def StartupInstance(opts, args):
446
  """Startup an instance.
447

    
448
  Args:
449
    opts - class with options as members
450
    args - list containing a single element, the instance name
451

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

    
475

    
476
def RebootInstance(opts, args):
477
  """Reboot an instance
478

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

    
483
  """
484
  if opts.multi_mode is None:
485
    opts.multi_mode = _SHUTDOWN_INSTANCES
486
  inames = _ExpandMultiNames(opts.multi_mode, args)
487
  if not inames:
488
    raise errors.OpPrereqError("Selection filter does not match any instances")
489
  multi_on = opts.multi_mode != _SHUTDOWN_INSTANCES or len(inames) > 1
490
  if not (opts.force_multi or not multi_on
491
          or _ConfirmOperation(inames, "reboot")):
492
    return 1
493
  for name in inames:
494
    op = opcodes.OpRebootInstance(instance_name=name,
495
                                  reboot_type=opts.reboot_type,
496
                                  ignore_secondaries=opts.ignore_secondaries)
497

    
498
    SubmitOrSend(op, opts)
499
  return 0
500

    
501

    
502
def ShutdownInstance(opts, args):
503
  """Shutdown an instance.
504

    
505
  Args:
506
    opts - class with options as members
507
    args - list containing a single element, the instance name
508

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

    
530

    
531
def ReplaceDisks(opts, args):
532
  """Replace the disks of an instance
533

    
534
  Args:
535
    opts - class with options as members
536
    args - list with a single element, the instance name
537

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

    
557
  op = opcodes.OpReplaceDisks(instance_name=args[0], disks=disks,
558
                              remote_node=new_2ndary, mode=mode,
559
                              iallocator=iallocator)
560
  SubmitOrSend(op, opts)
561
  return 0
562

    
563

    
564
def FailoverInstance(opts, args):
565
  """Failover an instance.
566

    
567
  The failover is done by shutting it down on its present node and
568
  starting it on the secondary.
569

    
570
  Args:
571
    opts - class with options as members
572
    args - list with a single element, the instance name
573
  Opts used:
574
    force - whether to failover without asking questions.
575

    
576
  """
577
  instance_name = args[0]
578
  force = opts.force
579

    
580
  if not force:
581
    usertext = ("Failover will happen to image %s."
582
                " This requires a shutdown of the instance. Continue?" %
583
                (instance_name,))
584
    if not AskUser(usertext):
585
      return 1
586

    
587
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
588
                                  ignore_consistency=opts.ignore_consistency)
589
  SubmitOrSend(op, opts)
590
  return 0
591

    
592

    
593
def ConnectToInstanceConsole(opts, args):
594
  """Connect to the console of an instance.
595

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

    
600
  """
601
  instance_name = args[0]
602

    
603
  op = opcodes.OpConnectConsole(instance_name=instance_name)
604
  cmd = SubmitOpCode(op)
605

    
606
  if opts.show_command:
607
    print utils.ShellQuoteArgs(cmd)
608
  else:
609
    try:
610
      os.execvp(cmd[0], cmd)
611
    finally:
612
      sys.stderr.write("Can't run console command %s with arguments:\n'%s'" %
613
                       (cmd, " ".join(argv)))
614
      os._exit(1)
615

    
616

    
617
def _FormatBlockDevInfo(buf, dev, indent_level, static):
618
  """Show block device information.
619

    
620
  This is only used by ShowInstanceConfig(), but it's too big to be
621
  left for an inline definition.
622

    
623
  """
624
  def helper(buf, dtype, status):
625
    """Format one line for physical device status."""
626
    if not status:
627
      buf.write("not active\n")
628
    else:
629
      (path, major, minor, syncp, estt, degr, ldisk) = status
630
      if major is None:
631
        major_string = "N/A"
632
      else:
633
        major_string = str(major)
634

    
635
      if minor is None:
636
        minor_string = "N/A"
637
      else:
638
        minor_string = str(minor)
639

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

    
667
  if dev["iv_name"] is not None:
668
    data = "  - %s, " % dev["iv_name"]
669
  else:
670
    data = "  - "
671
  data += "type: %s" % dev["dev_type"]
672
  if dev["logical_id"] is not None:
673
    data += ", logical_id: %s" % (dev["logical_id"],)
674
  elif dev["physical_id"] is not None:
675
    data += ", physical_id: %s" % (dev["physical_id"],)
676
  buf.write("%*s%s\n" % (2*indent_level, "", data))
677
  if not static:
678
    buf.write("%*s    primary:   " % (2*indent_level, ""))
679
    helper(buf, dev["dev_type"], dev["pstatus"])
680

    
681
  if dev["sstatus"] and not static:
682
    buf.write("%*s    secondary: " % (2*indent_level, ""))
683
    helper(buf, dev["dev_type"], dev["sstatus"])
684

    
685
  if dev["children"]:
686
    for child in dev["children"]:
687
      _FormatBlockDevInfo(buf, child, indent_level+1, static)
688

    
689

    
690
def ShowInstanceConfig(opts, args):
691
  """Compute instance run-time status.
692

    
693
  """
694
  retcode = 0
695
  op = opcodes.OpQueryInstanceData(instances=args, static=opts.static)
696
  result = SubmitOpCode(op)
697
  if not result:
698
    logger.ToStdout("No instances.")
699
    return 1
700

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

    
745
    else:
746
      # auto-handle other hypervisor types
747
      hvattrs = [(key, key) for key in instance["hv_actual"]]
748

    
749
    for key, desc in hvattrs:
750
      if key in instance["hv_instance"]:
751
        val = instance["hv_instance"][key]
752
      else:
753
        val = "default (%s)" % instance["hv_actual"][key]
754
      buf.write("    - %s: %s\n" % (desc, val))
755
    buf.write("  Hardware:\n")
756
    buf.write("    - VCPUs: %d\n" % instance["vcpus"])
757
    buf.write("    - memory: %dMiB\n" % instance["memory"])
758
    buf.write("    - NICs: %s\n" %
759
              ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
760
                         (mac, ip, bridge)
761
                         for mac, ip, bridge in instance["nics"]]))
762
    buf.write("  Block devices:\n")
763

    
764
    for device in instance["disks"]:
765
      _FormatBlockDevInfo(buf, device, 1, opts.static)
766

    
767
  logger.ToStdout(buf.getvalue().rstrip('\n'))
768
  return retcode
769

    
770

    
771
def SetInstanceParams(opts, args):
772
  """Modifies an instance.
773

    
774
  All parameters take effect only at the next restart of the instance.
775

    
776
  Args:
777
    opts - class with options as members
778
    args - list with a single element, the instance name
779
  Opts used:
780
    memory - the new memory size
781
    vcpus - the new number of cpus
782
    mac - the new MAC address of the instance
783

    
784
  """
785
  if not (opts.mem or opts.vcpus or opts.ip or opts.bridge or opts.mac or
786
          opts.hypervisor):
787
    logger.ToStdout("Please give at least one of the parameters.")
788
    return 1
789

    
790
  op = opcodes.OpSetInstanceParams(instance_name=args[0], mem=opts.mem,
791
                                   vcpus=opts.vcpus, ip=opts.ip,
792
                                   bridge=opts.bridge, mac=opts.mac,
793
                                   hvparams=opts.hypervisor,
794
                                   force=opts.force)
795

    
796
  # even if here we process the result, we allow submit only
797
  result = SubmitOrSend(op, opts)
798

    
799
  if result:
800
    logger.ToStdout("Modified instance %s" % args[0])
801
    for param, data in result:
802
      logger.ToStdout(" - %-5s -> %s" % (param, data))
803
    logger.ToStdout("Please don't forget that these parameters take effect"
804
                    " only at the next start of the instance.")
805
  return 0
806

    
807

    
808
# options used in more than one cmd
809
node_opt = make_option("-n", "--node", dest="node", help="Target node",
810
                       metavar="<node>")
811

    
812
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
813
                    metavar="<os>")
814

    
815
# multi-instance selection options
816
m_force_multi = make_option("--force-multiple", dest="force_multi",
817
                            help="Do not ask for confirmation when more than"
818
                            " one instance is affected",
819
                            action="store_true", default=False)
820

    
821
m_pri_node_opt = make_option("--primary", dest="multi_mode",
822
                             help="Filter by nodes (primary only)",
823
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
824

    
825
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
826
                             help="Filter by nodes (secondary only)",
827
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
828

    
829
m_node_opt = make_option("--node", dest="multi_mode",
830
                         help="Filter by nodes (primary and secondary)",
831
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
832

    
833
m_clust_opt = make_option("--all", dest="multi_mode",
834
                          help="Select all instances in the cluster",
835
                          const=_SHUTDOWN_CLUSTER, action="store_const")
836

    
837
m_inst_opt = make_option("--instance", dest="multi_mode",
838
                         help="Filter by instance name [default]",
839
                         const=_SHUTDOWN_INSTANCES, action="store_const")
840

    
841

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

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

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

    
1076
aliases = {
1077
  'activate_block_devs': 'activate-disks',
1078
  'replace_disks': 'replace-disks',
1079
  'start': 'startup',
1080
  'stop': 'shutdown',
1081
  }
1082

    
1083
if __name__ == '__main__':
1084
  sys.exit(GenericMain(commands, aliases=aliases,
1085
                       override={"tag_type": constants.TAG_INSTANCE}))