Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 3a24c527

History | View | Annotate | Download (43 kB)

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

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

    
21

    
22
import sys
23
import os
24
import itertools
25
import simplejson
26
from optparse import make_option
27
from cStringIO import StringIO
28

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

    
36

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

    
43

    
44
_VALUE_TRUE = "true"
45

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

    
50

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

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

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

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

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

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

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

    
107
  return inames
108

    
109

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

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

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

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

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

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

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

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

    
144

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

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

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

    
165
  return result_path
166

    
167

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

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

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

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

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

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

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

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

    
253
  for line in data:
254
    ToStdout(line)
255

    
256
  return 0
257

    
258

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

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

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

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

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

    
281
  ValidateBeParams(opts.beparams)
282

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

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

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

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

    
313
  SubmitOrSend(op, opts)
314
  return 0
315

    
316

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

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

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

    
332
  primary_node and secondary_node has precedence over iallocator.
333

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

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

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

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

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

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

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

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

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

    
420
    ToStdout("%s: %s", name, cli.SendJob([op]))
421

    
422
  return 0
423

    
424

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

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

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

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

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

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

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

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

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

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

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

    
473
  return 0
474

    
475

    
476
def RemoveInstance(opts, args):
477
  """Remove 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
  instance_name = args[0]
485
  force = opts.force
486

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

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

    
499

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

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

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

    
514

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

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

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

    
531

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

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

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

    
544

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

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

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

    
560

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

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

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

    
591

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

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

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

    
614
    SubmitOrSend(op, opts)
615
  return 0
616

    
617

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

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

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

    
646

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

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

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

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

    
679

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

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

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

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

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

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

    
708

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

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

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

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

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

    
732

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

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

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

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

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

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

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

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

    
805

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

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

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

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

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

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

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

    
888

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

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

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

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

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

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

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

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

    
928

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

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

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

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

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

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

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

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

    
962

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

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

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

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

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