Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_cluster.py @ afc3c260

History | View | Annotate | Download (31 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011 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 os.path
30
import time
31
import OpenSSL
32

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

    
44

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

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

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

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

    
65
  if not opts.drbd_storage and opts.drbd_helper:
66
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
67
    return 1
68

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

    
73
  master_netdev = opts.master_netdev
74
  if master_netdev is None:
75
    master_netdev = constants.DEFAULT_BRIDGE
76

    
77
  hvlist = opts.enabled_hypervisors
78
  if hvlist is None:
79
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
80
  hvlist = hvlist.split(",")
81

    
82
  hvparams = dict(opts.hvparams)
83
  beparams = opts.beparams
84
  nicparams = opts.nicparams
85

    
86
  # prepare beparams dict
87
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
88
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
89

    
90
  # prepare nicparams dict
91
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
92
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
93

    
94
  # prepare ndparams dict
95
  if opts.ndparams is None:
96
    ndparams = dict(constants.NDC_DEFAULTS)
97
  else:
98
    ndparams = objects.FillDict(constants.NDC_DEFAULTS, opts.ndparams)
99
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
100

    
101
  # prepare hvparams dict
102
  for hv in constants.HYPER_TYPES:
103
    if hv not in hvparams:
104
      hvparams[hv] = {}
105
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
106
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
107

    
108
  if opts.candidate_pool_size is None:
109
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
110

    
111
  if opts.mac_prefix is None:
112
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
113

    
114
  uid_pool = opts.uid_pool
115
  if uid_pool is not None:
116
    uid_pool = uidpool.ParseUidPool(uid_pool)
117

    
118
  if opts.prealloc_wipe_disks is None:
119
    opts.prealloc_wipe_disks = False
120

    
121
  try:
122
    primary_ip_version = int(opts.primary_ip_version)
123
  except (ValueError, TypeError), err:
124
    ToStderr("Invalid primary ip version value: %s" % str(err))
125
    return 1
126

    
127
  bootstrap.InitCluster(cluster_name=args[0],
128
                        secondary_ip=opts.secondary_ip,
129
                        vg_name=vg_name,
130
                        mac_prefix=opts.mac_prefix,
131
                        master_netdev=master_netdev,
132
                        file_storage_dir=opts.file_storage_dir,
133
                        enabled_hypervisors=hvlist,
134
                        hvparams=hvparams,
135
                        beparams=beparams,
136
                        nicparams=nicparams,
137
                        ndparams=ndparams,
138
                        candidate_pool_size=opts.candidate_pool_size,
139
                        modify_etc_hosts=opts.modify_etc_hosts,
140
                        modify_ssh_setup=opts.modify_ssh_setup,
141
                        maintain_node_health=opts.maintain_node_health,
142
                        drbd_helper=drbd_helper,
143
                        uid_pool=uid_pool,
144
                        default_iallocator=opts.default_iallocator,
145
                        primary_ip_version=primary_ip_version,
146
                        prealloc_wipe_disks=opts.prealloc_wipe_disks,
147
                        )
148
  op = opcodes.OpClusterPostInit()
149
  SubmitOpCode(op, opts=opts)
150
  return 0
151

    
152

    
153
@UsesRPC
154
def DestroyCluster(opts, args):
155
  """Destroy the cluster.
156

157
  @param opts: the command line options selected by the user
158
  @type args: list
159
  @param args: should be an empty list
160
  @rtype: int
161
  @return: the desired exit code
162

163
  """
164
  if not opts.yes_do_it:
165
    ToStderr("Destroying a cluster is irreversible. If you really want"
166
             " destroy this cluster, supply the --yes-do-it option.")
167
    return 1
168

    
169
  op = opcodes.OpClusterDestroy()
170
  master = SubmitOpCode(op, opts=opts)
171
  # if we reached this, the opcode didn't fail; we can proceed to
172
  # shutdown all the daemons
173
  bootstrap.FinalizeClusterDestroy(master)
174
  return 0
175

    
176

    
177
def RenameCluster(opts, args):
178
  """Rename the cluster.
179

180
  @param opts: the command line options selected by the user
181
  @type args: list
182
  @param args: should contain only one element, the new cluster name
183
  @rtype: int
184
  @return: the desired exit code
185

186
  """
187
  cl = GetClient()
188

    
189
  (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
190

    
191
  new_name = args[0]
192
  if not opts.force:
193
    usertext = ("This will rename the cluster from '%s' to '%s'. If you are"
194
                " connected over the network to the cluster name, the"
195
                " operation is very dangerous as the IP address will be"
196
                " removed from the node and the change may not go through."
197
                " Continue?") % (cluster_name, new_name)
198
    if not AskUser(usertext):
199
      return 1
200

    
201
  op = opcodes.OpClusterRename(name=new_name)
202
  result = SubmitOpCode(op, opts=opts, cl=cl)
203

    
204
  if result:
205
    ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
206

    
207
  return 0
208

    
209

    
210
def RedistributeConfig(opts, args):
211
  """Forces push of the cluster configuration.
212

213
  @param opts: the command line options selected by the user
214
  @type args: list
215
  @param args: empty list
216
  @rtype: int
217
  @return: the desired exit code
218

219
  """
220
  op = opcodes.OpClusterRedistConf()
221
  SubmitOrSend(op, opts)
222
  return 0
223

    
224

    
225
def ShowClusterVersion(opts, args):
226
  """Write version of ganeti software to the standard output.
227

228
  @param opts: the command line options selected by the user
229
  @type args: list
230
  @param args: should be an empty list
231
  @rtype: int
232
  @return: the desired exit code
233

234
  """
235
  cl = GetClient()
236
  result = cl.QueryClusterInfo()
237
  ToStdout("Software version: %s", result["software_version"])
238
  ToStdout("Internode protocol: %s", result["protocol_version"])
239
  ToStdout("Configuration format: %s", result["config_version"])
240
  ToStdout("OS api version: %s", result["os_api_version"])
241
  ToStdout("Export interface: %s", result["export_version"])
242
  return 0
243

    
244

    
245
def ShowClusterMaster(opts, args):
246
  """Write name of master node to the standard output.
247

248
  @param opts: the command line options selected by the user
249
  @type args: list
250
  @param args: should be an empty list
251
  @rtype: int
252
  @return: the desired exit code
253

254
  """
255
  master = bootstrap.GetMaster()
256
  ToStdout(master)
257
  return 0
258

    
259

    
260
def _PrintGroupedParams(paramsdict, level=1, roman=False):
261
  """Print Grouped parameters (be, nic, disk) by group.
262

263
  @type paramsdict: dict of dicts
264
  @param paramsdict: {group: {param: value, ...}, ...}
265
  @type level: int
266
  @param level: Level of indention
267

268
  """
269
  indent = "  " * level
270
  for item, val in sorted(paramsdict.items()):
271
    if isinstance(val, dict):
272
      ToStdout("%s- %s:", indent, item)
273
      _PrintGroupedParams(val, level=level + 1, roman=roman)
274
    elif roman and isinstance(val, int):
275
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
276
    else:
277
      ToStdout("%s  %s: %s", indent, item, val)
278

    
279

    
280
def ShowClusterConfig(opts, args):
281
  """Shows cluster information.
282

283
  @param opts: the command line options selected by the user
284
  @type args: list
285
  @param args: should be an empty list
286
  @rtype: int
287
  @return: the desired exit code
288

289
  """
290
  cl = GetClient()
291
  result = cl.QueryClusterInfo()
292

    
293
  ToStdout("Cluster name: %s", result["name"])
294
  ToStdout("Cluster UUID: %s", result["uuid"])
295

    
296
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
297
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
298

    
299
  ToStdout("Master node: %s", result["master"])
300

    
301
  ToStdout("Architecture (this node): %s (%s)",
302
           result["architecture"][0], result["architecture"][1])
303

    
304
  if result["tags"]:
305
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
306
  else:
307
    tags = "(none)"
308

    
309
  ToStdout("Tags: %s", tags)
310

    
311
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
312
  ToStdout("Enabled hypervisors: %s",
313
           utils.CommaJoin(result["enabled_hypervisors"]))
314

    
315
  ToStdout("Hypervisor parameters:")
316
  _PrintGroupedParams(result["hvparams"])
317

    
318
  ToStdout("OS-specific hypervisor parameters:")
319
  _PrintGroupedParams(result["os_hvp"])
320

    
321
  ToStdout("OS parameters:")
322
  _PrintGroupedParams(result["osparams"])
323

    
324
  ToStdout("Hidden OSes: %s", utils.CommaJoin(result["hidden_os"]))
325
  ToStdout("Blacklisted OSes: %s", utils.CommaJoin(result["blacklisted_os"]))
326

    
327
  ToStdout("Cluster parameters:")
328
  ToStdout("  - candidate pool size: %s",
329
            compat.TryToRoman(result["candidate_pool_size"],
330
                              convert=opts.roman_integers))
331
  ToStdout("  - master netdev: %s", result["master_netdev"])
332
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
333
  if result["reserved_lvs"]:
334
    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
335
  else:
336
    reserved_lvs = "(none)"
337
  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
338
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
339
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
340
  ToStdout("  - maintenance of node health: %s",
341
           result["maintain_node_health"])
342
  ToStdout("  - uid pool: %s",
343
            uidpool.FormatUidPool(result["uid_pool"],
344
                                  roman=opts.roman_integers))
345
  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
346
  ToStdout("  - primary ip version: %d", result["primary_ip_version"])
347
  ToStdout("  - preallocation wipe disks: %s", result["prealloc_wipe_disks"])
348

    
349
  ToStdout("Default node parameters:")
350
  _PrintGroupedParams(result["ndparams"], roman=opts.roman_integers)
351

    
352
  ToStdout("Default instance parameters:")
353
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
354

    
355
  ToStdout("Default nic parameters:")
356
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
357

    
358
  return 0
359

    
360

    
361
def ClusterCopyFile(opts, args):
362
  """Copy a file from master to some nodes.
363

364
  @param opts: the command line options selected by the user
365
  @type args: list
366
  @param args: should contain only one element, the path of
367
      the file to be copied
368
  @rtype: int
369
  @return: the desired exit code
370

371
  """
372
  filename = args[0]
373
  if not os.path.exists(filename):
374
    raise errors.OpPrereqError("No such filename '%s'" % filename,
375
                               errors.ECODE_INVAL)
376

    
377
  cl = GetClient()
378

    
379
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
380

    
381
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
382
                           secondary_ips=opts.use_replication_network)
383

    
384
  srun = ssh.SshRunner(cluster_name=cluster_name)
385
  for node in results:
386
    if not srun.CopyFileToNode(node, filename):
387
      ToStderr("Copy of file %s to node %s failed", filename, node)
388

    
389
  return 0
390

    
391

    
392
def RunClusterCommand(opts, args):
393
  """Run a command on some nodes.
394

395
  @param opts: the command line options selected by the user
396
  @type args: list
397
  @param args: should contain the command to be run and its arguments
398
  @rtype: int
399
  @return: the desired exit code
400

401
  """
402
  cl = GetClient()
403

    
404
  command = " ".join(args)
405

    
406
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
407

    
408
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
409
                                                    "master_node"])
