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