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