gnt-cluster 17.6 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/python
#

# Copyright (C) 2006, 2007 Google Inc.
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.


import sys
from optparse import make_option
import pprint
25
import os.path
Iustin Pop's avatar
Iustin Pop committed
26
27
28

from ganeti.cli import *
from ganeti import opcodes
29
from ganeti import constants
30
from ganeti import errors
31
from ganeti import utils
32
from ganeti import bootstrap
33
34
from ganeti import ssh
from ganeti import ssconf
Iustin Pop's avatar
Iustin Pop committed
35
36
37
38
39
40
41
42
43
44


def InitCluster(opts, args):
  """Initialize the cluster.

  Args:
    opts - class with options as members
    args - list of arguments, expected to be [clustername]

  """
45
46
47
48
49
50
51
52
  if not opts.lvm_storage and opts.vg_name:
    print ("Options --no-lvm-storage and --vg-name conflict.")
    return 1

  vg_name = opts.vg_name
  if opts.lvm_storage and not opts.vg_name:
    vg_name = constants.DEFAULT_VG

53
54
55
56
57
58
59
60
  bootstrap.InitCluster(cluster_name=args[0],
                        secondary_ip=opts.secondary_ip,
                        hypervisor_type=opts.hypervisor_type,
                        vg_name=vg_name,
                        mac_prefix=opts.mac_prefix,
                        def_bridge=opts.def_bridge,
                        master_netdev=opts.master_netdev,
                        file_storage_dir=opts.file_storage_dir)
Iustin Pop's avatar
Iustin Pop committed
61
62
63
64
65
66
67
68
  return 0


def DestroyCluster(opts, args):
  """Destroy the cluster.

  Args:
    opts - class with options as members
69

Iustin Pop's avatar
Iustin Pop committed
70
71
72
  """
  if not opts.yes_do_it:
    print ("Destroying a cluster is irreversibly. If you really want destroy"
Iustin Pop's avatar
Iustin Pop committed
73
           " this cluster, supply the --yes-do-it option.")
Iustin Pop's avatar
Iustin Pop committed
74
75
76
    return 1

  op = opcodes.OpDestroyCluster()
Iustin Pop's avatar
Iustin Pop committed
77
78
79
80
  master = SubmitOpCode(op)
  # if we reached this, the opcode didn't fail; we can proceed to
  # shutdown all the daemons
  bootstrap.FinalizeClusterDestroy(master)
Iustin Pop's avatar
Iustin Pop committed
81
82
83
  return 0


84
85
86
87
88
89
90
91
92
93
94
95
96
97
def RenameCluster(opts, args):
  """Rename the cluster.

  Args:
    opts - class with options as members, we use force only
    args - list of arguments, expected to be [new_name]

  """
  name = args[0]
  if not opts.force:
    usertext = ("This will rename the cluster to '%s'. If you are connected"
                " over the network to the cluster name, the operation is very"
                " dangerous as the IP address will be removed from the node"
                " and the change may not go through. Continue?") % name
98
    if not AskUser(usertext):
99
100
101
102
103
104
105
      return 1

  op = opcodes.OpRenameCluster(name=name)
  SubmitOpCode(op)
  return 0


Iustin Pop's avatar
Iustin Pop committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
def ShowClusterVersion(opts, args):
  """Write version of ganeti software to the standard output.

  Args:
    opts - class with options as members

  """
  op = opcodes.OpQueryClusterInfo()
  result = SubmitOpCode(op)
  print ("Software version: %s" % result["software_version"])
  print ("Internode protocol: %s" % result["protocol_version"])
  print ("Configuration format: %s" % result["config_version"])
  print ("OS api version: %s" % result["os_api_version"])
  print ("Export interface: %s" % result["export_version"])
  return 0


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

  Args:
    opts - class with options as members

  """
Michael Hanselmann's avatar
Michael Hanselmann committed
130
  print GetClient().QueryConfigValues(["master_node"])[0]
Iustin Pop's avatar
Iustin Pop committed
131
132
133
134
135
136
137
138
139
140
141
142
143
144
  return 0


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

  """
  op = opcodes.OpQueryClusterInfo()
  result = SubmitOpCode(op)

  print ("Cluster name: %s" % result["name"])

  print ("Master node: %s" % result["master"])

145
146
  print ("Architecture (this node): %s (%s)" %
         (result["architecture"][0], result["architecture"][1]))
Iustin Pop's avatar
Iustin Pop committed
147

