Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 1410fa8d

History | View | Annotate | Download (26.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
"""Cluster related commands"""
22

    
23
# pylint: disable-msg=W0401,W0613,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0613: Unused argument, since all functions follow the same API
26
# W0614: Unused import %s from wildcard import (since we need cli)
27
# C0103: Invalid name gnt-cluster
28

    
29
import sys
30
import os.path
31
import time
32
import OpenSSL
33

    
34
from ganeti.cli import *
35
from ganeti import opcodes
36
from ganeti import constants
37
from ganeti import errors
38
from ganeti import utils
39
from ganeti import bootstrap
40
from ganeti import ssh
41
from ganeti import objects
42
from ganeti import uidpool
43
from ganeti import compat
44

    
45

    
46
@UsesRPC
47
def InitCluster(opts, args):
48
  """Initialize the cluster.
49

    
50
  @param opts: the command line options selected by the user
51
  @type args: list
52
  @param args: should contain only one element, the desired
53
      cluster name
54
  @rtype: int
55
  @return: the desired exit code
56

    
57
  """
58
  if not opts.lvm_storage and opts.vg_name:
59
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
60
    return 1
61

    
62
  vg_name = opts.vg_name
63
  if opts.lvm_storage and not opts.vg_name:
64
    vg_name = constants.DEFAULT_VG
65

    
66
  hvlist = opts.enabled_hypervisors
67
  if hvlist is None:
68
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
69
  hvlist = hvlist.split(",")
70

    
71
  hvparams = dict(opts.hvparams)
72
  beparams = opts.beparams
73
  nicparams = opts.nicparams
74

    
75
  # prepare beparams dict
76
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
77
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
78

    
79
  # prepare nicparams dict
80
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
81
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
82

    
83
  # prepare hvparams dict
84
  for hv in constants.HYPER_TYPES:
85
    if hv not in hvparams:
86
      hvparams[hv] = {}
87
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
88
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
89

    
90
  if opts.candidate_pool_size is None:
91
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
92

    
93
  if opts.mac_prefix is None:
94
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
95

    
96
  uid_pool = opts.uid_pool
97
  if uid_pool is not None:
98
    uid_pool = uidpool.ParseUidPool(uid_pool)
99

    
100
  bootstrap.InitCluster(cluster_name=args[0],
101
                        secondary_ip=opts.secondary_ip,
102
                        vg_name=vg_name,
103
                        mac_prefix=opts.mac_prefix,
104
                        master_netdev=opts.master_netdev,
105
                        file_storage_dir=opts.file_storage_dir,
106
                        enabled_hypervisors=hvlist,
107
                        hvparams=hvparams,
108
                        beparams=beparams,
109
                        nicparams=nicparams,
110
                        candidate_pool_size=opts.candidate_pool_size,
111
                        modify_etc_hosts=opts.modify_etc_hosts,
112
                        modify_ssh_setup=opts.modify_ssh_setup,
113
                        maintain_node_health=opts.maintain_node_health,
114
                        uid_pool=uid_pool,
115
                        )
116
  op = opcodes.OpPostInitCluster()
117
  SubmitOpCode(op, opts=opts)
118
  return 0
119

    
120

    
121
@UsesRPC
122
def DestroyCluster(opts, args):
123
  """Destroy the cluster.
124

    
125
  @param opts: the command line options selected by the user
126
  @type args: list
127
  @param args: should be an empty list
128
  @rtype: int
129
  @return: the desired exit code
130

    
131
  """
132
  if not opts.yes_do_it:
133
    ToStderr("Destroying a cluster is irreversible. If you really want"
134
             " destroy this cluster, supply the --yes-do-it option.")
135
    return 1
136

    
137
  op = opcodes.OpDestroyCluster()
138
  master = SubmitOpCode(op, opts=opts)
139
  # if we reached this, the opcode didn't fail; we can proceed to
140
  # shutdown all the daemons
141
  bootstrap.FinalizeClusterDestroy(master)
142
  return 0
143

    
144

    
145
def RenameCluster(opts, args):
146
  """Rename the cluster.
147

    
148
  @param opts: the command line options selected by the user
149
  @type args: list
150
  @param args: should contain only one element, the new cluster name
151
  @rtype: int
152
  @return: the desired exit code
153

    
154
  """
155
  name = args[0]
156
  if not opts.force:
157
    usertext = ("This will rename the cluster to '%s'. If you are connected"
158
                " over the network to the cluster name, the operation is very"
159
                " dangerous as the IP address will be removed from the node"
160
                " and the change may not go through. Continue?") % name
161
    if not AskUser(usertext):
162
      return 1
163

    
164
  op = opcodes.OpRenameCluster(name=name)
165
  SubmitOpCode(op, opts=opts)
166
  return 0
167

    
168

    
169
def RedistributeConfig(opts, args):
170
  """Forces push of the cluster configuration.
171

    
172
  @param opts: the command line options selected by the user
173
  @type args: list
174
  @param args: empty list
175
  @rtype: int
176
  @return: the desired exit code
177

    
178
  """
179
  op = opcodes.OpRedistributeConfig()
180
  SubmitOrSend(op, opts)
181
  return 0
182

    
183

    
184
def ShowClusterVersion(opts, args):
185
  """Write version of ganeti software to the standard output.
186

    
187
  @param opts: the command line options selected by the user
188
  @type args: list
189
  @param args: should be an empty list
190
  @rtype: int
191
  @return: the desired exit code
192

    
193
  """
194
  cl = GetClient()
195
  result = cl.QueryClusterInfo()
196
  ToStdout("Software version: %s", result["software_version"])
197
  ToStdout("Internode protocol: %s", result["protocol_version"])
198
  ToStdout("Configuration format: %s", result["config_version"])
199
  ToStdout("OS api version: %s", result["os_api_version"])
200
  ToStdout("Export interface: %s", result["export_version"])
201
  return 0
202

    
203

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

    
207
  @param opts: the command line options selected by the user
208
  @type args: list
209
  @param args: should be an empty list
210
  @rtype: int
211
  @return: the desired exit code
212

    
213
  """
214
  master = bootstrap.GetMaster()
215
  ToStdout(master)
216
  return 0
217

    
218

    
219
def _PrintGroupedParams(paramsdict, level=1):
220
  """Print Grouped parameters (be, nic, disk) by group.
221

    
222
  @type paramsdict: dict of dicts
223
  @param paramsdict: {group: {param: value, ...}, ...}
224
  @type level: int
225
  @param level: Level of indention
226

    
227
  """
228
  indent = "  " * level
229
  for item, val in sorted(paramsdict.items()):
230
    if isinstance(val, dict):
231
      ToStdout("%s- %s:", indent, item)
232
      _PrintGroupedParams(val, level=level + 1)
233
    else:
234
      ToStdout("%s  %s: %s", indent, item, val)
235

    
236

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

    
240
  @param opts: the command line options selected by the user
241
  @type args: list
242
  @param args: should be an empty list
243
  @rtype: int
244
  @return: the desired exit code
245

    
246
  """
247
  cl = GetClient()
248
  result = cl.QueryClusterInfo()
249

    
250
  ToStdout("Cluster name: %s", result["name"])
251
  ToStdout("Cluster UUID: %s", result["uuid"])
252

    
253
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
254
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
255

    
256
  ToStdout("Master node: %s", result["master"])
257

    
258
  ToStdout("Architecture (this node): %s (%s)",
259
           result["architecture"][0], result["architecture"][1])
260

    
261
  if result["tags"]:
262
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
263
  else:
264
    tags = "(none)"
265

    
266
  ToStdout("Tags: %s", tags)
267

    
268
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
269
  ToStdout("Enabled hypervisors: %s",
270
           utils.CommaJoin(result["enabled_hypervisors"]))
271

    
272
  ToStdout("Hypervisor parameters:")
273
  _PrintGroupedParams(result["hvparams"])
274

    
275
  ToStdout("OS specific hypervisor parameters:")
276
  _PrintGroupedParams(result["os_hvp"])
277

    
278
  ToStdout("Cluster parameters:")
279
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
280
  ToStdout("  - master netdev: %s", result["master_netdev"])
281
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
282
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
283
  ToStdout("  - maintenance of node health: %s",
284
           result["maintain_node_health"])
285
  ToStdout("  - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"]))
