Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 069dcc86

History | View | Annotate | Download (19.2 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
from optparse import make_option
25
from cStringIO import StringIO
26

    
27
from ganeti.cli import *
28
from ganeti import opcodes
29
from ganeti import logger
30
from ganeti import constants
31
from ganeti import utils
32

    
33

    
34
def ListInstances(opts, args):
35
  """List nodes and their properties.
36

    
37
  """
38
  if opts.output is None:
39
    selected_fields = ["name", "os", "pnode", "admin_state",
40
                       "oper_state", "oper_ram"]
41
  else:
42
    selected_fields = opts.output.split(",")
43

    
44
  op = opcodes.OpQueryInstances(output_fields=selected_fields, names=[])
45
  output = SubmitOpCode(op)
46

    
47
  if not opts.no_headers:
48
    headers = {"name": "Instance", "os": "OS", "pnode": "Primary_node",
49
               "snodes": "Secondary_Nodes", "admin_state": "Autostart",
50
               "oper_state": "Status", "admin_ram": "Configured_memory",
51
               "oper_ram": "Memory", "disk_template": "Disk_template",
52
               "ip": "IP Address", "mac": "MAC Address",
53
               "bridge": "Bridge",
54
               "sda_size": "Disk/0", "sdb_size": "Disk/1"}
55
  else:
56
    headers = None
57

    
58
  if opts.human_readable:
59
    unitfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size"]
60
  else:
61
    unitfields = None
62

    
63
  numfields = ["admin_ram", "oper_ram", "sda_size", "sdb_size"]
64

    
65
  # change raw values to nicer strings
66
  for row in output:
67
    for idx, field in enumerate(selected_fields):
68
      val = row[idx]
69
      if field == "snodes":
70
        val = ",".join(val) or "-"
71
      elif field == "admin_state":
72
        if val:
73
          val = "yes"
74
        else:
75
          val = "no"
76
      elif field == "oper_state":
77
        if val is None:
78
          val = "(node down)"
79
        elif val: # True
80
          val = "running"
81
        else:
82
          val = "stopped"
83
      elif field == "oper_ram":
84
        if val is None:
85
          val = "(node down)"
86
      elif field == "sda_size" or field == "sdb_size":
87
        if val is None:
88
          val = "N/A"
89
      row[idx] = str(val)
90

    
91
  data = GenerateTable(separator=opts.separator, headers=headers,
92
                       fields=selected_fields, unitfields=unitfields,
93
                       numfields=numfields, data=output)
94

    
95
  for line in data:
96
    logger.ToStdout(line)
97

    
98
  return 0
99

    
100

    
101
def AddInstance(opts, args):
102
  """Add an instance to the cluster.
103

    
104
  Args:
105
    opts - class with options as members
106
    args - list with a single element, the instance name
107
  Opts used:
108
    mem - amount of memory to allocate to instance (MiB)
109
    size - amount of disk space to allocate to instance (MiB)
110
    os - which OS to run on instance
111
    node - node to run new instance on
112

    
113
  """
114
  instance = args[0]
115

    
116
  op = opcodes.OpCreateInstance(instance_name=instance, mem_size=opts.mem,
117
                                disk_size=opts.size, swap_size=opts.swap,
118
                                disk_template=opts.disk_template,
119
                                mode=constants.INSTANCE_CREATE,
120
                                os_type=opts.os, pnode=opts.node,
121
                                snode=opts.snode, vcpus=opts.vcpus,
122
                                ip=opts.ip, bridge=opts.bridge, start=True,
123
                                wait_for_sync=opts.wait_for_sync)
124
  SubmitOpCode(op)
125
  return 0
126

    
127

    
128
def ReinstallInstance(opts, args):
129
  """Reinstall an instance.
130

    
131
  Args:
132
    opts - class with options as members
133
    args - list containing a single element, the instance name
134

    
135
  """
136
  instance_name = args[0]
137

    
138
  if not opts.force:
139
    usertext = ("This will reinstall the instance %s and remove "
140
                "all data. Continue?") % instance_name
141
    if not opts._ask_user(usertext):
142
      return 1
143

    
144
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
145
                                   os_type=opts.os)
