Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ b883637f

History | View | Annotate | Download (30 kB)

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

    
4
# Copyright (C) 2006, 2007, 2010 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
  if not opts.drbd_storage and opts.drbd_helper:
67
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
68
    return 1
69

    
70
  drbd_helper = opts.drbd_helper
71
  if opts.drbd_storage and not opts.drbd_helper:
72
    drbd_helper = constants.DEFAULT_DRBD_HELPER
73

    
74
  hvlist = opts.enabled_hypervisors
75
  if hvlist is None:
76
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
77
  hvlist = hvlist.split(",")
78

    
79
  hvparams = dict(opts.hvparams)
80
  beparams = opts.beparams
81
  nicparams = opts.nicparams
82

    
83
  # prepare beparams dict
84
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
85
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
86

    
87
  # prepare nicparams dict
88
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
89
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
90

    
91
  # prepare hvparams dict
92
  for hv in constants.HYPER_TYPES:
93
    if hv not in hvparams:
94
      hvparams[hv] = {}
95
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
96
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
97

    
98
  if opts.candidate_pool_size is None:
99
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
100

    
101
  if opts.mac_prefix is None:
102
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
103

    
104
  uid_pool = opts.uid_pool
105
  if uid_pool is not None:
106
    uid_pool = uidpool.ParseUidPool(uid_pool)
107

    
108
  if opts.prealloc_wipe_disks is None:
109
    opts.prealloc_wipe_disks = False
110

    
111
  try:
112
    primary_ip_version = int(opts.primary_ip_version)
113
  except (ValueError, TypeError), err:
114
    ToStderr("Invalid primary ip version value: %s" % str(err))
115
    return 1
116

    
117
  bootstrap.InitCluster(cluster_name=args[0],
118
                        secondary_ip=opts.secondary_ip,
119
                        vg_name=vg_name,
120
                        mac_prefix=opts.mac_prefix,
121
                        master_netdev=opts.master_netdev,
122
                        file_storage_dir=opts.file_storage_dir,
123
                        enabled_hypervisors=hvlist,
124
                        hvparams=hvparams,
125
                        beparams=beparams,
126
                        nicparams=nicparams,
127
                        candidate_pool_size=opts.candidate_pool_size,
128
                        modify_etc_hosts=opts.modify_etc_hosts,
129
                        modify_ssh_setup=opts.modify_ssh_setup,
130
                        maintain_node_health=opts.maintain_node_health,
131
                        drbd_helper=drbd_helper,
132
                        uid_pool=uid_pool,
133
                        default_iallocator=opts.default_iallocator,
134
                        primary_ip_version=primary_ip_version,
135
                        prealloc_wipe_disks=opts.prealloc_wipe_disks,
136
                        )
137
  op = opcodes.OpPostInitCluster()
138
  SubmitOpCode(op, opts=opts)
139
  return 0
140

    
141

    
142
@UsesRPC
143
def DestroyCluster(opts, args):
144
  """Destroy the cluster.
145

    
146
  @param opts: the command line options selected by the user
147
  @type args: list
148
  @param args: should be an empty list
149
  @rtype: int
150
  @return: the desired exit code
151

    
152
  """
153
  if not opts.yes_do_it:
154
    ToStderr("Destroying a cluster is irreversible. If you really want"
155
             " destroy this cluster, supply the --yes-do-it option.")
156
    return 1
157

    
158
  op = opcodes.OpDestroyCluster()
159
  master = SubmitOpCode(op, opts=opts)
160
  # if we reached this, the opcode didn't fail; we can proceed to
161
  # shutdown all the daemons
162
  bootstrap.FinalizeClusterDestroy(master)
163
  return 0
164

    
165

    
166
def RenameCluster(opts, args):
167
  """Rename the cluster.
168

    
169
  @param opts: the command line options selected by the user
170
  @type args: list
171
  @param args: should contain only one element, the new cluster name
172
  @rtype: int
173
  @return: the desired exit code
174

    
175
  """
176
  cl = GetClient()
