gnt-cluster 24.9 KB
Newer Older
Iustin Pop's avatar
Iustin Pop committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/usr/bin/python
#

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

Iustin Pop's avatar
Iustin Pop committed
21
"""Cluster related commands"""
Iustin Pop's avatar
Iustin Pop committed
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)
Iustin Pop's avatar
Iustin Pop committed
27
# C0103: Invalid name gnt-cluster
28

Iustin Pop's avatar
Iustin Pop committed
29
import sys
30
import os.path
31
import time
32
import OpenSSL
Iustin Pop's avatar
Iustin Pop committed
33
34
35

from ganeti.cli import *
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
Iustin Pop's avatar
Iustin Pop committed
43
44


45
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
46
47
48
def InitCluster(opts, args):
  """Initialize the cluster.

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

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

  vg_name = opts.vg_name
  if opts.lvm_storage and not opts.vg_name:
    vg_name = constants.DEFAULT_VG

65
  hvlist = opts.enabled_hypervisors
66
67
  if hvlist is None:
    hvlist = constants.DEFAULT_ENABLED_HYPERVISOR
68
  hvlist = hvlist.split(",")
69

70
  hvparams = dict(opts.hvparams)
71
  beparams = opts.beparams
72
  nicparams = opts.nicparams
73
74

  # prepare beparams dict
75
  beparams = objects.FillDict(constants.BEC_DEFAULTS, beparams)
76
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
77

78
79
80
81
  # prepare nicparams dict
  nicparams = objects.FillDict(constants.NICC_DEFAULTS, nicparams)
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

82
83
84
85
  # prepare hvparams dict
  for hv in constants.HYPER_TYPES:
    if hv not in hvparams:
      hvparams[hv] = {}
86
    hvparams[hv] = objects.FillDict(constants.HVC_DEFAULTS[hv], hvparams[hv])
87
    utils.ForceDictType(hvparams[hv], constants.HVS_PARAMETER_TYPES)
88

89
90
91
  if opts.candidate_pool_size is None:
    opts.candidate_pool_size = constants.MASTER_POOL_SIZE_DEFAULT

92
93
94
  if opts.mac_prefix is None:
    opts.mac_prefix = constants.DEFAULT_MAC_PREFIX

95
96
97
98
99
  bootstrap.InitCluster(cluster_name=args[0],
                        secondary_ip=opts.secondary_ip,
                        vg_name=vg_name,
                        mac_prefix=opts.mac_prefix,
                        master_netdev=opts.master_netdev,
100
101
102
                        file_storage_dir=opts.file_storage_dir,
                        enabled_hypervisors=hvlist,
                        hvparams=hvparams,
103
                        beparams=beparams,
104
                        nicparams=nicparams,
105
                        candidate_pool_size=opts.candidate_pool_size,
106
                        modify_etc_hosts=opts.modify_etc_hosts,
107
                        modify_ssh_setup=opts.modify_ssh_setup,
108
                        maintain_node_health=opts.maintain_node_health,
109
                        )
110
  op = opcodes.OpPostInitCluster()
111
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
112
113
114
  return 0


115
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
116
117
118
def DestroyCluster(opts, args):
  """Destroy the cluster.

119
120
121
122
123
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
124

Iustin Pop's avatar
Iustin Pop committed
125
126
  """
  if not opts.yes_do_it:
127
128
    ToStderr("Destroying a cluster is irreversible. If you really want"
             " destroy this cluster, supply the --yes-do-it option.")
Iustin Pop's avatar
Iustin Pop committed
129
130
131
    return 1

  op = opcodes.OpDestroyCluster()
132
  master = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
133
134
135
  # if we reached this, the opcode didn't fail; we can proceed to
  # shutdown all the daemons
  bootstrap.FinalizeClusterDestroy(master)
Iustin Pop's avatar
Iustin Pop committed
136
137
138
  return 0


139
140
141
def RenameCluster(opts, args):
  """Rename the cluster.

142
143
144
145
146
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the new cluster name
  @rtype: int
  @return: the desired exit code
