From 278468d2e227f7b1d974dea0270b76a218e1d83e Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Wed, 1 Feb 2017 10:20:33 +0200 Subject: [PATCH] Enables or disables TRIM delete notifications. Delete notification (also known as trim or unmap) is a feature that notifies the underlying storage devices that data blocks are no longer in use so they can be freed. Implements: blueprint delete-notifications Co-Authored-By: Micu Matei-Marius Change-Id: I2a1670f921fce1165df48096c6fde8660b21282e --- cloudbaseinit/conf/default.py | 4 ++ cloudbaseinit/osutils/base.py | 4 ++ cloudbaseinit/osutils/windows.py | 10 ++++ cloudbaseinit/plugins/common/trim.py | 36 +++++++++++++ cloudbaseinit/tests/osutils/test_windows.py | 25 +++++++++ .../tests/plugins/common/test_trim.py | 53 +++++++++++++++++++ 6 files changed, 132 insertions(+) create mode 100644 cloudbaseinit/plugins/common/trim.py create mode 100644 cloudbaseinit/tests/plugins/common/test_trim.py diff --git a/cloudbaseinit/conf/default.py b/cloudbaseinit/conf/default.py index c01ddee0..b7073209 100644 --- a/cloudbaseinit/conf/default.py +++ b/cloudbaseinit/conf/default.py @@ -224,6 +224,10 @@ class GlobalOptions(conf_base.Options): help='Volume mount points on which a Windows page file needs ' 'to be created. E.g.: ' '"\\\\?\\GLOBALROOT\\device\\Harddisk1\\Partition1\\"'), + cfg.BoolOpt( + 'trim_enabled', default=False, + help='Enables or disables TRIM delete notifications for ' + 'the underlying storage device.'), ] self._cli_options = [ diff --git a/cloudbaseinit/osutils/base.py b/cloudbaseinit/osutils/base.py index f0a3f3c5..2ff6d04c 100644 --- a/cloudbaseinit/osutils/base.py +++ b/cloudbaseinit/osutils/base.py @@ -139,3 +139,7 @@ class BaseOSUtils(object): def set_real_time_clock_utc(self, utc): raise NotImplementedError() + + def enable_trim(self, enable): + """Enables or disables TRIM delete notifications.""" + raise NotImplementedError() diff --git a/cloudbaseinit/osutils/windows.py b/cloudbaseinit/osutils/windows.py index 4a21daf7..be5023f9 100644 --- a/cloudbaseinit/osutils/windows.py +++ b/cloudbaseinit/osutils/windows.py @@ -1416,3 +1416,13 @@ class WindowsUtils(base.BaseOSUtils): 0, winreg.KEY_ALL_ACCESS) as key: winreg.SetValueEx(key, 'PagingFiles', 0, winreg.REG_MULTI_SZ, values) + + def enable_trim(self, enable): + """Enables or disables TRIM delete notifications.""" + args = ["fsutil.exe", "behavior", "set", "disabledeletenotify", + "0" if enable else "1"] + (out, err, ret_val) = self.execute_system32_process(args) + if ret_val: + raise exception.CloudbaseInitException( + 'TRIM configurating failed.\nOutput: %(out)s\nError:' + ' %(err)s' % {'out': out, 'err': err}) diff --git a/cloudbaseinit/plugins/common/trim.py b/cloudbaseinit/plugins/common/trim.py new file mode 100644 index 00000000..cf5dc74c --- /dev/null +++ b/cloudbaseinit/plugins/common/trim.py @@ -0,0 +1,36 @@ +# Copyright 2017 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from oslo_log import log as oslo_logging + +from cloudbaseinit import conf as cloudbaseinit_conf +from cloudbaseinit.osutils import factory as osutils_factory +from cloudbaseinit.plugins.common import base as plugin_base + +CONF = cloudbaseinit_conf.CONF +LOG = oslo_logging.getLogger(__name__) + + +class TrimConfigPlugin(plugin_base.BasePlugin): + + def execute(self, service, shared_data): + osutils = osutils_factory.get_os_utils() + osutils.enable_trim(CONF.trim_enabled) + LOG.info("TRIM enabled status: %s", CONF.trim_enabled) + + return plugin_base.PLUGIN_EXECUTION_DONE, False + + def get_os_requirements(self): + return 'win32', (6, 1) diff --git a/cloudbaseinit/tests/osutils/test_windows.py b/cloudbaseinit/tests/osutils/test_windows.py index 2278f7e9..a691eebe 100644 --- a/cloudbaseinit/tests/osutils/test_windows.py +++ b/cloudbaseinit/tests/osutils/test_windows.py @@ -2336,3 +2336,28 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase): self._winreg_mock.SetValueEx.assert_called_with( key, 'PagingFiles', 0, self._winreg_mock.REG_MULTI_SZ, expected_values) + + @mock.patch('cloudbaseinit.osutils.windows.WindowsUtils' + '.execute_system32_process') + def _test_trim(self, mock_execute_process, err): + if err: + mock_execute_process.return_value = ("fake out", "", 1) + self.assertRaises(exception.CloudbaseInitException, + self._winutils.enable_trim, True) + else: + args = ["fsutil.exe", "behavior", + "set", "disabledeletenotify"] + + mock_execute_process.return_value = ("fake out", "fake err", 0) + + self._winutils.enable_trim(True) + mock_execute_process.assert_called_with(args + ["0"]) + + self._winutils.enable_trim(False) + mock_execute_process.assert_called_with(args + ["1"]) + + def test_trim(self): + self._test_trim(err=False) + + def test_trim_exception(self): + self._test_trim(err=True) diff --git a/cloudbaseinit/tests/plugins/common/test_trim.py b/cloudbaseinit/tests/plugins/common/test_trim.py new file mode 100644 index 00000000..488ea9f1 --- /dev/null +++ b/cloudbaseinit/tests/plugins/common/test_trim.py @@ -0,0 +1,53 @@ +# Copyright 2017 Cloudbase Solutions Srl +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import unittest + +try: + import unittest.mock as mock +except ImportError: + import mock + +from cloudbaseinit.plugins.common import base +from cloudbaseinit.plugins.common import trim +from cloudbaseinit.tests import testutils + + +class TrimPluginPluginTests(unittest.TestCase): + + def setUp(self): + self._trim_plugin = trim.TrimConfigPlugin() + + @mock.patch('cloudbaseinit.osutils.factory.get_os_utils') + def _test_trim(self, mock_get_os_utils, status): + shared_data = 'fake_shared_data' + mock_os_utils = mock.Mock() + mock_get_os_utils.return_value = mock_os_utils + + with testutils.ConfPatcher('trim_enabled', status): + response = self._trim_plugin.execute(mock.Mock(), shared_data) + + mock_os_utils.enable_trim.assert_called_once_with(status) + self.assertEqual(response, (base.PLUGIN_EXECUTION_DONE, False)) + + def test_trim_enable(self): + self._test_trim(status=True) + + def test_trim_disable(self): + self._test_trim(status=False) + + def test_get_os_requirements(self): + response = self._trim_plugin.get_os_requirements() + + self.assertEqual(response, ('win32', (6, 1)))