Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 2f79bd34

History | View | Annotate | Download (43.1 kB)

1
#!/usr/bin/python
2
#
3

    
4
# Copyright (C) 2006, 2007 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
import os
28
import itertools
29
import simplejson
30
from optparse import make_option
31
from cStringIO import StringIO
32

    
33
from ganeti.cli import *
34
from ganeti import cli
35
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import utils
38
from ganeti import errors
39

    
40

    
41
_SHUTDOWN_CLUSTER = "cluster"
42
_SHUTDOWN_NODES_BOTH = "nodes"
43
_SHUTDOWN_NODES_PRI = "nodes-pri"
44
_SHUTDOWN_NODES_SEC = "nodes-sec"
45
_SHUTDOWN_INSTANCES = "instances"
46

    
47

    
48
_VALUE_TRUE = "true"
49

    
50
_LIST_DEF_FIELDS = [
51
  "name", "hypervisor", "os", "pnode", "status", "oper_ram",
52
  ]
53

    
54

    
55
def _ExpandMultiNames(mode, names):
56
  """Expand the given names using the passed mode.
57

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

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

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

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

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

    
108
  else:
109
    raise errors.OpPrereqError("Unknown mode '%s'" % mode)
110

    
111
  return inames
112

    
113

    
114
def _ConfirmOperation(inames, text):
115
  """Ask the user to confirm an operation on a list of instances.
116

    
117
  This function is used to request confirmation for doing an operation
118
  on a given list of instances.
119

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

    
124
  Returns: boolean depending on user's confirmation.
125

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

    
133
  choices = [('y', True, 'Yes, execute the %s' % text),
134
             ('n', False, 'No, abort the %s' % text)]
135

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

    
142
  choice = AskUser(ask, choices)
143
  if choice == 'v':
144
    choices.pop(1)
145
    choice = AskUser(msg + affected, choices)
146
  return choice
147

    
148

    
149
def _TransformPath(user_input):
150
  """Transform a user path into a canonical value.
151

    
152
  This function transforms the a path passed as textual information
153
  into the constants that the LU code expects.
154

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

    
169
  return result_path
170

    
171

    
172
def ListInstances(opts, args):
173
  """List instances and their properties.
174

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

    
183
  output = GetClient().QueryInstances([], selected_fields)
184

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

    
214
  if opts.human_readable:
215
    unitfields = ["be/memory", "oper_ram", "sda_size", "sdb_size"]
216
  else:
217
    unitfields = None
218

    
219
  numfields = ["be/memory", "oper_ram", "sda_size", "sdb_size", "be/vcpus",
220
               "serial_no"]
221

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

    
253
  data = GenerateTable(separator=opts.separator, headers=headers,
254
                       fields=selected_fields, unitfields=unitfields,
255
                       numfields=numfields, data=output)
256

    
257
  for line in data:
258
    ToStdout(line)
259

    
260
  return 0
261

    
262

    
263
def AddInstance(opts, args):
264
  """Add an instance to the cluster.
265

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

    
275
  """
276
  instance = args[0]
277

    
278
  (pnode, snode) = SplitNodeOption(opts.node)
279

    
280
  hypervisor = None
281
  hvparams = {}
282
  if opts.hypervisor:
283
    hypervisor, hvparams = opts.hypervisor
284

    
285
  ValidateBeParams(opts.beparams)
286

    
287
##  kernel_path = _TransformPath(opts.kernel_path)
288
##  initrd_path = _TransformPath(opts.initrd_path)
289

    
290
##  hvm_acpi = opts.hvm_acpi == _VALUE_TRUE
291
##  hvm_pae = opts.hvm_pae == _VALUE_TRUE
292

    
293
##  if ((opts.hvm_cdrom_image_path is not None) and
294
##      (opts.hvm_cdrom_image_path.lower() == constants.VALUE_NONE)):
295
##    hvm_cdrom_image_path = None
296
##  else:
297
##    hvm_cdrom_image_path = opts.hvm_cdrom_image_path
298

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

    
317
  SubmitOrSend(op, opts)
318
  return 0
