Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ 403f5172

History | View | Annotate | Download (26.6 kB)

1
#!/usr/bin/python
2
#
3

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

    
21
"""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("OS parameters:")
281
  _PrintGroupedParams(result["osparams"])
282

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

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

    
299
  ToStdout("Default nic parameters:")
300
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
301

    
302
  return 0
303

    
304

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

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

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

    
321
  cl = GetClient()
322

    
323
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
324

    
325
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
326
                           secondary_ips=opts.use_replication_network)
327

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

    
333
  return 0
334

    
335

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

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

    
345
  """
346
  cl = GetClient()
347

    
348
  command = " ".join(args)
349

    
350
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
351

    
352
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
353
                                                    "master_node"])
354

    
355
  srun = ssh.SshRunner(cluster_name=cluster_name)
356

    
357
  # Make sure master node is at list end
358
  if master_node in nodes:
359
    nodes.remove(master_node)
360
    nodes.append(master_node)
361

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

    
369
  return 0
370

    
371

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

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

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

    
394

    
395
def VerifyDisks(opts, args):
396
  """Verify integrity of cluster disks.
397

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

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

    
410
  bad_nodes, instances, missing = result
411

    
412
  retcode = constants.EXIT_SUCCESS
413

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

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

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

    
452
  return retcode
453

    
454

    
455
def RepairDiskSizes(opts, args):
456
  """Verify sizes of cluster disks.
457

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

    
464
  """
465
  op = opcodes.OpRepairDiskSizes(instances=args)
466
  SubmitOpCode(op, opts=opts)
467

    
468

    
469
@UsesRPC
470
def MasterFailover(opts, args):
471
  """Failover the master node.
472

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

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

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

    
492
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
493

    
494

    
495
def SearchTags(opts, args):
496
  """Searches the tags on all the cluster.
497

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

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

    
514

    
515
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
516
                 new_confd_hmac_key, new_cds, cds_filename,
517
                 force):
518
  """Renews cluster certificates, keys and secrets.
519

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

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

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

    
547
  if rapi_cert_filename:
548
    # Read and verify new certificate
549
    try:
550
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
551

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

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

    
566
  else:
567
    rapi_cert_pem = None
568

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

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

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

    
593
    files_to_copy = []
594

    
595
    if new_cluster_cert:
596
      files_to_copy.append(constants.NODED_CERT_FILE)
597

    
598
    if new_rapi_cert or rapi_cert_pem:
599
      files_to_copy.append(constants.RAPI_CERT_FILE)
600

    
601
    if new_confd_hmac_key:
602
      files_to_copy.append(constants.CONFD_HMAC_KEY)
603

    
604
    if new_cds or cds:
605
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
606

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

    
614
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
615

    
616
  ToStdout("All requested certificates and keys have been replaced."
617
           " Running \"gnt-cluster verify\" now is recommended.")
618

    
619
  return 0
620

    
621

    
622
def RenewCrypto(opts, args):
623
  """Renews cluster certificates, keys and secrets.
624

    
625
  """
626
  return _RenewCrypto(opts.new_cluster_cert,
627
                      opts.new_rapi_cert,
628
                      opts.rapi_cert,
629
                      opts.new_confd_hmac_key,
630
                      opts.new_cluster_domain_secret,
631
                      opts.cluster_domain_secret,
632
                      opts.force)
633

    
634

    
635
def SetClusterParams(opts, args):
636
  """Modify the cluster.
637

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

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

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

    
661
  if not opts.lvm_storage:
662
    vg_name = ""
663

    
664
  hvlist = opts.enabled_hypervisors
665
  if hvlist is not None:
666
    hvlist = hvlist.split(",")
667

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

    
673
  beparams = opts.beparams
674
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
675

    
676
  nicparams = opts.nicparams
677
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
678

    
679

    
680
  mnh = opts.maintain_node_health
681

    
682
  uid_pool = opts.uid_pool
683
  if uid_pool is not None:
684
    uid_pool = uidpool.ParseUidPool(uid_pool)
685

    
686
  add_uids = opts.add_uids
687
  if add_uids is not None:
688
    add_uids = uidpool.ParseUidPool(add_uids)
689

    
690
  remove_uids = opts.remove_uids
691
  if remove_uids is not None:
692
    remove_uids = uidpool.ParseUidPool(remove_uids)
693

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

    
708

    
709
def QueueOps(opts, args):
710
  """Queue operations.
711

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

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

    
735
  return 0
736

    
737

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

    
744

    
745
def WatcherOps(opts, args):
746
  """Watcher operations.
747

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

    
754
  """
755
  command = args[0]
756
  client = GetClient()
757

    
758
  if command == "continue":
759
    client.SetWatcherPause(None)
760
    ToStdout("The watcher is no longer paused.")
761

    
762
  elif command == "pause":
763
    if len(args) < 2:
764
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
765

    
766
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
767
    _ShowWatcherPause(result)
768

    
769
  elif command == "info":
770
    result = client.QueryConfigValues(["watcher_pause"])
771
    _ShowWatcherPause(result[0])
772

    
773
  else:
774
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
775
                               errors.ECODE_INVAL)
776

    
777
  return 0
778

    
779

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

    
869

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