410

    
411
  srun = ssh.SshRunner(cluster_name=cluster_name)
412

    
413
  # Make sure master node is at list end
414
  if master_node in nodes:
415
    nodes.remove(master_node)
416
    nodes.append(master_node)
417

    
418
  for name in nodes:
419
    result = srun.Run(name, "root", command)
420
    ToStdout("------------------------------------------------")
421
    ToStdout("node: %s", name)
422
    ToStdout("%s", result.output)
423
    ToStdout("return code = %s", result.exit_code)
424

    
425
  return 0
426

    
427

    
428
def VerifyCluster(opts, args):
429
  """Verify integrity of cluster, performing various test on nodes.
430

431
  @param opts: the command line options selected by the user
432
  @type args: list
433
  @param args: should be an empty list
434
  @rtype: int
435
  @return: the desired exit code
436

437
  """
438
  skip_checks = []
439
  if opts.skip_nplusone_mem:
440
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
441
  op = opcodes.OpClusterVerify(skip_checks=skip_checks,
442
                               verbose=opts.verbose,
443
                               error_codes=opts.error_codes,
444
                               debug_simulate_errors=opts.simulate_errors)
445
  if SubmitOpCode(op, opts=opts):
446
    return 0
447
  else:
448
    return 1
449

    
450

    
451
def VerifyDisks(opts, args):
452
  """Verify integrity of cluster disks.
453

454
  @param opts: the command line options selected by the user
455
  @type args: list
456
  @param args: should be an empty list
457
  @rtype: int
458
  @return: the desired exit code
459

460
  """