286

    
287
  ToStdout("Default instance parameters:")
288
  _PrintGroupedParams(result["beparams"])
289

    
290
  ToStdout("Default nic parameters:")
291
  _PrintGroupedParams(result["nicparams"])
292

    
293
  return 0
294

    
295

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

    
299
  @param opts: the command line options selected by the user
300
  @type args: list
301
  @param args: should contain only one element, the path of
302
      the file to be copied
303
  @rtype: int
304
  @return: the desired exit code
305

    
306
  """
307
  filename = args[0]
308
  if not os.path.exists(filename):
309
    raise errors.OpPrereqError("No such filename '%s'" % filename,
310
                               errors.ECODE_INVAL)
311

    
312
  cl = GetClient()
313

    
314
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
315

    
316
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
317
                           secondary_ips=opts.use_replication_network)
318

    
319
  srun = ssh.SshRunner(cluster_name=cluster_name)
320
  for node in results:
321
    if not srun.CopyFileToNode(node, filename):
322
      ToStderr("Copy of file %s to node %s failed", filename, node)
323

    
324
  return 0
325

    
326

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

    
330
  @param opts: the command line options selected by the user
331
  @type args: list
332
  @param args: should contain the command to be run and its arguments
333
  @rtype: int
334
  @return: the desired exit code
335

    
336
  """
