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