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