148
149
150
151
152
153
154
155
156
157
158
159
160
161
  print ("Default hypervisor: %s" % result["hypervisor_type"])
  print ("Enabled hypervisors: %s" % ", ".join(result["enabled_hypervisors"]))

  print "Hypervisor parameters:"
  for hv_name, hv_dict in result["hvparams"].items():
    print "  - %s:" % hv_name
    for item, val in hv_dict.iteritems():
      print "      %s: %s" % (item, val)

  print "Cluster parameters:"
  for gr_name, gr_dict in result["beparams"].items():
    print "  - %s:" % gr_name
    for item, val in gr_dict.iteritems():
      print "      %s: %s" % (item, val)
162

Iustin Pop's avatar
Iustin Pop committed
163
164
165
166
167
168
169
170
171
172
173
174
175
  return 0


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

  Args:
    opts - class with options as members
    args - list containing a single element, the file name
  Opts used:
    nodes - list containing the name of target nodes; if empty, all nodes

  """
176
177
178
179
  filename = args[0]
  if not os.path.exists(filename):
    raise errors.OpPrereqError("No such filename '%s'" % filename)

Iustin Pop's avatar
Iustin Pop committed
180
181
  cl = GetClient()

182
183
  myname = utils.HostInfo().name

Iustin Pop's avatar
Iustin Pop committed
184
185
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]

186
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
Iustin Pop's avatar
Iustin Pop committed
187
  results = [row[0] for row in SubmitOpCode(op, cl=cl) if row[0] != myname]
Michael Hanselmann's avatar
Michael Hanselmann committed
188

Iustin Pop's avatar
Iustin Pop committed
189
  srun = ssh.SshRunner(cluster_name=cluster_name)
190
191
192
193
194
  for node in results:
    if not srun.CopyFileToNode(node, filename):
      print >> sys.stderr, ("Copy of file %s to node %s failed" %
                            (filename, node))

Iustin Pop's avatar
Iustin Pop committed
195
196
197
198
199
200
201
202
203
204
205
206
207
  return 0


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

  Args:
    opts - class with options as members
    args - the command list as a list
  Opts used:
    nodes: list containing the name of target nodes; if empty, all nodes

  """
Iustin Pop's avatar
Iustin Pop committed
208
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
209

Iustin Pop's avatar
Iustin Pop committed
210
  command = " ".join(args)
211
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
Iustin Pop's avatar
Iustin Pop committed
212
213
214
215
  nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]

  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
                                                    "master_node"])
216

Iustin Pop's avatar
Iustin Pop committed
217
  srun = ssh.SshRunner(cluster_name=cluster_name)
218

Michael Hanselmann's avatar
Michael Hanselmann committed
219
  # Make sure master node is at list end
220
221
222
223
224
225
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
Iustin Pop's avatar
Iustin Pop committed
226
    print ("------------------------------------------------")
227
228
229
230
231
    print ("node: %s" % name)
    print ("%s" % result.output)
    print ("return code = %s" % result.exit_code)

  return 0
Iustin Pop's avatar
Iustin Pop committed
232
233
234
235
236
237
238
239
240


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

  Args:
    opts - class with options as members

  """
Iustin Pop's avatar
Iustin Pop committed
241
  skip_checks = []
242
243
244
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
245
246
247
248
  if SubmitOpCode(op):
    return 0
  else:
    return 1
Iustin Pop's avatar
Iustin Pop committed
249
250


251
252
253
254
255
256
257
258
259
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

  Args:
    opts - class with options as members

  """
  op = opcodes.OpVerifyDisks()
  result = SubmitOpCode(op)
260
  if not isinstance(result, (list, tuple)) or len(result) != 4:
261
262
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

263
264
  nodes, nlvm, instances, missing = result

265
266
267
268
269
  if nodes:
    print "Nodes unreachable or with bad data:"
    for name in nodes:
      print "\t%s" % name
  retcode = constants.EXIT_SUCCESS
270
271
272
273
274
275
276
277

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

278
279
  if instances:
    for iname in instances:
280
281
      if iname in missing:
        continue
282
283
284
285
286
287
288
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
      try:
        print "Activating disks for instance '%s'" % iname
        SubmitOpCode(op)
      except errors.GenericError, err:
        nret, msg = FormatError(err)
        retcode |= nret
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
        print >> sys.stderr, ("Error activating disks for instance %s: %s" %
                              (iname, msg))

  if missing:
    for iname, ival in missing.iteritems():
      all_missing = utils.all(ival, lambda x: x[0] in nlvm)
      if all_missing:
        print ("Instance %s cannot be verified as it lives on"
               " broken nodes" % iname)
      else:
        print "Instance %s has missing logical volumes:" % iname
        ival.sort()
        for node, vol in ival:
          if node in nlvm:
            print ("\tbroken node %s /dev/xenvg/%s" % (node, vol))
          else:
            print ("\t%s /dev/xenvg/%s" % (node, vol))
    print ("You need to run replace_disks for all the above"
           " instances, if this message persist after fixing nodes.")
    retcode |= 1