461
  cl = GetClient()
462

    
463
  op = opcodes.OpClusterVerifyDisks()
464
  result = SubmitOpCode(op, opts=opts, cl=cl)
465
  if not isinstance(result, (list, tuple)) or len(result) != 3:
466
    raise errors.ProgrammerError("Unknown result type for OpClusterVerifyDisks")
467

    
468
  bad_nodes, instances, missing = result
469

    
470
  retcode = constants.EXIT_SUCCESS
471

    
472
  if bad_nodes:
473
    for node, text in bad_nodes.items():
474
      ToStdout("Error gathering data on node %s: %s",
475
               node, utils.SafeEncode(text[-400:]))
476
      retcode |= 1
477
      ToStdout("You need to fix these nodes first before fixing instances")
478

    
479
  if instances:
480
    for iname in instances:
481
      if iname in missing:
482
        continue
483
      op = opcodes.OpInstanceActivateDisks(instance_name=iname)
484
      try:
485
        ToStdout("Activating disks for instance '%s'", iname)
486
        SubmitOpCode(op, opts=opts, cl=cl)
487
      except errors.GenericError, err:
488
        nret, msg = FormatError(err)
489
        retcode |= nret
490
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
491

    
492
  if missing:
493
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])
494

    
495
    for iname, ival in missing.iteritems():
