Statistics
| Branch: | Tag: | Revision:

root / scripts / gnt-cluster @ f1b083ce

History | View | Annotate | Download (29.2 kB)

1
#!/usr/bin/python
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 sys
30
import os.path
31
import time
32
import OpenSSL
33

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

    
45

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

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

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

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

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

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

    
74
  hvlist = opts.enabled_hypervisors
75
  if hvlist is None:
76
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
77
  hvlist = hvlist.split(",")
78

    
79
  hvparams = dict(opts.hvparams)
80
  beparams = opts.beparams
81
  nicparams = opts.nicparams
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 hvparams dict
92
  for hv in constants.HYPER_TYPES:
93
    if hv not in hvparams:
94
      hvparams[hv] = {}
95
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
96
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
97

    
98
  if opts.candidate_pool_size is None:
99
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT
100

    
101
  if opts.mac_prefix is None:
102
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX
103

    
104
  uid_pool = opts.uid_pool
105
  if uid_pool is not None:
106
    uid_pool = uidpool.ParseUidPool(uid_pool)
107

    
108
  bootstrap.InitCluster(cluster_name=args[0],
109
                        secondary_ip=opts.secondary_ip,
110
                        vg_name=vg_name,
111
                        mac_prefix=opts.mac_prefix,
112
                        master_netdev=opts.master_netdev,
113
                        file_storage_dir=opts.file_storage_dir,
114
                        enabled_hypervisors=hvlist,
115
                        hvparams=hvparams,
116
                        beparams=beparams,
117
                        nicparams=nicparams,
118
                        candidate_pool_size=opts.candidate_pool_size,
119
                        modify_etc_hosts=opts.modify_etc_hosts,
120
                        modify_ssh_setup=opts.modify_ssh_setup,
121
                        maintain_node_health=opts.maintain_node_health,
122
                        drbd_helper=drbd_helper,
123
                        uid_pool=uid_pool,
124
                        default_iallocator=opts.default_iallocator,
125
                        )
126
  op = opcodes.OpPostInitCluster()
127
  SubmitOpCode(op, opts=opts)
128
  return 0
129

    
130

    
131
@UsesRPC
132
def DestroyCluster(opts, args):
133
  """Destroy the cluster.
134

    
135
  @param opts: the command line options selected by the user
136
  @type args: list
137
  @param args: should be an empty list
138
  @rtype: int
139
  @return: the desired exit code
140

    
141
  """
142
  if not opts.yes_do_it:
143
    ToStderr("Destroying a cluster is irreversible. If you really want"
144
             " destroy this cluster, supply the --yes-do-it option.")
145
    return 1
146

    
147
  op = opcodes.OpDestroyCluster()
148
  master = SubmitOpCode(op, opts=opts)
149
  # if we reached this, the opcode didn't fail; we can proceed to
150
  # shutdown all the daemons
151
  bootstrap.FinalizeClusterDestroy(master)
152
  return 0
153

    
154

    
155
def RenameCluster(opts, args):
156
  """Rename the cluster.
157

    
158
  @param opts: the command line options selected by the user
159
  @type args: list
160
  @param args: should contain only one element, the new cluster name
161
  @rtype: int
162
  @return: the desired exit code
163

    
164
  """
165
  cl = GetClient()
166

    
167
  (cluster_name, ) = cl.QueryConfigValues(["cluster_name"])
168

    
169
  new_name = args[0]
170
  if not opts.force:
171
    usertext = ("This will rename the cluster from '%s' to '%s'. If you are"
172
                " connected over the network to the cluster name, the"
173
                " operation is very dangerous as the IP address will be"
174
                " removed from the node and the change may not go through."
175
                " Continue?") % (cluster_name, new_name)
176
    if not AskUser(usertext):
177
      return 1
178

    
179
  op = opcodes.OpRenameCluster(name=new_name)
180
  result = SubmitOpCode(op, opts=opts, cl=cl)
181

    
182
  if result:
183
    ToStdout("Cluster renamed from '%s' to '%s'", cluster_name, result)
184

    
185
  return 0
186

    
187

    
188
def RedistributeConfig(opts, args):
189
  """Forces push of the cluster configuration.
190

    
191
  @param opts: the command line options selected by the user
192
  @type args: list
193
  @param args: empty list
194
  @rtype: int
195
  @return: the desired exit code
196

    
197
  """