337
  cl = GetClient()
338

    
339
  command = " ".join(args)
340

    
341
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
342

    
343
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
344
                                                    "master_node"])
345

    
346
  srun = ssh.SshRunner(cluster_name=cluster_name)
347

    
348
  # Make sure master node is at list end
349
  if master_node in nodes:
350
    nodes.remove(master_node)
351
    nodes.append(master_node)
352

    
353
  for name in nodes:
354
    result = srun.Run(name, "root", command)
355
    ToStdout("------------------------------------------------")
356
    ToStdout("node: %s", name)
357
    ToStdout("%s", result.output)
358
    ToStdout("return code = %s", result.exit_code)
359

    
360
  return 0
361

    
362

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

    
366
  @param opts: the command line options selected by the user
367
  @type args: list
368
  @param args: should be an empty list
369
  @rtype: int
370
  @return: the desired exit code
371

    
372
  """
373
  skip_checks = []
374
  if opts.skip_nplusone_mem:
375
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
376
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
377
                               verbose=opts.verbose,
378
                               error_codes=opts.error_codes,
379
                               debug_simulate_errors=opts.simulate_errors)
380
  if SubmitOpCode(op, opts=opts):
381
    return 0
382
  else:
383
    return 1
384

    
385

    
386
def VerifyDisks(opts, args):
387
  """Verify integrity of cluster disks.
388

    
389
  @param opts: the command line options selected by the user
390
  @type args: list
391
  @param args: should be an empty list
392
  @rtype: int
393
  @return: the desired exit code
394

    
395
  """
396
  op = opcodes.OpVerifyDisks()
397
  result = SubmitOpCode(op, opts=opts)
398
  if not isinstance(result, (list, tuple)) or len(result) != 3:
399
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
400

    
401
  bad_nodes, instances, missing = result
402

    
403
  retcode = constants.EXIT_SUCCESS
404

    
405
  if bad_nodes:
406
    for node, text in bad_nodes.items():
407
      ToStdout("Error gathering data on node %s: %s",
408
               node, utils.SafeEncode(text[-400:]))
409
      retcode |= 1
410
      ToStdout("You need to fix these nodes first before fixing instances")
411

    
412
  if instances:
413
    for iname in instances:
414
      if iname in missing:
415
        continue
416
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
417
      try:
418
        ToStdout("Activating disks for instance '%s'", iname)
419
        SubmitOpCode(op, opts=opts)
420
      except errors.GenericError, err:
421
        nret, msg = FormatError(err)
422
        retcode |= nret
423
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
424

    
425
  if missing:
426
    for iname, ival in missing.iteritems():
427
      all_missing = compat.all(ival, lambda x: x[0] in bad_nodes)
428
      if all_missing:
429
        ToStdout("Instance %s cannot be verified as it lives on"
430
                 " broken nodes", iname)
431
      else:
432
        ToStdout("Instance %s has missing logical volumes:", iname)
433
        ival.sort()
434
        for node, vol in ival:
435
          if node in bad_nodes:
436
            ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
437
          else:
438
            ToStdout("\t%s /dev/xenvg/%s", node, vol)
439
    ToStdout("You need to run replace_disks for all the above"
440
           " instances, if this message persist after fixing nodes.")
441
    retcode |= 1
442

    
443
  return retcode
444

    
445

    
446
def RepairDiskSizes(opts, args):
447
  """Verify sizes of cluster disks.
448

    
449
  @param opts: the command line options selected by the user
450
  @type args: list
