gnt_node.py 23.6 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
                         readd=opts.readd, group=opts.nodegroup)
217
  SubmitOpCode(op, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
218
219
220
221
222


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

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

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

  if not opts.no_headers:
235
    headers = _LIST_HEADERS
236
237
238
  else:
    headers = None

239
  unitfields = ["dtotal", "dfree", "mtotal", "mnode", "mfree"]
240

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

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

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

  return 0


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

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

  dst_node = opts.dst_node
  iallocator = opts.iallocator

292
293
294
295
296
297
298
299
300
  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
301
302
    return constants.EXIT_SUCCESS

303
304
305
  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
306
307
    return constants.EXIT_CONFIRMATION

308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
  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
328
329


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

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

344
345
346
  # 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,
347
                         use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
348
349
350
  node, pinst = result[0]

  if not pinst:
351
    ToStderr("No primary instances on node %s, exiting.", node)
Iustin Pop's avatar
Iustin Pop committed
352
353
354
355
356
357
358
359
360
361
    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

362
  jex = JobExecutor(cl=cl, opts=opts)
Iustin Pop's avatar
Iustin Pop committed
363
364
365
  for iname in pinst:
    op = opcodes.OpFailoverInstance(instance_name=iname,
                                    ignore_consistency=opts.ignore_consistency)
366
367
368
369
370
    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
371
  else:
372
    ToStdout("There were errors during the failover:\n"
373
             "%d error(s) out of %d instance(s).", bad_cnt, len(results))
Iustin Pop's avatar
Iustin Pop committed
374
375
376
  return retcode


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

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

385
  result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
386
387
388
389
390
391
392
393
394
395
396
397
  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

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


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

414
415
416
417
418
419
420
421
  @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
422
  """
423
424
  cl = GetClient()
  result = cl.QueryNodes(fields=["name", "pip", "sip",
425
426
                                 "pinst_list", "sinst_list",
                                 "master_candidate", "drained", "offline"],
427
                         names=args, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
428

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

  return 0


def RemoveNode(opts, args):
454
455
456
457
458
459
460
461
462
463
  """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
464
  op = opcodes.OpRemoveNode(node_name=args[0])
465
  SubmitOpCode(op, opts=opts)
466
  return 0
Iustin Pop's avatar
Iustin Pop committed
467
468


Iustin Pop's avatar
Iustin Pop committed
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
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)
486
  result = SubmitOpCode(op, opts=opts)
487
488
  if result:
    ToStderr(result)
Iustin Pop's avatar
Iustin Pop committed
489
490
491
  return 0


492
493
494
def ListVolumes(opts, args):
  """List logical volumes on node(s).

495
496
497
498
499
500
501
502
  @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

503
  """
504
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
505
506

  op = opcodes.OpQueryNodeVolumes(nodes=args, output_fields=selected_fields)
507
  output = SubmitOpCode(op, opts=opts)
508
509

  if not opts.no_headers:
510
511
512
513
514
515
    headers = {"node": "Node", "phys": "PhysDev",
               "vg": "VG", "name": "Name",
               "size": "Size", "instance": "Instance"}
  else:
    headers = None

516
  unitfields = ["size"]
517
518
519

  numfields = ["size"]

520
521
  data = GenerateTable(separator=opts.separator, headers=headers,
                       fields=selected_fields, unitfields=unitfields,
522
                       numfields=numfields, data=output, units=opts.units)
523
524

  for line in data:
525
    ToStdout(line)
526
527
528
529

  return 0


Iustin Pop's avatar
Iustin Pop committed
530
def ListStorage(opts, args):
531
532
533
534
535
536
537
538
539
540
541
  """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

  """
542
543
544
545
  # 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

546
  storage_type = ConvertStorageType(opts.user_storage_type)
547

548
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
549
550

  op = opcodes.OpQueryNodeStorage(nodes=args,
551
                                  storage_type=storage_type,
552
                                  output_fields=selected_fields)
553
  output = SubmitOpCode(op, opts=opts)
554
555
556

  if not opts.no_headers:
    headers = {
557
558
      constants.SF_NODE: "Node",
      constants.SF_TYPE: "Type",
559
560
561
562
563
564
565
566
567
568
569
570
      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]

571
572
573
574
575
576
577
578
579
580
581
  # 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)

582
583
584
585
586
587
588
589
590
591
  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
592
def ModifyStorage(opts, args):
593
594
595
596
597
598
599
600
601
602
603
  """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

604
  storage_type = ConvertStorageType(user_storage_type)
605
606
607
608

  changes = {}

  if opts.allocatable is not None:
Iustin Pop's avatar
Iustin Pop committed
609
    changes[constants.SF_ALLOCATABLE] = opts.allocatable
610
611
612
613
614
615

  if changes:
    op = opcodes.OpModifyNodeStorage(node_name=node_name,
                                     storage_type=storage_type,
                                     name=volume_name,
                                     changes=changes)
616
    SubmitOpCode(op, opts=opts)
617
618
  else:
    ToStderr("No changes to perform, exiting.")
619
620


Iustin Pop's avatar
Iustin Pop committed
621
def RepairStorage(opts, args):
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
  """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,
637
638
                                   name=volume_name,
                                   ignore_consistency=opts.ignore_consistency)
639
  SubmitOpCode(op, opts=opts)
640
641


Iustin Pop's avatar
Iustin Pop committed
642
643
644
645
646
647
648
649
650
651
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
652
653
654
  all_changes = [opts.master_candidate, opts.drained, opts.offline,
                 opts.master_capable, opts.vm_capable]
  if all_changes.count(None) == len(all_changes):
Iustin Pop's avatar
Iustin Pop committed
655
656
657
658
    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
659
660
661
                               master_candidate=opts.master_candidate,
                               offline=opts.offline,
                               drained=opts.drained,
662
                               master_capable=opts.master_capable,
Iustin Pop's avatar
Iustin Pop committed
663
                               vm_capable=opts.vm_capable,
664
665
                               force=opts.force,
                               auto_promote=opts.auto_promote)
Iustin Pop's avatar
Iustin Pop committed
666
667
668
669
670
671
672
673
674
675
676

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


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