From 148855f74e0c7c6507daa9c6e0aff7388edaca05 Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Wed, 29 Aug 2018 01:37:53 +0300 Subject: [PATCH] Add PyMI as requirement Since PyMI requires the MI API, available only since Windows 8 / Windows Server 2012 or as an addon on some previous versions, we need to ensure that we fall back to the legacy WMI module if PyMI cannot be loaded. Partially-Implements: blueprint json-network-config Change-Id: Ibded4cee3d3d7dc39e53bca12e015c9ef83c7f3d --- cloudbaseinit/osutils/windows.py | 3 +- .../tests/utils/windows/test_bootconfig.py | 2 +- .../tests/utils/windows/test_wmi_loader.py | 67 +++++++++++++++++++ cloudbaseinit/utils/windows/bootconfig.py | 4 +- cloudbaseinit/utils/windows/licensing.py | 5 +- cloudbaseinit/utils/windows/rdp.py | 4 +- .../windows/storage/wsm_storage_manager.py | 4 +- cloudbaseinit/utils/windows/wmi_loader.py | 44 ++++++++++++ requirements.txt | 1 + 9 files changed, 126 insertions(+), 8 deletions(-) create mode 100644 cloudbaseinit/tests/utils/windows/test_wmi_loader.py create mode 100644 cloudbaseinit/utils/windows/wmi_loader.py diff --git a/cloudbaseinit/osutils/windows.py b/cloudbaseinit/osutils/windows.py index fcbd8050..e4ece759 100644 --- a/cloudbaseinit/osutils/windows.py +++ b/cloudbaseinit/osutils/windows.py @@ -34,7 +34,6 @@ import win32process import win32security import win32service import winerror -import wmi from cloudbaseinit import exception from cloudbaseinit.osutils import base @@ -42,7 +41,9 @@ from cloudbaseinit.utils.windows import disk from cloudbaseinit.utils.windows import network from cloudbaseinit.utils.windows import privilege from cloudbaseinit.utils.windows import timezone +from cloudbaseinit.utils.windows import wmi_loader +wmi = wmi_loader.wmi() LOG = oslo_logging.getLogger(__name__) AF_INET6 = 23 diff --git a/cloudbaseinit/tests/utils/windows/test_bootconfig.py b/cloudbaseinit/tests/utils/windows/test_bootconfig.py index b807a025..36a09aa0 100644 --- a/cloudbaseinit/tests/utils/windows/test_bootconfig.py +++ b/cloudbaseinit/tests/utils/windows/test_bootconfig.py @@ -33,7 +33,7 @@ class BootConfigTest(unittest.TestCase): self._wmi_mock = mock.MagicMock() self._module_patcher = mock.patch.dict( 'sys.modules', { - 'wmi': self._wmi_mock}) + "wmi": self._wmi_mock}) self.snatcher = testutils.LogSnatcher(MODPATH) self._module_patcher.start() self.bootconfig = importlib.import_module(MODPATH) diff --git a/cloudbaseinit/tests/utils/windows/test_wmi_loader.py b/cloudbaseinit/tests/utils/windows/test_wmi_loader.py new file mode 100644 index 00000000..1d8cd472 --- /dev/null +++ b/cloudbaseinit/tests/utils/windows/test_wmi_loader.py @@ -0,0 +1,67 @@ +# Copyright 2018 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 importlib +import os +import unittest + +try: + import unittest.mock as mock +except ImportError: + import mock + +from cloudbaseinit import exception + +MODPATH = "cloudbaseinit.utils.windows.wmi_loader" + + +class WMILoaderTests(unittest.TestCase): + def test_load_pymi(self): + with mock.patch.dict('sys.modules', {'wmi': mock.sentinel.wmi}): + wmi_loader = importlib.import_module(MODPATH) + self.assertEqual(mock.sentinel.wmi, wmi_loader.wmi()) + + @mock.patch('imp.load_source') + @mock.patch('os.path.isfile') + def test_load_legacy_wmi(self, mock_isfile, mock_load_source): + mock_isfile.return_value = True + + mock_site = mock.MagicMock() + fake_site_path = "fake_site_path" + mock_site.getsitepackages.return_value = [fake_site_path] + mock_load_source.return_value = mock.sentinel.wmi + + with mock.patch.dict('sys.modules', {'wmi': None, 'site': mock_site}): + wmi_loader = importlib.import_module(MODPATH) + self.assertEqual(mock.sentinel.wmi, wmi_loader.wmi()) + + fake_wmi_path = os.path.join(fake_site_path, "wmi.py") + mock_isfile.assert_called_once_with(fake_wmi_path) + mock_load_source.assert_called_once_with("wmi", fake_wmi_path) + + @mock.patch('os.path.isfile') + def test_load_legacy_wmi_fail(self, mock_isfile): + mock_isfile.return_value = False + + mock_site = mock.MagicMock() + fake_site_path = "fake_site_path" + mock_site.getsitepackages.return_value = [fake_site_path] + + with mock.patch.dict('sys.modules', {'wmi': None, 'site': mock_site}): + wmi_loader = importlib.import_module(MODPATH) + self.assertRaises( + exception.ItemNotFoundException, wmi_loader.wmi) + + fake_wmi_path = os.path.join(fake_site_path, "wmi.py") + mock_isfile.assert_called_once_with(fake_wmi_path) diff --git a/cloudbaseinit/utils/windows/bootconfig.py b/cloudbaseinit/utils/windows/bootconfig.py index 54710ae1..5a18a1ab 100644 --- a/cloudbaseinit/utils/windows/bootconfig.py +++ b/cloudbaseinit/utils/windows/bootconfig.py @@ -13,11 +13,13 @@ # under the License. from oslo_log import log as oslo_logging -import wmi from cloudbaseinit import constant from cloudbaseinit import exception from cloudbaseinit.osutils import factory as osutils_factory +from cloudbaseinit.utils.windows import wmi_loader + +wmi = wmi_loader.wmi() LOG = oslo_logging.getLogger(__name__) diff --git a/cloudbaseinit/utils/windows/licensing.py b/cloudbaseinit/utils/windows/licensing.py index 8bd9d3bf..8e70a369 100644 --- a/cloudbaseinit/utils/windows/licensing.py +++ b/cloudbaseinit/utils/windows/licensing.py @@ -14,14 +14,15 @@ import os -import wmi - from oslo_log import log as oslo_logging from cloudbaseinit import constant from cloudbaseinit import exception from cloudbaseinit.osutils import factory as osutils_factory from cloudbaseinit.utils.windows import productkeys +from cloudbaseinit.utils.windows import wmi_loader + +wmi = wmi_loader.wmi() LOG = oslo_logging.getLogger(__name__) diff --git a/cloudbaseinit/utils/windows/rdp.py b/cloudbaseinit/utils/windows/rdp.py index ff2069f6..b53e8676 100644 --- a/cloudbaseinit/utils/windows/rdp.py +++ b/cloudbaseinit/utils/windows/rdp.py @@ -12,13 +12,13 @@ # License for the specific language governing permissions and limitations # under the License. -import wmi - from oslo_log import log as oslo_logging from six.moves import winreg from cloudbaseinit import exception +from cloudbaseinit.utils.windows import wmi_loader +wmi = wmi_loader.wmi() LOG = oslo_logging.getLogger(__name__) diff --git a/cloudbaseinit/utils/windows/storage/wsm_storage_manager.py b/cloudbaseinit/utils/windows/storage/wsm_storage_manager.py index 0179c857..7a494afa 100644 --- a/cloudbaseinit/utils/windows/storage/wsm_storage_manager.py +++ b/cloudbaseinit/utils/windows/storage/wsm_storage_manager.py @@ -13,7 +13,6 @@ # under the License. import ctypes -import wmi from oslo_log import log as oslo_logging from six.moves import winreg @@ -21,6 +20,9 @@ from six.moves import winreg from cloudbaseinit import exception from cloudbaseinit.utils.windows import kernel32 from cloudbaseinit.utils.windows.storage import base +from cloudbaseinit.utils.windows import wmi_loader + +wmi = wmi_loader.wmi() LOG = oslo_logging.getLogger(__name__) diff --git a/cloudbaseinit/utils/windows/wmi_loader.py b/cloudbaseinit/utils/windows/wmi_loader.py new file mode 100644 index 00000000..4faf7e98 --- /dev/null +++ b/cloudbaseinit/utils/windows/wmi_loader.py @@ -0,0 +1,44 @@ +# Copyright 2018 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 imp +import os +import site + +from oslo_log import log as oslo_logging + +from cloudbaseinit import exception + +LOG = oslo_logging.getLogger(__name__) + + +def wmi(): + try: + # PyMI depends on the MI API, not available by default on systems older + # than Windows 8 / Windows Server 2012 + import wmi + return wmi + except ImportError: + LOG.debug("Couldn't load PyMI module, using legacy WMI") + + wmi_path = None + for packages_path in site.getsitepackages(): + path = os.path.join(packages_path, "wmi.py") + if os.path.isfile(path): + wmi_path = path + break + if wmi_path is None: + raise exception.ItemNotFoundException("wmi module not found") + + return imp.load_source("wmi", wmi_path) diff --git a/requirements.txt b/requirements.txt index 455d9d2f..02ff0e48 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,5 +14,6 @@ requests untangle==1.1.1 pywin32;sys_platform=="win32" comtypes;sys_platform=="win32" +pymi;sys_platform=="win32" wmi;sys_platform=="win32" tzlocal;sys_platform=="win32"