451
  @param args: optional list of instances to restrict check to
452
  @rtype: int
453
  @return: the desired exit code
454

    
455
  """
456
  op = opcodes.OpRepairDiskSizes(instances=args)
457
  SubmitOpCode(op, opts=opts)
458

    
459

    
460
@UsesRPC
461
def MasterFailover(opts, args):
462
  """Failover the master node.
463

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

    
468
  @param opts: the command line options selected by the user
469
  @type args: list
470
  @param args: should be an empty list
471
  @rtype: int
472
  @return: the desired exit code
473

    
474
  """
475
  if opts.no_voting:
476
    usertext = ("This will perform the failover even if most other nodes"
477
                " are down, or if this node is outdated. This is dangerous"
478
                " as it can lead to a non-consistent cluster. Check the"
479
                " gnt-cluster(8) man page before proceeding. Continue?")
480
    if not AskUser(usertext):
481
      return 1
482

    
483
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
484

    
485

    
486
def SearchTags(opts, args):
487
  """Searches the tags on all the cluster.
488

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

    
495
  """
496
  op = opcodes.OpSearchTags(pattern=args[0])
497
  result = SubmitOpCode(op, opts=opts)
498
  if not result:
499
    return 1
500
  result = list(result)
501
  result.sort()
502
  for path, tag in result:
503
    ToStdout("%s %s", path, tag)
504

    
505

    
506
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
507
                 new_confd_hmac_key, new_cds, cds_filename,
508
                 force):
509
  """Renews cluster certificates, keys and secrets.
510

    
511
  @type new_cluster_cert: bool
512
  @param new_cluster_cert: Whether to generate a new cluster certificate
513
  @type new_rapi_cert: bool
514
  @param new_rapi_cert: Whether to generate a new RAPI certificate
515
  @type rapi_cert_filename: string
516
  @param rapi_cert_filename: Path to file containing new RAPI certificate
517
  @type new_confd_hmac_key: bool
518
  @param new_confd_hmac_key: Whether to generate a new HMAC key
519
  @type new_cds: bool
520
  @param new_cds: Whether to generate a new cluster domain secret
521
  @type cds_filename: string
522
  @param cds_filename: Path to file containing new cluster domain secret
523
  @type force: bool
524
  @param force: Whether to ask user for confirmation
525

    
526
  """
527
  if new_rapi_cert and rapi_cert_filename:
528
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
529
             " options can be specified at the same time.")
530
    return 1
531

    
532
  if new_cds and cds_filename:
533
    ToStderr("Only one of the --new-cluster-domain-secret and"
534
             " --cluster-domain-secret options can be specified at"
535
             " the same time.")
536
    return 1
537

    
538
  if rapi_cert_filename:
539
    # Read and verify new certificate
540
    try:
541
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
542

    
543
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
544
                                      rapi_cert_pem)
545
    except Exception, err: # pylint: disable-msg=W0703
546
      ToStderr("Can't load new RAPI certificate from %s: %s" %
547
               (rapi_cert_filename, str(err)))
548
      return 1
549

    
550
    try:
551
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
552
    except Exception, err: # pylint: disable-msg=W0703
553
      ToStderr("Can't load new RAPI private key from %s: %s" %
554
               (rapi_cert_filename, str(err)))
555
      return 1
556

    
557
  else:
558
    rapi_cert_pem = None
559

    
560
  if cds_filename:
561
    try:
562
      cds = utils.ReadFile(cds_filename)
563
    except Exception, err: # pylint: disable-msg=W0703
564
      ToStderr("Can't load new cluster domain secret from %s: %s" %
565
               (cds_filename, str(err)))
566
      return 1
567
  else:
568
    cds = None
569

    
570
  if not force:
571
    usertext = ("This requires all daemons on all nodes to be restarted and"
572
                " may take some time. Continue?")
573
    if not AskUser(usertext):
574
      return 1
575

    
576
  def _RenewCryptoInner(ctx):
577
    ctx.feedback_fn("Updating certificates and keys")
578
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
579
                                    new_confd_hmac_key,
580
                                    new_cds,
581
                                    rapi_cert_pem=rapi_cert_pem,
582
                                    cds=cds)
583

    
584
    files_to_copy = []
585

    
586
    if new_cluster_cert:
587
      files_to_copy.append(constants.NODED_CERT_FILE)
588

    
589
    if new_rapi_cert or rapi_cert_pem:
590
      files_to_copy.append(constants.RAPI_CERT_FILE)
591

    
592
    if new_confd_hmac_key:
593
      files_to_copy.append(constants.CONFD_HMAC_KEY)
594

    
595
    if new_cds or cds:
596
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
597

    
598
    if files_to_copy:
599
      for node_name in ctx.nonmaster_nodes:
600
        ctx.feedback_fn("Copying %s to %s" %
601
                        (", ".join(files_to_copy), node_name))
602
        for file_name in files_to_copy:
603
          ctx.ssh.CopyFileToNode(node_name, file_name)
604

    
605
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
606

    
607
  ToStdout("All requested certificates and keys have been replaced."
608
           " Running \"gnt-cluster verify\" now is recommended.")
609

    
610
  return 0
611

    
612

    
613
def RenewCrypto(opts, args):
614
  """Renews cluster certificates, keys and secrets.
