blob: 72b872a662c84fa9ef11eeaebd49e4acced191e5 [file] [log] [blame]
xixuan52c2fba2016-05-20 17:02:48 -07001# 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 14:46:20 -080023import datetime
xixuan3bc974e2016-10-18 17:21:43 -070024import glob
xixuan52c2fba2016-05-20 17:02:48 -070025import logging
26import os
xixuan447ad9d2017-02-28 14:46:20 -080027import 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-20 17:02:48 -070034
xixuancf58dd32016-08-24 13:57:06 -070035# only import setup_chromite before chromite import.
36import setup_chromite # pylint: disable=unused-import
xixuan52c2fba2016-05-20 17:02:48 -070037try:
38 from chromite.lib import osutils
39except ImportError as e:
xixuan447ad9d2017-02-28 14:46:20 -080040 _Log('chromite cannot be imported: %r', e)
xixuan52c2fba2016-05-20 17:02:48 -070041 osutils = None
42
xixuan447ad9d2017-02-28 14:46:20 -080043
xixuan52c2fba2016-05-20 17:02:48 -070044# Path for status tracking log.
xixuan3bc974e2016-10-18 17:21:43 -070045_TRACK_LOG_FILE_PATH = '/tmp/auto-update/tracking_log/%s_%s.log'
xixuan52c2fba2016-05-20 17:02:48 -070046
xixuan447ad9d2017-02-28 14:46:20 -080047# 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-20 17:02:48 -070053# Path for executing log.
xixuan3bc974e2016-10-18 17:21:43 -070054_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-20 17:02:48 -070059
60# The string for update process finished
61FINISHED = 'Completed'
62ERROR_TAG = 'Error'
63
64
65def ReadOneLine(filename):
66 """Read one line from file.
67
68 Args:
69 filename: The file to be read.
70 """
71 return open(filename, 'r').readline().rstrip('\n')
72
73
74def IsProcessAlive(pid):
75 """Detect whether a process is alive or not.
76
77 Args:
78 pid: The process id.
79 """
80 path = '/proc/%s/stat' % pid
81 try:
82 stat = ReadOneLine(path)
83 except IOError:
84 if not os.path.exists(path):
85 return False
86
87 raise
88
89 return stat.split()[2] != 'Z'
90
91
92def GetExecuteLogFile(host_name, pid):
93 """Return the whole path of execute log file."""
xixuan3bc974e2016-10-18 17:21:43 -070094 if not os.path.exists(os.path.dirname(_EXECUTE_LOG_FILE_PATH)):
95 osutils.SafeMakedirs(os.path.dirname(_EXECUTE_LOG_FILE_PATH))
xixuan52c2fba2016-05-20 17:02:48 -070096
xixuan3bc974e2016-10-18 17:21:43 -070097 return _EXECUTE_LOG_FILE_PATH % (host_name, pid)
xixuan52c2fba2016-05-20 17:02:48 -070098
99
100def GetTrackStatusFile(host_name, pid):
101 """Return the whole path of track status file."""
xixuan3bc974e2016-10-18 17:21:43 -0700102 if not os.path.exists(os.path.dirname(_TRACK_LOG_FILE_PATH)):
103 osutils.SafeMakedirs(os.path.dirname(_TRACK_LOG_FILE_PATH))
xixuan52c2fba2016-05-20 17:02:48 -0700104
xixuan3bc974e2016-10-18 17:21:43 -0700105 return _TRACK_LOG_FILE_PATH % (host_name, pid)
106
107
xixuan447ad9d2017-02-28 14:46:20 -0800108def GetAllTrackStatusFileByTime():
109 """Return all track status files existing in TRACK_LOG_FILE_PATH.
110
111 Returns:
112 A track status file list ordered by created time reversely.
113 """
114 return sorted(glob.glob(_TRACK_LOG_FILE_PATH % ('*', '*')),
115 key=os.path.getctime, reverse=True)
116
117
118def ParsePidFromTrackLogFileName(track_log_filename):
119 """Parse pid from a given track log file's name.
120
121 The track log file's name for auto-update is fixed:
122 hostname_pid.log
123
124 This func is used to parse pid from a given track log file.
125
126 Args:
127 track_log_filename: the filename of the track log to be parsed.
128
129 Returns:
130 the parsed pid (int).
131 """
132 match = re.match(_TRACK_LOG_FILE_NAME_PATTERN, track_log_filename)
133 try:
134 return int(match.groups()[1])
135 except (AttributeError, IndexError, ValueError) as e:
136 _Log('Cannot parse pid from track log file %s: %s', track_log_filename, e)
137 return None
138
139
xixuan3bc974e2016-10-18 17:21:43 -0700140def GetAllTrackStatusFileByHostName(host_name):
141 """Return a list of existing track status files generated for a host."""
142 return glob.glob(_TRACK_LOG_FILE_PATH % (host_name, '*'))
143
144
xixuan447ad9d2017-02-28 14:46:20 -0800145def GetAllRunningAUProcess():
146 """Get all the ongoing AU processes' pids from tracking logs.
147
148 This func only checks the tracking logs generated in latest several hours,
149 which is for avoiding the case that 'there's a running process whose id is
150 as the same as a previous AU process'.
151
152 Returns:
153 A list of background AU processes' pids.
154 """
155 pids = []
156 now = datetime.datetime.now()
157 track_log_list = GetAllTrackStatusFileByTime()
158 # Only check log file created in 3 hours.
159 for track_log in track_log_list:
160 try:
161 created_time = datetime.datetime.fromtimestamp(
162 os.path.getctime(track_log))
163 if now - created_time >= datetime.timedelta(hours=AU_PROCESS_HOUR_GAP):
164 break
165
166 pid = ParsePidFromTrackLogFileName(os.path.basename(track_log))
167 if pid and IsProcessAlive(pid):
168 pids.append(pid)
169 except (ValueError, os.error) as e:
170 _Log('Error happened in getting pid from %s: %s', track_log, e)
171
172 return pids
173
174
xixuan3bc974e2016-10-18 17:21:43 -0700175def GetAUTempDirectory(host_name, pid):
176 """Return the temp dir for storing codes and logs during auto-update."""
177 au_tempdir = _CROS_UPDATE_TEMP_PATH % (host_name, pid)
178 if not os.path.exists(au_tempdir):
179 osutils.SafeMakedirs(au_tempdir)
180
181 return au_tempdir
xixuan52c2fba2016-05-20 17:02:48 -0700182
183
xixuan1bbfaba2016-10-13 17:53:22 -0700184def ReadExecuteLogFile(host_name, pid):
185 """Return the content of execute log file."""
186 return osutils.ReadFile(GetExecuteLogFile(host_name, pid))
187
188
xixuan52c2fba2016-05-20 17:02:48 -0700189def DelTrackStatusFile(host_name, pid):
190 """Delete the track status log."""
191 osutils.SafeUnlink(GetTrackStatusFile(host_name, pid))
192
193
xixuan1bbfaba2016-10-13 17:53:22 -0700194def DelExecuteLogFile(host_name, pid):
195 """Delete the track status log."""
196 osutils.SafeUnlink(GetExecuteLogFile(host_name, pid))
197
198
xixuan3bc974e2016-10-18 17:21:43 -0700199def DelAUTempDirectory(host_name, pid):
200 """Delete the directory including auto-update-related logs."""
201 osutils.RmDir(GetAUTempDirectory(host_name, pid))
202
203
xixuan52c2fba2016-05-20 17:02:48 -0700204class AUProgress(object):
205 """Used for tracking the CrOS auto-update progress."""
206
207 def __init__(self, host_name, pid):
208 """Initialize a CrOS update progress instance.
209
210 Args:
211 host_name: The name of host, should be in the file_name of the status
212 tracking file of auto-update process.
213 pid: The process id, should be in the file_name too.
214 """
215 self.host_name = host_name
216 self.pid = pid
217
218 @property
219 def track_status_file(self):
220 """The track status file to record the CrOS auto-update progress."""
221 return GetTrackStatusFile(self.host_name, self.pid)
222
223 def WriteStatus(self, content):
224 """Write auto-update progress into status tracking file.
225
226 Args:
227 content: The content to be recorded.
228 """
229 if not self.track_status_file:
230 return
231
232 try:
233 with open(self.track_status_file, 'w') as out_log:
234 out_log.write(content)
235 except Exception as e:
236 logging.error('Cannot write au status: %r', e)
237
238 def ReadStatus(self):
239 """Read auto-update progress from status tracking file."""
xixuan28d99072016-10-06 12:24:16 -0700240 with open(self.track_status_file, 'r') as out_log:
241 return out_log.read().rstrip('\n')