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

Source Code for Module ganeti.client.gnt_instance

   1  # 
   2  # 
   3   
   4  # Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011 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  """Instance related commands""" 
  22   
  23  # pylint: disable=W0401,W0614,C0103 
  24  # W0401: Wildcard import ganeti.cli 
  25  # W0614: Unused import %s from wildcard import (since we need cli) 
  26  # C0103: Invalid name gnt-instance 
  27   
  28  import itertools 
  29  import simplejson 
  30  import logging 
  31  from cStringIO import StringIO 
  32   
  33  from ganeti.cli import * 
  34  from ganeti import opcodes 
  35  from ganeti import constants 
  36  from ganeti import compat 
  37  from ganeti import utils 
  38  from ganeti import errors 
  39  from ganeti import netutils 
  40  from ganeti import ssh 
  41  from ganeti import objects 
  42   
  43   
  44  _EXPAND_CLUSTER = "cluster" 
  45  _EXPAND_NODES_BOTH = "nodes" 
  46  _EXPAND_NODES_PRI = "nodes-pri" 
  47  _EXPAND_NODES_SEC = "nodes-sec" 
  48  _EXPAND_NODES_BOTH_BY_TAGS = "nodes-by-tags" 
  49  _EXPAND_NODES_PRI_BY_TAGS = "nodes-pri-by-tags" 
  50  _EXPAND_NODES_SEC_BY_TAGS = "nodes-sec-by-tags" 
  51  _EXPAND_INSTANCES = "instances" 
  52  _EXPAND_INSTANCES_BY_TAGS = "instances-by-tags" 
  53   
  54  _EXPAND_NODES_TAGS_MODES = frozenset([ 
  55    _EXPAND_NODES_BOTH_BY_TAGS, 
  56    _EXPAND_NODES_PRI_BY_TAGS, 
  57    _EXPAND_NODES_SEC_BY_TAGS, 
  58    ]) 
  59   
  60   
  61  #: default list of options for L{ListInstances} 
  62  _LIST_DEF_FIELDS = [ 
  63    "name", "hypervisor", "os", "pnode", "status", "oper_ram", 
  64    ] 
  65   
  66   
