Skip to content

Commit efad5ea

Browse files
authored
Add started by and fix reboot always (#77)
* Add started by and fix reboot always * Augmented test
1 parent d812d4a commit efad5ea

File tree

8 files changed

+64
-8
lines changed

8 files changed

+64
-8
lines changed

src/core/src/bootstrap/Constants.py

Lines changed: 6 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.19"
33+
EXT_VERSION = "1.6.20"
3434

3535
# Runtime environments
3636
TEST = 'Test'
@@ -186,6 +186,11 @@ class RebootStatus(EnumBackport):
186186
COMPLETED = "Completed"
187187
FAILED = "Failed"
188188

189+
# StartedBy Patch Assessment Summary Status Values
190+
class PatchAssessmentSummaryStartedBy(EnumBackport):
191+
USER = "User"
192+
PLATFORM = "Platform"
193+
189194
# Maintenance Window
190195
PACKAGE_INSTALL_EXPECTED_MAX_TIME_IN_MINUTES = 5
191196

src/core/src/core_logic/PatchInstaller.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -277,14 +277,17 @@ def mark_installation_completed(self):
277277
# todo: temp fix to test auto patching, this will be reset to using the maintenanceRunId string as is, once the corresponding changes in RSM are made
278278
# patch_version = str(self.execution_config.maintenance_run_id)
279279
patch_version = datetime.datetime.strptime(self.execution_config.maintenance_run_id.split(" ")[0], "%m/%d/%Y").strftime('%Y.%m.%d')
280-
self.status_handler.set_patch_metadata_for_healthstore_substatus_json(patch_version=patch_version if patch_version is not None and patch_version != "" else Constants.PATCH_VERSION_UNKNOWN,
281-
report_to_healthstore=True,
282-
wait_after_update=False)
280+
283281
except ValueError as e:
284282
error_message = "Maintenance Run Id is in incorrect format. Expected=[DateTimeUTC]. Actual=[{0}]. Error=[{1}]".format(str(self.execution_config.maintenance_run_id), repr(e))
285283
self.composite_logger.log_error(error_message)
286284
raise Exception(error_message)
287285

286+
self.status_handler.set_patch_metadata_for_healthstore_substatus_json(
287+
patch_version=patch_version if patch_version is not None and patch_version != "" else Constants.PATCH_VERSION_UNKNOWN,
288+
report_to_healthstore=True,
289+
wait_after_update=False)
290+
288291
# region Installation Progress support
289292
def perform_status_reconciliation_conditionally(self, package_manager, condition=True):
290293
"""Periodically based on the condition check, writes out success records as required; returns count of detected installs.

src/core/src/core_logic/RebootManager.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ def start_reboot_if_required_and_time_available(self, current_time_available):
104104
self.composite_logger.log(" - There was no reboot pending detected. Reboot is being skipped as it's not required, as per patch installation configuration (" + str(Constants.REBOOT_IF_REQUIRED) + ").")
105105
return False
106106

107+
# prevent repeated reboots
108+
if self.reboot_setting == Constants.REBOOT_ALWAYS and not reboot_pending and self.status_handler.get_installation_reboot_status() == Constants.RebootStatus.COMPLETED:
109+
self.composite_logger.log(" - At least one reboot has occurred, and there's no reboot pending, so the conditions for the 'Reboot Always' setting is fulfilled and reboot won't be repeated.")
110+
return False
111+
107112
# attempt to reboot is enough time is available
108113
if self.reboot_setting == Constants.REBOOT_ALWAYS or (self.reboot_setting == Constants.REBOOT_IF_REQUIRED and reboot_pending):
109114
if self.is_reboot_time_available(current_time_available):

src/core/src/service_interfaces/StatusHandler.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ def get_os_name_and_version(self):
202202
# endregion
203203

204204
# region - Installation Reboot Status
205+
def get_installation_reboot_status(self):
206+
""" Safe retrieval of currently stored reboot status (stateful) """
207+
return self.__installation_reboot_status
208+
205209
def set_installation_reboot_status(self, new_reboot_status):
206210
""" Valid reboot statuses: NotNeeded, Required, Started, Failed, Completed """
207211
if new_reboot_status not in [Constants.RebootStatus.NOT_NEEDED, Constants.RebootStatus.REQUIRED, Constants.RebootStatus.STARTED, Constants.RebootStatus.FAILED, Constants.RebootStatus.COMPLETED]:
@@ -270,7 +274,10 @@ def __new_assessment_summary_json(self, assessment_packages_json):
270274
else:
271275
other_patch_count += 1
272276

273-
# Compose substatus message
277+
# discern started by
278+
started_by = Constants.PatchAssessmentSummaryStartedBy.PLATFORM if self.execution_config.operation == Constants.AUTO_ASSESSMENT else Constants.PatchAssessmentSummaryStartedBy.USER
279+
280+
# Compose sub-status message
274281
return {
275282
"assessmentActivityId": str(self.execution_config.activity_id),
276283
"rebootPending": self.is_reboot_pending,
@@ -279,6 +286,7 @@ def __new_assessment_summary_json(self, assessment_packages_json):
279286
"patches": assessment_packages_json,
280287
"startTime": str(self.execution_config.start_time),
281288
"lastModifiedTime": str(self.env_layer.datetime.timestamp()),
289+
"startedBy": str(started_by),
282290
"errors": self.__set_errors_json(self.__assessment_total_error_count, self.__assessment_errors)
283291
}
284292

src/core/tests/Test_RebootManager.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,27 @@ def test_reboot_always_time_available(self):
7474
self.assertEqual(reboot_manager.start_reboot_if_required_and_time_available(20), True)
7575
runtime.stop()
7676

77+
def test_reboot_always_runs_only_once_if_no_reboot_is_required(self):
78+
reboot_setting_in_api = 'Always'
79+
argument_composer = ArgumentComposer()
80+
argument_composer.reboot_setting = reboot_setting_in_api
81+
runtime = RuntimeCompositor(argument_composer.get_composed_arguments(), True, Constants.YUM)
82+
reboot_manager = runtime.reboot_manager
83+
84+
# Validate single reboot scenario
85+
runtime.status_handler.is_reboot_pending = True
86+
self.assertEqual(reboot_manager.start_reboot_if_required_and_time_available(20), True)
87+
88+
# mock completing the reboot once, with no reboot required
89+
runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.REQUIRED)
90+
runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.STARTED)
91+
runtime.status_handler.is_reboot_pending = False
92+
runtime.status_handler.set_installation_reboot_status(Constants.RebootStatus.COMPLETED)
93+
94+
# no further reboot should be required
95+
self.assertEqual(reboot_manager.start_reboot_if_required_and_time_available(20), False)
96+
runtime.stop()
97+
7798
def test_reboot_always_time_not_available(self):
7899
reboot_setting_in_api = 'Always'
79100
argument_composer = ArgumentComposer()

src/core/tests/Test_StatusHandler.py

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ def tearDown(self):
3333
def test_set_package_assessment_status(self):
3434
packages, package_versions = self.runtime.package_manager.get_all_updates()
3535
self.runtime.status_handler.set_package_assessment_status(packages, package_versions)
36-
substatus_file_data = []
3736
with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle:
3837
substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0]
3938
self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY)
@@ -42,6 +41,21 @@ def test_set_package_assessment_status(self):
4241
self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin")
4342
self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "samba-libs")
4443
self.assertTrue("python-samba_2:4.4.5+dfsg-2ubuntu5.4" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"]))
44+
self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["startedBy"], Constants.PatchAssessmentSummaryStartedBy.USER)
45+
46+
def test_set_package_assessment_status_started_by(self):
47+
self.runtime.execution_config.operation = Constants.AUTO_ASSESSMENT
48+
packages, package_versions = self.runtime.package_manager.get_all_updates()
49+
self.runtime.status_handler.set_package_assessment_status(packages, package_versions)
50+
with self.runtime.env_layer.file_system.open(self.runtime.execution_config.status_file_path, 'r') as file_handle:
51+
substatus_file_data = json.load(file_handle)[0]["status"]["substatus"][0]
52+
self.assertEqual(substatus_file_data["name"], Constants.PATCH_ASSESSMENT_SUMMARY)
53+
self.assertEqual(len(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"]), 3)
54+
self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["name"], "python-samba")
55+
self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][1]["name"], "samba-common-bin")
56+
self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][2]["name"], "samba-libs")
57+
self.assertTrue("python-samba_2:4.4.5+dfsg-2ubuntu5.4" in str(json.loads(substatus_file_data["formattedMessage"]["message"])["patches"][0]["patchId"]))
58+
self.assertEqual(json.loads(substatus_file_data["formattedMessage"]["message"])["startedBy"], Constants.PatchAssessmentSummaryStartedBy.PLATFORM)
4559

4660
def test_set_package_install_status(self):
4761
packages, package_versions = self.runtime.package_manager.get_all_updates()

src/extension/src/Constants.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ def __iter__(self):
2828
yield item
2929

3030
# Extension version (todo: move to a different file)
31-
EXT_VERSION = "1.6.19"
31+
EXT_VERSION = "1.6.20"
3232

3333
# Runtime environments
3434
TEST = 'Test'

src/extension/src/manifest.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<ExtensionImage xmlns="http://schemas.microsoft.com/windowsazure">
33
<ProviderNameSpace>Microsoft.CPlat.Core</ProviderNameSpace>
44
<Type>LinuxPatchExtension</Type>
5-
<Version>1.6.19</Version>
5+
<Version>1.6.20</Version>
66
<Label>Microsoft Azure VM InGuest Patch Extension for Linux Virtual Machines</Label>
77
<HostingResources>VmRole</HostingResources>
88
<MediaLink></MediaLink>

0 commit comments

Comments
 (0)