615

    
616
  """
617
  return _RenewCrypto(opts.new_cluster_cert,
618
                      opts.new_rapi_cert,
619
                      opts.rapi_cert,
620
                      opts.new_confd_hmac_key,
621
                      opts.new_cluster_domain_secret,
622
                      opts.cluster_domain_secret,
623
                      opts.force)
624

    
625

    
626
def SetClusterParams(opts, args):
627
  """Modify the cluster.
628

    
629
  @param opts: the command line options selected by the user
630
  @type args: list
631
  @param args: should be an empty list
632
  @rtype: int
633
  @return: the desired exit code
634

    
635
  """
636
  if not (not opts.lvm_storage or opts.vg_name or
637
          opts.enabled_hypervisors or opts.hvparams or
638
          opts.beparams or opts.nicparams or
639
          opts.candidate_pool_size is not None or
640
          opts.uid_pool is not None or
641
          opts.maintain_node_health is not None or
642
          opts.add_uids is not None or
643
          opts.remove_uids is not None):
644
    ToStderr("Please give at least one of the parameters.")
645
    return 1
646

    
647
  vg_name = opts.vg_name
648
  if not opts.lvm_storage and opts.vg_name:
649
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
650
    return 1
651

    
652
  if not opts.lvm_storage:
653
    vg_name = ""
654

    
655
  hvlist = opts.enabled_hypervisors
656
  if hvlist is not None:
657
    hvlist = hvlist.split(",")
658

    
659
  # a list of (name, dict) we can pass directly to dict() (or [])
660
  hvparams = dict(opts.hvparams)
661
  for hv_params in hvparams.values():
662
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
663

    
664
  beparams = opts.beparams
665
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
666

    
667
  nicparams = opts.nicparams
668
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
669

    
670

    
671
  mnh = opts.maintain_node_health
672

    
673
  uid_pool = opts.uid_pool
674
  if uid_pool is not None:
675
    uid_pool = uidpool.ParseUidPool(uid_pool)
676

    
677
  add_uids = opts.add_uids
678
  if add_uids is not None:
679
    add_uids = uidpool.ParseUidPool(add_uids)
680

    
681
  remove_uids = opts.remove_uids
682
  if remove_uids is not None:
683
    remove_uids = uidpool.ParseUidPool(remove_uids)
684

    
685
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
686
                                  enabled_hypervisors=hvlist,
687
                                  hvparams=hvparams,
688
                                  os_hvp=None,
689
                                  beparams=beparams,
690
                                  nicparams=nicparams,
691
                                  candidate_pool_size=opts.candidate_pool_size,
692
                                  maintain_node_health=mnh,
693
                                  uid_pool=uid_pool,
694
                                  add_uids=add_uids,
695
                                  remove_uids=remove_uids)
696
  SubmitOpCode(op, opts=opts)
697
  return 0
698

    
699

    
700
def QueueOps(opts, args):
701
  """Queue operations.
702

    
703
  @param opts: the command line options selected by the user
704
  @type args: list
705
  @param args: should contain only one element, the subcommand
706
  @rtype: int
707
  @return: the desired exit code
