blob: c337e0a0c6eec099be857b5ac9a946b487ffabcd [file] [log] [blame]
# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A progress class for tracking CrOS auto-update process.
This class is mainly designed for:
1. Set the pattern for generating the filenames of
track_status_file/execute_log_file.
track_status_file: Used for record the current step of CrOS auto-update
process. Only has one line.
execute_log_file: Used for record the whole logging info of the CrOS
auto-update process, including any debug information.
2. Write current auto-update process into the track_status_file.
3. Read current auto-update process from the track_status_file.
This file also offers external functions that are related to add/check/delete
the progress of the CrOS auto-update process.
"""
from __future__ import print_function
import datetime
import glob
import logging
import os
import re
import log_util
# Module-local log function.
def _Log(message, *args):
return log_util.LogWithTag('CROS_UPDATE_PROGRESS', message, *args)
# only import setup_chromite before chromite import.
import setup_chromite # pylint: disable=unused-import
try:
from chromite.lib import osutils
except ImportError as e:
_Log('chromite cannot be imported: %r', e)
osutils = None
# Path for status tracking log.
_TRACK_LOG_FILE_PATH = '/tmp/auto-update/tracking_log/%s_%s.log'
# Pattern for status tracking log filename.
_TRACK_LOG_FILE_NAME_PATTERN = r'([^_]+)_([^_]+).log'
# The gap hour used in checking AU processes' count.
AU_PROCESS_HOUR_GAP = 3
# Path for executing log.
_EXECUTE_LOG_FILE_PATH = '/tmp/auto-update/executing_log/%s_%s.log'
# Path and files for temporarily saving devserver codes, devserver and
# update engine log.
_CROS_UPDATE_TEMP_PATH = '/tmp/cros-update_%s_%s'
_CROS_HOSTLOG_PATTERN = 'devserver_hostlog*'
# The string for update process finished
FINISHED = 'Completed'
ERROR_TAG = 'Error'
def ReadOneLine(filename):
"""Read one line from file.
Args:
filename: The file to be read.
"""
return open(filename, 'r').readline().rstrip('\n')
def IsProcessAlive(pid):
"""Detect whether a process is alive or not.
Args:
pid: The process id.
"""
path = '/proc/%s/stat' % pid
try:
stat = ReadOneLine(path)
except IOError:
if not os.path.exists(path):
return False
raise
return stat.split()[2] != 'Z'
def GetExecuteLogFile(host_name, pid):
"""Return the whole path of execute log file."""
if not os.path.exists(os.path.dirname(_EXECUTE_LOG_FILE_PATH)):
osutils.SafeMakedirs(os.path.dirname(_EXECUTE_LOG_FILE_PATH))
return _EXECUTE_LOG_FILE_PATH % (host_name, pid)
def GetTrackStatusFile(host_name, pid):
"""Return the whole path of track status file."""
if not os.path.exists(os.path.dirname(_TRACK_LOG_FILE_PATH)):
osutils.SafeMakedirs(os.path.dirname(_TRACK_LOG_FILE_PATH))
return _TRACK_LOG_FILE_PATH % (host_name, pid)
def GetAllTrackStatusFileByTime():
"""Return all track status files existing in TRACK_LOG_FILE_PATH.
Returns:
A track status file list ordered by created time reversely.
"""
return sorted(glob.glob(_TRACK_LOG_FILE_PATH % ('*', '*')),
key=os.path.getctime, reverse=True)
def ParsePidFromTrackLogFileName(track_log_filename):
"""Parse pid from a given track log file's name.
The track log file's name for auto-update is fixed:
hostname_pid.log
This func is used to parse pid from a given track log file.
Args:
track_log_filename: the filename of the track log to be parsed.
Returns:
the parsed pid (int).
"""
match = re.match(_TRACK_LOG_FILE_NAME_PATTERN, track_log_filename)
try:
return int(match.groups()[1])
except (AttributeError, IndexError, ValueError) as e:
_Log('Cannot parse pid from track log file %s: %s', track_log_filename, e)
return None
def GetAllTrackStatusFileByHostName(host_name):
"""Return a list of existing track status files generated for a host."""
return glob.glob(_TRACK_LOG_FILE_PATH % (host_name, '*'))
def GetAllRunningAUProcess():
"""Get all the ongoing AU processes' pids from tracking logs.
This func only checks the tracking logs generated in latest several hours,
which is for avoiding the case that 'there's a running process whose id is
as the same as a previous AU process'.
Returns:
A list of background AU processes' pids.
"""
pids = []
now = datetime.datetime.now()
track_log_list = GetAllTrackStatusFileByTime()
# Only check log file created in 3 hours.
for track_log in track_log_list:
try:
created_time = datetime.datetime.fromtimestamp(
os.path.getctime(track_log))
if now - created_time >= datetime.timedelta(hours=AU_PROCESS_HOUR_GAP):
break
pid = ParsePidFromTrackLogFileName(os.path.basename(track_log))
if pid and IsProcessAlive(pid):
pids.append(pid)
except (ValueError, os.error) as e:
_Log('Error happened in getting pid from %s: %s', track_log, e)
return pids
def GetAUTempDirectory(host_name, pid):
"""Return the temp dir for storing codes and logs during auto-update."""
au_tempdir = _CROS_UPDATE_TEMP_PATH % (host_name, pid)
if not os.path.exists(au_tempdir):
osutils.SafeMakedirs(au_tempdir)
return au_tempdir
def ReadExecuteLogFile(host_name, pid):
"""Return the content of execute log file."""
return osutils.ReadFile(GetExecuteLogFile(host_name, pid))
def ReadAUHostLogFiles(host_name, pid):
"""Returns a dictionary containing the devserver host log files."""
au_dir = GetAUTempDirectory(host_name, pid)
hostlog_filenames = glob.glob(os.path.join(au_dir, _CROS_HOSTLOG_PATTERN))
hostlog_files = {}
for f in hostlog_filenames:
hostlog_files[os.path.basename(f)] = osutils.ReadFile(f)
return hostlog_files
def DelTrackStatusFile(host_name, pid):
"""Delete the track status log."""
osutils.SafeUnlink(GetTrackStatusFile(host_name, pid))
def DelExecuteLogFile(host_name, pid):
"""Delete the track status log."""
osutils.SafeUnlink(GetExecuteLogFile(host_name, pid))
def DelAUTempDirectory(host_name, pid):
"""Delete the directory including auto-update-related logs."""
osutils.RmDir(GetAUTempDirectory(host_name, pid))
class AUProgress(object):
"""Used for tracking the CrOS auto-update progress."""
def __init__(self, host_name, pid):
"""Initialize a CrOS update progress instance.
Args:
host_name: The name of host, should be in the file_name of the status
tracking file of auto-update process.
pid: The process id, should be in the file_name too.
"""
self.host_name = host_name
self.pid = pid
@property
def track_status_file(self):
"""The track status file to record the CrOS auto-update progress."""
return GetTrackStatusFile(self.host_name, self.pid)
def WriteStatus(self, content):
"""Write auto-update progress into status tracking file.
Args:
content: The content to be recorded.
"""
if not self.track_status_file:
return
try:
with open(self.track_status_file, 'w') as out_log:
out_log.write(content)
except Exception as e:
logging.error('Cannot write au status: %r', e)
def ReadStatus(self):
"""Read auto-update progress from status tracking file."""
with open(self.track_status_file, 'r') as out_log:
return out_log.read().rstrip('\n')