Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 66ecc479

History | View | Annotate | Download (26.5 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, roman=False):
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, roman=roman)
233
    elif roman and isinstance(val, int):
234
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
235
    else:
236
      ToStdout("%s  %s: %s", indent, item, val)
237

    
238

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

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

    
248
  """
249
  cl = GetClient()
250
  result = cl.QueryClusterInfo()
251

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

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

    
258
  ToStdout("Master node: %s", result["master"])
259

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

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

    
268
  ToStdout("Tags: %s", tags)
269

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

    
274
  ToStdout("Hypervisor parameters:")
275
  _PrintGroupedParams(result["hvparams"])
276

    
277
  ToStdout("OS specific hypervisor parameters:")
278
  _PrintGroupedParams(result["os_hvp"])
279

    
280
  ToStdout("Cluster parameters:")
281
  ToStdout("  - candidate pool size: %s",
282
            compat.TryToRoman(result["candidate_pool_size"],
283
                              convert=opts.roman_integers))
284
  ToStdout("  - master netdev: %s", result["master_netdev"])
285
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
286
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
287
  ToStdout("  - maintenance of node health: %s",
288
           result["maintain_node_health"])
289
  ToStdout("  - uid pool: %s",
290
            uidpool.FormatUidPool(result["uid_pool"],
291
                                  roman=opts.roman_integers))
292

    
293
  ToStdout("Default instance parameters:")
294
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
295

    
296
  ToStdout("Default nic parameters:")
297
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
298

    
299
  return 0
300

    
301

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

    
305
  @param opts: the command line options selected by the user
306
  @type args: list
307
  @param args: should contain only one element, the path of
308
      the file to be copied
309
  @rtype: int
310
  @return: the desired exit code
311

    
312
  """
313
  filename = args[0]
314
  if not os.path.exists(filename):
315
    raise errors.OpPrereqError("No such filename '%s'" % filename,
316
                               errors.ECODE_INVAL)
317

    
318
  cl = GetClient()
319

    
320
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
321

    
322
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
323
                           secondary_ips=opts.use_replication_network)
324

    
325
  srun = ssh.SshRunner(cluster_name=cluster_name)
326
  for node in results:
327
    if not srun.CopyFileToNode(node, filename):
328
      ToStderr("Copy of file %s to node %s failed", filename, node)
329

    
330
  return 0
331

    
332

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

    
336
  @param opts: the command line options selected by the user
337
  @type args: list
338
  @param args: should contain the command to be run and its arguments
339
  @rtype: int
340
  @return: the desired exit code
341

    
342
  """
343
  cl = GetClient()
344

    
345
  command = " ".join(args)
346

    
347
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
348

    
349
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
350
                                                    "master_node"])
351

    
352
  srun = ssh.SshRunner(cluster_name=cluster_name)
353

    
354
  # Make sure master node is at list end
355
  if master_node in nodes:
356
    nodes.remove(master_node)
357
    nodes.append(master_node)
358

    
359
  for name in nodes:
360
    result = srun.Run(name, "root", command)
361
    ToStdout("------------------------------------------------")
362
    ToStdout("node: %s", name)
363
    ToStdout("%s", result.output)
364
    ToStdout("return code = %s", result.exit_code)
365

    
366
  return 0
367

    
368

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

    
372
  @param opts: the command line options selected by the user
373
  @type args: list
374
  @param args: should be an empty list
375
  @rtype: int
376
  @return: the desired exit code
377

    
378
  """
379
  skip_checks = []
380
  if opts.skip_nplusone_mem:
381
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
382
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
383
                               verbose=opts.verbose,
384
                               error_codes=opts.error_codes,
385
                               debug_simulate_errors=opts.simulate_errors)
386
  if SubmitOpCode(op, opts=opts):
387
    return 0
388
  else:
389
    return 1
390

    
391

    
392
def VerifyDisks(opts, args):
393
  """Verify integrity of cluster disks.
394

    
395
  @param opts: the command line options selected by the user
396
  @type args: list
397
  @param args: should be an empty list
398
  @rtype: int
399
  @return: the desired exit code
400

    
401
  """
402
  op = opcodes.OpVerifyDisks()
403
  result = SubmitOpCode(op, opts=opts)
404
  if not isinstance(result, (list, tuple)) or len(result) != 3:
405
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
406

    
407
  bad_nodes, instances, missing = result
408

    
409
  retcode = constants.EXIT_SUCCESS
410

    
411
  if bad_nodes:
412
    for node, text in bad_nodes.items():
413
      ToStdout("Error gathering data on node %s: %s",
414
               node, utils.SafeEncode(text[-400:]))
415
      retcode |= 1
416
      ToStdout("You need to fix these nodes first before fixing instances")
417

    
418
  if instances:
419
    for iname in instances:
420
      if iname in missing:
421
        continue