496
      all_missing = compat.all(x[0] in bad_nodes for x in ival)
497
      if all_missing:
498
        ToStdout("Instance %s cannot be verified as it lives on"
499
                 " broken nodes", iname)
500
      else:
501
        ToStdout("Instance %s has missing logical volumes:", iname)
502
        ival.sort()
503
        for node, vol in ival:
504
          if node in bad_nodes:
505
            ToStdout("\tbroken node %s /dev/%s/%s", node, vg_name, vol)
506
          else:
507
            ToStdout("\t%s /dev/%s/%s", node, vg_name, vol)
508

    
509
    ToStdout("You need to run replace_disks for all the above"
510
             " instances, if this message persist after fixing nodes.")
511
    retcode |= 1
512

    
513
  return retcode
514

    
515

    
516
def RepairDiskSizes(opts, args):
517
  """Verify sizes of cluster disks.
518

519
  @param opts: the command line options selected by the user
520
  @type args: list
521
  @param args: optional list of instances to restrict check to
522
  @rtype: int
523
  @return: the desired exit code
524

525
  """
526
  op = opcodes.OpClusterRepairDiskSizes(instances=args)
527
  SubmitOpCode(op, opts=opts)
528

    
529

    
530
@UsesRPC
531
def MasterFailover(opts, args):
532
  """Failover the master node.
533

534
  This command, when run on a non-master node, will cause the current
535
  master to cease being master, and the non-master to become new
536
  master.
537

538
  @param opts: the command line options selected by the user
539
  @type args: list
540
  @param args: should be an empty list
541
  @rtype: int
542
  @return: the desired exit code
543

544
  """
