Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 098c0958

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
  instance = args[0]
86

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

    
98

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

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

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

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

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

    
121

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

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

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

    
138

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

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

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

    
151

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

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

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

    
166

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

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

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

    
180

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

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

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

    
195

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

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

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

    
210

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

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

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

    
226

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

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

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

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

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

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

    
256

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

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

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

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

    
278

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

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

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

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

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

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

    
330

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

    
334
  """
335
  retcode = 0
336
  op = opcodes.OpQueryInstanceData(instances=args)
337
  result = SubmitOpCode(op)
338

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

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

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

    
365
  logger.ToStdout(buf.getvalue().rstrip('\n'))
366
  return retcode
367

    
368

    
369
def SetInstanceParms(opts, args):
370
  """Modifies an instance.
371

    
372
  All parameters take effect only at the next restart of the instance.
373

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

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

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

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

    
399

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

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

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

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

    
521
if __name__ == '__main__':
522
  retcode = GenericMain(commands)
523
  sys.exit(retcode)