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