177

    
178
  (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
179

    
180
  new_name = args[0]
181
  if not opts.force:
182
    usertext = ("This will rename the cluster from '%s' to '%s'. If you are"
183
                " connected over the network to the cluster name, the"
184
                " operation is very dangerous as the IP address will be"
185
                " removed from the node and the change may not go through."
186
                " Continue?") % (cluster_name, new_name)
187
    if not AskUser(usertext):
188
      return 1
189

    
190
  op = opcodes.OpRenameCluster(name=new_name)
191
  result = SubmitOpCode(op, opts=opts, cl=cl)
192

    
193
  if result:
194
    ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
195

    
196
  return 0
197

    
198

    
199
def RedistributeConfig(opts, args):
200
  """Forces push of the cluster configuration.
201

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

    
208
  """
209
  op = opcodes.OpRedistributeConfig()
210
  SubmitOrSend(op, opts)
211
  return 0
212

    
213

    
214
def ShowClusterVersion(opts, args):
215
  """Write version of ganeti software to the standard output.
216

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

    
223
  """
224
  cl = GetClient()
225
  result = cl.QueryClusterInfo()
226
  ToStdout("Software version: %s", result["software_version"])
227
  ToStdout("Internode protocol: %s", result["protocol_version"])
228
  ToStdout("Configuration format: %s", result["config_version"])
229
  ToStdout("OS api version: %s", result["os_api_version"])
230
  ToStdout("Export interface: %s", result["export_version"])
231
  return 0
232

    
233

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

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

    
243
  """
244
  master = bootstrap.GetMaster()
245
  ToStdout(master)
246
  return 0
247

    
248

    
249
def _PrintGroupedParams(paramsdict, level=1, roman=False):
250
  """Print Grouped parameters (be, nic, disk) by group.
251

    
252
  @type paramsdict: dict of dicts
253
  @param paramsdict: {group: {param: value, ...}, ...}
254
  @type level: int
255
  @param level: Level of indention
256

    
257
  """
258
  indent = "  " * level
259
  for item, val in sorted(paramsdict.items()):
260
    if isinstance(val, dict):
261
      ToStdout("%s- %s:", indent, item)
262
      _PrintGroupedParams(val, level=level + 1, roman=roman)
263
    elif roman and isinstance(val, int):
264
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
265
    else:
266
      ToStdout("%s  %s: %s", indent, item, val)
267

    
268

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

    
272
  @param opts: the command line options selected by the user
273
  @type args: list
274
  @param args: should be an empty list
275
  @rtype: int
276
  @return: the desired exit code
277

    
278
  """
279
  cl = GetClient()
280
  result = cl.QueryClusterInfo()
281

    
282
  ToStdout("Cluster name: %s", result["name"])
283
  ToStdout("Cluster UUID: %s", result["uuid"])
284

    
285
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
286
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
287

    
288
  ToStdout("Master node: %s", result["master"])
289

    
290
  ToStdout("Architecture (this node): %s (%s)",
291
           result["architecture"][0], result["architecture"][1])
292

    
293
  if result["tags"]:
294
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
295
  else:
296
    tags = "(none)"
297

    
298
  ToStdout("Tags: %s", tags)
299

    
300
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
301
  ToStdout("Enabled hypervisors: %s",
302
           utils.CommaJoin(result["enabled_hypervisors"]))
303

    
304
  ToStdout("Hypervisor parameters:")
305
  _PrintGroupedParams(result["hvparams"])
306

    
307
  ToStdout("OS-specific hypervisor parameters:")
308
  _PrintGroupedParams(result["os_hvp"])
309

    
310
  ToStdout("OS parameters:")
311
  _PrintGroupedParams(result["osparams"])
312

    
313
  ToStdout("Cluster parameters:")
314
  ToStdout("  - candidate pool size: %s",
315
            compat.TryToRoman(result["candidate_pool_size"],
316
                              convert=opts.roman_integers))
317
  ToStdout("  - master netdev: %s", result["master_netdev"])
318
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
319
  if result["reserved_lvs"]:
320
    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
321
  else:
322
    reserved_lvs = "(none)"
323
  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
324
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
325
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
326
  ToStdout("  - maintenance of node health: %s",
327
           result["maintain_node_health"])
328
  ToStdout("  - uid pool: %s",
329
            uidpool.FormatUidPool(result["uid_pool"],
330
                                  roman=opts.roman_integers))
331
  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
332
  ToStdout("  - primary ip version: %d", result["primary_ip_version"])
333
  ToStdout("  - preallocation wipe disks: %s", result["prealloc_wipe_disks"])
334

    
335
  ToStdout("Default instance parameters:")
336
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
337

    
338
  ToStdout("Default nic parameters:")
339
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
340

    
341
  return 0
342

    
343

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

    
347
  @param opts: the command line options selected by the user
348
  @type args: list
349
  @param args: should contain only one element, the path of
350
      the file to be copied
351
  @rtype: int
352
  @return: the desired exit code
353

    
354
  """
355
  filename = args[0]
356
  if not os.path.exists(filename):
357
    raise errors.OpPrereqError("No such filename '%s'" % filename,
358
                               errors.ECODE_INVAL)
359

    
360
  cl = GetClient()
361

    
362
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
363

    
364
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
365
                           secondary_ips=opts.use_replication_network)
366

    
367
  srun = ssh.SshRunner(cluster_name=cluster_name)
368
  for node in results:
369
    if not srun.CopyFileToNode(node, filename):
370
      ToStderr("Copy of file %s to node %s failed", filename, node)
371

    
372
  return 0
373

    
374

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

    
378
  @param opts: the command line options selected by the user
379
  @type args: list
380
  @param args: should contain the command to be run and its arguments
381
  @rtype: int
382
  @return: the desired exit code
383

    
384
  """
385
  cl = GetClient()
386

    
387
  command = " ".join(args)
388

    
389
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
390

    
391
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
392
                                                    "master_node"])
