Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_cluster.py @ f0b1bafe

History | View | Annotate | Download (30.9 kB)

1
#
2
#
3

    
4
# Copyright (C) 2006, 2007, 2010, 2011 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21
"""Cluster related commands"""
22

    
23
# pylint: disable-msg=W0401,W0613,W0614,C0103
24
# W0401: Wildcard import ganeti.cli
25
# W0613: Unused argument, since all functions follow the same API
26
# W0614: Unused import %s from wildcard import (since we need cli)
27
# C0103: Invalid name gnt-cluster
28

    
29
import os.path
30
import time
31
import OpenSSL
32

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

    
44

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
152

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

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

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

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

    
176

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

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

186
  """
187
  cl = GetClient()
188

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

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

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

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

    
207
  return 0
208

    
209

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

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

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

    
224

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

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

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

    
244

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

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

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

    
259

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

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

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

    
279

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
358
  return 0
359

    
360

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

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

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

    
377
  cl = GetClient()
378

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

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

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

    
389
  return 0
390

    
391

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

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

401
  """
402
  cl = GetClient()
403

    
404
  command = " ".join(args)
405

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

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

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

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

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

    
425
  return 0
426

    
427

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

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

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

    
450

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

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

460
  """
461
  cl = GetClient()
462

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

    
468
  bad_nodes, instances, missing = result
469

    
470
  retcode = constants.EXIT_SUCCESS
471

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

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

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

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

    
511
  return retcode
512

    
513

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

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

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

    
527

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

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

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

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

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

    
553

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

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

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

    
571

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

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

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

    
591

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

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

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

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

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

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

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

    
643
  else:
644
    rapi_cert_pem = None
645

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

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

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

    
670
    files_to_copy = []
671

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

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

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

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

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

    
691
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
692

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

    
696
  return 0
697

    
698

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

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

    
711

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

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

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

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

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

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

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

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

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

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

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

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

    
773
  mnh = opts.maintain_node_health
774

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

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

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

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

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

    
813

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

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

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

    
840
  return 0
841

    
842

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

    
849

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

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

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

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

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

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

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

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

    
882
  return 0
883

    
884

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

    
982

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

    
988

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