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