Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-instance @ fe7b0351

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
  SubmitOpCode(op)
116

    
117
  return 0
118

    
119

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

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

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

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

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

    
142

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

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

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

    
159

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

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

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

    
172

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

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

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

    
187

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

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

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

    
201

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

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

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

    
216

    
217
def RemoveMDDRBDComponent(opts, args):
218
  """Connect to the console of an instance
219

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

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

    
231

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

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

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

    
247

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

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

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

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

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

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

    
276

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

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

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

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

    
298

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

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

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

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

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

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

    
350

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

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

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

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

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

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

    
388

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

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

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

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

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

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

    
419

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

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

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

    
540
if __name__ == '__main__':
541
  retcode = GenericMain(commands)
542
  sys.exit(retcode)