198
  op = opcodes.OpRedistributeConfig()
199
  SubmitOrSend(op, opts)
200
  return 0
201

    
202

    
203
def ShowClusterVersion(opts, args):
204
  """Write version of ganeti software to the standard output.
205

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

    
212
  """
213
  cl = GetClient()
214
  result = cl.QueryClusterInfo()
215
  ToStdout("Software version: %s", result["software_version"])
216
  ToStdout("Internode protocol: %s", result["protocol_version"])
217
  ToStdout("Configuration format: %s", result["config_version"])
218
  ToStdout("OS api version: %s", result["os_api_version"])
219
  ToStdout("Export interface: %s", result["export_version"])
220
  return 0
221

    
222

    
223
def ShowClusterMaster(opts, args):
224
  """Write name of master node to the standard output.
225

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

    
232
  """
233
  master = bootstrap.GetMaster()
234
  ToStdout(master)
235
  return 0
236

    
237

    
238
def _PrintGroupedParams(paramsdict, level=1, roman=False):
239
  """Print Grouped parameters (be, nic, disk) by group.
240

    
241
  @type paramsdict: dict of dicts
242
  @param paramsdict: {group: {param: value, ...}, ...}
243
  @type level: int
244
  @param level: Level of indention
245

    
246
  """
247
  indent = "  " * level
248
  for item, val in sorted(paramsdict.items()):
249
    if isinstance(val, dict):
250
      ToStdout("%s- %s:", indent, item)
251
      _PrintGroupedParams(val, level=level + 1, roman=roman)
252
    elif roman and isinstance(val, int):
253
      ToStdout("%s  %s: %s", indent, item, compat.TryToRoman(val))
254
    else:
255
      ToStdout("%s  %s: %s", indent, item, val)
256

    
257

    
258
def ShowClusterConfig(opts, args):
259
  """Shows cluster information.
260

    
261
  @param opts: the command line options selected by the user
262
  @type args: list
263
  @param args: should be an empty list
264
  @rtype: int
265
  @return: the desired exit code
266

    
267
  """
268
  cl = GetClient()
269
  result = cl.QueryClusterInfo()
270

    
271
  ToStdout("Cluster name: %s", result["name"])
272
  ToStdout("Cluster UUID: %s", result["uuid"])
273

    
274
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
275
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))
276

    
277
  ToStdout("Master node: %s", result["master"])
278

    
279
  ToStdout("Architecture (this node): %s (%s)",
280
           result["architecture"][0], result["architecture"][1])
281

    
282
  if result["tags"]:
283
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
284
  else:
285
    tags = "(none)"
286

    
287
  ToStdout("Tags: %s", tags)
288

    
289
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
290
  ToStdout("Enabled hypervisors: %s",
291
           utils.CommaJoin(result["enabled_hypervisors"]))
292

    
293
  ToStdout("Hypervisor parameters:")
294
  _PrintGroupedParams(result["hvparams"])
295

    
296
  ToStdout("OS-specific hypervisor parameters:")
297
  _PrintGroupedParams(result["os_hvp"])
298

    
299
  ToStdout("OS parameters:")
300
  _PrintGroupedParams(result["osparams"])
301

    
302
  ToStdout("Cluster parameters:")
303
  ToStdout("  - candidate pool size: %s",
304
            compat.TryToRoman(result["candidate_pool_size"],
305
                              convert=opts.roman_integers))
306
  ToStdout("  - master netdev: %s", result["master_netdev"])
307
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
308
  if result["reserved_lvs"]:
309
    reserved_lvs = utils.CommaJoin(result["reserved_lvs"])
310
  else:
311
    reserved_lvs = "(none)"
312
  ToStdout("  - lvm reserved volumes: %s", reserved_lvs)
313
  ToStdout("  - drbd usermode helper: %s", result["drbd_usermode_helper"])
314
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
315
  ToStdout("  - maintenance of node health: %s",
316
           result["maintain_node_health"])
317
  ToStdout("  - uid pool: %s",
318
            uidpool.FormatUidPool(result["uid_pool"],
319
                                  roman=opts.roman_integers))
320
  ToStdout("  - default instance allocator: %s", result["default_iallocator"])
321

    
322
  ToStdout("Default instance parameters:")
