Revision ebeb600f

b/doc/rapi.rst
414 414
  File storage driver.
415 415
``iallocator`` (string)
416 416
  Instance allocator name.
417
``source_handshake``
418
  Signed handshake from source (remote import only).
419
``source_x509_ca`` (string)
420
  Source X509 CA in PEM format (remote import only).
421
``source_instance_name`` (string)
422
  Source instance name (remote import only).
417 423
``hypervisor`` (string)
418 424
  Hypervisor name.
419 425
``hvparams`` (dict)
......
579 585
Takes no parameters.
580 586

  
581 587

  
588
``/2/instances/[instance_name]/prepare-export``
589
+++++++++++++++++++++++++++++++++++++++++++++++++
590

  
591
Prepares an export of an instance.
592

  
593
It supports the following commands: ``PUT``.
594

  
595
``PUT``
596
~~~~~~~
597

  
598
Takes one parameter, ``mode``, for the export mode. Returns a job ID.
599

  
600

  
601
``/2/instances/[instance_name]/export``
602
+++++++++++++++++++++++++++++++++++++++++++++++++
603

  
604
Exports an instance.
605

  
606
It supports the following commands: ``PUT``.
607

  
608
``PUT``
609
~~~~~~~
610

  
611
Returns a job ID.
612

  
613
Body parameters:
614

  
615
``mode`` (string)
616
  Export mode.
617
``destination`` (required)
618
  Destination information, depends on export mode.
619
``shutdown`` (bool, required)
620
  Whether to shutdown instance before export.
621
``remove_instance`` (bool)
622
  Whether to remove instance after export.
623
``x509_key_name``
624
  Name of X509 key (remote export only).
625
``destination_x509_ca``
626
  Destination X509 CA (remote export only).
627

  
628

  
582 629
``/2/instances/[instance_name]/tags``
583 630
+++++++++++++++++++++++++++++++++++++
584 631

  
b/lib/rapi/client.py
840 840
                             ("/%s/instances/%s/replace-disks" %
841 841
                              (GANETI_RAPI_VERSION, instance)), query, None)
842 842

  
843
  def PrepareExport(self, instance, mode):
844
    """Prepares an instance for an export.
845

  
846
    @type instance: string
847
    @param instance: Instance name
848
    @type mode: string
849
    @param mode: Export mode
850
    @rtype: string
851
    @return: Job ID
852

  
853
    """
854
    query = [("mode", mode)]
855
    return self._SendRequest(HTTP_PUT,
856
                             ("/%s/instances/%s/prepare-export" %
857
                              (GANETI_RAPI_VERSION, instance)), query, None)
858

  
859
  def ExportInstance(self, instance, mode, destination, shutdown=None,
860
                     remove_instance=None,
861
                     x509_key_name=None, destination_x509_ca=None):
862
    """Exports an instance.
863

  
864
    @type instance: string
865
    @param instance: Instance name
866
    @type mode: string
867
    @param mode: Export mode
868
    @rtype: string
869
    @return: Job ID
870

  
871
    """
872
    body = {
873
      "destination": destination,
874
      "mode": mode,
875
      }
876

  
877
    if shutdown is not None:
878
      body["shutdown"] = shutdown
879

  
880
    if remove_instance is not None:
881
      body["remove_instance"] = remove_instance
882

  
883
    if x509_key_name is not None:
884
      body["x509_key_name"] = x509_key_name
885

  
886
    if destination_x509_ca is not None:
887
      body["destination_x509_ca"] = destination_x509_ca
888

  
889
    return self._SendRequest(HTTP_PUT,
890
                             ("/%s/instances/%s/export" %
891
                              (GANETI_RAPI_VERSION, instance)), None, body)
892

  
843 893
  def GetJobs(self):
