blob: 0a7d5c3931024b72145131cca7f50844a792b68c [file] [log] [blame]
Amin Hassani86583432019-10-03 10:45:45 -07001# -*- coding: utf-8 -*-
2# Copyright 2016 The Chromium OS Authors. All rights reserved.
xixuan52c2fba2016-05-20 17:02:48 -07003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""A progress class for tracking CrOS auto-update process.
7
8This class is mainly designed for:
9 1. Set the pattern for generating the filenames of
10 track_status_file/execute_log_file.
11 track_status_file: Used for record the current step of CrOS auto-update
12 process. Only has one line.
13 execute_log_file: Used for record the whole logging info of the CrOS
14 auto-update process, including any debug information.
15 2. Write current auto-update process into the track_status_file.
16 3. Read current auto-update process from the track_status_file.
17
18This file also offers external functions that are related to add/check/delete
19the progress of the CrOS auto-update process.
20"""
21
22from __future__ import print_function
23
xixuan447ad9d2017-02-28 14:46:20 -080024import datetime
xixuan3bc974e2016-10-18 17:21:43 -070025import glob
Amin Hassani86583432019-10-03 10:45:45 -070026import logging # pylint: disable=cros-logging-import
xixuan52c2fba2016-05-20 17:02:48 -070027import os
xixuan447ad9d2017-02-28 14:46:20 -080028import re
29
30import log_util
31
Amin Hassani86583432019-10-03 10:45:45 -070032import setup_chromite # pylint: disable=unused-import
Amin Hassanie1163962019-10-16 14:54:01 -070033from chromite.lib import osutils
xixuan52c2fba2016-05-20 17:02:48 -070034
Amin Hassani86583432019-10-03 10:45:45 -070035# Module-local log function.
36def _Log(message, *args):
37 return log_util.LogWithTag('CROS_UPDATE_PROGRESS', message, *args)
xixuan447ad9d2017-02-28 14:46:20 -080038
xixuan52c2fba2016-05-20 17:02:48 -070039# Path for status tracking log.
xixuan3bc974e2016-10-18 17:21:43 -070040_TRACK_LOG_FILE_PATH = '/tmp/auto-update/tracking_log/%s_%s.log'
xixuan52c2fba2016-05-20 17:02:48 -070041
xixuan447ad9d2017-02-28 14:46:20 -080042# Pattern for status tracking log filename.
43_TRACK_LOG_FILE_NAME_PATTERN = r'([^_]+)_([^_]+).log'
44
45# The gap hour used in checking AU processes' count.
46AU_PROCESS_HOUR_GAP = 3
47
xixuan52c2fba2016-05-20 17:02:48 -070048# Path for executing log.
xixuan3bc974e2016-10-18 17:21:43 -070049_EXECUTE_LOG_FILE_PATH = '/tmp/auto-update/executing_log/%s_%s.log'
50
51# Path and files for temporarily saving devserver codes, devserver and
52# update engine log.
53_CROS_UPDATE_TEMP_PATH = '/tmp/cros-update_%s_%s'
xixuan52c2fba2016-05-20 17:02:48 -070054
David Haddock9f459632017-05-11 14:45:46 -070055_CROS_HOSTLOG_PATTERN = 'devserver_hostlog*'
56
xixuan52c2fba2016-05-20 17:02:48 -070057# The string for update process finished
58FINISHED = 'Completed'
59ERROR_TAG = 'Error'
60
61
62def ReadOneLine(filename):
63 """Read one line from file.
64
65 Args:
66 filename: The file to be read.
67 """
68 return open(filename, 'r').readline().rstrip('\n')
69
70
71def IsProcessAlive(pid):
72 """Detect whether a process is alive or not.
73
74 Args:
75 pid: The process id.
76 """
77 path = '/proc/%s/stat' % pid
78 try:
79 stat = ReadOneLine(path)
80 except IOError:
81 if not os.path.exists(path):
82 return False
83
84 raise
85
86 return stat.split()[2] != 'Z'
87
88
89def GetExecuteLogFile(host_name, pid):
90 """Return the whole path of execute log file."""
xixuan3bc974e2016-10-18 17:21:43 -070091 if not os.path.exists(os.path.dirname(_EXECUTE_LOG_FILE_PATH)):
92 osutils.SafeMakedirs(os.path.dirname(_EXECUTE_LOG_FILE_PATH))
xixuan52c2fba2016-05-20 17:02:48 -070093
xixuan3bc974e2016-10-18 17:21:43 -070094 return _EXECUTE_LOG_FILE_PATH % (host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -070095
96
97def GetTrackStatusFile(host_name, pid):
98 """Return the whole path of track status file."""
xixuan3bc974e2016-10-18 17:21:43 -070099 if not os.path.exists(os.path.dirname(_TRACK_LOG_FILE_PATH)):
100 osutils.SafeMakedirs(os.path.dirname(_TRACK_LOG_FILE_PATH))
xixuan52c2fba2016-05-20 17:02:48 -0700101
xixuan3bc974e2016-10-18 17:21:43 -0700102 return _TRACK_LOG_FILE_PATH % (host_name, pid)
103
104
xixuan447ad9d2017-02-28 14:46:20 -0800105def GetAllTrackStatusFileByTime():
106 """Return all track status files existing in TRACK_LOG_FILE_PATH.
107
108 Returns:
109 A track status file list ordered by created time reversely.
110 """
111 return sorted(glob.glob(_TRACK_LOG_FILE_PATH % ('*', '*')),
112 key=os.path.getctime, reverse=True)
113
114
115def ParsePidFromTrackLogFileName(track_log_filename):
116 """Parse pid from a given track log file's name.
117
118 The track log file's name for auto-update is fixed:
119 hostname_pid.log
120
121 This func is used to parse pid from a given track log file.
122
123 Args:
124 track_log_filename: the filename of the track log to be parsed.
125
126 Returns:
127 the parsed pid (int).
128 """
129 match = re.match(_TRACK_LOG_FILE_NAME_PATTERN, track_log_filename)
130 try:
131 return int(match.groups()[1])
132 except (AttributeError, IndexError, ValueError) as e:
133 _Log('Cannot parse pid from track log file %s: %s', track_log_filename, e)
134 return None
135
136
xixuan3bc974e2016-10-18 17:21:43 -0700137def GetAllTrackStatusFileByHostName(host_name):
138 """Return a list of existing track status files generated for a host."""
139 return glob.glob(_TRACK_LOG_FILE_PATH % (host_name, '*'))
140
141
xixuan447ad9d2017-02-28 14:46:20 -0800142def GetAllRunningAUProcess():
143 """Get all the ongoing AU processes' pids from tracking logs.
144
145 This func only checks the tracking logs generated in latest several hours,
146 which is for avoiding the case that 'there's a running process whose id is
147 as the same as a previous AU process'.
148
149 Returns:
150 A list of background AU processes' pids.
151 """
152 pids = []
153 now = datetime.datetime.now()
154 track_log_list = GetAllTrackStatusFileByTime()
155 # Only check log file created in 3 hours.
156 for track_log in track_log_list:
157 try:
158 created_time = datetime.datetime.fromtimestamp(
159 os.path.getctime(track_log))
160 if now - created_time >= datetime.timedelta(hours=AU_PROCESS_HOUR_GAP):
161 break
162
163 pid = ParsePidFromTrackLogFileName(os.path.basename(track_log))
164 if pid and IsProcessAlive(pid):
165 pids.append(pid)
166 except (ValueError, os.error) as e:
167 _Log('Error happened in getting pid from %s: %s', track_log, e)
168
169 return pids
170
171
xixuan3bc974e2016-10-18 17:21:43 -0700172def GetAUTempDirectory(host_name, pid):
173 """Return the temp dir for storing codes and logs during auto-update."""
174 au_tempdir = _CROS_UPDATE_TEMP_PATH % (host_name, pid)
175 if not os.path.exists(au_tempdir):
176 osutils.SafeMakedirs(au_tempdir)
177
178 return au_tempdir
xixuan52c2fba2016-05-20 17:02:48 -0700179
180
xixuan1bbfaba2016-10-13 17:53:22 -0700181def ReadExecuteLogFile(host_name, pid):
182 """Return the content of execute log file."""
183 return osutils.ReadFile(GetExecuteLogFile(host_name, pid))
184
185
David Haddock9f459632017-05-11 14:45:46 -0700186def ReadAUHostLogFiles(host_name, pid):
187 """Returns a dictionary containing the devserver host log files."""
188 au_dir = GetAUTempDirectory(host_name, pid)
189 hostlog_filenames = glob.glob(os.path.join(au_dir, _CROS_HOSTLOG_PATTERN))
190 hostlog_files = {}
191 for f in hostlog_filenames:
192 hostlog_files[os.path.basename(f)] = osutils.ReadFile(f)
193 return hostlog_files
194
195
xixuan52c2fba2016-05-20 17:02:48 -0700196def DelTrackStatusFile(host_name, pid):
197 """Delete the track status log."""
198 osutils.SafeUnlink(GetTrackStatusFile(host_name, pid))
199
200
xixuan1bbfaba2016-10-13 17:53:22 -0700201def DelExecuteLogFile(host_name, pid):
202 """Delete the track status log."""
203 osutils.SafeUnlink(GetExecuteLogFile(host_name, pid))
204
205
xixuan3bc974e2016-10-18 17:21:43 -0700206def DelAUTempDirectory(host_name, pid):
207 """Delete the directory including auto-update-related logs."""
208 osutils.RmDir(GetAUTempDirectory(host_name, pid))
209
210
xixuan52c2fba2016-05-20 17:02:48 -0700211class AUProgress(object):
212 """Used for tracking the CrOS auto-update progress."""
213
214 def __init__(self, host_name, pid):
215 """Initialize a CrOS update progress instance.
216
217 Args:
218 host_name: The name of host, should be in the file_name of the status
219 tracking file of auto-update process.
220 pid: The process id, should be in the file_name too.
221 """
222 self.host_name = host_name
223 self.pid = pid
224
225 @property
226 def track_status_file(self):
227 """The track status file to record the CrOS auto-update progress."""
228 return GetTrackStatusFile(self.host_name, self.pid)
229
230 def WriteStatus(self, content):
231 """Write auto-update progress into status tracking file.
232
233 Args:
234 content: The content to be recorded.
235 """
236 if not self.track_status_file:
237 return
238
239 try:
240 with open(self.track_status_file, 'w') as out_log:
241 out_log.write(content)
242 except Exception as e:
243 logging.error('Cannot write au status: %r', e)
244
245 def ReadStatus(self):
246 """Read auto-update progress from status tracking file."""
xixuan28d99072016-10-06 12:24:16 -0700247 with open(self.track_status_file, 'r') as out_log:
248 return out_log.read().rstrip('\n')