1#!/usr/bin/env python 2 3r""" 4This module provides utilities for code updates. 5""" 6 7import os 8import re 9import sys 10import tarfile 11import time 12 13robot_pgm_dir_path = os.path.dirname(__file__) + os.sep 14repo_data_path = re.sub('/lib', '/data', robot_pgm_dir_path) 15sys.path.append(repo_data_path) 16 17import bmc_ssh_utils as bsu 18import gen_robot_keyword as keyword 19import gen_print as gp 20import variables as var 21from robot.libraries.BuiltIn import BuiltIn 22 23 24def verify_no_duplicate_image_priorities(image_purpose): 25 r""" 26 Check that there are no active images with the same purpose and priority. 27 28 Description of argument(s): 29 image_purpose The purpose that images must have to be 30 checked for priority duplicates. 31 """ 32 33 taken_priorities = {} 34 _, image_names = keyword.run_key("Get Software Objects " 35 + "version_type=" + image_purpose) 36 37 for image_name in image_names: 38 _, image = keyword.run_key("Get Host Software Property " + image_name) 39 if image["Activation"] != var.ACTIVE: 40 continue 41 image_priority = image["Priority"] 42 if image_priority in taken_priorities: 43 BuiltIn().fail("Found active images with the same priority.\n" 44 + gp.sprint_vars(image, 45 taken_priorities[image_priority])) 46 taken_priorities[image_priority] = image 47 48 49def get_non_running_bmc_software_object(): 50 r""" 51 Get the URI to a BMC image from software that is not running on the BMC. 52 """ 53 54 # Get the version of the image currently running on the BMC. 55 _, cur_img_version = keyword.run_key("Get BMC Version") 56 # Remove the surrounding double quotes from the version. 57 cur_img_version = cur_img_version.replace('"', '') 58 59 _, images = keyword.run_key("Read Properties " 60 + var.SOFTWARE_VERSION_URI + "enumerate") 61 62 for image_name in images: 63 _, image_properties = keyword.run_key( 64 "Get Host Software Property " + image_name) 65 if 'Purpose' in image_properties and 'Version' in image_properties \ 66 and image_properties['Purpose'] != var.VERSION_PURPOSE_HOST \ 67 and image_properties['Version'] != cur_img_version: 68 return image_name 69 BuiltIn().fail("Did not find any non-running BMC images.") 70 71 72def delete_all_pnor_images(): 73 r""" 74 Delete all PNOR images from the BMC. 75 """ 76 77 keyword.run_key("Initiate Host PowerOff") 78 79 status, images = keyword.run_key("Get Software Objects " 80 + var.VERSION_PURPOSE_HOST) 81 for image_name in images: 82 keyword.run_key("Delete Image And Verify " + image_name + " " 83 + var.VERSION_PURPOSE_HOST) 84 85 86def wait_for_activation_state_change(version_id, initial_state): 87 r""" 88 Wait for the current activation state of ${version_id} to 89 change from the state provided by the calling function. 90 91 Description of argument(s): 92 version_id The version ID whose state change we are 93 waiting for. 94 initial_state The activation state we want to wait for. 95 """ 96 97 keyword.run_key_u("Open Connection And Log In") 98 retry = 0 99 num_read_errors = 0 100 read_fail_threshold = 1 101 while (retry < 60): 102 # TODO: Use retry option in run_key when available. 103 status, software_state = keyword.run_key("Read Properties " 104 + var.SOFTWARE_VERSION_URI 105 + str(version_id), 106 ignore=1) 107 if status == 'FAIL': 108 num_read_errors += 1 109 if num_read_errors > read_fail_threshold: 110 message = "Read errors exceeds threshold:\n " \ 111 + gp.sprint_vars(num_read_errors, read_fail_threshold) 112 BuiltIn().fail(message) 113 time.sleep(10) 114 continue 115 116 current_state = (software_state)["Activation"] 117 if (initial_state == current_state): 118 time.sleep(10) 119 retry += 1 120 num_read_errors = 0 121 else: 122 return 123 return 124 125 126def get_latest_file(dir_path): 127 r""" 128 Get the path to the latest uploaded file. 129 130 Description of argument(s): 131 dir_path Path to the dir from which the name of the 132 last updated file or folder will be 133 returned to the calling function. 134 """ 135 136 stdout, stderr, rc = \ 137 bsu.bmc_execute_command("cd " + dir_path 138 + "; stat -c '%Y %n' * |" 139 + " sort -k1,1nr | head -n 1") 140 return stdout.split(" ")[-1] 141 142 143def get_version_tar(tar_file_path): 144 r""" 145 Read the image version from the MANIFEST inside the tarball. 146 147 Description of argument(s): 148 tar_file_path The path to a tar file that holds the image 149 version inside the MANIFEST. 150 """ 151 152 version = "" 153 tar = tarfile.open(tar_file_path) 154 for member in tar.getmembers(): 155 f = tar.extractfile(member) 156 content = f.read() 157 if content.find(b"version=") == -1: 158 # This tar member does not contain the version. 159 continue 160 content = content.decode("utf-8").split("\n") 161 content = [x for x in content if "version=" in x] 162 version = content[0].split("=")[-1] 163 break 164 tar.close() 165 return version 166 167 168def get_image_version(file_path): 169 r""" 170 Read the file for a version object. 171 172 Description of argument(s): 173 file_path The path to a file that holds the image 174 version. 175 """ 176 177 stdout, stderr, rc = \ 178 bsu.bmc_execute_command("cat " + file_path 179 + " | grep \"version=\"", ignore_err=1) 180 return (stdout.split("\n")[0]).split("=")[-1] 181 182 183def get_image_purpose(file_path): 184 r""" 185 Read the file for a purpose object. 186 187 Description of argument(s): 188 file_path The path to a file that holds the image 189 purpose. 190 """ 191 192 stdout, stderr, rc = \ 193 bsu.bmc_execute_command("cat " + file_path 194 + " | grep \"purpose=\"", ignore_err=1) 195 return stdout.split("=")[-1] 196 197 198def get_image_path(image_version): 199 r""" 200 Query the upload image dir for the presence of image matching 201 the version that was read from the MANIFEST before uploading 202 the image. Based on the purpose verify the activation object 203 exists and is either READY or INVALID. 204 205 Description of argument(s): 206 image_version The version of the image that should match 207 one of the images in the upload dir. 208 """ 209 210 stdout, stderr, rc = \ 211 bsu.bmc_execute_command("ls -d " + var.IMAGE_UPLOAD_DIR_PATH + "*/") 212 213 image_list = stdout.split("\n") 214 retry = 0 215 while (retry < 10): 216 for i in range(0, len(image_list)): 217 version = get_image_version(image_list[i] + "MANIFEST") 218 if (version == image_version): 219 return image_list[i] 220 time.sleep(10) 221 retry += 1 222 223 224def verify_image_upload(image_version, 225 timeout=3): 226 r""" 227 Verify the image was uploaded correctly and that it created 228 a valid d-bus object. If the first check for the image 229 fails, try again until we reach the timeout. 230 231 Description of argument(s): 232 image_version The version from the image's manifest file 233 (e.g. "v2.2-253-g00050f1"). 234 timeout How long, in minutes, to keep trying to 235 find the image on the BMC. Default is 3 minutes. 236 """ 237 238 image_path = get_image_path(image_version) 239 image_version_id = image_path.split("/")[-2] 240 241 keyword.run_key_u("Open Connection And Log In") 242 image_purpose = get_image_purpose(image_path + "MANIFEST") 243 if (image_purpose == var.VERSION_PURPOSE_BMC 244 or image_purpose == var.VERSION_PURPOSE_HOST): 245 uri = var.SOFTWARE_VERSION_URI + image_version_id 246 ret_values = "" 247 for itr in range(timeout * 2): 248 status, ret_values = \ 249 keyword.run_key("Read Attribute " + uri + " Activation") 250 251 if ((ret_values == var.READY) or (ret_values == var.INVALID) 252 or (ret_values == var.ACTIVE)): 253 return True, image_version_id 254 else: 255 time.sleep(30) 256 257 # If we exit the for loop, the timeout has been reached 258 gp.print_var(ret_values) 259 return False, None 260 else: 261 gp.print_var(image_purpose) 262 return False, None 263 264 265def verify_image_not_in_bmc_uploads_dir(image_version, timeout=3): 266 r""" 267 Check that an image with the given version is not unpacked inside of the 268 BMCs image uploads directory. If no image is found, retry every 30 seconds 269 until the given timeout is hit, in case the BMC takes time 270 unpacking the image. 271 272 Description of argument(s): 273 image_version The version of the image to look for on 274 the BMC. 275 timeout How long, in minutes, to try to find an 276 image on the BMC. Default is 3 minutes. 277 """ 278 279 for i in range(timeout * 2): 280 stdout, stderr, rc = \ 281 bsu.bmc_execute_command('ls ' + var.IMAGE_UPLOAD_DIR_PATH 282 + '*/MANIFEST 2>/dev/null ' 283 + '| xargs grep -rl "version=' 284 + image_version + '"') 285 image_dir = os.path.dirname(stdout.split('\n')[0]) 286 if '' != image_dir: 287 bsu.bmc_execute_command('rm -rf ' + image_dir) 288 BuiltIn().fail('Found invalid BMC Image: ' + image_dir) 289 time.sleep(30) 290