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