319

    
320

    
321
def BatchCreate(opts, args):
322
  """Create instances on a batched base.
323

    
324
  This function reads a json with instances defined in the form:
325

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

    
336
  primary_node and secondary_node has precedence over iallocator.
337

    
338
  Args:
339
    opts: The parsed command line options
340
    args: Argument passed to the command in our case the json file
341

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

    
358
  def _PopulateWithDefaults(spec):
359
    """Returns a new hash combined with default values."""
360
    mydict = _DEFAULT_SPECS.copy()
361
    mydict.update(spec)
362
    return mydict
363

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

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

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

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

    
399
    hypervisor = None
400
    hvparams = {}
401
    if specs['hypervisor']:
402
      hypervisor, hvparams = specs['hypervisor'].iteritems()
403

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

    
424
    ToStdout("%s: %s", name, cli.SendJob([op]))
425

    
426
  return 0
427

    
428

    
429
def ReinstallInstance(opts, args):
430
  """Reinstall an instance.
431

    
432
  Args:
433
    opts - class with options as members
434
    args - list containing a single element, the instance name
435

    
436
  """
437
  instance_name = args[0]
438

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

    
443
    if not result:
444
      ToStdout("Can't get the OS list")
445
      return 1
446

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

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

    
459
    if selected == 'exit':
460
      ToStdout("User aborted reinstall, exiting")
461
      return 1
462

    
463
    os_name = selected
464
  else:
465
    os_name = opts.os
466

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

    
473
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
474
                                   os_type=os_name)
475
  SubmitOrSend(op, opts)
476

    
477
  return 0
478

    
479

    
480
def RemoveInstance(opts, args):
481
  """Remove an instance.
482

    
483
  Args:
484
    opts - class with options as members
485
    args - list containing a single element, the instance name
486

    
487
  """
488
  instance_name = args[0]
489
  force = opts.force
490

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

    
498
  op = opcodes.OpRemoveInstance(instance_name=instance_name,
499
                                ignore_failures=opts.ignore_failures)
500
  SubmitOrSend(op, opts)
501
  return 0
502

    
503

    
504
def RenameInstance(opts, args):
505
  """Rename an instance.
506

    
507
  Args:
508
    opts - class with options as members
509
    args - list containing two elements, the instance name and the new name
510

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

    
518

    
519
def ActivateDisks(opts, args):
520
  """Activate an instance's disks.
521

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

    
527
  """
528
  instance_name = args[0]
529
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
530
  disks_info = SubmitOrSend(op, opts)
531
  for host, iname, nname in disks_info:
532
    ToStdout("%s:%s:%s", host, iname, nname)
533
  return 0
534

    
535

    
536
def DeactivateDisks(opts, args):
537
  """Command-line interface for _ShutdownInstanceBlockDevices.
538

    
539
  This function takes the instance name, looks for its primary node
540
  and the tries to shutdown its block devices on that node.
541

    
542
  """
543
  instance_name = args[0]
544
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
545
  SubmitOrSend(op, opts)
546
  return 0
547

    
548

    
549
def GrowDisk(opts, args):
550
  """Command-line interface for _ShutdownInstanceBlockDevices.
551

    
552
  This function takes the instance name, looks for its primary node
553
  and the tries to shutdown its block devices on that node.
554

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

    
564

    
565
def StartupInstance(opts, args):
566
  """Startup an instance.
567

    
568
  Args:
569
    opts - class with options as members
570
    args - list containing a single element, the instance name
571

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

    
595

    
596
def RebootInstance(opts, args):
597
  """Reboot an instance
598

    
599
  Args:
600
    opts - class with options as members
601
    args - list containing a single element, the instance name
602

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

    
618
    SubmitOrSend(op, opts)
619
  return 0
620

    
621

    
622
def ShutdownInstance(opts, args):
623
  """Shutdown an instance.
624

    
625
  Args:
626
    opts - class with options as members
627
    args - list containing a single element, the instance name
628

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

    
650

    
651
def ReplaceDisks(opts, args):
652
  """Replace the disks of an instance
653

    
654
  Args:
655
    opts - class with options as members
656
    args - list with a single element, the instance name
657

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

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

    
683

    
684
def FailoverInstance(opts, args):
685
  """Failover an instance.
686

    
687
  The failover is done by shutting it down on its present node and
688
  starting it on the secondary.
