diff --git a/anaconda.spec b/anaconda.spec index 2db6bcb98b77a9b9ce8602b561cc5f0383ba1241..846e372d92e3aa9aa3d94f045692b070e1747685 100644 --- a/anaconda.spec +++ b/anaconda.spec @@ -1,7 +1,7 @@ %define _empty_manifest_terminate_build 0 Name: anaconda Version: 33.19 -Release: 16 +Release: 17 Summary: Graphical system installer License: GPLv2+ and MIT URL: http://fedoraproject.org/wiki/Anaconda @@ -29,6 +29,7 @@ Patch9016: bugfix-fix-password-policy.patch Patch9017: add-boot-args-for-smmu-and-video.patch Patch9018: disable-disk-encryption.patch Patch9019: bugfix-set-up-LD_PRELOAD-for-the-Storage-and-Services-module.patch +Patch9020: bugfix-Propagate-a-lazy-proxy-of-the-storage-model.patch Patch6001: anaconda-Fix-stage2-as-default-sources.patch Patch6002: anaconda-Allow-to-detect-devices-with-the-iso9660-file-system.patch @@ -267,6 +268,12 @@ update-desktop-database &> /dev/null || : %{_datadir}/gtk-doc %changelog +* Thu Feb 4 2021 liuxin264 - 33.19-17 +- Type:bugfix +- ID:NA +- SUG:NA +- DESC:fix Propagate a lazy proxy of the storage model + * Thu Jan 14 2021 yuboyun - 33.19-16 - Type:bugfix - ID:NA diff --git a/bugfix-Propagate-a-lazy-proxy-of-the-storage-model.patch b/bugfix-Propagate-a-lazy-proxy-of-the-storage-model.patch new file mode 100644 index 0000000000000000000000000000000000000000..e1642e3d952349c3e2b209423ad758e944290c42 --- /dev/null +++ b/bugfix-Propagate-a-lazy-proxy-of-the-storage-model.patch @@ -0,0 +1,291 @@ +From 39d3a894411e3069cdb14354509153028a48e6c5 Mon Sep 17 00:00:00 2001 +From: Vendula Poncova +Date: Wed, 2 Sep 2020 13:40:36 +0200 +Subject: [PATCH] Propagate a lazy proxy of the storage model + +The storage model for the partitioning modules should be always created lazily. +When we reset the partitioning, the new model shouldn't be created until we try +to work with it. Then we create a new copy of the storage model, hide disks that +are not selected and possibly initialize empty disks. + +There is a problem with modules that need to be able to work with the same copy +of the storage model as the partitioning modules. At this moment, it is only the +device tree module. The suggested solution is to propagate a lazy proxy of the +storage model. It will not trigger the creation of the copy until we try to +access the attributes of the storage model. + +Basically, the device tree module always gets the storage model on demand from +the storage property of the partitioning module. + +Related: rhbz#1868577 +--- + pyanaconda/core/util.py | 37 +++++++ + .../modules/storage/partitioning/base.py | 28 ++++-- + .../module_part_interactive_test.py | 18 ++++ + tests/nosetests/pyanaconda_tests/util_test.py | 97 ++++++++++++++++++- + 4 files changed, 170 insertions(+), 10 deletions(-) + +diff --git a/pyanaconda/core/util.py b/pyanaconda/core/util.py +index 4615f9fd8..60b6ff310 100644 +--- a/pyanaconda/core/util.py ++++ b/pyanaconda/core/util.py +@@ -1431,3 +1431,40 @@ def is_smt_enabled(): + except (IOError, ValueError): + log.warning("Failed to detect SMT.") + return False ++ ++ ++class LazyObject(object): ++ """The lazy object.""" ++ ++ def __init__(self, getter): ++ """Create a proxy of an object. ++ ++ The object might not exist until we call the given ++ function. The function is called only when we try ++ to access the attributes of the object. ++ ++ The returned object is not cached in this class. ++ We call the function every time. ++ ++ :param getter: a function that returns the object ++ """ ++ self._getter = getter ++ ++ @property ++ def _object(self): ++ return self._getter() ++ ++ def __eq__(self, other): ++ return self._object == other ++ ++ def __hash__(self): ++ return self._object.__hash__() ++ ++ def __getattr__(self, name): ++ return getattr(self._object, name) ++ ++ def __setattr__(self, name, value): ++ if name in ("_getter", ): ++ return super().__setattr__(name, value) ++ ++ return setattr(self._object, name, value) +diff --git a/pyanaconda/modules/storage/partitioning/base.py b/pyanaconda/modules/storage/partitioning/base.py +index 989fa0a7b..c8b4b95ac 100644 +--- a/pyanaconda/modules/storage/partitioning/base.py ++++ b/pyanaconda/modules/storage/partitioning/base.py +@@ -17,12 +17,13 @@ + # License and may only be used or replicated with the express permission of + # Red Hat, Inc. + # +-from abc import abstractmethod, abstractproperty ++from abc import abstractmethod + + from blivet.devices import PartitionDevice, TmpFSDevice, LVMLogicalVolumeDevice, \ + LVMVolumeGroupDevice, MDRaidArrayDevice, BTRFSDevice + + from dasbus.server.publishable import Publishable ++from pyanaconda.core.util import LazyObject + from pyanaconda.modules.common.base.base import KickstartBaseModule + from pyanaconda.modules.common.errors.storage import UnavailableStorageError + from pyanaconda.anaconda_loggers import get_module_logger +@@ -45,7 +46,8 @@ class PartitioningModule(KickstartBaseModule, Publishable): + self._selected_disks = [] + self._device_tree_module = None + +- @abstractproperty ++ @property ++ @abstractmethod + def partitioning_method(self): + """Type of the partitioning method.""" + return None +@@ -67,8 +69,22 @@ class PartitioningModule(KickstartBaseModule, Publishable): + + return self._storage_playground + ++ @property ++ def lazy_storage(self): ++ """The lazy storage model. ++ ++ Provides a lazy access to the storage model. This property will not ++ trigger a creation of the storage playground. The playground will be ++ created on the first access of the storage attributes. ++ """ ++ return LazyObject(lambda: self.storage) ++ + def _create_storage_playground(self): + """Prepare the current storage model for partitioning.""" ++ log.debug( ++ "Creating a new storage playground for %s with " ++ "selected disks %s.", self, self._selected_disks ++ ) + storage = self._current_storage.copy() + storage.select_disks(self._selected_disks) + return storage +@@ -77,16 +93,10 @@ class PartitioningModule(KickstartBaseModule, Publishable): + """Update the current storage.""" + self._current_storage = storage + +- if self._device_tree_module: +- self._device_tree_module.on_storage_changed(self.storage) +- + def on_partitioning_reset(self): + """Drop the storage playground.""" + self._storage_playground = None + +- if self._device_tree_module: +- self._device_tree_module.on_storage_changed(self.storage) +- + def on_selected_disks_changed(self, selection): + """Keep the current disk selection.""" + self._selected_disks = selection +@@ -100,7 +110,7 @@ class PartitioningModule(KickstartBaseModule, Publishable): + + if not module: + module = self._create_device_tree() +- module.on_storage_changed(self.storage) ++ module.on_storage_changed(self.lazy_storage) + self._device_tree_module = module + + return module +diff --git a/tests/nosetests/pyanaconda_tests/module_part_interactive_test.py b/tests/nosetests/pyanaconda_tests/module_part_interactive_test.py +index 13d33feab..32fe589b7 100644 +--- a/tests/nosetests/pyanaconda_tests/module_part_interactive_test.py ++++ b/tests/nosetests/pyanaconda_tests/module_part_interactive_test.py +@@ -71,6 +71,24 @@ class InteractivePartitioningInterfaceTestCase(unittest.TestCase): + """Test Method property.""" + self.assertEqual(self.interface.PartitioningMethod, PARTITIONING_METHOD_INTERACTIVE) + ++ @patch_dbus_publish_object ++ def lazy_storage_test(self, publisher): ++ """Make sure that the storage playground is created lazily.""" ++ self.module.on_storage_changed(create_storage()) ++ ++ device_tree_module = self.module.get_device_tree() ++ self.assertIsNone(self.module._storage_playground) ++ ++ device_tree_module.get_disks() ++ self.assertIsNotNone(self.module._storage_playground) ++ ++ self.module.on_partitioning_reset() ++ self.module.on_storage_changed(create_storage()) ++ self.assertIsNone(self.module._storage_playground) ++ ++ device_tree_module.get_actions() ++ self.assertIsNotNone(self.module._storage_playground) ++ + @patch_dbus_publish_object + def get_device_tree_test(self, publisher): + """Test GetDeviceTree.""" +diff --git a/tests/nosetests/pyanaconda_tests/util_test.py b/tests/nosetests/pyanaconda_tests/util_test.py +index 1da8362dc..76f1c4465 100644 +--- a/tests/nosetests/pyanaconda_tests/util_test.py ++++ b/tests/nosetests/pyanaconda_tests/util_test.py +@@ -29,7 +29,7 @@ from unittest.mock import Mock, patch + from pyanaconda.errors import ExitError + from pyanaconda.core.process_watchers import WatchProcesses + from pyanaconda.core import util +-from pyanaconda.core.util import synchronized ++from pyanaconda.core.util import synchronized, LazyObject + from pyanaconda.core.configuration.anaconda import conf + + from timer import timer +@@ -829,3 +829,98 @@ class MiscTests(unittest.TestCase): + ) + self.assertEqual(get_anaconda_version_string(), "1.0") + self.assertEqual(get_anaconda_version_string(build_time_version=True), "1.0-1") ++ ++ ++class LazyObjectTestCase(unittest.TestCase): ++ ++ class Object(object): ++ ++ def __init__(self): ++ self._x = 0 ++ ++ @property ++ def x(self): ++ return self._x ++ ++ @x.setter ++ def x(self, value): ++ self._x = value ++ ++ def f(self, value): ++ self._x += value ++ ++ def setUp(self): ++ self._obj = None ++ ++ @property ++ def obj(self): ++ if not self._obj: ++ self._obj = self.Object() ++ ++ return self._obj ++ ++ @property ++ def lazy_obj(self): ++ return LazyObject(lambda: self.obj) ++ ++ def get_set_test(self): ++ self.assertIsNotNone(self.lazy_obj) ++ self.assertIsNone(self._obj) ++ ++ self.assertEqual(self.lazy_obj.x, 0) ++ self.assertIsNotNone(self._obj) ++ ++ self.obj.x = -10 ++ self.assertEqual(self.obj.x, -10) ++ self.assertEqual(self.lazy_obj.x, -10) ++ ++ self.lazy_obj.x = 10 ++ self.assertEqual(self.obj.x, 10) ++ self.assertEqual(self.lazy_obj.x, 10) ++ ++ self.lazy_obj.f(90) ++ self.assertEqual(self.obj.x, 100) ++ self.assertEqual(self.lazy_obj.x, 100) ++ ++ def eq_test(self): ++ a = object() ++ lazy_a1 = LazyObject(lambda: a) ++ lazy_a2 = LazyObject(lambda: a) ++ ++ self.assertEqual(a, lazy_a1) ++ self.assertEqual(lazy_a1, a) ++ ++ self.assertEqual(a, lazy_a2) ++ self.assertEqual(lazy_a2, a) ++ ++ self.assertEqual(lazy_a1, lazy_a2) ++ self.assertEqual(lazy_a2, lazy_a1) ++ ++ self.assertEqual(lazy_a1, lazy_a1) ++ self.assertEqual(lazy_a2, lazy_a2) ++ ++ def neq_test(self): ++ a = object() ++ lazy_a = LazyObject(lambda: a) ++ ++ b = object() ++ lazy_b = LazyObject(lambda: b) ++ ++ self.assertNotEqual(b, lazy_a) ++ self.assertNotEqual(lazy_a, b) ++ ++ self.assertNotEqual(lazy_a, lazy_b) ++ self.assertNotEqual(lazy_b, lazy_a) ++ ++ def hash_test(self): ++ a = object() ++ lazy_a1 = LazyObject(lambda: a) ++ lazy_a2 = LazyObject(lambda: a) ++ ++ b = object() ++ lazy_b1 = LazyObject(lambda: b) ++ lazy_b2 = LazyObject(lambda: b) ++ ++ self.assertEqual({a, lazy_a1, lazy_a2}, {a}) ++ self.assertEqual({b, lazy_b1, lazy_b2}, {b}) ++ self.assertEqual({lazy_a1, lazy_b2}, {a, b})