323
  _PrintGroupedParams(result["beparams"], roman=opts.roman_integers)
324

    
325
  ToStdout("Default nic parameters:")
326
  _PrintGroupedParams(result["nicparams"], roman=opts.roman_integers)
327

    
328
  return 0
329

    
330

    
331
def ClusterCopyFile(opts, args):
332
  """Copy a file from master to some nodes.
333

    
334
  @param opts: the command line options selected by the user
335
  @type args: list
336
  @param args: should contain only one element, the path of
337
      the file to be copied
338
  @rtype: int
339
  @return: the desired exit code
340

    
341
  """
342
  filename = args[0]
343
  if not os.path.exists(filename):
344
    raise errors.OpPrereqError("No such filename '%s'" % filename,
345
                               errors.ECODE_INVAL)
346

    
347
  cl = GetClient()
348

    
349
  cluster_name = cl.QueryConfigValues(["cluster_name"])[0]
350

    
351
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
352
                           secondary_ips=opts.use_replication_network)
353

    
354
  srun = ssh.SshRunner(cluster_name=cluster_name)
355
  for node in results:
356
    if not srun.CopyFileToNode(node, filename):
357
      ToStderr("Copy of file %s to node %s failed", filename, node)
358

    
359
  return 0
360

    
361

    
362
def RunClusterCommand(opts, args):
363
  """Run a command on some nodes.
364

    
365
  @param opts: the command line options selected by the user
366
  @type args: list
367
  @param args: should contain the command to be run and its arguments
368
  @rtype: int
369
  @return: the desired exit code
370

    
371
  """
372
  cl = GetClient()
373

    
374
  command = " ".join(args)
375

    
376
  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
377

    
378
  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
379
                                                    "master_node"])
380

    
381
  srun = ssh.SshRunner(cluster_name=cluster_name)
382

    
383
  # Make sure master node is at list end
384
  if master_node in nodes:
385
    nodes.remove(master_node)
386
    nodes.append(master_node)
387

    
388
  for name in nodes:
389
    result = srun.Run(name, "root", command)
390
    ToStdout("------------------------------------------------")
391
    ToStdout("node: %s", name)
392
    ToStdout("%s", result.output)
393
    ToStdout("return code = %s", result.exit_code)
394

    
395
  return 0
396

    
397

    
398
def VerifyCluster(opts, args):
399
  """Verify integrity of cluster, performing various test on nodes.
400

    
401
  @param opts: the command line options selected by the user
402
  @type args: list
403
  @param args: should be an empty list
404
  @rtype: int
405
  @return: the desired exit code
406

    
407
  """
408
  skip_checks = []
409
  if opts.skip_nplusone_mem:
410
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
411
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
412
                               verbose=opts.verbose,
413
                               error_codes=opts.error_codes,
414
                               debug_simulate_errors=opts.simulate_errors)
415
  if SubmitOpCode(op, opts=opts):
416
    return 0
417
  else:
418
    return 1
419

    
420

    
421
def VerifyDisks(opts, args):
422
  """Verify integrity of cluster disks.
423

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

    
430
  """
431
  cl = GetClient()
432

    
433
  op = opcodes.OpVerifyDisks()
434
  result = SubmitOpCode(op, opts=opts, cl=cl)
435
  if not isinstance(result, (list, tuple)) or len(result) != 3:
436
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")
437

    
438
  bad_nodes, instances, missing = result
439

    
440
  retcode = constants.EXIT_SUCCESS
441

    
442
  if bad_nodes:
443
    for node, text in bad_nodes.items():
444
      ToStdout("Error gathering data on node %s: %s",
445
               node, utils.SafeEncode(text[-400:]))
446
      retcode |= 1
447
      ToStdout("You need to fix these nodes first before fixing instances")
448

    
449
  if instances:
450
    for iname in instances:
451
      if iname in missing:
452
        continue
453
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
454
      try:
455
        ToStdout("Activating disks for instance '%s'", iname)
456
        SubmitOpCode(op, opts=opts, cl=cl)
457
      except errors.GenericError, err:
458
        nret, msg = FormatError(err)
459
        retcode |= nret
460
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
461

    
462
  if missing:
463
    (vg_name, ) = cl.QueryConfigValues(["volume_group_name"])
464

    
465
    for iname, ival in missing.iteritems():
