Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 4331f6cd

History | View | Annotate | Download (21.6 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
# pylint: disable-msg=W0401,W0614
23
# W0401: Wildcard import ganeti.cli
24
# W0614: Unused import %s from wildcard import (since we need cli)
25

    
26
import sys
27
from optparse import make_option
28
import os.path
29

    
30
from ganeti.cli import *
31
from ganeti import opcodes
32
from ganeti import constants
33
from ganeti import errors
34
from ganeti import utils
35
from ganeti import bootstrap
36
from ganeti import ssh
37

    
38

    
39
@UsesRPC
40
def InitCluster(opts, args):
41
  """Initialize the cluster.
42

    
43
  @param opts: the command line options selected by the user
44
  @type args: list
45
  @param args: should contain only one element, the desired
46
      cluster name
47
  @rtype: int
48
  @return: the desired exit code
49

    
50
  """
51
  if not opts.lvm_storage and opts.vg_name:
52
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
53
    return 1
54

    
55
  vg_name = opts.vg_name
56
  if opts.lvm_storage and not opts.vg_name:
57
    vg_name = constants.DEFAULT_VG
58

    
59
  hvlist = opts.enabled_hypervisors
60
  if hvlist is not None:
61
    hvlist = hvlist.split(",")
62
  else:
63
    hvlist = [constants.DEFAULT_ENABLED_HYPERVISOR]
64

    
65
  # avoid an impossible situation
66
  if opts.default_hypervisor in hvlist:
67
    default_hypervisor = opts.default_hypervisor
68
  else:
69
    default_hypervisor = hvlist[0]
70

    
71
  hvparams = opts.hvparams
72
  if hvparams:
73
    # a list of (name, dict) we can pass directly to dict()
74
    hvparams = dict(opts.hvparams)
75
  else:
76
    # otherwise init as empty dict
77
    hvparams = {}
78

    
79
  beparams = opts.beparams
80
  # check for invalid parameters
81
  for parameter in beparams:
82
    if parameter not in constants.BES_PARAMETERS:
83
      ToStderr("Invalid backend parameter: %s", parameter)
84
      return 1
85

    
86
  # prepare beparams dict
87
  for parameter in constants.BES_PARAMETERS:
88
    if parameter not in beparams:
89
      beparams[parameter] = constants.BEC_DEFAULTS[parameter]
90

    
91
  # type wrangling
92
  try:
93
    beparams[constants.BE_VCPUS] = int(beparams[constants.BE_VCPUS])
94
  except ValueError:
95
    ToStderr("%s must be an integer", constants.BE_VCPUS)
96
    return 1
97

    
98
  if not isinstance(beparams[constants.BE_MEMORY], int):
99
    beparams[constants.BE_MEMORY] = utils.ParseUnit(
100
        beparams[constants.BE_MEMORY])
101

    
102
  # prepare hvparams dict
103
  for hv in constants.HYPER_TYPES:
104
    if hv not in hvparams:
105
      hvparams[hv] = {}
106
    for parameter in constants.HVC_DEFAULTS[hv]:
107
      if parameter not in hvparams[hv]:
108
        hvparams[hv][parameter] = constants.HVC_DEFAULTS[hv][parameter]
109

    
110
  for hv in hvlist:
111
    if hv not in constants.HYPER_TYPES:
112
      ToStderr("invalid hypervisor: %s", hv)
113
      return 1
114

    
115
  bootstrap.InitCluster(cluster_name=args[0],
116
                        secondary_ip=opts.secondary_ip,
117
                        vg_name=vg_name,
118
                        mac_prefix=opts.mac_prefix,
119
                        def_bridge=opts.def_bridge,
120
                        master_netdev=opts.master_netdev,
121
                        file_storage_dir=opts.file_storage_dir,
122
                        enabled_hypervisors=hvlist,
123
                        default_hypervisor=default_hypervisor,
124
                        hvparams=hvparams,
125
                        beparams=beparams)
126
  return 0
127

    
128

    
129
@UsesRPC
130
def DestroyCluster(opts, args):
131
  """Destroy the cluster.
132

    
133
  @param opts: the command line options selected by the user
134
  @type args: list
135
  @param args: should be an empty list
136
  @rtype: int
137
  @return: the desired exit code
138

    
139
  """
140
  if not opts.yes_do_it:
141
    ToStderr("Destroying a cluster is irreversible. If you really want"
142
             " destroy this cluster, supply the --yes-do-it option.")
143
    return 1
144

    
145
  op = opcodes.OpDestroyCluster()
146
  master = SubmitOpCode(op)
147
  # if we reached this, the opcode didn't fail; we can proceed to
148
  # shutdown all the daemons
149
  bootstrap.FinalizeClusterDestroy(master)
150
  return 0
151

    
152

    
153
def RenameCluster(opts, args):
154
  """Rename the cluster.
155

    
156
  @param opts: the command line options selected by the user
157
  @type args: list
158
  @param args: should contain only one element, the new cluster name
159
  @rtype: int
160
  @return: the desired exit code
161

    
162
  """
163
  name = args[0]
164
  if not opts.force:
165
    usertext = ("This will rename the cluster to '%s'. If you are connected"
166
                " over the network to the cluster name, the operation is very"
167
                " dangerous as the IP address will be removed from the node"
168
                " and the change may not go through. Continue?") % name
169
    if not AskUser(usertext):
170
      return 1
171

    
172
  op = opcodes.OpRenameCluster(name=name)
173
  SubmitOpCode(op)
174
  return 0
175

    
176

    
177
def ShowClusterVersion(opts, args):
178
  """Write version of ganeti software to the standard output.
179

    
180
  @param opts: the command line options selected by the user
181
  @type args: list
182
  @param args: should be an empty list
183
  @rtype: int
184
  @return: the desired exit code
185

    
186
  """
187
  op = opcodes.OpQueryClusterInfo()
188
  result = SubmitOpCode(op)
189
  ToStdout("Software version: %s", result["software_version"])
190
  ToStdout("Internode protocol: %s", result["protocol_version"])
191
  ToStdout("Configuration format: %s", result["config_version"])
192
  ToStdout("OS api version: %s", result["os_api_version"])
193
  ToStdout("Export interface: %s", result["export_version"])
194
  return 0
195

    
196

    
197
def ShowClusterMaster(opts, args):
198
  """Write name of master node to the standard output.
199

    
200
  @param opts: the command line options selected by the user
201
  @type args: list
202
  @param args: should be an empty list
203
  @rtype: int
204
  @return: the desired exit code
205

    
206
  """
207
  ToStdout("%s", GetClient().QueryConfigValues(["master_node"])[0])
208
  return 0
209

    
210

    
211
def ShowClusterConfig(opts, args):
212
  """Shows cluster information.
213

    
214
  @param opts: the command line options selected by the user
215
  @type args: list
216
  @param args: should be an empty list
217
  @rtype: int
218
  @return: the desired exit code
219

    
220
  """
221
  op = opcodes.OpQueryClusterInfo()
222
  result = SubmitOpCode(op)
223

    
224
  ToStdout("Cluster name: %s", result["name"])
225

    
226
  ToStdout("Master node: %s", result["master"])
227

    
228
  ToStdout("Architecture (this node): %s (%s)",
229
           result["architecture"][0], result["architecture"][1])
230

    
231
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
232
  ToStdout("Enabled hypervisors: %s", ", ".join(result["enabled_hypervisors"]))
233

    
234
  ToStdout("Hypervisor parameters:")
235
  for hv_name, hv_dict in result["hvparams"].items():
236
    ToStdout("  - %s:", hv_name)
237
    for item, val in hv_dict.iteritems():
238
      ToStdout("      %s: %s", item, val)
239

    
240
  ToStdout("Cluster parameters:")
241
  for gr_name, gr_dict in result["beparams"].items():
242
    ToStdout("  - %s:", gr_name)
243
    for item, val in gr_dict.iteritems():
244
      ToStdout("      %s: %s", item, val)
245

    
246
  return 0
247

    
248

    
249
def ClusterCopyFile(opts, args):
250
  """Copy a file from master to some nodes.
251

    
252
  @param opts: the command line options selected by the user
253
  @type args: list
254
  @param args: should contain only one element, the path of
255
      the file to be copied
256
  @rtype: int
257
  @return: the desired exit code
258

    
259
  """
260
  filename = args[0]
261
  if not os.path.exists(filename):
262
    raise errors.OpPrereqError("No such filename '%s'" % filename)
263

    
264
  cl = GetClient()
265

    
266
  myname = utils.HostInfo().name
267

    
268
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
269

    
270
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
271
  results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
272

    
273
  srun = ssh.SshRunner(cluster_name=cluster_name)
274
  for node in results:
275
    if not srun.CopyFileToNode(node, filename):
276
      ToStderr("Copy of file %s to node %s failed", filename, node)
277

    
278
  return 0
279

    
280

    
281
def RunClusterCommand(opts, args):
282
  """Run a command on some nodes.
283

    
284
  @param opts: the command line options selected by the user
285
  @type args: list
286
  @param args: should contain the command to be run and its arguments
287
  @rtype: int
288
  @return: the desired exit code
289

    
290
  """
291
  cl = GetClient()
292

    
293
  command = " ".join(args)
294
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
295
  nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
296

    
297
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
298
                                                    "master_node"])