689

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

    
696
  """
697
  instance_name = args[0]
698
  force = opts.force
699

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

    
707
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
708
                                  ignore_consistency=opts.ignore_consistency)
709
  SubmitOrSend(op, opts)
710
  return 0
711

    
712

    
713
def ConnectToInstanceConsole(opts, args):
714
  """Connect to the console of an instance.
715

    
716
  Args:
717
    opts - class with options as members
718
    args - list with a single element, the instance name
719

    
720
  """
721
  instance_name = args[0]
722

    
723
  op = opcodes.OpConnectConsole(instance_name=instance_name)
724
  cmd = SubmitOpCode(op)
725

    
726
  if opts.show_command:
727
    ToStdout("%s", utils.ShellQuoteArgs(cmd))
728
  else:
729
    try:
730
      os.execvp(cmd[0], cmd)
731
    finally:
732
      ToStderr("Can't run console command %s with arguments:\n'%s'",
733
               cmd[0], " ".join(cmd))
734
      os._exit(1)
735

    
736

    
737
def _FormatBlockDevInfo(buf, dev, indent_level, static):
738
  """Show block device information.
739

    
740
  This is only used by ShowInstanceConfig(), but it's too big to be
741
  left for an inline definition.
742

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

    
755
      if minor is None:
756
        minor_string = "N/A"
757
      else:
758
        minor_string = str(minor)
759

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

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

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

    
805
  if dev["children"]:
806
    for child in dev["children"]:
807
      _FormatBlockDevInfo(buf, child, indent_level+1, static)
808

    
809

    
810
def ShowInstanceConfig(opts, args):
811
  """Compute instance run-time status.
812

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

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

    
865
    else:
866
      # auto-handle other hypervisor types
867
      hvattrs = [(key, key) for key in instance["hv_actual"]]
868

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

    
886
    for device in instance["disks"]:
887
      _FormatBlockDevInfo(buf, device, 1, opts.static)
888

    
889
  ToStdout(buf.getvalue().rstrip('\n'))
890
  return retcode
891

    
892

    
893
def SetInstanceParams(opts, args):
894
  """Modifies an instance.
895

    
896
  All parameters take effect only at the next restart of the instance.
897

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

    
904
  """
905
  if not (opts.ip or opts.bridge or opts.mac or
906
          opts.hypervisor or opts.beparams):
907
    ToStderr("Please give at least one of the parameters.")
908
    return 1
909

    
910
  if constants.BE_MEMORY in opts.beparams:
911
    opts.beparams[constants.BE_MEMORY] = utils.ParseUnit(
912
      opts.beparams[constants.BE_MEMORY])
913

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

    
921
  # even if here we process the result, we allow submit only
922
  result = SubmitOrSend(op, opts)
923

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

    
932

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

    
937
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
938
                    metavar="<os>")
939

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

    
946
m_pri_node_opt = make_option("--primary", dest="multi_mode",
947
                             help="Filter by nodes (primary only)",
948
                             const=_SHUTDOWN_NODES_PRI, action="store_const")
949

    
950
m_sec_node_opt = make_option("--secondary", dest="multi_mode",
951
                             help="Filter by nodes (secondary only)",
952
                             const=_SHUTDOWN_NODES_SEC, action="store_const")
953

    
954
m_node_opt = make_option("--node", dest="multi_mode",
955
                         help="Filter by nodes (primary and secondary)",
956
                         const=_SHUTDOWN_NODES_BOTH, action="store_const")
957

    
958
m_clust_opt = make_option("--all", dest="multi_mode",
959
                          help="Select all instances in the cluster",
960
                          const=_SHUTDOWN_CLUSTER, action="store_const")
961

    
962
m_inst_opt = make_option("--instance", dest="multi_mode",
963
                         help="Filter by instance name [default]",
964
                         const=_SHUTDOWN_INSTANCES, action="store_const")
965

    
966

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

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

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

    
1193
aliases = {
1194
  'activate_block_devs': 'activate-disks',
1195
  'replace_disks': 'replace-disks',
1196
  'start': 'startup',
1197
  'stop': 'shutdown',
1198
  }
1199

    
1200
if __name__ == '__main__':
1201
  sys.exit(GenericMain(commands, aliases=aliases,
1202
                       override={"tag_type": constants.TAG_INSTANCE}))