gnt-cluster 25.1 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
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

99
100
101
102
103
  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,
104
105
106
                        file_storage_dir=opts.file_storage_dir,
                        enabled_hypervisors=hvlist,
                        hvparams=hvparams,
107
                        beparams=beparams,
108
                        nicparams=nicparams,
109
                        candidate_pool_size=opts.candidate_pool_size,
110
                        modify_etc_hosts=opts.modify_etc_hosts,
111
                        modify_ssh_setup=opts.modify_ssh_setup,
112
                        maintain_node_health=opts.maintain_node_health,
113
                        uid_pool=uid_pool,
114
                        )
115
  op = opcodes.OpPostInitCluster()
116
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
117
118
119
  return 0


120
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
121
122
123
def DestroyCluster(opts, args):
  """Destroy the cluster.

124
125
126
127
128
  @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
129

Iustin Pop's avatar
Iustin Pop committed
130
131
  """
  if not opts.yes_do_it:
132
133
    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
134
135
136
    return 1

  op = opcodes.OpDestroyCluster()
137
  master = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
138
139
140
  # 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
141
142
143
  return 0


144
145
146
def RenameCluster(opts, args):
  """Rename the cluster.

147
148
149
150
151
  @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
152
153
154
155
156
157
158
159

  """
  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
160
    if not AskUser(usertext):
161
162
163
      return 1

  op = opcodes.OpRenameCluster(name=name)
164
  SubmitOpCode(op, opts=opts)
165
166
167
  return 0


168
169
170
171
172
173
174
175
176
177
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

  """
178
  op = opcodes.OpRedistributeConfig()
179
180
181
182
  SubmitOrSend(op, opts)
  return 0


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

186
187
188
189
190
  @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
191
192

  """
193
194
  cl = GetClient()
  result = cl.QueryClusterInfo()
195
196
197
198
199
  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
200
201
202
203
204
205
  return 0


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

206
207
208
209
210
  @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
211
212

  """
213
214
  master = bootstrap.GetMaster()
  ToStdout(master)
Iustin Pop's avatar
Iustin Pop committed
215
216
  return 0

217

218
def _PrintGroupedParams(paramsdict, level=1):
219
220
221
222
  """Print Grouped parameters (be, nic, disk) by group.

  @type paramsdict: dict of dicts
  @param paramsdict: {group: {param: value, ...}, ...}
223
224
  @type level: int
  @param level: Level of indention
225
226

  """
227
  indent = "  " * level
228
  for item, val in sorted(paramsdict.items()):
229
230
231
232
233
    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
234

235

Iustin Pop's avatar
Iustin Pop committed
236
237
238
def ShowClusterConfig(opts, args):
  """Shows cluster information.

239
240
241
242
243
244
  @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
245
  """
246
247
  cl = GetClient()
  result = cl.QueryClusterInfo()
Iustin Pop's avatar
Iustin Pop committed
248

249
  ToStdout("Cluster name: %s", result["name"])
250
  ToStdout("Cluster UUID: %s", result["uuid"])
Iustin Pop's avatar
Iustin Pop committed
251

252
253
254
  ToStdout("Creation time: %s", utils.FormatTime(result["ctime"]))
  ToStdout("Modification time: %s", utils.FormatTime(result["mtime"]))

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

257
258
  ToStdout("Architecture (this node): %s (%s)",
           result["architecture"][0], result["architecture"][1])
Iustin Pop's avatar
Iustin Pop committed
259

260
  if result["tags"]:
261
    tags = utils.CommaJoin(utils.NiceSort(result["tags"]))
262
263
264
265
266
  else:
    tags = "(none)"

  ToStdout("Tags: %s", tags)

267
  ToStdout("Default hypervisor: %s", result["default_hypervisor"])
268
269
  ToStdout("Enabled hypervisors: %s",
           utils.CommaJoin(result["enabled_hypervisors"]))
270

271
  ToStdout("Hypervisor parameters:")
272
  _PrintGroupedParams(result["hvparams"])
273

274
275
276
  ToStdout("OS specific hypervisor parameters:")
  _PrintGroupedParams(result["os_hvp"])

277
  ToStdout("Cluster parameters:")
278
  ToStdout("  - candidate pool size: %s", result["candidate_pool_size"])
279
280
281
  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"])
282
283
  ToStdout("  - maintenance of node health: %s",
           result["maintain_node_health"])
284
  ToStdout("  - uid pool: %s", uidpool.FormatUidPool(result["uid_pool"]))
285
286

  ToStdout("Default instance parameters:")
287
288
289
290
  _PrintGroupedParams(result["beparams"])

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

Iustin Pop's avatar
Iustin Pop committed
292
293
294
295
296
297
  return 0


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

298
299
300
301
302
303
  @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
304
305

  """
306
307
  filename = args[0]
  if not os.path.exists(filename):
308
309
    raise errors.OpPrereqError("No such filename '%s'" % filename,
                               errors.ECODE_INVAL)
310

Iustin Pop's avatar
Iustin Pop committed
311
312
313
314
  cl = GetClient()

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

315
316
  results = GetOnlineNodes(nodes=opts.nodes, cl=cl, filter_master=True,
                           secondary_ips=opts.use_replication_network)
Michael Hanselmann's avatar
Michael Hanselmann committed
317

Iustin Pop's avatar
Iustin Pop committed
318
  srun = ssh.SshRunner(cluster_name=cluster_name)
319
320
  for node in results:
    if not srun.CopyFileToNode(node, filename):
321
      ToStderr("Copy of file %s to node %s failed", filename, node)
