blob: c337e0a0c6eec099be857b5ac9a946b487ffabcd [file] [log] [blame]
xixuan52c2fba2016-05-21 00:02:481# Copyright (c) 2016 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""A progress class for tracking CrOS auto-update process.
6
7This class is mainly designed for:
8 1. Set the pattern for generating the filenames of
9 track_status_file/execute_log_file.
10 track_status_file: Used for record the current step of CrOS auto-update
11 process. Only has one line.
12 execute_log_file: Used for record the whole logging info of the CrOS
13 auto-update process, including any debug information.
14 2. Write current auto-update process into the track_status_file.
15 3. Read current auto-update process from the track_status_file.
16
17This file also offers external functions that are related to add/check/delete
18the progress of the CrOS auto-update process.
19"""
20
21from __future__ import print_function
22
xixuan447ad9d2017-02-28 22:46:2023import datetime
xixuan3bc974e2016-10-19 00:21:4324import glob
xixuan52c2fba2016-05-21 00:02:4825import logging
26import os
xixuan447ad9d2017-02-28 22:46:2027import re
28
29import log_util
30
31# Module-local log function.
32def _Log(message, *args):
33 return log_util.LogWithTag('CROS_UPDATE_PROGRESS', message, *args)
xixuan52c2fba2016-05-21 00:02:4834
xixuancf58dd32016-08-24 20:57:0635# only import setup_chromite before chromite import.
36import setup_chromite # pylint: disable=unused-import
xixuan52c2fba2016-05-21 00:02:4837try:
38 from chromite.lib import osutils
39except ImportError as e:
xixuan447ad9d2017-02-28 22:46:2040 _Log('chromite cannot be imported: %r', e)
xixuan52c2fba2016-05-21 00:02:4841 osutils = None
42
xixuan447ad9d2017-02-28 22:46:2043
xixuan52c2fba2016-05-21 00:02:4844# Path for status tracking log.
xixuan3bc974e2016-10-19 00:21:4345_TRACK_LOG_FILE_PATH = '/tmp/auto-update/tracking_log/%s_%s.log'
xixuan52c2fba2016-05-21 00:02:4846
xixuan447ad9d2017-02-28 22:46:2047# Pattern for status tracking log filename.
48_TRACK_LOG_FILE_NAME_PATTERN = r'([^_]+)_([^_]+).log'
49
50# The gap hour used in checking AU processes' count.
51AU_PROCESS_HOUR_GAP = 3
52
xixuan52c2fba2016-05-21 00:02:4853# Path for executing log.
xixuan3bc974e2016-10-19 00:21:4354_EXECUTE_LOG_FILE_PATH = '/tmp/auto-update/executing_log/%s_%s.log'
55
56# Path and files for temporarily saving devserver codes, devserver and
57# update engine log.
58_CROS_UPDATE_TEMP_PATH = '/tmp/cros-update_%s_%s'
xixuan52c2fba2016-05-21 00:02:4859
David Haddock9f459632017-05-11 21:45:4660_CROS_HOSTLOG_PATTERN = 'devserver_hostlog*'
61
xixuan52c2fba2016-05-21 00:02:4862# The string for update process finished
63FINISHED = 'Completed'
64ERROR_TAG = 'Error'
65
66
67def ReadOneLine(filename):
68 """Read one line from file.
69
70 Args:
71 filename: The file to be read.
72 """
73 return open(filename, 'r').readline().rstrip('\n')
74
75
76def IsProcessAlive(pid):
77 """Detect whether a process is alive or not.
78
79 Args:
80 pid: The process id.
81 """
82 path = '/proc/%s/stat' % pid
83 try:
84 stat = ReadOneLine(path)
85 except IOError:
86 if not os.path.exists(path):
87 return False
88
89 raise
90
91 return stat.split()[2] != 'Z'
92
93
94def GetExecuteLogFile(host_name, pid):
95 """Return the whole path of execute log file."""
xixuan3bc974e2016-10-19 00:21:4396 if not os.path.exists(os.path.dirname(_EXECUTE_LOG_FILE_PATH)):
97 osutils.SafeMakedirs(os.path.dirname(_EXECUTE_LOG_FILE_PATH))
xixuan52c2fba2016-05-21 00:02:4898
xixuan3bc974e2016-10-19 00:21:4399 return _EXECUTE_LOG_FILE_PATH % (host_name, pid)
xixuan52c2fba2016-05-21 00:02:48100
101
102def GetTrackStatusFile(host_name, pid):
103 """Return the whole path of track status file."""
xixuan3bc974e2016-10-19 00:21:43104 if not os.path.exists(os.path.dirname(_TRACK_LOG_FILE_PATH)):
105 osutils.SafeMakedirs(os.path.dirname(_TRACK_LOG_FILE_PATH))
xixuan52c2fba2016-05-21 00:02:48106
xixuan3bc974e2016-10-19 00:21:43107 return _TRACK_LOG_FILE_PATH % (host_name, pid)
108
109
xixuan447ad9d2017-02-28 22:46:20110def GetAllTrackStatusFileByTime():
111 """Return all track status files existing in TRACK_LOG_FILE_PATH.
112
113 Returns:
114 A track status file list ordered by created time reversely.
115 """
116 return sorted(glob.glob(_TRACK_LOG_FILE_PATH % ('*', '*')),
117 key=os.path.getctime, reverse=True)
118
119
120def ParsePidFromTrackLogFileName(track_log_filename):
121 """Parse pid from a given track log file's name.
122
123 The track log file's name for auto-update is fixed:
124 hostname_pid.log
125
126 This func is used to parse pid from a given track log file.
127
128 Args:
129 track_log_filename: the filename of the track log to be parsed.
130
131 Returns:
132 the parsed pid (int).
133 """
134 match = re.match(_TRACK_LOG_FILE_NAME_PATTERN, track_log_filename)
135 try:
136 return int(match.groups()[1])
137 except (AttributeError, IndexError, ValueError) as e:
138 _Log('Cannot parse pid from track log file %s: %s', track_log_filename, e)
139 return None
140
141
xixuan3bc974e2016-10-19 00:21:43142def GetAllTrackStatusFileByHostName(host_name):
143 """Return a list of existing track status files generated for a host."""
144 return glob.glob(_TRACK_LOG_FILE_PATH % (host_name, '*'))
145
146
xixuan447ad9d2017-02-28 22:46:20147def GetAllRunningAUProcess():
148 """Get all the ongoing AU processes' pids from tracking logs.
149
150 This func only checks the tracking logs generated in latest several hours,
151 which is for avoiding the case that 'there's a running process whose id is
152 as the same as a previous AU process'.
153
154 Returns:
155 A list of background AU processes' pids.
156 """
157 pids = []
158 now = datetime.datetime.now()
159 track_log_list = GetAllTrackStatusFileByTime()
160 # Only check log file created in 3 hours.
161 for track_log in track_log_list:
162 try:
163 created_time = datetime.datetime.fromtimestamp(
164 os.path.getctime(track_log))
165 if now - created_time >= datetime.timedelta(hours=AU_PROCESS_HOUR_GAP):
166 break
167
168 pid = ParsePidFromTrackLogFileName(os.path.basename(track_log))
169 if pid and IsProcessAlive(pid):
170 pids.append(pid)
171 except (ValueError, os.error) as e:
172 _Log('Error happened in getting pid from %s: %s', track_log, e)
173
174 return pids
175
176
xixuan3bc974e2016-10-19 00:21:43177def GetAUTempDirectory(host_name, pid):
178 """Return the temp dir for storing codes and logs during auto-update."""
179 au_tempdir = _CROS_UPDATE_TEMP_PATH % (host_name, pid)
180 if not os.path.exists(au_tempdir):
181 osutils.SafeMakedirs(au_tempdir)
182
183 return au_tempdir
xixuan52c2fba2016-05-21 00:02:48184
185
xixuan1bbfaba2016-10-14 00:53:22186def ReadExecuteLogFile(host_name, pid):
187 """Return the content of execute log file."""
188 return osutils.ReadFile(GetExecuteLogFile(host_name, pid))
189
190
David Haddock9f459632017-05-11 21:45:46191def ReadAUHostLogFiles(host_name, pid):
192 """Returns a dictionary containing the devserver host log files."""
193 au_dir = GetAUTempDirectory(host_name, pid)
194 hostlog_filenames = glob.glob(os.path.join(au_dir, _CROS_HOSTLOG_PATTERN))
195 hostlog_files = {}
196 for f in hostlog_filenames:
197 hostlog_files[os.path.basename(f)] = osutils.ReadFile(f)
198 return hostlog_files
199
200
xixuan52c2fba2016-05-21 00:02:48201def DelTrackStatusFile(host_name, pid):
202 """Delete the track status log."""
203 osutils.SafeUnlink(GetTrackStatusFile(host_name, pid))
204
205
xixuan1bbfaba2016-10-14 00:53:22206def DelExecuteLogFile(host_name, pid):
207 """Delete the track status log."""
208 osutils.SafeUnlink(GetExecuteLogFile(host_name, pid))
209
210
xixuan3bc974e2016-10-19 00:21:43211def DelAUTempDirectory(host_name, pid):
212 """Delete the directory including auto-update-related logs."""
213 osutils.RmDir(GetAUTempDirectory(host_name, pid))
214
215
xixuan52c2fba2016-05-21 00:02:48216class AUProgress(object):
217 """Used for tracking the CrOS auto-update progress."""
218
219 def __init__(self, host_name, pid):
220 """Initialize a CrOS update progress instance.
221
222 Args:
223 host_name: The name of host, should be in the file_name of the status
224 tracking file of auto-update process.
225 pid: The process id, should be in the file_name too.
226 """
227 self.host_name = host_name
228 self.pid = pid
229
230 @property
231 def track_status_file(self):
232 """The track status file to record the CrOS auto-update progress."""
233 return GetTrackStatusFile(self.host_name, self.pid)
234
235 def WriteStatus(self, content):
236 """Write auto-update progress into status tracking file.
237
238 Args:
239 content: The content to be recorded.
240 """
241 if not self.track_status_file:
242 return
243
244 try:
245 with open(self.track_status_file, 'w') as out_log:
246 out_log.write(content)
247 except Exception as e:
248 logging.error('Cannot write au status: %r', e)
249
250 def ReadStatus(self):
251 """Read auto-update progress from status tracking file."""
xixuan28d99072016-10-06 19:24:16252 with open(self.track_status_file, 'r') as out_log:
253 return out_log.read().rstrip('\n')