Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ a8083063

History | View | Annotate | Download (18.8 kB)

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

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

    
21

    
22
import sys
23
import os
24
from optparse import make_option
25
import textwrap
26
from cStringIO import StringIO
27

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

    
34

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

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

    
45
  op = opcodes.OpQueryInstances(output_fields=selected_fields)
46
  output = SubmitOpCode(op)
47

    
48
  mlens = [0 for name in selected_fields]
49

    
50
  format_fields = []
51
  unitformat_fields = ("admin_ram", "oper_ram")
52
  for field in selected_fields:
53
    if field in ("admin_ram", "oper_ram"):
54
      format_fields.append("%*s")
55
    else:
56
      format_fields.append("%-*s")
57
  separator = opts.separator
58
  if "%" in separator:
59
    separator = separator.replace("%", "%%")
60
  format = separator.join(format_fields)
61

    
62
  for row in output:
63
    for idx, val in enumerate(row):
64
      if opts.human_readable and selected_fields[idx] in unitformat_fields:
65
        try:
66
          val = int(val)
67
        except ValueError:
68
          pass
69
        else:
70
          val = row[idx] = utils.FormatUnit(val)
71
      mlens[idx] = max(mlens[idx], len(val))
72

    
73
  if not opts.no_headers:
74
    header_list = {"name": "Instance", "os": "OS", "pnode": "Primary_node",
75
                   "snodes": "Secondary_Nodes", "admin_state": "Autostart",
76
                   "oper_state": "Status", "admin_ram": "Configured_memory",
77
                   "oper_ram": "Memory", "disk_template": "Disk_template",
78
                   "ip": "IP Address", "mac": "MAC Address",
79
                   "bridge": "Bridge"}
80
    args = []
81
    for idx, name in enumerate(selected_fields):
82
      hdr = header_list[name]
83
      mlens[idx] = max(mlens[idx], len(hdr))
84
      args.append(mlens[idx])
85
      args.append(hdr)
86
    logger.ToStdout(format % tuple(args))
87

    
88
  for line in output:
89
    args = []
90
    for idx in range(len(selected_fields)):
91
      args.append(mlens[idx])
92
      args.append(line[idx])
93
    logger.ToStdout(format % tuple(args))
94

    
95
  return 0
96

    
97

    
98
def AddInstance(opts, args):
99
  """Add an instance to the cluster.
100

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

    
110
  """
111

    
112
  instance = args[0]
113

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

    
125

    
126
def RemoveInstance(opts, args):
127
  """Remove an instance.
128

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

    
133
  """
134
  instance_name = args[0]
135
  force = opts.force
136

    
137
  if not force:
138
    usertext = ("This will remove the volumes of the instance %s"
139
                " (including mirrors), thus removing all the data"
140
                " of the instance. Continue?") % instance_name
141
    if not opts._ask_user(usertext):
142
      return 1
143

    
144
  op = opcodes.OpRemoveInstance(instance_name=instance_name)
145
  SubmitOpCode(op)
146
  return 0
147

    
148

    
149
def ActivateDisks(opts, args):
150
  """Activate an instance's disks.
151

    
152
  This serves two purposes:
153
    - it allows one (as long as the instance is not running) to mount
154
    the disks and modify them from the node
155
    - it repairs inactive secondary drbds
156

    
157
  """
158
  instance_name = args[0]
159
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
160
  disks_info = SubmitOpCode(op)
161
  for host, iname, nname in disks_info:
162
    print "%s:%s:%s" % (host, iname, nname)
163
  return 0
164

    
165

    
166
def DeactivateDisks(opts, args):
167
  """Command-line interface for _ShutdownInstanceBlockDevices.
168

    
169
  This function takes the instance name, looks for its primary node
170
  and the tries to shutdown its block devices on that node.
171

    
172
  """
173
  instance_name = args[0]
174
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
175
  SubmitOpCode(op)
176
  return 0
177

    
178

    
179
def StartupInstance(opts, args):
180
  """Shutdown an instance.
181

    
182
  Args:
183
    opts - class with options as members
184
    args - list containing a single element, the instance name
185

    
186
  """
187
  instance_name = args[0]
188
  op = opcodes.OpStartupInstance(instance_name=instance_name, force=opts.force,
189
                                 extra_args=opts.extra_args)
190
  SubmitOpCode(op)
191
  return 0
192

    
193

    
194
def ShutdownInstance(opts, args):
195
  """Shutdown an instance.
196

    
197
  Args:
198
    opts - class with options as members
199
    args - list containing a single element, the instance name
200

    
201
  """
202
  instance_name = args[0]
203
  op = opcodes.OpShutdownInstance(instance_name=instance_name)
