Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 644eeef9

History | View | Annotate | Download (18.4 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)
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
  data = GenerateTable(separator=opts.separator, headers=headers,
66
                       fields=selected_fields, unitfields=unitfields,
67
                       numfields=numfields, data=output)
68

    
69
  for line in data:
70
    logger.ToStdout(line)
71

    
72
  return 0
73

    
74

    
75
def AddInstance(opts, args):
76
  """Add an instance to the cluster.
77

    
78
  Args:
79
    opts - class with options as members
80
    args - list with a single element, the instance name
81
  Opts used:
82
    mem - amount of memory to allocate to instance (MiB)
83
    size - amount of disk space to allocate to instance (MiB)
84
    os - which OS to run on instance
85
    node - node to run new instance on
86

    
87
  """
88
  instance = args[0]
89

    
90
  op = opcodes.OpCreateInstance(instance_name=instance, mem_size=opts.mem,
91
                                disk_size=opts.size, swap_size=opts.swap,
92
                                disk_template=opts.disk_template,
93
                                mode=constants.INSTANCE_CREATE,
94
                                os_type=opts.os, pnode=opts.node,
95
                                snode=opts.snode, vcpus=opts.vcpus,
96
                                ip=opts.ip, bridge=opts.bridge, start=True,
97
                                wait_for_sync=opts.wait_for_sync)
98
  SubmitOpCode(op)
99
  return 0
100

    
101

    
102
def ReinstallInstance(opts, args):
103
  """Reinstall an instance.
104

    
105
  Args:
106
    opts - class with options as members
107
    args - list containing a single element, the instance name
108

    
109
  """
110
  instance_name = args[0]
111

    
112
  if not opts.force:
113
    usertext = ("This will reinstall the instance %s and remove "
114
                "all data. Continue?") % instance_name
115
    if not opts._ask_user(usertext):
116
      return 1
117

    
118
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
119
                                   os_type=opts.os)
120
  SubmitOpCode(op)
121

    
122
  return 0
123

    
124

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

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

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

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

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

    
147

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

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

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

    
164

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

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

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

    
177

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

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

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

    
192

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

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

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

    
206

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

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

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

    
221

    
222
def RemoveMDDRBDComponent(opts, args):
223
  """Remove a component from a remote_raid1 disk.
224

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

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

    
236

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

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

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

    
252

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

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

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

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

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

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

    
281

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

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

    
289
  """
290
  instance_name = args[0]
291

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

    
303

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

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

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

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

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

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

    
355

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

    
359
  """
360
  retcode = 0
361
  op = opcodes.OpQueryInstanceData(instances=args)
362
  result = SubmitOpCode(op)
363

    
364
  if not result:
365
    logger.ToStdout("No instances.")
366
    return 1
367

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

    
387
    for device in instance["disks"]:
388
      _FormatBlockDevInfo(buf, device, 1)
389

    
390
  logger.ToStdout(buf.getvalue().rstrip('\n'))
391
  return retcode
392

    
393

    
394
def SetInstanceParms(opts, args):
395
  """Modifies an instance.
396

    
397
  All parameters take effect only at the next restart of the instance.
398

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

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

    
411
  op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem,
412
                                  vcpus=opts.vcpus, ip=opts.ip,
413
                                  bridge=opts.bridge)
414
  result = SubmitOpCode(op)
415

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

    
424

    
425
# options used in more than one cmd
426
node_opt = make_option("-n", "--node", dest="node", help="Target node",
427
                       metavar="<node>")
428

    
429
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
430
                    metavar="<os>")
431

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

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

    
547
if __name__ == '__main__':
548
  sys.exit(GenericMain(commands))