Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ 16be8703

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

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

    
71
  return 0
72

    
73

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

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

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

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

    
100

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

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

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

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

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

    
121
  return 0
122

    
123

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

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

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

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

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

    
146

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

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

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

    
163

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

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

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

    
176

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

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

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

    
191

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

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

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

    
205

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

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

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

    
220

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

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

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

    
235

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

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

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

    
251

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

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

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

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

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

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

    
280

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

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

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

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

    
302

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

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

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

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

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

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

    
354

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

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

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

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

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

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

    
392

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

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

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

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

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

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

    
423

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

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

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

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

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