From f1fb9e4414f67ab83b118b0cc36553c96db90610 Mon Sep 17 00:00:00 2001 From: Alessandro Pilotti Date: Wed, 1 Mar 2017 18:23:07 +0200 Subject: [PATCH] Adds Azure guest agent plugin Adds the Azure guest agent plugin which configures the WindowsAzureGuestAgent, WindowsAzureTelemetryService and RdAgent services. Change-Id: I713c2b17bb083767272cefa2bf517ff1a0cbb992 Co-Authored-By: Stefan Caraiman Implements: blueprint add-azure-guest-plugin --- cloudbaseinit/metadata/services/base.py | 3 + cloudbaseinit/osutils/base.py | 3 + cloudbaseinit/osutils/windows.py | 24 ++ .../plugins/windows/azureguestagent.py | 267 ++++++++++++++++++ cloudbaseinit/tests/osutils/test_windows.py | 37 ++- .../plugins/windows/test_azureguestagent.py | 233 +++++++++++++++ 6 files changed, 566 insertions(+), 1 deletion(-) create mode 100644 cloudbaseinit/plugins/windows/azureguestagent.py create mode 100644 cloudbaseinit/tests/plugins/windows/test_azureguestagent.py diff --git a/cloudbaseinit/metadata/services/base.py b/cloudbaseinit/metadata/services/base.py index c5ca9b74..d7433e91 100644 --- a/cloudbaseinit/metadata/services/base.py +++ b/cloudbaseinit/metadata/services/base.py @@ -160,6 +160,9 @@ class BaseMetadataService(object): def get_server_certs(self): pass + def get_vm_agent_package_provisioning_data(self): + pass + def get_client_auth_certs(self): pass diff --git a/cloudbaseinit/osutils/base.py b/cloudbaseinit/osutils/base.py index 4942b290..276b3f88 100644 --- a/cloudbaseinit/osutils/base.py +++ b/cloudbaseinit/osutils/base.py @@ -181,6 +181,9 @@ class BaseOSUtils(object): """Enables or disables TRIM delete notifications.""" raise NotImplementedError() + def get_file_version(self, path): + raise NotImplementedError() + def set_path_admin_acls(self, path): raise NotImplementedError() diff --git a/cloudbaseinit/osutils/windows.py b/cloudbaseinit/osutils/windows.py index 46ba8d55..d58a7905 100644 --- a/cloudbaseinit/osutils/windows.py +++ b/cloudbaseinit/osutils/windows.py @@ -26,6 +26,7 @@ import pywintypes import six from six.moves import winreg from tzlocal import windows_tz +import win32api from win32com import client import win32net import win32netcon @@ -1576,3 +1577,26 @@ class WindowsUtils(base.BaseOSUtils): raise exception.CloudbaseInitException( 'Failed to take path ownership.\nOutput: %(out)s\nError:' ' %(err)s' % {'out': out, 'err': err}) + + def check_dotnet_is_installed(self, version): + # See: https://msdn.microsoft.com/en-us/library/hh925568(v=vs.110).aspx + if str(version) != "4": + raise exception.CloudbaseInitException( + "Only checking for version 4 is supported at the moment") + try: + with winreg.OpenKey(winreg.HKEY_LOCAL_MACHINE, 'SOFTWARE\\' + 'Microsoft\\NET Framework Setup\\NDP\\' + 'v%s\\Full' % version) as key: + return winreg.QueryValueEx(key, 'Install')[0] != 0 + except WindowsError as ex: + if ex.winerror == 2: + return False + else: + raise + + def get_file_version(self, path): + info = win32api.GetFileVersionInfo(path, '\\') + ms = info['FileVersionMS'] + ls = info['FileVersionLS'] + return (win32api.HIWORD(ms), win32api.LOWORD(ms), + win32api.HIWORD(ls), win32api.LOWORD(ls)) diff --git a/cloudbaseinit/plugins/windows/azureguestagent.py b/cloudbaseinit/plugins/windows/azureguestagent.py new file mode 100644 index 00000000..4a21208a --- /dev/null +++ b/cloudbaseinit/plugins/windows/azureguestagent.py @@ -0,0 +1,267 @@ +# Copyright (c) 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 datetime +import os +import shutil +import zipfile + +from oslo_log import log as oslo_logging +from six.moves import winreg + +from cloudbaseinit import conf as cloudbaseinit_conf +from cloudbaseinit import exception +from cloudbaseinit.osutils import factory as osutils_factory +from cloudbaseinit.plugins.common import base + +CONF = cloudbaseinit_conf.CONF +LOG = oslo_logging.getLogger(__name__) + +SERVICE_NAME_RDAGENT = "RdAgent" +SERVICE_NAME_WAGUESTAGENT = "WindowsAzureGuestAgent" +SERVICE_NAME_WA_TELEMETRY = "WindowsAzureTelemetryService" +RDAGENT_FILENAME = "WaAppAgent.exe" + +GUEST_AGENT_FILENAME = "Microsoft.Azure.Agent.Windows.exe" +NANO_VMAGENT_FILENAME = "WaSvc.exe" +GUEST_AGENT_EVENTNAME = "Global\AzureAgentStopRequest" + +LOGMAN_TRACE_NOT_RUNNING = 0x80300104 +LOGMAN_TRACE_NOT_FOUND = 0x80300002 + +GUEST_AGENT_ROOT_PATH = "WindowsAzure" +PACKAGES_ROOT_PATH = "Packages" +GUEST_AGENT_SOURCE_PATH = '$$\\OEM\GuestAgent' + +VM_AGENT_PACKAGE = "VmAgent_Nano.zip" + + +class AzureGuestAgentPlugin(base.BasePlugin): + @staticmethod + def _check_delete_service(osutils, service_name): + if osutils.check_service_exists(service_name): + svc_status = osutils.get_service_status(service_name) + if svc_status != osutils.SERVICE_STATUS_STOPPED: + osutils.stop_service(service_name, wait=True) + osutils.delete_service(service_name) + + @staticmethod + def _remove_agent_services(osutils): + LOG.info("Stopping and removing any existing Azure guest agent " + "services") + for service_name in [ + SERVICE_NAME_RDAGENT, SERVICE_NAME_WAGUESTAGENT, + SERVICE_NAME_WA_TELEMETRY]: + AzureGuestAgentPlugin._check_delete_service( + osutils, service_name) + + @staticmethod + def _remove_azure_dirs(): + for path in [GUEST_AGENT_ROOT_PATH, PACKAGES_ROOT_PATH]: + full_path = os.path.join(os.getenv("SystemDrive"), "\\", path) + if os.path.exists(full_path): + LOG.info("Removing folder: %s", full_path) + try: + shutil.rmtree(full_path) + except Exception as ex: + LOG.error("Failed to remove path: %s", full_path) + LOG.exception(ex) + + @staticmethod + def _set_registry_vm_type(vm_type="IAAS"): + with winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\Windows Azure") as key: + winreg.SetValueEx(key, "VMType", 0, winreg.REG_SZ, vm_type) + + @staticmethod + def _set_registry_ga_params(install_version, install_timestamp): + with winreg.CreateKey(winreg.HKEY_LOCAL_MACHINE, + "SOFTWARE\\Microsoft\\GuestAgent") as key: + + install_version_str = "%s.%s.%s.%s" % install_version + winreg.SetValueEx( + key, "Incarnation", 0, winreg.REG_SZ, install_version_str) + + install_timestamp_str = install_timestamp.strftime( + '%m/%d/%Y %I:%M:%S %p') + winreg.SetValueEx( + key, "VmProvisionedAt", 0, winreg.REG_SZ, + install_timestamp_str) + + @staticmethod + def _configure_vm_agent(osutils, vm_agent_target_path): + vm_agent_zip_path = os.path.join(os.getenv("SystemDrive"), '\\', + "Windows", "NanoGuestAgent", + VM_AGENT_PACKAGE) + vm_agent_log_path = os.path.join(os.getenv("SystemDrive"), '\\', + GUEST_AGENT_ROOT_PATH, "Logs") + if not os.path.exists(vm_agent_log_path): + os.makedirs(vm_agent_log_path) + with zipfile.ZipFile(vm_agent_zip_path) as zf: + zf.extractall(vm_agent_target_path) + vm_agent_service_path = os.path.join( + vm_agent_target_path, NANO_VMAGENT_FILENAME) + vm_agent_service_path = ("{service_path} -name {agent_name} -ownLog " + "{log_path}\\W_svc.log -svcLog {log_path}" + "\\S_svc.log -event {event_name} -- " + "{vm_agent_target_path}\\" + "{guest_agent}".format( + service_path=vm_agent_service_path, + agent_name=SERVICE_NAME_WAGUESTAGENT, + log_path=vm_agent_log_path, + event_name=GUEST_AGENT_EVENTNAME, + vm_agent_target_path=vm_agent_target_path, + guest_agent=GUEST_AGENT_FILENAME)) + + osutils.create_service( + SERVICE_NAME_WAGUESTAGENT, SERVICE_NAME_WAGUESTAGENT, + vm_agent_service_path, osutils.SERVICE_START_MODE_MANUAL) + + @staticmethod + def _configure_rd_agent(osutils, ga_target_path): + rd_agent_service_path = os.path.join( + ga_target_path, RDAGENT_FILENAME) + # TODO(alexpilotti): Add a retry here as the service could have been + # marked for deletion + osutils.create_service( + SERVICE_NAME_RDAGENT, SERVICE_NAME_RDAGENT, + rd_agent_service_path, osutils.SERVICE_START_MODE_MANUAL) + + path = os.path.join(ga_target_path, "TransparentInstaller.dll") + ga_version = osutils.get_file_version(path) + ga_install_time = datetime.datetime.now() + + AzureGuestAgentPlugin._set_registry_vm_type() + AzureGuestAgentPlugin._set_registry_ga_params( + ga_version, ga_install_time) + + @staticmethod + def _stop_event_trace(osutils, name, ets=False): + return AzureGuestAgentPlugin._run_logman(osutils, "stop", name, ets) + + @staticmethod + def _delete_event_trace(osutils, name): + return AzureGuestAgentPlugin._run_logman(osutils, "delete", name) + + @staticmethod + def _run_logman(osutils, action, name, ets=False): + args = ["logman.exe"] + if ets: + args += ["-ets"] + args += [action, name] + (out, err, ret_val) = osutils.execute_system32_process(args) + if ret_val not in [ + 0, LOGMAN_TRACE_NOT_RUNNING, LOGMAN_TRACE_NOT_FOUND]: + LOG.error( + 'logman failed.\nExit code: %(ret_val)s\n' + 'Output: %(out)s\nError: %(err)s', + {'ret_val': hex(ret_val), 'out': out, 'err': err}) + + @staticmethod + def _stop_ga_event_traces(osutils): + LOG.info("Stopping Azure guest agent event traces") + AzureGuestAgentPlugin._stop_event_trace(osutils, "GAEvents") + AzureGuestAgentPlugin._stop_event_trace(osutils, "RTEvents") + AzureGuestAgentPlugin._stop_event_trace( + osutils, "WindowsAzure-GuestAgent-Metrics", ets=True) + AzureGuestAgentPlugin._stop_event_trace( + osutils, "WindowsAzure-GuestAgent-Diagnostic", ets=True) + + @staticmethod + def _delete_ga_event_traces(osutils): + LOG.info("Deleting Azure guest agent event traces") + AzureGuestAgentPlugin._delete_event_trace(osutils, "GAEvents") + AzureGuestAgentPlugin._delete_event_trace(osutils, "RTEvents") + + @staticmethod + def _get_guest_agent_source_path(osutils): + base_paths = osutils.get_logical_drives() + for base_path in base_paths: + path = os.path.join(base_path, GUEST_AGENT_SOURCE_PATH) + if os.path.exists(path): + return path + raise exception.CloudbaseInitException( + "Azure guest agent source folder not found") + + def execute(self, service, shared_data): + provisioning_data = service.get_vm_agent_package_provisioning_data() + if not provisioning_data: + LOG.info("Azure guest agent provisioning data not present") + elif not provisioning_data.get("provision"): + LOG.info("Skipping Azure guest agent provisioning as by metadata " + "request") + else: + osutils = osutils_factory.get_os_utils() + + self._remove_agent_services(osutils) + # TODO(alexpilotti): Check for processes that might still be + # running + self._remove_azure_dirs() + + if not osutils.is_nano_server(): + ga_package_name = provisioning_data.get("package_name") + if not ga_package_name: + raise exception.ItemNotFoundException( + "Azure guest agent package_name not found in metadata") + LOG.debug("Azure guest agent package name: %s", + ga_package_name) + + ga_path = self._get_guest_agent_source_path(osutils) + ga_zip_path = os.path.join(ga_path, ga_package_name) + if not os.path.exists(ga_zip_path): + raise exception.CloudbaseInitException( + "Azure guest agent package file not found: %s" % + ga_zip_path) + + self._stop_ga_event_traces(osutils) + self._delete_ga_event_traces(osutils) + + ga_target_path = os.path.join( + os.getenv("SystemDrive"), '\\', GUEST_AGENT_ROOT_PATH, + "Packages") + + if os.path.exists(ga_target_path): + shutil.rmtree(ga_target_path) + os.makedirs(ga_target_path) + + with zipfile.ZipFile(ga_zip_path) as zf: + zf.extractall(ga_target_path) + + self._configure_rd_agent(osutils, ga_target_path) + + if not osutils.check_dotnet_is_installed("4"): + LOG.warn("The .Net framework 4.5 or greater is required " + "by the Azure guest agent") + else: + osutils.set_service_start_mode( + SERVICE_NAME_RDAGENT, + osutils.SERVICE_START_MODE_AUTOMATIC) + osutils.start_service(SERVICE_NAME_RDAGENT) + else: + vm_agent_target_path = os.path.join( + os.getenv("SystemDrive"), '\\', GUEST_AGENT_ROOT_PATH, + "Packages", "GuestAgent") + if not os.path.exists(vm_agent_target_path): + os.makedirs(vm_agent_target_path) + self._configure_vm_agent(osutils, vm_agent_target_path) + + osutils.set_service_start_mode( + SERVICE_NAME_WAGUESTAGENT, + osutils.SERVICE_START_MODE_AUTOMATIC) + osutils.start_service(SERVICE_NAME_WAGUESTAGENT) + + return 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 42000fc4..6e0386c9 100644 --- a/cloudbaseinit/tests/osutils/test_windows.py +++ b/cloudbaseinit/tests/osutils/test_windows.py @@ -52,6 +52,7 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase): self._pywintypes_mock = mock.MagicMock() self._pywintypes_mock.error = fake.FakeError self._pywintypes_mock.com_error = fake.FakeComError + self._win32api_mock = mock.MagicMock() self._win32com_mock = mock.MagicMock() self._win32process_mock = mock.MagicMock() self._win32security_mock = mock.MagicMock() @@ -72,7 +73,8 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase): _module_patcher = mock.patch.dict( 'sys.modules', - {'win32com': self._win32com_mock, + {'win32api': self._win32api_mock, + 'win32com': self._win32com_mock, 'win32process': self._win32process_mock, 'win32security': self._win32security_mock, 'win32net': self._win32net_mock, @@ -2552,3 +2554,36 @@ class TestWindowsUtils(testutils.CloudbaseInitTestBase): def test_take_path_ownership(self): self._test_take_path_ownership() + + def _test_check_dotnet_is_installed(self, version): + if str(version) != "4": + self.assertRaises(exception.CloudbaseInitException, + self._winutils.check_dotnet_is_installed, + version) + return + res = self._winutils.check_dotnet_is_installed(version) + key = self._winreg_mock.OpenKey.return_value.__enter__.return_value + self._winreg_mock.OpenKey.assert_called_with( + self._winreg_mock.HKEY_LOCAL_MACHINE, + 'SOFTWARE\\Microsoft\\NET Framework Setup\\NDP\\' + 'v%s\\Full' % version) + self._winreg_mock.QueryValueEx.assert_called_with( + key, 'Install') + self.assertTrue(res) + + def test_check_dotnet_is_installed_not_v4(self): + fake_version = 1 + self._test_check_dotnet_is_installed(fake_version) + + def test_check_dotnet_is_installed_v4(self): + fake_version = 4 + self._test_check_dotnet_is_installed(fake_version) + + def test_get_file_version(self): + mock_path = mock.sentinel.fake_path + mock_info = mock.MagicMock() + self._win32api_mock.GetFileVersionInfo.return_value = mock_info + res = self._winutils.get_file_version(mock_path) + self._win32api_mock.GetFileVersionInfo.assert_called_once_with( + mock_path, '\\') + self.assertIsNotNone(res) diff --git a/cloudbaseinit/tests/plugins/windows/test_azureguestagent.py b/cloudbaseinit/tests/plugins/windows/test_azureguestagent.py new file mode 100644 index 00000000..4ea9c9c5 --- /dev/null +++ b/cloudbaseinit/tests/plugins/windows/test_azureguestagent.py @@ -0,0 +1,233 @@ +# Copyright (c) 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 datetime +import importlib +import os +import unittest + +try: + import unittest.mock as mock +except ImportError: + import mock + +from cloudbaseinit import conf as cloudbaseinit_conf +from cloudbaseinit import exception +from cloudbaseinit.plugins.common import base +from cloudbaseinit.tests import testutils + +CONF = cloudbaseinit_conf.CONF +MODPATH = "cloudbaseinit.plugins.windows.azureguestagent" + + +class AzureGuestAgentPluginTest(unittest.TestCase): + + def setUp(self): + self.mock_wmi = mock.MagicMock() + self._moves_mock = mock.MagicMock() + patcher = mock.patch.dict( + "sys.modules", + { + "wmi": self.mock_wmi, + "six.moves": self._moves_mock + } + ) + patcher.start() + self.addCleanup(patcher.stop) + self._winreg_mock = self._moves_mock.winreg + self._azureguestagent = importlib.import_module(MODPATH) + self._azureagentplugin = self._azureguestagent.AzureGuestAgentPlugin() + self.snatcher = testutils.LogSnatcher(MODPATH) + + def test_check_delete_service(self): + mock_osutils = mock.Mock() + mock_service_name = mock.sentinel.name + self._azureagentplugin._check_delete_service(mock_osutils, + mock_service_name) + mock_osutils.check_service_exists.assert_called_once_with( + mock_service_name) + mock_osutils.get_service_status.assert_called_once_with( + mock_service_name) + mock_osutils.stop_service.assert_called_once_with(mock_service_name, + wait=True) + mock_osutils.delete_service.assert_called_once_with(mock_service_name) + + @mock.patch(MODPATH + ".AzureGuestAgentPlugin._check_delete_service") + def test_remove_agent_services(self, mock_check_delete_service): + mock_osutils = mock.Mock() + expected_logs = ["Stopping and removing any existing Azure guest " + "agent services"] + with self.snatcher: + self._azureagentplugin._remove_agent_services(mock_osutils) + self.assertEqual(self.snatcher.output, expected_logs) + self.assertEqual(mock_check_delete_service.call_count, 3) + + @mock.patch("shutil.rmtree") + @mock.patch("os.path.exists") + @mock.patch("os.getenv") + def test_remove_azure_dirs(self, mock_os_getenv, + mock_exists, mock_rmtree): + mock_rmtree.side_effect = (None, Exception) + mock_exists.return_value = True + with self.snatcher: + self._azureagentplugin._remove_azure_dirs() + mock_os_getenv.assert_called_with("SystemDrive") + self.assertEqual(mock_os_getenv.call_count, 2) + self.assertEqual(mock_exists.call_count, 2) + self.assertEqual(mock_rmtree.call_count, 2) + + def test_set_registry_vm_type(self): + vm_type = mock.sentinel.vm + key_name = "SOFTWARE\\Microsoft\\Windows Azure" + + self._azureagentplugin._set_registry_vm_type(vm_type) + key = self._winreg_mock.CreateKey.return_value.__enter__.return_value + self._winreg_mock.CreateKey.assert_called_with( + self._winreg_mock.HKEY_LOCAL_MACHINE, key_name) + self._winreg_mock.SetValueEx.assert_called_once_with( + key, "VMType", 0, self._winreg_mock.REG_SZ, vm_type) + + def test_set_registry_ga_params(self): + fake_version = (1, 2, 3, 4) + fake_install_timestamp = datetime.datetime.now() + key_name = "SOFTWARE\\Microsoft\\GuestAgent" + + self._azureagentplugin._set_registry_ga_params(fake_version, + fake_install_timestamp) + + self._winreg_mock.CreateKey.assert_called_with( + self._winreg_mock.HKEY_LOCAL_MACHINE, key_name) + self.assertEqual(self._winreg_mock.SetValueEx.call_count, 2) + + @mock.patch(MODPATH + ".AzureGuestAgentPlugin._set_registry_ga_params") + @mock.patch(MODPATH + ".AzureGuestAgentPlugin._set_registry_vm_type") + def test_configure_rd_agent(self, mock_set_registry_vm_type, + mock_set_registry_ga_params): + mock_osutils = mock.Mock() + fake_ga_path = "C:\\" + expected_rd_path = os.path.join(fake_ga_path, + self._azureguestagent.RDAGENT_FILENAME) + expected_path = os.path.join(fake_ga_path, "TransparentInstaller.dll") + self._azureagentplugin._configure_rd_agent(mock_osutils, fake_ga_path) + mock_osutils.create_service.assert_called_once_with( + self._azureguestagent.SERVICE_NAME_RDAGENT, + self._azureguestagent.SERVICE_NAME_RDAGENT, + expected_rd_path, + mock_osutils.SERVICE_START_MODE_MANUAL) + mock_osutils.get_file_version.assert_called_once_with(expected_path) + mock_set_registry_vm_type.assert_called_once_with() + + @mock.patch(MODPATH + ".AzureGuestAgentPlugin._run_logman") + def test_stop_event_trace(self, mock_run_logman): + mock_osutils = mock.Mock() + fake_name = mock.sentinel.event_name + res = self._azureagentplugin._stop_event_trace(mock_osutils, fake_name) + mock_run_logman.assert_called_once_with(mock_osutils, "stop", + fake_name, False) + self.assertIsNotNone(res) + + @mock.patch(MODPATH + ".AzureGuestAgentPlugin._run_logman") + def test_delete_event_trace(self, mock_run_logman): + mock_osutils = mock.Mock() + fake_name = mock.sentinel.event_name + res = self._azureagentplugin._delete_event_trace(mock_osutils, + fake_name) + mock_run_logman.assert_called_once_with(mock_osutils, "delete", + fake_name) + self.assertIsNotNone(res) + + def test_run_logman(self): + mock_osutils = mock.Mock() + fake_action = mock.sentinel.action + fake_name = mock.sentinel.cmd_name + expected_args = ["logman.exe", "-ets", fake_action, fake_name] + mock_osutils.execute_system32_process.return_value = (0, 0, -1) + self._azureagentplugin._run_logman(mock_osutils, fake_action, + fake_name, True) + mock_osutils.execute_system32_process.assert_called_once_with( + expected_args) + + @mock.patch(MODPATH + ".AzureGuestAgentPlugin._stop_event_trace") + def test_stop_ga_event_traces(self, mock_stop_event_trace): + mock_osutils = mock.Mock() + expected_logs = ["Stopping Azure guest agent event traces"] + with self.snatcher: + self._azureagentplugin._stop_ga_event_traces(mock_osutils) + self.assertEqual(mock_stop_event_trace.call_count, 4) + self.assertEqual(self.snatcher.output, expected_logs) + + @mock.patch(MODPATH + ".AzureGuestAgentPlugin._delete_event_trace") + def test_delete_ga_event_traces(self, mock_delete_event_trace): + mock_osutils = mock.Mock() + expected_logs = ["Deleting Azure guest agent event traces"] + with self.snatcher: + self._azureagentplugin._delete_ga_event_traces(mock_osutils) + self.assertEqual(mock_delete_event_trace.call_count, 2) + self.assertEqual(self.snatcher.output, expected_logs) + + @mock.patch("os.path.exists") + def _test_get_guest_agent_source_path(self, mock_exists, + drives=None, exists=False): + mock_osutils = mock.Mock() + mock_exists.return_value = exists + mock_osutils.get_logical_drives.return_value = drives + if not exists: + self.assertRaises( + exception.CloudbaseInitException, + self._azureagentplugin._get_guest_agent_source_path, + mock_osutils) + return + res = self._azureagentplugin._get_guest_agent_source_path(mock_osutils) + self.assertIsNotNone(res) + + def test_get_guest_agent_source_path_no_agent(self): + self._test_get_guest_agent_source_path(drives=[]) + + def test_get_guest_agent_source_path(self): + mock_drive = "C:" + self._test_get_guest_agent_source_path(drives=[mock_drive], + exists=True) + + def _test_execute(self, + provisioning_data=None, expected_logs=None): + mock_service = mock.Mock() + mock_sharedata = mock.Mock() + expected_res = (base.PLUGIN_EXECUTION_DONE, False) + (mock_service.get_vm_agent_package_provisioning_data. + return_value) = provisioning_data + if not provisioning_data or not provisioning_data.get("provision"): + with self.snatcher: + res = self._azureagentplugin.execute(mock_service, + mock_sharedata) + (mock_service.get_vm_agent_package_provisioning_data. + assert_called_once_with()) + self.assertEqual(res, expected_res) + self.assertEqual(self.snatcher.output, expected_logs) + return + + def test_execute_no_data(self): + expected_logs = ["Azure guest agent provisioning data not present"] + self._test_execute(expected_logs=expected_logs) + + def test_execute_no_provision(self): + mock_data = {"provision": None} + expected_logs = ["Skipping Azure guest agent provisioning " + "as by metadata request"] + self._test_execute(provisioning_data=mock_data, + expected_logs=expected_logs) + + def test_get_os_requirements(self): + expected_res = ('win32', (6, 1)) + res = self._azureagentplugin.get_os_requirements() + self.assertEqual(res, expected_res)