545
  if opts.no_voting:
546
    usertext = ("This will perform the failover even if most other nodes"
547
                " are down, or if this node is outdated. This is dangerous"
548
                " as it can lead to a non-consistent cluster. Check the"
549
                " gnt-cluster(8) man page before proceeding. Continue?")
550
    if not AskUser(usertext):
551
      return 1
552

    
553
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
554

    
555

    
556
def MasterPing(opts, args):
557
  """Checks if the master is alive.
558

559
  @param opts: the command line options selected by the user
560
  @type args: list
561
  @param args: should be an empty list
562
  @rtype: int
563
  @return: the desired exit code
564

565
  """
566
  try:
567
    cl = GetClient()
568
    cl.QueryClusterInfo()
569
    return 0
570
  except Exception: # pylint: disable-msg=W0703
571
    return 1
572

    
573

    
574
def SearchTags(opts, args):
575
  """Searches the tags on all the cluster.
576

577
  @param opts: the command line options selected by the user
578
  @type args: list
579
  @param args: should contain only one element, the tag pattern
580
  @rtype: int
581
  @return: the desired exit code
582

583
  """
584
  op = opcodes.OpTagsSearch(pattern=args[0])
585
  result = SubmitOpCode(op, opts=opts)
586
  if not result:
587
    return 1
588
  result = list(result)
589
  result.sort()
590
  for path, tag in result:
591
    ToStdout("%s %s", path, tag)
592

    
593

    
594
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
595
                 new_confd_hmac_key, new_cds, cds_filename,
596
                 force):
597
  """Renews cluster certificates, keys and secrets.
598

599
  @type new_cluster_cert: bool
600
  @param new_cluster_cert: Whether to generate a new cluster certificate
601
  @type new_rapi_cert: bool
602
  @param new_rapi_cert: Whether to generate a new RAPI certificate
603
  @type rapi_cert_filename: string
604
  @param rapi_cert_filename: Path to file containing new RAPI certificate
605
  @type new_confd_hmac_key: bool
606
  @param new_confd_hmac_key: Whether to generate a new HMAC key
607
  @type new_cds: bool
608
  @param new_cds: Whether to generate a new cluster domain secret
609
  @type cds_filename: string
610
  @param cds_filename: Path to file containing new cluster domain secret
611
  @type force: bool
612
  @param force: Whether to ask user for confirmation
613

614
  """
615
  if new_rapi_cert and rapi_cert_filename:
616
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
617
             " options can be specified at the same time.")
618
    return 1
619

    
620
  if new_cds and cds_filename:
621
    ToStderr("Only one of the --new-cluster-domain-secret and"
622
             " --cluster-domain-secret options can be specified at"
623
             " the same time.")
624
    return 1
625

    
626
  if rapi_cert_filename:
627
    # Read and verify new certificate
628
    try:
629
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
630

    
631
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
632
                                      rapi_cert_pem)
633
    except Exception, err: # pylint: disable-msg=W0703
634
      ToStderr("Can't load new RAPI certificate from %s: %s" %
635
               (rapi_cert_filename, str(err)))
636
      return 1
637

    
638
    try:
639
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
640
    except Exception, err: # pylint: disable-msg=W0703
641
      ToStderr("Can't load new RAPI private key from %s: %s" %
642
               (rapi_cert_filename, str(err)))
643
      return 1
644

    
645
  else:
646
    rapi_cert_pem = None
647

    
648
  if cds_filename:
649
    try:
650
      cds = utils.ReadFile(cds_filename)
651
    except Exception, err: # pylint: disable-msg=W0703
652
      ToStderr("Can't load new cluster domain secret from %s: %s" %
653
               (cds_filename, str(err)))