147
148
149
150
151
152
153
154

  """
  name = args[0]
  if not opts.force:
    usertext = ("This will rename the cluster to '%s'. If you are connected"
                " over the network to the cluster name, the operation is very"
                " dangerous as the IP address will be removed from the node"
                " and the change may not go through. Continue?") % name
155
    if not AskUser(usertext):
156
157
158
      return 1

  op = opcodes.OpRenameCluster(name=name)
159
  SubmitOpCode(op, opts=opts)
160
161
162
  return 0


163
164
165
166
167
168
169
170
171
172
def RedistributeConfig(opts, args):
  """Forces push of the cluster configuration.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: empty list
  @rtype: int
  @return: the desired exit code

  """
173
  op = opcodes.OpRedistributeConfig()
174
175
176
177
  SubmitOrSend(op, opts)
  return 0


Iustin Pop's avatar
Iustin Pop committed
178
179
180
def ShowClusterVersion(opts, args):
  """Write version of ganeti software to the standard output.

181
182
183
184
185
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
186
187

  """
188
189
  cl = GetClient()
  result = cl.QueryClusterInfo()
190
191
192
193
194
  ToStdout("Software version: %s", result["software_version"])
  ToStdout("Internode protocol: %s", result["protocol_version"])
  ToStdout("Configuration format: %s", result["config_version"])
  ToStdout("OS api version: %s", result["os_api_version"])
  ToStdout("Export interface: %s", result["export_version"])
Iustin Pop's avatar
Iustin Pop committed
195
196
197
198
199
200
  return 0


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

201
202
203
204
205
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
206
207

  """
208
209
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
210
211
  return 0

212

213
def _PrintGroupedParams(paramsdict, level=1):
214
215
216
217
  """Print Grouped parameters (be, nic, disk) by group.

  @type paramsdict: dict of dicts
  @param paramsdict: {group: {param: value, ...}, ...}
218
219
  @type level: int
  @param level: Level of indention
220
221

  """
222
223
224
225
226
227
228
  indent = "  " * level
  for item, val in paramsdict.items():
    if isinstance(val, dict):
      ToStdout("%s- %s:", indent, item)
      _PrintGroupedParams(val, level=level + 1)
    else:
      ToStdout("%s  %s: %s", indent, item, val)
Iustin Pop's avatar
Iustin Pop committed
229

230

Iustin Pop's avatar
Iustin Pop committed
231
232
233
def ShowClusterConfig(opts, args):
  """Shows cluster information.

234
235
236
237
238
239
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
240
  """
241
242
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
243

244
  ToStdout("Cluster name: %s", result["name"])
245
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
246

247
248
249
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

250
  ToStdout("Master node: %s", result["master"])
Iustin Pop's avatar
Iustin Pop committed
251

252
253
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
254

255
  if result["tags"]:
256
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
257
258
259
260
261
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

262
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
263
264
  ToStdout("Enabled hypervisors: %s",
           utils.CommaJoin(result["enabled_hypervisors"]))
265

266
  ToStdout("Hypervisor parameters:")
267
  _PrintGroupedParams(result["hvparams"])
268

269
270
271
  ToStdout("OS specific hypervisor parameters:")
  _PrintGroupedParams(result["os_hvp"])

272
  ToStdout("Cluster parameters:")
273
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
274
275
276
  ToStdout("  - master netdev: %s", result["master_netdev"])
  ToStdout("  - lvm volume group: %s", result["volume_group_name"])
  ToStdout("  - file storage path: %s", result["file_storage_dir"])
277
278
  ToStdout("  - maintenance of node health: %s",
           result["maintain_node_health"])
279
  ToStdout("  - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"]))
280
281

  ToStdout("Default instance parameters:")
282
283
284
285
  _PrintGroupedParams(result["beparams"])

  ToStdout("Default nic parameters:")
  _PrintGroupedParams(result["nicparams"])
286

Iustin Pop's avatar
Iustin Pop committed
287
288
289
290
291
292
  return 0


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