393

    
394
  srun = ssh.SshRunner(cluster_name=cluster_name)
395

    
396
  # Make sure master node is at list end
397
  if master_node in nodes:
398
    nodes.remove(master_node)
399
    nodes.append(master_node)
400

    
401
  for name in nodes:
402
    result = srun.Run(name, "root", command)
403
    ToStdout("------------------------------------------------")
404
    ToStdout("node: %s", name)
405
    ToStdout("%s", result.output)
406
    ToStdout("return code = %s", result.exit_code)
407

    
408
  return 0
409

    
410

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

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

    
420
  """
421
  skip_checks = []
422
  if opts.skip_nplusone_mem:
423
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
424
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
425
                               verbose=opts.verbose,
426
                               error_codes=opts.error_codes,
427
                               debug_simulate_errors=opts.simulate_errors)
428
  if SubmitOpCode(op, opts=opts):
429
    return 0
430
  else:
431
    return 1
432

    
433

    
434
def VerifyDisks(opts, args):
435
  """Verify integrity of cluster disks.
436

    
437
  @param opts: the command line options selected by the user
438
  @type args: list
439
  @param args: should be an empty list
440
  @rtype: int
441
  @return: the desired exit code
442

    
443
  """
444
  cl = GetClient()
445

    
446
  op = opcodes.OpVerifyDisks()
447
  result = SubmitOpCode(op, opts=opts, cl=cl)
448
  if not isinstance(result, (list, tuple)) or len(result) != 3:
449
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
450

    
451
  bad_nodes, instances, missing = result
452

    
453
  retcode = constants.EXIT_SUCCESS
454

    
455
  if bad_nodes:
456
    for node, text in bad_nodes.items():
457
      ToStdout("Error gathering data on node %s: %s",
458
               node, utils.SafeEncode(text[-400:]))
459
      retcode |= 1
460
      ToStdout("You need to fix these nodes first before fixing instances")
461

    
462
  if instances:
463
    for iname in instances:
464
      if iname in missing:
465
        continue
466
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
467
      try:
468
        ToStdout("Activating disks for instance '%s'", iname)
469
        SubmitOpCode(op, opts=opts, cl=cl)
470
      except errors.GenericError, err:
471
        nret, msg = FormatError(err)
472
        retcode |= nret
473
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
474

    
475
  if missing:
476
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])
477

    
478
    for iname, ival in missing.iteritems():
479
      all_missing = compat.all(x[0] in bad_nodes for x in ival)
480
      if all_missing:
481
        ToStdout("Instance %s cannot be verified as it lives on"
482
                 " broken nodes", iname)
483
      else:
484
        ToStdout("Instance %s has missing logical volumes:", iname)
485
        ival.sort()
486
        for node, vol in ival:
487
          if node in bad_nodes:
488
            ToStdout("\tbroken node %s /dev/%s/%s", node, vg_name, vol)
489
          else:
490
            ToStdout("\t%s /dev/%s/%s", node, vg_name, vol)
491

    
492
    ToStdout("You need to run replace_disks for all the above"
493
             " instances, if this message persist after fixing nodes.")
494
    retcode |= 1
495

    
496
  return retcode
497

    
498

    
499
def RepairDiskSizes(opts, args):
500
  """Verify sizes of cluster disks.
