gnt_node.py 23.8 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
428
                                 "pinst_list", "sinst_list",
                                 "master_candidate", "drained", "offline"],
429
                         names=args, use_locking=False)
Iustin Pop's avatar
Iustin Pop committed
430

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

  return 0


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


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


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

497
498
499
500
501
502
503
504
  @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

505
  """
506
  selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS)
507
508

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

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

518
  unitfields = ["size"]
519
520
521

  numfields = ["size"]

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

  for line in data:
527
    ToStdout(line)
528
529
530
531

  return 0


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

  """
544
545
546
547
  # 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

548
  storage_type = ConvertStorageType(opts.user_storage_type)
549

550
  selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS)
551
552

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

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

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

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

606
  storage_type = ConvertStorageType(user_storage_type)
607
608
609
610

  changes = {}

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

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


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


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

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


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