293
294
295
296
297
298
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the path of
      the file to be copied
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
299
300

  """
301
302
  filename = args[0]
  if not os.path.exists(filename):
303
304
    raise errors.OpPrereqError("No such filename '%s'" % filename,
                               errors.ECODE_INVAL)
305

Iustin Pop's avatar
Iustin Pop committed
306
307
308
309
  cl = GetClient()

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

310
311
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
312

Iustin Pop's avatar
Iustin Pop committed
313
  srun = ssh.SshRunner(cluster_name=cluster_name)
314
315
  for node in results:
    if not srun.CopyFileToNode(node, filename):
316
      ToStderr("Copy of file %s to node %s failed", filename, node)
317

Iustin Pop's avatar
Iustin Pop committed
318
319
320
321
322
323
  return 0


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

324
325
326
327
328
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain the command to be run and its arguments
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
329
330

  """
Iustin Pop's avatar
Iustin Pop committed
331
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
332

Iustin Pop's avatar
Iustin Pop committed
333
  command = " ".join(args)
334
335

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
336
337
338

  cluster_name, master_node = cl.QueryConfigValues(["cluster_name",
                                                    "master_node"])
339

Iustin Pop's avatar
Iustin Pop committed
340
  srun = ssh.SshRunner(cluster_name=cluster_name)
341

Michael Hanselmann's avatar
Michael Hanselmann committed
342
  # Make sure master node is at list end
343
344
345
346
347
348
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
349
350
351
352
    ToStdout("------------------------------------------------")
    ToStdout("node: %s", name)
    ToStdout("%s", result.output)
    ToStdout("return code = %s", result.exit_code)
353
354

  return 0
Iustin Pop's avatar
Iustin Pop committed
355
356
357
358
359


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

360
361
362
363
364
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
Iustin Pop's avatar
Iustin Pop committed
365
366

  """
Iustin Pop's avatar
Iustin Pop committed
367
  skip_checks = []
368
369
  if opts.skip_nplusone_mem:
    skip_checks.append(constants.VERIFY_NPLUSONE_MEM)
370
371
  op = opcodes.OpVerifyCluster(skip_checks=skip_checks,
                               verbose=opts.verbose,
372
373
                               error_codes=opts.error_codes,
                               debug_simulate_errors=opts.simulate_errors)
374
  if SubmitOpCode(op, opts=opts):
375
376
377
    return 0
  else:
    return 1
Iustin Pop's avatar
Iustin Pop committed
378
379


380
381
382
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

383
384
385
386
387
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
388
389
390

  """
  op = opcodes.OpVerifyDisks()
391
  result = SubmitOpCode(op, opts=opts)
392
  if not isinstance(result, (list, tuple)) or len(result) != 3:
393
394
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

395
  bad_nodes, instances, missing = result
396

397
  retcode = constants.EXIT_SUCCESS
398

399
400
401
  if bad_nodes:
    for node, text in bad_nodes.items():
      ToStdout("Error gathering data on node %s: %s",
402
               node, utils.SafeEncode(text[-400:]))
403
      retcode |= 1
404
      ToStdout("You need to fix these nodes first before fixing instances")
405

406
407
  if instances:
    for iname in instances:
408
409
      if iname in missing:
        continue
410
411
      op = opcodes.OpActivateInstanceDisks(instance_name=iname)
      try:
412
        ToStdout("Activating disks for instance '%s'", iname)
413
        SubmitOpCode(op, opts=opts)
414
415
416
      except errors.GenericError, err:
        nret, msg = FormatError(err)
        retcode |= nret
417
        ToStderr("Error activating disks for instance %s: %s", iname, msg)
418
419
420

  if missing:
    for iname, ival in missing.iteritems():
421
      all_missing = utils.all(ival, lambda x: x[0] in bad_nodes)
422
      if all_missing:
423
424
        ToStdout("Instance %s cannot be verified as it lives on"
                 " broken nodes", iname)
425
      else:
426
        ToStdout("Instance %s has missing logical volumes:", iname)
427
428
        ival.sort()
        for node, vol in ival:
429
          if node in bad_nodes:
430
            ToStdout("\tbroken node %s /dev/xenvg/%s", node, vol)
431
          else:
432
433
            ToStdout("\t%s /dev/xenvg/%s", node, vol)
    ToStdout("You need to run replace_disks for all the above"
434
435
           " instances, if this message persist after fixing nodes.")
    retcode |= 1
436
437
438
439

  return retcode


440
441
442
443
444
445
446
447
448
449
450
def RepairDiskSizes(opts, args):
  """Verify sizes of cluster disks.

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

  """
  op = opcodes.OpRepairDiskSizes(instances=args)
451
  SubmitOpCode(op, opts=opts)
452
453


454
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
455
456
457
458
459
460
461
def MasterFailover(opts, args):
  """Failover the master node.

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

462
463
464
465
466
467
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
468
  """
469
470
471
472
473
474
475
476
477
  if opts.no_voting:
    usertext = ("This will perform the failover even if most other nodes"
                " are down, or if this node is outdated. This is dangerous"
                " as it can lead to a non-consistent cluster. Check the"
                " gnt-cluster(8) man page before proceeding. Continue?")
    if not AskUser(usertext):
      return 1

  return bootstrap.MasterFailover(no_voting=opts.no_voting)
Iustin Pop's avatar
Iustin Pop committed
478
479


Iustin Pop's avatar
Iustin Pop committed
480
481
482
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

483
484
485
486
487
488
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the tag pattern
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
489
490
  """
  op = opcodes.OpSearchTags(pattern=args[0])
491
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
492
493
494
495
496
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
497
    ToStdout("%s %s", path, tag)
Iustin Pop's avatar
Iustin Pop committed
498
499


500
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
501
                 new_confd_hmac_key, force):
502
503
504
505
506
507
508
509
  """Renews cluster certificates, keys and secrets.

  @type new_cluster_cert: bool
  @param new_cluster_cert: Whether to generate a new cluster certificate
  @type new_rapi_cert: bool
  @param new_rapi_cert: Whether to generate a new RAPI certificate
  @type rapi_cert_filename: string
  @param rapi_cert_filename: Path to file containing new RAPI certificate
510
511
  @type new_confd_hmac_key: bool
  @param new_confd_hmac_key: Whether to generate a new HMAC key
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
  @type force: bool
  @param force: Whether to ask user for confirmation

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

  if rapi_cert_filename:
    # Read and verify new certificate
    try:
      rapi_cert_pem = utils.ReadFile(rapi_cert_filename)

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

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

  else:
    rapi_cert_pem = None

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

  def _RenewCryptoInner(ctx):
    ctx.feedback_fn("Updating certificates and keys")
    bootstrap.GenerateClusterCrypto(new_cluster_cert, new_rapi_cert,
552
                                    new_confd_hmac_key,
553
554
555
556
557
                                    rapi_cert_pem=rapi_cert_pem)

    files_to_copy = []

    if new_cluster_cert:
558
      files_to_copy.append(constants.NODED_CERT_FILE)
559
560
561
562

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

563
564
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587

    if files_to_copy:
      for node_name in ctx.nonmaster_nodes:
        ctx.feedback_fn("Copying %s to %s" %
                        (", ".join(files_to_copy), node_name))
        for file_name in files_to_copy:
          ctx.ssh.CopyFileToNode(node_name, file_name)

  RunWhileClusterStopped(ToStdout, _RenewCryptoInner)

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

  return 0


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

  """
  return _RenewCrypto(opts.new_cluster_cert,
                      opts.new_rapi_cert,
                      opts.rapi_cert,
588
                      opts.new_confd_hmac_key,
589
590
591
                      opts.force)


592
593
594
def SetClusterParams(opts, args):
  """Modify the cluster.

595
596
597
598
599
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should be an empty list
  @rtype: int
  @return: the desired exit code