422
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
423
      try:
424
        ToStdout("Activating disks for instance '%s'", iname)
425
        SubmitOpCode(op, opts=opts)
426
      except errors.GenericError, err:
427
        nret, msg = FormatError(err)
428
        retcode |= nret
429
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
430

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

    
449
  return retcode
450

    
451

    
452
def RepairDiskSizes(opts, args):
453
  """Verify sizes of cluster disks.
454

    
455
  @param opts: the command line options selected by the user
456
  @type args: list
457
  @param args: optional list of instances to restrict check to
458
  @rtype: int
459
  @return: the desired exit code
460

    
461
  """
462
  op = opcodes.OpRepairDiskSizes(instances=args)
463
  SubmitOpCode(op, opts=opts)
464

    
465

    
466
@UsesRPC
467
def MasterFailover(opts, args):
468
  """Failover the master node.
469

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

    
474
  @param opts: the command line options selected by the user
475
  @type args: list
476
  @param args: should be an empty list
477
  @rtype: int
478
  @return: the desired exit code
479

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

    
489
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
490

    
491

    
492
def SearchTags(opts, args):
493
  """Searches the tags on all the cluster.
494

    
495
  @param opts: the command line options selected by the user
496
  @type args: list
497
  @param args: should contain only one element, the tag pattern
498
  @rtype: int
499
  @return: the desired exit code
500

    
501
  """
502
  op = opcodes.OpSearchTags(pattern=args[0])
503
  result = SubmitOpCode(op, opts=opts)
504
  if not result:
505
    return 1
506
  result = list(result)
507
  result.sort()
508
  for path, tag in result:
509
    ToStdout("%s %s", path, tag)
510

    
511

    
512
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
513
                 new_confd_hmac_key, new_cds, cds_filename,
514
                 force):
515
  """Renews cluster certificates, keys and secrets.
516

    
517
  @type new_cluster_cert: bool
518
  @param new_cluster_cert: Whether to generate a new cluster certificate
519
  @type new_rapi_cert: bool
520
  @param new_rapi_cert: Whether to generate a new RAPI certificate
521
  @type rapi_cert_filename: string
522
  @param rapi_cert_filename: Path to file containing new RAPI certificate
523
  @type new_confd_hmac_key: bool
524
  @param new_confd_hmac_key: Whether to generate a new HMAC key
525
  @type new_cds: bool
526
  @param new_cds: Whether to generate a new cluster domain secret
527
  @type cds_filename: string
528
  @param cds_filename: Path to file containing new cluster domain secret
529
  @type force: bool
530
  @param force: Whether to ask user for confirmation
531

    
532
  """
533
  if new_rapi_cert and rapi_cert_filename:
534
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
535
             " options can be specified at the same time.")
536
    return 1
537

    
538
  if new_cds and cds_filename:
539
    ToStderr("Only one of the --new-cluster-domain-secret and"
540
             " --cluster-domain-secret options can be specified at"
541
             " the same time.")
542
    return 1
543

    
544
  if rapi_cert_filename:
545
    # Read and verify new certificate
546
    try:
547
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
548

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

    
556
    try:
557
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
558
    except Exception, err: # pylint: disable-msg=W0703
559
      ToStderr("Can't load new RAPI private key from %s: %s" %
560
               (rapi_cert_filename, str(err)))
561
      return 1
562

    
563
  else:
564
    rapi_cert_pem = None
565

    
566
  if cds_filename:
567
    try:
568
      cds = utils.ReadFile(cds_filename)
569
    except Exception, err: # pylint: disable-msg=W0703
570
      ToStderr("Can't load new cluster domain secret from %s: %s" %
571
               (cds_filename, str(err)))
572
      return 1
573
  else:
574
    cds = None
575

    
576
  if not force:
577
    usertext = ("This requires all daemons on all nodes to be restarted and"
578
                " may take some time. Continue?")
579
    if not AskUser(usertext):
580
      return 1
581

    
582
  def _RenewCryptoInner(ctx):
583
    ctx.feedback_fn("Updating certificates and keys")
584
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
585
                                    new_confd_hmac_key,
586
                                    new_cds,
587
                                    rapi_cert_pem=rapi_cert_pem,
588
                                    cds=cds)
589

    
590
    files_to_copy = []
591

    
592
    if new_cluster_cert:
593
      files_to_copy.append(constants.NODED_CERT_FILE)
594

    
595
    if new_rapi_cert or rapi_cert_pem:
596
      files_to_copy.append(constants.RAPI_CERT_FILE)
597

    
598
    if new_confd_hmac_key:
599
      files_to_copy.append(constants.CONFD_HMAC_KEY)
600

    
601
    if new_cds or cds:
602
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
603

    
604
    if files_to_copy:
605
      for node_name in ctx.nonmaster_nodes:
606
        ctx.feedback_fn("Copying %s to %s" %
607
                        (", ".join(files_to_copy), node_name))
608
        for file_name in files_to_copy:
609
          ctx.ssh.CopyFileToNode(node_name, file_name)
610

    
611
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
612

    
613
  ToStdout("All requested certificates and keys have been replaced."
614
           " Running \"gnt-cluster verify\" now is recommended.")
615

    
616
  return 0
617

    
618

    
619
def RenewCrypto(opts, args):
620
  """Renews cluster certificates, keys and secrets.
