gnt_node.py 24.1 KB
Newer Older
1
#
Iustin Pop's avatar
Iustin Pop committed
2
3
#

4
# Copyright (C) 2006, 2007, 2008, 2009, 2010 Google Inc.
Iustin Pop's avatar
Iustin Pop committed
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#
# 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
"""Node 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-node
28

Iustin Pop's avatar
Iustin Pop committed
29
from ganeti.cli import *
30
from ganeti import bootstrap
Iustin Pop's avatar
Iustin Pop committed
31
32
from ganeti import opcodes
from ganeti import utils
33
from ganeti import constants
34
from ganeti import compat
Iustin Pop's avatar
Iustin Pop committed
35
from ganeti import errors
36
from ganeti import netutils
Iustin Pop's avatar
Iustin Pop committed
37
38


39
#: default list of field for L{ListNodes}
40
41
42
43
44
45
_LIST_DEF_FIELDS = [
  "name", "dtotal", "dfree",
  "mtotal", "mnode", "mfree",
  "pinst_cnt", "sinst_cnt",
  ]

46

47
48
49
50
#: Default field list for L{ListVolumes}
_LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"]


51
52
53
54
55
56
57
58
59
60
61
62
#: default list of field for L{ListStorage}
_LIST_STOR_DEF_FIELDS = [
  constants.SF_NODE,
  constants.SF_TYPE,
  constants.SF_NAME,
  constants.SF_SIZE,
  constants.SF_USED,
  constants.SF_FREE,
  constants.SF_ALLOCATABLE,
  ]


63
64
65
66
67
68
69
70
#: headers (and full field list for L{ListNodes}
_LIST_HEADERS = {
  "name": "Node", "pinst_cnt": "Pinst", "sinst_cnt": "Sinst",
  "pinst_list": "PriInstances", "sinst_list": "SecInstances",
  "pip": "PrimaryIP", "sip": "SecondaryIP",
  "dtotal": "DTotal", "dfree": "DFree",
  "mtotal": "MTotal", "mnode": "MNode", "mfree": "MFree",
  "bootid": "BootID",
71
  "ctotal": "CTotal", "cnodes": "CNodes", "csockets": "CSockets",
72
73
74
75
  "tags": "Tags",
  "serial_no": "SerialNo",
  "master_candidate": "MasterC",
  "master": "IsMaster",
76
  "offline": "Offline", "drained": "Drained",
Iustin Pop's avatar
Iustin Pop committed
77
  "role": "Role",
78
79
  "ctime": "CTime", "mtime": "MTime", "uuid": "UUID",
  "master_capable": "MasterCapable", "vm_capable": "VMCapable",
80
81
  }

82
83
84
85
86
87
88
89
90
91
92
93
94

#: headers (and full field list for L{ListStorage}
_LIST_STOR_HEADERS = {
  constants.SF_NODE: "Node",
  constants.SF_TYPE: "Type",
  constants.SF_NAME: "Name",
  constants.SF_SIZE: "Size",
  constants.SF_USED: "Used",
  constants.SF_FREE: "Free",
  constants.SF_ALLOCATABLE: "Allocatable",
  }


95
96
97
98
99
100
101
#: User-facing storage unit types
_USER_STORAGE_TYPE = {
  constants.ST_FILE: "file",
  constants.ST_LVM_PV: "lvm-pv",
  constants.ST_LVM_VG: "lvm-vg",
  }

102
_STORAGE_TYPE_OPT = \
103
  cli_option("-t", "--storage-type",
104
105
106
107
             dest="user_storage_type",
             choices=_USER_STORAGE_TYPE.keys(),
             default=None,
             metavar="STORAGE_TYPE",
108
109
             help=("Storage type (%s)" %
                   utils.CommaJoin(_USER_STORAGE_TYPE.keys())))
110
111
112
113
114
115
116

_REPAIRABLE_STORAGE_TYPES = \
  [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems()
   if constants.SO_FIX_CONSISTENCY in so]

_MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys()

Michael Hanselmann's avatar
Michael Hanselmann committed
117

118
119
120
121
122
123
NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True,
                              action="store_false", dest="node_setup",
                              help=("Do not make initial SSH setup on remote"
                                    " node (needs to be done manually)"))


124
125
126
127
128
129
130
def ConvertStorageType(user_storage_type):
  """Converts a user storage type to its internal name.

  """
  try:
    return _USER_STORAGE_TYPE[user_storage_type]
  except KeyError:
131
132
    raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type,
                               errors.ECODE_INVAL)
133
134


135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
def _RunSetupSSH(options, nodes):
  """Wrapper around utils.RunCmd to call setup-ssh

  @param options: The command line options
  @param nodes: The nodes to setup

  """
  cmd = [constants.SETUP_SSH]

  # Pass --debug|--verbose to the external script if set on our invocation
  # --debug overrides --verbose
  if options.debug:
    cmd.append("--debug")
  elif options.verbose:
    cmd.append("--verbose")
150
  if not options.ssh_key_check:
151
    cmd.append("--no-ssh-key-check")
152
153
154
155
156
157
158
159
160
161
162

  cmd.extend(nodes)

  result = utils.RunCmd(cmd, interactive=True)

  if result.failed:
    errmsg = ("Command '%s' failed with exit code %s; output %r" %
              (result.cmd, result.exit_code, result.output))
    raise errors.OpExecError(errmsg)


163
@UsesRPC
Iustin Pop's avatar
Iustin Pop committed
164
def AddNode(opts, args):
165
166
167
168
169
170
171
  """Add a node to the cluster.

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

  """
Iustin Pop's avatar
Iustin Pop committed
174
  cl = GetClient()
175
  node = netutils.GetHostname(name=args[0]).name
Iustin Pop's avatar
Iustin Pop committed
176
177
178
179
  readd = opts.readd

  try:
    output = cl.QueryNodes(names=[node], fields=['name', 'sip'],
180
                           use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
181
182
183
184
185
186
187
188
189
190
191
192
    node_exists, sip = output[0]
  except (errors.OpPrereqError, errors.OpExecError):
    node_exists = ""
    sip = None

  if readd:
    if not node_exists:
      ToStderr("Node %s not in the cluster"
               " - please retry without '--readd'", node)
      return 1
  else:
    if node_exists:
193
      ToStderr("Node %s already in the cluster (as %s)"
Iustin Pop's avatar
Iustin Pop committed
194
               " - please retry with '--readd'", node, node_exists)
195
      return 1
Iustin Pop's avatar
Iustin Pop committed
196
    sip = opts.secondary_ip
197

Iustin Pop's avatar
Iustin Pop committed
198
199
  # read the cluster name from the master
  output = cl.QueryConfigValues(['cluster_name'])
200
  cluster_name = output[0]
Iustin Pop's avatar
Iustin Pop committed
201

202
  if not readd and opts.node_setup:
Iustin Pop's avatar
Iustin Pop committed
203
204
205
206
207
208
    ToStderr("-- WARNING -- \n"
             "Performing this operation is going to replace the ssh daemon"
             " keypair\n"
             "on the target machine (%s) with the ones of the"
             " current one\n"
             "and grant full intra-cluster ssh root access to/from it\n", node)
209

210
211
  if opts.node_setup:
    _RunSetupSSH(opts, [node])
212

213
214
  bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check)

Iustin Pop's avatar
Iustin Pop committed
215
  op = opcodes.OpAddNode(node_name=args[0], secondary_ip=sip,
216
217
218
                         readd=opts.readd, group=opts.nodegroup,
                         vm_capable=opts.vm_capable,
                         master_capable=opts.master_capable)
219
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
220
221
222
223
224


def ListNodes(opts, args):
  """List nodes and their properties.