146
  SubmitOpCode(op)
147

    
148
  return 0
149

    
150

    
151
def RemoveInstance(opts, args):
152
  """Remove an instance.
153

    
154
  Args:
155
    opts - class with options as members
156
    args - list containing a single element, the instance name
157

    
158
  """
159
  instance_name = args[0]
160
  force = opts.force
161

    
162
  if not force:
163
    usertext = ("This will remove the volumes of the instance %s"
164
                " (including mirrors), thus removing all the data"
165
                " of the instance. Continue?") % instance_name
166
    if not opts._ask_user(usertext):
167
      return 1
168

    
169
  op = opcodes.OpRemoveInstance(instance_name=instance_name)
170
  SubmitOpCode(op)
171
  return 0
172

    
173

    
174
def ActivateDisks(opts, args):
175
  """Activate an instance's disks.
176

    
177
  This serves two purposes:
178
    - it allows one (as long as the instance is not running) to mount
179
    the disks and modify them from the node
180
    - it repairs inactive secondary drbds
181

    
182
  """
183
  instance_name = args[0]
184
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
185
  disks_info = SubmitOpCode(op)
186
  for host, iname, nname in disks_info:
187
    print "%s:%s:%s" % (host, iname, nname)
188
  return 0
189

    
190

    
191
def DeactivateDisks(opts, args):
192
  """Command-line interface for _ShutdownInstanceBlockDevices.
193

    
194
  This function takes the instance name, looks for its primary node
195
  and the tries to shutdown its block devices on that node.
196

    
197
  """
198
  instance_name = args[0]
199
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
200
  SubmitOpCode(op)
201
  return 0
202

    
203

    
204
def StartupInstance(opts, args):
205
  """Shutdown an instance.
206

    
207
  Args:
208
    opts - class with options as members
209
    args - list containing a single element, the instance name
210

    
211
  """
212
  instance_name = args[0]
213
  op = opcodes.OpStartupInstance(instance_name=instance_name, force=opts.force,
214
                                 extra_args=opts.extra_args)
215
  SubmitOpCode(op)
216
  return 0
217

    
218

    
219
def ShutdownInstance(opts, args):
220
  """Shutdown an instance.
221

    
222
  Args:
223
    opts - class with options as members
224
    args - list containing a single element, the instance name
225

    
226
  """
227
  instance_name = args[0]
228
  op = opcodes.OpShutdownInstance(instance_name=instance_name)
229
  SubmitOpCode(op)
230
  return 0
231

    
232

    
233
def AddMDDRBDComponent(opts, args):
234
  """Add a new component to a remote_raid1 disk.
235

    
236
  Args:
237
    opts - class with options as members
238
    args - list with a single element, the instance name
239

    
240
  """
241
  op = opcodes.OpAddMDDRBDComponent(instance_name=args[0],
242
                                    disk_name=opts.disk,
243
                                    remote_node=opts.node)
244
  SubmitOpCode(op)
245
  return 0
246

    
247

    
248
def RemoveMDDRBDComponent(opts, args):
249
  """Remove a component from a remote_raid1 disk.
250

    
251
  Args:
252
    opts - class with options as members
253
    args - list with a single element, the instance name
254

    
255
  """
256
  op = opcodes.OpRemoveMDDRBDComponent(instance_name=args[0],
257
                                       disk_name=opts.disk,
258
                                       disk_id=opts.port)
259
  SubmitOpCode(op)
260
  return 0
261

    
262

    
263
def ReplaceDisks(opts, args):
264
  """Replace the disks of an instance
265

    
266
  Args:
267
    opts - class with options as members
268
    args - list with a single element, the instance name
269

    
270
  """
271
  instance_name = args[0]
272
  new_secondary = opts.new_secondary
273
  op = opcodes.OpReplaceDisks(instance_name=args[0],
274
                              remote_node=opts.new_secondary)
275
  SubmitOpCode(op)
276
  return 0
