1*9c5db199SXin Li# Copyright 2021 The Chromium OS Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Liimport base64 6*9c5db199SXin Liimport logging 7*9c5db199SXin Liimport os 8*9c5db199SXin Liimport json 9*9c5db199SXin Liimport random 10*9c5db199SXin Liimport stat 11*9c5db199SXin Liimport string 12*9c5db199SXin Li 13*9c5db199SXin Li# Shell command to force unmount a mount point if it is mounted 14*9c5db199SXin LiFORCED_UMOUNT_DIR_IF_MOUNTPOINT_CMD = ( 15*9c5db199SXin Li 'if mountpoint -q %(dir)s; then umount -l %(dir)s; fi') 16*9c5db199SXin Li# Shell command to set exec and suid flags 17*9c5db199SXin LiSET_MOUNT_FLAGS_CMD = 'mount -o remount,exec,suid %s' 18*9c5db199SXin Li# Shell command to send SIGHUP to dbus daemon 19*9c5db199SXin LiDBUS_RELOAD_COMMAND = 'killall -HUP dbus-daemon' 20*9c5db199SXin Li 21*9c5db199SXin Li 22*9c5db199SXin Lidef extract_from_image(host, image_name, dest_dir): 23*9c5db199SXin Li """ 24*9c5db199SXin Li Extract contents of an image to a directory. 25*9c5db199SXin Li 26*9c5db199SXin Li @param host: The DUT to execute the command on 27*9c5db199SXin Li @param image_name: Name of image 28*9c5db199SXin Li @param dest_dir: directory where contents of image will be placed. 29*9c5db199SXin Li 30*9c5db199SXin Li """ 31*9c5db199SXin Li 32*9c5db199SXin Li if not host.path_exists('/var/lib/imageloader/%s' % image_name): 33*9c5db199SXin Li raise Exception('Image %s not found on host %s' % (image_name, host)) 34*9c5db199SXin Li 35*9c5db199SXin Li def gen_random_str(length): 36*9c5db199SXin Li """ 37*9c5db199SXin Li Generate random string 38*9c5db199SXin Li 39*9c5db199SXin Li @param length: Length of the string 40*9c5db199SXin Li 41*9c5db199SXin Li @return random string of specified length 42*9c5db199SXin Li 43*9c5db199SXin Li """ 44*9c5db199SXin Li return ''.join( 45*9c5db199SXin Li [random.choice(string.hexdigits) for _ in range(length)]) 46*9c5db199SXin Li 47*9c5db199SXin Li image_mount_point = '/tmp/image_%s' % gen_random_str(8) 48*9c5db199SXin Li 49*9c5db199SXin Li # Create directories from scratch 50*9c5db199SXin Li host.run(['rm', '-rf', dest_dir]) 51*9c5db199SXin Li host.run(['mkdir', '-p', '--mode', '0755', dest_dir, image_mount_point]) 52*9c5db199SXin Li 53*9c5db199SXin Li try: 54*9c5db199SXin Li # Mount image and copy content to the destination directory 55*9c5db199SXin Li host.run([ 56*9c5db199SXin Li 'imageloader', '--mount', 57*9c5db199SXin Li '--mount_component=%s' % image_name, 58*9c5db199SXin Li '--mount_point=%s' % image_mount_point 59*9c5db199SXin Li ]) 60*9c5db199SXin Li 61*9c5db199SXin Li host.run(['cp', '-r', '%s/*' % image_mount_point, '%s/' % dest_dir]) 62*9c5db199SXin Li except Exception as e: 63*9c5db199SXin Li raise Exception( 64*9c5db199SXin Li 'Error extracting content from image %s on host %s ' % 65*9c5db199SXin Li (image_name, host), e) 66*9c5db199SXin Li finally: 67*9c5db199SXin Li # Unmount image and remove the temporary directory 68*9c5db199SXin Li host.run([ 69*9c5db199SXin Li 'imageloader', '--unmount', 70*9c5db199SXin Li '--mount_point=%s' % image_mount_point 71*9c5db199SXin Li ]) 72*9c5db199SXin Li host.run(['rm', '-rf', image_mount_point]) 73*9c5db199SXin Li 74*9c5db199SXin Li 75*9c5db199SXin Lidef _stop_chrome_if_necessary(host): 76*9c5db199SXin Li """ 77*9c5db199SXin Li Stop chrome if it is running. 78*9c5db199SXin Li 79*9c5db199SXin Li @param host: The DUT to execute the command on 80*9c5db199SXin Li 81*9c5db199SXin Li @return True if chrome was stopped. False otherwise. 82*9c5db199SXin Li 83*9c5db199SXin Li """ 84*9c5db199SXin Li status = host.run_output('status ui') 85*9c5db199SXin Li if 'start' in status: 86*9c5db199SXin Li return host.run('stop ui', ignore_status=True).exit_status == 0 87*9c5db199SXin Li 88*9c5db199SXin Li return False 89*9c5db199SXin Li 90*9c5db199SXin Li 91*9c5db199SXin Lidef _mount_chrome(host, chrome_dir, chrome_mount_point): 92*9c5db199SXin Li """ 93*9c5db199SXin Li Mount chrome to a mount point 94*9c5db199SXin Li 95*9c5db199SXin Li @param host: The DUT to execute the command on 96*9c5db199SXin Li @param chrome_dir: directory where the chrome binary and artifacts 97*9c5db199SXin Li will be placed. 98*9c5db199SXin Li @param chrome_mount_point: Chrome mount point 99*9c5db199SXin Li 100*9c5db199SXin Li """ 101*9c5db199SXin Li chrome_stopped = _stop_chrome_if_necessary(host) 102*9c5db199SXin Li _umount_chrome(host, chrome_mount_point) 103*9c5db199SXin Li 104*9c5db199SXin Li # Mount chrome to the desired chrome directory 105*9c5db199SXin Li # Upon restart, this version of chrome will be used instead. 106*9c5db199SXin Li host.run(['mount', '--rbind', chrome_dir, chrome_mount_point]) 107*9c5db199SXin Li 108*9c5db199SXin Li # Chrome needs partition to have exec and suid flags set 109*9c5db199SXin Li host.run(SET_MOUNT_FLAGS_CMD % chrome_mount_point) 110*9c5db199SXin Li 111*9c5db199SXin Li # Send SIGHUP to dbus-daemon to tell it to reload its configs. This won't 112*9c5db199SXin Li # pick up major changes (bus type, logging, etc.), but all we care about is 113*9c5db199SXin Li # getting the latest policy from /opt/google/chrome/dbus so that Chrome will 114*9c5db199SXin Li # be authorized to take ownership of its service names. 115*9c5db199SXin Li host.run(DBUS_RELOAD_COMMAND, ignore_status=True) 116*9c5db199SXin Li 117*9c5db199SXin Li if chrome_stopped: 118*9c5db199SXin Li host.run('start ui', ignore_status=True) 119*9c5db199SXin Li 120*9c5db199SXin Li 121*9c5db199SXin Lidef _umount_chrome(host, chrome_mount_point): 122*9c5db199SXin Li """ 123*9c5db199SXin Li Unmount chrome 124*9c5db199SXin Li 125*9c5db199SXin Li @param host: The DUT to execute the command on 126*9c5db199SXin Li @param chrome_mount_point: Chrome mount point 127*9c5db199SXin Li 128*9c5db199SXin Li """ 129*9c5db199SXin Li chrome_stopped = _stop_chrome_if_necessary(host) 130*9c5db199SXin Li # Unmount chrome. Upon restart, the default version of chrome 131*9c5db199SXin Li # under the root partition will be used. 132*9c5db199SXin Li try: 133*9c5db199SXin Li host.run(FORCED_UMOUNT_DIR_IF_MOUNTPOINT_CMD % 134*9c5db199SXin Li {'dir': chrome_mount_point}) 135*9c5db199SXin Li except Exception as e: 136*9c5db199SXin Li raise Exception('Exception during cleanup on host %s' % host, e) 137*9c5db199SXin Li 138*9c5db199SXin Li if chrome_stopped: 139*9c5db199SXin Li host.run('start ui', ignore_status=True) 140*9c5db199SXin Li 141*9c5db199SXin Li 142*9c5db199SXin Lidef setup_host(host, chrome_dir, chrome_mount_point): 143*9c5db199SXin Li """ 144*9c5db199SXin Li Perform setup on host. 145*9c5db199SXin Li 146*9c5db199SXin Li Mount chrome to point to the version provisioned by TLS. 147*9c5db199SXin Li The provisioning mechanism of chrome from the chrome builder is 148*9c5db199SXin Li based on Lacros Tast Test on Skylab (go/lacros-tast-on-skylab). 149*9c5db199SXin Li 150*9c5db199SXin Li The lacros image provisioned by TLS contains the chrome binary 151*9c5db199SXin Li and artifacts. 152*9c5db199SXin Li 153*9c5db199SXin Li @param host: The DUT to execute the command on 154*9c5db199SXin Li @param chrome_dir: directory where the chrome binary and artifacts 155*9c5db199SXin Li will be placed. 156*9c5db199SXin Li @param chrome_mount_point: Chrome mount point 157*9c5db199SXin Li 158*9c5db199SXin Li """ 159*9c5db199SXin Li logging.info("Setting up host:%s", host) 160*9c5db199SXin Li try: 161*9c5db199SXin Li extract_from_image(host, 'lacros', chrome_dir) 162*9c5db199SXin Li if chrome_mount_point: 163*9c5db199SXin Li _mount_chrome(host, '%s/out/Release' % chrome_dir, 164*9c5db199SXin Li chrome_mount_point) 165*9c5db199SXin Li except Exception as e: 166*9c5db199SXin Li raise Exception( 167*9c5db199SXin Li 'Exception while mounting %s on host %s' % 168*9c5db199SXin Li (chrome_mount_point, host), e) 169*9c5db199SXin Li 170*9c5db199SXin Li 171*9c5db199SXin Lidef cleanup_host(host, chrome_dir, chrome_mount_point): 172*9c5db199SXin Li """ 173*9c5db199SXin Li Umount chrome and perform cleanup. 174*9c5db199SXin Li 175*9c5db199SXin Li @param host: The DUT to execute the command on 176*9c5db199SXin Li @param chrome_dir: directory where the chrome binary and artifacts 177*9c5db199SXin Li is placed. 178*9c5db199SXin Li @param chrome_mount_point: Chrome mount point 179*9c5db199SXin Li 180*9c5db199SXin Li """ 181*9c5db199SXin Li logging.info("Unmounting chrome on host: %s", host) 182*9c5db199SXin Li try: 183*9c5db199SXin Li if chrome_mount_point: 184*9c5db199SXin Li _umount_chrome(host, chrome_mount_point) 185*9c5db199SXin Li host.run(['rm', '-rf', chrome_dir]) 186*9c5db199SXin Li except Exception as e: 187*9c5db199SXin Li raise Exception('Exception during cleanup on host %s' % host, e) 188*9c5db199SXin Li 189*9c5db199SXin Li 190*9c5db199SXin Lidef get_tast_expr_from_file(host, args_dict, results_dir, base_path=None): 191*9c5db199SXin Li """ 192*9c5db199SXin Li Get Tast expression from argument dictionary using a file. 193*9c5db199SXin Li If the tast_expr_file and tast_expr_key are in the dictionary returns the 194*9c5db199SXin Li tast expression from the file. If either/both args are not in the dict, 195*9c5db199SXin Li None is returned. 196*9c5db199SXin Li tast_expr_file expects a file containing a json dictionary which it will 197*9c5db199SXin Li then use tast_expr_key to pull the tast_expr. 198*9c5db199SXin Li 199*9c5db199SXin Li The tast_expr_file is a json file containing a dictionary of names to tast 200*9c5db199SXin Li expressions like: 201*9c5db199SXin Li 202*9c5db199SXin Li { 203*9c5db199SXin Li "default": "(\"group:mainline\" && \"dep:lacros\" && !informational)", 204*9c5db199SXin Li "tast_disabled_tests_from_lacros_example": "(\"group:mainline\" && \"dep:lacros\" && !informational && !\"name:lacros.Basic\")" 205*9c5db199SXin Li } 206*9c5db199SXin Li 207*9c5db199SXin Li @param host: Host having the provisioned lacros image with the file 208*9c5db199SXin Li @param args_dict: Argument dictionary 209*9c5db199SXin Li @param results_dir: Where to store the tast_expr_file from the dut 210*9c5db199SXin Li @param base_path: Base path of the provisioned folder 211*9c5db199SXin Li 212*9c5db199SXin Li """ 213*9c5db199SXin Li tast_expr_file_name = args_dict.get('tast_expr_file') 214*9c5db199SXin Li tast_expr_key = args_dict.get('tast_expr_key') 215*9c5db199SXin Li if tast_expr_file_name and tast_expr_key: 216*9c5db199SXin Li if base_path: 217*9c5db199SXin Li tast_expr_file_name = os.path.join(base_path, tast_expr_file_name) 218*9c5db199SXin Li 219*9c5db199SXin Li # Get the tast expr file from the provisioned lacros folder 220*9c5db199SXin Li if not host.path_exists(tast_expr_file_name): 221*9c5db199SXin Li raise Exception( 222*9c5db199SXin Li 'tast_expr_file: %s could not be found on the dut' % 223*9c5db199SXin Li tast_expr_file_name) 224*9c5db199SXin Li local_file_name = os.path.join(results_dir, 225*9c5db199SXin Li os.path.basename(tast_expr_file_name)) 226*9c5db199SXin Li st = os.stat(results_dir) 227*9c5db199SXin Li os.chmod(results_dir, st.st_mode | stat.S_IWRITE) 228*9c5db199SXin Li host.get_file(tast_expr_file_name, local_file_name, delete_dest=True) 229*9c5db199SXin Li 230*9c5db199SXin Li with open(local_file_name) as tast_expr_file: 231*9c5db199SXin Li expr_dict = json.load(tast_expr_file) 232*9c5db199SXin Li expr = expr_dict.get(tast_expr_key) 233*9c5db199SXin Li # If both args were provided, the entry is expected in the file 234*9c5db199SXin Li if not expr: 235*9c5db199SXin Li raise Exception('tast_expr_key: %s could not be found' % 236*9c5db199SXin Li tast_expr_key) 237*9c5db199SXin Li logging.info("tast_expr retreived from:%s", tast_expr_file) 238*9c5db199SXin Li return expr 239*9c5db199SXin Li return None 240*9c5db199SXin Li 241*9c5db199SXin Li 242*9c5db199SXin Lidef get_tast_expr(args_dict): 243*9c5db199SXin Li """ 244*9c5db199SXin Li Get Tast expression from argument dictionary. 245*9c5db199SXin Li Users have options of using tast_expr or tast_expr_b64 in dictionary. 246*9c5db199SXin Li tast_expr_b64 expects a base64 encoded tast_expr, for instance: 247*9c5db199SXin Li tast_expr = '("group:mainline" && "dep:lacros")' 248*9c5db199SXin Li tast_expr_b64 = base64.b64encode(s.encode('utf-8')).decode('ascii') 249*9c5db199SXin Li 250*9c5db199SXin Li @param args_dict: Argument dictionary 251*9c5db199SXin Li 252*9c5db199SXin Li """ 253*9c5db199SXin Li expr = args_dict.get('tast_expr') 254*9c5db199SXin Li if expr: 255*9c5db199SXin Li return expr 256*9c5db199SXin Li 257*9c5db199SXin Li expr_b64 = args_dict.get('tast_expr_b64') 258*9c5db199SXin Li if expr_b64: 259*9c5db199SXin Li try: 260*9c5db199SXin Li expr = base64.b64decode(expr_b64).decode() 261*9c5db199SXin Li return expr 262*9c5db199SXin Li except Exception as e: 263*9c5db199SXin Li raise Exception('Failed to decode tast_expr_b64: %s' % 264*9c5db199SXin Li expr_b64) from e 265*9c5db199SXin Li 266*9c5db199SXin Li raise Exception( 267*9c5db199SXin Li '''Tast expression is unspecified: set tast_expr or tast_expr_b64 in --args.\n''' 268*9c5db199SXin Li ''' Example: test_that --args="tast_expr=lacros.Basic"\n''' 269*9c5db199SXin Li ''' If the expression contains spaces, consider transforming it to\n''' 270*9c5db199SXin Li ''' base64 and passing it via tast_expr_b64 flag.\n''' 271*9c5db199SXin Li ''' Example:\n''' 272*9c5db199SXin Li ''' In Python:\n''' 273*9c5db199SXin Li ''' tast_expr = '("group:mainline" && "dep:lacros")'\n''' 274*9c5db199SXin Li ''' # Yields 'KCJncm91cDptYWlubGluZSIgJiYgImRlcDpsYWNyb3MiKQ=='\n''' 275*9c5db199SXin Li ''' tast_expr_b64 = base64.b64encode(s.encode('utf-8')).decode('ascii')\n''' 276*9c5db199SXin Li ''' Then in Autotest CLI:\n''' 277*9c5db199SXin Li ''' test_that --args="tast_expr_b64=KCJncm91cDptYWlubGluZSIgJiYgImRlcDpsYWNyb3MiKQ=="\n''' 278*9c5db199SXin Li ''' More details at go/lacros-on-skylab.''') 279