299

    
300
  srun = ssh.SshRunner(cluster_name=cluster_name)
301

    
302
  # Make sure master node is at list end
303
  if master_node in nodes:
304
    nodes.remove(master_node)
305
    nodes.append(master_node)
306

    
307
  for name in nodes:
308
    result = srun.Run(name, "root", command)
309
    ToStdout("------------------------------------------------")
310
    ToStdout("node: %s", name)
311
    ToStdout("%s", result.output)
312
    ToStdout("return code = %s", result.exit_code)
313

    
314
  return 0
315

    
316

    
317
def VerifyCluster(opts, args):
318
  """Verify integrity of cluster, performing various test on nodes.
319

    
320
  @param opts: the command line options selected by the user
321
  @type args: list
322
  @param args: should be an empty list
323
  @rtype: int
324
  @return: the desired exit code
325

    
326
  """
327
  skip_checks = []
328
  if opts.skip_nplusone_mem:
329
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
330
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
331
  if SubmitOpCode(op):
332
    return 0
333
  else:
334
    return 1
335

    
336

    
337
def VerifyDisks(opts, args):
338
  """Verify integrity of cluster disks.
339

    
340
  @param opts: the command line options selected by the user
341
  @type args: list
342
  @param args: should be an empty list
343
  @rtype: int
344
  @return: the desired exit code
345

    
346
  """
