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