From 92cd70a2240dd5106ebfffecd6007942e898903a Mon Sep 17 00:00:00 2001 From: Pranali Deore Date: Fri, 26 Aug 2022 04:18:31 +0000 Subject: [PATCH] Add support for glance-download import method Implements: blueprint glance-download-import-support Change-Id: Ia2bfad82bccf9acb6103b21112e680c44e295d39 --- glanceclient/tests/unit/v2/base.py | 4 +- glanceclient/tests/unit/v2/test_images.py | 20 ++- glanceclient/tests/unit/v2/test_shell_v2.py | 151 ++++++++++++++++-- glanceclient/v2/images.py | 12 +- glanceclient/v2/shell.py | 60 ++++++- ...wnload-import-method-10525254db3e8e7a.yaml | 5 + 6 files changed, 232 insertions(+), 20 deletions(-) create mode 100644 releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml diff --git a/glanceclient/tests/unit/v2/base.py b/glanceclient/tests/unit/v2/base.py index 694cd0fe..043acb12 100644 --- a/glanceclient/tests/unit/v2/base.py +++ b/glanceclient/tests/unit/v2/base.py @@ -113,8 +113,8 @@ class BaseController(testtools.TestCase): resp = self.controller.deassociate(*args) self._assertRequestId(resp) - def image_import(self, *args): - resp = self.controller.image_import(*args) + def image_import(self, *args, **kwargs): + resp = self.controller.image_import(*args, **kwargs) self._assertRequestId(resp) diff --git a/glanceclient/tests/unit/v2/test_images.py b/glanceclient/tests/unit/v2/test_images.py index 199d6ec9..60ddb3a7 100644 --- a/glanceclient/tests/unit/v2/test_images.py +++ b/glanceclient/tests/unit/v2/test_images.py @@ -1201,7 +1201,7 @@ class TestController(testtools.TestCase): body = ''.join([b for b in body]) self.assertEqual('GOODCHECKSUM', body) - def test_image_import(self): + def test_image_import_web_download(self): uri = 'http://example.com/image.qcow' data = [('method', {'name': 'web-download', 'uri': uri})] @@ -1211,6 +1211,24 @@ class TestController(testtools.TestCase): data)] self.assertEqual(expect, self.api.calls) + def test_image_import_glance_download(self): + region = 'REGION2' + remote_image_id = '75baf7b6-253a-11ed-8307-4b1057986a78' + image_id = '606b0e88-7c5a-4d54-b5bb-046105d4de6f' + service_interface = 'public' + data = [('method', + {'name': 'glance-download', + 'glance_region': region, + 'glance_image_id': remote_image_id, + 'glance_service_interface': service_interface})] + self.controller.image_import( + image_id, 'glance-download', remote_region=region, + remote_image_id=remote_image_id, + remote_service_interface=service_interface) + expect = [('POST', '/v2/images/%s/import' % image_id, {}, + data)] + self.assertEqual(expect, self.api.calls) + def test_download_no_data(self): resp = utils.FakeResponse(headers={}, status_code=204) self.controller.controller.http_client.get = mock.Mock( diff --git a/glanceclient/tests/unit/v2/test_shell_v2.py b/glanceclient/tests/unit/v2/test_shell_v2.py index c24a1c98..b9ced589 100644 --- a/glanceclient/tests/unit/v2/test_shell_v2.py +++ b/glanceclient/tests/unit/v2/test_shell_v2.py @@ -957,12 +957,14 @@ class ShellV2Test(testtools.TestCase): 'progress': False, 'file': None, 'uri': None, + 'remote_region': None, 'import_method': None} import_info_response = {'import-methods': { 'type': 'array', 'description': 'Import methods available.', - 'value': ['glance-direct', 'web-download', 'copy-image']}} + 'value': ['glance-direct', 'web-download', 'copy-image', + 'glance-download']}} def _mock_utils_exit(self, msg): sys.exit(msg) @@ -1451,6 +1453,100 @@ class ShellV2Test(testtools.TestCase): pass mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_glance_download_no_region_and_id( + self, mock_stdin, mock_utils_exit): + expected_msg = ('REMOTE GlANCE REGION and REMOTE IMAGE ID are ' + 'required for glance-download import method. ' + 'Please use --remote-region and ' + '--remote-image-id .') + my_args = self.base_args.copy() + my_args['import_method'] = 'glance-download' + args = self._make_args(my_args) + mock_stdin.isatty = lambda: True + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_glance_download_with_uri( + self, mock_stdin, mock_utils_exit): + expected_msg = ('You cannot specify a --uri with the ' + 'glance-download import method.') + my_args = self.base_args.copy() + my_args['import_method'] = 'glance-download' + my_args['remote_region'] = 'REGION2' + my_args['remote_image_id'] = 'IMG2' + my_args['uri'] = 'https://example.com/some/stuff' + args = self._make_args(my_args) + mock_stdin.isatty = lambda: True + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_glance_download_with_file( + self, mock_stdin, mock_utils_exit): + expected_msg = ('You cannot specify a --file with the ' + 'glance-download import method.') + my_args = self.base_args.copy() + my_args['import_method'] = 'glance-download' + my_args['remote_region'] = 'REGION2' + my_args['remote_image_id'] = 'IMG2' + my_args['file'] = 'my.browncow' + args = self._make_args(my_args) + mock_stdin.isatty = lambda: True + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + + @mock.patch('glanceclient.common.utils.exit') + @mock.patch('sys.stdin', autospec=True) + def test_neg_image_create_via_import_glance_download_with_data( + self, mock_stdin, mock_utils_exit): + expected_msg = ('You cannot pass data via stdin with the ' + 'glance-download import method.') + my_args = self.base_args.copy() + my_args['import_method'] = 'glance-download' + my_args['remote_region'] = 'REGION2' + my_args['remote_image_id'] = 'IMG2' + args = self._make_args(my_args) + mock_stdin.isatty = lambda: False + mock_utils_exit.side_effect = self._mock_utils_exit + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_info.return_value = self.import_info_response + try: + test_shell.do_image_create_via_import(self.gc, args) + self.fail("utils.exit should have been called") + except SystemExit: + pass + mock_utils_exit.assert_called_once_with(expected_msg) + @mock.patch('glanceclient.common.utils.exit') @mock.patch('sys.stdin', autospec=True) def test_neg_image_create_via_import_bad_method( @@ -2114,13 +2210,16 @@ class ShellV2Test(testtools.TestCase): mock_import.return_value = None test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( - 'IMG-01', 'glance-direct', None, backend=None, - all_stores=None, allow_failure=True, stores=None) + 'IMG-01', 'glance-direct', uri=None, + remote_region=None, remote_image_id=None, + remote_service_interface=None, + backend=None, all_stores=None, + allow_failure=True, stores=None) def test_image_import_web_download(self): args = self._make_args( {'id': 'IMG-01', 'uri': 'http://example.com/image.qcow', - 'import_method': 'web-download'}) + 'import_method': 'web-download'}) with mock.patch.object(self.gc.images, 'image_import') as mock_import: with mock.patch.object(self.gc.images, 'get') as mocked_get: with mock.patch.object(self.gc.images, @@ -2133,7 +2232,33 @@ class ShellV2Test(testtools.TestCase): test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( 'IMG-01', 'web-download', - 'http://example.com/image.qcow', + uri='http://example.com/image.qcow', + remote_region=None, remote_image_id=None, + remote_service_interface=None, + all_stores=None, allow_failure=True, + backend=None, stores=None) + + def test_image_import_glance_download(self): + args = self._make_args( + {'id': 'IMG-01', 'uri': None, 'remote-region': 'REGION2', + 'remote-image-id': 'IMG-02', + 'import_method': 'glance-download', + 'remote-service-interface': 'public'}) + with mock.patch.object(self.gc.images, 'image_import') as mock_import: + with mock.patch.object(self.gc.images, 'get') as mocked_get: + with mock.patch.object(self.gc.images, + 'get_import_info') as mocked_info: + mocked_get.return_value = {'status': 'queued', + 'container_format': 'bare', + 'disk_format': 'raw'} + mocked_info.return_value = self.import_info_response + mock_import.return_value = None + test_shell.do_image_import(self.gc, args) + mock_import.assert_called_once_with( + 'IMG-01', 'glance-download', + uri=None, remote_region='REGION2', + remote_image_id='IMG-02', + remote_service_interface='public', all_stores=None, allow_failure=True, backend=None, stores=None) @@ -2175,9 +2300,11 @@ class ShellV2Test(testtools.TestCase): mock_import.return_value = None test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( - 'IMG-02', 'glance-direct', None, all_stores=None, - allow_failure=True, stores=['site1', 'site2'], - backend=None) + 'IMG-02', 'glance-direct', uri=None, + remote_region=None, remote_image_id=None, + remote_service_interface=None, + all_stores=None, allow_failure=True, + stores=['site1', 'site2'], backend=None) @mock.patch('glanceclient.common.utils.print_image') @mock.patch('glanceclient.v2.shell._validate_backend') @@ -2197,9 +2324,11 @@ class ShellV2Test(testtools.TestCase): mock_import.return_value = None test_shell.do_image_import(self.gc, args) mock_import.assert_called_once_with( - 'IMG-02', 'copy-image', None, all_stores=None, - allow_failure=True, stores=['file1', 'file2'], - backend=None) + 'IMG-02', 'copy-image', uri=None, + remote_region=None, remote_image_id=None, + remote_service_interface=None, + all_stores=None, allow_failure=True, + stores=['file1', 'file2'], backend=None) @mock.patch('glanceclient.common.utils.exit') def test_neg_image_import_copy_image_not_active( diff --git a/glanceclient/v2/images.py b/glanceclient/v2/images.py index eeb5ee12..67e5f7e0 100644 --- a/glanceclient/v2/images.py +++ b/glanceclient/v2/images.py @@ -353,8 +353,9 @@ class Controller(object): @utils.add_req_id_to_object() def image_import(self, image_id, method='glance-direct', uri=None, - backend=None, stores=None, allow_failure=True, - all_stores=None): + remote_region=None, remote_image_id=None, + remote_service_interface=None, backend=None, + stores=None, allow_failure=True, all_stores=None): """Import Image via method.""" headers = {} url = '/v2/images/%s/import' % image_id @@ -370,6 +371,13 @@ class Controller(object): if allow_failure: data['all_stores_must_succeed'] = False + if remote_region and remote_image_id: + if remote_service_interface: + data['method']['glance_service_interface'] = \ + remote_service_interface + data['method']['glance_region'] = remote_region + data['method']['glance_image_id'] = remote_image_id + if uri: if method == 'web-download': data['method']['uri'] = uri diff --git a/glanceclient/v2/shell.py b/glanceclient/v2/shell.py index 84e36395..773c1986 100644 --- a/glanceclient/v2/shell.py +++ b/glanceclient/v2/shell.py @@ -148,6 +148,14 @@ def do_image_create(gc, args): 'record if no import-method and no data is supplied')) @utils.arg('--uri', metavar='', default=None, help=_('URI to download the external image.')) +@utils.arg('--remote-region', metavar='', default=None, + help=_('REMOTE_GLANCE_REGION to download the image.')) +@utils.arg('--remote-image-id', metavar='', default=None, + help=_('The IMAGE ID of the image of remote glance, which needs' + 'to be imported with glance-download')) +@utils.arg('--remote-service-interface', metavar='', + default='public', + help=_('The Remote Glance Service Interface for glance-download')) @utils.arg('--store', metavar='', default=utils.env('OS_IMAGE_STORE', default=None), help='Backend store to upload image to.') @@ -293,6 +301,22 @@ def do_image_create_via_import(gc, args): utils.exit("You cannot pass data via stdin with the web-download " "import method.") + if args.import_method == 'glance-download': + if not (args.remote_region and args.remote_image_id): + utils.exit("REMOTE GlANCE REGION and REMOTE IMAGE ID are " + "required for glance-download import method. " + "Please use --remote-region and " + "--remote-image-id .") + if args.uri: + utils.exit("You cannot specify a --uri with the glance-download " + "import method.") + if file_name: + utils.exit("You cannot specify a --file with the glance-download " + "import method.") + if using_stdin: + utils.exit("You cannot pass data via stdin with the " + "glance-download import method.") + # process image = gc.images.create(**fields) try: @@ -726,6 +750,14 @@ def do_image_stage(gc, args): '"image-stage".')) @utils.arg('--uri', metavar='', default=None, help=_('URI to download the external image.')) +@utils.arg('--remote-region', metavar='', default=None, + help=_('REMOTE GLANCE REGION to download the image.')) +@utils.arg('--remote-image-id', metavar='', default=None, + help=_('The IMAGE ID of the image of remote glance, which needs' + 'to be imported with glance-download')) +@utils.arg('--remote-service-interface', metavar='', + default='public', + help=_('The Remote Glance Service Interface for glance-download')) @utils.arg('id', metavar='', help=_('ID of image to import.')) @utils.arg('--store', metavar='', @@ -757,6 +789,10 @@ def do_image_import(gc, args): stores = getattr(args, "stores", None) all_stores = getattr(args, "os_all_stores", None) allow_failure = getattr(args, "os_allow_failure", True) + uri = getattr(args, "uri", None) + remote_region = getattr(args, "remote-region", None) + remote_image_id = getattr(args, "remote-image-id", None) + remote_service_interface = getattr(args, "remote-service-interface", None) if not getattr(args, 'from_create', False): if (args.store and (stores or all_stores)) or (stores and all_stores): @@ -800,6 +836,20 @@ def do_image_import(gc, args): utils.exit("Import method should be 'web-download' if URI is " "provided.") + if args.import_method == 'glance-download' and \ + not (remote_region and remote_image_id): + utils.exit("Provide REMOTE_IMAGE_ID and remote-region for " + "'glance-download' import method.") + if remote_region and args.import_method != 'glance-download': + utils.exit("Import method should be 'glance-download' if " + "REMOTE REGION is provided.") + if remote_image_id and args.import_method != 'glance-download': + utils.exit("Import method should be 'glance-download' if " + "REMOTE IMAGE ID is provided.") + if remote_service_interface and args.import_method != 'glance-download': + utils.exit("Import method should be 'glance-download' if " + "REMOTE SERVICE INTERFACE is provided.") + if args.import_method == 'copy-image' and not (stores or all_stores): utils.exit("Provide either --stores or --all-stores for " "'copy-image' import method.") @@ -827,10 +877,12 @@ def do_image_import(gc, args): "an image with status 'active'.") # finally, do the import - gc.images.image_import(args.id, args.import_method, args.uri, - backend=backend, - stores=stores, all_stores=all_stores, - allow_failure=allow_failure) + gc.images.image_import(args.id, args.import_method, uri=uri, + remote_region=remote_region, + remote_image_id=remote_image_id, + remote_service_interface=remote_service_interface, + backend=backend, stores=stores, + all_stores=all_stores, allow_failure=allow_failure) image = gc.images.get(args.id) utils.print_image(image) diff --git a/releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml b/releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml new file mode 100644 index 00000000..d449e314 --- /dev/null +++ b/releasenotes/notes/add-support-for-glance-download-import-method-10525254db3e8e7a.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add support for new ``glance-download`` image-import method to + import image from another glance/region in federated deployment.