347
  op = opcodes.OpVerifyDisks()
348
  result = SubmitOpCode(op)
349
  if not isinstance(result, (list, tuple)) or len(result) != 4:
350
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
351

    
352
  nodes, nlvm, instances, missing = result
353

    
354
  if nodes:
355
    ToStdout("Nodes unreachable or with bad data:")
356
    for name in nodes:
357
      ToStdout("\t%s", name)
358
  retcode = constants.EXIT_SUCCESS
359

    
360
  if nlvm:
361
    for node, text in nlvm.iteritems():
362
      ToStdout("Error on node %s: LVM error: %s",
363
               node, text[-400:].encode('string_escape'))
364
      retcode |= 1
365
      ToStdout("You need to fix these nodes first before fixing instances")
366

    
367
  if instances:
368
    for iname in instances:
369
      if iname in missing:
370
        continue
371
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
372
      try:
373
        ToStdout("Activating disks for instance '%s'", iname)
374
        SubmitOpCode(op)
375
      except errors.GenericError, err:
376
        nret, msg = FormatError(err)
377
        retcode |= nret
378
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
379

    
380
  if missing:
381
    for iname, ival in missing.iteritems():
382
      all_missing = utils.all(ival, lambda x: x[0] in nlvm)
383
      if all_missing:
384
        ToStdout("Instance %s cannot be verified as it lives on"
385
                 " broken nodes", iname)
386
      else:
387
        ToStdout("Instance %s has missing logical volumes:", iname)
388
        ival.sort()
389
        for node, vol in ival:
390
          if node in nlvm:
391
            ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
392
          else:
393
            ToStdout("\t%s /dev/xenvg/%s", node, vol)
394
    ToStdout("You need to run replace_disks for all the above"
395
           " instances, if this message persist after fixing nodes.")
396
    retcode |= 1
397

    
398
  return retcode
399

    
400

    
401
@UsesRPC
402
def MasterFailover(opts, args):
403
  """Failover the master node.
404

    
405
  This command, when run on a non-master node, will cause the current
406
  master to cease being master, and the non-master to become new
407
  master.
408

    
409
  @param opts: the command line options selected by the user
410
  @type args: list
411
  @param args: should be an empty list
412
  @rtype: int
413
  @return: the desired exit code
414

    
415
  """
416
  return bootstrap.MasterFailover()