501

    
502
  @param opts: the command line options selected by the user
503
  @type args: list
504
  @param args: optional list of instances to restrict check to
505
  @rtype: int
506
  @return: the desired exit code
507

    
508
  """
509
  op = opcodes.OpRepairDiskSizes(instances=args)
510
  SubmitOpCode(op, opts=opts)
511

    
512

    
513
@UsesRPC
514
def MasterFailover(opts, args):
515
  """Failover the master node.
516

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

    
521
  @param opts: the command line options selected by the user
522
  @type args: list
523
  @param args: should be an empty list
524
  @rtype: int
525
  @return: the desired exit code
526

    
527
  """
528
  if opts.no_voting:
529
    usertext = ("This will perform the failover even if most other nodes"
530
                " are down, or if this node is outdated. This is dangerous"
531
                " as it can lead to a non-consistent cluster. Check the"
532
                " gnt-cluster(8) man page before proceeding. Continue?")
533
    if not AskUser(usertext):
534
      return 1
535

    
536
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
537

    
538

    
539
def MasterPing(opts, args):
540
  """Checks if the master is alive.
541

    
542
  @param opts: the command line options selected by the user
543
  @type args: list
544
  @param args: should be an empty list
545
  @rtype: int
546
  @return: the desired exit code
547

    
548
  """
549
  try:
550
    cl = GetClient()
551
    cl.QueryClusterInfo()
552
    return 0
553
  except Exception: # pylint: disable-msg=W0703
554
    return 1
555

    
556

    
557
def SearchTags(opts, args):
558
  """Searches the tags on all the cluster.
559

    
560
  @param opts: the command line options selected by the user
561
  @type args: list
562
  @param args: should contain only one element, the tag pattern
563
  @rtype: int
564
  @return: the desired exit code
565

    
566
  """
567
  op = opcodes.OpSearchTags(pattern=args[0])
568
  result = SubmitOpCode(op, opts=opts)
569
  if not result:
570
    return 1
571
  result = list(result)
572
  result.sort()
573
  for path, tag in result:
574
    ToStdout("%s %s", path, tag)
575

    
576

    
577
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
578
                 new_confd_hmac_key, new_cds, cds_filename,
579
                 force):
580
  """Renews cluster certificates, keys and secrets.
581

    
582
  @type new_cluster_cert: bool
583
  @param new_cluster_cert: Whether to generate a new cluster certificate
584
  @type new_rapi_cert: bool
585
  @param new_rapi_cert: Whether to generate a new RAPI certificate
586
  @type rapi_cert_filename: string
587
  @param rapi_cert_filename: Path to file containing new RAPI certificate
588
  @type new_confd_hmac_key: bool
589
  @param new_confd_hmac_key: Whether to generate a new HMAC key
590
  @type new_cds: bool
591
  @param new_cds: Whether to generate a new cluster domain secret
592
  @type cds_filename: string
593
  @param cds_filename: Path to file containing new cluster domain secret
594
  @type force: bool
595
  @param force: Whether to ask user for confirmation