844 894
    """Gets all jobs for the cluster.
845 895

  
b/lib/rapi/connector.py
205 205
      rlib2.R_2_instances_name_activate_disks,
206 206
    re.compile(r'^/2/instances/(%s)/deactivate-disks$' % instance_name_pattern):
207 207
      rlib2.R_2_instances_name_deactivate_disks,
208
    re.compile(r'^/2/instances/(%s)/prepare-export$' % instance_name_pattern):
209
      rlib2.R_2_instances_name_prepare_export,
210
    re.compile(r'^/2/instances/(%s)/export$' % instance_name_pattern):
211
      rlib2.R_2_instances_name_export,
208 212

  
209 213
    "/2/jobs": rlib2.R_2_jobs,
210 214
    re.compile(r'/2/jobs/(%s)$' % job_id_pattern):
b/lib/rapi/rlib2.py
578 578
                                             default=None),
579 579
    file_driver=baserlib.CheckParameter(data, "file_driver",
580 580
                                        default=constants.FD_LOOP),
581
    source_handshake=baserlib.CheckParameter(data, "source_handshake",
582
                                             default=None),
583
    source_x509_ca=baserlib.CheckParameter(data, "source_x509_ca",
584
                                           default=None),
585
    source_instance_name=baserlib.CheckParameter(data, "source_instance_name",
586
                                                 default=None),
581 587
    iallocator=baserlib.CheckParameter(data, "iallocator", default=None),
582 588
    hypervisor=baserlib.CheckParameter(data, "hypervisor", default=None),
583 589
    hvparams=hvparams,
......
891 897
    return baserlib.SubmitJob([op])
892 898

  
893 899

  
900
class R_2_instances_name_prepare_export(baserlib.R_Generic):
901
  """/2/instances/[instance_name]/prepare-export resource.
902

  
903
  """
904
  def PUT(self):
905
    """Prepares an export for an instance.
906

  
907
    @return: a job id
908

  
909
    """
910
    instance_name = self.items[0]
911
    mode = self._checkStringVariable("mode")
912

  
913
    op = opcodes.OpPrepareExport(instance_name=instance_name,
914
                                 mode=mode)
915

  
916
    return baserlib.SubmitJob([op])
917

  
918

  
919
def _ParseExportInstanceRequest(name, data):
920
  """Parses a request for an instance export.
921

  
922
  @rtype: L{opcodes.OpExportInstance}
923
  @return: Instance export opcode
924

  
925
  """
926
  mode = baserlib.CheckParameter(data, "mode",
927
                                 default=constants.EXPORT_MODE_LOCAL)
928
  target_node = baserlib.CheckParameter(data, "destination")
929
  shutdown = baserlib.CheckParameter(data, "shutdown", exptype=bool)
930
  remove_instance = baserlib.CheckParameter(data, "remove_instance",
931
                                            exptype=bool, default=False)
932
  x509_key_name = baserlib.CheckParameter(data, "x509_key_name", default=None)
933
  destination_x509_ca = baserlib.CheckParameter(data, "destination_x509_ca",
934
                                                default=None)
935

  
936
  return opcodes.OpExportInstance(instance_name=name,
937
                                  mode=mode,
938
                                  target_node=target_node,
939
                                  shutdown=shutdown,
940
                                  remove_instance=remove_instance,
941
                                  x509_key_name=x509_key_name,
942
                                  destination_x509_ca=destination_x509_ca)
943

  
944

  
945
class R_2_instances_name_export(baserlib.R_Generic):
946
  """/2/instances/[instance_name]/export resource.
947

  
948
  """
949
  def PUT(self):
950
    """Exports an instance.
951

  
952
    @return: a job id
953

  
954
    """
955
    if not isinstance(self.request_body, dict):
956
      raise http.HttpBadRequest("Invalid body contents, not a dictionary")
957

  
958
    op = _ParseExportInstanceRequest(self.items[0], self.request_body)
959

  
960
    return baserlib.SubmitJob([op])
961

  
962

  
894 963
class _R_Tags(baserlib.R_Generic):
895 964
  """ Quasiclass for tagging resources
896 965

  
b/qa/qa_rapi.py
198 198
     _VerifyReturnsJob, 'PUT', None),
199 199
    ])
200 200

  
201
  # Test OpPrepareExport
202
  (job_id, ) = _DoTests([
203
    ("/2/instances/%s/prepare-export?mode=%s" %
204
     (instance["name"], constants.EXPORT_MODE_REMOTE),
205
     _VerifyReturnsJob, "PUT", None),
206
    ])
207

  
208
  result = _WaitForRapiJob(job_id)[0]
209
  AssertEqual(len(result["handshake"]), 3)
210
  AssertEqual(result["handshake"][0], constants.RIE_VERSION)
211
  AssertEqual(len(result["x509_key_name"]), 3)
