"Fossies" - the Fresh Open Source Software Archive

Member "glance-20.0.1/glance/tests/unit/async_/flows/test_api_image_import.py" (12 Aug 2020, 11325 Bytes) of package /linux/misc/openstack/glance-20.0.1.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. See also the latest Fossies "Diffs" side-by-side code changes report for "test_api_image_import.py": 20.0.0_vs_20.0.1.

    1 # Copyright 2018 Verizon Wireless
    2 # All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 
   16 from unittest import mock
   17 
   18 from glance_store import exceptions as store_exceptions
   19 from oslo_config import cfg
   20 
   21 import glance.async_.flows.api_image_import as import_flow
   22 from glance.common.exception import ImportTaskError
   23 from glance import context
   24 from glance import gateway
   25 import glance.tests.utils as test_utils
   26 
   27 from cursive import exception as cursive_exception
   28 
   29 CONF = cfg.CONF
   30 
   31 TASK_TYPE = 'api_image_import'
   32 TASK_ID1 = 'dbbe7231-020f-4311-87e1-5aaa6da56c02'
   33 IMAGE_ID1 = '41f5b3b0-f54c-4cef-bd45-ce3e376a142f'
   34 UUID1 = 'c80a1a6c-bd1f-41c5-90ee-81afedb1d58d'
   35 TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
   36 
   37 
   38 class TestApiImageImportTask(test_utils.BaseTestCase):
   39 
   40     def setUp(self):
   41         super(TestApiImageImportTask, self).setUp()
   42 
   43         self.wd_task_input = {
   44             "import_req": {
   45                 "method": {
   46                     "name": "web-download",
   47                     "uri": "http://example.com/image.browncow"
   48                 }
   49             }
   50         }
   51 
   52         self.gd_task_input = {
   53             "import_req": {
   54                 "method": {
   55                     "name": "glance-direct"
   56                 }
   57             }
   58         }
   59 
   60         self.mock_task_repo = mock.MagicMock()
   61         self.mock_image_repo = mock.MagicMock()
   62 
   63     @mock.patch('glance.async_.flows.api_image_import._VerifyStaging.__init__')
   64     @mock.patch('taskflow.patterns.linear_flow.Flow.add')
   65     @mock.patch('taskflow.patterns.linear_flow.__init__')
   66     def _pass_uri(self, mock_lf_init, mock_flow_add, mock_VS_init,
   67                   uri, file_uri, import_req):
   68         flow_kwargs = {"task_id": TASK_ID1,
   69                        "task_type": TASK_TYPE,
   70                        "task_repo": self.mock_task_repo,
   71                        "image_repo": self.mock_image_repo,
   72                        "image_id": IMAGE_ID1,
   73                        "import_req": import_req}
   74 
   75         mock_lf_init.return_value = None
   76         mock_VS_init.return_value = None
   77 
   78         self.config(node_staging_uri=uri)
   79         import_flow.get_flow(**flow_kwargs)
   80         mock_VS_init.assert_called_with(TASK_ID1, TASK_TYPE,
   81                                         self.mock_task_repo,
   82                                         file_uri)
   83 
   84     def test_get_flow_handles_node_uri_with_ending_slash(self):
   85         test_uri = 'file:///some/where/'
   86         expected_uri = '{0}{1}'.format(test_uri, IMAGE_ID1)
   87         self._pass_uri(uri=test_uri, file_uri=expected_uri,
   88                        import_req=self.gd_task_input['import_req'])
   89         self._pass_uri(uri=test_uri, file_uri=expected_uri,
   90                        import_req=self.wd_task_input['import_req'])
   91 
   92     def test_get_flow_handles_node_uri_without_ending_slash(self):
   93         test_uri = 'file:///some/where'
   94         expected_uri = '{0}/{1}'.format(test_uri, IMAGE_ID1)
   95         self._pass_uri(uri=test_uri, file_uri=expected_uri,
   96                        import_req=self.wd_task_input['import_req'])
   97         self._pass_uri(uri=test_uri, file_uri=expected_uri,
   98                        import_req=self.gd_task_input['import_req'])
   99 
  100 
  101 class TestImportToStoreTask(test_utils.BaseTestCase):
  102 
  103     def setUp(self):
  104         super(TestImportToStoreTask, self).setUp()
  105         self.gateway = gateway.Gateway()
  106         self.context = context.RequestContext(user_id=TENANT1,
  107                                               project_id=TENANT1,
  108                                               overwrite=False)
  109         self.img_factory = self.gateway.get_image_factory(self.context)
  110 
  111     def test_raises_when_image_deleted(self):
  112         img_repo = mock.MagicMock()
  113         image_import = import_flow._ImportToStore(TASK_ID1, TASK_TYPE,
  114                                                   img_repo, "http://url",
  115                                                   IMAGE_ID1, "store1", False,
  116                                                   True)
  117         image = self.img_factory.new_image(image_id=UUID1)
  118         image.status = "deleted"
  119         img_repo.get.return_value = image
  120         self.assertRaises(ImportTaskError, image_import.execute)
  121 
  122     @mock.patch("glance.async_.flows.api_image_import.image_import")
  123     def test_remove_store_from_property(self, mock_import):
  124         img_repo = mock.MagicMock()
  125         image_import = import_flow._ImportToStore(TASK_ID1, TASK_TYPE,
  126                                                   img_repo, "http://url",
  127                                                   IMAGE_ID1, "store1", True,
  128                                                   True)
  129         extra_properties = {"os_glance_importing_to_stores": "store1,store2"}
  130         image = self.img_factory.new_image(image_id=UUID1,
  131                                            extra_properties=extra_properties)
  132         img_repo.get.return_value = image
  133         image_import.execute()
  134         self.assertEqual(
  135             image.extra_properties['os_glance_importing_to_stores'], "store2")
  136 
  137     @mock.patch("glance.async_.flows.api_image_import.image_import")
  138     def test_raises_when_all_stores_must_succeed(self, mock_import):
  139         img_repo = mock.MagicMock()
  140         image_import = import_flow._ImportToStore(TASK_ID1, TASK_TYPE,
  141                                                   img_repo, "http://url",
  142                                                   IMAGE_ID1, "store1", True,
  143                                                   True)
  144         image = self.img_factory.new_image(image_id=UUID1)
  145         img_repo.get.return_value = image
  146         mock_import.set_image_data.side_effect = \
  147             cursive_exception.SignatureVerificationError(
  148                 "Signature verification failed")
  149         self.assertRaises(cursive_exception.SignatureVerificationError,
  150                           image_import.execute)
  151 
  152     @mock.patch("glance.async_.flows.api_image_import.image_import")
  153     def test_doesnt_raise_when_not_all_stores_must_succeed(self, mock_import):
  154         img_repo = mock.MagicMock()
  155         image_import = import_flow._ImportToStore(TASK_ID1, TASK_TYPE,
  156                                                   img_repo, "http://url",
  157                                                   IMAGE_ID1, "store1", False,
  158                                                   True)
  159         image = self.img_factory.new_image(image_id=UUID1)
  160         img_repo.get.return_value = image
  161         mock_import.set_image_data.side_effect = \
  162             cursive_exception.SignatureVerificationError(
  163                 "Signature verification failed")
  164         try:
  165             image_import.execute()
  166             self.assertEqual(image.extra_properties['os_glance_failed_import'],
  167                              "store1")
  168         except cursive_exception.SignatureVerificationError:
  169             self.fail("Exception shouldn't be raised")
  170 
  171 
  172 class TestDeleteFromFS(test_utils.BaseTestCase):
  173     def test_delete_with_backends_deletes(self):
  174         task = import_flow._DeleteFromFS(TASK_ID1, TASK_TYPE)
  175         self.config(enabled_backends='file:foo')
  176         with mock.patch.object(import_flow.store_api, 'delete') as mock_del:
  177             task.execute(mock.sentinel.path)
  178             mock_del.assert_called_once_with(
  179                 mock.sentinel.path,
  180                 'os_glance_staging_store')
  181 
  182     def test_delete_with_backends_delete_fails(self):
  183         self.config(enabled_backends='file:foo')
  184         task = import_flow._DeleteFromFS(TASK_ID1, TASK_TYPE)
  185         with mock.patch.object(import_flow.store_api, 'delete') as mock_del:
  186             mock_del.side_effect = store_exceptions.NotFound(image=IMAGE_ID1,
  187                                                              message='Testing')
  188             # If we didn't swallow this we would explode here
  189             task.execute(mock.sentinel.path)
  190             mock_del.assert_called_once_with(
  191                 mock.sentinel.path,
  192                 'os_glance_staging_store')
  193 
  194             # Raise something unexpected and make sure it bubbles up
  195             mock_del.side_effect = RuntimeError
  196             self.assertRaises(RuntimeError,
  197                               task.execute, mock.sentinel.path)
  198 
  199     @mock.patch('os.path.exists')
  200     @mock.patch('os.unlink')
  201     def test_delete_without_backends_exists(self, mock_unlink, mock_exists):
  202         mock_exists.return_value = True
  203         task = import_flow._DeleteFromFS(TASK_ID1, TASK_TYPE)
  204         task.execute('1234567foo')
  205         # FIXME(danms): I have no idea why the code arbitrarily snips
  206         # the first seven characters from the path. Need a comment or
  207         # *something*.
  208         mock_unlink.assert_called_once_with('foo')
  209 
  210         mock_unlink.reset_mock()
  211         mock_unlink.side_effect = OSError(123, 'failed')
  212         # Make sure we swallow the OSError and don't explode
  213         task.execute('1234567foo')
  214 
  215     @mock.patch('os.path.exists')
  216     @mock.patch('os.unlink')
  217     def test_delete_without_backends_missing(self, mock_unlink, mock_exists):
  218         mock_exists.return_value = False
  219         task = import_flow._DeleteFromFS(TASK_ID1, TASK_TYPE)
  220         task.execute('foo')
  221         mock_unlink.assert_not_called()
  222 
  223 
  224 class TestVerifyImageStateTask(test_utils.BaseTestCase):
  225     def test_verify_active_status(self):
  226         fake_img = mock.MagicMock(status='active')
  227         mock_repo = mock.MagicMock()
  228         mock_repo.get.return_value = fake_img
  229 
  230         task = import_flow._VerifyImageState(TASK_ID1, TASK_TYPE,
  231                                              mock_repo, IMAGE_ID1,
  232                                              'anything!')
  233 
  234         task.execute()
  235 
  236         fake_img.status = 'importing'
  237         self.assertRaises(import_flow._NoStoresSucceeded,
  238                           task.execute)
  239 
  240     def test_revert_copy_status_unchanged(self):
  241         fake_img = mock.MagicMock(status='active')
  242         mock_repo = mock.MagicMock()
  243         mock_repo.get.return_value = fake_img
  244         task = import_flow._VerifyImageState(TASK_ID1, TASK_TYPE,
  245                                              mock_repo, IMAGE_ID1,
  246                                              'copy-image')
  247         task.revert(mock.sentinel.result)
  248 
  249         # If we are doing copy-image, no state update should be made
  250         mock_repo.save_image.assert_not_called()
  251 
  252     def test_reverts_state_nocopy(self):
  253         fake_img = mock.MagicMock(status='importing')
  254         mock_repo = mock.MagicMock()
  255         mock_repo.get.return_value = fake_img
  256         task = import_flow._VerifyImageState(TASK_ID1, TASK_TYPE,
  257                                              mock_repo, IMAGE_ID1,
  258                                              'glance-direct')
  259         task.revert(mock.sentinel.result)
  260 
  261         # Except for copy-image, image state should revert to queued
  262         mock_repo.save_image.assert_called_once()