596

    
597
  """
598
  if new_rapi_cert and rapi_cert_filename:
599
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
600
             " options can be specified at the same time.")
601
    return 1
602

    
603
  if new_cds and cds_filename:
604
    ToStderr("Only one of the --new-cluster-domain-secret and"
605
             " --cluster-domain-secret options can be specified at"
606
             " the same time.")
607
    return 1
608

    
609
  if rapi_cert_filename:
610
    # Read and verify new certificate
611
    try:
612
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
613

    
614
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
615
                                      rapi_cert_pem)
616
    except Exception, err: # pylint: disable-msg=W0703
617
      ToStderr("Can't load new RAPI certificate from %s: %s" %
618
               (rapi_cert_filename, str(err)))
619
      return 1
620

    
621
    try:
622
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
623
    except Exception, err: # pylint: disable-msg=W0703
624
      ToStderr("Can't load new RAPI private key from %s: %s" %
625
               (rapi_cert_filename, str(err)))
626
      return 1
627

    
628
  else:
629
    rapi_cert_pem = None
630

    
631
  if cds_filename:
632
    try:
633
      cds = utils.ReadFile(cds_filename)
634
    except Exception, err: # pylint: disable-msg=W0703
635
      ToStderr("Can't load new cluster domain secret from %s: %s" %
636
               (cds_filename, str(err)))
637
      return 1
638
  else:
639
    cds = None
640

    
641
  if not force:
642
    usertext = ("This requires all daemons on all nodes to be restarted and"
643
                " may take some time. Continue?")
644
    if not AskUser(usertext):
645
      return 1
646

    
647
  def _RenewCryptoInner(ctx):
648
    ctx.feedback_fn("Updating certificates and keys")
649
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
650
                                    new_confd_hmac_key,
651
                                    new_cds,
652
                                    rapi_cert_pem=rapi_cert_pem,
653
                                    cds=cds)
654

    
655
    files_to_copy = []
656

    
657
    if new_cluster_cert:
658
      files_to_copy.append(constants.NODED_CERT_FILE)
659

    
660
    if new_rapi_cert or rapi_cert_pem:
661
      files_to_copy.append(constants.RAPI_CERT_FILE)
662

    
663
    if new_confd_hmac_key:
664
      files_to_copy.append(constants.CONFD_HMAC_KEY)
665

    
666
    if new_cds or cds:
667
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
668

    
669
    if files_to_copy:
670
      for node_name in ctx.nonmaster_nodes:
671
        ctx.feedback_fn("Copying %s to %s" %
672
                        (", ".join(files_to_copy), node_name))
673
        for file_name in files_to_copy:
674
          ctx.ssh.CopyFileToNode(node_name, file_name)
675

    
676
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
677

    
678
  ToStdout("All requested certificates and keys have been replaced."
679
           " Running \"gnt-cluster verify\" now is recommended.")
680

    
681
  return 0
682

    
683

    
684
def RenewCrypto(opts, args):
685
  """Renews cluster certificates, keys and secrets.
686

    
687
  """
688
  return _RenewCrypto(opts.new_cluster_cert,
689
                      opts.new_rapi_cert,
690
                      opts.rapi_cert,
691
                      opts.new_confd_hmac_key,
692
                      opts.new_cluster_domain_secret,
693
                      opts.cluster_domain_secret,
694
                      opts.force)
695

    
696

    
697
def SetClusterParams(opts, args):
698
  """Modify the cluster.
699

    
700
  @param opts: the command line options selected by the user
701
  @type args: list
702
  @param args: should be an empty list
703
  @rtype: int
704
  @return: the desired exit code