225
226
227
228
229
230
  @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
231
  """
232
  selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS)
Iustin Pop's avatar
Iustin Pop committed
233

Iustin Pop's avatar
Iustin Pop committed
234
  output = GetClient().QueryNodes(args, selected_fields, opts.do_locking)
Iustin Pop's avatar
Iustin Pop committed
235
236

  if not opts.no_headers:
237
    headers = _LIST_HEADERS
238
239
240
  else:
    headers = None

241
  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
242

243
244
  numfields = ["dtotal", "dfree",
               "mtotal", "mnode", "mfree",
245
               "pinst_cnt", "sinst_cnt",
246
               "ctotal", "serial_no"]
247

248
  list_type_fields = ("pinst_list", "sinst_list", "tags")
249
250
251
252
  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
253
      if field in list_type_fields:
254
        val = ",".join(val)
255
256
      elif field in ('master', 'master_candidate', 'offline', 'drained',
                     'master_capable', 'vm_capable'):
257
258
259
260
        if val:
          val = 'Y'
        else:
          val = 'N'
261
262
      elif field == "ctime" or field == "mtime":
        val = utils.FormatTime(val)
263
264
      elif val is None:
        val = "?"
265
      elif opts.roman_integers and isinstance(val, int):
266
        val = compat.TryToRoman(val)
267
      row[idx] = str(val)
268

269
270
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
271
                       numfields=numfields, data=output, units=opts.units)
272
  for line in data:
273
    ToStdout(line)
Iustin Pop's avatar
Iustin Pop committed
274
275
276
277

  return 0


Iustin Pop's avatar
Iustin Pop committed
278
279
280
def EvacuateNode(opts, args):
  """Relocate all secondary instance from a node.