204
  SubmitOpCode(op)
205
  return 0
206

    
207

    
208
def AddMDDRBDComponent(opts, args):
209
  """Add a new component to a remote_raid1 disk.
210

    
211
  Args:
212
    opts - class with options as members
213
    args - list with a single element, the instance name
214

    
215
  """
216
  op = opcodes.OpAddMDDRBDComponent(instance_name=args[0],
217
                                    disk_name=opts.disk,
218
                                    remote_node=opts.node)
219
  SubmitOpCode(op)
220
  return 0
221

    
222

    
223
def RemoveMDDRBDComponent(opts, args):
224
  """Connect to the console of an instance
225

    
226
  Args:
227
    opts - class with options as members
228
    args - list with a single element, the instance name
229

    
230
  """
231
  op = opcodes.OpRemoveMDDRBDComponent(instance_name=args[0],
232
                                       disk_name=opts.disk,
233
                                       disk_id=opts.port)
234
  SubmitOpCode(op)
235
  return 0
236

    
237

    
238
def ReplaceDisks(opts, args):
239
  """Replace the disks of an instance
240

    
241
  Args:
242
    opts - class with options as members
243
    args - list with a single element, the instance name
244

    
245
  """
246
  instance_name = args[0]
247
  new_secondary = opts.new_secondary
248
  op = opcodes.OpReplaceDisks(instance_name=args[0],
249
                              remote_node=opts.new_secondary)
250
  SubmitOpCode(op)
251
  return 0
252

    
253

    
254
def FailoverInstance(opts, args):
255
  """Failover an instance.
256

    
257
  The failover is done by shutting it down on its present node and
258
  starting it on the secondary.
259

    
260
  Args:
261
    opts - class with options as members
262
    args - list with a single element, the instance name
263
  Opts used:
264
    force - whether to failover without asking questions.
265

    
266
  """
267
  instance_name = args[0]
268
  force = opts.force
269

    
270
  if not force:
271
    usertext = ("Failover will happen to image %s."
272
                " This requires a shutdown of the instance. Continue?" %
273
                (instance_name,))
274
    usertext = textwrap.fill(usertext)
275
    if not opts._ask_user(usertext):
276
      return 1
277

    
278
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
279
                                  ignore_consistency=opts.ignore_consistency)
280
  SubmitOpCode(op)
281
  return 0
282

    
283

    
284
def ConnectToInstanceConsole(opts, args):
285
  """Connect to the console of an instance.
286

    
287
  Args:
288
    opts - class with options as members
289
    args - list with a single element, the instance name
290

    
291
  """
292
  instance_name = args[0]
293

    
294
  op = opcodes.OpConnectConsole(instance_name=instance_name)
295
  node, console_cmd = SubmitOpCode(op)
296
  # drop lock and exec so other commands can run while we have console
297
  utils.Unlock("cmd")
298
  try:
299
    os.execv("/usr/bin/ssh", ["ssh", "-qt", node, console_cmd])
300
  finally:
301
    sys.stderr.write("Can't run console command %s on node %s" %
302
                     (console_cmd, node))
303
    os._exit(1)
304

    
305

    
306
def _FormatBlockDevInfo(buf, dev, indent_level):
307
  """Show block device information.
308

    
309
  This is only used by ShowInstanceConfig(), but it's too big to be
310
  left for an inline definition.
311

    
312
  """
313
  def helper(buf, dtype, status):
314
    """Format one line for phsyical device status."""
315
    if not status:
316
      buf.write("not active\n")
317
    else:
318
      (path, major, minor, syncp, estt, degr) = status
319
      buf.write("%s (%d:%d)" % (path, major, minor))
320
      if dtype in ("md_raid1", "drbd"):
321
        if syncp is not None:
322
          sync_text = "*RECOVERING* %5.2f%%," % syncp
323
          if estt:
324
            sync_text += " ETA %ds" % estt
325
          else:
326
            sync_text += " ETA unknown"
327
        else:
328
          sync_text = "in sync"
329
        if degr:
330
          degr_text = "*DEGRADED*"
331
        else:
332
          degr_text = "ok"
333
        buf.write(" %s, status %s" % (sync_text, degr_text))
334
      buf.write("\n")
335

    
336
  if dev["iv_name"] is not None:
337
    data = "  - %s, " % dev["iv_name"]
338
  else:
339
    data = "  - "
340
  data += "type: %s" % dev["dev_type"]
341
  if dev["logical_id"] is not None:
342
    data += ", logical_id: %s" % (dev["logical_id"],)
343
  elif dev["physical_id"] is not None:
344
    data += ", physical_id: %s" % (dev["physical_id"],)
