Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 24838135

History | View | Annotate | Download (38.8 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):
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
  buf.write("%*s    primary:   " % (2*indent_level, ""))
678
  helper(buf, dev["dev_type"], dev["pstatus"])
679

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

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

    
688

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

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

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

    
740
    else:
741
      # auto-handle other hypervisor types
742
      hvattrs = [(key, key) for key in instance["hv_actual"]]
743

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

    
759
    for device in instance["disks"]:
760
      _FormatBlockDevInfo(buf, device, 1)
761

    
762
  logger.ToStdout(buf.getvalue().rstrip('\n'))
763
  return retcode
764

    
765

    
766
def SetInstanceParams(opts, args):
767
  """Modifies an instance.
768

    
769
  All parameters take effect only at the next restart of the instance.
770

    
771
  Args:
772
    opts - class with options as members
773
    args - list with a single element, the instance name
774
  Opts used:
775
    memory - the new memory size
776
    vcpus - the new number of cpus
777
    mac - the new MAC address of the instance
778

    
779
  """
780
  if not (opts.mem or opts.vcpus or opts.ip or opts.bridge or opts.mac or
781
          opts.hypervisor):
782
    logger.ToStdout("Please give at least one of the parameters.")
783
    return 1
784

    
785
  op = opcodes.OpSetInstanceParams(instance_name=args[0], mem=opts.mem,
786
                                   vcpus=opts.vcpus, ip=opts.ip,
787
                                   bridge=opts.bridge, mac=opts.mac,
788
                                   hvparams=opts.hypervisor,
789
                                   force=opts.force)
790

    
791
  # even if here we process the result, we allow submit only
792
  result = SubmitOrSend(op, opts)
793

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

    
802

    
803
# options used in more than one cmd
804
node_opt = make_option("-n", "--node", dest="node", help="Target node",
805
                       metavar="<node>")
806

    
807
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
808
                    metavar="<os>")
809

    
810
# multi-instance selection options
811
m_force_multi = make_option("--force-multiple", dest="force_multi",
812
                            help="Do not ask for confirmation when more than"
813
                            " one instance is affected",
814
                            action="store_true", default=False)
815

    
816
m_pri_node_opt = make_option("--primary", dest="multi_mode",
817
                             help="Filter by nodes (primary only)",
818
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
819

    
820
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
821
                             help="Filter by nodes (secondary only)",
822
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
823

    
824
m_node_opt = make_option("--node", dest="multi_mode",
825
                         help="Filter by nodes (primary and secondary)",
826
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
827

    
828
m_clust_opt = make_option("--all", dest="multi_mode",
829
                          help="Select all instances in the cluster",
830
                          const=_SHUTDOWN_CLUSTER, action="store_const")
831

    
832
m_inst_opt = make_option("--instance", dest="multi_mode",
833
                         help="Filter by instance name [default]",
834
                         const=_SHUTDOWN_INSTANCES, action="store_const")
835

    
836

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

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

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

    
1066
aliases = {
1067
  'activate_block_devs': 'activate-disks',
1068
  'replace_disks': 'replace-disks',
1069
  'start': 'startup',
1070
  'stop': 'shutdown',
1071
  }
1072

    
1073
if __name__ == '__main__':
1074
  sys.exit(GenericMain(commands, aliases=aliases,
1075
                       override={"tag_type": constants.TAG_INSTANCE}))