281
282
283
284
285
286
  @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
287
  """
288
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
289
  force = opts.force
290
291
292
293

  dst_node = opts.dst_node
  iallocator = opts.iallocator

294
295
296
297
298
299
300
301
302
  op = opcodes.OpNodeEvacuationStrategy(nodes=args,
                                        iallocator=iallocator,
                                        remote_node=dst_node)

  result = SubmitOpCode(op, cl=cl, opts=opts)
  if not result:
    # no instances to migrate
    ToStderr("No secondary instances on node(s) %s, exiting.",
             utils.CommaJoin(args))
Iustin Pop's avatar
Iustin Pop committed
303
304
    return constants.EXIT_SUCCESS

305
306
307
  if not force and not AskUser("Relocate instance(s) %s from node(s) %s?" %
                               (",".join("'%s'" % name[0] for name in result),
                               utils.CommaJoin(args))):
Iustin Pop's avatar
Iustin Pop committed
308
309
    return constants.EXIT_CONFIRMATION

310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
  jex = JobExecutor(cl=cl, opts=opts)
  for row in result:
    iname = row[0]
    node = row[1]
    ToStdout("Will relocate instance %s to node %s", iname, node)
    op = opcodes.OpReplaceDisks(instance_name=iname,
                                remote_node=node, disks=[],
                                mode=constants.REPLACE_DISK_CHG,
                                early_release=opts.early_release)
    jex.QueueJob(iname, op)
  results = jex.GetResults()
  bad_cnt = len([row for row in results if not row[0]])
  if bad_cnt == 0:
    ToStdout("All %d instance(s) failed over successfully.", len(results))
    rcode = constants.EXIT_SUCCESS
  else:
    ToStdout("There were errors during the failover:\n"
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
    rcode = constants.EXIT_FAILURE
  return rcode
Iustin Pop's avatar
Iustin Pop committed
330
331


Iustin Pop's avatar
Iustin Pop committed
332
333
334
def FailoverNode(opts, args):
  """Failover all primary instance on a node.

335
336
337
338
339
340
  @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
341
  """
342
  cl = GetClient()
Iustin Pop's avatar
Iustin Pop committed
343
344
345
  force = opts.force
  selected_fields = ["name", "pinst_list"]

346
347
348
  # these fields are static data anyway, so it doesn't matter, but
  # locking=True should be safer
  result = cl.QueryNodes(names=args, fields=selected_fields,
349
                         use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
350
351
352
  node, pinst = result[0]

  if not pinst:
353
    ToStderr("No primary instances on node %s, exiting.", node)
Iustin Pop's avatar
Iustin Pop committed
354
355
356
357
358
359
360
361
362
363
    return 0

  pinst = utils.NiceSort(pinst)

  retcode = 0

  if not force and not AskUser("Fail over instance(s) %s?" %
                               (",".join("'%s'" % name for name in pinst))):
    return 2

364
  jex = JobExecutor(cl=cl, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
365
366
367
  for iname in pinst:
    op = opcodes.OpFailoverInstance(instance_name=iname,
                                    ignore_consistency=opts.ignore_consistency)
368
369
370
371
372
    jex.QueueJob(iname, op)
  results = jex.GetResults()
  bad_cnt = len([row for row in results if not row[0]])
  if bad_cnt == 0:
    ToStdout("All %d instance(s) failed over successfully.", len(results))
Iustin Pop's avatar
Iustin Pop committed
373
  else:
374
    ToStdout("There were errors during the failover:\n"
375
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
Iustin Pop's avatar
Iustin Pop committed
376
377
378
  return retcode


Iustin Pop's avatar
Iustin Pop committed
379
380
381
382
383
384
385
386
def MigrateNode(opts, args):
  """Migrate all primary instance on a node.

  """
  cl = GetClient()
  force = opts.force
  selected_fields = ["name", "pinst_list"]

387
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
388
389
390
391
392
393
394
395
396
397
398
399
  node, pinst = result[0]

  if not pinst:
    ToStdout("No primary instances on node %s, exiting." % node)
    return 0

  pinst = utils.NiceSort(pinst)

  if not force and not AskUser("Migrate instance(s) %s?" %
                               (",".join("'%s'" % name for name in pinst))):
    return 2

400
  # this should be removed once --non-live is deprecated
401
  if not opts.live and opts.migration_mode is not None:
402
    raise errors.OpPrereqError("Only one of the --non-live and "
403
                               "--migration-mode options can be passed",
404
405
                               errors.ECODE_INVAL)
  if not opts.live: # --non-live passed
406
    mode = constants.HT_MIGRATION_NONLIVE
407
  else:
408
409
    mode = opts.migration_mode
  op = opcodes.OpMigrateNode(node_name=args[0], mode=mode)
410
  SubmitOpCode(op, cl=cl, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
411
412


Iustin Pop's avatar
Iustin Pop committed
413
414
415
def ShowNodeConfig(opts, args):
  """Show node information.

