Package ganeti :: Package client :: Module gnt_node
[hide private]
[frames] | no frames]

Source Code for Module ganeti.client.gnt_node

  1  # 
  2  # 
  3   
  4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012 Google Inc. 
  5  # 
  6  # This program is free software; you can redistribute it and/or modify 
  7  # it under the terms of the GNU General Public License as published by 
  8  # the Free Software Foundation; either version 2 of the License, or 
  9  # (at your option) any later version. 
 10  # 
 11  # This program is distributed in the hope that it will be useful, but 
 12  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 14  # General Public License for more details. 
 15  # 
 16  # You should have received a copy of the GNU General Public License 
 17  # along with this program; if not, write to the Free Software 
 18  # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 
 19  # 02110-1301, USA. 
 20   
 21  """Node related commands""" 
 22   
 23  # pylint: disable=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) 
 27  # C0103: Invalid name gnt-node 
 28   
 29  import itertools 
 30   
 31  from ganeti.cli import * 
 32  from ganeti import cli 
 33  from ganeti import bootstrap 
 34  from ganeti import opcodes 
 35  from ganeti import utils 
 36  from ganeti import constants 
 37  from ganeti import errors 
 38  from ganeti import netutils 
 39  from cStringIO import StringIO 
 40   
 41   
 42  #: default list of field for L{ListNodes} 
 43  _LIST_DEF_FIELDS = [ 
 44    "name", "dtotal", "dfree", 
 45    "mtotal", "mnode", "mfree", 
 46    "pinst_cnt", "sinst_cnt", 
 47    ] 
 48   
 49   
 50  #: Default field list for L{ListVolumes} 
 51  _LIST_VOL_DEF_FIELDS = ["node", "phys", "vg", "name", "size", "instance"] 
 52   
 53   
 54  #: default list of field for L{ListStorage} 
 55  _LIST_STOR_DEF_FIELDS = [ 
 56    constants.SF_NODE, 
 57    constants.SF_TYPE, 
 58    constants.SF_NAME, 
 59    constants.SF_SIZE, 
 60    constants.SF_USED, 
 61    constants.SF_FREE, 
 62    constants.SF_ALLOCATABLE, 
 63    ] 
 64   
 65   
 66  #: default list of power commands 
 67  _LIST_POWER_COMMANDS = ["on", "off", "cycle", "status"] 
 68   
 69   
 70  #: headers (and full field list) for L{ListStorage} 
 71  _LIST_STOR_HEADERS = { 
 72    constants.SF_NODE: "Node", 
 73    constants.SF_TYPE: "Type", 
 74    constants.SF_NAME: "Name", 
 75    constants.SF_SIZE: "Size", 
 76    constants.SF_USED: "Used", 
 77    constants.SF_FREE: "Free", 
 78    constants.SF_ALLOCATABLE: "Allocatable", 
 79    } 
 80   
 81   
 82  #: User-facing storage unit types 
 83  _USER_STORAGE_TYPE = { 
 84    constants.ST_FILE: "file", 
 85    constants.ST_LVM_PV: "lvm-pv", 
 86    constants.ST_LVM_VG: "lvm-vg", 
 87    } 
 88   
 89  _STORAGE_TYPE_OPT = \ 
 90    cli_option("-t", "--storage-type", 
 91               dest="user_storage_type", 
 92               choices=_USER_STORAGE_TYPE.keys(), 
 93               default=None, 
 94               metavar="STORAGE_TYPE", 
 95               help=("Storage type (%s)" % 
 96                     utils.CommaJoin(_USER_STORAGE_TYPE.keys()))) 
 97   
 98  _REPAIRABLE_STORAGE_TYPES = \ 
 99    [st for st, so in constants.VALID_STORAGE_OPERATIONS.iteritems() 
100     if constants.SO_FIX_CONSISTENCY in so] 
101   
102  _MODIFIABLE_STORAGE_TYPES = constants.MODIFIABLE_STORAGE_FIELDS.keys() 
103   
104   
105  _OOB_COMMAND_ASK = frozenset([constants.OOB_POWER_OFF, 
106                                constants.OOB_POWER_CYCLE]) 
107   
108   
109  NONODE_SETUP_OPT = cli_option("--no-node-setup", default=True, 
110                                action="store_false", dest="node_setup", 
111                                help=("Do not make initial SSH setup on remote" 
112                                      " node (needs to be done manually)")) 
113   
114  IGNORE_STATUS_OPT = cli_option("--ignore-status", default=False, 
115                                 action="store_true", dest="ignore_status", 
116                                 help=("Ignore the Node(s) offline status" 
117                                       " (potentially DANGEROUS)")) 
118 119 120 -def ConvertStorageType(user_storage_type):
121 """Converts a user storage type to its internal name. 122 123 """ 124 try: 125 return _USER_STORAGE_TYPE[user_storage_type] 126 except KeyError: 127 raise errors.OpPrereqError("Unknown storage type: %s" % user_storage_type, 128 errors.ECODE_INVAL)
129
130 131 -def _RunSetupSSH(options, nodes):
132 """Wrapper around utils.RunCmd to call setup-ssh 133 134 @param options: The command line options 135 @param nodes: The nodes to setup 136 137 """ 138 cmd = [constants.SETUP_SSH] 139 140 # Pass --debug|--verbose to the external script if set on our invocation 141 # --debug overrides --verbose 142 if options.debug: 143 cmd.append("--debug") 144 elif options.verbose: 145 cmd.append("--verbose") 146 if not options.ssh_key_check: 147 cmd.append("--no-ssh-key-check") 148 if options.force_join: 149 cmd.append("--force-join") 150 151 cmd.extend(nodes) 152 153 result = utils.RunCmd(cmd, interactive=True) 154 155 if result.failed: 156 errmsg = ("Command '%s' failed with exit code %s; output %r" % 157 (result.cmd, result.exit_code, result.output)) 158 raise errors.OpExecError(errmsg)
159
160 161 @UsesRPC 162 -def AddNode(opts, args):
163 """Add a node to the cluster. 164 165 @param opts: the command line options selected by the user 166 @type args: list 167 @param args: should contain only one element, the new node name 168 @rtype: int 169 @return: the desired exit code 170 171 """ 172 cl = GetClient() 173 node = netutils.GetHostname(name=args[0]).name 174 readd = opts.readd 175 176 try: 177 output = cl.QueryNodes(names=[node], fields=["name", "sip", "master"], 178 use_locking=False) 179 node_exists, sip, is_master = output[0] 180 except (errors.OpPrereqError, errors.OpExecError): 181 node_exists = "" 182 sip = None 183 184 if readd: 185 if not node_exists: 186 ToStderr("Node %s not in the cluster" 187 " - please retry without '--readd'", node) 188 return 1 189 if is_master: 190 ToStderr("Node %s is the master, cannot readd", node) 191 return 1 192 else: 193 if node_exists: 194 ToStderr("Node %s already in the cluster (as %s)" 195 " - please retry with '--readd'", node, node_exists) 196 return 1 197 sip = opts.secondary_ip 198 199 # read the cluster name from the master 200 output = cl.QueryConfigValues(["cluster_name"]) 201 cluster_name = output[0] 202 203 if not readd and opts.node_setup: 204 ToStderr("-- WARNING -- \n" 205 "Performing this operation is going to replace the ssh daemon" 206 " keypair\n" 207 "on the target machine (%s) with the ones of the" 208 " current one\n" 209 "and grant full intra-cluster ssh root access to/from it\n", node) 210 211 if opts.node_setup: 212 _RunSetupSSH(opts, [node]) 213 214 bootstrap.SetupNodeDaemon(cluster_name, node, opts.ssh_key_check) 215 216 op = opcodes.OpNodeAdd(node_name=args[0], secondary_ip=sip, 217 readd=opts.readd, group=opts.nodegroup, 218 vm_capable=opts.vm_capable, ndparams=opts.ndparams, 219 master_capable=opts.master_capable) 220 SubmitOpCode(op, opts=opts)
221
222 223 -def ListNodes(opts, args):
224 """List nodes and their properties. 225 226 @param opts: the command line options selected by the user 227 @type args: list 228 @param args: nodes to list, or empty for all 229 @rtype: int 230 @return: the desired exit code 231 232 """ 233 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS) 234 235 fmtoverride = dict.fromkeys(["pinst_list", "sinst_list", "tags"], 236 (",".join, False)) 237 238 return GenericList(constants.QR_NODE, selected_fields, args, opts.units, 239 opts.separator, not opts.no_headers, 240 format_override=fmtoverride, verbose=opts.verbose, 241 force_filter=opts.force_filter)
242
243 244 -def ListNodeFields(opts, args):
245 """List node fields. 246 247 @param opts: the command line options selected by the user 248 @type args: list 249 @param args: fields to list, or empty for all 250 @rtype: int 251 @return: the desired exit code 252 253 """ 254 return GenericListFields(constants.QR_NODE, args, opts.separator, 255 not opts.no_headers)
256
257 258 -def EvacuateNode(opts, args):
259 """Relocate all secondary instance from a node. 260 261 @param opts: the command line options selected by the user 262 @type args: list 263 @param args: should be an empty list 264 @rtype: int 265 @return: the desired exit code 266 267 """ 268 if opts.dst_node is not None: 269 ToStderr("New secondary node given (disabling iallocator), hence evacuating" 270 " secondary instances only.") 271 opts.secondary_only = True 272 opts.primary_only = False 273 274 if opts.secondary_only and opts.primary_only: 275 raise errors.OpPrereqError("Only one of the --primary-only and" 276 " --secondary-only options can be passed", 277 errors.ECODE_INVAL) 278 elif opts.primary_only: 279 mode = constants.IALLOCATOR_NEVAC_PRI 280 elif opts.secondary_only: 281 mode = constants.IALLOCATOR_NEVAC_SEC 282 else: 283 mode = constants.IALLOCATOR_NEVAC_ALL 284 285 # Determine affected instances 286 fields = [] 287 288 if not opts.secondary_only: 289 fields.append("pinst_list") 290 if not opts.primary_only: 291 fields.append("sinst_list") 292 293 cl = GetClient() 294 295 result = cl.QueryNodes(names=args, fields=fields, use_locking=False) 296 instances = set(itertools.chain(*itertools.chain(*itertools.chain(result)))) 297 298 if not instances: 299 # No instances to evacuate 300 ToStderr("No instances to evacuate on node(s) %s, exiting.", 301 utils.CommaJoin(args)) 302 return constants.EXIT_SUCCESS 303 304 if not (opts.force or 305 AskUser("Relocate instance(s) %s from node(s) %s?" % 306 (utils.CommaJoin(utils.NiceSort(instances)), 307 utils.CommaJoin(args)))): 308 return constants.EXIT_CONFIRMATION 309 310 # Evacuate node 311 op = opcodes.OpNodeEvacuate(node_name=args[0], mode=mode, 312 remote_node=opts.dst_node, 313 iallocator=opts.iallocator, 314 early_release=opts.early_release) 315 result = SubmitOpCode(op, cl=cl, opts=opts) 316 317 # Keep track of submitted jobs 318 jex = JobExecutor(cl=cl, opts=opts) 319 320 for (status, job_id) in result[constants.JOB_IDS_KEY]: 321 jex.AddJobId(None, status, job_id) 322 323 results = jex.GetResults() 324 bad_cnt = len([row for row in results if not row[0]]) 325 if bad_cnt == 0: 326 ToStdout("All instances evacuated successfully.") 327 rcode = constants.EXIT_SUCCESS 328 else: 329 ToStdout("There were %s errors during the evacuation.", bad_cnt) 330 rcode = constants.EXIT_FAILURE 331 332 return rcode
333
334 335 -def FailoverNode(opts, args):
336 """Failover all primary instance on a node. 337 338 @param opts: the command line options selected by the user 339 @type args: list 340 @param args: should be an empty list 341 @rtype: int 342 @return: the desired exit code 343 344 """ 345 cl = GetClient() 346 force = opts.force 347 selected_fields = ["name", "pinst_list"] 348 349 # these fields are static data anyway, so it doesn't matter, but 350 # locking=True should be safer 351 result = cl.QueryNodes(names=args, fields=selected_fields, 352 use_locking=False) 353 node, pinst = result[0] 354 355 if not pinst: 356 ToStderr("No primary instances on node %s, exiting.", node) 357 return 0 358 359 pinst = utils.NiceSort(pinst) 360 361 retcode = 0 362 363 if not force and not AskUser("Fail over instance(s) %s?" % 364 (",".join("'%s'" % name for name in pinst))): 365 return 2 366 367 jex = JobExecutor(cl=cl, opts=opts) 368 for iname in pinst: 369 op = opcodes.OpInstanceFailover(instance_name=iname, 370 ignore_consistency=opts.ignore_consistency, 371 iallocator=opts.iallocator) 372 jex.QueueJob(iname, op) 373 results = jex.GetResults() 374 bad_cnt = len([row for row in results if not row[0]]) 375 if bad_cnt == 0: 376 ToStdout("All %d instance(s) failed over successfully.", len(results)) 377 else: 378 ToStdout("There were errors during the failover:\n" 379 "%d error(s) out of %d instance(s).", bad_cnt, len(results)) 380 return retcode
381
382 383 -def MigrateNode(opts, args):
384 """Migrate all primary instance on a node. 385 386 """ 387 cl = GetClient() 388 force = opts.force 389 selected_fields = ["name", "pinst_list"] 390 391 result = cl.QueryNodes(names=args, fields=selected_fields, use_locking=False) 392 ((node, pinst), ) = result 393 394 if not pinst: 395 ToStdout("No primary instances on node %s, exiting." % node) 396 return 0 397 398 pinst = utils.NiceSort(pinst) 399 400 if not (force or 401 AskUser("Migrate instance(s) %s?" % 402 utils.CommaJoin(utils.NiceSort(pinst)))): 403 return constants.EXIT_CONFIRMATION 404 405 # this should be removed once --non-live is deprecated 406 if not opts.live and opts.migration_mode is not None: 407 raise errors.OpPrereqError("Only one of the --non-live and " 408 "--migration-mode options can be passed", 409 errors.ECODE_INVAL) 410 if not opts.live: # --non-live passed 411 mode = constants.HT_MIGRATION_NONLIVE 412 else: 413 mode = opts.migration_mode 414 415 op = opcodes.OpNodeMigrate(node_name=args[0], mode=mode, 416 iallocator=opts.iallocator, 417 target_node=opts.dst_node) 418 419 result = SubmitOpCode(op, cl=cl, opts=opts) 420 421 # Keep track of submitted jobs 422 jex = JobExecutor(cl=cl, opts=opts) 423 424 for (status, job_id) in result[constants.JOB_IDS_KEY]: 425 jex.AddJobId(None, status, job_id) 426 427 results = jex.GetResults() 428 bad_cnt = len([row for row in results if not row[0]]) 429 if bad_cnt == 0: 430 ToStdout("All instances migrated successfully.") 431 rcode = constants.EXIT_SUCCESS 432 else: 433 ToStdout("There were %s errors during the node migration.", bad_cnt) 434 rcode = constants.EXIT_FAILURE 435 436 return rcode
437
438 439 -def ShowNodeConfig(opts, args):
440 """Show node information. 441 442 @param opts: the command line options selected by the user 443 @type args: list 444 @param args: should either be an empty list, in which case 445 we show information about all nodes, or should contain 446 a list of nodes to be queried for information 447 @rtype: int 448 @return: the desired exit code 449 450 """ 451 cl = GetClient() 452 result = cl.QueryNodes(fields=["name", "pip", "sip", 453 "pinst_list", "sinst_list", 454 "master_candidate", "drained", "offline", 455 "master_capable", "vm_capable", "powered", 456 "ndparams", "custom_ndparams"], 457 names=args, use_locking=False) 458 459 for (name, primary_ip, secondary_ip, pinst, sinst, is_mc, drained, offline, 460 master_capable, vm_capable, powered, ndparams, 461 ndparams_custom) in result: 462 ToStdout("Node name: %s", name) 463 ToStdout(" primary ip: %s", primary_ip) 464 ToStdout(" secondary ip: %s", secondary_ip) 465 ToStdout(" master candidate: %s", is_mc) 466 ToStdout(" drained: %s", drained) 467 ToStdout(" offline: %s", offline) 468 if powered is not None: 469 ToStdout(" powered: %s", powered) 470 ToStdout(" master_capable: %s", master_capable) 471 ToStdout(" vm_capable: %s", vm_capable) 472 if vm_capable: 473 if pinst: 474 ToStdout(" primary for instances:") 475 for iname in utils.NiceSort(pinst): 476 ToStdout(" - %s", iname) 477 else: 478 ToStdout(" primary for no instances") 479 if sinst: 480 ToStdout(" secondary for instances:") 481 for iname in utils.NiceSort(sinst): 482 ToStdout(" - %s", iname) 483 else: 484 ToStdout(" secondary for no instances") 485 ToStdout(" node parameters:") 486 buf = StringIO() 487 FormatParameterDict(buf, ndparams_custom, ndparams, level=2) 488 ToStdout(buf.getvalue().rstrip("\n")) 489 490 return 0
491
492 493 -def RemoveNode(opts, args):
494 """Remove a node from the cluster. 495 496 @param opts: the command line options selected by the user 497 @type args: list 498 @param args: should contain only one element, the name of 499 the node to be removed 500 @rtype: int 501 @return: the desired exit code 502 503 """ 504 op = opcodes.OpNodeRemove(node_name=args[0]) 505 SubmitOpCode(op, opts=opts) 506 return 0
507
508 509 -def PowercycleNode(opts, args):
510 """Remove a node from the cluster. 511 512 @param opts: the command line options selected by the user 513 @type args: list 514 @param args: should contain only one element, the name of 515 the node to be removed 516 @rtype: int 517 @return: the desired exit code 518 519 """ 520 node = args[0] 521 if (not opts.confirm and 522 not AskUser("Are you sure you want to hard powercycle node %s?" % node)): 523 return 2 524 525 op = opcodes.OpNodePowercycle(node_name=node, force=opts.force) 526 result = SubmitOpCode(op, opts=opts) 527 if result: 528 ToStderr(result) 529 return 0
530
531 532 -def PowerNode(opts, args):
533 """Change/ask power state of a node. 534 535 @param opts: the command line options selected by the user 536 @type args: list 537 @param args: should contain only one element, the name of 538 the node to be removed 539 @rtype: int 540 @return: the desired exit code 541 542 """ 543 command = args.pop(0) 544 545 if opts.no_headers: 546 headers = None 547 else: 548 headers = {"node": "Node", "status": "Status"} 549 550 if command not in _LIST_POWER_COMMANDS: 551 ToStderr("power subcommand %s not supported." % command) 552 return constants.EXIT_FAILURE 553 554 oob_command = "power-%s" % command 555 556 if oob_command in _OOB_COMMAND_ASK: 557 if not args: 558 ToStderr("Please provide at least one node for this command") 559 return constants.EXIT_FAILURE 560 elif not opts.force and not ConfirmOperation(args, "nodes", 561 "power %s" % command): 562 return constants.EXIT_FAILURE 563 assert len(args) > 0 564 565 opcodelist = [] 566 if not opts.ignore_status and oob_command == constants.OOB_POWER_OFF: 567 # TODO: This is a little ugly as we can't catch and revert 568 for node in args: 569 opcodelist.append(opcodes.OpNodeSetParams(node_name=node, offline=True, 570 auto_promote=opts.auto_promote)) 571 572 opcodelist.append(opcodes.OpOobCommand(node_names=args, 573 command=oob_command, 574 ignore_status=opts.ignore_status, 575 timeout=opts.oob_timeout, 576 power_delay=opts.power_delay)) 577 578 cli.SetGenericOpcodeOpts(opcodelist, opts) 579 580 job_id = cli.SendJob(opcodelist) 581 582 # We just want the OOB Opcode status 583 # If it fails PollJob gives us the error message in it 584 result = cli.PollJob(job_id)[-1] 585 586 errs = 0 587 data = [] 588 for node_result in result: 589 (node_tuple, data_tuple) = node_result 590 (_, node_name) = node_tuple 591 (data_status, data_node) = data_tuple 592 if data_status == constants.RS_NORMAL: 593 if oob_command == constants.OOB_POWER_STATUS: 594 if data_node[constants.OOB_POWER_STATUS_POWERED]: 595 text = "powered" 596 else: 597 text = "unpowered" 598 data.append([node_name, text]) 599 else: 600 # We don't expect data here, so we just say, it was successfully invoked 601 data.append([node_name, "invoked"]) 602 else: 603 errs += 1 604 data.append([node_name, cli.FormatResultError(data_status, True)]) 605 606 data = GenerateTable(separator=opts.separator, headers=headers, 607 fields=["node", "status"], data=data) 608 609 for line in data: 610 ToStdout(line) 611 612 if errs: 613 return constants.EXIT_FAILURE 614 else: 615 return constants.EXIT_SUCCESS
616
617 618 -def Health(opts, args):
619 """Show health of a node using OOB. 620 621 @param opts: the command line options selected by the user 622 @type args: list 623 @param args: should contain only one element, the name of 624 the node to be removed 625 @rtype: int 626 @return: the desired exit code 627 628 """ 629 op = opcodes.OpOobCommand(node_names=args, command=constants.OOB_HEALTH, 630 timeout=opts.oob_timeout) 631 result = SubmitOpCode(op, opts=opts) 632 633 if opts.no_headers: 634 headers = None 635 else: 636 headers = {"node": "Node", "status": "Status"} 637 638 errs = 0 639 data = [] 640 for node_result in result: 641 (node_tuple, data_tuple) = node_result 642 (_, node_name) = node_tuple 643 (data_status, data_node) = data_tuple 644 if data_status == constants.RS_NORMAL: 645 data.append([node_name, "%s=%s" % tuple(data_node[0])]) 646 for item, status in data_node[1:]: 647 data.append(["", "%s=%s" % (item, status)]) 648 else: 649 errs += 1 650 data.append([node_name, cli.FormatResultError(data_status, True)]) 651 652 data = GenerateTable(separator=opts.separator, headers=headers, 653 fields=["node", "status"], data=data) 654 655 for line in data: 656 ToStdout(line) 657 658 if errs: 659 return constants.EXIT_FAILURE 660 else: 661 return constants.EXIT_SUCCESS
662
663 664 -def ListVolumes(opts, args):
665 """List logical volumes on node(s). 666 667 @param opts: the command line options selected by the user 668 @type args: list 669 @param args: should either be an empty list, in which case 670 we list data for all nodes, or contain a list of nodes 671 to display data only for those 672 @rtype: int 673 @return: the desired exit code 674 675 """ 676 selected_fields = ParseFields(opts.output, _LIST_VOL_DEF_FIELDS) 677 678 op = opcodes.OpNodeQueryvols(nodes=args, output_fields=selected_fields) 679 output = SubmitOpCode(op, opts=opts) 680 681 if not opts.no_headers: 682 headers = {"node": "Node", "phys": "PhysDev", 683 "vg": "VG", "name": "Name", 684 "size": "Size", "instance": "Instance"} 685 else: 686 headers = None 687 688 unitfields = ["size"] 689 690 numfields = ["size"] 691 692 data = GenerateTable(separator=opts.separator, headers=headers, 693 fields=selected_fields, unitfields=unitfields, 694 numfields=numfields, data=output, units=opts.units) 695 696 for line in data: 697 ToStdout(line) 698 699 return 0
700
701 702 -def ListStorage(opts, args):
703 """List physical volumes on node(s). 704 705 @param opts: the command line options selected by the user 706 @type args: list 707 @param args: should either be an empty list, in which case 708 we list data for all nodes, or contain a list of nodes 709 to display data only for those 710 @rtype: int 711 @return: the desired exit code 712 713 """ 714 # TODO: Default to ST_FILE if LVM is disabled on the cluster 715 if opts.user_storage_type is None: 716 opts.user_storage_type = constants.ST_LVM_PV 717 718 storage_type = ConvertStorageType(opts.user_storage_type) 719 720 selected_fields = ParseFields(opts.output, _LIST_STOR_DEF_FIELDS) 721 722 op = opcodes.OpNodeQueryStorage(nodes=args, 723 storage_type=storage_type, 724 output_fields=selected_fields) 725 output = SubmitOpCode(op, opts=opts) 726 727 if not opts.no_headers: 728 headers = { 729 constants.SF_NODE: "Node", 730 constants.SF_TYPE: "Type", 731 constants.SF_NAME: "Name", 732 constants.SF_SIZE: "Size", 733 constants.SF_USED: "Used", 734 constants.SF_FREE: "Free", 735 constants.SF_ALLOCATABLE: "Allocatable", 736 } 737 else: 738 headers = None 739 740 unitfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE] 741 numfields = [constants.SF_SIZE, constants.SF_USED, constants.SF_FREE] 742 743 # change raw values to nicer strings 744 for row in output: 745 for idx, field in enumerate(selected_fields): 746 val = row[idx] 747 if field == constants.SF_ALLOCATABLE: 748 if val: 749 val = "Y" 750 else: 751 val = "N" 752 row[idx] = str(val) 753 754 data = GenerateTable(separator=opts.separator, headers=headers, 755 fields=selected_fields, unitfields=unitfields, 756 numfields=numfields, data=output, units=opts.units) 757 758 for line in data: 759 ToStdout(line) 760 761 return 0
762
763 764 -def ModifyStorage(opts, args):
765 """Modify storage volume on a node. 766 767 @param opts: the command line options selected by the user 768 @type args: list 769 @param args: should contain 3 items: node name, storage type and volume name 770 @rtype: int 771 @return: the desired exit code 772 773 """ 774 (node_name, user_storage_type, volume_name) = args 775 776 storage_type = ConvertStorageType(user_storage_type) 777 778 changes = {} 779 780 if opts.allocatable is not None: 781 changes[constants.SF_ALLOCATABLE] = opts.allocatable 782 783 if changes: 784 op = opcodes.OpNodeModifyStorage(node_name=node_name, 785 storage_type=storage_type, 786 name=volume_name, 787 changes=changes) 788 SubmitOpCode(op, opts=opts) 789 else: 790 ToStderr("No changes to perform, exiting.")
791
792 793 -def RepairStorage(opts, args):
794 """Repairs a storage volume on a node. 795 796 @param opts: the command line options selected by the user 797 @type args: list 798 @param args: should contain 3 items: node name, storage type and volume name 799 @rtype: int 800 @return: the desired exit code 801 802 """ 803 (node_name, user_storage_type, volume_name) = args 804 805 storage_type = ConvertStorageType(user_storage_type) 806 807 op = opcodes.OpRepairNodeStorage(node_name=node_name, 808 storage_type=storage_type, 809 name=volume_name, 810 ignore_consistency=opts.ignore_consistency) 811 SubmitOpCode(op, opts=opts)
812
813 814 -def SetNodeParams(opts, args):
815 """Modifies a node. 816 817 @param opts: the command line options selected by the user 818 @type args: list 819 @param args: should contain only one element, the node name 820 @rtype: int 821 @return: the desired exit code 822 823 """ 824 all_changes = [opts.master_candidate, opts.drained, opts.offline, 825 opts.master_capable, opts.vm_capable, opts.secondary_ip, 826 opts.ndparams] 827 if all_changes.count(None) == len(all_changes): 828 ToStderr("Please give at least one of the parameters.") 829 return 1 830 831 op = opcodes.OpNodeSetParams(node_name=args[0], 832 master_candidate=opts.master_candidate, 833 offline=opts.offline, 834 drained=opts.drained, 835 master_capable=opts.master_capable, 836 vm_capable=opts.vm_capable, 837 secondary_ip=opts.secondary_ip, 838 force=opts.force, 839 ndparams=opts.ndparams, 840 auto_promote=opts.auto_promote, 841 powered=opts.node_powered) 842 843 # even if here we process the result, we allow submit only 844 result = SubmitOrSend(op, opts) 845 846 if result: 847 ToStdout("Modified node %s", args[0]) 848 for param, data in result: 849 ToStdout(" - %-5s -> %s", param, data) 850 return 0
851 852 853 commands = { 854 "add": ( 855 AddNode, [ArgHost(min=1, max=1)], 856 [SECONDARY_IP_OPT, READD_OPT, NOSSH_KEYCHECK_OPT, NODE_FORCE_JOIN_OPT, 857 NONODE_SETUP_OPT, VERBOSE_OPT, NODEGROUP_OPT, PRIORITY_OPT, 858 CAPAB_MASTER_OPT, CAPAB_VM_OPT, NODE_PARAMS_OPT], 859 "[-s ip] [--readd] [--no-ssh-key-check] [--force-join]" 860 " [--no-node-setup] [--verbose]" 861 " <node_name>", 862 "Add a node to the cluster"), 863 "evacuate": ( 864 EvacuateNode, ARGS_ONE_NODE, 865 [FORCE_OPT, IALLOCATOR_OPT, NEW_SECONDARY_OPT, EARLY_RELEASE_OPT, 866 PRIORITY_OPT, PRIMARY_ONLY_OPT, SECONDARY_ONLY_OPT], 867 "[-f] {-I <iallocator> | -n <dst>} <node>", 868 "Relocate the primary and/or secondary instances from a node"), 869 "failover": ( 870 FailoverNode, ARGS_ONE_NODE, [FORCE_OPT, IGNORE_CONSIST_OPT, 871 IALLOCATOR_OPT, PRIORITY_OPT], 872 "[-f] <node>", 873 "Stops the primary instances on a node and start them on their" 874 " secondary node (only for instances with drbd disk template)"), 875 "migrate": ( 876 MigrateNode, ARGS_ONE_NODE, 877 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, DST_NODE_OPT, 878 IALLOCATOR_OPT, PRIORITY_OPT], 879 "[-f] <node>", 880 "Migrate all the primary instance on a node away from it" 881 " (only for instances of type drbd)"), 882 "info": ( 883 ShowNodeConfig, ARGS_MANY_NODES, [], 884 "[<node_name>...]", "Show information about the node(s)"), 885 "list": ( 886 ListNodes, ARGS_MANY_NODES, 887 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT, 888 FORCE_FILTER_OPT], 889 "[nodes...]", 890 "Lists the nodes in the cluster. The available fields can be shown using" 891 " the \"list-fields\" command (see the man page for details)." 892 " The default field list is (in order): %s." % 893 utils.CommaJoin(_LIST_DEF_FIELDS)), 894 "list-fields": ( 895 ListNodeFields, [ArgUnknown()], 896 [NOHDR_OPT, SEP_OPT], 897 "[fields...]", 898 "Lists all available fields for nodes"), 899 "modify": ( 900 SetNodeParams, ARGS_ONE_NODE, 901 [FORCE_OPT, SUBMIT_OPT, MC_OPT, DRAINED_OPT, OFFLINE_OPT, 902 CAPAB_MASTER_OPT, CAPAB_VM_OPT, SECONDARY_IP_OPT, 903 AUTO_PROMOTE_OPT, DRY_RUN_OPT, PRIORITY_OPT, NODE_PARAMS_OPT, 904 NODE_POWERED_OPT], 905 "<node_name>", "Alters the parameters of a node"), 906 "powercycle": ( 907 PowercycleNode, ARGS_ONE_NODE, 908 [FORCE_OPT, CONFIRM_OPT, DRY_RUN_OPT, PRIORITY_OPT], 909 "<node_name>", "Tries to forcefully powercycle a node"), 910 "power": ( 911 PowerNode, 912 [ArgChoice(min=1, max=1, choices=_LIST_POWER_COMMANDS), 913 ArgNode()], 914 [SUBMIT_OPT, AUTO_PROMOTE_OPT, PRIORITY_OPT, IGNORE_STATUS_OPT, 915 FORCE_OPT, NOHDR_OPT, SEP_OPT, OOB_TIMEOUT_OPT, POWER_DELAY_OPT], 916 "on|off|cycle|status [nodes...]", 917 "Change power state of node by calling out-of-band helper."), 918 "remove": ( 919 RemoveNode, ARGS_ONE_NODE, [DRY_RUN_OPT, PRIORITY_OPT], 920 "<node_name>", "Removes a node from the cluster"), 921 "volumes": ( 922 ListVolumes, [ArgNode()], 923 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, PRIORITY_OPT], 924 "[<node_name>...]", "List logical volumes on node(s)"), 925 "list-storage": ( 926 ListStorage, ARGS_MANY_NODES, 927 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, _STORAGE_TYPE_OPT, 928 PRIORITY_OPT], 929 "[<node_name>...]", "List physical volumes on node(s). The available" 930 " fields are (see the man page for details): %s." % 931 (utils.CommaJoin(_LIST_STOR_HEADERS))), 932 "modify-storage": ( 933 ModifyStorage, 934 [ArgNode(min=1, max=1), 935 ArgChoice(min=1, max=1, choices=_MODIFIABLE_STORAGE_TYPES), 936 ArgFile(min=1, max=1)], 937 [ALLOCATABLE_OPT, DRY_RUN_OPT, PRIORITY_OPT], 938 "<node_name> <storage_type> <name>", "Modify storage volume on a node"), 939 "repair-storage": ( 940 RepairStorage, 941 [ArgNode(min=1, max=1), 942 ArgChoice(min=1, max=1, choices=_REPAIRABLE_STORAGE_TYPES), 943 ArgFile(min=1, max=1)], 944 [IGNORE_CONSIST_OPT, DRY_RUN_OPT, PRIORITY_OPT], 945 "<node_name> <storage_type> <name>", 946 "Repairs a storage volume on a node"), 947 "list-tags": ( 948 ListTags, ARGS_ONE_NODE, [], 949 "<node_name>", "List the tags of the given node"), 950 "add-tags": ( 951 AddTags, [ArgNode(min=1, max=1), ArgUnknown()], [TAG_SRC_OPT, PRIORITY_OPT], 952 "<node_name> tag...", "Add tags to the given node"), 953 "remove-tags": ( 954 RemoveTags, [ArgNode(min=1, max=1), ArgUnknown()], 955 [TAG_SRC_OPT, PRIORITY_OPT], 956 "<node_name> tag...", "Remove tags from the given node"), 957 "health": ( 958 Health, ARGS_MANY_NODES, 959 [NOHDR_OPT, SEP_OPT, SUBMIT_OPT, PRIORITY_OPT, OOB_TIMEOUT_OPT], 960 "[<node_name>...]", "List health of node(s) using out-of-band"), 961 }
962 963 964 -def Main():
965 return GenericMain(commands, override={"tag_type": constants.TAG_NODE})
966