Revision 539d65ba

b/doc/rapi.rst
789 789
``POST``
790 790
~~~~~~~~
791 791

  
792
Takes the parameters ``mode`` (one of ``replace_on_primary``,
793
``replace_on_secondary``, ``replace_new_secondary`` or
794
``replace_auto``), ``disks`` (comma separated list of disk indexes),
795
``remote_node`` and ``iallocator``.
792
Returns a job ID.
793

  
794
Body parameters:
796 795

  
797
Either ``remote_node`` or ``iallocator`` needs to be defined when using
798
``mode=replace_new_secondary``.
796
.. opcode_params:: OP_INSTANCE_REPLACE_DISKS
797
   :exclude: instance_name
799 798

  
800
``mode`` is a mandatory parameter. ``replace_auto`` tries to determine
801
the broken disk(s) on its own and replacing it.
799
Ganeti 2.4 and below used query parameters. Those are deprecated and
800
should no longer be used.
802 801

  
803 802

  
804 803
``/2/instances/[instance_name]/activate-disks``
b/lib/rapi/client.py
956 956
                              (GANETI_RAPI_VERSION, instance)), query, None)
957 957

  
958 958
  def ReplaceInstanceDisks(self, instance, disks=None, mode=REPLACE_DISK_AUTO,
959
                           remote_node=None, iallocator=None, dry_run=False):
959
                           remote_node=None, iallocator=None):
960 960
    """Replaces disks on an instance.
961 961

  
962 962
    @type instance: str
......
971 971
    @type iallocator: str or None
972 972
    @param iallocator: instance allocator plugin to use (for use with
973 973
                       replace_auto mode)
974
    @type dry_run: bool
975
    @param dry_run: whether to perform a dry run
976 974

  
977 975
    @rtype: string
978 976
    @return: job id
......
982 980
      ("mode", mode),
983 981
      ]
984 982

  
985
    if disks:
983
    # TODO: Convert to body parameters
984

  
985
    if disks is not None:
986 986
      query.append(("disks", ",".join(str(idx) for idx in disks)))
987 987

  
988
    if remote_node:
988
    if remote_node is not None:
989 989
      query.append(("remote_node", remote_node))
990 990

  
991
    if iallocator:
991
    if iallocator is not None:
992 992
      query.append(("iallocator", iallocator))
993 993

  
994
    if dry_run:
995
      query.append(("dry-run", 1))
996

  
997 994
    return self._SendRequest(HTTP_POST,
998 995
                             ("/%s/instances/%s/replace-disks" %
999 996
                              (GANETI_RAPI_VERSION, instance)), query, None)
b/lib/rapi/rlib2.py
999 999

  
1000 1000
  # Parse disks
1001 1001
  try:
1002
    raw_disks = data["disks"]
1002
    raw_disks = data.pop("disks")
1003 1003
  except KeyError:
1004 1004
    pass
1005 1005
  else:
1006
    if not ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102
1007
      # Backwards compatibility for strings of the format "1, 2, 3"
1008
      try:
1009
        data["disks"] = [int(part) for part in raw_disks.split(",")]
1010
      except (TypeError, ValueError), err:
1011
        raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
1006
    if raw_disks:
1007
      if ht.TListOf(ht.TInt)(raw_disks): # pylint: disable=E1102
1008
        data["disks"] = raw_disks
1009
      else:
1010
        # Backwards compatibility for strings of the format "1, 2, 3"
1011
        try:
1012
          data["disks"] = [int(part) for part in raw_disks.split(",")]
1013
        except (TypeError, ValueError), err:
1014
          raise http.HttpBadRequest("Invalid disk index passed: %s" % str(err))
1012 1015

  
1013 1016
  return baserlib.FillOpcode(opcodes.OpInstanceReplaceDisks, data, override)
1014 1017

  
......
1021 1024
    """Replaces disks on an instance.