416
417
418
419
420
421
422
423
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should either be an empty list, in which case
      we show information about all nodes, or should contain
      a list of nodes to be queried for information
  @rtype: int
  @return: the desired exit code

Iustin Pop's avatar
Iustin Pop committed
424
  """
425
426
  cl = GetClient()
  result = cl.QueryNodes(fields=["name", "pip", "sip",
427
                                 "pinst_list", "sinst_list",
428
429
                                 "master_candidate", "drained", "offline",
                                 "master_capable", "vm_capable"],
430
                         names=args, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
431

432
  for (name, primary_ip, secondary_ip, pinst, sinst,
433
       is_mc, drained, offline, master_capable, vm_capable) in result:
434
435
436
    ToStdout("Node name: %s", name)
    ToStdout("  primary ip: %s", primary_ip)
    ToStdout("  secondary ip: %s", secondary_ip)
437
438
439
    ToStdout("  master candidate: %s", is_mc)
    ToStdout("  drained: %s", drained)
    ToStdout("  offline: %s", offline)
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
    ToStdout("  master_capable: %s", master_capable)
    ToStdout("  vm_capable: %s", vm_capable)
    if vm_capable:
      if pinst:
        ToStdout("  primary for instances:")
        for iname in utils.NiceSort(pinst):
          ToStdout("    - %s", iname)
      else:
        ToStdout("  primary for no instances")
      if sinst:
        ToStdout("  secondary for instances:")
        for iname in utils.NiceSort(sinst):
          ToStdout("    - %s", iname)
      else:
        ToStdout("  secondary for no instances")
Iustin Pop's avatar
Iustin Pop committed
455
456
457
458
459

  return 0


def RemoveNode(opts, args):
460
461
462
463
464
465
466
467
468
469
  """Remove a node from the cluster.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the name of
      the node to be removed
  @rtype: int
  @return: the desired exit code

  """
Iustin Pop's avatar
Iustin Pop committed
470
  op = opcodes.OpRemoveNode(node_name=args[0])
471
  SubmitOpCode(op, opts=opts)
472
  return 0
Iustin Pop's avatar
Iustin Pop committed
473
474


Iustin Pop's avatar
Iustin Pop committed
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
def PowercycleNode(opts, args):
  """Remove a node from the cluster.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain only one element, the name of
      the node to be removed
  @rtype: int
  @return: the desired exit code

  """
  node = args[0]
  if (not opts.confirm and
      not AskUser("Are you sure you want to hard powercycle node %s?" % node)):
    return 2

  op = opcodes.OpPowercycleNode(node_name=node, force=opts.force)
492
  result = SubmitOpCode(op, opts=opts)
493
494
  if result:
    ToStderr(result)
Iustin Pop's avatar
Iustin Pop committed
495
496
497
  return 0


498
499
500
def ListVolumes(opts, args):
  """List logical volumes on node(s).

501
502
503
504
505
506
507
508
  @param opts: the command line options selected by the user
  @type args: list
  @param args: should either be an empty list, in which case
      we list data for all nodes, or contain a list of nodes
      to display data only for those
  @rtype: int
  @return: the desired exit code