654
      return 1
655
  else:
656
    cds = None
657

    
658
  if not force:
659
    usertext = ("This requires all daemons on all nodes to be restarted and"
660
                " may take some time. Continue?")
661
    if not AskUser(usertext):
662
      return 1
663

    
664
  def _RenewCryptoInner(ctx):
665
    ctx.feedback_fn("Updating certificates and keys")
666
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
667
                                    new_confd_hmac_key,
668
                                    new_cds,
669
                                    rapi_cert_pem=rapi_cert_pem,
670
                                    cds=cds)
671

    
672
    files_to_copy = []
673

    
674
    if new_cluster_cert:
675
      files_to_copy.append(constants.NODED_CERT_FILE)
676

    
677
    if new_rapi_cert or rapi_cert_pem:
678
      files_to_copy.append(constants.RAPI_CERT_FILE)
679

    
680
    if new_confd_hmac_key:
681
      files_to_copy.append(constants.CONFD_HMAC_KEY)
682

    
683
    if new_cds or cds:
684
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
685

    
686
    if files_to_copy:
687
      for node_name in ctx.nonmaster_nodes:
688
        ctx.feedback_fn("Copying %s to %s" %
689
                        (", ".join(files_to_copy), node_name))
690
        for file_name in files_to_copy:
691
          ctx.ssh.CopyFileToNode(node_name, file_name)
692

    
693
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
694

    
695
  ToStdout("All requested certificates and keys have been replaced."
696
           " Running \"gnt-cluster verify\" now is recommended.")
697

    
698
  return 0
699

    
700

    
701
def RenewCrypto(opts, args):
702
  """Renews cluster certificates, keys and secrets.
703

704
  """
705
  return _RenewCrypto(opts.new_cluster_cert,
706
                      opts.new_rapi_cert,
707
                      opts.rapi_cert,
708
                      opts.new_confd_hmac_key,
709
                      opts.new_cluster_domain_secret,
710
                      opts.cluster_domain_secret,
711
                      opts.force)
712

    
713

    
714
def SetClusterParams(opts, args):
715
  """Modify the cluster.
716

717
  @param opts: the command line options selected by the user
718
  @type args: list
719
  @param args: should be an empty list
720
  @rtype: int
721
  @return: the desired exit code
722

723
  """
724
  if not (not opts.lvm_storage or opts.vg_name or
725
          not opts.drbd_storage or opts.drbd_helper or
726
          opts.enabled_hypervisors or opts.hvparams or
727
          opts.beparams or opts.nicparams or opts.ndparams or
728
          opts.candidate_pool_size is not None or
729
          opts.uid_pool is not None or
730
          opts.maintain_node_health is not None or
731
          opts.add_uids is not None or
732
          opts.remove_uids is not None or
733
          opts.default_iallocator is not None or
734
          opts.reserved_lvs is not None or
735
          opts.master_netdev is not None or
736
          opts.prealloc_wipe_disks is not None):
737
    ToStderr("Please give at least one of the parameters.")
738
    return 1
739

    
740
  vg_name = opts.vg_name
741
  if not opts.lvm_storage and opts.vg_name:
742
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
743
    return 1
744

    
745
  if not opts.lvm_storage:
746
    vg_name = ""
747

    
748
  drbd_helper = opts.drbd_helper
749
  if not opts.drbd_storage and opts.drbd_helper:
750
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
751
    return 1
752

    
753
  if not opts.drbd_storage:
754
    drbd_helper = ""
755

    
756
  hvlist = opts.enabled_hypervisors
757
  if hvlist is not None:
758
    hvlist = hvlist.split(",")
759

    
760
  # a list of (name, dict) we can pass directly to dict() (or [])
761
  hvparams = dict(opts.hvparams)
762
  for hv_params in hvparams.values():
763
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
764

    
765
  beparams = opts.beparams
766
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
767

    
768
  nicparams = opts.nicparams
