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