417

    
418

    
419
def SearchTags(opts, args):
420
  """Searches the tags on all the cluster.
421

    
422
  @param opts: the command line options selected by the user
423
  @type args: list
424
  @param args: should contain only one element, the tag pattern
425
  @rtype: int
426
  @return: the desired exit code
427

    
428
  """
429
  op = opcodes.OpSearchTags(pattern=args[0])
430
  result = SubmitOpCode(op)
431
  if not result:
432
    return 1
433
  result = list(result)
434
  result.sort()
435
  for path, tag in result:
436
    ToStdout("%s %s", path, tag)
437

    
438

    
439
def SetClusterParams(opts, args):
440
  """Modify the cluster.
441

    
442
  @param opts: the command line options selected by the user
443
  @type args: list
444
  @param args: should be an empty list
445
  @rtype: int
446
  @return: the desired exit code
447

    
448
  """
449
  if not (not opts.lvm_storage or opts.vg_name or
450
          opts.enabled_hypervisors or opts.hvparams or
451
          opts.beparams):
452
    ToStderr("Please give at least one of the parameters.")
453
    return 1
454

    
455
  vg_name = opts.vg_name
456
  if not opts.lvm_storage and opts.vg_name:
457
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
458
    return 1
459

    
460
  hvlist = opts.enabled_hypervisors
461
  if hvlist is not None:
462
    hvlist = hvlist.split(",")
463

    
464
  hvparams = opts.hvparams
465
  if hvparams:
466
    # a list of (name, dict) we can pass directly to dict()
467
    hvparams = dict(opts.hvparams)
468

    
469
  beparams = opts.beparams
470

    
471
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
472
                                  enabled_hypervisors=hvlist,
473
                                  hvparams=hvparams,
474
                                  beparams=beparams)
475
  SubmitOpCode(op)
476
  return 0
477

    
478

    
479
def QueueOps(opts, args):
480
  """Queue operations.
481

    
482
  @param opts: the command line options selected by the user
483
  @type args: list
484
  @param args: should contain only one element, the subcommand
485
  @rtype: int
486
  @return: the desired exit code
487

    
488
  """
489
  command = args[0]
490
  client = GetClient()
491
  if command in ("drain", "undrain"):
492
    drain_flag = command == "drain"
493
    client.SetQueueDrainFlag(drain_flag)
494
  elif command == "info":
495
    result = client.QueryConfigValues(["drain_flag"])
496
    if result[0]:
497
      val = "set"
498
    else:
499
      val = "unset"
500
    ToStdout("The drain flag is %s" % val)
501
  return 0
502

    
503
# this is an option common to more than one command, so we declare
504
# it here and reuse it
505
node_option = make_option("-n", "--node", action="append", dest="nodes",
506
                          help="Node to copy to (if not given, all nodes),"
507
                               " can be given multiple times",
508
                          metavar="<node>", default=[])
509

    
510
commands = {
511
  'init': (InitCluster, ARGS_ONE,
512
           [DEBUG_OPT,
513
            make_option("-s", "--secondary-ip", dest="secondary_ip",
514
                        help="Specify the secondary ip for this node;"
515
                        " if given, the entire cluster must have secondary"
516
                        " addresses",
517
                        metavar="ADDRESS", default=None),
518
            make_option("-m", "--mac-prefix", dest="mac_prefix",
519
                        help="Specify the mac prefix for the instance IP"
520
                        " addresses, in the format XX:XX:XX",
521
                        metavar="PREFIX",
522
                        default="aa:00:00",),
523
            make_option("-g", "--vg-name", dest="vg_name",
524
                        help="Specify the volume group name "
525
                        " (cluster-wide) for disk allocation [xenvg]",
526
                        metavar="VG",
527
                        default=None,),
528
            make_option("-b", "--bridge", dest="def_bridge",
529
                        help="Specify the default bridge name (cluster-wide)"
530
                          " to connect the instances to [%s]" %
531
                          constants.DEFAULT_BRIDGE,
532
                        metavar="BRIDGE",
533
                        default=constants.DEFAULT_BRIDGE,),
534
            make_option("--master-netdev", dest="master_netdev",
535
                        help="Specify the node interface (cluster-wide)"
536
                          " on which the master IP address will be added "
537
                          " [%s]" % constants.DEFAULT_BRIDGE,
538
                        metavar="NETDEV",
539
                        default=constants.DEFAULT_BRIDGE,),
540
            make_option("--file-storage-dir", dest="file_storage_dir",
541
                        help="Specify the default directory (cluster-wide)"
542
                             " for storing the file-based disks [%s]" %
543
                             constants.DEFAULT_FILE_STORAGE_DIR,
544
                        metavar="DIR",
545
                        default=constants.DEFAULT_FILE_STORAGE_DIR,),
546
            make_option("--no-lvm-storage", dest="lvm_storage",
547
                        help="No support for lvm based instances"
548
                             " (cluster-wide)",
549
                        action="store_false", default=True,),
550
            make_option("--enabled-hypervisors", dest="enabled_hypervisors",
551
                        help="Comma-separated list of hypervisors",
552
                        type="string", default=None),
553
            make_option("-t", "--default-hypervisor",
554
                        dest="default_hypervisor",
555
                        help="Default hypervisor to use for instance creation",
556
                        choices=list(constants.HYPER_TYPES),
557
                        default=constants.DEFAULT_ENABLED_HYPERVISOR),
558
            ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
559
                       help="Hypervisor and hypervisor options, in the"
560
                         " format"
561
                       " hypervisor:option=value,option=value,...",
562
                       default=[],
563
                       action="append",
564
                       type="identkeyval"),
565
            keyval_option("-B", "--backend-parameters", dest="beparams",
566
                          type="keyval", default={},
567
                          help="Backend parameters"),
568
            ],
569
           "[opts...] <cluster_name>",
570
           "Initialises a new cluster configuration"),
