Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 8f348e36

History | View | Annotate | Download (22.1 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 = [opts.default_hypervisor]
64

    
65
  # avoid an impossible situation
66
  if opts.default_hypervisor not in hvlist:
67
    ToStderr("The default hypervisor requested (%s) is not"
68
             " within the enabled hypervisor list (%s)" %
69
             (opts.default_hypervisor, hvlist))
70
    return 1
71

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

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

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

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

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

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

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

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

    
129

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

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

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

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

    
153

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

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

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

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

    
177

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

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

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

    
197

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

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

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

    
211

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

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

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

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

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

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

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

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

    
241
  ToStdout("Cluster parameters:")
242
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
243

    
244
  ToStdout("Default instance parameters:")
245
  for gr_name, gr_dict in result["beparams"].items():
246
    ToStdout("  - %s:", gr_name)
247
    for item, val in gr_dict.iteritems():
248
      ToStdout("      %s: %s", item, val)
249

    
250
  return 0
251

    
252

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

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

    
263
  """
264
  filename = args[0]
265
  if not os.path.exists(filename):
266
    raise errors.OpPrereqError("No such filename '%s'" % filename)
267

    
268
  cl = GetClient()
269

    
270
  myname = utils.HostInfo().name
271

    
272
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
273

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

    
277
  srun = ssh.SshRunner(cluster_name=cluster_name)
278
  for node in results:
279
    if not srun.CopyFileToNode(node, filename):
280
      ToStderr("Copy of file %s to node %s failed", filename, node)
281

    
282
  return 0
283

    
284

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

    
288
  @param opts: the command line options selected by the user
289
  @type args: list
290
  @param args: should contain the command to be run and its arguments
291
  @rtype: int
292
  @return: the desired exit code
293

    
294
  """
295
  cl = GetClient()
296

    
297
  command = " ".join(args)
298
  op = opcodes.OpQueryNodes(output_fields=["name"], names=opts.nodes)
299
  nodes = [row[0] for row in SubmitOpCode(op, cl=cl)]
300

    
301
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
302
                                                    "master_node"])
303

    
304
  srun = ssh.SshRunner(cluster_name=cluster_name)
305

    
306
  # Make sure master node is at list end
307
  if master_node in nodes:
308
    nodes.remove(master_node)
309
    nodes.append(master_node)
310

    
311
  for name in nodes:
312
    result = srun.Run(name, "root", command)
313
    ToStdout("------------------------------------------------")
314
    ToStdout("node: %s", name)
315
    ToStdout("%s", result.output)
316
    ToStdout("return code = %s", result.exit_code)
317

    
318
  return 0
319

    
320

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

    
324
  @param opts: the command line options selected by the user
325
  @type args: list
326
  @param args: should be an empty list
327
  @rtype: int
328
  @return: the desired exit code
329

    
330
  """
331
  skip_checks = []
332
  if opts.skip_nplusone_mem:
333
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
334
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks)
335
  if SubmitOpCode(op):
336
    return 0
337
  else:
338
    return 1
339

    
340

    
341
def VerifyDisks(opts, args):
342
  """Verify integrity of cluster disks.
343

    
344
  @param opts: the command line options selected by the user
345
  @type args: list
346
  @param args: should be an empty list
347
  @rtype: int
348
  @return: the desired exit code
349

    
350
  """
351
  op = opcodes.OpVerifyDisks()
352
  result = SubmitOpCode(op)
353
  if not isinstance(result, (list, tuple)) or len(result) != 4:
354
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
355

    
356
  nodes, nlvm, instances, missing = result
357

    
358
  if nodes:
359
    ToStdout("Nodes unreachable or with bad data:")
360
    for name in nodes:
361
      ToStdout("\t%s", name)
362
  retcode = constants.EXIT_SUCCESS
363

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

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

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

    
402
  return retcode
403

    
404

    
405
@UsesRPC
406
def MasterFailover(opts, args):
407
  """Failover the master node.
408

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

    
413
  @param opts: the command line options selected by the user
414
  @type args: list
415
  @param args: should be an empty list
416
  @rtype: int
417
  @return: the desired exit code
418

    
419
  """
420
  return bootstrap.MasterFailover()
421

    
422

    
423
def SearchTags(opts, args):
424
  """Searches the tags on all the cluster.
425

    
426
  @param opts: the command line options selected by the user
427
  @type args: list
428
  @param args: should contain only one element, the tag pattern
429
  @rtype: int
430
  @return: the desired exit code
431

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

    
442

    
443
def SetClusterParams(opts, args):
444
  """Modify the cluster.
445

    
446
  @param opts: the command line options selected by the user
447
  @type args: list
448
  @param args: should be an empty list
449
  @rtype: int
450
  @return: the desired exit code
451

    
452
  """
453
  if not (not opts.lvm_storage or opts.vg_name or
454
          opts.enabled_hypervisors or opts.hvparams or
455
          opts.beparams or opts.candidate_pool_size is not None):
456
    ToStderr("Please give at least one of the parameters.")
457
    return 1
458

    
459
  vg_name = opts.vg_name
460
  if not opts.lvm_storage and opts.vg_name:
461
    ToStdout("Options --no-lvm-storage and --vg-name conflict.")
462
    return 1
463

    
464
  hvlist = opts.enabled_hypervisors
465
  if hvlist is not None:
466
    hvlist = hvlist.split(",")
467

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

    
473
  beparams = opts.beparams
474

    
475
  op = opcodes.OpSetClusterParams(vg_name=opts.vg_name,
476
                                  enabled_hypervisors=hvlist,
477
                                  hvparams=hvparams,
478
                                  beparams=beparams,
479
                                  candidate_pool_size=opts.candidate_pool_size)
480
  SubmitOpCode(op)
481
  return 0
482

    
483

    
484
def QueueOps(opts, args):
485
  """Queue operations.
486

    
487
  @param opts: the command line options selected by the user
488
  @type args: list
489
  @param args: should contain only one element, the subcommand
490
  @rtype: int
491
  @return: the desired exit code
492

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

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

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

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