466
      all_missing = compat.all(x[0] in bad_nodes for x in ival)
467
      if all_missing:
468
        ToStdout("Instance %s cannot be verified as it lives on"
469
                 " broken nodes", iname)
470
      else:
471
        ToStdout("Instance %s has missing logical volumes:", iname)
472
        ival.sort()
473
        for node, vol in ival:
474
          if node in bad_nodes:
475
            ToStdout("\tbroken node %s /dev/%s/%s", node, vg_name, vol)
476
          else:
477
            ToStdout("\t%s /dev/%s/%s", node, vg_name, vol)
478

    
479
    ToStdout("You need to run replace_disks for all the above"
480
             " instances, if this message persist after fixing nodes.")
481
    retcode |= 1
482

    
483
  return retcode
484

    
485

    
486
def RepairDiskSizes(opts, args):
487
  """Verify sizes of cluster disks.
488

    
489
  @param opts: the command line options selected by the user
490
  @type args: list
491
  @param args: optional list of instances to restrict check to
492
  @rtype: int
493
  @return: the desired exit code
494

    
495
  """
496
  op = opcodes.OpRepairDiskSizes(instances=args)
497
  SubmitOpCode(op, opts=opts)
498

    
499

    
500
@UsesRPC
501
def MasterFailover(opts, args):
502
  """Failover the master node.
503

    
504
  This command, when run on a non-master node, will cause the current
505
  master to cease being master, and the non-master to become new
506
  master.
507

    
508
  @param opts: the command line options selected by the user
509
  @type args: list
510
  @param args: should be an empty list
511
  @rtype: int
512
  @return: the desired exit code
513

    
514
  """
515
  if opts.no_voting:
516
    usertext = ("This will perform the failover even if most other nodes"
517
                " are down, or if this node is outdated. This is dangerous"
518
                " as it can lead to a non-consistent cluster. Check the"
519
                " gnt-cluster(8) man page before proceeding. Continue?")
520
    if not AskUser(usertext):
521
      return 1
522

    
523
  return bootstrap.MasterFailover(no_voting=opts.no_voting)
524

    
525

    
526
def MasterPing(opts, args):
527
  """Checks if the master is alive.
528

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

    
535
  """
536
  try:
537
    cl = GetClient()
538
    cl.QueryClusterInfo()
539
    return 0
540
  except Exception: # pylint: disable-msg=W0703
541
    return 1
542

    
543

    
544
def SearchTags(opts, args):
545
  """Searches the tags on all the cluster.
546

    
547
  @param opts: the command line options selected by the user
548
  @type args: list
549
  @param args: should contain only one element, the tag pattern
550
  @rtype: int
551
  @return: the desired exit code
552

    
553
  """
554
  op = opcodes.OpSearchTags(pattern=args[0])
555
  result = SubmitOpCode(op, opts=opts)
556
  if not result:
557
    return 1
558
  result = list(result)
559
  result.sort()
560
  for path, tag in result:
561
    ToStdout("%s %s", path, tag)
562

    
563

    
564
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
565
                 new_confd_hmac_key, new_cds, cds_filename,
566
                 force):
567
  """Renews cluster certificates, keys and secrets.
568

    
569
  @type new_cluster_cert: bool
570
  @param new_cluster_cert: Whether to generate a new cluster certificate
571
  @type new_rapi_cert: bool
572
  @param new_rapi_cert: Whether to generate a new RAPI certificate
573
  @type rapi_cert_filename: string
574
  @param rapi_cert_filename: Path to file containing new RAPI certificate
575
  @type new_confd_hmac_key: bool
576
  @param new_confd_hmac_key: Whether to generate a new HMAC key
577
  @type new_cds: bool
578
  @param new_cds: Whether to generate a new cluster domain secret
579
  @type cds_filename: string
580
  @param cds_filename: Path to file containing new cluster domain secret
581
  @type force: bool
582
  @param force: Whether to ask user for confirmation
583

    
584
  """
585
  if new_rapi_cert and rapi_cert_filename:
586
    ToStderr("Only one of the --new-rapi-certficate and --rapi-certificate"
587
             " options can be specified at the same time.")
588
    return 1
589

    
590
  if new_cds and cds_filename:
591
    ToStderr("Only one of the --new-cluster-domain-secret and"
592
             " --cluster-domain-secret options can be specified at"
593
             " the same time.")