1022 1025

  
1023 1026
    """
1024
    op = _ParseInstanceReplaceDisksRequest(self.items[0], self.request_body)
1027
    if self.request_body:
1028
      body = self.request_body
1029
    elif self.queryargs:
1030
      # Legacy interface, do not modify/extend
1031
      body = {
1032
        "remote_node": self._checkStringVariable("remote_node", default=None),
1033
        "mode": self._checkStringVariable("mode", default=None),
1034
        "disks": self._checkStringVariable("disks", default=None),
1035
        "iallocator": self._checkStringVariable("iallocator", default=None),
1036
        }
1037
    else:
1038
      body = {}
1039

  
1040
    op = _ParseInstanceReplaceDisksRequest(self.items[0], body)
1025 1041

  
1026 1042
    return baserlib.SubmitJob([op])
1027 1043

  
b/qa/ganeti-qa.py
382 382
  if qa_config.TestEnabled("instance-replace-disks"):
383 383
    othernode = qa_config.AcquireNode(exclude=[pnode, snode])
384 384
    try:
385
      RunTestIf("rapi", qa_rapi.TestRapiInstanceReplaceDisks, instance)
385 386
      RunTest(qa_instance.TestReplaceDisks,
386 387
              instance, pnode, snode, othernode)
387 388
    finally:
b/qa/qa_rapi.py
618 618
  _WaitForRapiJob(_rapi_client.ReinstallInstance(instance["name"]))
619 619

  
620 620

  
621
def TestRapiInstanceReplaceDisks(instance):
622
  """Test replacing instance disks via RAPI"""
623
  _WaitForRapiJob(_rapi_client.ReplaceInstanceDisks(instance["name"],
624
    mode=constants.REPLACE_DISK_AUTO, disks=[]))
625
  _WaitForRapiJob(_rapi_client.ReplaceInstanceDisks(instance["name"],
626
    mode=constants.REPLACE_DISK_SEC, disks="0"))
627

  
628

  
621 629
def TestRapiInstanceModify(instance):
622 630
  """Test modifying instance via RAPI"""
623 631
  def _ModifyInstance(**kwargs):
b/test/ganeti.rapi.client_unittest.py
669 669
  def testReplaceInstanceDisks(self):
670 670
    self.rapi.AddResponse("999")
671 671
    job_id = self.client.ReplaceInstanceDisks("instance-name",
672
        disks=[0, 1], dry_run=True, iallocator="hail")
672
        disks=[0, 1], iallocator="hail")
673 673
    self.assertEqual(999, job_id)
674 674
    self.assertHandler(rlib2.R_2_instances_name_replace_disks)
675 675
    self.assertItems(["instance-name"])
676 676
    self.assertQuery("disks", ["0,1"])
677 677
    self.assertQuery("mode", ["replace_auto"])
678 678
    self.assertQuery("iallocator", ["hail"])
679
    self.assertDryRun()
680 679

  
681 680
    self.rapi.AddResponse("1000")
682 681
    job_id = self.client.ReplaceInstanceDisks("instance-bar",
683
        disks=[1], mode="replace_on_secondary", remote_node="foo-node",
684
        dry_run=True)
682
        disks=[1], mode="replace_on_secondary", remote_node="foo-node")
685 683
    self.assertEqual(1000, job_id)
686 684
    self.assertItems(["instance-bar"])
687 685
    self.assertQuery("disks", ["1"])
688 686
    self.assertQuery("remote_node", ["foo-node"])
689
    self.assertDryRun()
690 687

  
691 688
    self.rapi.AddResponse("5175")
692 689
    self.assertEqual(5175, self.client.ReplaceInstanceDisks("instance-moo"))
b/test/ganeti.rapi.rlib2_unittest.py
529 529
    self.assertFalse(hasattr(op, "iallocator"))
530 530
    self.assertFalse(hasattr(op, "disks"))
531 531

  
532
  def testNoDisks(self):
533
    self.assertRaises(http.HttpBadRequest, self.Parse, "inst20661", {})
534

  
535
    for disks in [None, "", {}]:
536
      self.assertRaises(http.HttpBadRequest, self.Parse, "inst20661", {
537
        "disks": disks,
538
        })
539

  
532 540
  def testWrong(self):
533 541
    self.assertRaises(http.HttpBadRequest, self.Parse, "inst",
534 542
                      { "mode": constants.REPLACE_DISK_AUTO,

Also available in: Unified diff