322

Iustin Pop's avatar
Iustin Pop committed
323
324
325
326
327
328
  return 0


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

329
330
331
332
333
  @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
334
335

  """
Iustin Pop's avatar
Iustin Pop committed
336
  cl = GetClient()
Michael Hanselmann's avatar
Michael Hanselmann committed
337

Iustin Pop's avatar
Iustin Pop committed
338
  command = " ".join(args)
339
340

  nodes = GetOnlineNodes(nodes=opts.nodes, cl=cl)
Iustin Pop's avatar
Iustin Pop committed
341
342
343

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

Iustin Pop's avatar
Iustin Pop committed
345
  srun = ssh.SshRunner(cluster_name=cluster_name)
346

Michael Hanselmann's avatar
Michael Hanselmann committed
347
  # Make sure master node is at list end
348
349
350
351
352
353
  if master_node in nodes:
    nodes.remove(master_node)
    nodes.append(master_node)

  for name in nodes:
    result = srun.Run(name, "root", command)
354
355
356
357
    ToStdout("------------------------------------------------")
    ToStdout("node: %s", name)
    ToStdout("%s", result.output)
    ToStdout("return code = %s", result.exit_code)
358
359

  return 0
Iustin Pop's avatar
Iustin Pop committed
360
361
362
363
364


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

365
366
367
368
369
  @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
370
371

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


385
386
387
def VerifyDisks(opts, args):
  """Verify integrity of cluster disks.

388
389
390
391
392
  @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
393
394
395

  """
  op = opcodes.OpVerifyDisks()
396
  result = SubmitOpCode(op, opts=opts)
397
  if not isinstance(result, (list, tuple)) or len(result) != 3:
398
399
    raise errors.ProgrammerError("Unknown result type for OpVerifyDisks")

400
  bad_nodes, instances, missing = result
401

402
  retcode = constants.EXIT_SUCCESS
403

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

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

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

  return retcode


445
446
447
448
449
450
451
452
453
454
455
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)
456
  SubmitOpCode(op, opts=opts)
457
458


459
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
460
461
462
463
464
465
466
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.

467
468
469
470
471
472
  @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
473
  """
474
475
476
477
478
479
480
481
482
  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
483
484


Iustin Pop's avatar
Iustin Pop committed
485
486
487
def SearchTags(opts, args):
  """Searches the tags on all the cluster.

488
489
490
491
492
493
  @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
494
495
  """
  op = opcodes.OpSearchTags(pattern=args[0])
496
  result = SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
497
498
499
500
501
  if not result:
    return 1
  result = list(result)
  result.sort()
  for path, tag in result:
502
    ToStdout("%s %s", path, tag)
Iustin Pop's avatar
Iustin Pop committed
503
504


505
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
506
                 new_confd_hmac_key, force):
507
508
509
510
511
512
513
514
  """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
515
516
  @type new_confd_hmac_key: bool
  @param new_confd_hmac_key: Whether to generate a new HMAC key
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
552
553
554
555
556
  @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,
557
                                    new_confd_hmac_key,
558
559
560
561
562
                                    rapi_cert_pem=rapi_cert_pem)

    files_to_copy = []

    if new_cluster_cert:
563
      files_to_copy.append(constants.NODED_CERT_FILE)
564
565
566
567

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

568
569
    if new_confd_hmac_key:
      files_to_copy.append(constants.CONFD_HMAC_KEY)
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592

    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,
593
                      opts.new_confd_hmac_key,
594
595
596
                      opts.force)


597
598
599
def SetClusterParams(opts, args):
  """Modify the cluster.

600
601
602
603
604
  @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
605
606

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

  vg_name = opts.vg_name
  if not opts.lvm_storage and opts.vg_name:
620
    ToStderr("Options --no-lvm-storage and --vg-name conflict.")
621
    return 1
622
623
624

  if not opts.lvm_storage:
    vg_name = ""
625

626
627
628
629
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

630
631
  # a list of (name, dict) we can pass directly to dict() (or [])
  hvparams = dict(opts.hvparams)
Iustin Pop's avatar
Iustin Pop committed
632
  for hv_params in hvparams.values():
633
    utils.ForceDictType(hv_params, constants.HVS_PARAMETER_TYPES)
634
635

  beparams = opts.beparams
636
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
637

638
639
640
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

641

642
643
  mnh = opts.maintain_node_health

644
645
646
647
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

648
649
650
651
652
653
654
655
  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)

656
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
657
658
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
659
                                  os_hvp=None,
660
                                  beparams=beparams,
661
                                  nicparams=nicparams,
662
                                  candidate_pool_size=opts.candidate_pool_size,
663
                                  maintain_node_health=mnh,
664
665
666
                                  uid_pool=uid_pool,
                                  add_uids=add_uids,
                                  remove_uids=remove_uids)
667
  SubmitOpCode(op, opts=opts)
668
669
670
  return 0


671
672
673
def QueueOps(opts, args):
  """Queue operations.

674
675
676
677
678
679
  @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

680
681
682
683
684
685
686
687
688
  """
  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]:
689
      val = "set"
690
    else:
691
692
      val = "unset"
    ToStdout("The drain flag is %s" % val)
693
  else:
694
695
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
696

697
698
  return 0

699

700
701
702
703
704
705
706
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))


707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
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)
722
    ToStdout("The watcher is no longer paused.")
723
724
725

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

728
729
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
730
731
732

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
733
    _ShowWatcherPause(result[0])
734
735

  else:
736
737
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
738
739
740
741

  return 0


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

830

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