345
  buf.write("%*s%s\n" % (2*indent_level, "", data))
346
  buf.write("%*s    primary:   " % (2*indent_level, ""))
347
  helper(buf, dev["dev_type"], dev["pstatus"])
348

    
349
  if dev["sstatus"]:
350
    buf.write("%*s    secondary: " % (2*indent_level, ""))
351
    helper(buf, dev["dev_type"], dev["sstatus"])
352

    
353
  if dev["children"]:
354
    for child in dev["children"]:
355
      _FormatBlockDevInfo(buf, child, indent_level+1)
356

    
357

    
358
def ShowInstanceConfig(opts, args):
359
  """Compute instance run-time status.
360

    
361
  """
362

    
363
  retcode = 0
364
  op = opcodes.OpQueryInstanceData(instances=args)
365
  result = SubmitOpCode(op)
366

    
367
  if not result:
368
    logger.ToStdout("No instances.")
369
    return 1
370

    
371
  buf = StringIO()
372
  retcode = 0
373
  for instance_name in result:
374
    instance = result[instance_name]
375
    buf.write("Instance name: %s\n" % instance["name"])
376
    buf.write("State: configured to be %s, actual state is %s\n" %
377
              (instance["config_state"], instance["run_state"]))
378
    buf.write("  Nodes:\n")
379
    buf.write("    - primary: %s\n" % instance["pnode"])
380
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
381
    buf.write("  Operating system: %s\n" % instance["os"])
382
    buf.write("  Hardware:\n")
383
    buf.write("    - memory: %dMiB\n" % instance["memory"])
384
    buf.write("    - NICs: %s\n" %
385
        ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
386
                   (mac, ip, bridge)
387
                     for mac, ip, bridge in instance["nics"]]))
388
    buf.write("  Block devices:\n")
389

    
390
    for device in instance["disks"]:
391
      _FormatBlockDevInfo(buf, device, 1)
392

    
393
  logger.ToStdout(buf.getvalue().rstrip('\n'))
394
  return retcode
395

    
396

    
397
def SetInstanceParms(opts, args):
398
  """Modifies an instance.
399

    
400
  All parameters take effect only at the next restart of the instance.
401

    
402
  Args:
403
    opts - class with options as members
404
    args - list with a single element, the instance name
405
  Opts used:
406
    memory - the new memory size
407
    vcpus - the new number of cpus
408

    
409
  """
410
  if not opts.mem and not opts.vcpus and not opts.ip and not opts.bridge:
411
    logger.ToStdout("Please give at least one of the parameters.")
412
    return 1
413

    
414
  op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem,
415
                                  vcpus=opts.vcpus, ip=opts.ip,
416
                                  bridge=opts.bridge)
417
  result = SubmitOpCode(op)
418

    
419
  if result:
420
    logger.ToStdout("Modified instance %s" % args[0])
421
    for param, data in result:
422
      logger.ToStdout(" - %-5s -> %s" % (param, data))
423
    logger.ToStdout("Please don't forget that these parameters take effect"
424
                    " only at the next start of the instance.")
425
  return 0
426

    
427

    
428
# options used in more than one cmd
429
node_opt = make_option("-n", "--node", dest="node", help="Target node",
430
                       metavar="<node>")
431
force_opt = make_option("-f", "--force", dest="force", action="store_true",
432
                        default=False, help="Force the operation")
433

    
434
# this is defined separately due to readability only
435
add_opts = [
436
  DEBUG_OPT,
437
  node_opt,
438
  cli_option("-s", "--os-size", dest="size", help="Disk size",
439
             default=20 * 1024, type="unit", metavar="<size>"),
440
  cli_option("--swap-size", dest="swap", help="Swap size",
441
             default=4 * 1024, type="unit", metavar="<size>"),
442
  cli_option("-o", "--os-type", dest="os", help="What OS to run",
443
             metavar="<os>"),
444
  cli_option("-m", "--memory", dest="mem", help="Memory size",
445
              default=128, type="unit", metavar="<mem>"),
446
  make_option("-p", "--cpu", dest="vcpus", help="Number of virtual CPUs",
447
              default=1, type="int", metavar="<PROC>"),
448
  make_option("-t", "--disk-template", dest="disk_template",
449
              help="Custom disk setup (diskless, plain, local_raid1 or"
450
              " remote_raid1)", default=None, metavar="TEMPL"),
451
  make_option("-i", "--ip", dest="ip",
452
              help="IP address ('none' [default], 'auto', or specify address)",
453
              default='none', type="string", metavar="<ADDRESS>"),
454
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
455
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
456
  make_option("--secondary-node", dest="snode",
457
              help="Secondary node for remote_raid1 disk layout",
458
              metavar="<node>"),
459
  make_option("-b", "--bridge", dest="bridge",
460
              help="Bridge to connect this instance to",
461
              default=None, metavar="<bridge>")
462
  ]
