Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_cluster.py @ cb1ef973

History | View | Annotate | Download (30.4 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
  ndparams = opts.ndparams
82

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

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

    
91
  # prepare ndparams dict
92
  ndparams = objects.FillDict(constants.NDC_DEFAULTS, ndparams)
93
  utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
94

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

    
102
  if opts.candidate_pool_size is None:
103
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
104

    
105
  if opts.mac_prefix is None:
106
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
107

    
108
  uid_pool = opts.uid_pool
109
  if uid_pool is not None:
110
    uid_pool = uidpool.ParseUidPool(uid_pool)
111

    
112
  if opts.prealloc_wipe_disks is None:
113
    opts.prealloc_wipe_disks = False
114

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

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

    
146

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

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

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

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

    
170

    
171
def RenameCluster(opts, args):
172
  """Rename the cluster.
173

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

180
  """
181
  cl = GetClient()
182

    
183
  (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
184

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

    
195
  op = opcodes.OpRenameCluster(name=new_name)
196
  result = SubmitOpCode(op, opts=opts, cl=cl)
197

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

    
201
  return 0
202

    
203

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

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

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

    
218

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

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

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

    
238

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

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

248
  """
249
  master = bootstrap.GetMaster()
250
  ToStdout(master)
251
  return 0
252

    
253

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

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

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

    
273

    
274
def ShowClusterConfig(opts, args):
275
  """Shows cluster information.
276

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

283
  """
284
  cl = GetClient()
285
  result = cl.QueryClusterInfo()
286

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

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

    
293
  ToStdout("Master node: %s", result["master"])
294

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

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

    
303
  ToStdout("Tags: %s", tags)
304

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

    
309
  ToStdout("Hypervisor parameters:")
310
  _PrintGroupedParams(result["hvparams"])
311

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

    
315
  ToStdout("OS parameters:")
316
  _PrintGroupedParams(result["osparams"])
317

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

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

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

    
346
  return 0
347

    
348

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

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

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

    
365
  cl = GetClient()
366

    
367
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
368

    
369
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
370
                           secondary_ips=opts.use_replication_network)
371

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

    
377
  return 0
378

    
379

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

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

389
  """
390
  cl = GetClient()
391

    
392
  command = " ".join(args)
393

    
394
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
395

    
396
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
397
                                                    "master_node"])
398

    
399
  srun = ssh.SshRunner(cluster_name=cluster_name)
400

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

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

    
413
  return 0
414

    
415

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

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

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

    
438

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

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

448
  """
449
  cl = GetClient()
450

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

    
456
  bad_nodes, instances, missing = result
457

    
458
  retcode = constants.EXIT_SUCCESS
459

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

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

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

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

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

    
501
  return retcode
502

    
503

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

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

513
  """
514
  op = opcodes.OpRepairDiskSizes(instances=args)
515
  SubmitOpCode(op, opts=opts)
516

    
517

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

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

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

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

    
541
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
542

    
543

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

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

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

    
561

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

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

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

    
581

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

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

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

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

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

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

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

    
633
  else:
634
    rapi_cert_pem = None
635

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

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

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

    
660
    files_to_copy = []
661

    
662
    if new_cluster_cert:
663
      files_to_copy.append(constants.NODED_CERT_FILE)
664

    
665
    if new_rapi_cert or rapi_cert_pem:
666
      files_to_copy.append(constants.RAPI_CERT_FILE)
667

    
668
    if new_confd_hmac_key:
669
      files_to_copy.append(constants.CONFD_HMAC_KEY)
670

    
671
    if new_cds or cds:
672
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
673

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

    
681
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
682

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

    
686
  return 0
687

    
688

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

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

    
701

    
702
def SetClusterParams(opts, args):
703
  """Modify the cluster.
704

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

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

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

    
732
  if not opts.lvm_storage:
733
    vg_name = ""
734

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

    
740
  if not opts.drbd_storage:
741
    drbd_helper = ""
742

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

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

    
752
  beparams = opts.beparams
753
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
754

    
755
  nicparams = opts.nicparams
756
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
757

    
758
  ndparams = opts.ndparams
759
  utils.ForceDictType(ndparams, constants.NDS_PARAMETER_TYPES)
760

    
761
  mnh = opts.maintain_node_health
762

    
763
  uid_pool = opts.uid_pool
764
  if uid_pool is not None:
765
    uid_pool = uidpool.ParseUidPool(uid_pool)
766

    
767
  add_uids = opts.add_uids
768
  if add_uids is not None:
769
    add_uids = uidpool.ParseUidPool(add_uids)
770

    
771
  remove_uids = opts.remove_uids
772
  if remove_uids is not None:
773
    remove_uids = uidpool.ParseUidPool(remove_uids)
774

    
775
  if opts.reserved_lvs is not None:
776
    if opts.reserved_lvs == "":
777
      opts.reserved_lvs = []
778
    else:
779
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
780

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

    
800

    
801
def QueueOps(opts, args):
802
  """Queue operations.
803

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

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

    
827
  return 0
828

    
829

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

    
836

    
837
def WatcherOps(opts, args):
838
  """Watcher operations.
839

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

846
  """
847
  command = args[0]
848
  client = GetClient()
849

    
850
  if command == "continue":
851
    client.SetWatcherPause(None)
852
    ToStdout("The watcher is no longer paused.")
853

    
854
  elif command == "pause":
855
    if len(args) < 2:
856
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
857

    
858
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
859
    _ShowWatcherPause(result)
860

    
861
  elif command == "info":
862
    result = client.QueryConfigValues(["watcher_pause"])
863
    _ShowWatcherPause(result[0])
864

    
865
  else:
866
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
867
                               errors.ECODE_INVAL)
868

    
869
  return 0
870

    
871

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

    
969

    
970
#: dictionary with aliases for commands
971
aliases = {
972
  'masterfailover': 'master-failover',
973
}
974

    
975

    
976
def Main():
977
  return GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
978
                     aliases=aliases)