OSPatching/patch/AbstractPatching.py (738 lines of code) (raw):

#!/usr/bin/python # # AbstractPatching is the base patching class of all the linux distros # # Copyright 2014 Microsoft Corporation # # 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 os import sys import re import json import random import shutil import time import datetime import logging import logging.handlers from Utils.WAAgentUtil import waagent from ConfigOptions import ConfigOptions mfile = os.path.join(os.getcwd(), 'HandlerManifest.json') with open(mfile,'r') as f: manifest = json.loads(f.read())[0] Version = manifest['version'] StatusTest = { "Scheduled" : { "Idle" : None, "Healthy" : None }, "Oneoff" : { "Idle" : None, "Healthy" : None } } try: from scheduled.idleTest import is_vm_idle StatusTest["Scheduled"]["Idle"] = is_vm_idle except: pass try: from oneoff.idleTest import is_vm_idle StatusTest["Oneoff"]["Idle"] = is_vm_idle except: pass try: from scheduled.healthyTest import is_vm_healthy StatusTest["Scheduled"]["Healthy"] = is_vm_healthy except: pass try: from oneoff.healthyTest import is_vm_healthy StatusTest["Oneoff"]["Healthy"] = is_vm_healthy except: pass class AbstractPatching(object): """ AbstractPatching defines a skeleton neccesary for a concrete Patching class. """ def __init__(self, hutil): self.hutil = hutil self.syslogger = None self.patched = [] self.to_patch = [] self.downloaded = [] self.download_retry_queue = [] # Patching Configuration self.disabled = None self.stop = None self.reboot_after_patch = None self.category = None self.install_duration = None self.oneoff = None self.interval_of_weeks = None self.day_of_week = None self.start_time = None self.download_time = None self.download_duration = 3600 self.gap_between_stage = 60 self.current_configs = dict() self.category_required = ConfigOptions.category["required"] self.category_all = ConfigOptions.category["all"] # Crontab Variables self.crontab = '/etc/crontab' self.cron_restart_cmd = 'service cron restart' self.cron_chkconfig_cmd = 'chkconfig cron on' # Path Variables self.cwd = os.getcwd() self.package_downloaded_path = os.path.join(self.cwd, 'package.downloaded') self.package_patched_path = os.path.join(self.cwd, 'package.patched') self.stop_flag_path = os.path.join(self.cwd, 'StopOSPatching') self.history_scheduled = os.path.join(self.cwd, 'scheduled/history') self.scheduled_configs_file = os.path.join(self.cwd, 'scheduled/configs') self.dist_upgrade_list = None self.dist_upgrade_list_key = 'distUpgradeList' self.dist_upgrade_all = False self.dist_upgrade_all_key = 'distUpgradeAll' # Reboot Requirements self.reboot_required = False self.open_deleted_files_before = list() self.open_deleted_files_after = list() self.needs_restart = list() def is_string_none_or_empty(self, str): if str is None or len(str) < 1: return True return False def parse_settings(self, settings): disabled = settings.get("disabled") if disabled is None or str(disabled).lower() not in ConfigOptions.disabled: msg = "The value of parameter \"disabled\" is empty or invalid. Set it False by default." self.log_and_syslog(logging.WARNING, msg) self.disabled = False else: if str(disabled).lower() == "true": self.disabled = True else: self.disabled = False self.current_configs["disabled"] = str(self.disabled) if self.disabled: msg = "The extension is disabled." self.log_and_syslog(logging.WARNING, msg) return stop = settings.get("stop") if stop is None or str(stop).lower() not in ConfigOptions.stop: msg = "The value of parameter \"stop\" is empty or invalid. Set it False by default." self.log_and_syslog(logging.WARNING, msg) self.stop = False else: if str(stop).lower() == 'true': self.stop = True else: self.stop = False self.current_configs["stop"] = str(self.stop) reboot_after_patch = settings.get("rebootAfterPatch") if reboot_after_patch is None or reboot_after_patch.lower() not in ConfigOptions.reboot_after_patch: msg = "The value of parameter \"rebootAfterPatch\" is empty or invalid. Set it \"rebootifneed\" by default." self.log_and_syslog(logging.WARNING, msg) self.reboot_after_patch = ConfigOptions.reboot_after_patch[0] else: self.reboot_after_patch = reboot_after_patch.lower() waagent.AddExtensionEvent(name=self.hutil.get_name(), op=waagent.WALAEventOperation.Enable, isSuccess=True, version=Version, message="rebootAfterPatch="+self.reboot_after_patch) self.current_configs["rebootAfterPatch"] = self.reboot_after_patch category = settings.get('category') if category is None or category.lower() not in ConfigOptions.category.values(): msg = "The value of parameter \"category\" is empty or invalid. Set it " + self.category_required + " by default." self.log_and_syslog(logging.WARNING, msg) self.category = self.category_required else: self.category = category.lower() waagent.AddExtensionEvent(name=self.hutil.get_name(), op=waagent.WALAEventOperation.Enable, isSuccess=True, version=Version, message="category="+self.category) self.current_configs["category"] = self.category self.dist_upgrade_list = settings.get(self.dist_upgrade_list_key) if not self.is_string_none_or_empty(self.dist_upgrade_list): self.current_configs[self.dist_upgrade_list_key] = self.dist_upgrade_list dist_upgrade_all = settings.get(self.dist_upgrade_all_key) if dist_upgrade_all is None: msg = "The value of parameter \"{0}\" is empty or invalid. Set it false by default.".format(self.dist_upgrade_all_key) self.log_and_syslog(logging.INFO, msg) self.dist_upgrade_all = False elif str(dist_upgrade_all).lower() == 'true': self.dist_upgrade_all = True else: self.dist_upgrade_all = False self.current_configs[self.dist_upgrade_all_key] = str(self.dist_upgrade_all) check_hrmin = re.compile(r'^[0-9]{1,2}:[0-9]{1,2}$') install_duration = settings.get('installDuration') if install_duration is None or not re.match(check_hrmin, install_duration): msg = "The value of parameter \"installDuration\" is empty or invalid. Set it 1 hour by default." self.log_and_syslog(logging.WARNING, msg) self.install_duration = 3600 self.current_configs["installDuration"] = "01:00" else: hr_min = install_duration.split(':') self.install_duration = int(hr_min[0]) * 3600 + int(hr_min[1]) * 60 self.current_configs["installDuration"] = install_duration if self.install_duration <= 300: msg = "The value of parameter \"installDuration\" is smaller than 5 minutes. The extension will not reserve 5 minutes for reboot. It is recommended to set \"installDuration\" more than 30 minutes." self.log_and_syslog(logging.WARNING, msg) else: msg = "The extension will reserve 5 minutes for reboot." # 5 min for reboot self.install_duration -= 300 self.log_and_syslog(logging.INFO, msg) # The parameter "downloadDuration" is not exposed to users. So there's no log. download_duration = settings.get('downloadDuration') if download_duration is not None and re.match(check_hrmin, download_duration): hr_min = download_duration.split(':') self.download_duration = int(hr_min[0]) * 3600 + int(hr_min[1]) * 60 oneoff = settings.get('oneoff') if oneoff is None or str(oneoff).lower() not in ConfigOptions.oneoff: msg = "The value of parameter \"oneoff\" is empty or invalid. Set it False by default." self.log_and_syslog(logging.WARNING, msg) self.oneoff = False else: if str(oneoff).lower() == "true": self.oneoff = True msg = "The extension will run in one-off mode." else: self.oneoff = False msg = "The extension will run in scheduled task mode." self.log_and_syslog(logging.INFO, msg) self.current_configs["oneoff"] = str(self.oneoff) if not self.oneoff: start_time = settings.get('startTime') if start_time is None or not re.match(check_hrmin, start_time): msg = "The parameter \"startTime\" is empty or invalid. It defaults to 03:00." self.log_and_syslog(logging.WARNING, msg) start_time = "03:00" try: start_time_dt = datetime.datetime.strptime(start_time, '%H:%M') self.start_time = datetime.time(start_time_dt.hour, start_time_dt.minute) except ValueError: msg = "The parameter \"startTime\" is invalid. It defaults to 03:00." self.log_and_syslog(logging.WARNING, msg) self.start_time = datetime.time(3) download_time_dt = start_time_dt - datetime.timedelta(seconds=self.download_duration) self.download_time = datetime.time(download_time_dt.hour, download_time_dt.minute) self.current_configs["startTime"] = start_time day_of_week = settings.get("dayOfWeek") if day_of_week is None or day_of_week == "": msg = "The parameter \"dayOfWeek\" is empty. dayOfWeek defaults to Everyday." self.log_and_syslog(logging.WARNING, msg) day_of_week = "everyday" self.day_of_week = ConfigOptions.day_of_week["everyday"] else: for day in day_of_week.split('|'): day = day.strip().lower() if day not in ConfigOptions.day_of_week: msg = "The parameter \"dayOfWeek\" is invalid. dayOfWeek defaults to Everyday." self.log_and_syslog(logging.WARNING, msg) day_of_week = "everyday" break if "everyday" in day_of_week: self.day_of_week = ConfigOptions.day_of_week["everyday"] else: self.day_of_week = [ConfigOptions.day_of_week[day.strip().lower()] for day in day_of_week.split('|')] waagent.AddExtensionEvent(name=self.hutil.get_name(), op=waagent.WALAEventOperation.Enable, isSuccess=True, version=Version, message="dayOfWeek=" + day_of_week) self.current_configs["dayOfWeek"] = day_of_week interval_of_weeks = settings.get('intervalOfWeeks') if interval_of_weeks is None or interval_of_weeks not in ConfigOptions.interval_of_weeks: msg = "The parameter \"intervalOfWeeks\" is empty or invalid. intervalOfWeeks defaults to 1." self.log_and_syslog(logging.WARNING, msg) self.interval_of_weeks = '1' else: self.interval_of_weeks = interval_of_weeks waagent.AddExtensionEvent(name=self.hutil.get_name(), op=waagent.WALAEventOperation.Enable, isSuccess=True, version=Version, message="intervalOfWeeks="+self.interval_of_weeks) self.current_configs["intervalOfWeeks"] = self.interval_of_weeks # Save the latest configuration for scheduled task to avoid one-off mode's affection waagent.SetFileContents(self.scheduled_configs_file, json.dumps(self.current_configs)) msg = "Current Configuration: " + self.get_current_config() self.log_and_syslog(logging.INFO, msg) def install(self): pass def enable(self): if self.stop: self.stop_download() self.create_stop_flag() return self.delete_stop_flag() if not self.disabled and self.oneoff: script_file_path = os.path.realpath(sys.argv[0]) os.system(' '.join(['python', script_file_path, '-oneoff', '>/dev/null 2>&1 &'])) else: waagent.SetFileContents(self.history_scheduled, '') self.set_download_cron() self.set_patch_cron() self.restart_cron() def disable(self): self.disabled = True self.enable() def stop_download(self): ''' kill the process of downloading and its subprocess. return code: 100 - There are no downloading process to stop 0 - The downloading process is stopped ''' script_file_path = os.path.realpath(sys.argv[0]) script_file = os.path.basename(script_file_path) retcode, output = waagent.RunGetOutput('ps -ef | grep "' + script_file + ' -download" | grep -v grep | grep -v sh | awk \'{print $2}\'') if retcode > 0: self.log_and_syslog(logging.ERROR, output) if output != '': retcode, output2 = waagent.RunGetOutput("ps -ef | awk '{if($3==" + output.strip() + ") {print $2}}'") if retcode > 0: self.log_and_syslog(logging.ERROR, output2) if output2 != '': waagent.Run('kill -9 ' + output2.strip()) waagent.Run('kill -9 ' + output.strip()) return 0 return 100 def set_download_cron(self): script_file_path = os.path.realpath(sys.argv[0]) script_dir = os.path.dirname(script_file_path) script_file = os.path.basename(script_file_path) old_line_end = ' '.join([script_file, '-download']) if self.disabled: new_line = '\n' else: if self.download_time > self.start_time: dow = ','.join([str((day - 1) % 7) for day in self.day_of_week]) else: dow = ','.join([str(day % 7) for day in self.day_of_week]) hr = str(self.download_time.hour) minute = str(self.download_time.minute) new_line = ' '.join(['\n' + minute, hr, '* *', dow, 'root cd', script_dir, '&& python check.py', self.interval_of_weeks, '&& python', script_file, '-download > /dev/null 2>&1\n']) waagent.ReplaceFileContentsAtomic(self.crontab, '\n'.join(filter(lambda a: a and (old_line_end not in a), waagent.GetFileContents(self.crontab).split('\n'))) + new_line) def set_patch_cron(self): script_file_path = os.path.realpath(sys.argv[0]) script_dir = os.path.dirname(script_file_path) script_file = os.path.basename(script_file_path) old_line_end = ' '.join([script_file, '-patch']) if self.disabled: new_line = '\n' else: start_time_dt = datetime.datetime(100, 1, 1, self.start_time.hour, self.start_time.minute) start_hr = str(self.start_time.hour) start_minute = str(self.start_time.minute) start_dow = ','.join([str(day % 7) for day in self.day_of_week]) cleanup_time_dt = start_time_dt + datetime.timedelta(minutes=1) cleanup_hr = str(cleanup_time_dt.hour) cleanup_minute = str(cleanup_time_dt.minute) if start_time_dt.day < cleanup_time_dt.day: cleanup_dow = ','.join([str((day + 1) % 7) for day in self.day_of_week]) else: cleanup_dow = ','.join([str(day % 7) for day in self.day_of_week]) new_line = ' '.join(['\n' + start_minute, start_hr, '* *', start_dow, 'root cd', script_dir, '&& python check.py', self.interval_of_weeks, '&& python', script_file, '-patch >/dev/null 2>&1\n']) new_line += ' '.join([cleanup_minute, cleanup_hr, '* *', cleanup_dow, 'root rm -f', self.stop_flag_path, '\n']) waagent.ReplaceFileContentsAtomic(self.crontab, "\n".join(filter(lambda a: a and (old_line_end not in a) and (self.stop_flag_path not in a), waagent.GetFileContents(self.crontab).split('\n'))) + new_line) def restart_cron(self): retcode,output = waagent.RunGetOutput(self.cron_restart_cmd) if retcode > 0: self.log_and_syslog(logging.ERROR, output) def download(self): # Read the latest configuration for scheduled task settings = json.loads(waagent.GetFileContents(self.scheduled_configs_file)) self.parse_settings(settings) self.provide_vm_status_test(StatusTest["Scheduled"]) if not self.check_vm_idle(StatusTest["Scheduled"]): return if self.exists_stop_flag(): self.log_and_syslog(logging.INFO, "Downloading patches is stopped/canceled") return waagent.SetFileContents(self.package_downloaded_path, '') waagent.SetFileContents(self.package_patched_path, '') start_download_time = time.time() # Installing security patches is mandatory self._download(self.category_required) if self.category == self.category_all: self._download(self.category_all) self.retry_download() end_download_time = time.time() waagent.AddExtensionEvent(name=self.hutil.get_name(), op=waagent.WALAEventOperation.Download, isSuccess=True, version=Version, message=" ".join(["Real downloading time is", str(round(end_download_time-start_download_time,3)), "s"])) def _download(self, category): self.log_and_syslog(logging.INFO, "Start to check&download patches (Category:" + category + ")") retcode, downloadlist = self.check(category) if retcode > 0: msg = "Failed to check valid upgrades" self.log_and_syslog(logging.ERROR, msg) self.hutil.do_exit(1, 'Enable', 'error', '0', msg) if 'walinuxagent' in downloadlist: downloadlist.remove('walinuxagent') if not downloadlist: self.log_and_syslog(logging.INFO, "No packages are available for update.") return self.log_and_syslog(logging.INFO, "There are " + str(len(downloadlist)) + " packages to upgrade.") self.log_and_syslog(logging.INFO, "Download list: " + ' '.join(downloadlist)) for pkg_name in downloadlist: if pkg_name in self.downloaded: continue retcode = self.download_package(pkg_name) if retcode != 0: self.log_and_syslog(logging.ERROR, "Failed to download the package: " + pkg_name) self.log_and_syslog(logging.INFO, "Put {0} into a retry queue".format(pkg_name)) self.download_retry_queue.append((pkg_name, category)) continue self.downloaded.append(pkg_name) self.log_and_syslog(logging.INFO, "Package " + pkg_name + " is downloaded.") waagent.AppendFileContents(self.package_downloaded_path, pkg_name + ' ' + category + '\n') def retry_download(self): retry_count = 0 max_retry_count = 12 self.log_and_syslog(logging.INFO, "Retry queue: {0}".format( " ".join([pkg_name for pkg_name,category in self.download_retry_queue]))) while self.download_retry_queue: pkg_name, category = self.download_retry_queue[0] self.download_retry_queue = self.download_retry_queue[1:] retcode = self.download_package(pkg_name) if retcode == 0: self.downloaded.append(pkg_name) self.log_and_syslog(logging.INFO, "Package " + pkg_name + " is downloaded.") waagent.AppendFileContents(self.package_downloaded_path, pkg_name + ' ' + category + '\n') else: self.log_and_syslog(logging.ERROR, "Failed to download the package: " + pkg_name) self.log_and_syslog(logging.INFO, "Put {0} back into a retry queue".format(pkg_name)) self.download_retry_queue.append((pkg_name,category)) retry_count = retry_count + 1 if retry_count > max_retry_count: err_msg = ("Failed to download after {0} retries, " "retry queue: {1}").format(max_retry_count, " ".join([pkg_name for pkg_name,category in self.download_retry_queue])) self.log_and_syslog(logging.ERROR, err_msg) waagent.AddExtensionEvent(name=self.hutil.get_name(), op=waagent.WALAEventOperation.Download, isSuccess=False, version=Version, message=err_msg) break k = retry_count if (retry_count < 10) else 10 interval = int(random.uniform(0, 2 ** k)) self.log_and_syslog(logging.INFO, ("Sleep {0}s before " "the next retry, current retry_count = {1}").format(interval, retry_count)) time.sleep(interval) def patch(self): # Read the latest configuration for scheduled task settings = json.loads(waagent.GetFileContents(self.scheduled_configs_file)) self.parse_settings(settings) if not self.check_vm_idle(StatusTest["Scheduled"]): return if self.exists_stop_flag(): self.log_and_syslog(logging.INFO, "Installing patches is stopped/canceled") self.delete_stop_flag() return # Record the scheduled time waagent.AppendFileContents(self.history_scheduled, time.strftime("%Y-%m-%d %a", time.localtime()) + '\n' ) # Record the open deleted files before patching self.open_deleted_files_before = self.check_open_deleted_files() retcode = self.stop_download() if retcode == 0: self.log_and_syslog(logging.WARNING, "Download time exceeded. The pending package will be downloaded in the next cycle") waagent.AddExtensionEvent(name=self.hutil.get_name(), op=waagent.WALAEventOperation.Download, isSuccess=False, version=Version, message="Downloading time out") global start_patch_time start_patch_time = time.time() pkg_failed = [] is_time_out = [False, False] patchlist = self.get_pkg_to_patch(self.category_required) is_time_out[0],failed = self._patch(self.category_required, patchlist) pkg_failed.extend(failed) if not self.exists_stop_flag(): if not is_time_out[0]: patchlist = self.get_pkg_to_patch(self.category_all) if len(patchlist) == 0: self.log_and_syslog(logging.INFO, "No packages are available for update. (Category:" + self.category_all + ")") else: self.log_and_syslog(logging.INFO, "Going to sleep for " + str(self.gap_between_stage) + "s") time.sleep(self.gap_between_stage) is_time_out[1],failed = self._patch(self.category_all, patchlist) pkg_failed.extend(failed) else: msg = "Installing patches (Category:" + self.category_all + ") is stopped/canceled" self.log_and_syslog(logging.INFO, msg) if is_time_out[0] or is_time_out[1]: msg = "Patching time out" self.log_and_syslog(logging.WARNING, msg) waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Patch", isSuccess=False, version=Version, message=msg) self.open_deleted_files_after = self.check_open_deleted_files() self.delete_stop_flag() #self.report() if StatusTest["Scheduled"]["Healthy"]: is_healthy = StatusTest["Scheduled"]["Healthy"]() msg = "Checking the VM is healthy after patching: " + str(is_healthy) self.log_and_syslog(logging.INFO, msg) waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Check healthy", isSuccess=is_healthy, version=Version, message=msg) if self.patched is not None and len(self.patched) > 0: self.reboot_if_required() def _patch(self, category, patchlist): if self.exists_stop_flag(): self.log_and_syslog(logging.INFO, "Installing patches (Category:" + category + ") is stopped/canceled") return False,list() if not patchlist: self.log_and_syslog(logging.INFO, "No packages are available for update.") return False,list() self.log_and_syslog(logging.INFO, "Start to install " + str(len(patchlist)) +" patches (Category:" + category + ")") self.log_and_syslog(logging.INFO, "Patch list: " + ' '.join(patchlist)) pkg_failed = [] for pkg_name in patchlist: if pkg_name == 'walinuxagent': continue current_patch_time = time.time() if current_patch_time - start_patch_time > self.install_duration: msg = "Patching time exceeded. The pending package will be patched in the next cycle" self.log_and_syslog(logging.WARNING, msg) return True,pkg_failed retcode = self.patch_package(pkg_name) if retcode != 0: self.log_and_syslog(logging.ERROR, "Failed to patch the package:" + pkg_name) pkg_failed.append(' '.join([pkg_name, category])) continue self.patched.append(pkg_name) self.log_and_syslog(logging.INFO, "Package " + pkg_name + " is patched.") waagent.AppendFileContents(self.package_patched_path, pkg_name + ' ' + category + '\n') return False,pkg_failed def patch_one_off(self): """ Called when startTime is empty string, which means a on-demand patch. """ self.provide_vm_status_test(StatusTest["Oneoff"]) if not self.check_vm_idle(StatusTest["Oneoff"]): return global start_patch_time start_patch_time = time.time() self.log_and_syslog(logging.INFO, "Going to patch one-off") waagent.SetFileContents(self.package_downloaded_path, '') waagent.SetFileContents(self.package_patched_path, '') # Record the open deleted files before patching self.open_deleted_files_before = self.check_open_deleted_files() pkg_failed = [] is_time_out = [False, False] retcode, patchlist_required = self.check(self.category_required) if retcode > 0: msg = "Failed to check valid upgrades" self.log_and_syslog(logging.ERROR, msg) self.hutil.do_exit(1, 'Enable', 'error', '0', msg) if not patchlist_required: self.log_and_syslog(logging.INFO, "No packages are available for update. (Category:" + self.category_required + ")") else: is_time_out[0],failed = self._patch(self.category_required, patchlist_required) pkg_failed.extend(failed) if self.category == self.category_all: if not self.exists_stop_flag(): if not is_time_out[0]: retcode, patchlist_other = self.check(self.category_all) if retcode > 0: msg = "Failed to check valid upgrades" self.log_and_syslog(logging.ERROR, msg) self.hutil.do_exit(1, 'Enable', 'error', '0', msg) patchlist_other = [pkg for pkg in patchlist_other if pkg not in patchlist_required] if len(patchlist_other) == 0: self.log_and_syslog(logging.INFO, "No packages are available for update. (Category:" + self.category_all + ")") else: self.log_and_syslog(logging.INFO, "Going to sleep for " + str(self.gap_between_stage) + "s") time.sleep(self.gap_between_stage) self.log_and_syslog(logging.INFO, "Going to patch one-off (Category:" + self.category_all + ")") is_time_out[1],failed = self._patch(self.category_all, patchlist_other) pkg_failed.extend(failed) else: self.log_and_syslog(logging.INFO, "Installing patches (Category:" + self.category_all + ") is stopped/canceled") if is_time_out[0] or is_time_out[1]: waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Oneoff Patch", isSuccess=False, version=Version, message="Patching time out") shutil.copy2(self.package_patched_path, self.package_downloaded_path) for pkg in pkg_failed: waagent.AppendFileContents(self.package_downloaded_path, pkg + '\n') self.open_deleted_files_after = self.check_open_deleted_files() self.delete_stop_flag() #self.report() if StatusTest["Oneoff"]["Healthy"]: is_healthy = StatusTest["Oneoff"]["Healthy"]() msg = "Checking the VM is healthy after patching: " + str(is_healthy) self.log_and_syslog(logging.INFO, msg) waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Check healthy", isSuccess=is_healthy, version=Version, message=msg) if self.patched is not None and len(self.patched) > 0: self.reboot_if_required() def reboot_if_required(self): self.check_reboot() self.check_needs_restart() msg = '' if self.reboot_after_patch == 'notrequired' and self.reboot_required: msg += 'Pending Reboot' if self.needs_restart: msg += ': ' + ' '.join(self.needs_restart) waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Reboot", isSuccess=False, version=Version, message=" ".join([self.reboot_after_patch, msg, str(len(self.needs_restart)), "packages need to restart"])) self.hutil.do_exit(0, 'Enable', 'success', '0', msg) if self.reboot_after_patch == 'required': msg += "System going to reboot(Required)" elif self.reboot_after_patch == 'auto' and self.reboot_required: msg += "System going to reboot(Auto)" elif self.reboot_after_patch == 'rebootifneed': if (self.reboot_required or self.needs_restart): msg += "System going to reboot(RebootIfNeed)" if msg: if self.needs_restart: msg += ': ' + ' '.join(self.needs_restart) self.log_and_syslog(logging.INFO, msg) waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Reboot", isSuccess=True, version=Version, message="Reboot") retcode = waagent.Run('reboot') if retcode != 0: self.log_and_syslog(logging.ERROR, "Failed to reboot") waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Reboot", isSuccess=False, version=Version, message="Failed to reboot") else: waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Reboot", isSuccess=False, version=Version, message="Not reboot") def check_needs_restart(self): self.needs_restart.extend(self.get_pkg_needs_restart()) patched_files = dict() for pkg in self.get_pkg_patched(): cmd = ' '.join([self.pkg_query_cmd, pkg]) try: retcode, output = waagent.RunGetOutput(cmd) patched_files[os.path.basename(pkg)] = [filename for filename in output.split("\n") if os.path.isfile(filename)] except Exception: self.log_and_syslog(logging.ERROR, "Failed to " + cmd) # for k,v in patched_files.items(): # self.log_and_syslog(logging.INFO, k + ": " + " ".join(v)) open_deleted_files = list() for filename in self.open_deleted_files_after: if filename not in self.open_deleted_files_before: open_deleted_files.append(filename) # self.log_and_syslog(logging.INFO, "Open deleted files: " + " ".join(open_deleted_files)) for pkg,files in patched_files.items(): for filename in files: realpath = os.path.realpath(filename) if realpath in open_deleted_files and pkg not in self.needs_restart: self.needs_restart.append(pkg) msg = "Packages needs to restart: " pkgs = " ".join(self.needs_restart) if pkgs: msg += pkgs else: msg = "There is no package which needs to restart" self.log_and_syslog(logging.INFO, msg) def get_pkg_needs_restart(self): return [] def check_open_deleted_files(self): ret = list() retcode,output = waagent.RunGetOutput('lsof | grep "DEL"') if retcode == 0: for line in output.split('\n'): if line: filename = line.split()[-1] if filename not in ret: ret.append(filename) return ret def create_stop_flag(self): waagent.SetFileContents(self.stop_flag_path, '') def delete_stop_flag(self): if self.exists_stop_flag(): os.remove(self.stop_flag_path) def exists_stop_flag(self): if os.path.isfile(self.stop_flag_path): return True else: return False def get_pkg_to_patch(self, category): if not os.path.isfile(self.package_downloaded_path): return [] pkg_to_patch = waagent.GetFileContents(self.package_downloaded_path) if not pkg_to_patch: return [] patchlist = [line.split()[0] for line in pkg_to_patch.split('\n') if line.endswith(category)] if patchlist is None: return [] return patchlist def get_pkg_patched(self): if not os.path.isfile(self.package_patched_path): return [] pkg_patched = waagent.GetFileContents(self.package_patched_path) if not pkg_patched: return [] patchedlist = [line.split()[0] for line in pkg_patched.split('\n') if line] return patchedlist def get_current_config(self): current_configs = [] for k,v in self.current_configs.items(): current_configs.append(k + "=" + v) return ",".join(current_configs) def provide_vm_status_test(self, status_test): for status,provided in status_test.items(): if provided is None: provided = "False" level = logging.WARNING else: provided = "True" level = logging.INFO msg = "The VM %s test script is provided: %s" % (status, provided) self.log_and_syslog(level, msg) waagent.AddExtensionEvent(name=self.hutil.get_name(), op="provides %s test script" % (status,), isSuccess=provided, version=Version, message=msg) def check_vm_idle(self, status_test): is_idle = True if status_test["Idle"]: is_idle = status_test["Idle"]() msg = "Checking the VM is idle: " + str(is_idle) self.log_and_syslog(logging.INFO, msg) waagent.AddExtensionEvent(name=self.hutil.get_name(), op="Check idle", isSuccess=is_idle, version=Version, message=msg) if not is_idle: self.log_and_syslog(logging.WARNING, "Current Operation is skipped.") return is_idle def log_and_syslog(self, level, message): if level == logging.INFO: self.hutil.log(message) elif level == logging.WARNING: self.hutil.log(" ".join(["Warning:", message])) elif level == logging.ERROR: self.hutil.error(message) if self.syslogger is None: self.init_syslog() self.syslog(level, message) def init_syslog(self): self.syslogger = logging.getLogger(self.hutil.get_name()) self.syslogger.setLevel(logging.INFO) formatter = logging.Formatter('%(name)s: %(levelname)s %(message)s') try: handler = logging.handlers.SysLogHandler(address='/dev/log') handler.setFormatter(formatter) self.syslogger.addHandler(handler) except: self.syslogger = None self.hutil.error("Syslog is not ready.") def syslog(self, level, message): if self.syslogger is None: return if level == logging.INFO: self.syslogger.info(message) elif level == logging.WARNING: self.syslogger.warning(message) elif level == logging.ERROR: self.syslogger.error(message)