blob: 27b1cae38fa3205b2dacab56d510e93b45aa0015 [file] [log] [blame]
Patrick Williamsc0f7c042017-02-23 20:41:17 -06001#!/usr/bin/env python3
Patrick Williamsc124f4f2015-09-15 14:41:29 -05002
3# Copyright (c) 2014 Intel Corporation
4#
5# This program is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License version 2 as
7# published by the Free Software Foundation.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License along
15# with this program; if not, write to the Free Software Foundation, Inc.,
16# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17
18# DESCRIPTION
19# This script is used to test public autobuilder images on remote hardware.
20# The script is called from a machine that is able download the images from the remote images repository and to connect to the test hardware.
21#
22# test-remote-image --image-type core-image-sato --repo-link http://192.168.10.2/images --required-packages rpm psplash
23#
24# Translation: Build the 'rpm' and 'pslash' packages and test a remote core-image-sato image using the http://192.168.10.2/images repository.
25#
26# You can also use the '-h' option to see some help information.
27
28import os
29import sys
30import argparse
31import logging
32import shutil
33from abc import ABCMeta, abstractmethod
34
35# Add path to scripts/lib in sys.path;
36scripts_path = os.path.abspath(os.path.dirname(os.path.abspath(sys.argv[0])))
37lib_path = scripts_path + '/lib'
38sys.path = sys.path + [lib_path]
39
40import scriptpath
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050041import argparse_oe
Patrick Williamsc124f4f2015-09-15 14:41:29 -050042
43# Add meta/lib to sys.path
44scriptpath.add_oe_lib_path()
45
46import oeqa.utils.ftools as ftools
47from oeqa.utils.commands import runCmd, bitbake, get_bb_var
48
49# Add all lib paths relative to BBPATH to sys.path; this is used to find and import the target controllers.
50for path in get_bb_var('BBPATH').split(":"):
51 sys.path.insert(0, os.path.abspath(os.path.join(path, 'lib')))
52
53# In order to import modules that contain target controllers, we need the bitbake libraries in sys.path .
54bitbakepath = scriptpath.add_bitbake_lib_path()
55if not bitbakepath:
56 sys.stderr.write("Unable to find bitbake by searching parent directory of this script or PATH\n")
57 sys.exit(1)
58
59# create a logger
60def logger_create():
61 log = logging.getLogger('hwauto')
62 log.setLevel(logging.DEBUG)
63
64 fh = logging.FileHandler(filename='hwauto.log', mode='w')
65 fh.setLevel(logging.DEBUG)
66
67 ch = logging.StreamHandler(sys.stdout)
68 ch.setLevel(logging.INFO)
69
70 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
71 fh.setFormatter(formatter)
72 ch.setFormatter(formatter)
73
74 log.addHandler(fh)
75 log.addHandler(ch)
76
77 return log
78
79# instantiate the logger
80log = logger_create()
81
82
83# Define and return the arguments parser for the script
84def get_args_parser():
85 description = "This script is used to run automated runtime tests using remotely published image files. You should prepare the build environment just like building local images and running the tests."
Patrick Williamsd8c66bc2016-06-20 12:57:21 -050086 parser = argparse_oe.ArgumentParser(description=description)
Patrick Williamsc124f4f2015-09-15 14:41:29 -050087 parser.add_argument('--image-types', required=True, action="store", nargs='*', dest="image_types", default=None, help='The image types to test(ex: core-image-minimal).')
88 parser.add_argument('--repo-link', required=True, action="store", type=str, dest="repo_link", default=None, help='The link to the remote images repository.')
89 parser.add_argument('--required-packages', required=False, action="store", nargs='*', dest="required_packages", default=None, help='Required packages for the tests. They will be built before the testing begins.')
90 parser.add_argument('--targetprofile', required=False, action="store", nargs=1, dest="targetprofile", default='AutoTargetProfile', help='The target profile to be used.')
91 parser.add_argument('--repoprofile', required=False, action="store", nargs=1, dest="repoprofile", default='PublicAB', help='The repo profile to be used.')
92 parser.add_argument('--skip-download', required=False, action="store_true", dest="skip_download", default=False, help='Skip downloading the images completely. This needs the correct files to be present in the directory specified by the target profile.')
93 return parser
94
Patrick Williamsc0f7c042017-02-23 20:41:17 -060095class BaseTargetProfile(object, metaclass=ABCMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -050096 """
97 This class defines the meta profile for a specific target (MACHINE type + image type).
98 """
99
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500100 def __init__(self, image_type):
101 self.image_type = image_type
102
103 self.kernel_file = None
104 self.rootfs_file = None
105 self.manifest_file = None
106 self.extra_download_files = [] # Extra files (full name) to be downloaded. They should be situated in repo_link
107
108 # This method is used as the standard interface with the target profile classes.
109 # It returns a dictionary containing a list of files and their meaning/description.
110 def get_files_dict(self):
111 files_dict = {}
112
113 if self.kernel_file:
114 files_dict['kernel_file'] = self.kernel_file
115 else:
116 log.error('The target profile did not set a kernel file.')
117 sys.exit(1)
118
119 if self.rootfs_file:
120 files_dict['rootfs_file'] = self.rootfs_file
121 else:
122 log.error('The target profile did not set a rootfs file.')
123 sys.exit(1)
124
125 if self.manifest_file:
126 files_dict['manifest_file'] = self.manifest_file
127 else:
128 log.error('The target profile did not set a manifest file.')
129 sys.exit(1)
130
131 for idx, f in enumerate(self.extra_download_files):
132 files_dict['extra_download_file' + str(idx)] = f
133
134 return files_dict
135
136class AutoTargetProfile(BaseTargetProfile):
137
138 def __init__(self, image_type):
139 super(AutoTargetProfile, self).__init__(image_type)
140 self.image_name = get_bb_var('IMAGE_LINK_NAME', target=image_type)
141 self.kernel_type = get_bb_var('KERNEL_IMAGETYPE', target=image_type)
142 self.controller = self.get_controller()
143
144 self.set_kernel_file()
145 self.set_rootfs_file()
146 self.set_manifest_file()
147 self.set_extra_download_files()
148
149 # Get the controller object that will be used by bitbake.
150 def get_controller(self):
151 from oeqa.controllers.testtargetloader import TestTargetLoader
152
153 target_controller = get_bb_var('TEST_TARGET')
154 bbpath = get_bb_var('BBPATH').split(':')
155
156 if target_controller == "qemu":
157 from oeqa.targetcontrol import QemuTarget
158 controller = QemuTarget
159 else:
160 testtargetloader = TestTargetLoader()
161 controller = testtargetloader.get_controller_module(target_controller, bbpath)
162 return controller
163
164 def set_kernel_file(self):
165 postconfig = "QA_GET_MACHINE = \"${MACHINE}\""
166 machine = get_bb_var('QA_GET_MACHINE', postconfig=postconfig)
167 self.kernel_file = self.kernel_type + '-' + machine + '.bin'
168
169 def set_rootfs_file(self):
170 image_fstypes = get_bb_var('IMAGE_FSTYPES').split(' ')
171 # Get a matching value between target's IMAGE_FSTYPES and the image fstypes suppoerted by the target controller.
172 fstype = self.controller.match_image_fstype(d=None, image_fstypes=image_fstypes)
173 if fstype:
174 self.rootfs_file = self.image_name + '.' + fstype
175 else:
176 log.error("Could not get a compatible image fstype. Check that IMAGE_FSTYPES and the target controller's supported_image_fstypes fileds have common values.")
177 sys.exit(1)
178
179 def set_manifest_file(self):
180 self.manifest_file = self.image_name + ".manifest"
181
182 def set_extra_download_files(self):
183 self.extra_download_files = self.get_controller_extra_files()
184 if not self.extra_download_files:
185 self.extra_download_files = []
186
187 def get_controller_extra_files(self):
188 controller = self.get_controller()
189 return controller.get_extra_files()
190
191
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600192class BaseRepoProfile(object, metaclass=ABCMeta):
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500193 """
194 This class defines the meta profile for an images repository.
195 """
196
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500197 def __init__(self, repolink, localdir):
198 self.localdir = localdir
199 self.repolink = repolink
200
201 # The following abstract methods are the interfaces to the repository profile classes derived from this abstract class.
202
203 # This method should check the file named 'file_name' if it is different than the upstream one.
204 # Should return False if the image is the same as the upstream and True if it differs.
205 @abstractmethod
206 def check_old_file(self, file_name):
207 pass
208
209 # This method should fetch file_name and create a symlink to localname if set.
210 @abstractmethod
211 def fetch(self, file_name, localname=None):
212 pass
213
214class PublicAB(BaseRepoProfile):
215
216 def __init__(self, repolink, localdir=None):
217 super(PublicAB, self).__init__(repolink, localdir)
218 if localdir is None:
219 self.localdir = os.path.join(os.environ['BUILDDIR'], 'PublicABMirror')
220
221 # Not yet implemented. Always returning True.
222 def check_old_file(self, file_name):
223 return True
224
225 def get_repo_path(self):
226 path = '/machines/'
227
228 postconfig = "QA_GET_MACHINE = \"${MACHINE}\""
229 machine = get_bb_var('QA_GET_MACHINE', postconfig=postconfig)
230 if 'qemu' in machine:
231 path += 'qemu/'
232
233 postconfig = "QA_GET_DISTRO = \"${DISTRO}\""
234 distro = get_bb_var('QA_GET_DISTRO', postconfig=postconfig)
235 path += distro.replace('poky', machine) + '/'
236 return path
237
238
239 def fetch(self, file_name, localname=None):
240 repo_path = self.get_repo_path()
241 link = self.repolink + repo_path + file_name
242
243 self.wget(link, self.localdir, localname)
244
245 def wget(self, link, localdir, localname=None, extraargs=None):
246 wget_cmd = '/usr/bin/env wget -t 2 -T 30 -nv --passive-ftp --no-check-certificate '
247
248 if localname:
249 wget_cmd += ' -O ' + localname + ' '
250
251 if extraargs:
252 wget_cmd += ' ' + extraargs + ' '
253
254 wget_cmd += " -P %s '%s'" % (localdir, link)
255 runCmd(wget_cmd)
256
257class HwAuto():
258
259 def __init__(self, image_types, repolink, required_packages, targetprofile, repoprofile, skip_download):
260 log.info('Initializing..')
261 self.image_types = image_types
262 self.repolink = repolink
263 self.required_packages = required_packages
264 self.targetprofile = targetprofile
265 self.repoprofile = repoprofile
266 self.skip_download = skip_download
267 self.repo = self.get_repo_profile(self.repolink)
268
269 # Get the repository profile; for now we only look inside this module.
270 def get_repo_profile(self, *args, **kwargs):
271 repo = getattr(sys.modules[__name__], self.repoprofile)(*args, **kwargs)
272 log.info("Using repo profile: %s" % repo.__class__.__name__)
273 return repo
274
275 # Get the target profile; for now we only look inside this module.
276 def get_target_profile(self, *args, **kwargs):
277 target = getattr(sys.modules[__name__], self.targetprofile)(*args, **kwargs)
278 log.info("Using target profile: %s" % target.__class__.__name__)
279 return target
280
281 # Run the testimage task on a build while redirecting DEPLOY_DIR_IMAGE to repo.localdir, where the images are downloaded.
282 def runTestimageBuild(self, image_type):
283 log.info("Running the runtime tests for %s.." % image_type)
284 postconfig = "DEPLOY_DIR_IMAGE = \"%s\"" % self.repo.localdir
285 result = bitbake("%s -c testimage" % image_type, ignore_status=True, postconfig=postconfig)
286 testimage_results = ftools.read_file(os.path.join(get_bb_var("T", image_type), "log.do_testimage"))
287 log.info('Runtime tests results for %s:' % image_type)
Patrick Williamsc0f7c042017-02-23 20:41:17 -0600288 print(testimage_results)
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500289 return result
290
291 # Start the procedure!
292 def run(self):
293 if self.required_packages:
294 # Build the required packages for the tests
295 log.info("Building the required packages: %s ." % ', '.join(map(str, self.required_packages)))
296 result = bitbake(self.required_packages, ignore_status=True)
297 if result.status != 0:
298 log.error("Could not build required packages: %s. Output: %s" % (self.required_packages, result.output))
299 sys.exit(1)
300
301 # Build the package repository meta data.
302 log.info("Building the package index.")
303 result = bitbake("package-index", ignore_status=True)
304 if result.status != 0:
305 log.error("Could not build 'package-index'. Output: %s" % result.output)
306 sys.exit(1)
307
308 # Create the directory structure for the images to be downloaded
309 log.info("Creating directory structure %s" % self.repo.localdir)
310 if not os.path.exists(self.repo.localdir):
311 os.makedirs(self.repo.localdir)
312
313 # For each image type, download the needed files and run the tests.
314 noissuesfound = True
315 for image_type in self.image_types:
316 if self.skip_download:
317 log.info("Skipping downloading the images..")
318 else:
319 target = self.get_target_profile(image_type)
320 files_dict = target.get_files_dict()
321 log.info("Downloading files for %s" % image_type)
322 for f in files_dict:
323 if self.repo.check_old_file(files_dict[f]):
324 filepath = os.path.join(self.repo.localdir, files_dict[f])
325 if os.path.exists(filepath):
326 os.remove(filepath)
327 self.repo.fetch(files_dict[f])
328
329 result = self.runTestimageBuild(image_type)
330 if result.status != 0:
331 noissuesfound = False
332
333 if noissuesfound:
334 log.info('Finished. No issues found.')
335 else:
336 log.error('Finished. Some runtime tests have failed. Returning non-0 status code.')
337 sys.exit(1)
338
339
340
341def main():
342
343 parser = get_args_parser()
344 args = parser.parse_args()
345
346 hwauto = HwAuto(image_types=args.image_types, repolink=args.repo_link, required_packages=args.required_packages, targetprofile=args.targetprofile, repoprofile=args.repoprofile, skip_download=args.skip_download)
347
348 hwauto.run()
349
350if __name__ == "__main__":
351 try:
352 ret = main()
353 except Exception:
354 ret = 1
355 import traceback
Patrick Williamsd8c66bc2016-06-20 12:57:21 -0500356 traceback.print_exc()
Patrick Williamsc124f4f2015-09-15 14:41:29 -0500357 sys.exit(ret)