571
  'destroy': (DestroyCluster, ARGS_NONE,
572
              [DEBUG_OPT,
573
               make_option("--yes-do-it", dest="yes_do_it",
574
                           help="Destroy cluster",
575
                           action="store_true"),
576
              ],
577
              "", "Destroy cluster"),
578
  'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
579
               "<new_name>",
580
               "Renames the cluster"),
581
  'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
582
             make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
583
                         help="Skip N+1 memory redundancy tests",
584
                         action="store_true",
585
                         default=False,),
586
             ],
587
             "", "Does a check on the cluster configuration"),
588
  'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
589
                   "", "Does a check on the cluster disk status"),
590
  'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
591
                     "", "Makes the current node the master"),
592
  'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
593
              "", "Shows the cluster version"),
594
  'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
595
                "", "Shows the cluster master"),
596
  'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
597
               "[-n node...] <filename>",
598
               "Copies a file to all (or only some) nodes"),
599
  'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
600
              "[-n node...] <command>",
601
              "Runs a command on all (or only some) nodes"),
602
  'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
603
                 "", "Show cluster configuration"),
604
  'list-tags': (ListTags, ARGS_NONE,
605
                [DEBUG_OPT], "", "List the tags of the cluster"),
606
  'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
607
               "tag...", "Add tags to the cluster"),
608
  'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
609
                  "tag...", "Remove tags from the cluster"),
610
  'search-tags': (SearchTags, ARGS_ONE,
611
                  [DEBUG_OPT], "", "Searches the tags on all objects on"
612
                  " the cluster for a given pattern (regex)"),
613
  'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
614
            "drain|undrain|info", "Change queue properties"),
615
  'modify': (SetClusterParams, ARGS_NONE,
616
             [DEBUG_OPT,
617
              make_option("-g", "--vg-name", dest="vg_name",
618
                          help="Specify the volume group name "
619
                          " (cluster-wide) for disk allocation "
620
                          "and enable lvm based storage",
621
                          metavar="VG",),
622
              make_option("--no-lvm-storage", dest="lvm_storage",
623
                          help="Disable support for lvm based instances"
624
                               " (cluster-wide)",
625
                          action="store_false", default=True,),
626
              make_option("--enabled-hypervisors", dest="enabled_hypervisors",
627
                          help="Comma-separated list of hypervisors",
628
                          type="string", default=None),
629
              ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
630
                         help="Hypervisor and hypervisor options, in the"
631
                         " format"
632
                         " hypervisor:option=value,option=value,...",
633
                         default=[],
634
                         action="append",
635
                         type="identkeyval"),
636
              keyval_option("-B", "--backend-parameters", dest="beparams",
637
                            type="keyval", default={},
638
                            help="Backend parameters"),
639
              ],
640
             "[opts...]",
641
             "Alters the parameters of the cluster"),
642
  }
643

    
644
if __name__ == '__main__':
645
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))