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