277

    
278

    
279
def FailoverInstance(opts, args):
280
  """Failover an instance.
281

    
282
  The failover is done by shutting it down on its present node and
283
  starting it on the secondary.
284

    
285
  Args:
286
    opts - class with options as members
287
    args - list with a single element, the instance name
288
  Opts used:
289
    force - whether to failover without asking questions.
290

    
291
  """
292
  instance_name = args[0]
293
  force = opts.force
294

    
295
  if not force:
296
    usertext = ("Failover will happen to image %s."
297
                " This requires a shutdown of the instance. Continue?" %
298
                (instance_name,))
299
    if not opts._ask_user(usertext):
300
      return 1
301

    
302
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
303
                                  ignore_consistency=opts.ignore_consistency)
304
  SubmitOpCode(op)
305
  return 0
306

    
307

    
308
def ConnectToInstanceConsole(opts, args):
309
  """Connect to the console of an instance.
310

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

    
315
  """
316
  instance_name = args[0]
317

    
318
  op = opcodes.OpConnectConsole(instance_name=instance_name)
319
  cmd, argv = SubmitOpCode(op)
320
  # drop lock and exec so other commands can run while we have console
321
  utils.Unlock("cmd")
322
  try:
323
    os.execvp(cmd, argv)
324
  finally:
325
    sys.stderr.write("Can't run console command %s with arguments:\n'%s'" %
326
                     (cmd, " ".join(argv)))
327
    os._exit(1)
328

    
329

    
330
def _FormatBlockDevInfo(buf, dev, indent_level):
331
  """Show block device information.
332

    
333
  This is only used by ShowInstanceConfig(), but it's too big to be
334
  left for an inline definition.
335

    
336
  """
337
  def helper(buf, dtype, status):
338
    """Format one line for phsyical device status."""
339
    if not status:
340
      buf.write("not active\n")
341
    else:
342
      (path, major, minor, syncp, estt, degr) = status
343
      buf.write("%s (%d:%d)" % (path, major, minor))
344
      if dtype in ("md_raid1", "drbd"):
345
        if syncp is not None:
346
          sync_text = "*RECOVERING* %5.2f%%," % syncp
347
          if estt:
348
            sync_text += " ETA %ds" % estt
349
          else:
350
            sync_text += " ETA unknown"
351
        else:
352
          sync_text = "in sync"
353
        if degr:
354
          degr_text = "*DEGRADED*"
355
        else:
356
          degr_text = "ok"
357
        buf.write(" %s, status %s" % (sync_text, degr_text))
358
      buf.write("\n")
359

    
360
  if dev["iv_name"] is not None:
361
    data = "  - %s, " % dev["iv_name"]
362
  else:
363
    data = "  - "
364
  data += "type: %s" % dev["dev_type"]
365
  if dev["logical_id"] is not None:
366
    data += ", logical_id: %s" % (dev["logical_id"],)
367
  elif dev["physical_id"] is not None:
368
    data += ", physical_id: %s" % (dev["physical_id"],)
369
  buf.write("%*s%s\n" % (2*indent_level, "", data))
370
  buf.write("%*s    primary:   " % (2*indent_level, ""))
371
  helper(buf, dev["dev_type"], dev["pstatus"])
372

    
373
  if dev["sstatus"]:
374
    buf.write("%*s    secondary: " % (2*indent_level, ""))
375
    helper(buf, dev["dev_type"], dev["sstatus"])
376

    
377
  if dev["children"]:
378
    for child in dev["children"]:
379
      _FormatBlockDevInfo(buf, child, indent_level+1)
380

    
381

    
382
def ShowInstanceConfig(opts, args):
383
  """Compute instance run-time status.
384

    
385
  """
386
  retcode = 0
387
  op = opcodes.OpQueryInstanceData(instances=args)
388
  result = SubmitOpCode(op)
389

    
390
  if not result:
391
    logger.ToStdout("No instances.")
392
    return 1
393

    
394
  buf = StringIO()
395
  retcode = 0
396
  for instance_name in result:
397
    instance = result[instance_name]