212
  AssertIn("-----BEGIN CERTIFICATE-----", result["x509_ca"])
213

  
201 214

  
202 215
def TestNode(node):
203 216
  """Testing getting node(s) info via remote API.
......
259 272
    ("/2/jobs/%s" % job_id, _VerifyJob, "GET", None),
260 273
    ])
261 274

  
262
  rapi.client_utils.PollJob(_rapi_client, job_id, cli.StdioJobPollReportCb())
275
  return rapi.client_utils.PollJob(_rapi_client, job_id,
276
                                   cli.StdioJobPollReportCb())
263 277

  
264 278

  
265 279
def TestRapiInstanceAdd(node, use_client):
b/test/ganeti.rapi.client_unittest.py
396 396
    self.assertItems(["instance-moo"])
397 397
    self.assertQuery("disks", None)
398 398

  
399
  def testPrepareExport(self):
400
    self.rapi.AddResponse("8326")
401
    self.assertEqual(8326, self.client.PrepareExport("inst1", "local"))
402
    self.assertHandler(rlib2.R_2_instances_name_prepare_export)
403
    self.assertItems(["inst1"])
404
    self.assertQuery("mode", ["local"])
405

  
406
  def testExportInstance(self):
407
    self.rapi.AddResponse("19695")
408
    job_id = self.client.ExportInstance("inst2", "local", "nodeX",
409
                                        shutdown=True)
410
    self.assertEqual(job_id, 19695)
411
    self.assertHandler(rlib2.R_2_instances_name_export)
412
    self.assertItems(["inst2"])
413

  
414
    data = serializer.LoadJson(self.http.last_request.data)
415
    self.assertEqual(data["mode"], "local")
416
    self.assertEqual(data["destination"], "nodeX")
417
    self.assertEqual(data["shutdown"], True)
418

  
399 419
  def testGetJobs(self):
400 420
    self.rapi.AddResponse('[ { "id": "123", "uri": "\\/2\\/jobs\\/123" },'
401 421
                          '  { "id": "124", "uri": "\\/2\\/jobs\\/124" } ]')
b/test/ganeti.rapi.rlib2_unittest.py
180 180
        self.assertRaises(http.HttpBadRequest, self.Parse, data, False)
181 181

  
182 182

  
183
class TestParseExportInstanceRequest(testutils.GanetiTestCase):
184
  def setUp(self):
185
    testutils.GanetiTestCase.setUp(self)
186

  
187
    self.Parse = rlib2._ParseExportInstanceRequest
188

  
189
  def test(self):
190
    name = "instmoo"
191
    data = {
192
      "mode": constants.EXPORT_MODE_REMOTE,
193
      "destination": [(1, 2, 3), (99, 99, 99)],
194
      "shutdown": True,
195
      "remove_instance": True,
196
      "x509_key_name": ("name", "hash"),
197
      "destination_x509_ca": ("x", "y", "z"),
198
      }
199
    op = self.Parse(name, data)
200
    self.assert_(isinstance(op, opcodes.OpExportInstance))
201
    self.assertEqual(op.instance_name, name)
202
    self.assertEqual(op.mode, constants.EXPORT_MODE_REMOTE)
203
    self.assertEqual(op.shutdown, True)
204
    self.assertEqual(op.remove_instance, True)
205
    self.assertEqualValues(op.x509_key_name, ("name", "hash"))
206
    self.assertEqualValues(op.destination_x509_ca, ("x", "y", "z"))
207

  
208
  def testDefaults(self):
209
    name = "inst1"
210
    data = {
211
      "destination": "node2",
212
      "shutdown": False,
213
      }
214
    op = self.Parse(name, data)
215
    self.assert_(isinstance(op, opcodes.OpExportInstance))
216
    self.assertEqual(op.instance_name, name)
217
    self.assertEqual(op.mode, constants.EXPORT_MODE_LOCAL)
218
    self.assertEqual(op.remove_instance, False)
219

  
220
  def testErrors(self):
221
    self.assertRaises(http.HttpBadRequest, self.Parse, "err1",
222
                      { "remove_instance": "True", })
223
    self.assertRaises(http.HttpBadRequest, self.Parse, "err1",
224
                      { "remove_instance": "False", })
225

  
226

  
183 227
if __name__ == '__main__':
184 228
  testutils.GanetiTestProgram()

Also available in: Unified diff