600
601

  """
602
603
  if not (not opts.lvm_storage or opts.vg_name or
          opts.enabled_hypervisors or opts.hvparams or
604
          opts.beparams or opts.nicparams or
605
          opts.candidate_pool_size is not None or
606
          opts.uid_pool is not None or
607
608
609
          opts.maintain_node_health is not None or
          opts.add_uids is not None or
          opts.remove_uids is not None):
610
    ToStderr("Please give at least one of the parameters.")
611
612
613
614
    return 1

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
615
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
616
    return 1
617
618
619

  if not opts.lvm_storage:
    vg_name = ""
620

621
622
623
624
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

625
626
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
627
  for hv_params in hvparams.values():
628
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
629
630

  beparams = opts.beparams
631
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
632

633
634
635
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

636

637
638
  mnh = opts.maintain_node_health

639
640
641
642
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

643
644
645
646
647
648
649
650
  add_uids = opts.add_uids
  if add_uids is not None:
    add_uids = uidpool.ParseUidPool(add_uids)

  remove_uids = opts.remove_uids
  if remove_uids is not None:
    remove_uids = uidpool.ParseUidPool(remove_uids)

651
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
652
653
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
654
                                  os_hvp=None,
655
                                  beparams=beparams,
656
                                  nicparams=nicparams,
657
                                  candidate_pool_size=opts.candidate_pool_size,
658
                                  maintain_node_health=mnh,
659
660
661
                                  uid_pool=uid_pool,
                                  add_uids=add_uids,
                                  remove_uids=remove_uids)
662
  SubmitOpCode(op, opts=opts)
663
664
665
  return 0


666
667
668
def QueueOps(opts, args):
  """Queue operations.

669
670
671
672
673
674
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the subcommand
  @rtype: int
  @return: the desired exit code