398
    buf.write("Instance name: %s\n" % instance["name"])
399
    buf.write("State: configured to be %s, actual state is %s\n" %
400
              (instance["config_state"], instance["run_state"]))
401
    buf.write("  Nodes:\n")
402
    buf.write("    - primary: %s\n" % instance["pnode"])
403
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
404
    buf.write("  Operating system: %s\n" % instance["os"])
405
    buf.write("  Hardware:\n")
406
    buf.write("    - memory: %dMiB\n" % instance["memory"])
407
    buf.write("    - NICs: %s\n" %
408
        ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
409
                   (mac, ip, bridge)
410
                     for mac, ip, bridge in instance["nics"]]))
411
    buf.write("  Block devices:\n")
412

    
413
    for device in instance["disks"]:
414
      _FormatBlockDevInfo(buf, device, 1)
415

    
416
  logger.ToStdout(buf.getvalue().rstrip('\n'))
417
  return retcode
418

    
419

    
420
def SetInstanceParms(opts, args):
421
  """Modifies an instance.
422

    
423
  All parameters take effect only at the next restart of the instance.
424

    
425
  Args:
426
    opts - class with options as members
427
    args - list with a single element, the instance name
428
  Opts used:
429
    memory - the new memory size
430
    vcpus - the new number of cpus
431

    
432
  """
433
  if not opts.mem and not opts.vcpus and not opts.ip and not opts.bridge:
434
    logger.ToStdout("Please give at least one of the parameters.")
435
    return 1
436

    
437
  op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem,
438
                                  vcpus=opts.vcpus, ip=opts.ip,
439
                                  bridge=opts.bridge)
440
  result = SubmitOpCode(op)
441

    
442
  if result:
443
    logger.ToStdout("Modified instance %s" % args[0])
444
    for param, data in result:
445
      logger.ToStdout(" - %-5s -> %s" % (param, data))
446
    logger.ToStdout("Please don't forget that these parameters take effect"
447
                    " only at the next start of the instance.")
448
  return 0
449

    
450

    
451
# options used in more than one cmd
452
node_opt = make_option("-n", "--node", dest="node", help="Target node",
453
                       metavar="<node>")
454

    
455
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
456
                    metavar="<os>")
457

    
458
# this is defined separately due to readability only
459
add_opts = [
460
  DEBUG_OPT,
461
  node_opt,
462
  cli_option("-s", "--os-size", dest="size", help="Disk size, in MiB unless"
463
             " a suffix is used",
464
             default=20 * 1024, type="unit", metavar="<size>"),
465
  cli_option("--swap-size", dest="swap", help="Swap size, in MiB unless a"
466
             " suffix is used",
467
             default=4 * 1024, type="unit", metavar="<size>"),
468
  os_opt,
469
  cli_option("-m", "--memory", dest="mem", help="Memory size (in MiB)",
470
              default=128, type="unit", metavar="<mem>"),
471
  make_option("-p", "--cpu", dest="vcpus", help="Number of virtual CPUs",
472
              default=1, type="int", metavar="<PROC>"),
473
  make_option("-t", "--disk-template", dest="disk_template",
474
              help="Custom disk setup (diskless, plain, local_raid1 or"
475
              " remote_raid1)", default=None, metavar="TEMPL"),
476
  make_option("-i", "--ip", dest="ip",
477
              help="IP address ('none' [default], 'auto', or specify address)",
478
              default='none', type="string", metavar="<ADDRESS>"),
479
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
480
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
481
  make_option("--secondary-node", dest="snode",
482
              help="Secondary node for remote_raid1 disk layout",
483
              metavar="<node>"),
484
  make_option("-b", "--bridge", dest="bridge",
485
              help="Bridge to connect this instance to",
486
              default=None, metavar="<bridge>")
487
  ]