594
    return 1
595

    
596
  if rapi_cert_filename:
597
    # Read and verify new certificate
598
    try:
599
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)
600

    
601
      OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
602
                                      rapi_cert_pem)
603
    except Exception, err: # pylint: disable-msg=W0703
604
      ToStderr("Can't load new RAPI certificate from %s: %s" %
605
               (rapi_cert_filename, str(err)))
606
      return 1
607

    
608
    try:
609
      OpenSSL.crypto.load_privatekey(OpenSSL.crypto.FILETYPE_PEM, rapi_cert_pem)
610
    except Exception, err: # pylint: disable-msg=W0703
611
      ToStderr("Can't load new RAPI private key from %s: %s" %
612
               (rapi_cert_filename, str(err)))
613
      return 1
614

    
615
  else:
616
    rapi_cert_pem = None
617

    
618
  if cds_filename:
619
    try:
620
      cds = utils.ReadFile(cds_filename)
621
    except Exception, err: # pylint: disable-msg=W0703
622
      ToStderr("Can't load new cluster domain secret from %s: %s" %
623
               (cds_filename, str(err)))
624
      return 1
625
  else:
626
    cds = None
627

    
628
  if not force:
629
    usertext = ("This requires all daemons on all nodes to be restarted and"
630
                " may take some time. Continue?")
631
    if not AskUser(usertext):
632
      return 1
633

    
634
  def _RenewCryptoInner(ctx):
635
    ctx.feedback_fn("Updating certificates and keys")
636
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
637
                                    new_confd_hmac_key,
638
                                    new_cds,
639
                                    rapi_cert_pem=rapi_cert_pem,
640
                                    cds=cds)
641

    
642
    files_to_copy = []
643

    
644
    if new_cluster_cert:
645
      files_to_copy.append(constants.NODED_CERT_FILE)
646

    
647
    if new_rapi_cert or rapi_cert_pem:
648
      files_to_copy.append(constants.RAPI_CERT_FILE)
649

    
650
    if new_confd_hmac_key:
651
      files_to_copy.append(constants.CONFD_HMAC_KEY)
652

    
653
    if new_cds or cds:
654
      files_to_copy.append(constants.CLUSTER_DOMAIN_SECRET_FILE)
655

    
656
    if files_to_copy:
657
      for node_name in ctx.nonmaster_nodes:
658
        ctx.feedback_fn("Copying %s to %s" %
659
                        (", ".join(files_to_copy), node_name))
660
        for file_name in files_to_copy:
661
          ctx.ssh.CopyFileToNode(node_name, file_name)
662

    
663
  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)
664

    
665
  ToStdout("All requested certificates and keys have been replaced."
666
           " Running \"gnt-cluster verify\" now is recommended.")
667

    
668
  return 0
669

    
670

    
671
def RenewCrypto(opts, args):
672
  """Renews cluster certificates, keys and secrets.
673

    
674
  """
675
  return _RenewCrypto(opts.new_cluster_cert,
676
                      opts.new_rapi_cert,
677
                      opts.rapi_cert,
678
                      opts.new_confd_hmac_key,
679
                      opts.new_cluster_domain_secret,
680
                      opts.cluster_domain_secret,
681
                      opts.force)
682

    
683

    
684
def SetClusterParams(opts, args):
685
  """Modify the cluster.
686

    
687
  @param opts: the command line options selected by the user
688
  @type args: list
689
  @param args: should be an empty list
690
  @rtype: int
691
  @return: the desired exit code
692

    
693
  """
694
  if not (not opts.lvm_storage or opts.vg_name or
695
          not opts.drbd_storage or opts.drbd_helper or
696
          opts.enabled_hypervisors or opts.hvparams or
697
          opts.beparams or opts.nicparams or
698
          opts.candidate_pool_size is not None or
699
          opts.uid_pool is not None or
700
          opts.maintain_node_health is not None or
701
          opts.add_uids is not None or
702
          opts.remove_uids is not None or
703
          opts.default_iallocator is not None or
704
          opts.reserved_lvs is not None):
705
    ToStderr("Please give at least one of the parameters.")
706
    return 1
707

    
708
  vg_name = opts.vg_name
709
  if not opts.lvm_storage and opts.vg_name:
710
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
711
    return 1
