Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 137161c9

History | View | Annotate | Download (17.7 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
  if not opts.no_headers:
49
    headers = {"name": "Instance", "os": "OS", "pnode": "Primary_node",
50
               "snodes": "Secondary_Nodes", "admin_state": "Autostart",
51
               "oper_state": "Status", "admin_ram": "Configured_memory",
52
               "oper_ram": "Memory", "disk_template": "Disk_template",
53
               "ip": "IP Address", "mac": "MAC Address",
54
               "bridge": "Bridge"}
55
  else:
56
    headers = None
57

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

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

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

    
69
  return 0
70

    
71

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

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

    
84
  """
85

    
86
  instance = args[0]
87

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

    
99

    
100
def RemoveInstance(opts, args):
101
  """Remove an instance.
102

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

    
107
  """
108
  instance_name = args[0]
109
  force = opts.force
110

    
111
  if not force:
112
    usertext = ("This will remove the volumes of the instance %s"
113
                " (including mirrors), thus removing all the data"
114
                " of the instance. Continue?") % instance_name
115
    if not opts._ask_user(usertext):
116
      return 1
117

    
118
  op = opcodes.OpRemoveInstance(instance_name=instance_name)
119
  SubmitOpCode(op)
120
  return 0
121

    
122

    
123
def ActivateDisks(opts, args):
124
  """Activate an instance's disks.
125

    
126
  This serves two purposes:
127
    - it allows one (as long as the instance is not running) to mount
128
    the disks and modify them from the node
129
    - it repairs inactive secondary drbds
130

    
131
  """
132
  instance_name = args[0]
133
  op = opcodes.OpActivateInstanceDisks(instance_name=instance_name)
134
  disks_info = SubmitOpCode(op)
135
  for host, iname, nname in disks_info:
136
    print "%s:%s:%s" % (host, iname, nname)
137
  return 0
138

    
139

    
140
def DeactivateDisks(opts, args):
141
  """Command-line interface for _ShutdownInstanceBlockDevices.
142

    
143
  This function takes the instance name, looks for its primary node
144
  and the tries to shutdown its block devices on that node.
145

    
146
  """
147
  instance_name = args[0]
148
  op = opcodes.OpDeactivateInstanceDisks(instance_name=instance_name)
149
  SubmitOpCode(op)
150
  return 0
151

    
152

    
153
def StartupInstance(opts, args):
154
  """Shutdown an instance.
155

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

    
160
  """
161
  instance_name = args[0]
162
  op = opcodes.OpStartupInstance(instance_name=instance_name, force=opts.force,
163
                                 extra_args=opts.extra_args)
164
  SubmitOpCode(op)
165
  return 0
166

    
167

    
168
def ShutdownInstance(opts, args):
169
  """Shutdown an instance.
170

    
171
  Args:
172
    opts - class with options as members
173
    args - list containing a single element, the instance name
174

    
175
  """
176
  instance_name = args[0]
177
  op = opcodes.OpShutdownInstance(instance_name=instance_name)
178
  SubmitOpCode(op)
179
  return 0
180

    
181

    
182
def AddMDDRBDComponent(opts, args):
183
  """Add a new component to a remote_raid1 disk.
184

    
185
  Args:
186
    opts - class with options as members
187
    args - list with a single element, the instance name
188

    
189
  """
190
  op = opcodes.OpAddMDDRBDComponent(instance_name=args[0],
191
                                    disk_name=opts.disk,
192
                                    remote_node=opts.node)
193
  SubmitOpCode(op)
194
  return 0
195

    
196

    
197
def RemoveMDDRBDComponent(opts, args):
198
  """Connect to the console of an instance
199

    
200
  Args:
201
    opts - class with options as members
202
    args - list with a single element, the instance name
203

    
204
  """
205
  op = opcodes.OpRemoveMDDRBDComponent(instance_name=args[0],
206
                                       disk_name=opts.disk,
207
                                       disk_id=opts.port)
208
  SubmitOpCode(op)
209
  return 0
210

    
211

    
212
def ReplaceDisks(opts, args):
213
  """Replace the disks of an instance
214

    
215
  Args:
216
    opts - class with options as members
217
    args - list with a single element, the instance name
218

    
219
  """
220
  instance_name = args[0]
221
  new_secondary = opts.new_secondary
222
  op = opcodes.OpReplaceDisks(instance_name=args[0],
223
                              remote_node=opts.new_secondary)
224
  SubmitOpCode(op)
225
  return 0
226

    
227

    
228
def FailoverInstance(opts, args):
229
  """Failover an instance.
230

    
231
  The failover is done by shutting it down on its present node and
232
  starting it on the secondary.
233

    
234
  Args:
235
    opts - class with options as members
236
    args - list with a single element, the instance name
237
  Opts used:
238
    force - whether to failover without asking questions.
239

    
240
  """
241
  instance_name = args[0]
242
  force = opts.force
243

    
244
  if not force:
245
    usertext = ("Failover will happen to image %s."
246
                " This requires a shutdown of the instance. Continue?" %
247
                (instance_name,))
248
    usertext = textwrap.fill(usertext)
249
    if not opts._ask_user(usertext):
250
      return 1
251

    
252
  op = opcodes.OpFailoverInstance(instance_name=instance_name,
253
                                  ignore_consistency=opts.ignore_consistency)
254
  SubmitOpCode(op)
255
  return 0
256

    
257

    
258
def ConnectToInstanceConsole(opts, args):
259
  """Connect to the console of an instance.
260

    
261
  Args:
262
    opts - class with options as members
263
    args - list with a single element, the instance name
264

    
265
  """
266
  instance_name = args[0]
267

    
268
  op = opcodes.OpConnectConsole(instance_name=instance_name)
269
  node, console_cmd = SubmitOpCode(op)
270
  # drop lock and exec so other commands can run while we have console
271
  utils.Unlock("cmd")
272
  try:
273
    os.execv("/usr/bin/ssh", ["ssh", "-qt", node, console_cmd])
274
  finally:
275
    sys.stderr.write("Can't run console command %s on node %s" %
276
                     (console_cmd, node))
277
    os._exit(1)
278

    
279

    
280
def _FormatBlockDevInfo(buf, dev, indent_level):
281
  """Show block device information.
282

    
283
  This is only used by ShowInstanceConfig(), but it's too big to be
284
  left for an inline definition.
285

    
286
  """
287
  def helper(buf, dtype, status):
288
    """Format one line for phsyical device status."""
289
    if not status:
290
      buf.write("not active\n")
291
    else:
292
      (path, major, minor, syncp, estt, degr) = status
293
      buf.write("%s (%d:%d)" % (path, major, minor))
294
      if dtype in ("md_raid1", "drbd"):
295
        if syncp is not None:
296
          sync_text = "*RECOVERING* %5.2f%%," % syncp
297
          if estt:
298
            sync_text += " ETA %ds" % estt
299
          else:
300
            sync_text += " ETA unknown"
301
        else:
302
          sync_text = "in sync"
303
        if degr:
304
          degr_text = "*DEGRADED*"
305
        else:
306
          degr_text = "ok"
307
        buf.write(" %s, status %s" % (sync_text, degr_text))
308
      buf.write("\n")
309

    
310
  if dev["iv_name"] is not None:
311
    data = "  - %s, " % dev["iv_name"]
312
  else:
313
    data = "  - "
314
  data += "type: %s" % dev["dev_type"]
315
  if dev["logical_id"] is not None:
316
    data += ", logical_id: %s" % (dev["logical_id"],)
317
  elif dev["physical_id"] is not None:
318
    data += ", physical_id: %s" % (dev["physical_id"],)
319
  buf.write("%*s%s\n" % (2*indent_level, "", data))
320
  buf.write("%*s    primary:   " % (2*indent_level, ""))
321
  helper(buf, dev["dev_type"], dev["pstatus"])
322

    
323
  if dev["sstatus"]:
324
    buf.write("%*s    secondary: " % (2*indent_level, ""))
325
    helper(buf, dev["dev_type"], dev["sstatus"])
326

    
327
  if dev["children"]:
328
    for child in dev["children"]:
329
      _FormatBlockDevInfo(buf, child, indent_level+1)
330

    
331

    
332
def ShowInstanceConfig(opts, args):
333
  """Compute instance run-time status.
334

    
335
  """
336

    
337
  retcode = 0
338
  op = opcodes.OpQueryInstanceData(instances=args)
339
  result = SubmitOpCode(op)
340

    
341
  if not result:
342
    logger.ToStdout("No instances.")
343
    return 1
344

    
345
  buf = StringIO()
346
  retcode = 0
347
  for instance_name in result:
348
    instance = result[instance_name]
349
    buf.write("Instance name: %s\n" % instance["name"])
350
    buf.write("State: configured to be %s, actual state is %s\n" %
351
              (instance["config_state"], instance["run_state"]))
352
    buf.write("  Nodes:\n")
353
    buf.write("    - primary: %s\n" % instance["pnode"])
354
    buf.write("    - secondaries: %s\n" % ", ".join(instance["snodes"]))
355
    buf.write("  Operating system: %s\n" % instance["os"])
356
    buf.write("  Hardware:\n")
357
    buf.write("    - memory: %dMiB\n" % instance["memory"])
358
    buf.write("    - NICs: %s\n" %
359
        ", ".join(["{MAC: %s, IP: %s, bridge: %s}" %
360
                   (mac, ip, bridge)
361
                     for mac, ip, bridge in instance["nics"]]))
362
    buf.write("  Block devices:\n")
363

    
364
    for device in instance["disks"]:
365
      _FormatBlockDevInfo(buf, device, 1)
366

    
367
  logger.ToStdout(buf.getvalue().rstrip('\n'))
368
  return retcode
369

    
370

    
371
def SetInstanceParms(opts, args):
372
  """Modifies an instance.
373

    
374
  All parameters take effect only at the next restart of the instance.
375

    
376
  Args:
377
    opts - class with options as members
378
    args - list with a single element, the instance name
379
  Opts used:
380
    memory - the new memory size
381
    vcpus - the new number of cpus
382

    
383
  """
384
  if not opts.mem and not opts.vcpus and not opts.ip and not opts.bridge:
385
    logger.ToStdout("Please give at least one of the parameters.")
386
    return 1
387

    
388
  op = opcodes.OpSetInstanceParms(instance_name=args[0], mem=opts.mem,
389
                                  vcpus=opts.vcpus, ip=opts.ip,
390
                                  bridge=opts.bridge)
391
  result = SubmitOpCode(op)
392

    
393
  if result:
394
    logger.ToStdout("Modified instance %s" % args[0])
395
    for param, data in result:
396
      logger.ToStdout(" - %-5s -> %s" % (param, data))
397
    logger.ToStdout("Please don't forget that these parameters take effect"
398
                    " only at the next start of the instance.")
399
  return 0
400

    
401

    
402
# options used in more than one cmd
403
node_opt = make_option("-n", "--node", dest="node", help="Target node",
404
                       metavar="<node>")
405
force_opt = make_option("-f", "--force", dest="force", action="store_true",
406
                        default=False, help="Force the operation")
407

    
408
# this is defined separately due to readability only
409
add_opts = [
410
  DEBUG_OPT,
411
  node_opt,
412
  cli_option("-s", "--os-size", dest="size", help="Disk size",
413
             default=20 * 1024, type="unit", metavar="<size>"),
414
  cli_option("--swap-size", dest="swap", help="Swap size",
415
             default=4 * 1024, type="unit", metavar="<size>"),
416
  cli_option("-o", "--os-type", dest="os", help="What OS to run",
417
             metavar="<os>"),
418
  cli_option("-m", "--memory", dest="mem", help="Memory size",
419
              default=128, type="unit", metavar="<mem>"),
420
  make_option("-p", "--cpu", dest="vcpus", help="Number of virtual CPUs",
421
              default=1, type="int", metavar="<PROC>"),
422
  make_option("-t", "--disk-template", dest="disk_template",
423
              help="Custom disk setup (diskless, plain, local_raid1 or"
424
              " remote_raid1)", default=None, metavar="TEMPL"),
425
  make_option("-i", "--ip", dest="ip",
426
              help="IP address ('none' [default], 'auto', or specify address)",
427
              default='none', type="string", metavar="<ADDRESS>"),
428
  make_option("--no-wait-for-sync", dest="wait_for_sync", default=True,
429
              action="store_false", help="Don't wait for sync (DANGEROUS!)"),
430
  make_option("--secondary-node", dest="snode",
431
              help="Secondary node for remote_raid1 disk layout",
432
              metavar="<node>"),
433
  make_option("-b", "--bridge", dest="bridge",
434
              help="Bridge to connect this instance to",
435
              default=None, metavar="<bridge>")
436
  ]
437

    
438

    
439
commands = {
440
  'add': (AddInstance, ARGS_ONE, add_opts,
441
          "[opts...] <name>",
442
          "Creates and adds a new instance to the cluster"),
443
  'add-mirror': (AddMDDRBDComponent, ARGS_ONE,
444
                [DEBUG_OPT, node_opt,
445
                 make_option("-b", "--disk", dest="disk", metavar="sdX",
446
                             help=("The name of the instance disk for which to"
447
                                   " add the mirror"))],
448
                "-n node -b disk <instance>",
449
                "Creates a new mirror for the instance"),
450
  'console': (ConnectToInstanceConsole, ARGS_ONE, [DEBUG_OPT],
451
              "<instance>",
452
              "Opens a console on the specified instance"),
453
  'failover': (FailoverInstance, ARGS_ONE,
454
               [DEBUG_OPT, force_opt,
455
                make_option("--ignore-consistency", dest="ignore_consistency",
456
                            action="store_true", default=False,
457
                            help="Ignore the consistency of the disks on"
458
                            " the secondary"),
459
                ],
460
               "[-f] <instance>",
461
               "Stops the instance and starts it on the backup node, using"
462
               " the remote mirror (only for instances of type remote_raid1)"),
463
  'info': (ShowInstanceConfig, ARGS_ANY, [DEBUG_OPT], "[<instance>...]",
464
           "Show information on the specified instance"),
465
  'list': (ListInstances, ARGS_NONE,
466
           [DEBUG_OPT, NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT],
467
           "", "Lists the instances and their status"),
468
  'remove': (RemoveInstance, ARGS_ONE, [DEBUG_OPT, force_opt],
469
             "[-f] <instance>", "Shuts down the instance and removes it"),
470
  'remove-mirror': (RemoveMDDRBDComponent, ARGS_ONE,
471
                   [DEBUG_OPT, node_opt,
472
                    make_option("-b", "--disk", dest="disk", metavar="sdX",
473
                                help=("The name of the instance disk"
474
                                      " for which to add the mirror")),
475
                    make_option("-p", "--port", dest="port", metavar="PORT",
476
                                help=("The port of the drbd device"
477
                                      " which to remove from the mirror"),
478
                                type="int"),
479
                    ],
480
                   "-b disk -p port <instance>",
481
                   "Removes a mirror from the instance"),
482
  'replace-disks': (ReplaceDisks, ARGS_ONE,
483
                    [DEBUG_OPT,
484
                     make_option("-n", "--new-secondary", dest="new_secondary",
485
                                 metavar="NODE",
486
                                 help=("New secondary node (if you want to"
487
                                       " change the secondary)"))],
488
                    "[-n NODE] <instance>",
489
                    "Replaces all disks for the instance"),
490

    
491
  'modify': (SetInstanceParms, ARGS_ONE,
492
             [DEBUG_OPT, force_opt,
493
              cli_option("-m", "--memory", dest="mem",
494
                         help="Memory size",
495
                         default=None, type="unit", metavar="<mem>"),
496
              make_option("-p", "--cpu", dest="vcpus",
497
                          help="Number of virtual CPUs",
498
                          default=None, type="int", metavar="<PROC>"),
499
              make_option("-i", "--ip", dest="ip",
500
                          help="IP address ('none' or numeric IP)",
501
                          default=None, type="string", metavar="<ADDRESS>"),
502
              make_option("-b", "--bridge", dest="bridge",
503
                          help="Bridge to connect this instance to",
504
                          default=None, type="string", metavar="<bridge>")
505
              ],
506
             "<instance>", "Alters the parameters of an instance"),
507
  'shutdown': (ShutdownInstance, ARGS_ONE, [DEBUG_OPT],
508
               "<instance>", "Stops an instance"),
509
  'startup': (StartupInstance, ARGS_ONE,
510
              [DEBUG_OPT, force_opt,
511
               make_option("-e", "--extra", dest="extra_args",
512
                           help="Extra arguments for the instance's kernel",
513
                           default=None, type="string", metavar="<PARAMS>"),
514
               ],
515
            "<instance>", "Starts an instance"),
516
  'activate-disks': (ActivateDisks, ARGS_ONE, [DEBUG_OPT],
517
                     "<instance>",
518
                     "Activate an instance's disks"),
519
  'deactivate-disks': (DeactivateDisks, ARGS_ONE, [DEBUG_OPT],
520
                       "<instance>",
521
                       "Deactivate an instance's disks"),
522
  }
523

    
524
if __name__ == '__main__':
525
  retcode = GenericMain(commands)
526
  sys.exit(retcode)