xref: /openbmc/openbmc-test-automation/lib/code_update_utils.py (revision 35aa8d53f99891f7fb7c8a1b02b6fb72ffd04727)
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 checked for
30                   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, taken_priorities[image_priority]))
45        taken_priorities[image_priority] = image
46
47
48def get_non_running_bmc_software_object():
49    r"""
50    Get the URI to a BMC image from software that is not running on the BMC.
51    """
52
53    # Get the version of the image currently running on the BMC.
54    _, cur_img_version = keyword.run_key("Get BMC Version")
55    # Remove the surrounding double quotes from the version.
56    cur_img_version = cur_img_version.replace('"', '')
57
58    _, images = keyword.run_key("Read Properties  "
59                                + var.SOFTWARE_VERSION_URI + "enumerate")
60
61    for image_name in images:
62        _, image_properties = keyword.run_key(
63            "Get Host Software Property  " + image_name)
64        if 'Purpose' in image_properties and 'Version' in image_properties \
65                and image_properties['Purpose'] != var.VERSION_PURPOSE_HOST \
66                and image_properties['Version'] != cur_img_version:
67            return image_name
68    BuiltIn().fail("Did not find any non-running BMC images.")
69
70
71def delete_all_pnor_images():
72    r"""
73    Delete all PNOR images from the BMC.
74    """
75
76    status, images = keyword.run_key("Get Software Objects  "
77                                     + var.VERSION_PURPOSE_HOST)
78    for image_name in images:
79        BuiltIn().log_to_console(image_name)
80        # Delete twice, in case the image is in the /tmp/images directory
81        keyword.run_key("Call Method  " + image_name
82                        + "  delete  data={\"data\":[]}")
83        keyword.run_key("Call Method  " + image_name
84                        + "  delete  data={\"data\":[]}")
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 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 < 30):
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 last
132                updated file or folder will be returned to the
133                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    tar = tarfile.open(tar_file_path)
153    for member in tar.getmembers():
154        f = tar.extractfile(member)
155        content = f.read()
156        if "version=" in content:
157            content = content.split("\n")
158            content = [x for x in content if "version=" in x]
159            version = content[0].split("=")[-1]
160            break
161    tar.close()
162    return version
163
164
165def get_image_version(file_path):
166    r"""
167    Read the file for a version object.
168
169    Description of argument(s):
170    file_path    The path to a file that holds the image version.
171    """
172
173    stdout, stderr, rc = \
174        bsu.bmc_execute_command("cat "  + file_path +
175                                " | grep \"version=\"", ignore_err=1)
176    return (stdout.split("\n")[0]).split("=")[-1]
177
178
179def get_image_purpose(file_path):
180    r"""
181    Read the file for a purpose object.
182
183    Description of argument(s):
184    file_path    The path to a file that holds the image purpose.
185    """
186
187    stdout, stderr, rc = \
188        bsu.bmc_execute_command("cat " + file_path +
189                                " | grep \"purpose=\"", ignore_err=1)
190    return stdout.split("=")[-1]
191
192
193def get_image_path(image_version):
194    r"""
195    Query the upload image dir for the presence of image matching
196    the version that was read from the MANIFEST before uploading
197    the image. Based on the purpose verify the activation object
198    exists and is either READY or INVALID.
199
200    Description of argument(s):
201    image_version    The version of the image that should match one
202                     of the images in the upload dir.
203    """
204
205    stdout, stderr, rc = \
206        bsu.bmc_execute_command("ls -d " + var.IMAGE_UPLOAD_DIR_PATH +
207                                "*/")
208
209    image_list = stdout.split("\n")
210    retry = 0
211    while (retry < 10):
212        for i in range(0, len(image_list)):
213            version = get_image_version(image_list[i] + "MANIFEST")
214            if (version == image_version):
215                return image_list[i]
216        time.sleep(10)
217        retry += 1
218
219
220def verify_image_upload(image_version,
221                        timeout=3):
222    r"""
223    Verify the image was uploaded correctly and that it created
224    a valid d-bus object. If the first check for the image
225    fails, try again until we reach the timeout.
226
227    Description of argument(s):
228    image_version  The version from the image's manifest file
229                   (e.g. "v2.2-253-g00050f1").
230    timeout        How long, in minutes, to keep trying to find the
231                   image on the BMC. Default is 3 minutes.
232    """
233
234    image_path = get_image_path(image_version)
235    image_version_id = image_path.split("/")[-2]
236
237    keyword.run_key_u("Open Connection And Log In")
238    image_purpose = get_image_purpose(image_path + "MANIFEST")
239    if (image_purpose == var.VERSION_PURPOSE_BMC or
240            image_purpose == var.VERSION_PURPOSE_HOST):
241        uri = var.SOFTWARE_VERSION_URI + image_version_id
242        ret_values = ""
243        for itr in range(timeout * 2):
244            status, ret_values = \
245                keyword.run_key("Read Attribute  " + uri + "  Activation")
246
247            if ((ret_values == var.READY) or (ret_values == var.INVALID)
248                    or (ret_values == var.ACTIVE)):
249                return True, image_version_id
250            else:
251                time.sleep(30)
252
253        # If we exit the for loop, the timeout has been reached
254        gp.print_var(ret_values)
255        return False, None
256    else:
257        gp.print_var(image_purpose)
258        return False, None
259
260
261def verify_image_not_in_bmc_uploads_dir(image_version, timeout=3):
262    r"""
263    Check that an image with the given version is not unpacked inside of the
264    BMCs image uploads directory. If no image is found, retry every 30 seconds
265    until the given timeout is hit, in case the BMC takes time
266    unpacking the image.
267
268    Description of argument(s):
269    image_version  The version of the image to look for on the BMC.
270    timeout        How long, in minutes, to try to find an image on the BMC.
271                   Default is 3 minutes.
272    """
273
274    for i in range(timeout * 2):
275        stdout, stderr, rc = \
276        bsu.bmc_execute_command('ls ' + var.IMAGE_UPLOAD_DIR_PATH +
277                                '*/MANIFEST 2>/dev/null ' +
278                                '| xargs grep -rl "version=' +
279                                image_version + '"')
280        image_dir = os.path.dirname(stdout.split('\n')[0])
281        if '' != image_dir:
282            bsu.bmc_execute_command('rm -rf ' + image_dir)
283            BuiltIn().fail('Found invalid BMC Image: ' + image_dir)
284        time.sleep(30)
285