Skip to content

Commit aaa60bc

Browse files
authored
Disable auto OS updates in YUM (#93)
* Disabling auto OS updates in yum * minor corrections * Addressing review comments * Resolving bugs in diable auto OS updates in yum and addresing some feedback comments * Merging with master and adressing some more review comments * Adding UTs and addressing some more PR comments * updating extension version to 1.6.27
1 parent 3e68b1b commit aaa60bc

File tree

12 files changed

+683
-63
lines changed

12 files changed

+683
-63
lines changed

src/core/src/bootstrap/Constants.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def __iter__(self):
3030
UNKNOWN = "Unknown"
3131

3232
# Extension version (todo: move to a different file)
33-
EXT_VERSION = "1.6.26"
33+
EXT_VERSION = "1.6.27"
3434

3535
# Runtime environments
3636
TEST = 'Test'
@@ -112,6 +112,13 @@ class AutomaticOSPatchStates(EnumBackport):
112112
DISABLED = "Disabled"
113113
ENABLED = "Enabled"
114114

115+
# List of auto OS update services in Yum
116+
# todo: move to yumpackagemanager
117+
class YumAutoOSUpdateServices(EnumBackport):
118+
YUM_CRON = "yum-cron"
119+
DNF_AUTOMATIC = "dnf-automatic"
120+
PACKAGEKIT = "packagekit"
121+
115122
# auto assessment states
116123
class AutoAssessmentStates(EnumBackport):
117124
UNKNOWN = "Unknown"

src/core/src/bootstrap/EnvLayer.py

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -274,7 +274,7 @@ def resolve_path(self, requested_path):
274274
else:
275275
return requested_path
276276

277-
def open(self, file_path, mode):
277+
def open(self, file_path, mode, raise_if_not_found=True):
278278
""" Provides a file handle to the file_path requested using implicit redirection where required """
279279
real_path = self.resolve_path(file_path)
280280
for i in range(0, Constants.MAX_FILE_OPERATION_RETRY_COUNT):
@@ -284,24 +284,29 @@ def open(self, file_path, mode):
284284
if i < Constants.MAX_FILE_OPERATION_RETRY_COUNT:
285285
time.sleep(i + 1)
286286
else:
287-
raise Exception("Unable to open {0} (retries exhausted). Error: {1}.".format(str(real_path), repr(error)))
287+
error_message = "Unable to open file (retries exhausted). [File={0}][Error={1}][RaiseIfNotFound={2}].".format(str(real_path), repr(error), str(raise_if_not_found))
288+
if raise_if_not_found:
289+
raise Exception(error_message)
290+
else:
291+
print(error_message)
292+
return None
288293

289-
def __obtain_file_handle(self, file_path_or_handle, mode='a+'):
294+
def __obtain_file_handle(self, file_path_or_handle, mode='a+', raise_if_not_found=True):
290295
""" Pass-through for handle. For path, resolution and handle open with retry. """
291296
is_path = False
292297
if isinstance(file_path_or_handle, str) or not(hasattr(file_path_or_handle, 'read') and hasattr(file_path_or_handle, 'write')):
293298
is_path = True
294-
file_path_or_handle = self.open(file_path_or_handle, mode)
299+
file_path_or_handle = self.open(file_path_or_handle, mode, raise_if_not_found)
295300
file_handle = file_path_or_handle
296301
return file_handle, is_path
297302

298-
def read_with_retry(self, file_path_or_handle):
303+
def read_with_retry(self, file_path_or_handle, raise_if_not_found=True):
299304
""" Reads all content from a given file path in a single operation """
300305
operation = "FILE_READ"
301306

302307
# only fully emulate non_exclusive_files from the real recording; exclusive files can be redirected and handled in emulator scenarios
303308
if not self.__emulator_enabled or (isinstance(file_path_or_handle, str) and os.path.basename(file_path_or_handle) not in self.__non_exclusive_files):
304-
file_handle, was_path = self.__obtain_file_handle(file_path_or_handle, 'r')
309+
file_handle, was_path = self.__obtain_file_handle(file_path_or_handle, 'r', raise_if_not_found)
305310
for i in range(0, Constants.MAX_FILE_OPERATION_RETRY_COUNT):
306311
try:
307312
value = file_handle.read()
@@ -313,7 +318,12 @@ def read_with_retry(self, file_path_or_handle):
313318
if i < Constants.MAX_FILE_OPERATION_RETRY_COUNT:
314319
time.sleep(i + 1)
315320
else:
316-
raise Exception("Unable to read from {0} (retries exhausted). Error: {1}.".format(str(file_path_or_handle), repr(error)))
321+
error_message = "Unable to read file (retries exhausted). [File={0}][Error={1}][RaiseIfNotFound={2}].".format(str(file_path_or_handle), repr(error), str(raise_if_not_found))
322+
if raise_if_not_found:
323+
raise Exception(error_message)
324+
else:
325+
print(error_message)
326+
return None
317327
else:
318328
code, output = self.__read_record(operation)
319329
return output

src/core/src/core_logic/ConfigurePatchingProcessor.py

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,20 @@ def __try_set_patch_mode(self):
6565
try:
6666
self.status_handler.set_current_operation(Constants.CONFIGURE_PATCHING)
6767
self.current_auto_os_patch_state = self.package_manager.get_current_auto_os_patch_state()
68-
self.composite_logger.log_debug("Current Auto OS Patch State is [State={0}]".format(str(self.current_auto_os_patch_state)))
6968

7069
# disable auto OS updates if VM is configured for platform updates only.
7170
# NOTE: this condition will be false for Assessment operations, since patchMode is not sent in the API request
72-
if self.current_auto_os_patch_state == Constants.AutomaticOSPatchStates.ENABLED and self.execution_config.patch_mode == Constants.PatchModes.AUTOMATIC_BY_PLATFORM:
71+
if self.current_auto_os_patch_state != Constants.AutomaticOSPatchStates.DISABLED and self.execution_config.patch_mode == Constants.PatchModes.AUTOMATIC_BY_PLATFORM:
7372
self.package_manager.disable_auto_os_update()
7473

7574
self.current_auto_os_patch_state = self.package_manager.get_current_auto_os_patch_state()
76-
self.composite_logger.log_debug("Current Auto OS Patch State is [State={0}]".format(str(self.current_auto_os_patch_state)))
7775

78-
self.__report_consolidated_configure_patch_status()
76+
if self.current_auto_os_patch_state == Constants.AutomaticOSPatchStates.UNKNOWN:
77+
# NOTE: only sending details in error objects for customer visibility on why patch state is unknown, overall configurepatching status will remain successful
78+
self.__report_consolidated_configure_patch_status(error="Extension attempted but could not disable some of the auto OS update service. Please check if the auto OS services are configured correctly")
79+
else:
80+
self.__report_consolidated_configure_patch_status()
81+
7982
self.composite_logger.log_debug("Completed processing patch mode configuration.")
8083
except Exception as error:
8184
self.composite_logger.log_error("Error while processing patch mode configuration. [Error={0}]".format(repr(error)))

src/core/src/package_managers/AptitudePackageManager.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -395,11 +395,14 @@ def get_current_auto_os_patch_state(self):
395395
self.composite_logger.log("Fetching the current automatic OS patch state on the machine...")
396396
self.__get_current_auto_os_updates_setting_on_machine()
397397
if int(self.unattended_upgrade_value) == 0:
398-
return Constants.AutomaticOSPatchStates.DISABLED
398+
current_auto_os_patch_state = Constants.AutomaticOSPatchStates.DISABLED
399399
elif int(self.unattended_upgrade_value) == 1:
400-
return Constants.AutomaticOSPatchStates.ENABLED
400+
current_auto_os_patch_state = Constants.AutomaticOSPatchStates.ENABLED
401401
else:
402-
return Constants.AutomaticOSPatchStates.UNKNOWN
402+
current_auto_os_patch_state = Constants.AutomaticOSPatchStates.UNKNOWN
403+
404+
self.composite_logger.log_debug("Current Auto OS Patch State is [State={0}]".format(str(current_auto_os_patch_state)))
405+
return current_auto_os_patch_state
403406

404407
def __get_current_auto_os_updates_setting_on_machine(self):
405408
""" Gets all the update settings related to auto OS updates currently set on the machine """
@@ -437,7 +440,22 @@ def backup_image_default_patch_configuration_if_not_exists(self):
437440
""" Records the default system settings for auto OS updates within patch extension artifacts for future reference.
438441
We only log the default system settings a VM comes with, any subsequent updates will not be recorded"""
439442
try:
440-
if not self.image_default_patch_configuration_backup_exists():
443+
image_default_patch_configuration_backup = {}
444+
445+
# read existing backup since it also contains backup from other update services. We need to preserve any existing data with backup file
446+
if self.image_default_patch_configuration_backup_exists():
447+
try:
448+
image_default_patch_configuration_backup = json.loads(self.env_layer.file_system.read_with_retry(self.image_default_patch_configuration_backup_path))
449+
except Exception as error:
450+
self.composite_logger.log_error("Unable to read backup for default patch state. Will attempt to re-write. [Exception={0}]".format(repr(error)))
451+
452+
# verify if existing backup is valid if not, write to backup
453+
is_backup_valid = self.is_image_default_patch_configuration_backup_valid(image_default_patch_configuration_backup)
454+
if is_backup_valid:
455+
self.composite_logger.log_debug("Since extension has a valid backup, no need to log the current settings again. [Default Auto OS update settings={0}] [File path={1}]"
456+
.format(str(image_default_patch_configuration_backup), self.image_default_patch_configuration_backup_path))
457+
else:
458+
self.composite_logger.log_debug("Since the backup is invalid, will add a new backup with the current auto OS update settings")
441459
self.__get_current_auto_os_updates_setting_on_machine()
442460

443461
backup_image_default_patch_configuration_json = {
@@ -462,7 +480,7 @@ def is_image_default_patch_configuration_backup_valid(self, image_default_patch_
462480
self.composite_logger.log_error("Extension does not have a valid backup of the default system configuration settings for auto OS updates.")
463481
return False
464482

465-
def update_os_patch_configuration_sub_setting(self, patch_configuration_sub_setting, value="0"):
483+
def update_os_patch_configuration_sub_setting(self, patch_configuration_sub_setting, value="0", patch_configuration_sub_setting_pattern_to_match=""):
466484
""" Updates (or adds if it doesn't exist) the given patch_configuration_sub_setting with the given value in os_patch_configuration_settings_file """
467485
try:
468486
# note: adding space between the patch_configuration_sub_setting and value since, we will have to do that if we have to add a patch_configuration_sub_setting that did not exist before

src/core/src/package_managers/PackageManager.py

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -348,27 +348,14 @@ def image_default_patch_configuration_backup_exists(self):
348348
self.composite_logger.log_debug("Default system configuration settings for auto OS updates aren't recorded in the extension")
349349
return False
350350

351-
# verify if the existing backup is valid
352-
try:
353-
image_default_patch_configuration_backup = json.loads(self.env_layer.file_system.read_with_retry(self.image_default_patch_configuration_backup_path))
354-
if self.is_image_default_patch_configuration_backup_valid(image_default_patch_configuration_backup):
355-
self.composite_logger.log_debug("Since extension has a valid backup, no need to log the current settings again. "
356-
"[Default Auto OS update settings={0}] [File path={1}]"
357-
.format(str(image_default_patch_configuration_backup), self.image_default_patch_configuration_backup_path))
358-
return True
359-
else:
360-
self.composite_logger.log_error("Since the backup is invalid, will add a new backup with the current auto OS update settings")
361-
return False
362-
except Exception as error:
363-
self.composite_logger.log_error("Unable to read backup for default auto OS update settings. [Exception={0}]".format(repr(error)))
364-
return False
351+
return True
365352

366353
@abstractmethod
367354
def is_image_default_patch_configuration_backup_valid(self, image_default_patch_configuration_backup):
368355
pass
369356

370357
@abstractmethod
371-
def update_os_patch_configuration_sub_setting(self, patch_configuration_sub_setting, value):
358+
def update_os_patch_configuration_sub_setting(self, patch_configuration_sub_setting, value, patch_configuration_sub_setting_pattern_to_match):
372359
pass
373360
# endregion
374361

0 commit comments

Comments
 (0)