488

    
489
commands = {
490
  'add': (AddInstance, ARGS_ONE, add_opts,
491
          "[opts...] <name>",
492
          "Creates and adds a new instance to the cluster"),
493
  'add-mirror': (AddMDDRBDComponent, ARGS_ONE,
494
                [DEBUG_OPT, node_opt,
495
                 make_option("-b", "--disk", dest="disk", metavar="sdX",
496
                             help=("The name of the instance disk for which to"
497
                                   " add the mirror"))],
498
                "-n node -b disk <instance>",
499
                "Creates a new mirror for the instance"),
500
  'console': (ConnectToInstanceConsole, ARGS_ONE, [DEBUG_OPT],
501
              "<instance>",
502
              "Opens a console on the specified instance"),
503
  'failover': (FailoverInstance, ARGS_ONE,
504
               [DEBUG_OPT, FORCE_OPT,
505
                make_option("--ignore-consistency", dest="ignore_consistency",
506
                            action="store_true", default=False,
507
                            help="Ignore the consistency of the disks on"
508
                            " the secondary"),
509
                ],
510
               "[-f] <instance>",
511
               "Stops the instance and starts it on the backup node, using"
512
               " the remote mirror (only for instances of type remote_raid1)"),
513
  'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT], "[<instance>...]",
514
           "Show information on the specified instance"),
515
  'list': (ListInstances, ARGS_NONE,
516
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
517
           "", "Lists the instances and their status"),
518
  'reinstall': (ReinstallInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT, os_opt],
519
                "[-f] <instance>", "Reinstall the instance"),
520
  'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
521
             "[-f] <instance>", "Shuts down the instance and removes it"),
522
  'remove-mirror': (RemoveMDDRBDComponent, ARGS_ONE,
523
                   [DEBUG_OPT, node_opt,
524
                    make_option("-b", "--disk", dest="disk", metavar="sdX",
525
                                help=("The name of the instance disk"
526
                                      " for which to add the mirror")),
527
                    make_option("-p", "--port", dest="port", metavar="PORT",
528
                                help=("The port of the drbd device"
529
                                      " which to remove from the mirror"),
530
                                type="int"),
531
                    ],
532
                   "-b disk -p port <instance>",
533
                   "Removes a mirror from the instance"),
534
  'replace-disks': (ReplaceDisks, ARGS_ONE,
535
                    [DEBUG_OPT,
536
                     make_option("-n", "--new-secondary", dest="new_secondary",
537
                                 metavar="NODE",
538
                                 help=("New secondary node (if you want to"
539
                                       " change the secondary)"))],
540
                    "[-n NODE] <instance>",
541
                    "Replaces all disks for the instance"),
542
  'modify': (SetInstanceParms, ARGS_ONE,
543
             [DEBUG_OPT, FORCE_OPT,
544
              cli_option("-m", "--memory", dest="mem",
545
                         help="Memory size",
546
                         default=None, type="unit", metavar="<mem>"),
547
              make_option("-p", "--cpu", dest="vcpus",
548
                          help="Number of virtual CPUs",
549
                          default=None, type="int", metavar="<PROC>"),
550
              make_option("-i", "--ip", dest="ip",
551
                          help="IP address ('none' or numeric IP)",
552
                          default=None, type="string", metavar="<ADDRESS>"),
553
              make_option("-b", "--bridge", dest="bridge",
554
                          help="Bridge to connect this instance to",
555
                          default=None, type="string", metavar="<bridge>")
556
              ],
557
             "<instance>", "Alters the parameters of an instance"),
558
  'shutdown': (ShutdownInstance, ARGS_ONE, [DEBUG_OPT],
559
               "<instance>", "Stops an instance"),
560
  'startup': (StartupInstance, ARGS_ONE,
561
              [DEBUG_OPT, FORCE_OPT,
562
               make_option("-e", "--extra", dest="extra_args",
563
                           help="Extra arguments for the instance's kernel",
564
                           default=None, type="string", metavar="<PARAMS>"),
565
               ],
566
            "<instance>", "Starts an instance"),
567
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT],
568
                     "<instance>",
569
                     "Activate an instance's disks"),
570
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT],
571
                       "<instance>",
572
                       "Deactivate an instance's disks"),
573
  }
574

    
575
if __name__ == '__main__':
576
  sys.exit(GenericMain(commands))