gnt-cluster 24.4 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
280

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

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

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


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

292
293
294
295
296
297
  @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
298
299

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

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

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

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

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

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


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

323
324
325
326
327
  @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
328
329

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

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

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

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

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

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

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

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


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

359
360
361
362
363
  @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
364
365

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


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

382
383
384
385
386
  @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
387
388
389

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

394
  bad_nodes, instances, missing = result
395

396
  retcode = constants.EXIT_SUCCESS
397

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

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

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

  return retcode


439
440
441
442
443
444
445
446
447
448
449
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)
450
  SubmitOpCode(op, opts=opts)
451
452


453
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
454
455
456
457
458
459
460
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.

461
462
463
464
465
466
  @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
467
  """
468
469
470
471
472
473
474
475
476
  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
477
478


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

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


499
def _RenewCrypto(new_cluster_cert, new_rapi_cert, rapi_cert_filename,
500
                 new_confd_hmac_key, force):
501
502
503
504
505
506
507
508
  """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
509
510
  @type new_confd_hmac_key: bool
  @param new_confd_hmac_key: Whether to generate a new HMAC key
511
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
  @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,
551
                                    new_confd_hmac_key,
552
553
554
555
556
                                    rapi_cert_pem=rapi_cert_pem)

    files_to_copy = []

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

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

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

    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,
587
                      opts.new_confd_hmac_key,
588
589
590
                      opts.force)


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

594
595
596
597
598
  @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
599
600

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

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

  if not opts.lvm_storage:
    vg_name = ""
617

618
619
620
621
  hvlist = opts.enabled_hypervisors
  if hvlist is not None:
    hvlist = hvlist.split(",")

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

  beparams = opts.beparams
628
  utils.ForceDictType(beparams, constants.BES_PARAMETER_TYPES)
629

630
631
632
  nicparams = opts.nicparams
  utils.ForceDictType(nicparams, constants.NICS_PARAMETER_TYPES)

633

634
635
  mnh = opts.maintain_node_health

636
637
638
639
  uid_pool = opts.uid_pool
  if uid_pool is not None:
    uid_pool = uidpool.ParseUidPool(uid_pool)

640
  op = opcodes.OpSetClusterParams(vg_name=vg_name,
641
642
                                  enabled_hypervisors=hvlist,
                                  hvparams=hvparams,
643
                                  os_hvp=None,
644
                                  beparams=beparams,
645
                                  nicparams=nicparams,
646
                                  candidate_pool_size=opts.candidate_pool_size,
647
648
                                  maintain_node_health=mnh,
                                  uid_pool=uid_pool)
649
  SubmitOpCode(op, opts=opts)
650
651
652
  return 0


653
654
655
def QueueOps(opts, args):
  """Queue operations.

656
657
658
659
660
661
  @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

662
663
664
665
666
667
668
669
670
  """
  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]:
671
      val = "set"
672
    else:
673
674
      val = "unset"
    ToStdout("The drain flag is %s" % val)
675
  else:
676
677
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
678

679
680
  return 0

681

682
683
684
685
686
687
688
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))


689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
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)
704
    ToStdout("The watcher is no longer paused.")
705
706
707

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

710
711
    result = client.SetWatcherPause(time.time() + ParseTimespec(args[1]))
    _ShowWatcherPause(result)
712
713
714

  elif command == "info":
    result = client.QueryConfigValues(["watcher_pause"])
715
    _ShowWatcherPause(result[0])
716
717

  else:
718
719
    raise errors.OpPrereqError("Command '%s' is not valid." % command,
                               errors.ECODE_INVAL)
720
721
722
723

  return 0


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

811

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