Statistics
| Branch: | Tag: | Revision:

root / lib / client / gnt_cluster.py @ f36c3e2d

History | View | Annotate | Download (31 kB)

1
#
2
#
3

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

    
21
"""Cluster related commands"""
22

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

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

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

    
44

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
152

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

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

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

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

    
176

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

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

186
  """
187
  cl = GetClient()
188

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

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

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

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

    
207
  return 0
208

    
209

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

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

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

    
224

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

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

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

    
244

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

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

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

    
259

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

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

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

    
279

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
359
  return 0
360

    
361

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

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

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

    
378
  cl = GetClient()
379

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

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

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

    
390
  return 0
391

    
392

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

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

402
  """
403
  cl = GetClient()
404

    
405
  command = " ".join(args)
406

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

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

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

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

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

    
426
  return 0
427

    
428

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

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

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

    
451

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

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

461
  """
462
  cl = GetClient()
463

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

    
469
  bad_nodes, instances, missing = result
470

    
471
  retcode = constants.EXIT_SUCCESS
472

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

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

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

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

    
512
  return retcode
513

    
514

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

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

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

    
528

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

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

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

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

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

    
554

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

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

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

    
572

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

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

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

    
592

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

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

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

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

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

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

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

    
644
  else:
645
    rapi_cert_pem = None
646

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

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

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

    
671
    files_to_copy = []
672

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

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

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

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

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

    
692
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
693

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

    
697
  return 0
698

    
699

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

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

    
712

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

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

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

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

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

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

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

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

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

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

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

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

    
774
  mnh = opts.maintain_node_health
775

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

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

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

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

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

    
814

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

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

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

    
841
  return 0
842

    
843

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

    
850

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

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

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

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

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

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

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

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

    
883
  return 0
884

    
885

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

    
983

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

    
989

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