705

    
706
  """
707
  if not (not opts.lvm_storage or opts.vg_name or
708
          not opts.drbd_storage or opts.drbd_helper or
709
          opts.enabled_hypervisors or opts.hvparams or
710
          opts.beparams or opts.nicparams or
711
          opts.candidate_pool_size is not None or
712
          opts.uid_pool is not None or
713
          opts.maintain_node_health is not None or
714
          opts.add_uids is not None or
715
          opts.remove_uids is not None or
716
          opts.default_iallocator is not None or
717
          opts.reserved_lvs is not None or
718
          opts.prealloc_wipe_disks is not None):
719
    ToStderr("Please give at least one of the parameters.")
720
    return 1
721

    
722
  vg_name = opts.vg_name
723
  if not opts.lvm_storage and opts.vg_name:
724
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
725
    return 1
726

    
727
  if not opts.lvm_storage:
728
    vg_name = ""
729

    
730
  drbd_helper = opts.drbd_helper
731
  if not opts.drbd_storage and opts.drbd_helper:
732
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
733
    return 1
734

    
735
  if not opts.drbd_storage:
736
    drbd_helper = ""
737

    
738
  hvlist = opts.enabled_hypervisors
739
  if hvlist is not None:
740
    hvlist = hvlist.split(",")
741

    
742
  # a list of (name, dict) we can pass directly to dict() (or [])
743
  hvparams = dict(opts.hvparams)
744
  for hv_params in hvparams.values():
745
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
746

    
747
  beparams = opts.beparams
748
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
749

    
750
  nicparams = opts.nicparams
751
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
752

    
753

    
754
  mnh = opts.maintain_node_health
755

    
756
  uid_pool = opts.uid_pool
757
  if uid_pool is not None:
758
    uid_pool = uidpool.ParseUidPool(uid_pool)
759

    
760
  add_uids = opts.add_uids
761
  if add_uids is not None:
762
    add_uids = uidpool.ParseUidPool(add_uids)
763

    
764
  remove_uids = opts.remove_uids
765
  if remove_uids is not None:
766
    remove_uids = uidpool.ParseUidPool(remove_uids)
767

    
768
  if opts.reserved_lvs is not None:
769
    if opts.reserved_lvs == "":
770
      opts.reserved_lvs = []
771
    else:
772
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
773

    
774
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
775
                                  drbd_helper=drbd_helper,
776
                                  enabled_hypervisors=hvlist,
777
                                  hvparams=hvparams,
778
                                  os_hvp=None,
779
                                  beparams=beparams,
780
                                  nicparams=nicparams,
781
                                  candidate_pool_size=opts.candidate_pool_size,
782
                                  maintain_node_health=mnh,
783
                                  uid_pool=uid_pool,
784
                                  add_uids=add_uids,
785
                                  remove_uids=remove_uids,
786
                                  default_iallocator=opts.default_iallocator,
787
                                  prealloc_wipe_disks=opts.prealloc_wipe_disks,
788
                                  reserved_lvs=opts.reserved_lvs)
789
  SubmitOpCode(op, opts=opts)
790
  return 0
791

    
792

    
793
def QueueOps(opts, args):
794
  """Queue operations.
795

    
796
  @param opts: the command line options selected by the user
797
  @type args: list
798
  @param args: should contain only one element, the subcommand
799
  @rtype: int
800
  @return: the desired exit code
801

    
802
  """
803
  command = args[0]
804
  client = GetClient()
805
  if command in ("drain", "undrain"):
806
    drain_flag = command == "drain"
807
    client.SetQueueDrainFlag(drain_flag)
808
  elif command == "info":
809
    result = client.QueryConfigValues(["drain_flag"])
810
    if result[0]:
811
      val = "set"
812
    else:
813
      val = "unset"
814
    ToStdout("The drain flag is %s" % val)
815
  else:
816
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
817
                               errors.ECODE_INVAL)
818

    
819
  return 0
820

    
821

    
822
def _ShowWatcherPause(until):
823
  if until is None or until < time.time():
824
    ToStdout("The watcher is not paused.")
825
  else:
826
    ToStdout("The watcher is paused until %s.", time.ctime(until))
827

    
828

    
829
def WatcherOps(opts, args):
830
  """Watcher operations.
831

    
832
  @param opts: the command line options selected by the user
833
  @type args: list
834
  @param args: should contain only one element, the subcommand
835
  @rtype: int
836
  @return: the desired exit code
