Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_cluster.py @ a6682fdc

History | View | Annotate | Download (30.8 kB)

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

    
346
  ToStdout("Default node parameters:")
347
  _PrintGroupedParams(result["ndparams"], roman=opts.roman_integers)
348

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

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

    
355
  return 0
356

    
357

    
358
def ClusterCopyFile(opts, args):
359
  """Copy a file from master to some nodes.
360

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

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

    
374
  cl = GetClient()
375

    
376
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
377

    
378
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
379
                           secondary_ips=opts.use_replication_network)
380

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

    
386
  return 0
387

    
388

    
389
def RunClusterCommand(opts, args):
390
  """Run a command on some nodes.
391

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

398
  """
399
  cl = GetClient()
400

    
401
  command = " ".join(args)
402

    
403
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
404

    
405
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
406
                                                    "master_node"])
407

    
408
  srun = ssh.SshRunner(cluster_name=cluster_name)
409

    
410
  # Make sure master node is at list end
411
  if master_node in nodes:
412
    nodes.remove(master_node)
413
    nodes.append(master_node)
414

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

    
422
  return 0
423

    
424

    
425
def VerifyCluster(opts, args):
426
  """Verify integrity of cluster, performing various test on nodes.
427

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

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

    
447

    
448
def VerifyDisks(opts, args):
449
  """Verify integrity of cluster disks.
450

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

457
  """
458
  cl = GetClient()
459

    
460
  op = opcodes.OpVerifyDisks()
461
  result = SubmitOpCode(op, opts=opts, cl=cl)
462
  if not isinstance(result, (list, tuple)) or len(result) != 3:
463
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
464

    
465
  bad_nodes, instances, missing = result
466

    
467
  retcode = constants.EXIT_SUCCESS
468

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

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

    
489
  if missing:
490
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])
491

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

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

    
510
  return retcode
511

    
512

    
513
def RepairDiskSizes(opts, args):
514
  """Verify sizes of cluster disks.
515

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

522
  """
523
  op = opcodes.OpClusterRepairDiskSizes(instances=args)
524
  SubmitOpCode(op, opts=opts)
525

    
526

    
527
@UsesRPC
528
def MasterFailover(opts, args):
529
  """Failover the master node.
530

531
  This command, when run on a non-master node, will cause the current
532
  master to cease being master, and the non-master to become new
533
  master.
534

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

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

    
550
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
551

    
552

    
553
def MasterPing(opts, args):
554
  """Checks if the master is alive.
555

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

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

    
570

    
571
def SearchTags(opts, args):
572
  """Searches the tags on all the cluster.
573

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

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

    
590

    
591
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
592
                 new_confd_hmac_key, new_cds, cds_filename,
593
                 force):
594
  """Renews cluster certificates, keys and secrets.
595

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

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

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

    
623
  if rapi_cert_filename:
624
    # Read and verify new certificate
625
    try:
626
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
627

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

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

    
642
  else:
643
    rapi_cert_pem = None
644

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

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

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

    
669
    files_to_copy = []
670

    
671
    if new_cluster_cert:
672
      files_to_copy.append(constants.NODED_CERT_FILE)
673

    
674
    if new_rapi_cert or rapi_cert_pem:
675
      files_to_copy.append(constants.RAPI_CERT_FILE)
676

    
677
    if new_confd_hmac_key:
678
      files_to_copy.append(constants.CONFD_HMAC_KEY)
679

    
680
    if new_cds or cds:
681
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
682

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

    
690
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
691

    
692
  ToStdout("All requested certificates and keys have been replaced."
693
           " Running \"gnt-cluster verify\" now is recommended.")
694

    
695
  return 0
696

    
697

    
698
def RenewCrypto(opts, args):
699
  """Renews cluster certificates, keys and secrets.
700

701
  """
702
  return _RenewCrypto(opts.new_cluster_cert,
703
                      opts.new_rapi_cert,
704
                      opts.rapi_cert,
705
                      opts.new_confd_hmac_key,
706
                      opts.new_cluster_domain_secret,
707
                      opts.cluster_domain_secret,
708
                      opts.force)
709

    
710

    
711
def SetClusterParams(opts, args):
712
  """Modify the cluster.
713

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

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

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

    
742
  if not opts.lvm_storage:
743
    vg_name = ""
744

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

    
750
  if not opts.drbd_storage:
751
    drbd_helper = ""
752

    
753
  hvlist = opts.enabled_hypervisors
754
  if hvlist is not None:
755
    hvlist = hvlist.split(",")
756

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

    
762
  beparams = opts.beparams
763
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
764

    
765
  nicparams = opts.nicparams
766
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
767

    
768
  ndparams = opts.ndparams
769
  if ndparams is not None:
770
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
771

    
772
  mnh = opts.maintain_node_health
773

    
774
  uid_pool = opts.uid_pool
775
  if uid_pool is not None:
776
    uid_pool = uidpool.ParseUidPool(uid_pool)
777

    
778
  add_uids = opts.add_uids
779
  if add_uids is not None:
780
    add_uids = uidpool.ParseUidPool(add_uids)
781

    
782
  remove_uids = opts.remove_uids
783
  if remove_uids is not None:
784
    remove_uids = uidpool.ParseUidPool(remove_uids)
785

    
786
  if opts.reserved_lvs is not None:
787
    if opts.reserved_lvs == "":
788
      opts.reserved_lvs = []
789
    else:
790
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
791

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

    
812

    
813
def QueueOps(opts, args):
814
  """Queue operations.
815

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

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

    
839
  return 0
840

    
841

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

    
848

    
849
def WatcherOps(opts, args):
850
  """Watcher operations.
851

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

858
  """
859
  command = args[0]
860
  client = GetClient()
861

    
862
  if command == "continue":
863
    client.SetWatcherPause(None)
864
    ToStdout("The watcher is no longer paused.")
865

    
866
  elif command == "pause":
867
    if len(args) < 2:
868
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
869

    
870
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
871
    _ShowWatcherPause(result)
872

    
873
  elif command == "info":
874
    result = client.QueryConfigValues(["watcher_pause"])
875
    _ShowWatcherPause(result[0])
876

    
877
  else:
878
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
879
                               errors.ECODE_INVAL)
880

    
881
  return 0
882

    
883

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

    
981

    
982
#: dictionary with aliases for commands
983
aliases = {
984
  'masterfailover': 'master-failover',
985
}
986

    
987

    
988
def Main():
989
  return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
990
                     aliases=aliases)