463

    
464

    
465
commands = {
466
  'add': (AddInstance, ARGS_ONE, add_opts,
467
          "[opts...] <name>",
468
          "Creates and adds a new instance to the cluster"),
469
  'add-mirror': (AddMDDRBDComponent, ARGS_ONE,
470
                [DEBUG_OPT, node_opt,
471
                 make_option("-b", "--disk", dest="disk", metavar="sdX",
472
                             help=("The name of the instance disk for which to"
473
                                   " add the mirror"))],
474
                "-n node -b disk <instance>",
475
                "Creates a new mirror for the instance"),
476
  'console': (ConnectToInstanceConsole, ARGS_ONE, [DEBUG_OPT],
477
              "<instance>",
478
              "Opens a console on the specified instance"),
479
  'failover': (FailoverInstance, ARGS_ONE,
480
               [DEBUG_OPT, force_opt,
481
                make_option("--ignore-consistency", dest="ignore_consistency",
482
                            action="store_true", default=False,
483
                            help="Ignore the consistency of the disks on"
484
                            " the secondary"),
485
                ],
486
               "[-f] <instance>",
487
               "Stops the instance and starts it on the backup node, using"
488
               " the remote mirror (only for instances of type remote_raid1)"),
489
  'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT], "[<instance>...]",
490
           "Show information on the specified instance"),
491
  'list': (ListInstances, ARGS_NONE,
492
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT,
493
            make_option("-o", "--output", dest="output", action="store",
494
                        type="string", help="Select output fields",
495
                        metavar="FIELDS")
496
            ],
497
           "", "Lists the instances and their status"),
498
  'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, force_opt],
499
             "[-f] <instance>", "Shuts down the instance and removes it"),
500
  'remove-mirror': (RemoveMDDRBDComponent, ARGS_ONE,
501
                   [DEBUG_OPT, node_opt,
502
                    make_option("-b", "--disk", dest="disk", metavar="sdX",
503
                                help=("The name of the instance disk"
504
                                      " for which to add the mirror")),
505
                    make_option("-p", "--port", dest="port", metavar="PORT",
506
                                help=("The port of the drbd device"
507
                                      " which to remove from the mirror"),
508
                                type="int"),
509
                    ],
510
                   "-b disk -p port <instance>",
511
                   "Removes a mirror from the instance"),
512
  'replace-disks': (ReplaceDisks, ARGS_ONE,
513
                    [DEBUG_OPT,
514
                     make_option("-n", "--new-secondary", dest="new_secondary",
515
                                 metavar="NODE",
516
                                 help=("New secondary node (if you want to"
517
                                       " change the secondary)"))],
518
                    "[-n NODE] <instance>",
519
                    "Replaces all disks for the instance"),
520

    
521
  'modify': (SetInstanceParms, ARGS_ONE,
522
             [DEBUG_OPT, force_opt,
523
              cli_option("-m", "--memory", dest="mem",
524
                         help="Memory size",
525
                         default=None, type="unit", metavar="<mem>"),
526
              make_option("-p", "--cpu", dest="vcpus",
527
                          help="Number of virtual CPUs",
528
                          default=None, type="int", metavar="<PROC>"),
529
              make_option("-i", "--ip", dest="ip",
530
                          help="IP address ('none' or numeric IP)",
531
                          default=None, type="string", metavar="<ADDRESS>"),
532
              make_option("-b", "--bridge", dest="bridge",
533
                          help="Bridge to connect this instance to",
534
                          default=None, type="string", metavar="<bridge>")
535
              ],
536
             "<instance>", "Alters the parameters of an instance"),
537
  'shutdown': (ShutdownInstance, ARGS_ONE, [DEBUG_OPT],
538
               "<instance>", "Stops an instance"),
539
  'startup': (StartupInstance, ARGS_ONE,
540
              [DEBUG_OPT, force_opt,
541
               make_option("-e", "--extra", dest="extra_args",
542
                           help="Extra arguments for the instance's kernel",
543
                           default=None, type="string", metavar="<PARAMS>"),
544
               ],
545
            "<instance>", "Starts an instance"),
546
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT],
547
                     "<instance>",
548
                     "Activate an instance's disks"),
549
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT],
550
                       "<instance>",
551
                       "Deactivate an instance's disks"),
552
  }
553

    
554
if __name__ == '__main__':
555
  retcode = GenericMain(commands)
556
  sys.exit(retcode)