"Fossies" - the Fresh Open Source Software Archive

Member "glance-24.1.0/glance/tests/unit/async_/test_async.py" (8 Jun 2022, 12007 Bytes) of package /linux/misc/openstack/glance-24.1.0.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 last Fossies "Diffs" side-by-side code changes report for "test_async.py": 20.0.1_vs_20.1.0.

    1 # Copyright 2014 OpenStack Foundation
    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 
   17 from unittest import mock
   18 
   19 import futurist
   20 import glance_store as store
   21 from oslo_config import cfg
   22 from taskflow.patterns import linear_flow
   23 
   24 import glance.async_
   25 from glance.async_.flows import api_image_import
   26 import glance.tests.utils as test_utils
   27 
   28 CONF = cfg.CONF
   29 
   30 
   31 class TestTaskExecutor(test_utils.BaseTestCase):
   32 
   33     def setUp(self):
   34         super(TestTaskExecutor, self).setUp()
   35         self.context = mock.Mock()
   36         self.task_repo = mock.Mock()
   37         self.image_repo = mock.Mock()
   38         self.image_factory = mock.Mock()
   39         self.executor = glance.async_.TaskExecutor(self.context,
   40                                                    self.task_repo,
   41                                                    self.image_repo,
   42                                                    self.image_factory)
   43 
   44     def test_begin_processing(self):
   45         # setup
   46         task_id = mock.ANY
   47         task_type = mock.ANY
   48         task = mock.Mock()
   49 
   50         with mock.patch.object(
   51                 glance.async_.TaskExecutor,
   52                 '_run') as mock_run:
   53             self.task_repo.get.return_value = task
   54             self.executor.begin_processing(task_id)
   55 
   56         # assert the call
   57         mock_run.assert_called_once_with(task_id, task_type)
   58 
   59     def test_with_admin_repo(self):
   60         admin_repo = mock.MagicMock()
   61         executor = glance.async_.TaskExecutor(self.context,
   62                                               self.task_repo,
   63                                               self.image_repo,
   64                                               self.image_factory,
   65                                               admin_repo=admin_repo)
   66         self.assertEqual(admin_repo, executor.admin_repo)
   67 
   68 
   69 class TestImportTaskFlow(test_utils.BaseTestCase):
   70 
   71     def setUp(self):
   72         super(TestImportTaskFlow, self).setUp()
   73         store.register_opts(CONF)
   74         self.config(default_store='file',
   75                     stores=['file', 'http'],
   76                     filesystem_store_datadir=self.test_dir,
   77                     group="glance_store")
   78         self.config(enabled_import_methods=[
   79             'glance-direct', 'web-download', 'copy-image'])
   80         self.config(node_staging_uri='file:///tmp/staging')
   81         store.create_stores(CONF)
   82         self.base_flow = ['ImageLock', 'ConfigureStaging', 'ImportToStore',
   83                           'DeleteFromFS', 'VerifyImageState',
   84                           'CompleteTask']
   85         self.import_plugins = ['Convert_Image',
   86                                'Decompress_Image',
   87                                'InjectMetadataProperties']
   88 
   89     def _get_flow(self, import_req=None):
   90         inputs = {
   91             'task_id': mock.sentinel.task_id,
   92             'task_type': mock.MagicMock(),
   93             'task_repo': mock.MagicMock(),
   94             'image_repo': mock.MagicMock(),
   95             'image_id': mock.MagicMock(),
   96             'import_req': import_req or mock.MagicMock(),
   97             'context': mock.MagicMock(),
   98         }
   99         inputs['image_repo'].get.return_value = mock.MagicMock(
  100             extra_properties={'os_glance_import_task': mock.sentinel.task_id})
  101         flow = api_image_import.get_flow(**inputs)
  102         return flow
  103 
  104     def _get_flow_tasks(self, flow):
  105         flow_comp = []
  106         for c, p in flow.iter_nodes():
  107             if isinstance(c, linear_flow.Flow):
  108                 flow_comp += self._get_flow_tasks(c)
  109             else:
  110                 name = str(c).split('-')
  111                 if len(name) > 1:
  112                     flow_comp.append(name[1])
  113         return flow_comp
  114 
  115     def test_get_default_flow(self):
  116         # This test will ensure that without import plugins
  117         # and without internal plugins flow builds with the
  118         # base_flow components
  119         flow = self._get_flow()
  120 
  121         flow_comp = self._get_flow_tasks(flow)
  122         # assert flow has all the tasks
  123         self.assertEqual(len(self.base_flow), len(flow_comp))
  124         for c in self.base_flow:
  125             self.assertIn(c, flow_comp)
  126 
  127     def test_get_flow_web_download_enabled(self):
  128         # This test will ensure that without import plugins
  129         # and with web-download plugin flow builds with
  130         # base_flow components and '_WebDownload'
  131         import_req = {
  132             'method': {
  133                 'name': 'web-download',
  134                 'uri': 'http://cloud.foo/image.qcow2'
  135             }
  136         }
  137 
  138         flow = self._get_flow(import_req=import_req)
  139 
  140         flow_comp = self._get_flow_tasks(flow)
  141         # assert flow has all the tasks
  142         self.assertEqual(len(self.base_flow) + 1, len(flow_comp))
  143         for c in self.base_flow:
  144             self.assertIn(c, flow_comp)
  145         self.assertIn('WebDownload', flow_comp)
  146 
  147     @mock.patch.object(store, 'get_store_from_store_identifier')
  148     def test_get_flow_copy_image_enabled(self, mock_store):
  149         # This test will ensure that without import plugins
  150         # and with copy-image plugin flow builds with
  151         # base_flow components and '_CopyImage'
  152         import_req = {
  153             'method': {
  154                 'name': 'copy-image',
  155                 'stores': ['fake-store']
  156             }
  157         }
  158 
  159         mock_store.return_value = mock.Mock()
  160         flow = self._get_flow(import_req=import_req)
  161 
  162         flow_comp = self._get_flow_tasks(flow)
  163         # assert flow has all the tasks
  164         self.assertEqual(len(self.base_flow) + 1, len(flow_comp))
  165         for c in self.base_flow:
  166             self.assertIn(c, flow_comp)
  167         self.assertIn('CopyImage', flow_comp)
  168 
  169     def test_get_flow_with_all_plugins_enabled(self):
  170         # This test will ensure that flow includes import plugins
  171         # and base flow
  172         self.config(image_import_plugins=['image_conversion',
  173                                           'image_decompression',
  174                                           'inject_image_metadata'],
  175                     group='image_import_opts')
  176 
  177         flow = self._get_flow()
  178 
  179         flow_comp = self._get_flow_tasks(flow)
  180         # assert flow has all the tasks (base_flow + plugins)
  181         plugins = CONF.image_import_opts.image_import_plugins
  182         self.assertEqual(len(self.base_flow) + len(plugins), len(flow_comp))
  183         for c in self.base_flow:
  184             self.assertIn(c, flow_comp)
  185         for c in self.import_plugins:
  186             self.assertIn(c, flow_comp)
  187 
  188     @mock.patch.object(store, 'get_store_from_store_identifier')
  189     def test_get_flow_copy_image_not_includes_import_plugins(
  190             self, mock_store):
  191         # This test will ensure that flow does not includes import
  192         # plugins as import method is copy image
  193         self.config(image_import_plugins=['image_conversion',
  194                                           'image_decompression',
  195                                           'inject_image_metadata'],
  196                     group='image_import_opts')
  197 
  198         mock_store.return_value = mock.Mock()
  199         import_req = {
  200             'method': {
  201                 'name': 'copy-image',
  202                 'stores': ['fake-store']
  203             }
  204         }
  205 
  206         flow = self._get_flow(import_req=import_req)
  207 
  208         flow_comp = self._get_flow_tasks(flow)
  209         # assert flow has all the tasks (just base and conversion)
  210         self.assertEqual(len(self.base_flow) + 1, len(flow_comp))
  211         for c in self.base_flow:
  212             self.assertIn(c, flow_comp)
  213         self.assertIn('CopyImage', flow_comp)
  214 
  215 
  216 @mock.patch('glance.async_._THREADPOOL_MODEL', new=None)
  217 class TestSystemThreadPoolModel(test_utils.BaseTestCase):
  218     def test_eventlet_model(self):
  219         model_cls = glance.async_.EventletThreadPoolModel
  220         self.assertEqual(futurist.GreenThreadPoolExecutor,
  221                          model_cls.get_threadpool_executor_class())
  222 
  223     def test_native_model(self):
  224         model_cls = glance.async_.NativeThreadPoolModel
  225         self.assertEqual(futurist.ThreadPoolExecutor,
  226                          model_cls.get_threadpool_executor_class())
  227 
  228     @mock.patch('glance.async_.ThreadPoolModel.get_threadpool_executor_class')
  229     def test_base_model_spawn(self, mock_gte):
  230         pool_cls = mock.MagicMock()
  231         pool_cls.configure_mock(__name__='fake')
  232         mock_gte.return_value = pool_cls
  233 
  234         model = glance.async_.ThreadPoolModel()
  235         result = model.spawn(print, 'foo', bar='baz')
  236 
  237         pool = pool_cls.return_value
  238 
  239         # Make sure the default size was passed to the executor
  240         pool_cls.assert_called_once_with(1)
  241 
  242         # Make sure we submitted the function to the executor
  243         pool.submit.assert_called_once_with(print, 'foo', bar='baz')
  244 
  245         # This isn't used anywhere, but make sure we get the future
  246         self.assertEqual(pool.submit.return_value, result)
  247 
  248     def test_model_map(self):
  249         model = glance.async_.EventletThreadPoolModel()
  250         results = model.map(lambda s: s.upper(), ['a', 'b', 'c'])
  251         self.assertEqual(['A', 'B', 'C'], list(results))
  252 
  253     @mock.patch('glance.async_.ThreadPoolModel.get_threadpool_executor_class')
  254     def test_base_model_init_with_size(self, mock_gte):
  255         mock_gte.return_value.__name__ = 'TestModel'
  256         with mock.patch.object(glance.async_, 'LOG') as mock_log:
  257             glance.async_.ThreadPoolModel(123)
  258             mock_log.debug.assert_called_once_with(
  259                 'Creating threadpool model %r with size %i',
  260                 'TestModel', 123)
  261         mock_gte.return_value.assert_called_once_with(123)
  262 
  263     def test_set_threadpool_model_native(self):
  264         glance.async_.set_threadpool_model('native')
  265         self.assertEqual(glance.async_.NativeThreadPoolModel,
  266                          glance.async_._THREADPOOL_MODEL)
  267 
  268     def test_set_threadpool_model_eventlet(self):
  269         glance.async_.set_threadpool_model('eventlet')
  270         self.assertEqual(glance.async_.EventletThreadPoolModel,
  271                          glance.async_._THREADPOOL_MODEL)
  272 
  273     def test_set_threadpool_model_unknown(self):
  274         # Unknown threadpool models are not tolerated
  275         self.assertRaises(RuntimeError,
  276                           glance.async_.set_threadpool_model,
  277                           'danthread9000')
  278 
  279     def test_set_threadpool_model_again(self):
  280         # Setting the model to the same thing is fine
  281         glance.async_.set_threadpool_model('native')
  282         glance.async_.set_threadpool_model('native')
  283 
  284     def test_set_threadpool_model_different(self):
  285         glance.async_.set_threadpool_model('native')
  286         # The model cannot be switched at runtime
  287         self.assertRaises(RuntimeError,
  288                           glance.async_.set_threadpool_model,
  289                           'eventlet')
  290 
  291     def test_set_threadpool_model_log(self):
  292         with mock.patch.object(glance.async_, 'LOG') as mock_log:
  293             glance.async_.set_threadpool_model('eventlet')
  294             mock_log.info.assert_called_once_with(
  295                 'Threadpool model set to %r', 'EventletThreadPoolModel')
  296 
  297     def test_get_threadpool_model(self):
  298         glance.async_.set_threadpool_model('native')
  299         self.assertEqual(glance.async_.NativeThreadPoolModel,
  300                          glance.async_.get_threadpool_model())
  301 
  302     def test_get_threadpool_model_unset(self):
  303         # If the model is not set, we get an AssertionError
  304         self.assertRaises(AssertionError,
  305                           glance.async_.get_threadpool_model)