708

    
709
  """
710
  command = args[0]
711
  client = GetClient()
712
  if command in ("drain", "undrain"):
713
    drain_flag = command == "drain"
714
    client.SetQueueDrainFlag(drain_flag)
715
  elif command == "info":
716
    result = client.QueryConfigValues(["drain_flag"])
717
    if result[0]:
718
      val = "set"
719
    else:
720
      val = "unset"
721
    ToStdout("The drain flag is %s" % val)
722
  else:
723
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
724
                               errors.ECODE_INVAL)
725

    
726
  return 0
727

    
728

    
729
def _ShowWatcherPause(until):
730
  if until is None or until < time.time():
731
    ToStdout("The watcher is not paused.")
732
  else:
733
    ToStdout("The watcher is paused until %s.", time.ctime(until))
734

    
735

    
736
def WatcherOps(opts, args):
737
  """Watcher operations.
738

    
739
  @param opts: the command line options selected by the user
740
  @type args: list
741
  @param args: should contain only one element, the subcommand
742
  @rtype: int
743
  @return: the desired exit code
744

    
745
  """
746
  command = args[0]
747
  client = GetClient()
748

    
749
  if command == "continue":
750
    client.SetWatcherPause(None)
751
    ToStdout("The watcher is no longer paused.")
752

    
753
  elif command == "pause":
754
    if len(args) < 2:
755
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
756

    
757
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
758
    _ShowWatcherPause(result)
759

    
760
  elif command == "info":
761
    result = client.QueryConfigValues(["watcher_pause"])
762
    _ShowWatcherPause(result[0])
763

    
764
  else:
765
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
766
                               errors.ECODE_INVAL)
767

    
768
  return 0
769

    
770

    
771
commands = {
772
  'init': (
773
    InitCluster, [ArgHost(min=1, max=1)],
774
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
775
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
776
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
777
     SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
778
     UIDPOOL_OPT],
779
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
780
  'destroy': (
781
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
782
    "", "Destroy cluster"),
783
  'rename': (
784
    RenameCluster, [ArgHost(min=1, max=1)],
785
    [FORCE_OPT],
786
    "<new_name>",
787
    "Renames the cluster"),
788
  'redist-conf': (
789
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
790
    "", "Forces a push of the configuration file and ssconf files"
791
    " to the nodes in the cluster"),
792
  'verify': (
793
    VerifyCluster, ARGS_NONE,
794
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
795
    "", "Does a check on the cluster configuration"),
796
  'verify-disks': (
797
    VerifyDisks, ARGS_NONE, [],
798
    "", "Does a check on the cluster disk status"),
799
  'repair-disk-sizes': (
800
    RepairDiskSizes, ARGS_MANY_INSTANCES, [],
801
    "", "Updates mismatches in recorded disk sizes"),
802
  'masterfailover': (
803
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
804
    "", "Makes the current node the master"),
805
  'version': (
806
    ShowClusterVersion, ARGS_NONE, [],
807
    "", "Shows the cluster version"),
808
  'getmaster': (
809
    ShowClusterMaster, ARGS_NONE, [],
810
    "", "Shows the cluster master"),
811
  'copyfile': (
812
    ClusterCopyFile, [ArgFile(min=1, max=1)],
813
    [NODE_LIST_OPT, USE_REPL_NET_OPT],
814
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
815
  'command': (
816
    RunClusterCommand, [ArgCommand(min=1)],
817
    [NODE_LIST_OPT],
818
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
819
  'info': (
820
    ShowClusterConfig, ARGS_NONE, [],
821
    "", "Show cluster configuration"),
822
  'list-tags': (
823
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
824
  'add-tags': (
825
    AddTags, [ArgUnknown()], [TAG_SRC_OPT],
826
    "tag...", "Add tags to the cluster"),
827
  'remove-tags': (
828
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
829
    "tag...", "Remove tags from the cluster"),
830
  'search-tags': (
831
    SearchTags, [ArgUnknown(min=1, max=1)],
832
    [], "", "Searches the tags on all objects on"
833
    " the cluster for a given pattern (regex)"),
834
  'queue': (
835
    QueueOps,
836
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
837
    [], "drain|undrain|info", "Change queue properties"),
838
  'watcher': (
839
    WatcherOps,
840
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
841
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
842
    [],
843
    "{pause <timespec>|continue|info}", "Change watcher properties"),
844
  'modify': (
845
    SetClusterParams, ARGS_NONE,
846
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
847
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
848
     UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT],
849
    "[opts...]",
850
    "Alters the parameters of the cluster"),
851
  "renew-crypto": (
852
    RenewCrypto, ARGS_NONE,
853
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
854
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
855
     NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
856
    "[opts...]",
857
    "Renews cluster certificates, keys and secrets"),
858
  }
859

    
860

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