Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 3ecf6786

History | View | Annotate | Download (18.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)
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
  else:
55
    headers = None
56

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

    
62
  numfields = ["admin_ram", "oper_ram"]
63

    
64
  OutputTable(separator=opts.separator, headers=headers,
65
      fields=selected_fields, unitfields=unitfields,
66
      numfields=numfields, data=output)
67

    
68
  return 0
69

    
70

    
71
def AddInstance(opts, args):
72
  """Add an instance to the cluster.
73

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

    
83
  """
84
  instance = args[0]
85

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

    
97

    
98
def ReinstallInstance(opts, args):
99
  """Reinstall an instance.
100

    
101
  Args:
102
    opts - class with options as members
103
    args - list containing a single element, the instance name
104

    
105
  """
106
  instance_name = args[0]
107

    
108
  if not opts.force:
109
    usertext = ("This will reinstall the instance %s and remove "
110
                "all data. Continue?") % instance_name
111
    if not opts._ask_user(usertext):
112
      return 1
113

    
114
  op = opcodes.OpReinstallInstance(instance_name=instance_name,
115
                                   os_type=opts.os)
116
  SubmitOpCode(op)
117

    
118
  return 0
119

    
120

    
121
def RemoveInstance(opts, args):
122
  """Remove an instance.
123

    
124
  Args:
125
    opts - class with options as members
126
    args - list containing a single element, the instance name
127

    
128
  """
129
  instance_name = args[0]
130
  force = opts.force
131

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

    
139
  op = opcodes.OpRemoveInstance(instance_name=instance_name)
140
  SubmitOpCode(op)
141
  return 0
142

    
143

    
144
def ActivateDisks(opts, args):
145
  """Activate an instance's disks.
146

    
147
  This serves two purposes:
148
    - it allows one (as long as the instance is not running) to mount
149
    the disks and modify them from the node
150
    - it repairs inactive secondary drbds
151

    
152
  """
153
  instance_name = args[0]
154
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
155
  disks_info = SubmitOpCode(op)
156
  for host, iname, nname in disks_info:
157
    print "%s:%s:%s" % (host, iname, nname)
158
  return 0
159

    
160

    
161
def DeactivateDisks(opts, args):
162
  """Command-line interface for _ShutdownInstanceBlockDevices.
163

    
164
  This function takes the instance name, looks for its primary node
165
  and the tries to shutdown its block devices on that node.
166

    
167
  """
168
  instance_name = args[0]
169
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
170
  SubmitOpCode(op)
171
  return 0
172

    
173

    
174
def StartupInstance(opts, args):
175
  """Shutdown an instance.
176

    
177
  Args:
178
    opts - class with options as members
179
    args - list containing a single element, the instance name
180

    
181
  """
182
  instance_name = args[0]
183
  op = opcodes.OpStartupInstance(instance_name=instance_name, force=opts.force,
184
                                 extra_args=opts.extra_args)
185
  SubmitOpCode(op)
186
  return 0
187

    
188

    
189
def ShutdownInstance(opts, args):
190
  """Shutdown an instance.
191

    
192
  Args:
193
    opts - class with options as members
194
    args - list containing a single element, the instance name
195

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

    
202

    
203
def AddMDDRBDComponent(opts, args):
204
  """Add a new component to a remote_raid1 disk.
205

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

    
210
  """
211
  op = opcodes.OpAddMDDRBDComponent(instance_name=args[0],
212
                                    disk_name=opts.disk,
213
                                    remote_node=opts.node)
214
  SubmitOpCode(op)
215
  return 0
216

    
217

    
218
def RemoveMDDRBDComponent(opts, args):
219
  """Remove a component from a remote_raid1 disk.
220

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

    
225
  """
226
  op = opcodes.OpRemoveMDDRBDComponent(instance_name=args[0],
227
                                       disk_name=opts.disk,
228
                                       disk_id=opts.port)
229
  SubmitOpCode(op)
230
  return 0
231

    
232

    
233
def ReplaceDisks(opts, args):
234
  """Replace the disks of an instance
235

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

    
240
  """
241
  instance_name = args[0]
242
  new_secondary = opts.new_secondary
243
  op = opcodes.OpReplaceDisks(instance_name=args[0],
244
                              remote_node=opts.new_secondary)
245
  SubmitOpCode(op)
246
  return 0
247

    
248

    
249
def FailoverInstance(opts, args):
250
  """Failover an instance.
251

    
252
  The failover is done by shutting it down on its present node and
253
  starting it on the secondary.
254

    
255
  Args:
256
    opts - class with options as members
257
    args - list with a single element, the instance name
258
  Opts used:
259
    force - whether to failover without asking questions.
260

    
261
  """
262
  instance_name = args[0]
263
  force = opts.force
264

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

    
272
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
273
                                  ignore_consistency=opts.ignore_consistency)
274
  SubmitOpCode(op)
275
  return 0
276

    
277

    
278
def ConnectToInstanceConsole(opts, args):
279
  """Connect to the console of an instance.
280

    
281
  Args:
282
    opts - class with options as members
283
    args - list with a single element, the instance name
284

    
285
  """
286
  instance_name = args[0]
287

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

    
299

    
300
def _FormatBlockDevInfo(buf, dev, indent_level):
301
  """Show block device information.
302

    
303
  This is only used by ShowInstanceConfig(), but it's too big to be
304
  left for an inline definition.
305

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

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

    
343
  if dev["sstatus"]:
344
    buf.write("%*s    secondary: " % (2*indent_level, ""))
345
    helper(buf, dev["dev_type"], dev["sstatus"])
346

    
347
  if dev["children"]:
348
    for child in dev["children"]:
349
      _FormatBlockDevInfo(buf, child, indent_level+1)
350

    
351

    
352
def ShowInstanceConfig(opts, args):
353
  """Compute instance run-time status.
354

    
355
  """
356
  retcode = 0
357
  op = opcodes.OpQueryInstanceData(instances=args)
358
  result = SubmitOpCode(op)
359

    
360
  if not result:
361
    logger.ToStdout("No instances.")
362
    return 1
363

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

    
383
    for device in instance["disks"]:
384
      _FormatBlockDevInfo(buf, device, 1)
385

    
386
  logger.ToStdout(buf.getvalue().rstrip('\n'))
387
  return retcode
388

    
389

    
390
def SetInstanceParms(opts, args):
391
  """Modifies an instance.
392

    
393
  All parameters take effect only at the next restart of the instance.
394

    
395
  Args:
396
    opts - class with options as members
397
    args - list with a single element, the instance name
398
  Opts used:
399
    memory - the new memory size
400
    vcpus - the new number of cpus
401

    
402
  """
403
  if not opts.mem and not opts.vcpus and not opts.ip and not opts.bridge:
404
    logger.ToStdout("Please give at least one of the parameters.")
405
    return 1
406

    
407
  op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem,
408
                                  vcpus=opts.vcpus, ip=opts.ip,
409
                                  bridge=opts.bridge)
410
  result = SubmitOpCode(op)
411

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

    
420

    
421
# options used in more than one cmd
422
node_opt = make_option("-n", "--node", dest="node", help="Target node",
423
                       metavar="<node>")
424

    
425
os_opt = cli_option("-o", "--os-type", dest="os", help="What OS to run",
426
                    metavar="<os>")
427

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

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

    
543
if __name__ == '__main__':
544
  sys.exit(GenericMain(commands))