769
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
770

    
771
  ndparams = opts.ndparams
772
  if ndparams is not None:
773
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
774

    
775
  mnh = opts.maintain_node_health
776

    
777
  uid_pool = opts.uid_pool
778
  if uid_pool is not None:
779
    uid_pool = uidpool.ParseUidPool(uid_pool)
780

    
781
  add_uids = opts.add_uids
782
  if add_uids is not None:
783
    add_uids = uidpool.ParseUidPool(add_uids)
784

    
785
  remove_uids = opts.remove_uids
786
  if remove_uids is not None:
787
    remove_uids = uidpool.ParseUidPool(remove_uids)
788

    
789
  if opts.reserved_lvs is not None:
790
    if opts.reserved_lvs == "":
791
      opts.reserved_lvs = []
792
    else:
793
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
794

    
795
  op = opcodes.OpClusterSetParams(vg_name=vg_name,
796
                                  drbd_helper=drbd_helper,
797
                                  enabled_hypervisors=hvlist,
798
                                  hvparams=hvparams,
799
                                  os_hvp=None,
800
                                  beparams=beparams,
801
                                  nicparams=nicparams,
802
                                  ndparams=ndparams,
803
                                  candidate_pool_size=opts.candidate_pool_size,
804
                                  maintain_node_health=mnh,
805
                                  uid_pool=uid_pool,
806
                                  add_uids=add_uids,
807
                                  remove_uids=remove_uids,
808
                                  default_iallocator=opts.default_iallocator,
809
                                  prealloc_wipe_disks=opts.prealloc_wipe_disks,
810
                                  master_netdev=opts.master_netdev,
811
                                  reserved_lvs=opts.reserved_lvs)
812
  SubmitOpCode(op, opts=opts)
813
  return 0
814

    
815

    
816
def QueueOps(opts, args):
817
  """Queue operations.
818

819
  @param opts: the command line options selected by the user
820
  @type args: list
821
  @param args: should contain only one element, the subcommand
822
  @rtype: int
823
  @return: the desired exit code
824

825
  """
826
  command = args[0]
827
  client = GetClient()
828
  if command in ("drain", "undrain"):
829
    drain_flag = command == "drain"
830
    client.SetQueueDrainFlag(drain_flag)
831
  elif command == "info":
832
    result = client.QueryConfigValues(["drain_flag"])
833
    if result[0]:
834
      val = "set"
835
    else:
836
      val = "unset"
837
    ToStdout("The drain flag is %s" % val)
838
  else:
839
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
840
                               errors.ECODE_INVAL)
841

    
842
  return 0
843

    
844

    
845
def _ShowWatcherPause(until):
846
  if until is None or until < time.time():
847
    ToStdout("The watcher is not paused.")
848
  else:
849
    ToStdout("The watcher is paused until %s.", time.ctime(until))
850

    
851

    
852
def WatcherOps(opts, args):
853
  """Watcher operations.
854

855
  @param opts: the command line options selected by the user
856
  @type args: list
857
  @param args: should contain only one element, the subcommand
858
  @rtype: int
859
  @return: the desired exit code
860

861
  """
862
  command = args[0]
863
  client = GetClient()
864

    
865
  if command == "continue":
866
    client.SetWatcherPause(None)
867
    ToStdout("The watcher is no longer paused.")
868

    
869
  elif command == "pause":
870
    if len(args) < 2:
871
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
872

    
873
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
874
    _ShowWatcherPause(result)
875

    
876
  elif command == "info":
877
    result = client.QueryConfigValues(["watcher_pause"])
878
    _ShowWatcherPause(result[0])
879

    
880
  else:
881
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
882
                               errors.ECODE_INVAL)
883

    
884
  return 0
885

    
886

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

    
984

    
985
#: dictionary with aliases for commands
986
aliases = {
987
  'masterfailover': 'master-failover',
988
}
989

    
990

    
991
def Main():
992
  return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
993
                     aliases=aliases)