blob: 0020984d87cda1cfd18e0c62986467ef7f8d447d [file] [log] [blame]
Dan Shi07e09af2013-04-12 09:31:29 -07001# pylint: disable-msg=C0111
2
Paul Pendlebury7c1fdcf2011-05-04 12:39:15 -07003# Copyright (c) 2011 The Chromium OS Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
mbligh57e78662008-06-17 19:53:49 +00006"""
7The main job wrapper for the server side.
8
9This is the core infrastructure. Derived from the client side job.py
10
11Copyright Martin J. Bligh, Andy Whitcroft 2007
12"""
13
Allen Liab020912016-09-19 18:07:41 -070014import errno
15import fcntl
16import getpass
17import itertools
18import logging
19import os
20import pickle
21import platform
22import re
23import select
24import shutil
25import sys
26import tempfile
27import time
28import traceback
Prathmesh Prabhu588007d2017-06-15 00:31:31 -070029import uuid
Allen Liab020912016-09-19 18:07:41 -070030import warnings
31
showard75cdfee2009-06-10 17:40:41 +000032from autotest_lib.client.bin import sysinfo
Allen Liab020912016-09-19 18:07:41 -070033from autotest_lib.client.common_lib import base_job
Dan Shif53d1262017-06-19 11:25:25 -070034from autotest_lib.client.common_lib import control_data
Allen Liab020912016-09-19 18:07:41 -070035from autotest_lib.client.common_lib import error
showard75cdfee2009-06-10 17:40:41 +000036from autotest_lib.client.common_lib import logging_manager
Allen Liab020912016-09-19 18:07:41 -070037from autotest_lib.client.common_lib import packages
38from autotest_lib.client.common_lib import utils
39from autotest_lib.server import profilers
Prathmesh Prabhu20ea1062017-07-06 10:23:42 -070040from autotest_lib.server import site_gtest_runner
Allen Liab020912016-09-19 18:07:41 -070041from autotest_lib.server import subcommand
42from autotest_lib.server import test
Simran Basi1bf60eb2015-12-01 16:39:29 -080043from autotest_lib.server import utils as server_utils
44from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Allen Liab020912016-09-19 18:07:41 -070045from autotest_lib.server.hosts import abstract_ssh
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -080046from autotest_lib.server.hosts import afe_store
Prathmesh Prabhu588007d2017-06-15 00:31:31 -070047from autotest_lib.server.hosts import file_store
48from autotest_lib.server.hosts import shadowing_store
Allen Liab020912016-09-19 18:07:41 -070049from autotest_lib.server.hosts import factory as host_factory
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -080050from autotest_lib.server.hosts import host_info
Hidehiko Abe06893302017-06-24 07:32:38 +090051from autotest_lib.server.hosts import ssh_multiplex
Allen Liab020912016-09-19 18:07:41 -070052from autotest_lib.tko import models as tko_models
Luigi Semenzatoe7064812017-02-03 14:47:59 -080053from autotest_lib.tko import parser_lib
jadmanski10646442008-08-13 14:05:21 +000054
Xixuan Wu60325a62017-11-20 17:21:15 -080055try:
56 from chromite.lib import metrics
57except ImportError:
58 metrics = utils.metrics_mock
59
jadmanski10646442008-08-13 14:05:21 +000060
mbligh084bc172008-10-18 14:02:45 +000061def _control_segment_path(name):
62 """Get the pathname of the named control segment file."""
jadmanski10646442008-08-13 14:05:21 +000063 server_dir = os.path.dirname(os.path.abspath(__file__))
mbligh084bc172008-10-18 14:02:45 +000064 return os.path.join(server_dir, "control_segments", name)
jadmanski10646442008-08-13 14:05:21 +000065
66
mbligh084bc172008-10-18 14:02:45 +000067CLIENT_CONTROL_FILENAME = 'control'
68SERVER_CONTROL_FILENAME = 'control.srv'
69MACHINES_FILENAME = '.machines'
jadmanski10646442008-08-13 14:05:21 +000070
mbligh084bc172008-10-18 14:02:45 +000071CLIENT_WRAPPER_CONTROL_FILE = _control_segment_path('client_wrapper')
72CRASHDUMPS_CONTROL_FILE = _control_segment_path('crashdumps')
73CRASHINFO_CONTROL_FILE = _control_segment_path('crashinfo')
showard45ae8192008-11-05 19:32:53 +000074CLEANUP_CONTROL_FILE = _control_segment_path('cleanup')
mbligh084bc172008-10-18 14:02:45 +000075VERIFY_CONTROL_FILE = _control_segment_path('verify')
mbligh084bc172008-10-18 14:02:45 +000076REPAIR_CONTROL_FILE = _control_segment_path('repair')
Alex Millercb79ba72013-05-29 14:43:00 -070077PROVISION_CONTROL_FILE = _control_segment_path('provision')
beepscb6f1e22013-06-28 19:14:10 -070078VERIFY_JOB_REPO_URL_CONTROL_FILE = _control_segment_path('verify_job_repo_url')
Dan Shi07e09af2013-04-12 09:31:29 -070079RESET_CONTROL_FILE = _control_segment_path('reset')
Luigi Semenzato542d0d52016-06-29 11:30:15 -070080GET_NETWORK_STATS_CONTROL_FILE = _control_segment_path('get_network_stats')
jadmanski10646442008-08-13 14:05:21 +000081
mbligh062ed152009-01-13 00:57:14 +000082
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -070083def get_machine_dicts(machine_names, store_dir, in_lab, use_shadow_store,
84 host_attributes=None):
Prathmesh Prabhu429d1002017-01-10 15:30:53 -080085 """Converts a list of machine names to list of dicts.
86
Prathmesh Prabhu588007d2017-06-15 00:31:31 -070087 TODO(crbug.com/678430): This function temporarily has a side effect of
88 creating files under workdir for backing a FileStore. This side-effect will
89 go away once callers of autoserv start passing in the FileStore.
90
Prathmesh Prabhu429d1002017-01-10 15:30:53 -080091 @param machine_names: A list of machine names.
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -070092 @param store_dir: A directory to contain store backing files.
93 @param use_shadow_store: If True, we should create a ShadowingStore where
94 actual store is backed by the AFE but we create a backing file to
95 shadow the store. If False, backing file should already exist at:
96 ${store_dir}/${hostname}.store
Prathmesh Prabhu429d1002017-01-10 15:30:53 -080097 @param in_lab: A boolean indicating whether we're running in lab.
98 @param host_attributes: Optional list of host attributes to add for each
99 host.
100 @returns: A list of dicts. Each dict has the following keys:
101 'hostname': Name of the machine originally in machine_names (str).
102 'afe_host': A frontend.Host object for the machine, or a stub if
103 in_lab is false.
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -0800104 'host_info_store': A host_info.CachingHostInfoStore object to obtain
105 host information. A stub if in_lab is False.
Hidehiko Abe06893302017-06-24 07:32:38 +0900106 'connection_pool': ssh_multiplex.ConnectionPool instance to share
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -0700107 master ssh connection across control scripts. This is set to
108 None, and should be overridden for connection sharing.
Prathmesh Prabhu429d1002017-01-10 15:30:53 -0800109 """
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -0700110 # See autoserv_parser.parse_args. Only one of in_lab or host_attributes can
111 # be provided.
112 if in_lab and host_attributes:
113 raise error.AutoservError(
114 'in_lab and host_attribute are mutually exclusive.')
115
Prathmesh Prabhu429d1002017-01-10 15:30:53 -0800116 machine_dict_list = []
Prathmesh Prabhu429d1002017-01-10 15:30:53 -0800117 for machine in machine_names:
Prathmesh Prabhucbeab122017-05-30 11:23:53 -0700118 if not in_lab:
119 afe_host = server_utils.EmptyAFEHost()
120 host_info_store = host_info.InMemoryHostInfoStore()
121 if host_attributes is not None:
122 afe_host.attributes.update(host_attributes)
123 info = host_info.HostInfo(attributes=host_attributes)
124 host_info_store.commit(info)
Prathmesh Prabhueb566792018-06-12 10:08:04 -0700125 elif use_shadow_store:
Prathmesh Prabhucbeab122017-05-30 11:23:53 -0700126 afe_host = _create_afe_host(machine)
Prathmesh Prabhueb566792018-06-12 10:08:04 -0700127 host_info_store = _create_afe_backed_host_info_store(store_dir,
128 machine)
129 else:
130 afe_host = server_utils.EmptyAFEHost()
131 host_info_store = _create_file_backed_host_info_store(store_dir,
132 machine)
Prathmesh Prabhucbeab122017-05-30 11:23:53 -0700133
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -0800134 machine_dict_list.append({
135 'hostname' : machine,
136 'afe_host' : afe_host,
Prathmesh Prabhucbeab122017-05-30 11:23:53 -0700137 'host_info_store': host_info_store,
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -0700138 'connection_pool': None,
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -0800139 })
Prathmesh Prabhucbeab122017-05-30 11:23:53 -0700140
Prathmesh Prabhu429d1002017-01-10 15:30:53 -0800141 return machine_dict_list
142
143
jadmanski2a89dac2010-06-11 14:32:58 +0000144class status_indenter(base_job.status_indenter):
145 """Provide a simple integer-backed status indenter."""
146 def __init__(self):
147 self._indent = 0
148
149
150 @property
151 def indent(self):
152 return self._indent
153
154
155 def increment(self):
156 self._indent += 1
157
158
159 def decrement(self):
160 self._indent -= 1
161
162
jadmanski52053632010-06-11 21:08:10 +0000163 def get_context(self):
164 """Returns a context object for use by job.get_record_context."""
165 class context(object):
166 def __init__(self, indenter, indent):
167 self._indenter = indenter
168 self._indent = indent
169 def restore(self):
170 self._indenter._indent = self._indent
171 return context(self, self._indent)
172
173
jadmanski2a89dac2010-06-11 14:32:58 +0000174class server_job_record_hook(object):
175 """The job.record hook for server job. Used to inject WARN messages from
176 the console or vlm whenever new logs are written, and to echo any logs
177 to INFO level logging. Implemented as a class so that it can use state to
178 block recursive calls, so that the hook can call job.record itself to
179 log WARN messages.
180
181 Depends on job._read_warnings and job._logger.
182 """
183 def __init__(self, job):
184 self._job = job
185 self._being_called = False
186
187
188 def __call__(self, entry):
189 """A wrapper around the 'real' record hook, the _hook method, which
190 prevents recursion. This isn't making any effort to be threadsafe,
191 the intent is to outright block infinite recursion via a
192 job.record->_hook->job.record->_hook->job.record... chain."""
193 if self._being_called:
194 return
195 self._being_called = True
196 try:
197 self._hook(self._job, entry)
198 finally:
199 self._being_called = False
200
201
202 @staticmethod
203 def _hook(job, entry):
204 """The core hook, which can safely call job.record."""
205 entries = []
206 # poll all our warning loggers for new warnings
207 for timestamp, msg in job._read_warnings():
208 warning_entry = base_job.status_log_entry(
209 'WARN', None, None, msg, {}, timestamp=timestamp)
210 entries.append(warning_entry)
211 job.record_entry(warning_entry)
212 # echo rendered versions of all the status logs to info
213 entries.append(entry)
214 for entry in entries:
215 rendered_entry = job._logger.render_entry(entry)
216 logging.info(rendered_entry)
217
218
Prathmesh Prabhu20ea1062017-07-06 10:23:42 -0700219class server_job(base_job.base_job):
mbligh0d0f67d2009-11-06 03:15:03 +0000220 """The server-side concrete implementation of base_job.
jadmanski10646442008-08-13 14:05:21 +0000221
mbligh0d0f67d2009-11-06 03:15:03 +0000222 Optional properties provided by this implementation:
223 serverdir
mbligh0d0f67d2009-11-06 03:15:03 +0000224
mbligh0d0f67d2009-11-06 03:15:03 +0000225 warning_manager
226 warning_loggers
jadmanski10646442008-08-13 14:05:21 +0000227 """
228
mbligh0d0f67d2009-11-06 03:15:03 +0000229 _STATUS_VERSION = 1
jadmanski10646442008-08-13 14:05:21 +0000230
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700231 # TODO crbug.com/285395 eliminate ssh_verbosity_flag
jadmanski10646442008-08-13 14:05:21 +0000232 def __init__(self, control, args, resultdir, label, user, machines,
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -0700233 machine_dict_list,
Prathmesh Prabhue0b70c62018-04-24 15:33:42 -0700234 client=False,
beepsd0672682013-09-16 17:32:16 -0700235 ssh_user=host_factory.DEFAULT_SSH_USER,
236 ssh_port=host_factory.DEFAULT_SSH_PORT,
237 ssh_pass=host_factory.DEFAULT_SSH_PASS,
238 ssh_verbosity_flag=host_factory.DEFAULT_SSH_VERBOSITY,
239 ssh_options=host_factory.DEFAULT_SSH_OPTIONS,
Prathmesh Prabhu7d08ac12018-08-17 17:34:39 -0700240 group_name='',
Fang Dengd1c2b732013-08-20 12:59:46 -0700241 tag='', disable_sysinfo=False,
Dan Shi70647ca2015-07-16 22:52:35 -0700242 control_filename=SERVER_CONTROL_FILENAME,
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -0700243 parent_job_id=None, in_lab=False):
jadmanski10646442008-08-13 14:05:21 +0000244 """
mbligh374f3412009-05-13 21:29:45 +0000245 Create a server side job object.
mblighb5dac432008-11-27 00:38:44 +0000246
mblighe7d9c602009-07-02 19:02:33 +0000247 @param control: The pathname of the control file.
248 @param args: Passed to the control file.
249 @param resultdir: Where to throw the results.
250 @param label: Description of the job.
251 @param user: Username for the job (email address).
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -0700252 @param machines: A list of hostnames of the machines to use for the job.
253 @param machine_dict_list: A list of dicts for each of the machines above
254 as returned by get_machine_dicts.
mblighe7d9c602009-07-02 19:02:33 +0000255 @param client: True if this is a client-side control file.
mblighe7d9c602009-07-02 19:02:33 +0000256 @param ssh_user: The SSH username. [root]
257 @param ssh_port: The SSH port number. [22]
258 @param ssh_pass: The SSH passphrase, if needed.
Fang Dengd1c2b732013-08-20 12:59:46 -0700259 @param ssh_verbosity_flag: The SSH verbosity flag, '-v', '-vv',
260 '-vvv', or an empty string if not needed.
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700261 @param ssh_options: A string giving additional options that will be
262 included in ssh commands.
mblighe7d9c602009-07-02 19:02:33 +0000263 @param group_name: If supplied, this will be written out as
mbligh374f3412009-05-13 21:29:45 +0000264 host_group_name in the keyvals file for the parser.
mblighe7d9c602009-07-02 19:02:33 +0000265 @param tag: The job execution tag from the scheduler. [optional]
Christopher Wiley8a91f232013-07-09 11:02:27 -0700266 @param disable_sysinfo: Whether we should disable the sysinfo step of
267 tests for a modest shortening of test time. [optional]
mblighe0cbc912010-03-11 18:03:07 +0000268 @param control_filename: The filename where the server control file
269 should be written in the results directory.
Dan Shi70647ca2015-07-16 22:52:35 -0700270 @param parent_job_id: Job ID of the parent job. Default to None if the
271 job does not have a parent job.
Simran Basi1bf60eb2015-12-01 16:39:29 -0800272 @param in_lab: Boolean that indicates if this is running in the lab
273 environment.
jadmanski10646442008-08-13 14:05:21 +0000274 """
Prathmesh Prabhu7d08ac12018-08-17 17:34:39 -0700275 super(server_job, self).__init__(resultdir=resultdir)
mbligh0d0f67d2009-11-06 03:15:03 +0000276 self.control = control
277 self._uncollected_log_file = os.path.join(self.resultdir,
278 'uncollected_logs')
279 debugdir = os.path.join(self.resultdir, 'debug')
280 if not os.path.exists(debugdir):
281 os.mkdir(debugdir)
282
283 if user:
284 self.user = user
285 else:
286 self.user = getpass.getuser()
287
jadmanski808f4b12010-04-09 22:30:31 +0000288 self.args = args
Peter Mayo7a875762012-06-13 14:38:15 -0400289 self.label = label
jadmanski10646442008-08-13 14:05:21 +0000290 self.machines = machines
mbligh0d0f67d2009-11-06 03:15:03 +0000291 self._client = client
jadmanski10646442008-08-13 14:05:21 +0000292 self.warning_loggers = set()
jadmanskif37df842009-02-11 00:03:26 +0000293 self.warning_manager = warning_manager()
mbligh0d0f67d2009-11-06 03:15:03 +0000294 self._ssh_user = ssh_user
295 self._ssh_port = ssh_port
296 self._ssh_pass = ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -0700297 self._ssh_verbosity_flag = ssh_verbosity_flag
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700298 self._ssh_options = ssh_options
mblighe7d9c602009-07-02 19:02:33 +0000299 self.tag = tag
jadmanski53aaf382008-11-17 16:22:31 +0000300 self.hosts = set()
mbligh0d0f67d2009-11-06 03:15:03 +0000301 self.drop_caches = False
mblighb5dac432008-11-27 00:38:44 +0000302 self.drop_caches_between_iterations = False
mblighe0cbc912010-03-11 18:03:07 +0000303 self._control_filename = control_filename
Christopher Wiley8a91f232013-07-09 11:02:27 -0700304 self._disable_sysinfo = disable_sysinfo
jadmanski10646442008-08-13 14:05:21 +0000305
showard75cdfee2009-06-10 17:40:41 +0000306 self.logging = logging_manager.get_logging_manager(
307 manage_stdout_and_stderr=True, redirect_fds=True)
308 subcommand.logging_manager_object = self.logging
jadmanski10646442008-08-13 14:05:21 +0000309
mbligh0d0f67d2009-11-06 03:15:03 +0000310 self.sysinfo = sysinfo.sysinfo(self.resultdir)
jadmanski043e1132008-11-19 17:10:32 +0000311 self.profilers = profilers.profilers(self)
jadmanskic09fc152008-10-15 17:56:59 +0000312
jadmanski10646442008-08-13 14:05:21 +0000313 job_data = {'label' : label, 'user' : user,
314 'hostname' : ','.join(machines),
Eric Li861b2d52011-02-04 14:50:35 -0800315 'drone' : platform.node(),
mbligh0d0f67d2009-11-06 03:15:03 +0000316 'status_version' : str(self._STATUS_VERSION),
showard170873e2009-01-07 00:22:26 +0000317 'job_started' : str(int(time.time()))}
Dan Shi70647ca2015-07-16 22:52:35 -0700318 # Save parent job id to keyvals, so parser can retrieve the info and
319 # write to tko_jobs record.
320 if parent_job_id:
321 job_data['parent_job_id'] = parent_job_id
mbligh374f3412009-05-13 21:29:45 +0000322 if group_name:
323 job_data['host_group_name'] = group_name
jadmanski10646442008-08-13 14:05:21 +0000324
mbligh0d0f67d2009-11-06 03:15:03 +0000325 # only write these keyvals out on the first job in a resultdir
326 if 'job_started' not in utils.read_keyval(self.resultdir):
Prathmesh Prabhu87fecd12017-07-06 10:13:43 -0700327 job_data.update(self._get_job_data())
mbligh0d0f67d2009-11-06 03:15:03 +0000328 utils.write_keyval(self.resultdir, job_data)
329
mbligh0d0f67d2009-11-06 03:15:03 +0000330 self.pkgmgr = packages.PackageManager(
331 self.autodir, run_function_dargs={'timeout':600})
showard21baa452008-10-21 00:08:39 +0000332
jadmanski550fdc22008-11-20 16:32:08 +0000333 self._register_subcommand_hooks()
334
Prathmesh Prabhu6f1d4782018-04-20 11:21:56 -0700335 # We no longer parse results as part of the server_job. These arguments
336 # can't be dropped yet because clients haven't all be cleaned up yet.
337 self.num_tests_run = -1
338 self.num_tests_failed = -1
339
jadmanski2a89dac2010-06-11 14:32:58 +0000340 # set up the status logger
jadmanski52053632010-06-11 21:08:10 +0000341 self._indenter = status_indenter()
jadmanski2a89dac2010-06-11 14:32:58 +0000342 self._logger = base_job.status_logger(
jadmanski52053632010-06-11 21:08:10 +0000343 self, self._indenter, 'status.log', 'status.log',
jadmanski2a89dac2010-06-11 14:32:58 +0000344 record_hook=server_job_record_hook(self))
345
Dan Shib03ea9d2013-08-15 17:13:27 -0700346 # Initialize a flag to indicate DUT failure during the test, e.g.,
347 # unexpected reboot.
348 self.failed_with_device_error = False
349
Hidehiko Abe06893302017-06-24 07:32:38 +0900350 self._connection_pool = ssh_multiplex.ConnectionPool()
351
Daniel Erat8184ad22018-04-04 18:29:48 -0700352 # List of functions to run after the main job function.
353 self._post_run_hooks = []
354
Dan Shi70647ca2015-07-16 22:52:35 -0700355 self.parent_job_id = parent_job_id
Simran Basi1bf60eb2015-12-01 16:39:29 -0800356 self.in_lab = in_lab
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -0700357 self.machine_dict_list = machine_dict_list
358 for machine_dict in self.machine_dict_list:
359 machine_dict['connection_pool'] = self._connection_pool
Dan Shi70647ca2015-07-16 22:52:35 -0700360
Richard Barnette9aec6932016-06-03 13:31:46 -0700361 # TODO(jrbarnette) The harness attribute is only relevant to
362 # client jobs, but it's required to be present, or we will fail
363 # server job unit tests. Yes, really.
Richard Barnetteab9769f2016-06-01 15:01:44 -0700364 #
Richard Barnette9aec6932016-06-03 13:31:46 -0700365 # TODO(jrbarnette) The utility of the 'harness' attribute even
366 # to client jobs is suspect. Probably, we should remove it.
Richard Barnetteab9769f2016-06-01 15:01:44 -0700367 self.harness = None
Richard Barnetteab9769f2016-06-01 15:01:44 -0700368
Dan Shif53d1262017-06-19 11:25:25 -0700369 if control:
Xixuan Wucd36ae02017-11-10 13:51:00 -0800370 parsed_control = control_data.parse_control(
371 control, raise_warnings=False)
372 self.fast = parsed_control.fast
373 self.max_result_size_KB = parsed_control.max_result_size_KB
Dan Shif53d1262017-06-19 11:25:25 -0700374 else:
Xixuan Wucd36ae02017-11-10 13:51:00 -0800375 self.fast = False
Dan Shif53d1262017-06-19 11:25:25 -0700376 # Set the maximum result size to be the default specified in
377 # global config, if the job has no control file associated.
378 self.max_result_size_KB = control_data.DEFAULT_MAX_RESULT_SIZE_KB
379
mbligh0d0f67d2009-11-06 03:15:03 +0000380
381 @classmethod
382 def _find_base_directories(cls):
383 """
384 Determine locations of autodir, clientdir and serverdir. Assumes
385 that this file is located within serverdir and uses __file__ along
386 with relative paths to resolve the location.
387 """
388 serverdir = os.path.abspath(os.path.dirname(__file__))
389 autodir = os.path.normpath(os.path.join(serverdir, '..'))
390 clientdir = os.path.join(autodir, 'client')
391 return autodir, clientdir, serverdir
392
393
Scott Zawalski91493c82013-01-25 16:15:20 -0500394 def _find_resultdir(self, resultdir, *args, **dargs):
mbligh0d0f67d2009-11-06 03:15:03 +0000395 """
396 Determine the location of resultdir. For server jobs we expect one to
397 always be explicitly passed in to __init__, so just return that.
398 """
399 if resultdir:
400 return os.path.normpath(resultdir)
401 else:
402 return None
403
jadmanski550fdc22008-11-20 16:32:08 +0000404
jadmanski2a89dac2010-06-11 14:32:58 +0000405 def _get_status_logger(self):
406 """Return a reference to the status logger."""
407 return self._logger
408
409
jadmanskie432dd22009-01-30 15:04:51 +0000410 @staticmethod
411 def _load_control_file(path):
412 f = open(path)
413 try:
414 control_file = f.read()
415 finally:
416 f.close()
417 return re.sub('\r', '', control_file)
418
419
jadmanski550fdc22008-11-20 16:32:08 +0000420 def _register_subcommand_hooks(self):
mbligh2b92b862008-11-22 13:25:32 +0000421 """
422 Register some hooks into the subcommand modules that allow us
423 to properly clean up self.hosts created in forked subprocesses.
424 """
jadmanski550fdc22008-11-20 16:32:08 +0000425 def on_fork(cmd):
426 self._existing_hosts_on_fork = set(self.hosts)
427 def on_join(cmd):
428 new_hosts = self.hosts - self._existing_hosts_on_fork
429 for host in new_hosts:
430 host.close()
431 subcommand.subcommand.register_fork_hook(on_fork)
432 subcommand.subcommand.register_join_hook(on_join)
433
jadmanski10646442008-08-13 14:05:21 +0000434
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700435 # TODO crbug.com/285395 add a kwargs parameter.
436 def _make_namespace(self):
437 """Create a namespace dictionary to be passed along to control file.
438
439 Creates a namespace argument populated with standard values:
440 machines, job, ssh_user, ssh_port, ssh_pass, ssh_verbosity_flag,
441 and ssh_options.
442 """
Simran Basi1bf60eb2015-12-01 16:39:29 -0800443 namespace = {'machines' : self.machine_dict_list,
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700444 'job' : self,
445 'ssh_user' : self._ssh_user,
446 'ssh_port' : self._ssh_port,
447 'ssh_pass' : self._ssh_pass,
448 'ssh_verbosity_flag' : self._ssh_verbosity_flag,
449 'ssh_options' : self._ssh_options}
450 return namespace
451
jadmanski10646442008-08-13 14:05:21 +0000452
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800453 def cleanup(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700454 """Cleanup machines.
455
456 @param labels: Comma separated job labels, will be used to
457 determine special task actions.
458 """
459 if not self.machines:
460 raise error.AutoservError('No machines specified to cleanup')
461 if self.resultdir:
462 os.chdir(self.resultdir)
463
464 namespace = self._make_namespace()
465 namespace.update({'job_labels': labels, 'args': ''})
466 self._execute_code(CLEANUP_CONTROL_FILE, namespace, protect=False)
467
468
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800469 def verify(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700470 """Verify machines are all ssh-able.
471
472 @param labels: Comma separated job labels, will be used to
473 determine special task actions.
474 """
jadmanski10646442008-08-13 14:05:21 +0000475 if not self.machines:
mbligh084bc172008-10-18 14:02:45 +0000476 raise error.AutoservError('No machines specified to verify')
mbligh0fce4112008-11-27 00:37:17 +0000477 if self.resultdir:
478 os.chdir(self.resultdir)
Fang Dengad78aca2014-10-02 18:15:46 -0700479
480 namespace = self._make_namespace()
481 namespace.update({'job_labels': labels, 'args': ''})
482 self._execute_code(VERIFY_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000483
484
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800485 def reset(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700486 """Reset machines by first cleanup then verify each machine.
487
488 @param labels: Comma separated job labels, will be used to
489 determine special task actions.
490 """
Dan Shi07e09af2013-04-12 09:31:29 -0700491 if not self.machines:
492 raise error.AutoservError('No machines specified to reset.')
493 if self.resultdir:
494 os.chdir(self.resultdir)
495
Fang Dengad78aca2014-10-02 18:15:46 -0700496 namespace = self._make_namespace()
497 namespace.update({'job_labels': labels, 'args': ''})
498 self._execute_code(RESET_CONTROL_FILE, namespace, protect=False)
Dan Shi07e09af2013-04-12 09:31:29 -0700499
500
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800501 def repair(self, labels):
Fang Dengad78aca2014-10-02 18:15:46 -0700502 """Repair machines.
503
Fang Dengad78aca2014-10-02 18:15:46 -0700504 @param labels: Comma separated job labels, will be used to
505 determine special task actions.
506 """
jadmanski10646442008-08-13 14:05:21 +0000507 if not self.machines:
508 raise error.AutoservError('No machines specified to repair')
mbligh0fce4112008-11-27 00:37:17 +0000509 if self.resultdir:
510 os.chdir(self.resultdir)
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700511
512 namespace = self._make_namespace()
J. Richard Barnettec2d99cf2015-11-18 12:46:15 -0800513 namespace.update({'job_labels': labels, 'args': ''})
mbligh0931b0a2009-04-08 17:44:48 +0000514 self._execute_code(REPAIR_CONTROL_FILE, namespace, protect=False)
jadmanski10646442008-08-13 14:05:21 +0000515
516
Alex Millercb79ba72013-05-29 14:43:00 -0700517 def provision(self, labels):
518 """
519 Provision all hosts to match |labels|.
520
521 @param labels: A comma seperated string of labels to provision the
522 host to.
523
524 """
Alex Millercb79ba72013-05-29 14:43:00 -0700525 control = self._load_control_file(PROVISION_CONTROL_FILE)
Alex Miller686fca82014-04-23 17:21:13 -0700526 self.run(control=control, job_labels=labels)
Alex Millercb79ba72013-05-29 14:43:00 -0700527
528
jadmanski10646442008-08-13 14:05:21 +0000529 def precheck(self):
530 """
531 perform any additional checks in derived classes.
532 """
533 pass
534
535
536 def enable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000537 """
538 Start or restart external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000539 """
540 pass
541
542
543 def disable_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000544 """
545 Pause or stop external logging mechanism.
jadmanski10646442008-08-13 14:05:21 +0000546 """
547 pass
548
549
550 def use_external_logging(self):
mbligh2b92b862008-11-22 13:25:32 +0000551 """
552 Return True if external logging should be used.
jadmanski10646442008-08-13 14:05:21 +0000553 """
554 return False
555
556
mbligh415dc212009-06-15 21:53:34 +0000557 def _make_parallel_wrapper(self, function, machines, log):
558 """Wrap function as appropriate for calling by parallel_simple."""
Dan Shi5adbce82015-12-11 10:21:40 -0800559 # machines could be a list of dictionaries, e.g.,
560 # [{'host_attributes': {}, 'hostname': '100.96.51.226'}]
Prathmesh Prabhu20ea1062017-07-06 10:23:42 -0700561 # The dictionary is generated in server_job.__init__, refer to
Dan Shi5adbce82015-12-11 10:21:40 -0800562 # variable machine_dict_list, then passed in with namespace, see method
Prathmesh Prabhu20ea1062017-07-06 10:23:42 -0700563 # server_job._make_namespace.
Dan Shi5adbce82015-12-11 10:21:40 -0800564 # To compare the machinese to self.machines, which is a list of machine
565 # hostname, we need to convert machines back to a list of hostnames.
Dan Shi5adbce82015-12-11 10:21:40 -0800566 if (machines and isinstance(machines, list)
567 and isinstance(machines[0], dict)):
568 machines = [m['hostname'] for m in machines]
Prathmesh Prabhu6f1d4782018-04-20 11:21:56 -0700569 if len(machines) > 1 and log:
jadmanski10646442008-08-13 14:05:21 +0000570 def wrapper(machine):
Simran Basi1bf60eb2015-12-01 16:39:29 -0800571 hostname = server_utils.get_hostname_from_machine(machine)
572 self.push_execution_context(hostname)
jadmanski609a5f42008-08-26 20:52:42 +0000573 os.chdir(self.resultdir)
Simran Basi1bf60eb2015-12-01 16:39:29 -0800574 machine_data = {'hostname' : hostname,
mbligh0d0f67d2009-11-06 03:15:03 +0000575 'status_version' : str(self._STATUS_VERSION)}
mbligh838d82d2009-03-11 17:14:31 +0000576 utils.write_keyval(self.resultdir, machine_data)
jadmanski10646442008-08-13 14:05:21 +0000577 result = function(machine)
578 return result
579 else:
580 wrapper = function
mbligh415dc212009-06-15 21:53:34 +0000581 return wrapper
582
583
584 def parallel_simple(self, function, machines, log=True, timeout=None,
585 return_results=False):
586 """
587 Run 'function' using parallel_simple, with an extra wrapper to handle
588 the necessary setup for continuous parsing, if possible. If continuous
589 parsing is already properly initialized then this should just work.
590
591 @param function: A callable to run in parallel given each machine.
592 @param machines: A list of machine names to be passed one per subcommand
593 invocation of function.
594 @param log: If True, output will be written to output in a subdirectory
595 named after each machine.
596 @param timeout: Seconds after which the function call should timeout.
597 @param return_results: If True instead of an AutoServError being raised
598 on any error a list of the results|exceptions from the function
599 called on each arg is returned. [default: False]
600
601 @raises error.AutotestError: If any of the functions failed.
602 """
603 wrapper = self._make_parallel_wrapper(function, machines, log)
Prathmesh Prabhud08c86b2017-07-21 16:14:33 -0700604 return subcommand.parallel_simple(
605 wrapper, machines,
606 subdir_name_constructor=server_utils.get_hostname_from_machine,
607 log=log, timeout=timeout, return_results=return_results)
mbligh415dc212009-06-15 21:53:34 +0000608
609
610 def parallel_on_machines(self, function, machines, timeout=None):
611 """
showardcd5fac42009-07-06 20:19:43 +0000612 @param function: Called in parallel with one machine as its argument.
mbligh415dc212009-06-15 21:53:34 +0000613 @param machines: A list of machines to call function(machine) on.
614 @param timeout: Seconds after which the function call should timeout.
615
616 @returns A list of machines on which function(machine) returned
617 without raising an exception.
618 """
showardcd5fac42009-07-06 20:19:43 +0000619 results = self.parallel_simple(function, machines, timeout=timeout,
mbligh415dc212009-06-15 21:53:34 +0000620 return_results=True)
621 success_machines = []
622 for result, machine in itertools.izip(results, machines):
623 if not isinstance(result, Exception):
624 success_machines.append(machine)
625 return success_machines
jadmanski10646442008-08-13 14:05:21 +0000626
627
Prathmesh Prabhu20ea1062017-07-06 10:23:42 -0700628 def record_skipped_test(self, skipped_test, message=None):
629 """Insert a failure record into status.log for this test."""
630 msg = message
631 if msg is None:
632 msg = 'No valid machines found for test %s.' % skipped_test
633 logging.info(msg)
634 self.record('START', None, skipped_test.test_name)
635 self.record('INFO', None, skipped_test.test_name, msg)
636 self.record('END TEST_NA', None, skipped_test.test_name, msg)
637
638
Allen Liab020912016-09-19 18:07:41 -0700639 def _has_failed_tests(self):
640 """Parse status log for failed tests.
641
642 This checks the current working directory and is intended only for use
643 by the run() method.
644
645 @return boolean
646 """
647 path = os.getcwd()
648
649 # TODO(ayatane): Copied from tko/parse.py. Needs extensive refactor to
650 # make code reuse plausible.
651 job_keyval = tko_models.job.read_keyval(path)
652 status_version = job_keyval.get("status_version", 0)
653
654 # parse out the job
Luigi Semenzatoe7064812017-02-03 14:47:59 -0800655 parser = parser_lib.parser(status_version)
Allen Liab020912016-09-19 18:07:41 -0700656 job = parser.make_job(path)
657 status_log = os.path.join(path, "status.log")
658 if not os.path.exists(status_log):
659 status_log = os.path.join(path, "status")
660 if not os.path.exists(status_log):
661 logging.warning("! Unable to parse job, no status file")
662 return True
663
664 # parse the status logs
665 status_lines = open(status_log).readlines()
666 parser.start(job)
667 tests = parser.end(status_lines)
668
669 # parser.end can return the same object multiple times, so filter out
670 # dups
671 job.tests = []
672 already_added = set()
673 for test in tests:
674 if test not in already_added:
675 already_added.add(test)
676 job.tests.append(test)
677
678 failed = False
679 for test in job.tests:
680 # The current job is still running and shouldn't count as failed.
681 # The parser will fail to parse the exit status of the job since it
682 # hasn't exited yet (this running right now is the job).
683 failed = failed or (test.status != 'GOOD'
684 and not _is_current_server_job(test))
685 return failed
686
687
688 def _collect_crashes(self, namespace, collect_crashinfo):
689 """Collect crashes.
690
691 @param namespace: namespace dict.
692 @param collect_crashinfo: whether to collect crashinfo in addition to
693 dumps
694 """
695 if collect_crashinfo:
696 # includes crashdumps
697 crash_control_file = CRASHINFO_CONTROL_FILE
698 else:
699 crash_control_file = CRASHDUMPS_CONTROL_FILE
700 self._execute_code(crash_control_file, namespace)
701
702
mbligh0d0f67d2009-11-06 03:15:03 +0000703 _USE_TEMP_DIR = object()
Richard Barnette71854c72018-03-30 14:22:09 -0700704 def run(self, collect_crashdumps=True, namespace={}, control=None,
beepscb6f1e22013-06-28 19:14:10 -0700705 control_file_dir=None, verify_job_repo_url=False,
Alex Millerca76bcc2014-04-18 18:47:28 -0700706 only_collect_crashinfo=False, skip_crash_collection=False,
Dan Shib669cbd2013-09-13 11:17:17 -0700707 job_labels='', use_packaging=True):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000708 # for a normal job, make sure the uncollected logs file exists
709 # for a crashinfo-only run it should already exist, bail out otherwise
jadmanski648c39f2010-03-19 17:38:01 +0000710 created_uncollected_logs = False
Alex Miller45554f32013-08-13 16:48:29 -0700711 logging.info("I am PID %s", os.getpid())
mbligh0d0f67d2009-11-06 03:15:03 +0000712 if self.resultdir and not os.path.exists(self._uncollected_log_file):
jadmanskifb9c0fa2009-04-29 17:39:16 +0000713 if only_collect_crashinfo:
714 # if this is a crashinfo-only run, and there were no existing
715 # uncollected logs, just bail out early
716 logging.info("No existing uncollected logs, "
717 "skipping crashinfo collection")
718 return
719 else:
mbligh0d0f67d2009-11-06 03:15:03 +0000720 log_file = open(self._uncollected_log_file, "w")
jadmanskifb9c0fa2009-04-29 17:39:16 +0000721 pickle.dump([], log_file)
722 log_file.close()
jadmanski648c39f2010-03-19 17:38:01 +0000723 created_uncollected_logs = True
jadmanskifb9c0fa2009-04-29 17:39:16 +0000724
jadmanski10646442008-08-13 14:05:21 +0000725 # use a copy so changes don't affect the original dictionary
726 namespace = namespace.copy()
727 machines = self.machines
jadmanskie432dd22009-01-30 15:04:51 +0000728 if control is None:
jadmanski02a3ba22009-11-13 20:47:27 +0000729 if self.control is None:
730 control = ''
731 else:
732 control = self._load_control_file(self.control)
jadmanskie432dd22009-01-30 15:04:51 +0000733 if control_file_dir is None:
734 control_file_dir = self.resultdir
jadmanski10646442008-08-13 14:05:21 +0000735
736 self.aborted = False
Aviv Keshetc5947fa2013-09-04 14:06:29 -0700737 namespace.update(self._make_namespace())
Prathmesh Prabhu20ea1062017-07-06 10:23:42 -0700738 namespace.update({
739 'args': self.args,
740 'job_labels': job_labels,
741 'gtest_runner': site_gtest_runner.gtest_runner(),
742 })
jadmanski10646442008-08-13 14:05:21 +0000743 test_start_time = int(time.time())
744
mbligh80e1eba2008-11-19 00:26:18 +0000745 if self.resultdir:
746 os.chdir(self.resultdir)
jadmanski779bd292009-03-19 17:33:33 +0000747 # touch status.log so that the parser knows a job is running here
jadmanski382303a2009-04-21 19:53:39 +0000748 open(self.get_status_log_path(), 'a').close()
mbligh80e1eba2008-11-19 00:26:18 +0000749 self.enable_external_logging()
jadmanskie432dd22009-01-30 15:04:51 +0000750
jadmanskicdd0c402008-09-19 21:21:31 +0000751 collect_crashinfo = True
mblighaebe3b62008-12-22 14:45:40 +0000752 temp_control_file_dir = None
jadmanski10646442008-08-13 14:05:21 +0000753 try:
showardcf8d4922009-10-14 16:08:39 +0000754 try:
Xixuan Wu60325a62017-11-20 17:21:15 -0800755 if not self.fast:
756 with metrics.SecondsTimer(
757 'chromeos/autotest/job/get_network_stats',
758 fields = {'stage': 'start'}):
759 namespace['network_stats_label'] = 'at-start'
760 self._execute_code(GET_NETWORK_STATS_CONTROL_FILE,
761 namespace)
762
showardcf8d4922009-10-14 16:08:39 +0000763 if only_collect_crashinfo:
764 return
765
beepscb6f1e22013-06-28 19:14:10 -0700766 # If the verify_job_repo_url option is set but we're unable
767 # to actually verify that the job_repo_url contains the autotest
768 # package, this job will fail.
769 if verify_job_repo_url:
770 self._execute_code(VERIFY_JOB_REPO_URL_CONTROL_FILE,
Dan Shicf4d2032015-03-12 15:04:21 -0700771 namespace)
beepscb6f1e22013-06-28 19:14:10 -0700772 else:
773 logging.warning('Not checking if job_repo_url contains '
774 'autotest packages on %s', machines)
775
jadmanskidef0c3c2009-03-25 20:07:10 +0000776 # determine the dir to write the control files to
777 cfd_specified = (control_file_dir
mbligh0d0f67d2009-11-06 03:15:03 +0000778 and control_file_dir is not self._USE_TEMP_DIR)
jadmanskidef0c3c2009-03-25 20:07:10 +0000779 if cfd_specified:
780 temp_control_file_dir = None
781 else:
782 temp_control_file_dir = tempfile.mkdtemp(
783 suffix='temp_control_file_dir')
784 control_file_dir = temp_control_file_dir
785 server_control_file = os.path.join(control_file_dir,
mblighe0cbc912010-03-11 18:03:07 +0000786 self._control_filename)
jadmanskidef0c3c2009-03-25 20:07:10 +0000787 client_control_file = os.path.join(control_file_dir,
788 CLIENT_CONTROL_FILENAME)
mbligh0d0f67d2009-11-06 03:15:03 +0000789 if self._client:
jadmanskidef0c3c2009-03-25 20:07:10 +0000790 namespace['control'] = control
791 utils.open_write_close(client_control_file, control)
mblighfeac0102009-04-28 18:31:12 +0000792 shutil.copyfile(CLIENT_WRAPPER_CONTROL_FILE,
793 server_control_file)
jadmanskidef0c3c2009-03-25 20:07:10 +0000794 else:
795 utils.open_write_close(server_control_file, control)
Dan Shicf4d2032015-03-12 15:04:21 -0700796
mbligh26f0d882009-06-22 18:30:01 +0000797 logging.info("Processing control file")
Dan Shib669cbd2013-09-13 11:17:17 -0700798 namespace['use_packaging'] = use_packaging
Dan Shicf4d2032015-03-12 15:04:21 -0700799 self._execute_code(server_control_file, namespace)
mbligh26f0d882009-06-22 18:30:01 +0000800 logging.info("Finished processing control file")
jadmanski10646442008-08-13 14:05:21 +0000801
Dan Shib03ea9d2013-08-15 17:13:27 -0700802 # If no device error occured, no need to collect crashinfo.
803 collect_crashinfo = self.failed_with_device_error
Prathmesh Prabhu0afec422017-02-02 15:32:27 -0800804 except Exception as e:
showardcf8d4922009-10-14 16:08:39 +0000805 try:
806 logging.exception(
807 'Exception escaped control file, job aborting:')
Fang Dengc439b6a2015-12-10 16:43:59 -0800808 reason = re.sub(base_job.status_log_entry.BAD_CHAR_REGEX,
809 ' ', str(e))
Eric Li6f27d4f2010-09-29 10:55:17 -0700810 self.record('INFO', None, None, str(e),
Fang Dengc439b6a2015-12-10 16:43:59 -0800811 {'job_abort_reason': reason})
showardcf8d4922009-10-14 16:08:39 +0000812 except:
813 pass # don't let logging exceptions here interfere
814 raise
jadmanski10646442008-08-13 14:05:21 +0000815 finally:
mblighaebe3b62008-12-22 14:45:40 +0000816 if temp_control_file_dir:
jadmanskie432dd22009-01-30 15:04:51 +0000817 # Clean up temp directory used for copies of the control files
mblighaebe3b62008-12-22 14:45:40 +0000818 try:
819 shutil.rmtree(temp_control_file_dir)
Prathmesh Prabhu0afec422017-02-02 15:32:27 -0800820 except Exception as e:
Ilja H. Friedel04be2bd2014-05-07 21:29:59 -0700821 logging.warning('Could not remove temp directory %s: %s',
mblighe7d9c602009-07-02 19:02:33 +0000822 temp_control_file_dir, e)
jadmanskie432dd22009-01-30 15:04:51 +0000823
jadmanskicdd0c402008-09-19 21:21:31 +0000824 if machines and (collect_crashdumps or collect_crashinfo):
Xixuan Wuf4e857a2017-11-21 15:59:02 -0800825 if skip_crash_collection or self.fast:
Christopher Wileyf594c5e2013-07-03 18:25:30 -0700826 logging.info('Skipping crash dump/info collection '
827 'as requested.')
jadmanskicdd0c402008-09-19 21:21:31 +0000828 else:
Xixuan Wuf4e857a2017-11-21 15:59:02 -0800829 with metrics.SecondsTimer(
830 'chromeos/autotest/job/collect_crashinfo'):
831 namespace['test_start_time'] = test_start_time
832 # Remove crash files for passing tests.
833 # TODO(ayatane): Tests that create crash files should be
834 # reported.
835 namespace['has_failed_tests'] = self._has_failed_tests()
836 self._collect_crashes(namespace, collect_crashinfo)
jadmanski10646442008-08-13 14:05:21 +0000837 self.disable_external_logging()
Chris Sosaf4d43ff2012-10-30 11:21:05 -0700838 if self._uncollected_log_file and created_uncollected_logs:
839 os.remove(self._uncollected_log_file)
Xixuan Wu60325a62017-11-20 17:21:15 -0800840
841 if not self.fast:
842 with metrics.SecondsTimer(
843 'chromeos/autotest/job/get_network_stats',
844 fields = {'stage': 'end'}):
845 namespace['network_stats_label'] = 'at-end'
846 self._execute_code(GET_NETWORK_STATS_CONTROL_FILE,
847 namespace)
jadmanski10646442008-08-13 14:05:21 +0000848
849
850 def run_test(self, url, *args, **dargs):
mbligh2b92b862008-11-22 13:25:32 +0000851 """
852 Summon a test object and run it.
jadmanski10646442008-08-13 14:05:21 +0000853
854 tag
855 tag to add to testname
856 url
857 url of the test to run
858 """
Christopher Wiley8a91f232013-07-09 11:02:27 -0700859 if self._disable_sysinfo:
860 dargs['disable_sysinfo'] = True
861
mblighfc3da5b2010-01-06 18:37:22 +0000862 group, testname = self.pkgmgr.get_package_name(url, 'test')
863 testname, subdir, tag = self._build_tagged_test_name(testname, dargs)
864 outputdir = self._make_test_outputdir(subdir)
jadmanski10646442008-08-13 14:05:21 +0000865
866 def group_func():
867 try:
868 test.runtest(self, url, tag, args, dargs)
Prathmesh Prabhu0afec422017-02-02 15:32:27 -0800869 except error.TestBaseException as e:
jadmanski10646442008-08-13 14:05:21 +0000870 self.record(e.exit_status, subdir, testname, str(e))
871 raise
Prathmesh Prabhu0afec422017-02-02 15:32:27 -0800872 except Exception as e:
jadmanski10646442008-08-13 14:05:21 +0000873 info = str(e) + "\n" + traceback.format_exc()
874 self.record('FAIL', subdir, testname, info)
875 raise
876 else:
mbligh2b92b862008-11-22 13:25:32 +0000877 self.record('GOOD', subdir, testname, 'completed successfully')
jadmanskide292df2008-08-26 20:51:14 +0000878
Prathmesh Prabhu271962f2017-02-02 17:22:37 -0800879 try:
880 result = self._run_group(testname, subdir, group_func)
881 except error.TestBaseException as e:
jadmanskide292df2008-08-26 20:51:14 +0000882 return False
jadmanskide292df2008-08-26 20:51:14 +0000883 else:
884 return True
jadmanski10646442008-08-13 14:05:21 +0000885
886
887 def _run_group(self, name, subdir, function, *args, **dargs):
Prathmesh Prabhu271962f2017-02-02 17:22:37 -0800888 """Underlying method for running something inside of a group."""
jadmanskide292df2008-08-26 20:51:14 +0000889 result, exc_info = None, None
jadmanski10646442008-08-13 14:05:21 +0000890 try:
891 self.record('START', subdir, name)
jadmanski52053632010-06-11 21:08:10 +0000892 result = function(*args, **dargs)
Prathmesh Prabhu0afec422017-02-02 15:32:27 -0800893 except error.TestBaseException as e:
jadmanskib88d6dc2009-01-10 00:33:18 +0000894 self.record("END %s" % e.exit_status, subdir, name)
Prathmesh Prabhu271962f2017-02-02 17:22:37 -0800895 raise
Prathmesh Prabhu0afec422017-02-02 15:32:27 -0800896 except Exception as e:
jadmanski10646442008-08-13 14:05:21 +0000897 err_msg = str(e) + '\n'
898 err_msg += traceback.format_exc()
899 self.record('END ABORT', subdir, name, err_msg)
900 raise error.JobError(name + ' failed\n' + traceback.format_exc())
901 else:
902 self.record('END GOOD', subdir, name)
Daniel Erat8184ad22018-04-04 18:29:48 -0700903 finally:
904 for hook in self._post_run_hooks:
905 hook()
jadmanski10646442008-08-13 14:05:21 +0000906
Prathmesh Prabhu271962f2017-02-02 17:22:37 -0800907 return result
jadmanski10646442008-08-13 14:05:21 +0000908
909
910 def run_group(self, function, *args, **dargs):
911 """\
Prathmesh Prabhu271962f2017-02-02 17:22:37 -0800912 @param function: subroutine to run
913 @returns: (result, exc_info). When the call succeeds, result contains
914 the return value of |function| and exc_info is None. If
915 |function| raises an exception, exc_info contains the tuple
916 returned by sys.exc_info(), and result is None.
jadmanski10646442008-08-13 14:05:21 +0000917 """
918
919 name = function.__name__
jadmanski10646442008-08-13 14:05:21 +0000920 # Allow the tag for the group to be specified.
921 tag = dargs.pop('tag', None)
922 if tag:
923 name = tag
924
Prathmesh Prabhu271962f2017-02-02 17:22:37 -0800925 try:
926 result = self._run_group(name, None, function, *args, **dargs)[0]
927 except error.TestBaseException:
928 return None, sys.exc_info()
929 return result, None
jadmanski10646442008-08-13 14:05:21 +0000930
931
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700932 def run_op(self, op, op_func, get_kernel_func):
jadmanski10646442008-08-13 14:05:21 +0000933 """\
934 A specialization of run_group meant specifically for handling
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700935 management operation. Includes support for capturing the kernel version
936 after the operation.
jadmanski10646442008-08-13 14:05:21 +0000937
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700938 Args:
939 op: name of the operation.
940 op_func: a function that carries out the operation (reboot, suspend)
941 get_kernel_func: a function that returns a string
942 representing the kernel version.
jadmanski10646442008-08-13 14:05:21 +0000943 """
jadmanski10646442008-08-13 14:05:21 +0000944 try:
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700945 self.record('START', None, op)
946 op_func()
Prathmesh Prabhu0afec422017-02-02 15:32:27 -0800947 except Exception as e:
jadmanski10646442008-08-13 14:05:21 +0000948 err_msg = str(e) + '\n' + traceback.format_exc()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700949 self.record('END FAIL', None, op, err_msg)
jadmanski4b51d542009-04-08 14:17:16 +0000950 raise
jadmanski10646442008-08-13 14:05:21 +0000951 else:
952 kernel = get_kernel_func()
Gwendal Grignou7a61d2f2014-05-23 11:05:51 -0700953 self.record('END GOOD', None, op,
Dale Curtis74a314b2011-06-23 14:55:46 -0700954 optional_fields={"kernel": kernel})
jadmanski10646442008-08-13 14:05:21 +0000955
956
jadmanskie432dd22009-01-30 15:04:51 +0000957 def run_control(self, path):
958 """Execute a control file found at path (relative to the autotest
959 path). Intended for executing a control file within a control file,
960 not for running the top-level job control file."""
961 path = os.path.join(self.autodir, path)
962 control_file = self._load_control_file(path)
mbligh0d0f67d2009-11-06 03:15:03 +0000963 self.run(control=control_file, control_file_dir=self._USE_TEMP_DIR)
jadmanskie432dd22009-01-30 15:04:51 +0000964
965
jadmanskic09fc152008-10-15 17:56:59 +0000966 def add_sysinfo_command(self, command, logfile=None, on_every_test=False):
mbligh4395bbd2009-03-25 19:34:17 +0000967 self._add_sysinfo_loggable(sysinfo.command(command, logf=logfile),
jadmanskic09fc152008-10-15 17:56:59 +0000968 on_every_test)
969
970
971 def add_sysinfo_logfile(self, file, on_every_test=False):
972 self._add_sysinfo_loggable(sysinfo.logfile(file), on_every_test)
973
974
975 def _add_sysinfo_loggable(self, loggable, on_every_test):
976 if on_every_test:
977 self.sysinfo.test_loggables.add(loggable)
978 else:
979 self.sysinfo.boot_loggables.add(loggable)
980
981
jadmanski10646442008-08-13 14:05:21 +0000982 def _read_warnings(self):
jadmanskif37df842009-02-11 00:03:26 +0000983 """Poll all the warning loggers and extract any new warnings that have
984 been logged. If the warnings belong to a category that is currently
985 disabled, this method will discard them and they will no longer be
986 retrievable.
987
988 Returns a list of (timestamp, message) tuples, where timestamp is an
989 integer epoch timestamp."""
jadmanski10646442008-08-13 14:05:21 +0000990 warnings = []
991 while True:
992 # pull in a line of output from every logger that has
993 # output ready to be read
mbligh2b92b862008-11-22 13:25:32 +0000994 loggers, _, _ = select.select(self.warning_loggers, [], [], 0)
jadmanski10646442008-08-13 14:05:21 +0000995 closed_loggers = set()
996 for logger in loggers:
997 line = logger.readline()
998 # record any broken pipes (aka line == empty)
999 if len(line) == 0:
1000 closed_loggers.add(logger)
1001 continue
jadmanskif37df842009-02-11 00:03:26 +00001002 # parse out the warning
1003 timestamp, msgtype, msg = line.split('\t', 2)
1004 timestamp = int(timestamp)
1005 # if the warning is valid, add it to the results
1006 if self.warning_manager.is_valid(timestamp, msgtype):
1007 warnings.append((timestamp, msg.strip()))
jadmanski10646442008-08-13 14:05:21 +00001008
1009 # stop listening to loggers that are closed
1010 self.warning_loggers -= closed_loggers
1011
1012 # stop if none of the loggers have any output left
1013 if not loggers:
1014 break
1015
1016 # sort into timestamp order
1017 warnings.sort()
1018 return warnings
1019
1020
showardcc929362010-01-25 21:20:41 +00001021 def _unique_subdirectory(self, base_subdirectory_name):
1022 """Compute a unique results subdirectory based on the given name.
1023
1024 Appends base_subdirectory_name with a number as necessary to find a
1025 directory name that doesn't already exist.
1026 """
1027 subdirectory = base_subdirectory_name
1028 counter = 1
1029 while os.path.exists(os.path.join(self.resultdir, subdirectory)):
1030 subdirectory = base_subdirectory_name + '.' + str(counter)
1031 counter += 1
1032 return subdirectory
1033
1034
jadmanski52053632010-06-11 21:08:10 +00001035 def get_record_context(self):
1036 """Returns an object representing the current job.record context.
1037
1038 The object returned is an opaque object with a 0-arg restore method
1039 which can be called to restore the job.record context (i.e. indentation)
1040 to the current level. The intention is that it should be used when
1041 something external which generate job.record calls (e.g. an autotest
1042 client) can fail catastrophically and the server job record state
1043 needs to be reset to its original "known good" state.
1044
1045 @return: A context object with a 0-arg restore() method."""
1046 return self._indenter.get_context()
1047
1048
showardcc929362010-01-25 21:20:41 +00001049 def record_summary(self, status_code, test_name, reason='', attributes=None,
1050 distinguishing_attributes=(), child_test_ids=None):
1051 """Record a summary test result.
1052
1053 @param status_code: status code string, see
1054 common_lib.log.is_valid_status()
1055 @param test_name: name of the test
1056 @param reason: (optional) string providing detailed reason for test
1057 outcome
1058 @param attributes: (optional) dict of string keyvals to associate with
1059 this result
1060 @param distinguishing_attributes: (optional) list of attribute names
1061 that should be used to distinguish identically-named test
1062 results. These attributes should be present in the attributes
1063 parameter. This is used to generate user-friendly subdirectory
1064 names.
1065 @param child_test_ids: (optional) list of test indices for test results
1066 used in generating this result.
1067 """
1068 subdirectory_name_parts = [test_name]
1069 for attribute in distinguishing_attributes:
1070 assert attributes
1071 assert attribute in attributes, '%s not in %s' % (attribute,
1072 attributes)
1073 subdirectory_name_parts.append(attributes[attribute])
1074 base_subdirectory_name = '.'.join(subdirectory_name_parts)
1075
1076 subdirectory = self._unique_subdirectory(base_subdirectory_name)
1077 subdirectory_path = os.path.join(self.resultdir, subdirectory)
1078 os.mkdir(subdirectory_path)
1079
1080 self.record(status_code, subdirectory, test_name,
1081 status=reason, optional_fields={'is_summary': True})
1082
1083 if attributes:
1084 utils.write_keyval(subdirectory_path, attributes)
1085
1086 if child_test_ids:
1087 ids_string = ','.join(str(test_id) for test_id in child_test_ids)
1088 summary_data = {'child_test_ids': ids_string}
1089 utils.write_keyval(os.path.join(subdirectory_path, 'summary_data'),
1090 summary_data)
1091
1092
Daniel Erat8184ad22018-04-04 18:29:48 -07001093 def add_post_run_hook(self, hook):
1094 """
1095 Registers a hook to run after the main job function.
1096
1097 This provides a mechanism by which tests that perform multiple tests of
1098 their own can write additional top-level results to the TKO status.log
1099 file.
1100
1101 @param hook: Function to invoke (without any args) after the main job
1102 function completes and the job status is logged.
1103 """
1104 self._post_run_hooks.append(hook)
1105
1106
jadmanski16a7ff72009-04-01 18:19:53 +00001107 def disable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +00001108 self.warning_manager.disable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +00001109 self.record("INFO", None, None,
1110 "disabling %s warnings" % warning_type,
1111 {"warnings.disable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +00001112
1113
jadmanski16a7ff72009-04-01 18:19:53 +00001114 def enable_warnings(self, warning_type):
jadmanskif37df842009-02-11 00:03:26 +00001115 self.warning_manager.enable_warnings(warning_type)
jadmanski16a7ff72009-04-01 18:19:53 +00001116 self.record("INFO", None, None,
1117 "enabling %s warnings" % warning_type,
1118 {"warnings.enable": warning_type})
jadmanskif37df842009-02-11 00:03:26 +00001119
1120
jadmanski779bd292009-03-19 17:33:33 +00001121 def get_status_log_path(self, subdir=None):
1122 """Return the path to the job status log.
1123
1124 @param subdir - Optional paramter indicating that you want the path
1125 to a subdirectory status log.
1126
1127 @returns The path where the status log should be.
1128 """
mbligh210bae62009-04-01 18:33:13 +00001129 if self.resultdir:
1130 if subdir:
1131 return os.path.join(self.resultdir, subdir, "status.log")
1132 else:
1133 return os.path.join(self.resultdir, "status.log")
jadmanski779bd292009-03-19 17:33:33 +00001134 else:
mbligh210bae62009-04-01 18:33:13 +00001135 return None
jadmanski779bd292009-03-19 17:33:33 +00001136
1137
jadmanski6bb32d72009-03-19 20:25:24 +00001138 def _update_uncollected_logs_list(self, update_func):
1139 """Updates the uncollected logs list in a multi-process safe manner.
1140
1141 @param update_func - a function that updates the list of uncollected
1142 logs. Should take one parameter, the list to be updated.
1143 """
Dan Shi07e09af2013-04-12 09:31:29 -07001144 # Skip log collection if file _uncollected_log_file does not exist.
1145 if not (self._uncollected_log_file and
1146 os.path.exists(self._uncollected_log_file)):
1147 return
mbligh0d0f67d2009-11-06 03:15:03 +00001148 if self._uncollected_log_file:
1149 log_file = open(self._uncollected_log_file, "r+")
mbligha788dc42009-03-26 21:10:16 +00001150 fcntl.flock(log_file, fcntl.LOCK_EX)
jadmanski6bb32d72009-03-19 20:25:24 +00001151 try:
1152 uncollected_logs = pickle.load(log_file)
1153 update_func(uncollected_logs)
1154 log_file.seek(0)
1155 log_file.truncate()
1156 pickle.dump(uncollected_logs, log_file)
jadmanski3bff9092009-04-22 18:09:47 +00001157 log_file.flush()
jadmanski6bb32d72009-03-19 20:25:24 +00001158 finally:
1159 fcntl.flock(log_file, fcntl.LOCK_UN)
1160 log_file.close()
1161
1162
1163 def add_client_log(self, hostname, remote_path, local_path):
1164 """Adds a new set of client logs to the list of uncollected logs,
1165 to allow for future log recovery.
1166
1167 @param host - the hostname of the machine holding the logs
1168 @param remote_path - the directory on the remote machine holding logs
1169 @param local_path - the local directory to copy the logs into
1170 """
1171 def update_func(logs_list):
1172 logs_list.append((hostname, remote_path, local_path))
1173 self._update_uncollected_logs_list(update_func)
1174
1175
1176 def remove_client_log(self, hostname, remote_path, local_path):
1177 """Removes a set of client logs from the list of uncollected logs,
1178 to allow for future log recovery.
1179
1180 @param host - the hostname of the machine holding the logs
1181 @param remote_path - the directory on the remote machine holding logs
1182 @param local_path - the local directory to copy the logs into
1183 """
1184 def update_func(logs_list):
1185 logs_list.remove((hostname, remote_path, local_path))
1186 self._update_uncollected_logs_list(update_func)
1187
1188
mbligh0d0f67d2009-11-06 03:15:03 +00001189 def get_client_logs(self):
1190 """Retrieves the list of uncollected logs, if it exists.
1191
1192 @returns A list of (host, remote_path, local_path) tuples. Returns
1193 an empty list if no uncollected logs file exists.
1194 """
1195 log_exists = (self._uncollected_log_file and
1196 os.path.exists(self._uncollected_log_file))
1197 if log_exists:
1198 return pickle.load(open(self._uncollected_log_file))
1199 else:
1200 return []
1201
1202
mbligh084bc172008-10-18 14:02:45 +00001203 def _fill_server_control_namespace(self, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001204 """
1205 Prepare a namespace to be used when executing server control files.
mbligh084bc172008-10-18 14:02:45 +00001206
1207 This sets up the control file API by importing modules and making them
1208 available under the appropriate names within namespace.
1209
1210 For use by _execute_code().
1211
1212 Args:
1213 namespace: The namespace dictionary to fill in.
1214 protect: Boolean. If True (the default) any operation that would
1215 clobber an existing entry in namespace will cause an error.
1216 Raises:
1217 error.AutoservError: When a name would be clobbered by import.
1218 """
1219 def _import_names(module_name, names=()):
mbligh2b92b862008-11-22 13:25:32 +00001220 """
1221 Import a module and assign named attributes into namespace.
mbligh084bc172008-10-18 14:02:45 +00001222
1223 Args:
1224 module_name: The string module name.
1225 names: A limiting list of names to import from module_name. If
1226 empty (the default), all names are imported from the module
1227 similar to a "from foo.bar import *" statement.
1228 Raises:
1229 error.AutoservError: When a name being imported would clobber
1230 a name already in namespace.
1231 """
1232 module = __import__(module_name, {}, {}, names)
1233
1234 # No names supplied? Import * from the lowest level module.
1235 # (Ugh, why do I have to implement this part myself?)
1236 if not names:
1237 for submodule_name in module_name.split('.')[1:]:
1238 module = getattr(module, submodule_name)
1239 if hasattr(module, '__all__'):
1240 names = getattr(module, '__all__')
1241 else:
1242 names = dir(module)
1243
1244 # Install each name into namespace, checking to make sure it
1245 # doesn't override anything that already exists.
1246 for name in names:
1247 # Check for conflicts to help prevent future problems.
1248 if name in namespace and protect:
1249 if namespace[name] is not getattr(module, name):
1250 raise error.AutoservError('importing name '
1251 '%s from %s %r would override %r' %
1252 (name, module_name, getattr(module, name),
1253 namespace[name]))
1254 else:
1255 # Encourage cleanliness and the use of __all__ for a
1256 # more concrete API with less surprises on '*' imports.
1257 warnings.warn('%s (%r) being imported from %s for use '
1258 'in server control files is not the '
Richard Barnetteab9769f2016-06-01 15:01:44 -07001259 'first occurrence of that import.' %
mbligh084bc172008-10-18 14:02:45 +00001260 (name, namespace[name], module_name))
1261
1262 namespace[name] = getattr(module, name)
1263
1264
1265 # This is the equivalent of prepending a bunch of import statements to
1266 # the front of the control script.
mbligha2b07dd2009-06-22 18:26:13 +00001267 namespace.update(os=os, sys=sys, logging=logging)
mbligh084bc172008-10-18 14:02:45 +00001268 _import_names('autotest_lib.server',
Richard Barnetteab9769f2016-06-01 15:01:44 -07001269 ('hosts', 'autotest', 'standalone_profiler'))
mbligh084bc172008-10-18 14:02:45 +00001270 _import_names('autotest_lib.server.subcommand',
1271 ('parallel', 'parallel_simple', 'subcommand'))
1272 _import_names('autotest_lib.server.utils',
1273 ('run', 'get_tmp_dir', 'sh_escape', 'parse_machine'))
1274 _import_names('autotest_lib.client.common_lib.error')
1275 _import_names('autotest_lib.client.common_lib.barrier', ('barrier',))
1276
1277 # Inject ourself as the job object into other classes within the API.
1278 # (Yuck, this injection is a gross thing be part of a public API. -gps)
1279 #
Allen Lid5abdab2017-02-07 16:03:43 -08001280 # XXX Autotest does not appear to use .job. Who does?
mbligh084bc172008-10-18 14:02:45 +00001281 namespace['autotest'].Autotest.job = self
1282 # server.hosts.base_classes.Host uses .job.
1283 namespace['hosts'].Host.job = self
Eric Li10222b82010-11-24 09:33:15 -08001284 namespace['hosts'].factory.ssh_user = self._ssh_user
1285 namespace['hosts'].factory.ssh_port = self._ssh_port
1286 namespace['hosts'].factory.ssh_pass = self._ssh_pass
Fang Dengd1c2b732013-08-20 12:59:46 -07001287 namespace['hosts'].factory.ssh_verbosity_flag = (
1288 self._ssh_verbosity_flag)
Aviv Keshetc5947fa2013-09-04 14:06:29 -07001289 namespace['hosts'].factory.ssh_options = self._ssh_options
mbligh084bc172008-10-18 14:02:45 +00001290
1291
1292 def _execute_code(self, code_file, namespace, protect=True):
mbligh2b92b862008-11-22 13:25:32 +00001293 """
1294 Execute code using a copy of namespace as a server control script.
mbligh084bc172008-10-18 14:02:45 +00001295
1296 Unless protect_namespace is explicitly set to False, the dict will not
1297 be modified.
1298
1299 Args:
1300 code_file: The filename of the control file to execute.
1301 namespace: A dict containing names to make available during execution.
1302 protect: Boolean. If True (the default) a copy of the namespace dict
1303 is used during execution to prevent the code from modifying its
1304 contents outside of this function. If False the raw dict is
1305 passed in and modifications will be allowed.
1306 """
1307 if protect:
1308 namespace = namespace.copy()
1309 self._fill_server_control_namespace(namespace, protect=protect)
1310 # TODO: Simplify and get rid of the special cases for only 1 machine.
showard3e66e8c2008-10-27 19:20:51 +00001311 if len(self.machines) > 1:
mbligh084bc172008-10-18 14:02:45 +00001312 machines_text = '\n'.join(self.machines) + '\n'
1313 # Only rewrite the file if it does not match our machine list.
1314 try:
1315 machines_f = open(MACHINES_FILENAME, 'r')
1316 existing_machines_text = machines_f.read()
1317 machines_f.close()
1318 except EnvironmentError:
1319 existing_machines_text = None
1320 if machines_text != existing_machines_text:
1321 utils.open_write_close(MACHINES_FILENAME, machines_text)
1322 execfile(code_file, namespace, namespace)
jadmanski10646442008-08-13 14:05:21 +00001323
1324
mblighfc3da5b2010-01-06 18:37:22 +00001325 def preprocess_client_state(self):
1326 """
1327 Produce a state file for initializing the state of a client job.
1328
1329 Creates a new client state file with all the current server state, as
1330 well as some pre-set client state.
1331
1332 @returns The path of the file the state was written into.
1333 """
1334 # initialize the sysinfo state
1335 self._state.set('client', 'sysinfo', self.sysinfo.serialize())
1336
1337 # dump the state out to a tempfile
1338 fd, file_path = tempfile.mkstemp(dir=self.tmpdir)
1339 os.close(fd)
mbligha2c99492010-01-27 22:59:50 +00001340
1341 # write_to_file doesn't need locking, we exclusively own file_path
mblighfc3da5b2010-01-06 18:37:22 +00001342 self._state.write_to_file(file_path)
1343 return file_path
1344
1345
1346 def postprocess_client_state(self, state_path):
1347 """
1348 Update the state of this job with the state from a client job.
1349
1350 Updates the state of the server side of a job with the final state
1351 of a client job that was run. Updates the non-client-specific state,
1352 pulls in some specific bits from the client-specific state, and then
1353 discards the rest. Removes the state file afterwards
1354
1355 @param state_file A path to the state file from the client.
1356 """
1357 # update the on-disk state
mblighfc3da5b2010-01-06 18:37:22 +00001358 try:
jadmanskib6e7bdb2010-04-13 16:00:39 +00001359 self._state.read_from_file(state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001360 os.remove(state_path)
mbligha2c99492010-01-27 22:59:50 +00001361 except OSError, e:
mblighfc3da5b2010-01-06 18:37:22 +00001362 # ignore file-not-found errors
1363 if e.errno != errno.ENOENT:
1364 raise
jadmanskib6e7bdb2010-04-13 16:00:39 +00001365 else:
1366 logging.debug('Client state file %s not found', state_path)
mblighfc3da5b2010-01-06 18:37:22 +00001367
1368 # update the sysinfo state
1369 if self._state.has('client', 'sysinfo'):
1370 self.sysinfo.deserialize(self._state.get('client', 'sysinfo'))
1371
1372 # drop all the client-specific state
1373 self._state.discard_namespace('client')
1374
1375
mbligh0a883702010-04-21 01:58:34 +00001376 def clear_all_known_hosts(self):
1377 """Clears known hosts files for all AbstractSSHHosts."""
1378 for host in self.hosts:
1379 if isinstance(host, abstract_ssh.AbstractSSHHost):
1380 host.clear_known_hosts()
1381
1382
Hidehiko Abe06893302017-06-24 07:32:38 +09001383 def close(self):
1384 """Closes this job's operation."""
1385
1386 # Use shallow copy, because host.close() internally discards itself.
1387 for host in list(self.hosts):
1388 host.close()
1389 assert not self.hosts
1390 self._connection_pool.shutdown()
1391
1392
Prathmesh Prabhu87fecd12017-07-06 10:13:43 -07001393 def _get_job_data(self):
1394 """Add custom data to the job keyval info.
1395
1396 When multiple machines are used in a job, change the hostname to
1397 the platform of the first machine instead of machine1,machine2,... This
1398 makes the job reports easier to read and keeps the tko_machines table from
1399 growing too large.
1400
1401 Returns:
1402 keyval dictionary with new hostname value, or empty dictionary.
1403 """
1404 job_data = {}
1405 # Only modify hostname on multimachine jobs. Assume all host have the same
1406 # platform.
1407 if len(self.machines) > 1:
1408 # Search through machines for first machine with a platform.
1409 for host in self.machines:
1410 keyval_path = os.path.join(self.resultdir, 'host_keyvals', host)
1411 keyvals = utils.read_keyval(keyval_path)
1412 host_plat = keyvals.get('platform', None)
1413 if not host_plat:
1414 continue
1415 job_data['hostname'] = host_plat
1416 break
1417 return job_data
1418
1419
jadmanskif37df842009-02-11 00:03:26 +00001420class warning_manager(object):
1421 """Class for controlling warning logs. Manages the enabling and disabling
1422 of warnings."""
1423 def __init__(self):
1424 # a map of warning types to a list of disabled time intervals
1425 self.disabled_warnings = {}
1426
1427
1428 def is_valid(self, timestamp, warning_type):
1429 """Indicates if a warning (based on the time it occured and its type)
1430 is a valid warning. A warning is considered "invalid" if this type of
1431 warning was marked as "disabled" at the time the warning occured."""
1432 disabled_intervals = self.disabled_warnings.get(warning_type, [])
1433 for start, end in disabled_intervals:
1434 if timestamp >= start and (end is None or timestamp < end):
1435 return False
1436 return True
1437
1438
1439 def disable_warnings(self, warning_type, current_time_func=time.time):
1440 """As of now, disables all further warnings of this type."""
1441 intervals = self.disabled_warnings.setdefault(warning_type, [])
1442 if not intervals or intervals[-1][1] is not None:
jadmanski16a7ff72009-04-01 18:19:53 +00001443 intervals.append((int(current_time_func()), None))
jadmanskif37df842009-02-11 00:03:26 +00001444
1445
1446 def enable_warnings(self, warning_type, current_time_func=time.time):
1447 """As of now, enables all further warnings of this type."""
1448 intervals = self.disabled_warnings.get(warning_type, [])
1449 if intervals and intervals[-1][1] is None:
jadmanski16a7ff72009-04-01 18:19:53 +00001450 intervals[-1] = (intervals[-1][0], int(current_time_func()))
Paul Pendlebury57593562011-06-15 10:45:49 -07001451
1452
Allen Liab020912016-09-19 18:07:41 -07001453def _is_current_server_job(test):
1454 """Return True if parsed test is the currently running job.
1455
1456 @param test: test instance from tko parser.
1457 """
1458 return test.testname == 'SERVER_JOB'
1459
1460
Prathmesh Prabhucbeab122017-05-30 11:23:53 -07001461def _create_afe_host(hostname):
1462 """Create an afe_host object backed by the AFE.
Prathmesh Prabhu28616da2017-01-11 12:58:25 -08001463
1464 @param hostname: Name of the host for which we want the Host object.
Prathmesh Prabhu28616da2017-01-11 12:58:25 -08001465 @returns: An object of type frontend.AFE
1466 """
Prathmesh Prabhu28616da2017-01-11 12:58:25 -08001467 afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
1468 hosts = afe.get_hosts(hostname=hostname)
1469 if not hosts:
1470 raise error.AutoservError('No hosts named %s found' % hostname)
1471
1472 return hosts[0]
1473
1474
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -07001475def _create_file_backed_host_info_store(store_dir, hostname):
1476 """Create a CachingHostInfoStore backed by an existing file.
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -08001477
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -07001478 @param store_dir: A directory to contain store backing files.
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -08001479 @param hostname: Name of the host for which we want the store.
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -07001480
1481 @returns: An object of type CachingHostInfoStore.
1482 """
1483 backing_file_path = os.path.join(store_dir, '%s.store' % hostname)
1484 if not os.path.isfile(backing_file_path):
1485 raise error.AutoservError(
1486 'Requested FileStore but no backing file at %s'
1487 % backing_file_path
1488 )
1489 return file_store.FileStore(backing_file_path)
1490
1491
1492def _create_afe_backed_host_info_store(store_dir, hostname):
1493 """Create a CachingHostInfoStore backed by the AFE.
1494
1495 @param store_dir: A directory to contain store backing files.
1496 @param hostname: Name of the host for which we want the store.
1497
1498 @returns: An object of type CachingHostInfoStore.
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -08001499 """
Prathmesh Prabhu588007d2017-06-15 00:31:31 -07001500 primary_store = afe_store.AfeStore(hostname)
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -08001501 try:
Prathmesh Prabhu588007d2017-06-15 00:31:31 -07001502 primary_store.get(force_refresh=True)
Prathmesh Prabhu07b5d982017-01-11 12:08:33 -08001503 except host_info.StoreError:
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -07001504 raise error.AutoservError(
1505 'Could not obtain HostInfo for hostname %s' % hostname)
1506 # Since the store wasn't initialized external to autoserv, we must
1507 # ensure that the store we create is unique within store_dir.
1508 backing_file_path = os.path.join(
1509 _make_unique_subdir(store_dir),
1510 '%s.store' % hostname,
1511 )
Prathmesh Prabhu588007d2017-06-15 00:31:31 -07001512 logging.info('Shadowing AFE store with a FileStore at %s',
1513 backing_file_path)
1514 shadow_store = file_store.FileStore(backing_file_path)
1515 return shadowing_store.ShadowingStore(primary_store, shadow_store)
1516
1517
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -07001518def _make_unique_subdir(workdir):
1519 """Creates a new subdir within workdir and returns the path to it."""
1520 store_dir = os.path.join(workdir, 'dir_%s' % uuid.uuid4())
Prathmesh Prabhu588007d2017-06-15 00:31:31 -07001521 _make_dirs_if_needed(store_dir)
Prathmesh Prabhu7fc39c52018-03-21 14:08:30 -07001522 return store_dir
Prathmesh Prabhu588007d2017-06-15 00:31:31 -07001523
1524
1525def _make_dirs_if_needed(path):
1526 """os.makedirs, but ignores failure because the leaf directory exists"""
1527 try:
1528 os.makedirs(path)
1529 except OSError as e:
1530 if e.errno != errno.EEXIST:
1531 raise