309
310
311
312

  return retcode


Iustin Pop's avatar
Iustin Pop committed
313
314
315
316
317
318
319
320
def MasterFailover(opts, args):
  """Failover the master node.

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

  """
321
  return bootstrap.MasterFailover()
Iustin Pop's avatar
Iustin Pop committed
322
323


Iustin Pop's avatar
Iustin Pop committed
324
325
326
327
328
329
330
331
332
333
334
335
336
337
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

  """
  op = opcodes.OpSearchTags(pattern=args[0])
  result = SubmitOpCode(op)
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
    print "%s %s" % (path, tag)


338
339
340
341
342
343
344
def SetClusterParams(opts, args):
  """Modify the cluster.

  Args:
    opts - class with options as members

  """
345
346
347
  if not (not opts.lvm_storage or opts.vg_name or
          opts.enabled_hypervisors or opts.hvparams or
          opts.beparams):
348
349
350
351
352
353
354
355
    print "Please give at least one of the parameters."
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
    print ("Options --no-lvm-storage and --vg-name conflict.")
    return 1

356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

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

  beparams = opts.beparams

  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
                                  beparams=beparams)
371
372
373
374
  SubmitOpCode(op)
  return 0


375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
def QueueOps(opts, args):
  """Queue operations.

  """
  command = args[0]
  client = GetClient()
  if command in ("drain", "undrain"):
    drain_flag = command == "drain"
    client.SetQueueDrainFlag(drain_flag)
  elif command == "info":
    result = client.QueryConfigValues(["drain_flag"])
    print "The drain flag is",
    if result[0]:
      print "set"
    else:
      print "unset"
  return 0

Iustin Pop's avatar
Iustin Pop committed
393
394
395
# this is an option common to more than one command, so we declare
# it here and reuse it
node_option = make_option("-n", "--node", action="append", dest="nodes",
396
397
398
                          help="Node to copy to (if not given, all nodes),"
                               " can be given multiple times",
                          metavar="<node>", default=[])
Iustin Pop's avatar
Iustin Pop committed
399
400
401
402
403
404
405
406
407
408

commands = {
  'init': (InitCluster, ARGS_ONE,
           [DEBUG_OPT,
            make_option("-s", "--secondary-ip", dest="secondary_ip",
                        help="Specify the secondary ip for this node;"
                        " if given, the entire cluster must have secondary"
                        " addresses",
                        metavar="ADDRESS", default=None),
            make_option("-t", "--hypervisor-type", dest="hypervisor_type",
409
                        help="Specify the hypervisor type "
410
411
                        "(xen-pvm, kvm, fake, xen-hvm)",
                        metavar="TYPE", choices=["xen-pvm",
412
                                                 "kvm",
413
                                                 "fake",
414
415
                                                 "xen-hvm"],
                        default="xen-pvm",),
Iustin Pop's avatar
Iustin Pop committed
416
417
418
419
420
421
422
423
424
            make_option("-m", "--mac-prefix", dest="mac_prefix",
                        help="Specify the mac prefix for the instance IP"
                        " addresses, in the format XX:XX:XX",
                        metavar="PREFIX",
                        default="aa:00:00",),
            make_option("-g", "--vg-name", dest="vg_name",
                        help="Specify the volume group name "
                        " (cluster-wide) for disk allocation [xenvg]",
                        metavar="VG",
425
                        default=None,),
Iustin Pop's avatar
Iustin Pop committed
426
427
            make_option("-b", "--bridge", dest="def_bridge",
                        help="Specify the default bridge name (cluster-wide)"
428
429
                          " to connect the instances to [%s]" %
                          constants.DEFAULT_BRIDGE,
Iustin Pop's avatar
Iustin Pop committed
430
                        metavar="BRIDGE",
431
                        default=constants.DEFAULT_BRIDGE,),
432
433
            make_option("--master-netdev", dest="master_netdev",
                        help="Specify the node interface (cluster-wide)"
434
435
                          " on which the master IP address will be added "
                          " [%s]" % constants.DEFAULT_BRIDGE,
436
                        metavar="NETDEV",
437
                        default=constants.DEFAULT_BRIDGE,),
438
439
440
441
442
443
            make_option("--file-storage-dir", dest="file_storage_dir",
                        help="Specify the default directory (cluster-wide)"
                             " for storing the file-based disks [%s]" %
                             constants.DEFAULT_FILE_STORAGE_DIR,
                        metavar="DIR",
                        default=constants.DEFAULT_FILE_STORAGE_DIR,),
444
445
446
447
            make_option("--no-lvm-storage", dest="lvm_storage",
                        help="No support for lvm based instances"
                             " (cluster-wide)",
                        action="store_false", default=True,),
Iustin Pop's avatar
Iustin Pop committed
448
            ],
449
           "[opts...] <cluster_name>",
Iustin Pop's avatar
Iustin Pop committed
450
451
452
453
454
455
456
           "Initialises a new cluster configuration"),
  'destroy': (DestroyCluster, ARGS_NONE,
              [DEBUG_OPT,
               make_option("--yes-do-it", dest="yes_do_it",
                           help="Destroy cluster",
                           action="store_true"),
              ],
457
              "", "Destroy cluster"),
458
  'rename': (RenameCluster, ARGS_ONE, [DEBUG_OPT, FORCE_OPT],
459
               "<new_name>",
460
               "Renames the cluster"),
461
462
463
464
465
466
  'verify': (VerifyCluster, ARGS_NONE, [DEBUG_OPT,
             make_option("--no-nplus1-mem", dest="skip_nplusone_mem",
                         help="Skip N+1 memory redundancy tests",
                         action="store_true",
                         default=False,),
             ],
467
             "", "Does a check on the cluster configuration"),
468
  'verify-disks': (VerifyDisks, ARGS_NONE, [DEBUG_OPT],
469
                   "", "Does a check on the cluster disk status"),
Iustin Pop's avatar
Iustin Pop committed
470
  'masterfailover': (MasterFailover, ARGS_NONE, [DEBUG_OPT],
471
                     "", "Makes the current node the master"),
Iustin Pop's avatar
Iustin Pop committed
472
  'version': (ShowClusterVersion, ARGS_NONE, [DEBUG_OPT],
473
              "", "Shows the cluster version"),
Iustin Pop's avatar
Iustin Pop committed
474
  'getmaster': (ShowClusterMaster, ARGS_NONE, [DEBUG_OPT],
475
                "", "Shows the cluster master"),
Iustin Pop's avatar
Iustin Pop committed
476
  'copyfile': (ClusterCopyFile, ARGS_ONE, [DEBUG_OPT, node_option],
477
               "[-n node...] <filename>",
Iustin Pop's avatar
Iustin Pop committed
478
479
               "Copies a file to all (or only some) nodes"),
  'command': (RunClusterCommand, ARGS_ATLEAST(1), [DEBUG_OPT, node_option],
480
              "[-n node...] <command>",
Iustin Pop's avatar
Iustin Pop committed
481
482
              "Runs a command on all (or only some) nodes"),
  'info': (ShowClusterConfig, ARGS_NONE, [DEBUG_OPT],
483
                 "", "Show cluster configuration"),
484
  'list-tags': (ListTags, ARGS_NONE,
485
                [DEBUG_OPT], "", "List the tags of the cluster"),
486
  'add-tags': (AddTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
487
               "tag...", "Add tags to the cluster"),
488
  'remove-tags': (RemoveTags, ARGS_ANY, [DEBUG_OPT, TAG_SRC_OPT],
489
                  "tag...", "Remove tags from the cluster"),
Iustin Pop's avatar
Iustin Pop committed
490
  'search-tags': (SearchTags, ARGS_ONE,
491
                  [DEBUG_OPT], "", "Searches the tags on all objects on"
Iustin Pop's avatar
Iustin Pop committed
492
                  " the cluster for a given pattern (regex)"),
493
494
  'queue': (QueueOps, ARGS_ONE, [DEBUG_OPT],
            "drain|undrain|info", "Change queue properties"),
495
496
497
498
499
500
501
502
503
504
505
  'modify': (SetClusterParams, ARGS_NONE,
             [DEBUG_OPT,
              make_option("-g", "--vg-name", dest="vg_name",
                          help="Specify the volume group name "
                          " (cluster-wide) for disk allocation "
                          "and enable lvm based storage",
                          metavar="VG",),
              make_option("--no-lvm-storage", dest="lvm_storage",
                          help="Disable support for lvm based instances"
                               " (cluster-wide)",
                          action="store_false", default=True,),
506
507
508
509
510
511
512
513
514
515
516
517
518
              make_option("--enabled-hypervisors", dest="enabled_hypervisors",
                          help="Comma-separated list of hypervisors",
                          type="string", default=None),
              ikv_option("-H", "--hypervisor-parameters", dest="hvparams",
                         help="Hypervisor and hypervisor options, in the"
                         " format"
                         " hypervisor:option=value,option=value,...",
                         default=[],
                         action="append",
                         type="identkeyval"),
              keyval_option("-B", "--backend-parameters", dest="beparams",
                            type="keyval", default={},
                            help="Backend parameters"),
519
520
521
              ],
             "[opts...]",
             "Alters the parameters of the cluster"),
Iustin Pop's avatar
Iustin Pop committed
522
523
524
  }

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