-
Notifications
You must be signed in to change notification settings - Fork 15
[Livepatching][MVP] Extension changes to support livepatching in Canonical #341
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
edfb17b
30ca413
d1aa7b6
4f99fe0
dd09f52
e1a9b9f
014eb5c
5a8da3f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -421,4 +421,10 @@ def datetime_string_to_posix_time(datetime_string, format_string): | |
| epoch = datetime.datetime(1970, 1, 1) | ||
| return int((dt - epoch).total_seconds()) | ||
|
|
||
| @staticmethod | ||
| def datetime_iso_basic_string_to_extended_string(datetime_string): | ||
| # type: (str) -> str | ||
| """Converts ISO 8601 basic date format (20240401T000000Z) to extended format (2024-04-01T00:00:00Z).""" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. type information missing for new code. see 413 for example
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| return datetime.datetime.strptime(datetime_string, "%Y%m%dT%H%M%SZ").strftime("%Y-%m-%dT%H:%M:%SZ") | ||
|
|
||
| # endregion - DateTime emulator and extensions | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -92,6 +92,11 @@ def __init__(self, env_layer, composite_logger, execution_parameters): | |
| # EULA config | ||
| self.accept_package_eula = self.__is_eula_accepted_for_all_patches() | ||
|
|
||
| # LivePatch config | ||
| self.livepatch_customer_config_settings = self.__get_livepatch_customer_config_in_json() | ||
| self.is_livepatch_requested = self.__is_livepatch_requested(self.livepatch_customer_config_settings) | ||
| self.is_livepatch_only_requested = self.__is_livepatch_only_requested(self.livepatch_customer_config_settings) | ||
|
Comment on lines
+97
to
+98
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as in the config section - what happens if both are true
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we have this code here if it's not consumed?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See: #341 (comment) |
||
|
|
||
|
rane-rajasi marked this conversation as resolved.
|
||
| def __transform_execution_config_for_auto_assessment(self): | ||
| self.activity_id = str(uuid.uuid4()) | ||
| self.included_classifications_list = self.included_package_name_mask_list = self.excluded_package_name_mask_list = [] | ||
|
|
@@ -246,10 +251,10 @@ def __is_eula_accepted_for_all_patches(self): | |
| try: | ||
| if os.path.exists(Constants.AzGPSPaths.EULA_SETTINGS): | ||
| eula_settings = json.loads(self.env_layer.file_system.read_with_retry(Constants.AzGPSPaths.EULA_SETTINGS) or 'null') | ||
| accept_eula_for_all_patches = self.__fetch_specific_eula_setting(eula_settings, Constants.EulaSettings.ACCEPT_EULA_FOR_ALL_PATCHES) | ||
| accepted_by = self.__fetch_specific_eula_setting(eula_settings, Constants.EulaSettings.ACCEPTED_BY) | ||
| last_modified = self.__fetch_specific_eula_setting(eula_settings, Constants.EulaSettings.LAST_MODIFIED) | ||
| if accept_eula_for_all_patches is not None and accept_eula_for_all_patches in [True, 'True', 'true', '1', 1]: | ||
| accept_eula_for_all_patches = self.__fetch_specific_setting(eula_settings, Constants.EulaSettings.ACCEPT_EULA_FOR_ALL_PATCHES) | ||
| accepted_by = self.__fetch_specific_setting(eula_settings, Constants.EulaSettings.ACCEPTED_BY) | ||
| last_modified = self.__fetch_specific_setting(eula_settings, Constants.EulaSettings.LAST_MODIFIED) | ||
| if self.__is_truthy(accept_eula_for_all_patches): | ||
| is_eula_accepted = True | ||
| self.composite_logger.log_debug("EULA config values from disk: [AcceptEULAForAllPatches={0}] [AcceptedBy={1}] [LastModified={2}]. Computed value of [IsEULAAccepted={3}]" | ||
| .format(str(accept_eula_for_all_patches), str(accepted_by), str(last_modified), str(is_eula_accepted))) | ||
|
|
@@ -260,10 +265,72 @@ def __is_eula_accepted_for_all_patches(self): | |
|
|
||
| return is_eula_accepted | ||
|
|
||
| def __get_livepatch_customer_config_in_json(self): | ||
| # type: () -> dict | ||
| """ Reads customer provided config on livepatch from disk and returns a dict with the config values. | ||
| NOTE: This is a temporary solution and will be deprecated soon """ | ||
| livepatch_customer_config = dict() | ||
| try: | ||
| if os.path.exists(Constants.AzGPSPaths.LIVEPATCH_CUSTOMER_SETTINGS): | ||
| livepatch_customer_config = json.loads(self.env_layer.file_system.read_with_retry(Constants.AzGPSPaths.LIVEPATCH_CUSTOMER_SETTINGS) or 'null') or dict() | ||
| self.composite_logger.log_debug("Livepatch customer config values from disk: [Config={0}]".format(str(livepatch_customer_config))) | ||
| else: | ||
| self.composite_logger.log_debug("No livepatch customer config found on the VM. Returning empty config.") | ||
| except Exception as error: | ||
| self.composite_logger.log_debug("Error occurred while reading and parsing livepatch customer config. Returning empty config. Error=[{0}]".format(repr(error))) | ||
|
|
||
| return livepatch_customer_config | ||
|
|
||
| def __is_livepatch_requested(self, livepatch_settings): | ||
| # type: (dict) -> bool | ||
| """ Determines if livepatch is requested in config settings. Returns a boolean.""" | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All new functions require type information (in and out)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| livepatch_requested = False | ||
|
|
||
| enable_livepatch = self.__fetch_specific_setting(livepatch_settings, Constants.LivePatchSettings.ENABLE_LIVEPATCH) | ||
| enabled_by = self.__fetch_specific_setting(livepatch_settings, Constants.LivePatchSettings.ENABLED_BY) | ||
| last_modified = self.__fetch_specific_setting(livepatch_settings, Constants.LivePatchSettings.LAST_MODIFIED) | ||
| if self.__is_truthy(enable_livepatch): | ||
| livepatch_requested = True | ||
| self.composite_logger.log_debug("Livepatch config values read from disk: [EnableLivePatch={0}] [EnabledBy={1}] [LastModified={2}]. Computed value of [LivePatchRequested={3}]" | ||
| .format(str(enable_livepatch), str(enabled_by), str(last_modified), str(livepatch_requested))) | ||
| else: | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The opposite of true isn't implicitly false. It can always be true, false or 'do nothing' (either because of bad data or because of no intent). You're logging the computed intent but never logging the actual data read out.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the actual value (read from file config) in the else block |
||
| self.composite_logger.log_debug("LivePatch is not requested for the VM. [EnableLivePatchValueFromConfig={0}]. Computed value of [LivePatchRequested={1}]" | ||
| .format(str(enable_livepatch),str(livepatch_requested))) | ||
|
|
||
| return livepatch_requested | ||
|
|
||
| def __is_livepatch_only_requested(self, livepatch_settings): | ||
| # type: (dict) -> bool | ||
| """ Determines if livepatch only, i.e. no cold patch, is requested in config settings. Returns a boolean.""" | ||
| """ NOTE: This is not in use currently but can be added in MVP if needed""" | ||
| livepatch_only_config = self.__fetch_specific_setting(livepatch_settings, Constants.LivePatchSettings.LIVEPATCH_ONLY) | ||
| livepatch_only_requested = self.__is_truthy(livepatch_only_config) | ||
| self.composite_logger.log_debug("Livepatch only config values from disk: [LivePatchOnly={0}]. Computed value of [LivePatchOnlyRequested={1}]" | ||
| .format(str(livepatch_only_config), str(livepatch_only_requested))) | ||
| return livepatch_only_requested | ||
|
|
||
| @staticmethod | ||
| def __fetch_specific_eula_setting(settings_source, setting_to_fetch): | ||
| """ Returns the specific setting value from eula_settings_source or None if not found """ | ||
| def __fetch_specific_setting(settings_source, setting_to_fetch): | ||
| # type: (dict, str) -> str or None | ||
| """ Returns the specific setting value from the given settings_source or None if not found """ | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Function type info here and elsewhere
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| if settings_source is not None and setting_to_fetch is not None and setting_to_fetch in settings_source: | ||
| return settings_source[setting_to_fetch] | ||
| return None | ||
|
|
||
| @staticmethod | ||
| def __is_truthy(value): | ||
| # type: (any) -> bool | ||
| """Case-insensitive truthy evaluator for config values.""" | ||
| if isinstance(value, bool): | ||
| return value | ||
| if isinstance(value, int): | ||
| return value == 1 | ||
|
|
||
| # Cross-version text types: | ||
| # py2 -> (str, unicode) | ||
| # py3 -> (str, str) | ||
| text_types = (str, type(u"")) | ||
| if isinstance(value, text_types): | ||
| return value.strip().lower() in ("true", "1") | ||
| return False | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What happens if both are true?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kjohn-msft If both are true, LPE should perform only livepatching i.e. no regular patch installation.
We had discussed the possibility of having this in MVP, back when we discussed MVP implementation. I've added only the config read for now, not using or applyiing it anywhere. Let me know if livepatch_only code flow should be added in MVP or to remove this section