509
  """
510
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
511
512

  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
513
  output = SubmitOpCode(op, opts=opts)
514
515

  if not opts.no_headers:
516
517
518
519
520
521
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

522
  unitfields = ["size"]
523
524
525

  numfields = ["size"]

526
527
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
528
                       numfields=numfields, data=output, units=opts.units)
529
530

  for line in data:
531
    ToStdout(line)
532
533
534
535

  return 0


Iustin Pop's avatar
Iustin Pop committed
536
def ListStorage(opts, args):
537
538
539
540
541
542
543
544
545
546
547
  """List physical volumes on node(s).

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should either be an empty list, in which case
      we list data for all nodes, or contain a list of nodes
      to display data only for those
  @rtype: int
  @return: the desired exit code

  """
548
549
550
551
  # TODO: Default to ST_FILE if LVM is disabled on the cluster
  if opts.user_storage_type is None:
    opts.user_storage_type = constants.ST_LVM_PV

552
  storage_type = ConvertStorageType(opts.user_storage_type)
553

554
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
555
556

  op = opcodes.OpQueryNodeStorage(nodes=args,
557
                                  storage_type=storage_type,
558
                                  output_fields=selected_fields)
559
  output = SubmitOpCode(op, opts=opts)
560
561
562

  if not opts.no_headers:
    headers = {
563
564
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
565
566
567
568
569
570
571
572
573
574
575
576
      constants.SF_NAME: "Name",
      constants.SF_SIZE: "Size",
      constants.SF_USED: "Used",
      constants.SF_FREE: "Free",
      constants.SF_ALLOCATABLE: "Allocatable",
      }
  else:
    headers = None

  unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]
  numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE]

577
578
579
580
581
582
583
584
585
586
587
  # change raw values to nicer strings
  for row in output:
    for idx, field in enumerate(selected_fields):
      val = row[idx]
      if field == constants.SF_ALLOCATABLE:
        if val:
          val = "Y"
        else:
          val = "N"
      row[idx] = str(val)

588
589
590
591
592
593
594
595
596
597
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
                       numfields=numfields, data=output, units=opts.units)

  for line in data:
    ToStdout(line)

  return 0


Iustin Pop's avatar
Iustin Pop committed
598
def ModifyStorage(opts, args):
599
600
601
602
603
604
605
606
607
608
609
  """Modify storage volume on a node.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain 3 items: node name, storage type and volume name
  @rtype: int
  @return: the desired exit code

  """
  (node_name, user_storage_type, volume_name) = args

610
  storage_type = ConvertStorageType(user_storage_type)
611
612
613
614

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
615
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
616
617
618
619
620
621

  if changes:
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
622
    SubmitOpCode(op, opts=opts)
623
624
  else:
    ToStderr("No changes to perform, exiting.")
625
626


Iustin Pop's avatar
Iustin Pop committed
627
def RepairStorage(opts, args):
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
  """Repairs a storage volume on a node.

  @param opts: the command line options selected by the user
  @type args: list
  @param args: should contain 3 items: node name, storage type and volume name
  @rtype: int
  @return: the desired exit code

  """
  (node_name, user_storage_type, volume_name) = args

  storage_type = ConvertStorageType(user_storage_type)

  op = opcodes.OpRepairNodeStorage(node_name=node_name,
                                   storage_type=storage_type,
643
644
                                   name=volume_name,
                                   ignore_consistency=opts.ignore_consistency)
645
  SubmitOpCode(op, opts=opts)
646
647


Iustin Pop's avatar
Iustin Pop committed
648
649
650
651
652
653
654
655
656
657
def SetNodeParams(opts, args):
  """Modifies a node.

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

  """
Iustin Pop's avatar
Iustin Pop committed
658
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
659
                 opts.master_capable, opts.vm_capable, opts.secondary_ip]
Iustin Pop's avatar
Iustin Pop committed
660
  if all_changes.count(None) == len(all_changes):
Iustin Pop's avatar
Iustin Pop committed
661
662
663
664
    ToStderr("Please give at least one of the parameters.")
    return 1

  op = opcodes.OpSetNodeParams(node_name=args[0],
Iustin Pop's avatar
Iustin Pop committed
665
666
667
                               master_candidate=opts.master_candidate,
                               offline=opts.offline,
                               drained=opts.drained,
668
                               master_capable=opts.master_capable,
Iustin Pop's avatar
Iustin Pop committed
669
                               vm_capable=opts.vm_capable,
670
                               secondary_ip=opts.secondary_ip,
671
672
                               force=opts.force,
                               auto_promote=opts.auto_promote)
Iustin Pop's avatar
Iustin Pop committed
673
674
675
676
677
678
679
680
681
682
683

  # even if here we process the result, we allow submit only
  result = SubmitOrSend(op, opts)

  if result:
    ToStdout("Modified node %s", args[0])
    for param, data in result:
      ToStdout(" - %-5s -> %s", param, data)
  return 0


Iustin Pop's avatar
Iustin Pop committed
684
commands = {
685
686
  'add': (
    AddNode, [ArgHost(min=1, max=1)],
687
    [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NONODE_SETUP_OPT,
688
689
     VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT, CAPAB_MASTER_OPT,
     CAPAB_VM_OPT],
690
    "[-s ip] [--readd] [--no-ssh-key-check] [--no-node-setup]  [--verbose] "
691
    " <node_name>",
692
693
    "Add a node to the cluster"),
  'evacuate': (
694
    EvacuateNode, [ArgNode(min=1)],
695
696
    [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT,
     PRIORITY_OPT],
697
698
699
700
    "[-f] {-I <iallocator> | -n <dst>} <node>",
    "Relocate the secondary instances from a node"
    " to other nodes (only for instances with drbd disk template)"),
  'failover': (
701
    FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT, PRIORITY_OPT],
702
703
704
705
    "[-f] <node>",
    "Stops the primary instances on a node and start them on their"
    " secondary node (only for instances with drbd disk template)"),
  'migrate': (
706
707
    MigrateNode, ARGS_ONE_NODE,
    [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, PRIORITY_OPT],
708
709
710
711
    "[-f] <node>",
    "Migrate all the primary instance on a node away from it"
    " (only for instances of type drbd)"),
  'info': (
712
    ShowNodeConfig, ARGS_MANY_NODES, [],
713
714
715
    "[<node_name>...]", "Show information about the node(s)"),
  'list': (
    ListNodes, ARGS_MANY_NODES,
716
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, SYNC_OPT, ROMAN_OPT],
717
718
719
    "[nodes...]",
    "Lists the nodes in the cluster. The available fields are (see the man"
    " page for details): %s. The default field list is (in order): %s." %
720
    (utils.CommaJoin(_LIST_HEADERS), utils.CommaJoin(_LIST_DEF_FIELDS))),
721
722
  'modify': (
    SetNodeParams, ARGS_ONE_NODE,
Iustin Pop's avatar
Iustin Pop committed
723
    [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT,
724
     CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT,
725
     AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
726
727
728
    "<node_name>", "Alters the parameters of a node"),
  'powercycle': (
    PowercycleNode, ARGS_ONE_NODE,
729
    [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT],
730
731
    "<node_name>", "Tries to forcefully powercycle a node"),
  'remove': (
732
    RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT],
733
734
735
    "<node_name>", "Removes a node from the cluster"),
  'volumes': (
    ListVolumes, [ArgNode()],
736
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT],
737
    "[<node_name>...]", "List logical volumes on node(s)"),
Iustin Pop's avatar
Iustin Pop committed
738
739
  'list-storage': (
    ListStorage, ARGS_MANY_NODES,
740
741
    [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT,
     PRIORITY_OPT],
742
743
    "[<node_name>...]", "List physical volumes on node(s). The available"
    " fields are (see the man page for details): %s." %
744
    (utils.CommaJoin(_LIST_STOR_HEADERS))),
Iustin Pop's avatar
Iustin Pop committed
745
746
  'modify-storage': (
    ModifyStorage,
747
748
749
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
750
    [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT],
751
    "<node_name> <storage_type> <name>", "Modify storage volume on a node"),
Iustin Pop's avatar
Iustin Pop committed
752
753
  'repair-storage': (
    RepairStorage,
754
755
756
    [ArgNode(min=1, max=1),
     ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES),
     ArgFile(min=1, max=1)],
757
    [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT],
758
759
760
    "<node_name> <storage_type> <name>",
    "Repairs a storage volume on a node"),
  'list-tags': (
761
    ListTags, ARGS_ONE_NODE, [],
762
763
    "<node_name>", "List the tags of the given node"),
  'add-tags': (
764
    AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT],
765
766
    "<node_name> tag...", "Add tags to the given node"),
  'remove-tags': (
767
768
    RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()],
    [TAG_SRC_OPT, PRIORITY_OPT],
769
    "<node_name> tag...", "Remove tags from the given node"),
Iustin Pop's avatar
Iustin Pop committed
770
771
772
  }


773
774
def Main():
  return GenericMain(commands, override={"tag_type": constants.TAG_NODE})