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