67 -def _ExpandMultiNames(mode, names, client=None):
68 """Expand the given names using the passed mode. 69 70 For _EXPAND_CLUSTER, all instances will be returned. For 71 _EXPAND_NODES_PRI/SEC, all instances having those nodes as 72 primary/secondary will be returned. For _EXPAND_NODES_BOTH, all 73 instances having those nodes as either primary or secondary will be 74 returned. For _EXPAND_INSTANCES, the given instances will be 75 returned. 76 77 @param mode: one of L{_EXPAND_CLUSTER}, L{_EXPAND_NODES_BOTH}, 78 L{_EXPAND_NODES_PRI}, L{_EXPAND_NODES_SEC} or 79 L{_EXPAND_INSTANCES} 80 @param names: a list of names; for cluster, it must be empty, 81 and for node and instance it must be a list of valid item 82 names (short names are valid as usual, e.g. node1 instead of 83 node1.example.com) 84 @rtype: list 85 @return: the list of names after the expansion 86 @raise errors.ProgrammerError: for unknown selection type 87 @raise errors.OpPrereqError: for invalid input parameters 88 89 """ 90 # pylint: disable=W0142 91 92 if client is None: 93 client = GetClient() 94 if mode == _EXPAND_CLUSTER: 95 if names: 96 raise errors.OpPrereqError("Cluster filter mode takes no arguments", 97 errors.ECODE_INVAL) 98 idata = client.QueryInstances([], ["name"], False) 99 inames = [row[0] for row in idata] 100 101 elif (mode in _EXPAND_NODES_TAGS_MODES or 102 mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_PRI, _EXPAND_NODES_SEC)): 103 if mode in _EXPAND_NODES_TAGS_MODES: 104 if not names: 105 raise errors.OpPrereqError("No node tags passed", errors.ECODE_INVAL) 106 ndata = client.QueryNodes([], ["name", "pinst_list", 107 "sinst_list", "tags"], False) 108 ndata = [row for row in ndata if set(row[3]).intersection(names)] 109 else: 110 if not names: 111 raise errors.OpPrereqError("No node names passed", errors.ECODE_INVAL) 112 ndata = client.QueryNodes(names, ["name", "pinst_list", "sinst_list"], 113 False) 114 115 ipri = [row[1] for row in ndata] 116 pri_names = list(itertools.chain(*ipri)) 117 isec = [row[2] for row in ndata] 118 sec_names = list(itertools.chain(*isec)) 119 if mode in (_EXPAND_NODES_BOTH, _EXPAND_NODES_BOTH_BY_TAGS): 120 inames = pri_names + sec_names 121 elif mode in (_EXPAND_NODES_PRI, _EXPAND_NODES_PRI_BY_TAGS): 122 inames = pri_names 123 elif mode in (_EXPAND_NODES_SEC, _EXPAND_NODES_SEC_BY_TAGS): 124 inames = sec_names 125 else: 126 raise errors.ProgrammerError("Unhandled shutdown type") 127 elif mode == _EXPAND_INSTANCES: 128 if not names: 129 raise errors.OpPrereqError("No instance names passed", 130 errors.ECODE_INVAL) 131 idata = client.QueryInstances(names, ["name"], False) 132 inames = [row[0] for row in idata] 133 elif mode == _EXPAND_INSTANCES_BY_TAGS: 134 if not names: 135 raise errors.OpPrereqError("No instance tags passed", 136 errors.ECODE_INVAL) 137 idata = client.QueryInstances([], ["name", "tags"], False) 138 inames = [row[0] for row in idata if set(row[1]).intersection(names)] 139 else: 140 raise errors.OpPrereqError("Unknown mode '%s'" % mode, errors.ECODE_INVAL) 141 142 return inames
143 144
145 -def _EnsureInstancesExist(client, names):
146 """Check for and ensure the given instance names exist. 147 148 This function will raise an OpPrereqError in case they don't 149 exist. Otherwise it will exit cleanly. 150 151 @type client: L{ganeti.luxi.Client} 152 @param client: the client to use for the query 153 @type names: list 154 @param names: the list of instance names to query 155 @raise errors.OpPrereqError: in case any instance is missing 156 157 """ 158 # TODO: change LUInstanceQuery to that it actually returns None 159 # instead of raising an exception, or devise a better mechanism 160 result = client.QueryInstances(names, ["name"], False) 161 for orig_name, row in zip(names, result): 162 if row[0] is None: 163 raise errors.OpPrereqError("Instance '%s' does not exist" % orig_name, 164 errors.ECODE_NOENT)
165 166
167 -def GenericManyOps(operation, fn):
168 """Generic multi-instance operations. 169 170 The will return a wrapper that processes the options and arguments 171 given, and uses the passed function to build the opcode needed for 172 the specific operation. Thus all the generic loop/confirmation code 173 is abstracted into this function. 174 175 """ 176 def realfn(opts, args): 177 if opts.multi_mode is None: 178 opts.multi_mode = _EXPAND_INSTANCES 179 cl = GetClient() 180 inames = _ExpandMultiNames(opts.multi_mode, args, client=cl) 181 if not inames: 182 if opts.multi_mode == _EXPAND_CLUSTER: 183 ToStdout("Cluster is empty, no instances to shutdown") 184 return 0 185 raise errors.OpPrereqError("Selection filter does not match" 186 " any instances", errors.ECODE_INVAL) 187 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1 188 if not (opts.force_multi or not multi_on 189 or ConfirmOperation(inames, "instances", operation)): 190 return 1 191 jex = JobExecutor(verbose=multi_on, cl=cl, opts=opts) 192 for name in inames: 193 op = fn(name, opts) 194 jex.QueueJob(name, op) 195 results = jex.WaitOrShow(not opts.submit_only) 196 rcode = compat.all(row[0] for row in results) 197 return int(not rcode)
198 return realfn 199 200
201 -def ListInstances(opts, args):
202 """List instances and their properties. 203 204 @param opts: the command line options selected by the user 205 @type args: list 206 @param args: should be an empty list 207 @rtype: int 208 @return: the desired exit code 209 210 """ 211 selected_fields = ParseFields(opts.output, _LIST_DEF_FIELDS) 212 213 fmtoverride = dict.fromkeys(["tags", "disk.sizes", "nic.macs", "nic.ips", 214 "nic.modes", "nic.links", "nic.bridges", 215 "snodes", "snodes.group", "snodes.group.uuid"], 216 (lambda value: ",".join(str(item) 217 for item in value), 218 False)) 219 220 return GenericList(constants.QR_INSTANCE, selected_fields, args, opts.units, 221 opts.separator, not opts.no_headers, 222 format_override=fmtoverride, verbose=opts.verbose, 223 force_filter=opts.force_filter)
224 225
226 -def ListInstanceFields(opts, args):
227 """List instance fields. 228 229 @param opts: the command line options selected by the user 230 @type args: list 231 @param args: fields to list, or empty for all 232 @rtype: int 233 @return: the desired exit code 234 235 """ 236 return GenericListFields(constants.QR_INSTANCE, args, opts.separator, 237 not opts.no_headers)
238 239
240 -def AddInstance(opts, args):
241 """Add an instance to the cluster. 242 243 This is just a wrapper over GenericInstanceCreate. 244 245 """ 246 return GenericInstanceCreate(constants.INSTANCE_CREATE, opts, args)
247 248
249 -def BatchCreate(opts, args):
250 """Create instances using a definition file. 251 252 This function reads a json file with instances defined 253 in the form:: 254 255 {"instance-name":{ 256 "disk_size": [20480], 257 "template": "drbd", 258 "backend": { 259 "memory": 512, 260 "vcpus": 1 }, 261 "os": "debootstrap", 262 "primary_node": "firstnode", 263 "secondary_node": "secondnode", 264 "iallocator": "dumb"} 265 } 266 267 Note that I{primary_node} and I{secondary_node} have precedence over 268 I{iallocator}. 269 270 @param opts: the command line options selected by the user 271 @type args: list 272 @param args: should contain one element, the json filename 273 @rtype: int 274 @return: the desired exit code 275 276 """ 277 _DEFAULT_SPECS = {"disk_size": [20 * 1024], 278 "backend": {}, 279 "iallocator": None, 280 "primary_node": None, 281 "secondary_node": None, 282 "nics": None, 283 "start": True, 284 "ip_check": True, 285 "name_check": True, 286 "hypervisor": None, 287 "hvparams": {}, 288 "file_storage_dir": None, 289 "force_variant": False, 290 "file_driver": "loop"} 291 292 def _PopulateWithDefaults(spec): 293 """Returns a new hash combined with default values.""" 294 mydict = _DEFAULT_SPECS.copy() 295 mydict.update(spec) 296 return mydict
297 298 def _Validate(spec): 299 """Validate the instance specs.""" 300 # Validate fields required under any circumstances 301 for required_field in ("os", "template"): 302 if required_field not in spec: 303 raise errors.OpPrereqError('Required field "%s" is missing.' % 304 required_field, errors.ECODE_INVAL) 305 # Validate special fields 306 if spec["primary_node"] is not None: 307 if (spec["template"] in constants.DTS_INT_MIRROR and 308 spec["secondary_node"] is None): 309 raise errors.OpPrereqError("Template requires secondary node, but" 310 " there was no secondary provided.", 311 errors.ECODE_INVAL) 312 elif spec["iallocator"] is None: 313 raise errors.OpPrereqError("You have to provide at least a primary_node" 314 " or an iallocator.", 315 errors.ECODE_INVAL) 316 317 if (spec["hvparams"] and 318 not isinstance(spec["hvparams"], dict)): 319 raise errors.OpPrereqError("Hypervisor parameters must be a dict.", 320 errors.ECODE_INVAL) 321 322 json_filename = args[0] 323 try: 324 instance_data = simplejson.loads(utils.ReadFile(json_filename)) 325 except Exception, err: # pylint: disable=W0703 326 ToStderr("Can't parse the instance definition file: %s" % str(err)) 327 return 1 328 329 if not isinstance(instance_data, dict): 330 ToStderr("The instance definition file is not in dict format.") 331 return 1 332 333 jex = JobExecutor(opts=opts) 334 335 # Iterate over the instances and do: 336 # * Populate the specs with default value 337 # * Validate the instance specs 338 i_names = utils.NiceSort(instance_data.keys()) # pylint: disable=E1103 339 for name in i_names: 340 specs = instance_data[name] 341 specs = _PopulateWithDefaults(specs) 342 _Validate(specs) 343 344 hypervisor = specs["hypervisor"] 345 hvparams = specs["hvparams"] 346 347 disks = [] 348 for elem in specs["disk_size"]: 349 try: 350 size = utils.ParseUnit(elem) 351 except (TypeError, ValueError), err: 352 raise errors.OpPrereqError("Invalid disk size '%s' for" 353 " instance %s: %s" % 354 (elem, name, err), errors.ECODE_INVAL) 355 disks.append({"size": size}) 356 357 utils.ForceDictType(specs["backend"], constants.BES_PARAMETER_TYPES) 358 utils.ForceDictType(hvparams, constants.HVS_PARAMETER_TYPES) 359 360 tmp_nics = [] 361 for field in constants.INIC_PARAMS: 362 if field in specs: 363 if not tmp_nics: 364 tmp_nics.append({}) 365 tmp_nics[0][field] = specs[field] 366 367 if specs["nics"] is not None and tmp_nics: 368 raise errors.OpPrereqError("'nics' list incompatible with using" 369 " individual nic fields as well", 370 errors.ECODE_INVAL) 371 elif specs["nics"] is not None: 372 tmp_nics = specs["nics"] 373 elif not tmp_nics: 374 tmp_nics = [{}] 375 376 op = opcodes.OpInstanceCreate(instance_name=name, 377 disks=disks, 378 disk_template=specs["template"], 379 mode=constants.INSTANCE_CREATE, 380 os_type=specs["os"], 381 force_variant=specs["force_variant"], 382 pnode=specs["primary_node"], 383 snode=specs["secondary_node"], 384 nics=tmp_nics, 385 start=specs["start"], 386 ip_check=specs["ip_check"], 387 name_check=specs["name_check"], 388 wait_for_sync=True, 389 iallocator=specs["iallocator"], 390 hypervisor=hypervisor, 391 hvparams=hvparams, 392 beparams=specs["backend"], 393 file_storage_dir=specs["file_storage_dir"], 394 file_driver=specs["file_driver"]) 395 396 jex.QueueJob(name, op) 397 # we never want to wait, just show the submitted job IDs 398 jex.WaitOrShow(False) 399 400 return 0 401 402
403 -def ReinstallInstance(opts, args):
404 """Reinstall an instance. 405 406 @param opts: the command line options selected by the user 407 @type args: list 408 @param args: should contain only one element, the name of the 409 instance to be reinstalled 410 @rtype: int 411 @return: the desired exit code 412 413 """ 414 # first, compute the desired name list 415 if opts.multi_mode is None: 416 opts.multi_mode = _EXPAND_INSTANCES 417 418 inames = _ExpandMultiNames(opts.multi_mode, args) 419 if not inames: 420 raise errors.OpPrereqError("Selection filter does not match any instances", 421 errors.ECODE_INVAL) 422 423 # second, if requested, ask for an OS 424 if opts.select_os is True: 425 op = opcodes.OpOsDiagnose(output_fields=["name", "variants"], names=[]) 426 result = SubmitOpCode(op, opts=opts) 427 428 if not result: 429 ToStdout("Can't get the OS list") 430 return 1 431 432 ToStdout("Available OS templates:") 433 number = 0 434 choices = [] 435 for (name, variants) in result: 436 for entry in CalculateOSNames(name, variants): 437 ToStdout("%3s: %s", number, entry) 438 choices.append(("%s" % number, entry, entry)) 439 number += 1 440 441 choices.append(("x", "exit", "Exit gnt-instance reinstall")) 442 selected = AskUser("Enter OS template number (or x to abort):", 443 choices) 444 445 if selected == "exit": 446 ToStderr("User aborted reinstall, exiting") 447 return 1 448 449 os_name = selected 450 os_msg = "change the OS to '%s'" % selected 451 else: 452 os_name = opts.os 453 if opts.os is not None: 454 os_msg = "change the OS to '%s'" % os_name 455 else: 456 os_msg = "keep the same OS" 457 458 # third, get confirmation: multi-reinstall requires --force-multi, 459 # single-reinstall either --force or --force-multi (--force-multi is 460 # a stronger --force) 461 multi_on = opts.multi_mode != _EXPAND_INSTANCES or len(inames) > 1 462 if multi_on: 463 warn_msg = ("Note: this will remove *all* data for the" 464 " below instances! It will %s.\n" % os_msg) 465 if not (opts.force_multi or 466 ConfirmOperation(inames, "instances", "reinstall", extra=warn_msg)): 467 return 1 468 else: 469 if not (opts.force or opts.force_multi): 470 usertext = ("This will reinstall the instance '%s' (and %s) which" 471 " removes all data. Continue?") % (inames[0], os_msg) 472 if not AskUser(usertext): 473 return 1 474 475 jex = JobExecutor(verbose=multi_on, opts=opts) 476 for instance_name in inames: 477 op = opcodes.OpInstanceReinstall(instance_name=instance_name, 478 os_type=os_name, 479 force_variant=opts.force_variant, 480 osparams=opts.osparams) 481 jex.QueueJob(instance_name, op) 482 483 jex.WaitOrShow(not opts.submit_only) 484 return 0
485 486
487 -def RemoveInstance(opts, args):
488 """Remove an instance. 489 490 @param opts: the command line options selected by the user 491 @type args: list 492 @param args: should contain only one element, the name of 493 the instance to be removed 494 @rtype: int 495 @return: the desired exit code 496 497 """ 498 instance_name = args[0] 499 force = opts.force 500 cl = GetClient() 501 502 if not force: 503 _EnsureInstancesExist(cl, [instance_name]) 504 505 usertext = ("This will remove the volumes of the instance %s" 506 " (including mirrors), thus removing all the data" 507 " of the instance. Continue?") % instance_name 508 if not AskUser(usertext): 509 return 1 510 511 op = opcodes.OpInstanceRemove(instance_name=instance_name, 512 ignore_failures=opts.ignore_failures, 513 shutdown_timeout=opts.shutdown_timeout) 514 SubmitOrSend(op, opts, cl=cl) 515 return 0
516 517
518 -def RenameInstance(opts, args):
519 """Rename an instance. 520 521 @param opts: the command line options selected by the user 522 @type args: list 523 @param args: should contain two elements, the old and the 524 new instance names 525 @rtype: int 526 @return: the desired exit code 527 528 """ 529 if not opts.name_check: 530 if not AskUser("As you disabled the check of the DNS entry, please verify" 531 " that '%s' is a FQDN. Continue?" % args[1]): 532 return 1 533 534 op = opcodes.OpInstanceRename(instance_name=args[0], 535 new_name=args[1], 536 ip_check=opts.ip_check, 537 name_check=opts.name_check) 538 result = SubmitOrSend(op, opts) 539 540 if result: 541 ToStdout("Instance '%s' renamed to '%s'", args[0], result) 542 543 return 0
544 545
546 -def ActivateDisks(opts, args):
547 """Activate an instance's disks. 548 549 This serves two purposes: 550 - it allows (as long as the instance is not running) 551 mounting the disks and modifying them from the node 552 - it repairs inactive secondary drbds 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 instance name 557 @rtype: int 558 @return: the desired exit code 559 560 """ 561 instance_name = args[0] 562 op = opcodes.OpInstanceActivateDisks(instance_name=instance_name, 563 ignore_size=opts.ignore_size) 564 disks_info = SubmitOrSend(op, opts) 565 for host, iname, nname in disks_info: 566 ToStdout("%s:%s:%s", host, iname, nname) 567 return 0
568 569
570 -def DeactivateDisks(opts, args):
571 """Deactivate an instance's disks. 572 573 This function takes the instance name, looks for its primary node 574 and the tries to shutdown its block devices on that node. 575 576 @param opts: the command line options selected by the user 577 @type args: list 578 @param args: should contain only one element, the instance name 579 @rtype: int 580 @return: the desired exit code 581 582 """ 583 instance_name = args[0] 584 op = opcodes.OpInstanceDeactivateDisks(instance_name=instance_name, 585 force=opts.force) 586 SubmitOrSend(op, opts) 587 return 0
588 589
590 -def RecreateDisks(opts, args):
591 """Recreate an instance's disks. 592 593 @param opts: the command line options selected by the user 594 @type args: list 595 @param args: should contain only one element, the instance name 596 @rtype: int 597 @return: the desired exit code 598 599 """ 600 instance_name = args[0] 601 if opts.disks: 602 try: 603 opts.disks = [int(v) for v in opts.disks.split(",")] 604 except (ValueError, TypeError), err: 605 ToStderr("Invalid disks value: %s" % str(err)) 606 return 1 607 else: 608 opts.disks = [] 609 610 if opts.node: 611 pnode, snode = SplitNodeOption(opts.node) 612 nodes = [pnode] 613 if snode is not None: 614 nodes.append(snode) 615 else: 616 nodes = [] 617 618 op = opcodes.OpInstanceRecreateDisks(instance_name=instance_name, 619 disks=opts.disks, 620 nodes=nodes) 621 SubmitOrSend(op, opts) 622 return 0
623 624
625 -def GrowDisk(opts, args):
626 """Grow an instance's disks. 627 628 @param opts: the command line options selected by the user 629 @type args: list 630 @param args: should contain two elements, the instance name 631 whose disks we grow and the disk name, e.g. I{sda} 632 @rtype: int 633 @return: the desired exit code 634 635 """ 636 instance = args[0] 637 disk = args[1] 638 try: 639 disk = int(disk) 640 except (TypeError, ValueError), err: 641 raise errors.OpPrereqError("Invalid disk index: %s" % str(err), 642 errors.ECODE_INVAL) 643 try: 644 amount = utils.ParseUnit(args[2]) 645 except errors.UnitParseError: 646 raise errors.OpPrereqError("Can't parse the given amount '%s'" % args[2], 647 errors.ECODE_INVAL) 648 op = opcodes.OpInstanceGrowDisk(instance_name=instance, 649 disk=disk, amount=amount, 650 wait_for_sync=opts.wait_for_sync) 651 SubmitOrSend(op, opts) 652 return 0
653 654
655 -def _StartupInstance(name, opts):
656 """Startup instances. 657 658 This returns the opcode to start an instance, and its decorator will 659 wrap this into a loop starting all desired instances. 660 661 @param name: the name of the instance to act on 662 @param opts: the command line options selected by the user 663 @return: the opcode needed for the operation 664 665 """ 666 op = opcodes.OpInstanceStartup(instance_name=name, 667 force=opts.force, 668 ignore_offline_nodes=opts.ignore_offline, 669 no_remember=opts.no_remember, 670 startup_paused=opts.startup_paused) 671 # do not add these parameters to the opcode unless they're defined 672 if opts.hvparams: 673 op.hvparams = opts.hvparams 674 if opts.beparams: 675 op.beparams = opts.beparams 676 return op
677 678
679 -def _RebootInstance(name, opts):
680 """Reboot instance(s). 681 682 This returns the opcode to reboot an instance, and its decorator 683 will wrap this into a loop rebooting all desired instances. 684 685 @param name: the name of the instance to act on 686 @param opts: the command line options selected by the user 687 @return: the opcode needed for the operation 688 689 """ 690 return opcodes.OpInstanceReboot(instance_name=name, 691 reboot_type=opts.reboot_type, 692 ignore_secondaries=opts.ignore_secondaries, 693 shutdown_timeout=opts.shutdown_timeout)
694 695
696 -def _ShutdownInstance(name, opts):
697 """Shutdown an instance. 698 699 This returns the opcode to shutdown an instance, and its decorator 700 will wrap this into a loop shutting down all desired instances. 701 702 @param name: the name of the instance to act on 703 @param opts: the command line options selected by the user 704 @return: the opcode needed for the operation 705 706 """ 707 return opcodes.OpInstanceShutdown(instance_name=name, 708 timeout=opts.timeout, 709 ignore_offline_nodes=opts.ignore_offline, 710 no_remember=opts.no_remember)
711 712
713 -def ReplaceDisks(opts, args):
714 """Replace the disks of an instance 715 716 @param opts: the command line options selected by the user 717 @type args: list 718 @param args: should contain only one element, the instance name 719 @rtype: int 720 @return: the desired exit code 721 722 """ 723 new_2ndary = opts.dst_node 724 iallocator = opts.iallocator 725 if opts.disks is None: 726 disks = [] 727 else: 728 try: 729 disks = [int(i) for i in opts.disks.split(",")] 730 except (TypeError, ValueError), err: 731 raise errors.OpPrereqError("Invalid disk index passed: %s" % str(err), 732 errors.ECODE_INVAL) 733 cnt = [opts.on_primary, opts.on_secondary, opts.auto, 734 new_2ndary is not None, iallocator is not None].count(True) 735 if cnt != 1: 736 raise errors.OpPrereqError("One and only one of the -p, -s, -a, -n and -I" 737 " options must be passed", errors.ECODE_INVAL) 738 elif opts.on_primary: 739 mode = constants.REPLACE_DISK_PRI 740 elif opts.on_secondary: 741 mode = constants.REPLACE_DISK_SEC 742 elif opts.auto: 743 mode = constants.REPLACE_DISK_AUTO 744 if disks: 745 raise errors.OpPrereqError("Cannot specify disks when using automatic" 746 " mode", errors.ECODE_INVAL) 747 elif new_2ndary is not None or iallocator is not None: 748 # replace secondary 749 mode = constants.REPLACE_DISK_CHG 750 751 op = opcodes.OpInstanceReplaceDisks(instance_name=args[0], disks=disks, 752 remote_node=new_2ndary, mode=mode, 753 iallocator=iallocator, 754 early_release=opts.early_release) 755 SubmitOrSend(op, opts) 756 return 0
757 758
759 -def FailoverInstance(opts, args):
760 """Failover an instance. 761 762 The failover is done by shutting it down on its present node and 763 starting it on the secondary. 764 765 @param opts: the command line options selected by the user 766 @type args: list 767 @param args: should contain only one element, the instance name 768 @rtype: int 769 @return: the desired exit code 770 771 """ 772 cl = GetClient() 773 instance_name = args[0] 774 force = opts.force 775 iallocator = opts.iallocator 776 target_node = opts.dst_node 777 778 if iallocator and target_node: 779 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target" 780 " node (-n) but not both", errors.ECODE_INVAL) 781 782 if not force: 783 _EnsureInstancesExist(cl, [instance_name]) 784 785 usertext = ("Failover will happen to image %s." 786 " This requires a shutdown of the instance. Continue?" % 787 (instance_name,)) 788 if not AskUser(usertext): 789 return 1 790 791 op = opcodes.OpInstanceFailover(instance_name=instance_name, 792 ignore_consistency=opts.ignore_consistency, 793 shutdown_timeout=opts.shutdown_timeout, 794 iallocator=iallocator, 795 target_node=target_node) 796 SubmitOrSend(op, opts, cl=cl) 797 return 0
798 799
800 -def MigrateInstance(opts, args):
801 """Migrate an instance. 802 803 The migrate is done without shutdown. 804 805 @param opts: the command line options selected by the user 806 @type args: list 807 @param args: should contain only one element, the instance name 808 @rtype: int 809 @return: the desired exit code 810 811 """ 812 cl = GetClient() 813 instance_name = args[0] 814 force = opts.force 815 iallocator = opts.iallocator 816 target_node = opts.dst_node 817 818 if iallocator and target_node: 819 raise errors.OpPrereqError("Specify either an iallocator (-I), or a target" 820 " node (-n) but not both", errors.ECODE_INVAL) 821 822 if not force: 823 _EnsureInstancesExist(cl, [instance_name]) 824 825 if opts.cleanup: 826 usertext = ("Instance %s will be recovered from a failed migration." 827 " Note that the migration procedure (including cleanup)" % 828 (instance_name,)) 829 else: 830 usertext = ("Instance %s will be migrated. Note that migration" % 831 (instance_name,)) 832 usertext += (" might impact the instance if anything goes wrong" 833 " (e.g. due to bugs in the hypervisor). Continue?") 834 if not AskUser(usertext): 835 return 1 836 837 # this should be removed once --non-live is deprecated 838 if not opts.live and opts.migration_mode is not None: 839 raise errors.OpPrereqError("Only one of the --non-live and " 840 "--migration-mode options can be passed", 841 errors.ECODE_INVAL) 842 if not opts.live: # --non-live passed 843 mode = constants.HT_MIGRATION_NONLIVE 844 else: 845 mode = opts.migration_mode 846 847 op = opcodes.OpInstanceMigrate(instance_name=instance_name, mode=mode, 848 cleanup=opts.cleanup, iallocator=iallocator, 849 target_node=target_node, 850 allow_failover=opts.allow_failover) 851 SubmitOpCode(op, cl=cl, opts=opts) 852 return 0
853 854
855 -def MoveInstance(opts, args):
856 """Move an instance. 857 858 @param opts: the command line options selected by the user 859 @type args: list 860 @param args: should contain only one element, the instance name 861 @rtype: int 862 @return: the desired exit code 863 864 """ 865 cl = GetClient() 866 instance_name = args[0] 867 force = opts.force 868 869 if not force: 870 usertext = ("Instance %s will be moved." 871 " This requires a shutdown of the instance. Continue?" % 872 (instance_name,)) 873 if not AskUser(usertext): 874 return 1 875 876 op = opcodes.OpInstanceMove(instance_name=instance_name, 877 target_node=opts.node, 878 shutdown_timeout=opts.shutdown_timeout, 879 ignore_consistency=opts.ignore_consistency) 880 SubmitOrSend(op, opts, cl=cl) 881 return 0
882 883
884 -def ConnectToInstanceConsole(opts, args):
885 """Connect to the console of an instance. 886 887 @param opts: the command line options selected by the user 888 @type args: list 889 @param args: should contain only one element, the instance name 890 @rtype: int 891 @return: the desired exit code 892 893 """ 894 instance_name = args[0] 895 896 cl = GetClient() 897 try: 898 cluster_name = cl.QueryConfigValues(["cluster_name"])[0] 899 ((console_data, oper_state), ) = \ 900 cl.QueryInstances([instance_name], ["console", "oper_state"], False) 901 finally: 902 # Ensure client connection is closed while external commands are run 903 cl.Close() 904 905 del cl 906 907 if not console_data: 908 if oper_state: 909 # Instance is running 910 raise errors.OpExecError("Console information for instance %s is" 911 " unavailable" % instance_name) 912 else: 913 raise errors.OpExecError("Instance %s is not running, can't get console" % 914 instance_name) 915 916 return _DoConsole(objects.InstanceConsole.FromDict(console_data), 917 opts.show_command, cluster_name)
918 919
920 -def _DoConsole(console, show_command, cluster_name, feedback_fn=ToStdout, 921 _runcmd_fn=utils.RunCmd):
922 """Acts based on the result of L{opcodes.OpInstanceConsole}. 923 924 @type console: L{objects.InstanceConsole} 925 @param console: Console object 926 @type show_command: bool 927 @param show_command: Whether to just display commands 928 @type cluster_name: string 929 @param cluster_name: Cluster name as retrieved from master daemon 930 931 """ 932 assert console.Validate() 933 934 if console.kind == constants.CONS_MESSAGE: 935 feedback_fn(console.message) 936 elif console.kind == constants.CONS_VNC: 937 feedback_fn("Instance %s has VNC listening on %s:%s (display %s)," 938 " URL <vnc://%s:%s/>", 939 console.instance, console.host, console.port, 940 console.display, console.host, console.port) 941 elif console.kind == constants.CONS_SSH: 942 # Convert to string if not already one 943 if isinstance(console.command, basestring): 944 cmd = console.command 945 else: 946 cmd = utils.ShellQuoteArgs(console.command) 947 948 srun = ssh.SshRunner(cluster_name=cluster_name) 949 ssh_cmd = srun.BuildCmd(console.host, console.user, cmd, 950 batch=True, quiet=False, tty=True) 951 952 if show_command: 953 feedback_fn(utils.ShellQuoteArgs(ssh_cmd)) 954 else: 955 result = _runcmd_fn(ssh_cmd, interactive=True) 956 if result.failed: 957 logging.error("Console command \"%s\" failed with reason '%s' and" 958 " output %r", result.cmd, result.fail_reason, 959 result.output) 960 raise errors.OpExecError("Connection to console of instance %s failed," 961 " please check cluster configuration" % 962 console.instance) 963 else: 964 raise errors.GenericError("Unknown console type '%s'" % console.kind) 965 966 return constants.EXIT_SUCCESS
967 968
969 -def _FormatLogicalID(dev_type, logical_id, roman):
970 """Formats the logical_id of a disk. 971 972 """ 973 if dev_type == constants.LD_DRBD8: 974 node_a, node_b, port, minor_a, minor_b, key = logical_id 975 data = [ 976 ("nodeA", "%s, minor=%s" % (node_a, compat.TryToRoman(minor_a, 977 convert=roman))), 978 ("nodeB", "%s, minor=%s" % (node_b, compat.TryToRoman(minor_b, 979 convert=roman))), 980 ("port", compat.TryToRoman(port, convert=roman)), 981 ("auth key", key), 982 ] 983 elif dev_type == constants.LD_LV: 984 vg_name, lv_name = logical_id 985 data = ["%s/%s" % (vg_name, lv_name)] 986 else: 987 data = [str(logical_id)] 988 989 return data
990 991
992 -def _FormatBlockDevInfo(idx, top_level, dev, roman):
993 """Show block device information. 994 995 This is only used by L{ShowInstanceConfig}, but it's too big to be 996 left for an inline definition. 997 998 @type idx: int 999 @param idx: the index of the current disk 1000 @type top_level: boolean 1001 @param top_level: if this a top-level disk? 1002 @type dev: dict 1003 @param dev: dictionary with disk information 1004 @type roman: boolean 1005 @param roman: whether to try to use roman integers 1006 @return: a list of either strings, tuples or lists 1007 (which should be formatted at a higher indent level) 1008 1009 """ 1010 def helper(dtype, status): 1011 """Format one line for physical device status. 1012 1013 @type dtype: str 1014 @param dtype: a constant from the L{constants.LDS_BLOCK} set 1015 @type status: tuple 1016 @param status: a tuple as returned from L{backend.FindBlockDevice} 1017 @return: the string representing the status 1018 1019 """ 1020 if not status: 1021 return "not active" 1022 txt = "" 1023 (path, major, minor, syncp, estt, degr, ldisk_status) = status 1024 if major is None: 1025 major_string = "N/A" 1026 else: 1027 major_string = str(compat.TryToRoman(major, convert=roman)) 1028 1029 if minor is None: 1030 minor_string = "N/A" 1031 else: 1032 minor_string = str(compat.TryToRoman(minor, convert=roman)) 1033 1034 txt += ("%s (%s:%s)" % (path, major_string, minor_string)) 1035 if dtype in (constants.LD_DRBD8, ): 1036 if syncp is not None: 1037 sync_text = "*RECOVERING* %5.2f%%," % syncp 1038 if estt: 1039 sync_text += " ETA %ss" % compat.TryToRoman(estt, convert=roman) 1040 else: 1041 sync_text += " ETA unknown" 1042 else: 1043 sync_text = "in sync" 1044 if degr: 1045 degr_text = "*DEGRADED*" 1046 else: 1047 degr_text = "ok" 1048 if ldisk_status == constants.LDS_FAULTY: 1049 ldisk_text = " *MISSING DISK*" 1050 elif ldisk_status == constants.LDS_UNKNOWN: 1051 ldisk_text = " *UNCERTAIN STATE*" 1052 else: 1053 ldisk_text = "" 1054 txt += (" %s, status %s%s" % (sync_text, degr_text, ldisk_text)) 1055 elif dtype == constants.LD_LV: 1056 if ldisk_status == constants.LDS_FAULTY: 1057 ldisk_text = " *FAILED* (failed drive?)" 1058 else: 1059 ldisk_text = "" 1060 txt += ldisk_text 1061 return txt
1062 1063 # the header 1064 if top_level: 1065 if dev["iv_name"] is not None: 1066 txt = dev["iv_name"] 1067 else: 1068 txt = "disk %s" % compat.TryToRoman(idx, convert=roman) 1069 else: 1070 txt = "child %s" % compat.TryToRoman(idx, convert=roman) 1071 if isinstance(dev["size"], int): 1072 nice_size = utils.FormatUnit(dev["size"], "h") 1073 else: 1074 nice_size = dev["size"] 1075 d1 = ["- %s: %s, size %s" % (txt, dev["dev_type"], nice_size)] 1076 data = [] 1077 if top_level: 1078 data.append(("access mode", dev["mode"])) 1079 if dev["logical_id"] is not None: 1080 try: 1081 l_id = _FormatLogicalID(dev["dev_type"], dev["logical_id"], roman) 1082 except ValueError: 1083 l_id = [str(dev["logical_id"])] 1084 if len(l_id) == 1: 1085 data.append(("logical_id", l_id[0])) 1086 else: 1087 data.extend(l_id) 1088 elif dev["physical_id"] is not None: 1089 data.append("physical_id:") 1090 data.append([dev["physical_id"]]) 1091 1092 if dev["pstatus"]: 1093 data.append(("on primary", helper(dev["dev_type"], dev["pstatus"]))) 1094 1095 if dev["sstatus"]: 1096 data.append(("on secondary", helper(dev["dev_type"], dev["sstatus"]))) 1097 1098 if dev["children"]: 1099 data.append("child devices:") 1100 for c_idx, child in enumerate(dev["children"]): 1101 data.append(_FormatBlockDevInfo(c_idx, False, child, roman)) 1102 d1.append(data) 1103 return d1 1104 1105
1106 -def _FormatList(buf, data, indent_level):
1107 """Formats a list of data at a given indent level. 1108 1109 If the element of the list is: 1110 - a string, it is simply formatted as is 1111 - a tuple, it will be split into key, value and the all the 1112 values in a list will be aligned all at the same start column 1113 - a list, will be recursively formatted 1114 1115 @type buf: StringIO 1116 @param buf: the buffer into which we write the output 1117 @param data: the list to format 1118 @type indent_level: int 1119 @param indent_level: the indent level to format at 1120 1121 """ 1122 max_tlen = max([len(elem[0]) for elem in data 1123 if isinstance(elem, tuple)] or [0]) 1124 for elem in data: 1125 if isinstance(elem, basestring): 1126 buf.write("%*s%s\n" % (2 * indent_level, "", elem)) 1127 elif isinstance(elem, tuple): 1128 key, value = elem 1129 spacer = "%*s" % (max_tlen - len(key), "") 1130 buf.write("%*s%s:%s %s\n" % (2 * indent_level, "", key, spacer, value)) 1131 elif isinstance(elem, list): 1132 _FormatList(buf, elem, indent_level + 1)
1133 1134
1135 -def ShowInstanceConfig(opts, args):
1136 """Compute instance run-time status. 1137 1138 @param opts: the command line options selected by the user 1139 @type args: list 1140 @param args: either an empty list, and then we query all 1141 instances, or should contain a list of instance names 1142 @rtype: int 1143 @return: the desired exit code 1144 1145 """ 1146 if not args and not opts.show_all: 1147 ToStderr("No instance selected." 1148 " Please pass in --all if you want to query all instances.\n" 1149 "Note that this can take a long time on a big cluster.") 1150 return 1 1151 elif args and opts.show_all: 1152 ToStderr("Cannot use --all if you specify instance names.") 1153 return 1 1154 1155 retcode = 0 1156 op = opcodes.OpInstanceQueryData(instances=args, static=opts.static, 1157 use_locking=not opts.static) 1158 result = SubmitOpCode(op, opts=opts) 1159 if not result: 1160 ToStdout("No instances.") 1161 return 1 1162 1163 buf = StringIO() 1164 retcode = 0 1165 for instance_name in result: 1166 instance = result[instance_name] 1167 buf.write("Instance name: %s\n" % instance["name"]) 1168 buf.write("UUID: %s\n" % instance["uuid"]) 1169 buf.write("Serial number: %s\n" % 1170 compat.TryToRoman(instance["serial_no"], 1171 convert=opts.roman_integers)) 1172 buf.write("Creation time: %s\n" % utils.FormatTime(instance["ctime"])) 1173 buf.write("Modification time: %s\n" % utils.FormatTime(instance["mtime"])) 1174 buf.write("State: configured to be %s" % instance["config_state"]) 1175 if instance["run_state"]: 1176 buf.write(", actual state is %s" % instance["run_state"]) 1177 buf.write("\n") 1178 ##buf.write("Considered for memory checks in cluster verify: %s\n" % 1179 ## instance["auto_balance"]) 1180 buf.write(" Nodes:\n") 1181 buf.write(" - primary: %s\n" % instance["pnode"]) 1182 buf.write(" - secondaries: %s\n" % utils.CommaJoin(instance["snodes"])) 1183 buf.write(" Operating system: %s\n" % instance["os"]) 1184 FormatParameterDict(buf, instance["os_instance"], instance["os_actual"], 1185 level=2) 1186 if "network_port" in instance: 1187 buf.write(" Allocated network port: %s\n" % 1188 compat.TryToRoman(instance["network_port"], 1189 convert=opts.roman_integers)) 1190 buf.write(" Hypervisor: %s\n" % instance["hypervisor"]) 1191 1192 # custom VNC console information 1193 vnc_bind_address = instance["hv_actual"].get(constants.HV_VNC_BIND_ADDRESS, 1194 None) 1195 if vnc_bind_address: 1196 port = instance["network_port"] 1197 display = int(port) - constants.VNC_BASE_PORT 1198 if display > 0 and vnc_bind_address == constants.IP4_ADDRESS_ANY: 1199 vnc_console_port = "%s:%s (display %s)" % (instance["pnode"], 1200 port, 1201 display) 1202 elif display > 0 and netutils.IP4Address.IsValid(vnc_bind_address): 1203 vnc_console_port = ("%s:%s (node %s) (display %s)" % 1204 (vnc_bind_address, port, 1205 instance["pnode"], display)) 1206 else: 1207 # vnc bind address is a file 1208 vnc_console_port = "%s:%s" % (instance["pnode"], 1209 vnc_bind_address) 1210 buf.write(" - console connection: vnc to %s\n" % vnc_console_port) 1211 1212 FormatParameterDict(buf, instance["hv_instance"], instance["hv_actual"], 1213 level=2) 1214 buf.write(" Hardware:\n") 1215 buf.write(" - VCPUs: %s\n" % 1216 compat.TryToRoman(instance["be_actual"][constants.BE_VCPUS], 1217 convert=opts.roman_integers)) 1218 buf.write(" - memory: %sMiB\n" % 1219 compat.TryToRoman(instance["be_actual"][constants.BE_MEMORY], 1220 convert=opts.roman_integers)) 1221 buf.write(" - NICs:\n") 1222 for idx, (ip, mac, mode, link) in enumerate(instance["nics"]): 1223 buf.write(" - nic/%d: MAC: %s, IP: %s, mode: %s, link: %s\n" % 1224 (idx, mac, ip, mode, link)) 1225 buf.write(" Disk template: %s\n" % instance["disk_template"]) 1226 buf.write(" Disks:\n") 1227 1228 for idx, device in enumerate(instance["disks"]): 1229 _FormatList(buf, _FormatBlockDevInfo(idx, True, device, 1230 opts.roman_integers), 2) 1231 1232 ToStdout(buf.getvalue().rstrip("\n")) 1233 return retcode
1234 1235
1236 -def SetInstanceParams(opts, args):
1237 """Modifies an instance. 1238 1239 All parameters take effect only at the next restart of the instance. 1240 1241 @param opts: the command line options selected by the user 1242 @type args: list 1243 @param args: should contain only one element, the instance name 1244 @rtype: int 1245 @return: the desired exit code 1246 1247 """ 1248 if not (opts.nics or opts.disks or opts.disk_template or 1249 opts.hvparams or opts.beparams or opts.os or opts.osparams): 1250 ToStderr("Please give at least one of the parameters.") 1251 return 1 1252 1253 for param in opts.beparams: 1254 if isinstance(opts.beparams[param], basestring): 1255 if opts.beparams[param].lower() == "default": 1256 opts.beparams[param] = constants.VALUE_DEFAULT 1257 1258 utils.ForceDictType(opts.beparams, constants.BES_PARAMETER_TYPES, 1259 allowed_values=[constants.VALUE_DEFAULT]) 1260 1261 for param in opts.hvparams: 1262 if isinstance(opts.hvparams[param], basestring): 1263 if opts.hvparams[param].lower() == "default": 1264 opts.hvparams[param] = constants.VALUE_DEFAULT 1265 1266 utils.ForceDictType(opts.hvparams, constants.HVS_PARAMETER_TYPES, 1267 allowed_values=[constants.VALUE_DEFAULT]) 1268 1269 for idx, (nic_op, nic_dict) in enumerate(opts.nics): 1270 try: 1271 nic_op = int(nic_op) 1272 opts.nics[idx] = (nic_op, nic_dict) 1273 except (TypeError, ValueError): 1274 pass 1275 1276 for idx, (disk_op, disk_dict) in enumerate(opts.disks): 1277 try: 1278 disk_op = int(disk_op) 1279 opts.disks[idx] = (disk_op, disk_dict) 1280 except (TypeError, ValueError): 1281 pass 1282 if disk_op == constants.DDM_ADD: 1283 if "size" not in disk_dict: 1284 raise errors.OpPrereqError("Missing required parameter 'size'", 1285 errors.ECODE_INVAL) 1286 disk_dict["size"] = utils.ParseUnit(disk_dict["size"]) 1287 1288 if (opts.disk_template and 1289 opts.disk_template in constants.DTS_INT_MIRROR and 1290 not opts.node): 1291 ToStderr("Changing the disk template to a mirrored one requires" 1292 " specifying a secondary node") 1293 return 1 1294 1295 op = opcodes.OpInstanceSetParams(instance_name=args[0], 1296 nics=opts.nics, 1297 disks=opts.disks, 1298 disk_template=opts.disk_template, 1299 remote_node=opts.node, 1300 hvparams=opts.hvparams, 1301 beparams=opts.beparams, 1302 os_name=opts.os, 1303 osparams=opts.osparams, 1304 force_variant=opts.force_variant, 1305 force=opts.force, 1306 wait_for_sync=opts.wait_for_sync) 1307 1308 # even if here we process the result, we allow submit only 1309 result = SubmitOrSend(op, opts) 1310 1311 if result: 1312 ToStdout("Modified instance %s", args[0]) 1313 for param, data in result: 1314 ToStdout(" - %-5s -> %s", param, data) 1315 ToStdout("Please don't forget that most parameters take effect" 1316 " only at the next start of the instance.") 1317 return 0
1318 1319
1320 -def ChangeGroup(opts, args):
1321 """Moves an instance to another group. 1322 1323 """ 1324 (instance_name, ) = args 1325 1326 cl = GetClient() 1327 1328 op = opcodes.OpInstanceChangeGroup(instance_name=instance_name, 1329 iallocator=opts.iallocator, 1330 target_groups=opts.to, 1331 early_release=opts.early_release) 1332 result = SubmitOpCode(op, cl=cl, opts=opts) 1333 1334 # Keep track of submitted jobs 1335 jex = JobExecutor(cl=cl, opts=opts) 1336 1337 for (status, job_id) in result[constants.JOB_IDS_KEY]: 1338 jex.AddJobId(None, status, job_id) 1339 1340 results = jex.GetResults() 1341 bad_cnt = len([row for row in results if not row[0]]) 1342 if bad_cnt == 0: 1343 ToStdout("Instance '%s' changed group successfully.", instance_name) 1344 rcode = constants.EXIT_SUCCESS 1345 else: 1346 ToStdout("There were %s errors while changing group of instance '%s'.", 1347 bad_cnt, instance_name) 1348 rcode = constants.EXIT_FAILURE 1349 1350 return rcode
1351 1352 1353 # multi-instance selection options 1354 m_force_multi = cli_option("--force-multiple", dest="force_multi", 1355 help="Do not ask for confirmation when more than" 1356 " one instance is affected", 1357 action="store_true", default=False) 1358 1359 m_pri_node_opt = cli_option("--primary", dest="multi_mode", 1360 help="Filter by nodes (primary only)", 1361 const=_EXPAND_NODES_PRI, action="store_const") 1362 1363 m_sec_node_opt = cli_option("--secondary", dest="multi_mode", 1364 help="Filter by nodes (secondary only)", 1365 const=_EXPAND_NODES_SEC, action="store_const") 1366 1367 m_node_opt = cli_option("--node", dest="multi_mode", 1368 help="Filter by nodes (primary and secondary)", 1369 const=_EXPAND_NODES_BOTH, action="store_const") 1370 1371 m_clust_opt = cli_option("--all", dest="multi_mode", 1372 help="Select all instances in the cluster", 1373 const=_EXPAND_CLUSTER, action="store_const") 1374 1375 m_inst_opt = cli_option("--instance", dest="multi_mode", 1376 help="Filter by instance name [default]", 1377 const=_EXPAND_INSTANCES, action="store_const") 1378 1379 m_node_tags_opt = cli_option("--node-tags", dest="multi_mode", 1380 help="Filter by node tag", 1381 const=_EXPAND_NODES_BOTH_BY_TAGS, 1382 action="store_const") 1383 1384 m_pri_node_tags_opt = cli_option("--pri-node-tags", dest="multi_mode", 1385 help="Filter by primary node tag", 1386 const=_EXPAND_NODES_PRI_BY_TAGS, 1387 action="store_const") 1388 1389 m_sec_node_tags_opt = cli_option("--sec-node-tags", dest="multi_mode", 1390 help="Filter by secondary node tag", 1391 const=_EXPAND_NODES_SEC_BY_TAGS, 1392 action="store_const") 1393 1394 m_inst_tags_opt = cli_option("--tags", dest="multi_mode", 1395 help="Filter by instance tag", 1396 const=_EXPAND_INSTANCES_BY_TAGS, 1397 action="store_const") 1398 1399 # this is defined separately due to readability only 1400 add_opts = [ 1401 NOSTART_OPT, 1402 OS_OPT, 1403 FORCE_VARIANT_OPT, 1404 NO_INSTALL_OPT, 1405 ] 1406 1407 commands = { 1408 "add": ( 1409 AddInstance, [ArgHost(min=1, max=1)], COMMON_CREATE_OPTS + add_opts, 1410 "[...] -t disk-type -n node[:secondary-node] -o os-type <name>", 1411 "Creates and adds a new instance to the cluster"), 1412 "batch-create": ( 1413 BatchCreate, [ArgFile(min=1, max=1)], [DRY_RUN_OPT, PRIORITY_OPT], 1414 "<instances.json>", 1415 "Create a bunch of instances based on specs in the file."), 1416 "console": ( 1417 ConnectToInstanceConsole, ARGS_ONE_INSTANCE, 1418 [SHOWCMD_OPT, PRIORITY_OPT], 1419 "[--show-cmd] <instance>", "Opens a console on the specified instance"), 1420 "failover": ( 1421 FailoverInstance, ARGS_ONE_INSTANCE, 1422 [FORCE_OPT, IGNORE_CONSIST_OPT, SUBMIT_OPT, SHUTDOWN_TIMEOUT_OPT, 1423 DRY_RUN_OPT, PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT], 1424 "[-f] <instance>", "Stops the instance, changes its primary node and" 1425 " (if it was originally running) starts it on the new node" 1426 " (the secondary for mirrored instances or any node" 1427 " for shared storage)."), 1428 "migrate": ( 1429 MigrateInstance, ARGS_ONE_INSTANCE, 1430 [FORCE_OPT, NONLIVE_OPT, MIGRATION_MODE_OPT, CLEANUP_OPT, DRY_RUN_OPT, 1431 PRIORITY_OPT, DST_NODE_OPT, IALLOCATOR_OPT, ALLOW_FAILOVER_OPT], 1432 "[-f] <instance>", "Migrate instance to its secondary node" 1433 " (only for mirrored instances)"), 1434 "move": ( 1435 MoveInstance, ARGS_ONE_INSTANCE, 1436 [FORCE_OPT, SUBMIT_OPT, SINGLE_NODE_OPT, SHUTDOWN_TIMEOUT_OPT, 1437 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_CONSIST_OPT], 1438 "[-f] <instance>", "Move instance to an arbitrary node" 1439 " (only for instances of type file and lv)"), 1440 "info": ( 1441 ShowInstanceConfig, ARGS_MANY_INSTANCES, 1442 [STATIC_OPT, ALL_OPT, ROMAN_OPT, PRIORITY_OPT], 1443 "[-s] {--all | <instance>...}", 1444 "Show information on the specified instance(s)"), 1445 "list": ( 1446 ListInstances, ARGS_MANY_INSTANCES, 1447 [NOHDR_OPT, SEP_OPT, USEUNITS_OPT, FIELDS_OPT, VERBOSE_OPT, 1448 FORCE_FILTER_OPT], 1449 "[<instance>...]", 1450 "Lists the instances and their status. The available fields can be shown" 1451 " using the \"list-fields\" command (see the man page for details)." 1452 " The default field list is (in order): %s." % 1453 utils.CommaJoin(_LIST_DEF_FIELDS), 1454 ), 1455 "list-fields": ( 1456 ListInstanceFields, [ArgUnknown()], 1457 [NOHDR_OPT, SEP_OPT], 1458 "[fields...]", 1459 "Lists all available fields for instances"), 1460 "reinstall": ( 1461 ReinstallInstance, [ArgInstance()], 1462 [FORCE_OPT, OS_OPT, FORCE_VARIANT_OPT, m_force_multi, m_node_opt, 1463 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, m_node_tags_opt, 1464 m_pri_node_tags_opt, m_sec_node_tags_opt, m_inst_tags_opt, SELECT_OS_OPT, 1465 SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT, OSPARAMS_OPT], 1466 "[-f] <instance>", "Reinstall a stopped instance"), 1467 "remove": ( 1468 RemoveInstance, ARGS_ONE_INSTANCE, 1469 [FORCE_OPT, SHUTDOWN_TIMEOUT_OPT, IGNORE_FAILURES_OPT, SUBMIT_OPT, 1470 DRY_RUN_OPT, PRIORITY_OPT], 1471 "[-f] <instance>", "Shuts down the instance and removes it"), 1472 "rename": ( 1473 RenameInstance, 1474 [ArgInstance(min=1, max=1), ArgHost(min=1, max=1)], 1475 [NOIPCHECK_OPT, NONAMECHECK_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT], 1476 "<instance> <new_name>", "Rename the instance"), 1477 "replace-disks": ( 1478 ReplaceDisks, ARGS_ONE_INSTANCE, 1479 [AUTO_REPLACE_OPT, DISKIDX_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT, 1480 NEW_SECONDARY_OPT, ON_PRIMARY_OPT, ON_SECONDARY_OPT, SUBMIT_OPT, 1481 DRY_RUN_OPT, PRIORITY_OPT], 1482 "[-s|-p|-n NODE|-I NAME] <instance>", 1483 "Replaces all disks for the instance"), 1484 "modify": ( 1485 SetInstanceParams, ARGS_ONE_INSTANCE, 1486 [BACKEND_OPT, DISK_OPT, FORCE_OPT, HVOPTS_OPT, NET_OPT, SUBMIT_OPT, 1487 DISK_TEMPLATE_OPT, SINGLE_NODE_OPT, OS_OPT, FORCE_VARIANT_OPT, 1488 OSPARAMS_OPT, DRY_RUN_OPT, PRIORITY_OPT, NWSYNC_OPT], 1489 "<instance>", "Alters the parameters of an instance"), 1490 "shutdown": ( 1491 GenericManyOps("shutdown", _ShutdownInstance), [ArgInstance()], 1492 [m_node_opt, m_pri_node_opt, m_sec_node_opt, m_clust_opt, 1493 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt, 1494 m_inst_tags_opt, m_inst_opt, m_force_multi, TIMEOUT_OPT, SUBMIT_OPT, 1495 DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, NO_REMEMBER_OPT], 1496 "<instance>", "Stops an instance"), 1497 "startup": ( 1498 GenericManyOps("startup", _StartupInstance), [ArgInstance()], 1499 [FORCE_OPT, m_force_multi, m_node_opt, m_pri_node_opt, m_sec_node_opt, 1500 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt, 1501 m_inst_tags_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, HVOPTS_OPT, 1502 BACKEND_OPT, DRY_RUN_OPT, PRIORITY_OPT, IGNORE_OFFLINE_OPT, 1503 NO_REMEMBER_OPT, STARTUP_PAUSED_OPT], 1504 "<instance>", "Starts an instance"), 1505 "reboot": ( 1506 GenericManyOps("reboot", _RebootInstance), [ArgInstance()], 1507 [m_force_multi, REBOOT_TYPE_OPT, IGNORE_SECONDARIES_OPT, m_node_opt, 1508 m_pri_node_opt, m_sec_node_opt, m_clust_opt, m_inst_opt, SUBMIT_OPT, 1509 m_node_tags_opt, m_pri_node_tags_opt, m_sec_node_tags_opt, 1510 m_inst_tags_opt, SHUTDOWN_TIMEOUT_OPT, DRY_RUN_OPT, PRIORITY_OPT], 1511 "<instance>", "Reboots an instance"), 1512 "activate-disks": ( 1513 ActivateDisks, ARGS_ONE_INSTANCE, 1514 [SUBMIT_OPT, IGNORE_SIZE_OPT, PRIORITY_OPT], 1515 "<instance>", "Activate an instance's disks"), 1516 "deactivate-disks": ( 1517 DeactivateDisks, ARGS_ONE_INSTANCE, 1518 [FORCE_OPT, SUBMIT_OPT, DRY_RUN_OPT, PRIORITY_OPT], 1519 "[-f] <instance>", "Deactivate an instance's disks"), 1520 "recreate-disks": ( 1521 RecreateDisks, ARGS_ONE_INSTANCE, 1522 [SUBMIT_OPT, DISKIDX_OPT, NODE_PLACEMENT_OPT, DRY_RUN_OPT, PRIORITY_OPT], 1523 "<instance>", "Recreate an instance's disks"), 1524 "grow-disk": ( 1525 GrowDisk, 1526 [ArgInstance(min=1, max=1), ArgUnknown(min=1, max=1), 1527 ArgUnknown(min=1, max=1)], 1528 [SUBMIT_OPT, NWSYNC_OPT, DRY_RUN_OPT, PRIORITY_OPT], 1529 "<instance> <disk> <size>", "Grow an instance's disk"), 1530 "change-group": ( 1531 ChangeGroup, ARGS_ONE_INSTANCE, 1532 [TO_GROUP_OPT, IALLOCATOR_OPT, EARLY_RELEASE_OPT], 1533 "[-I <iallocator>] [--to <group>]", "Change group of instance"), 1534 "list-tags": ( 1535 ListTags, ARGS_ONE_INSTANCE, [PRIORITY_OPT], 1536 "<instance_name>", "List the tags of the given instance"), 1537 "add-tags": ( 1538 AddTags, [ArgInstance(min=1, max=1), ArgUnknown()], 1539 [TAG_SRC_OPT, PRIORITY_OPT], 1540 "<instance_name> tag...", "Add tags to the given instance"), 1541 "remove-tags": ( 1542 RemoveTags, [ArgInstance(min=1, max=1), ArgUnknown()], 1543 [TAG_SRC_OPT, PRIORITY_OPT], 1544 "<instance_name> tag...", "Remove tags from given instance"), 1545 } 1546 1547 #: dictionary with aliases for commands 1548 aliases = { 1549 "start": "startup", 1550 "stop": "shutdown", 1551 } 1552 1553
1554 -def Main():
1555 return GenericMain(commands, aliases=aliases, 1556 override={"tag_type": constants.TAG_INSTANCE})
1557