621

    
622
  """
623
  return _RenewCrypto(opts.new_cluster_cert,
624
                      opts.new_rapi_cert,
625
                      opts.rapi_cert,
626
                      opts.new_confd_hmac_key,
627
                      opts.new_cluster_domain_secret,
628
                      opts.cluster_domain_secret,
629
                      opts.force)
630

    
631

    
632
def SetClusterParams(opts, args):
633
  """Modify the cluster.
634

    
635
  @param opts: the command line options selected by the user
636
  @type args: list
637
  @param args: should be an empty list
638
  @rtype: int
639
  @return: the desired exit code
640

    
641
  """
642
  if not (not opts.lvm_storage or opts.vg_name or
643
          opts.enabled_hypervisors or opts.hvparams or
644
          opts.beparams or opts.nicparams or
645
          opts.candidate_pool_size is not None or
646
          opts.uid_pool is not None or
647
          opts.maintain_node_health is not None or
648
          opts.add_uids is not None or
649
          opts.remove_uids is not None):
650
    ToStderr("Please give at least one of the parameters.")
651
    return 1
652

    
653
  vg_name = opts.vg_name
654
  if not opts.lvm_storage and opts.vg_name:
655
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
656
    return 1
657

    
658
  if not opts.lvm_storage:
659
    vg_name = ""
660

    
661
  hvlist = opts.enabled_hypervisors
662
  if hvlist is not None:
663
    hvlist = hvlist.split(",")
664

    
665
  # a list of (name, dict) we can pass directly to dict() (or [])
666
  hvparams = dict(opts.hvparams)
667
  for hv_params in hvparams.values():
668
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
669

    
670
  beparams = opts.beparams
671
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
672

    
673
  nicparams = opts.nicparams
674
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
675

    
676

    
677
  mnh = opts.maintain_node_health
678

    
679
  uid_pool = opts.uid_pool
680
  if uid_pool is not None:
681
    uid_pool = uidpool.ParseUidPool(uid_pool)
682

    
683
  add_uids = opts.add_uids
684
  if add_uids is not None:
685
    add_uids = uidpool.ParseUidPool(add_uids)
686

    
687
  remove_uids = opts.remove_uids
688
  if remove_uids is not None:
689
    remove_uids = uidpool.ParseUidPool(remove_uids)
690

    
691
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
692
                                  enabled_hypervisors=hvlist,
693
                                  hvparams=hvparams,
694
                                  os_hvp=None,
695
                                  beparams=beparams,
696
                                  nicparams=nicparams,
697
                                  candidate_pool_size=opts.candidate_pool_size,
698
                                  maintain_node_health=mnh,
699
                                  uid_pool=uid_pool,
700
                                  add_uids=add_uids,
701
                                  remove_uids=remove_uids)
702
  SubmitOpCode(op, opts=opts)
703
  return 0
704

    
705

    
706
def QueueOps(opts, args):
707
  """Queue operations.
708

    
709
  @param opts: the command line options selected by the user
710
  @type args: list
711
  @param args: should contain only one element, the subcommand
712
  @rtype: int
713
  @return: the desired exit code
714

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

    
732
  return 0
733

    
734

    
735
def _ShowWatcherPause(until):
736
  if until is None or until < time.time():
737
    ToStdout("The watcher is not paused.")
738
  else:
739
    ToStdout("The watcher is paused until %s.", time.ctime(until))
740

    
741

    
742
def WatcherOps(opts, args):
743
  """Watcher operations.
744

    
745
  @param opts: the command line options selected by the user
746
  @type args: list
747
  @param args: should contain only one element, the subcommand
748
  @rtype: int
749
  @return: the desired exit code
750

    
751
  """
752
  command = args[0]
753
  client = GetClient()
754

    
755
  if command == "continue":
756
    client.SetWatcherPause(None)
757
    ToStdout("The watcher is no longer paused.")
758

    
759
  elif command == "pause":
760
    if len(args) < 2:
761
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
762

    
763
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
764
    _ShowWatcherPause(result)
765

    
766
  elif command == "info":
767
    result = client.QueryConfigValues(["watcher_pause"])
768
    _ShowWatcherPause(result[0])
769

    
770
  else:
771
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
772
                               errors.ECODE_INVAL)
773

    
774
  return 0
775

    
776

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

    
866

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