837

    
838
  """
839
  command = args[0]
840
  client = GetClient()
841

    
842
  if command == "continue":
843
    client.SetWatcherPause(None)
844
    ToStdout("The watcher is no longer paused.")
845

    
846
  elif command == "pause":
847
    if len(args) < 2:
848
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
849

    
850
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
851
    _ShowWatcherPause(result)
852

    
853
  elif command == "info":
854
    result = client.QueryConfigValues(["watcher_pause"])
855
    _ShowWatcherPause(result[0])
856

    
857
  else:
858
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
859
                               errors.ECODE_INVAL)
860

    
861
  return 0
862

    
863

    
864
commands = {
865
  'init': (
866
    InitCluster, [ArgHost(min=1, max=1)],
867
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
868
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
869
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
870
     SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
871
     UIDPOOL_OPT, DRBD_HELPER_OPT, NODRBD_STORAGE_OPT,
872
     DEFAULT_IALLOCATOR_OPT, PRIMARY_IP_VERSION_OPT, PREALLOC_WIPE_DISKS_OPT],
873
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
874
  'destroy': (
875
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
876
    "", "Destroy cluster"),
877
  'rename': (
878
    RenameCluster, [ArgHost(min=1, max=1)],
879
    [FORCE_OPT, DRY_RUN_OPT],
880
    "<new_name>",
881
    "Renames the cluster"),
882
  'redist-conf': (
883
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT],
884
    "", "Forces a push of the configuration file and ssconf files"
885
    " to the nodes in the cluster"),
886
  'verify': (
887
    VerifyCluster, ARGS_NONE,
888
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT,
889
     DRY_RUN_OPT, PRIORITY_OPT],
890
    "", "Does a check on the cluster configuration"),
891
  'verify-disks': (
892
    VerifyDisks, ARGS_NONE, [PRIORITY_OPT],
893
    "", "Does a check on the cluster disk status"),
894
  'repair-disk-sizes': (
895
    RepairDiskSizes, ARGS_MANY_INSTANCES, [DRY_RUN_OPT, PRIORITY_OPT],
896
    "", "Updates mismatches in recorded disk sizes"),
897
  'master-failover': (
898
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
899
    "", "Makes the current node the master"),
900
  'master-ping': (
901
    MasterPing, ARGS_NONE, [],
902
    "", "Checks if the master is alive"),
903
  'version': (
904
    ShowClusterVersion, ARGS_NONE, [],
905
    "", "Shows the cluster version"),
906
  'getmaster': (
907
    ShowClusterMaster, ARGS_NONE, [],
908
    "", "Shows the cluster master"),
909
  'copyfile': (
910
    ClusterCopyFile, [ArgFile(min=1, max=1)],
911
    [NODE_LIST_OPT, USE_REPL_NET_OPT],
912
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
913
  'command': (
914
    RunClusterCommand, [ArgCommand(min=1)],
915
    [NODE_LIST_OPT],
916
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
917
  'info': (
918
    ShowClusterConfig, ARGS_NONE, [ROMAN_OPT],
919
    "[--roman]", "Show cluster configuration"),
920
  'list-tags': (
921
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
922
  'add-tags': (
923
    AddTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
924
    "tag...", "Add tags to the cluster"),
925
  'remove-tags': (
926
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
927
    "tag...", "Remove tags from the cluster"),
928
  'search-tags': (
929
    SearchTags, [ArgUnknown(min=1, max=1)], [PRIORITY_OPT], "",
930
    "Searches the tags on all objects on"
931
    " the cluster for a given pattern (regex)"),
932
  'queue': (
933
    QueueOps,
934
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
935
    [], "drain|undrain|info", "Change queue properties"),
936
  'watcher': (
937
    WatcherOps,
938
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
939
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
940
    [],
941
    "{pause <timespec>|continue|info}", "Change watcher properties"),
942
  'modify': (
943
    SetClusterParams, ARGS_NONE,
944
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
945
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
946
     UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT, DRBD_HELPER_OPT,
947
     NODRBD_STORAGE_OPT, DEFAULT_IALLOCATOR_OPT, RESERVED_LVS_OPT,
948
     DRY_RUN_OPT, PRIORITY_OPT, PREALLOC_WIPE_DISKS_OPT],
949
    "[opts...]",
950
    "Alters the parameters of the cluster"),
951
  "renew-crypto": (
952
    RenewCrypto, ARGS_NONE,
953
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
954
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT,
955
     NEW_CLUSTER_DOMAIN_SECRET_OPT, CLUSTER_DOMAIN_SECRET_OPT],
956
    "[opts...]",
957
    "Renews cluster certificates, keys and secrets"),
958
  }
959

    
960

    
961
#: dictionary with aliases for commands
962
aliases = {
963
  'masterfailover': 'master-failover',
964
}
965

    
966

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