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