712

    
713
  if not opts.lvm_storage:
714
    vg_name = ""
715

    
716
  drbd_helper = opts.drbd_helper
717
  if not opts.drbd_storage and opts.drbd_helper:
718
    ToStderr("Options --no-drbd-storage and --drbd-usermode-helper conflict.")
719
    return 1
720

    
721
  if not opts.drbd_storage:
722
    drbd_helper = ""
723

    
724
  hvlist = opts.enabled_hypervisors
725
  if hvlist is not None:
726
    hvlist = hvlist.split(",")
727

    
728
  # a list of (name, dict) we can pass directly to dict() (or [])
729
  hvparams = dict(opts.hvparams)
730
  for hv_params in hvparams.values():
731
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
732

    
733
  beparams = opts.beparams
734
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
735

    
736
  nicparams = opts.nicparams
737
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)
738

    
739

    
740
  mnh = opts.maintain_node_health
741

    
742
  uid_pool = opts.uid_pool
743
  if uid_pool is not None:
744
    uid_pool = uidpool.ParseUidPool(uid_pool)
745

    
746
  add_uids = opts.add_uids
747
  if add_uids is not None:
748
    add_uids = uidpool.ParseUidPool(add_uids)
749

    
750
  remove_uids = opts.remove_uids
751
  if remove_uids is not None:
752
    remove_uids = uidpool.ParseUidPool(remove_uids)
753

    
754
  if opts.reserved_lvs is not None:
755
    if opts.reserved_lvs == "":
756
      opts.reserved_lvs = []
757
    else:
758
      opts.reserved_lvs = utils.UnescapeAndSplit(opts.reserved_lvs, sep=",")
759

    
760
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
761
                                  drbd_helper=drbd_helper,
762
                                  enabled_hypervisors=hvlist,
763
                                  hvparams=hvparams,
764
                                  os_hvp=None,
765
                                  beparams=beparams,
766
                                  nicparams=nicparams,
767
                                  candidate_pool_size=opts.candidate_pool_size,
768
                                  maintain_node_health=mnh,
769
                                  uid_pool=uid_pool,
770
                                  add_uids=add_uids,
771
                                  remove_uids=remove_uids,
772
                                  default_iallocator=opts.default_iallocator,
773
                                  reserved_lvs=opts.reserved_lvs)
774
  SubmitOpCode(op, opts=opts)
775
  return 0
776

    
777

    
778
def QueueOps(opts, args):
779
  """Queue operations.
780

    
781
  @param opts: the command line options selected by the user
782
  @type args: list
783
  @param args: should contain only one element, the subcommand
784
  @rtype: int
785
  @return: the desired exit code
786

    
787
  """
788
  command = args[0]
789
  client = GetClient()
790
  if command in ("drain", "undrain"):
791
    drain_flag = command == "drain"
792
    client.SetQueueDrainFlag(drain_flag)
793
  elif command == "info":
794
    result = client.QueryConfigValues(["drain_flag"])
795
    if result[0]:
796
      val = "set"
797
    else:
798
      val = "unset"
799
    ToStdout("The drain flag is %s" % val)
800
  else:
801
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
802
                               errors.ECODE_INVAL)
803

    
804
  return 0
805

    
806

    
807
def _ShowWatcherPause(until):
808
  if until is None or until < time.time():
809
    ToStdout("The watcher is not paused.")
810
  else:
811
    ToStdout("The watcher is paused until %s.", time.ctime(until))
812

    
813

    
814
def WatcherOps(opts, args):
815
  """Watcher 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

    
827
  if command == "continue":
828
    client.SetWatcherPause(None)
829
    ToStdout("The watcher is no longer paused.")
830

    
831
  elif command == "pause":
832
    if len(args) < 2:
833
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
834

    
835
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
836
    _ShowWatcherPause(result)
837

    
838
  elif command == "info":
839
    result = client.QueryConfigValues(["watcher_pause"])
840
    _ShowWatcherPause(result[0])
841

    
842
  else:
843
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
844
                               errors.ECODE_INVAL)
845

    
846
  return 0
847

    
848

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

    
945

    
946
#: dictionary with aliases for commands
947
aliases = {
948
  'masterfailover': 'master-failover',
949
}
950

    
951

    
952
if __name__ == '__main__':
953
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER},
954
                       aliases=aliases))