Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_cluster.py @ 6204ee71

History | View | Annotate | Download (30.5 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
  hvlist = opts.enabled_hypervisors
74
  if hvlist is None:
75
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
76
  hvlist = hvlist.split(",")
77

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

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

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

    
90
  # prepare ndparams dict
91
  if opts.ndparams is None:
92
    ndparams = dict(constants.NDC_DEFAULTS)
93
  else:
94
    ndparams = objects.FillDict(constants.NDC_DEFAULTS, opts.ndparams)
95
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
96

    
97
  # prepare hvparams dict
98
  for hv in constants.HYPER_TYPES:
99
    if hv not in hvparams:
100
      hvparams[hv] = {}
101
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
102
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
103

    
104
  if opts.candidate_pool_size is None:
105
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
106

    
107
  if opts.mac_prefix is None:
108
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
109

    
110
  uid_pool = opts.uid_pool
111
  if uid_pool is not None:
112
    uid_pool = uidpool.ParseUidPool(uid_pool)
113

    
114
  if opts.prealloc_wipe_disks is None:
115
    opts.prealloc_wipe_disks = False
116

    
117
  try:
118
    primary_ip_version = int(opts.primary_ip_version)
119
  except (ValueError, TypeError), err:
120
    ToStderr("Invalid primary ip version value: %s" % str(err))
121
    return 1
122

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

    
148

    
149
@UsesRPC
150
def DestroyCluster(opts, args):
151
  """Destroy the cluster.
152

153
  @param opts: the command line options selected by the user
154
  @type args: list
155
  @param args: should be an empty list
156
  @rtype: int
157
  @return: the desired exit code
158

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

    
165
  op = opcodes.OpDestroyCluster()
166
  master = SubmitOpCode(op, opts=opts)
167
  # if we reached this, the opcode didn't fail; we can proceed to
168
  # shutdown all the daemons
169
  bootstrap.FinalizeClusterDestroy(master)
170
  return 0
171

    
172

    
173
def RenameCluster(opts, args):
174
  """Rename the cluster.
175

176
  @param opts: the command line options selected by the user
177
  @type args: list
178
  @param args: should contain only one element, the new cluster name
179
  @rtype: int
180
  @return: the desired exit code
181

182
  """
183
  cl = GetClient()
184

    
185
  (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
186

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

    
197
  op = opcodes.OpRenameCluster(name=new_name)
198
  result = SubmitOpCode(op, opts=opts, cl=cl)
199

    
200
  if result:
201
    ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
202

    
203
  return 0
204

    
205

    
206
def RedistributeConfig(opts, args):
207
  """Forces push of the cluster configuration.
208

209
  @param opts: the command line options selected by the user
210
  @type args: list
211
  @param args: empty list
212
  @rtype: int
213
  @return: the desired exit code
214

215
  """
216
  op = opcodes.OpRedistributeConfig()
217
  SubmitOrSend(op, opts)
218
  return 0
219

    
220

    
221
def ShowClusterVersion(opts, args):
222
  """Write version of ganeti software to the standard output.
223

224
  @param opts: the command line options selected by the user
225
  @type args: list
226
  @param args: should be an empty list
227
  @rtype: int
228
  @return: the desired exit code
229

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

    
240

    
241
def ShowClusterMaster(opts, args):
242
  """Write name of master node to the standard output.
243

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

250
  """
251
  master = bootstrap.GetMaster()
252
  ToStdout(master)
253
  return 0
254

    
255

    
256
def _PrintGroupedParams(paramsdict, level=1, roman=False):
257
  """Print Grouped parameters (be, nic, disk) by group.
258

259
  @type paramsdict: dict of dicts
260
  @param paramsdict: {group: {param: value, ...}, ...}
261
  @type level: int
262
  @param level: Level of indention
263

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

    
275

    
276
def ShowClusterConfig(opts, args):
277
  """Shows cluster information.
278

279
  @param opts: the command line options selected by the user
280
  @type args: list
281
  @param args: should be an empty list
282
  @rtype: int
283
  @return: the desired exit code
284

285
  """
286
  cl = GetClient()
287
  result = cl.QueryClusterInfo()
288

    
289
  ToStdout("Cluster name: %s", result["name"])
290
  ToStdout("Cluster UUID: %s", result["uuid"])
291

    
292
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
293
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
294

    
295
  ToStdout("Master node: %s", result["master"])
296

    
297
  ToStdout("Architecture (this node): %s (%s)",
298
           result["architecture"][0], result["architecture"][1])
299

    
300
  if result["tags"]:
301
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
302
  else:
303
    tags = "(none)"
304

    
305
  ToStdout("Tags: %s", tags)
306

    
307
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
308
  ToStdout("Enabled hypervisors: %s",
309
           utils.CommaJoin(result["enabled_hypervisors"]))
310

    
311
  ToStdout("Hypervisor parameters:")
312
  _PrintGroupedParams(result["hvparams"])
313

    
314
  ToStdout("OS-specific hypervisor parameters:")
315
  _PrintGroupedParams(result["os_hvp"])
316

    
317
  ToStdout("OS parameters:")
318
  _PrintGroupedParams(result["osparams"])
319

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

    
342
  ToStdout("Default instance parameters:")
343
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
344

    
345
  ToStdout("Default nic parameters:")
346
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
347

    
348
  return 0
349

    
350

    
351
def ClusterCopyFile(opts, args):
352
  """Copy a file from master to some nodes.
353

354
  @param opts: the command line options selected by the user
355
  @type args: list
356
  @param args: should contain only one element, the path of
357
      the file to be copied
358
  @rtype: int
359
  @return: the desired exit code
360

361
  """
362
  filename = args[0]
363
  if not os.path.exists(filename):
364
    raise errors.OpPrereqError("No such filename '%s'" % filename,
365
                               errors.ECODE_INVAL)
366

    
367
  cl = GetClient()
368

    
369
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
370

    
371
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
372
                           secondary_ips=opts.use_replication_network)
373

    
374
  srun = ssh.SshRunner(cluster_name=cluster_name)
375
  for node in results:
376
    if not srun.CopyFileToNode(node, filename):
377
      ToStderr("Copy of file %s to node %s failed", filename, node)
378

    
379
  return 0
380

    
381

    
382
def RunClusterCommand(opts, args):
383
  """Run a command on some nodes.
384

385
  @param opts: the command line options selected by the user
386
  @type args: list
387
  @param args: should contain the command to be run and its arguments
388
  @rtype: int
389
  @return: the desired exit code
390

391
  """
392
  cl = GetClient()
393

    
394
  command = " ".join(args)
395

    
396
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
397

    
398
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
399
                                                    "master_node"])
400

    
401
  srun = ssh.SshRunner(cluster_name=cluster_name)
402

    
403
  # Make sure master node is at list end
404
  if master_node in nodes:
405
    nodes.remove(master_node)
406
    nodes.append(master_node)
407

    
408
  for name in nodes:
409
    result = srun.Run(name, "root", command)
410
    ToStdout("------------------------------------------------")
411
    ToStdout("node: %s", name)
412
    ToStdout("%s", result.output)
413
    ToStdout("return code = %s", result.exit_code)
414

    
415
  return 0
416

    
417

    
418
def VerifyCluster(opts, args):
419
  """Verify integrity of cluster, performing various test on nodes.
420

421
  @param opts: the command line options selected by the user
422
  @type args: list
423
  @param args: should be an empty list
424
  @rtype: int
425
  @return: the desired exit code
426

427
  """
428
  skip_checks = []
429
  if opts.skip_nplusone_mem:
430
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
431
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
432
                               verbose=opts.verbose,
433
                               error_codes=opts.error_codes,
434
                               debug_simulate_errors=opts.simulate_errors)
435
  if SubmitOpCode(op, opts=opts):
436
    return 0
437
  else:
438
    return 1
439

    
440

    
441
def VerifyDisks(opts, args):
442
  """Verify integrity of cluster disks.
443

444
  @param opts: the command line options selected by the user
445
  @type args: list
446
  @param args: should be an empty list
447
  @rtype: int
448
  @return: the desired exit code
449

450
  """
451
  cl = GetClient()
452

    
453
  op = opcodes.OpVerifyDisks()
454
  result = SubmitOpCode(op, opts=opts, cl=cl)
455
  if not isinstance(result, (list, tuple)) or len(result) != 3:
456
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
457

    
458
  bad_nodes, instances, missing = result
459

    
460
  retcode = constants.EXIT_SUCCESS
461

    
462
  if bad_nodes:
463
    for node, text in bad_nodes.items():
464
      ToStdout("Error gathering data on node %s: %s",
465
               node, utils.SafeEncode(text[-400:]))
466
      retcode |= 1
467
      ToStdout("You need to fix these nodes first before fixing instances")
468

    
469
  if instances:
470
    for iname in instances:
471
      if iname in missing:
472
        continue
473
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
474
      try:
475
        ToStdout("Activating disks for instance '%s'", iname)
476
        SubmitOpCode(op, opts=opts, cl=cl)
477
      except errors.GenericError, err:
478
        nret, msg = FormatError(err)
479
        retcode |= nret
480
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
481

    
482
  if missing:
483
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])
484

    
485
    for iname, ival in missing.iteritems():
486
      all_missing = compat.all(x[0] in bad_nodes for x in ival)
487
      if all_missing:
488
        ToStdout("Instance %s cannot be verified as it lives on"
489
                 " broken nodes", iname)
490
      else:
491
        ToStdout("Instance %s has missing logical volumes:", iname)
492
        ival.sort()
493
        for node, vol in ival:
494
          if node in bad_nodes:
495
            ToStdout("\tbroken node %s /dev/%s/%s", node, vg_name, vol)
496
          else:
497
            ToStdout("\t%s /dev/%s/%s", node, vg_name, vol)
498

    
499
    ToStdout("You need to run replace_disks for all the above"
500
             " instances, if this message persist after fixing nodes.")
501
    retcode |= 1
502

    
503
  return retcode
504

    
505

    
506
def RepairDiskSizes(opts, args):
507
  """Verify sizes of cluster disks.
508

509
  @param opts: the command line options selected by the user
510
  @type args: list
511
  @param args: optional list of instances to restrict check to
512
  @rtype: int
513
  @return: the desired exit code
514

515
  """
516
  op = opcodes.OpRepairDiskSizes(instances=args)
517
  SubmitOpCode(op, opts=opts)
518

    
519

    
520
@UsesRPC
521
def MasterFailover(opts, args):
522
  """Failover the master node.
523

524
  This command, when run on a non-master node, will cause the current
525
  master to cease being master, and the non-master to become new
526
  master.
527

528
  @param opts: the command line options selected by the user
529
  @type args: list
530
  @param args: should be an empty list
531
  @rtype: int
532
  @return: the desired exit code
533

534
  """
535
  if opts.no_voting:
536
    usertext = ("This will perform the failover even if most other nodes"
537
                " are down, or if this node is outdated. This is dangerous"
538
                " as it can lead to a non-consistent cluster. Check the"
539
                " gnt-cluster(8) man page before proceeding. Continue?")
540
    if not AskUser(usertext):
541
      return 1
542

    
543
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
544

    
545

    
546
def MasterPing(opts, args):
547
  """Checks if the master is alive.
548

549
  @param opts: the command line options selected by the user
550
  @type args: list
551
  @param args: should be an empty list
552
  @rtype: int
553
  @return: the desired exit code
554

555
  """
556
  try:
557
    cl = GetClient()
558
    cl.QueryClusterInfo()
559
    return 0
560
  except Exception: # pylint: disable-msg=W0703
561
    return 1
562

    
563

    
564
def SearchTags(opts, args):
565
  """Searches the tags on all the cluster.
566

567
  @param opts: the command line options selected by the user
568
  @type args: list
569
  @param args: should contain only one element, the tag pattern
570
  @rtype: int
571
  @return: the desired exit code
572

573
  """
574
  op = opcodes.OpSearchTags(pattern=args[0])
575
  result = SubmitOpCode(op, opts=opts)
576
  if not result:
577
    return 1
578
  result = list(result)
579
  result.sort()
580
  for path, tag in result:
581
    ToStdout("%s %s", path, tag)
582

    
583

    
584
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
585
                 new_confd_hmac_key, new_cds, cds_filename,
586
                 force):
587
  """Renews cluster certificates, keys and secrets.
588

589
  @type new_cluster_cert: bool
590
  @param new_cluster_cert: Whether to generate a new cluster certificate
591
  @type new_rapi_cert: bool
592
  @param new_rapi_cert: Whether to generate a new RAPI certificate
593
  @type rapi_cert_filename: string
594
  @param rapi_cert_filename: Path to file containing new RAPI certificate
595
  @type new_confd_hmac_key: bool
596
  @param new_confd_hmac_key: Whether to generate a new HMAC key
597
  @type new_cds: bool
598
  @param new_cds: Whether to generate a new cluster domain secret
599
  @type cds_filename: string
600
  @param cds_filename: Path to file containing new cluster domain secret
601
  @type force: bool
602
  @param force: Whether to ask user for confirmation
603

604
  """
605
  if new_rapi_cert and rapi_cert_filename:
606
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
607
             " options can be specified at the same time.")
608
    return 1
609

    
610
  if new_cds and cds_filename:
611
    ToStderr("Only one of the --new-cluster-domain-secret and"
612
             " --cluster-domain-secret options can be specified at"
613
             " the same time.")
614
    return 1
615

    
616
  if rapi_cert_filename:
617
    # Read and verify new certificate
618
    try:
619
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
620

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

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

    
635
  else:
636
    rapi_cert_pem = None
637

    
638
  if cds_filename:
639
    try:
640
      cds = utils.ReadFile(cds_filename)
641
    except Exception, err: # pylint: disable-msg=W0703
642
      ToStderr("Can't load new cluster domain secret from %s: %s" %
643
               (cds_filename, str(err)))
644
      return 1
645
  else:
646
    cds = None
647

    
648
  if not force:
649
    usertext = ("This requires all daemons on all nodes to be restarted and"
650
                " may take some time. Continue?")
651
    if not AskUser(usertext):
652
      return 1
653

    
654
  def _RenewCryptoInner(ctx):
655
    ctx.feedback_fn("Updating certificates and keys")
656
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
657
                                    new_confd_hmac_key,
658
                                    new_cds,
659
                                    rapi_cert_pem=rapi_cert_pem,
660
                                    cds=cds)
661

    
662
    files_to_copy = []
663

    
664
    if new_cluster_cert:
665
      files_to_copy.append(constants.NODED_CERT_FILE)
666

    
667
    if new_rapi_cert or rapi_cert_pem:
668
      files_to_copy.append(constants.RAPI_CERT_FILE)
669

    
670
    if new_confd_hmac_key:
671
      files_to_copy.append(constants.CONFD_HMAC_KEY)
672

    
673
    if new_cds or cds:
674
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
675

    
676
    if files_to_copy:
677
      for node_name in ctx.nonmaster_nodes:
678
        ctx.feedback_fn("Copying %s to %s" %
679
                        (", ".join(files_to_copy), node_name))
680
        for file_name in files_to_copy:
681
          ctx.ssh.CopyFileToNode(node_name, file_name)
682

    
683
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
684

    
685
  ToStdout("All requested certificates and keys have been replaced."
686
           " Running \"gnt-cluster verify\" now is recommended.")
687

    
688
  return 0
689

    
690

    
691
def RenewCrypto(opts, args):
692
  """Renews cluster certificates, keys and secrets.
693

694
  """
695
  return _RenewCrypto(opts.new_cluster_cert,
696
                      opts.new_rapi_cert,
697
                      opts.rapi_cert,
698
                      opts.new_confd_hmac_key,
699
                      opts.new_cluster_domain_secret,
700
                      opts.cluster_domain_secret,
701
                      opts.force)
702

    
703

    
704
def SetClusterParams(opts, args):
705
  """Modify the cluster.
706

707
  @param opts: the command line options selected by the user
708
  @type args: list
709
  @param args: should be an empty list
710
  @rtype: int
711
  @return: the desired exit code
712

713
  """
714
  if not (not opts.lvm_storage or opts.vg_name or
715
          not opts.drbd_storage or opts.drbd_helper or
716
          opts.enabled_hypervisors or opts.hvparams or
717
          opts.beparams or opts.nicparams or opts.ndparams or
718
          opts.candidate_pool_size is not None or
719
          opts.uid_pool is not None or
720
          opts.maintain_node_health is not None or
721
          opts.add_uids is not None or
722
          opts.remove_uids is not None or
723
          opts.default_iallocator is not None or
724
          opts.reserved_lvs is not None or
725
          opts.prealloc_wipe_disks is not None):
726
    ToStderr("Please give at least one of the parameters.")
727
    return 1
728

    
729
  vg_name = opts.vg_name
730
  if not opts.lvm_storage and opts.vg_name:
731
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
732
    return 1
733

    
734
  if not opts.lvm_storage:
735
    vg_name = ""
736

    
737
  drbd_helper = opts.drbd_helper
738
  if not opts.drbd_storage and opts.drbd_helper:
739
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
740
    return 1
741

    
742
  if not opts.drbd_storage:
743
    drbd_helper = ""
744

    
745
  hvlist = opts.enabled_hypervisors
746
  if hvlist is not None:
747
    hvlist = hvlist.split(",")
748

    
749
  # a list of (name, dict) we can pass directly to dict() (or [])
750
  hvparams = dict(opts.hvparams)
751
  for hv_params in hvparams.values():
752
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
753

    
754
  beparams = opts.beparams
755
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
756

    
757
  nicparams = opts.nicparams
758
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
759

    
760
  ndparams = opts.ndparams
761
  if ndparams is not None:
762
    utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
763

    
764
  mnh = opts.maintain_node_health
765

    
766
  uid_pool = opts.uid_pool
767
  if uid_pool is not None:
768
    uid_pool = uidpool.ParseUidPool(uid_pool)
769

    
770
  add_uids = opts.add_uids
771
  if add_uids is not None:
772
    add_uids = uidpool.ParseUidPool(add_uids)
773

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

    
778
  if opts.reserved_lvs is not None:
779
    if opts.reserved_lvs == "":
780
      opts.reserved_lvs = []
781
    else:
782
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
783

    
784
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
785
                                  drbd_helper=drbd_helper,
786
                                  enabled_hypervisors=hvlist,
787
                                  hvparams=hvparams,
788
                                  os_hvp=None,
789
                                  beparams=beparams,
790
                                  nicparams=nicparams,
791
                                  ndparams=ndparams,
792
                                  candidate_pool_size=opts.candidate_pool_size,
793
                                  maintain_node_health=mnh,
794
                                  uid_pool=uid_pool,
795
                                  add_uids=add_uids,
796
                                  remove_uids=remove_uids,
797
                                  default_iallocator=opts.default_iallocator,
798
                                  prealloc_wipe_disks=opts.prealloc_wipe_disks,
799
                                  reserved_lvs=opts.reserved_lvs)
800
  SubmitOpCode(op, opts=opts)
801
  return 0
802

    
803

    
804
def QueueOps(opts, args):
805
  """Queue operations.
806

807
  @param opts: the command line options selected by the user
808
  @type args: list
809
  @param args: should contain only one element, the subcommand
810
  @rtype: int
811
  @return: the desired exit code
812

813
  """
814
  command = args[0]
815
  client = GetClient()
816
  if command in ("drain", "undrain"):
817
    drain_flag = command == "drain"
818
    client.SetQueueDrainFlag(drain_flag)
819
  elif command == "info":
820
    result = client.QueryConfigValues(["drain_flag"])
821
    if result[0]:
822
      val = "set"
823
    else:
824
      val = "unset"
825
    ToStdout("The drain flag is %s" % val)
826
  else:
827
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
828
                               errors.ECODE_INVAL)
829

    
830
  return 0
831

    
832

    
833
def _ShowWatcherPause(until):
834
  if until is None or until < time.time():
835
    ToStdout("The watcher is not paused.")
836
  else:
837
    ToStdout("The watcher is paused until %s.", time.ctime(until))
838

    
839

    
840
def WatcherOps(opts, args):
841
  """Watcher operations.
842

843
  @param opts: the command line options selected by the user
844
  @type args: list
845
  @param args: should contain only one element, the subcommand
846
  @rtype: int
847
  @return: the desired exit code
848

849
  """
850
  command = args[0]
851
  client = GetClient()
852

    
853
  if command == "continue":
854
    client.SetWatcherPause(None)
855
    ToStdout("The watcher is no longer paused.")
856

    
857
  elif command == "pause":
858
    if len(args) < 2:
859
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
860

    
861
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
862
    _ShowWatcherPause(result)
863

    
864
  elif command == "info":
865
    result = client.QueryConfigValues(["watcher_pause"])
866
    _ShowWatcherPause(result[0])
867

    
868
  else:
869
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
870
                               errors.ECODE_INVAL)
871

    
872
  return 0
873

    
874

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

    
972

    
973
#: dictionary with aliases for commands
974
aliases = {
975
  'masterfailover': 'master-failover',
976
}
977

    
978

    
979
def Main():
980
  return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
981
                     aliases=aliases)