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