675
676
677
678
679
680
681
682
683
  """
  command = args[0]
  client = GetClient()
  if command in ("drain", "undrain"):
    drain_flag = command == "drain"
    client.SetQueueDrainFlag(drain_flag)
  elif command == "info":
    result = client.QueryConfigValues(["drain_flag"])
    if result[0]:
684
      val = "set"
685
    else:
686
687
      val = "unset"
    ToStdout("The drain flag is %s" % val)
688
  else:
689
690
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
691

692
693
  return 0

694

695
696
697
698
699
700
701
def _ShowWatcherPause(until):
  if until is None or until < time.time():
    ToStdout("The watcher is not paused.")
  else:
    ToStdout("The watcher is paused until %s.", time.ctime(until))


702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
def WatcherOps(opts, args):
  """Watcher operations.

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

  """
  command = args[0]
  client = GetClient()

  if command == "continue":
    client.SetWatcherPause(None)
717
    ToStdout("The watcher is no longer paused.")
718
719
720

  elif command == "pause":
    if len(args) < 2:
721
      raise errors.OpPrereqError("Missing pause duration", errors.ECODE_INVAL)
722

723
724
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
725
726
727

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
728
    _ShowWatcherPause(result[0])
729
730

  else:
731
732
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
733
734
735
736

  return 0


Iustin Pop's avatar
Iustin Pop committed
737
commands = {
738
739
  'init': (
    InitCluster, [ArgHost(min=1, max=1)],
740
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, GLOBAL_FILEDIR_OPT,
741
     HVLIST_OPT, MAC_PREFIX_OPT, MASTER_NETDEV_OPT, NIC_PARAMS_OPT,
742
     NOLVM_STORAGE_OPT, NOMODIFY_ETCHOSTS_OPT, NOMODIFY_SSH_SETUP_OPT,
743
     SECONDARY_IP_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT],
744
745
    "[opts...] <cluster_name>", "Initialises a new cluster configuration"),
  'destroy': (
746
    DestroyCluster, ARGS_NONE, [YES_DOIT_OPT],
747
748
749
    "", "Destroy cluster"),
  'rename': (
    RenameCluster, [ArgHost(min=1, max=1)],
750
    [FORCE_OPT],
751
752
753
    "<new_name>",
    "Renames the cluster"),
  'redist-conf': (
754
    RedistributeConfig, ARGS_NONE, [SUBMIT_OPT],
755
756
757
758
    "", "Forces a push of the configuration file and ssconf files"
    " to the nodes in the cluster"),
  'verify': (
    VerifyCluster, ARGS_NONE,
759
    [VERBOSE_OPT, DEBUG_SIMERR_OPT, ERROR_CODES_OPT, NONPLUS1_OPT],
760
761
    "", "Does a check on the cluster configuration"),
  'verify-disks': (
762
    VerifyDisks, ARGS_NONE, [],
763
764
    "", "Does a check on the cluster disk status"),
  'repair-disk-sizes': (
765
    RepairDiskSizes, ARGS_MANY_INSTANCES, [],
766
767
    "", "Updates mismatches in recorded disk sizes"),
  'masterfailover': (
768
    MasterFailover, ARGS_NONE, [NOVOTING_OPT],
769
770
    "", "Makes the current node the master"),
  'version': (
771
    ShowClusterVersion, ARGS_NONE, [],
772
773
    "", "Shows the cluster version"),
  'getmaster': (
774
    ShowClusterMaster, ARGS_NONE, [],
775
776
777
    "", "Shows the cluster master"),
  'copyfile': (
    ClusterCopyFile, [ArgFile(min=1, max=1)],
778
    [NODE_LIST_OPT, USE_REPL_NET_OPT],
779
780
781
    "[-n node...] <filename>", "Copies a file to all (or only some) nodes"),
  'command': (
    RunClusterCommand, [ArgCommand(min=1)],
782
    [NODE_LIST_OPT],
783
784
    "[-n node...] <command>", "Runs a command on all (or only some) nodes"),
  'info': (
785
    ShowClusterConfig, ARGS_NONE, [],
786
787
    "", "Show cluster configuration"),
  'list-tags': (
788
    ListTags, ARGS_NONE, [], "", "List the tags of the cluster"),
789
  'add-tags': (
790
    AddTags, [ArgUnknown()], [TAG_SRC_OPT],
791
792
    "tag...", "Add tags to the cluster"),
  'remove-tags': (
793
    RemoveTags, [ArgUnknown()], [TAG_SRC_OPT],
794
795
796
    "tag...", "Remove tags from the cluster"),
  'search-tags': (
    SearchTags, [ArgUnknown(min=1, max=1)],
797
    [], "", "Searches the tags on all objects on"
798
799
800
801
    " the cluster for a given pattern (regex)"),
  'queue': (
    QueueOps,
    [ArgChoice(min=1, max=1, choices=["drain", "undrain", "info"])],
802
    [], "drain|undrain|info", "Change queue properties"),
803
804
805
806
  'watcher': (
    WatcherOps,
    [ArgChoice(min=1, max=1, choices=["pause", "continue", "info"]),
     ArgSuggest(min=0, max=1, choices=["30m", "1h", "4h"])],
807
    [],
808
809
810
    "{pause <timespec>|continue|info}", "Change watcher properties"),
  'modify': (
    SetClusterParams, ARGS_NONE,
811
    [BACKEND_OPT, CP_SIZE_OPT, ENABLED_HV_OPT, HVLIST_OPT,
812
     NIC_PARAMS_OPT, NOLVM_STORAGE_OPT, VG_NAME_OPT, MAINTAIN_NODE_HEALTH_OPT,
813
     UIDPOOL_OPT, ADD_UIDS_OPT, REMOVE_UIDS_OPT],
814
815
    "[opts...]",
    "Alters the parameters of the cluster"),
816
817
  "renew-crypto": (
    RenewCrypto, ARGS_NONE,
818
819
    [NEW_CLUSTER_CERT_OPT, NEW_RAPI_CERT_OPT, RAPI_CERT_OPT,
     NEW_CONFD_HMAC_KEY_OPT, FORCE_OPT],
820
821
    "[opts...]",
    "Renews cluster certificates, keys and secrets"),
Iustin Pop's avatar
Iustin Pop committed
822
823
  }

824

Iustin Pop's avatar
Iustin Pop committed
825
if __name__ == '__main__':
826
  sys.exit(GenericMain(commands, override={"tag_type": constants.TAG_CLUSTER}))