1#!/usr/bin/env python3 2# 3# Copyright (c) 2014 Intel Corporation 4# 5# SPDX-License-Identifier: GPL-2.0-only 6# 7 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 31import argparse_oe 32 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." 76 parser = argparse_oe.ArgumentParser(description=description) 77 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 85class BaseTargetProfile(object, metaclass=ABCMeta): 86 """ 87 This class defines the meta profile for a specific target (MACHINE type + image type). 88 """ 89 90 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 182class BaseRepoProfile(object, metaclass=ABCMeta): 183 """ 184 This class defines the meta profile for an images repository. 185 """ 186 187 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) 278 print(testimage_results) 279 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 346 traceback.print_exc() 347 sys.exit(ret) 348