1e7e9171eSGeorge Keishing#!/usr/bin/env python3
2de7d408dSCharles Paul Hofer
3de7d408dSCharles Paul Hoferr"""
4de7d408dSCharles Paul HoferThis module provides utilities for code updates.
5de7d408dSCharles Paul Hofer"""
6de7d408dSCharles Paul Hofer
7*20f38712SPatrick Williamsimport collections
8de7d408dSCharles Paul Hoferimport os
9de7d408dSCharles Paul Hoferimport re
10de7d408dSCharles Paul Hoferimport sys
11de7d408dSCharles Paul Hoferimport tarfile
12de7d408dSCharles Paul Hoferimport time
13*20f38712SPatrick Williams
1437c58c8cSGeorge Keishingfrom robot.libraries.BuiltIn import BuiltIn
15de7d408dSCharles Paul Hofer
16de7d408dSCharles Paul Hoferrobot_pgm_dir_path = os.path.dirname(__file__) + os.sep
17*20f38712SPatrick Williamsrepo_data_path = re.sub("/lib", "/data", robot_pgm_dir_path)
18de7d408dSCharles Paul Hofersys.path.append(repo_data_path)
19de7d408dSCharles Paul Hofer
2009679890SGeorge Keishingimport bmc_ssh_utils as bsu  # NOQA
2109679890SGeorge Keishingimport gen_print as gp  # NOQA
22*20f38712SPatrick Williamsimport gen_robot_keyword as keyword  # NOQA
2309679890SGeorge Keishingimport variables as var  # NOQA
2437c58c8cSGeorge Keishing
25c1fa2bc9SCharles Paul Hofer
268469a485SSushil Singhdef get_bmc_firmware(image_type, sw_dict):
278469a485SSushil Singh    r"""
288469a485SSushil Singh    Get the dictionary of image based on image type like either BMC or Host.
298469a485SSushil Singh
308469a485SSushil Singh    Description of argument(s):
318469a485SSushil Singh    image_type                     This value is either BMC update or Host update type.
3216b3c7bfSGeorge Keishing    sw_dict                        This contain dictionary of firmware inventory properties.
338469a485SSushil Singh    """
348469a485SSushil Singh
358469a485SSushil Singh    temp_dict = collections.OrderedDict()
368469a485SSushil Singh    for key, value in sw_dict.items():
37*20f38712SPatrick Williams        if value["image_type"] == image_type:
388469a485SSushil Singh            temp_dict[key] = value
398469a485SSushil Singh        else:
408469a485SSushil Singh            pass
418469a485SSushil Singh    return temp_dict
428469a485SSushil Singh
438469a485SSushil Singh
44096cd565SGunnar Millsdef verify_no_duplicate_image_priorities(image_purpose):
45c1fa2bc9SCharles Paul Hofer    r"""
46c1fa2bc9SCharles Paul Hofer    Check that there are no active images with the same purpose and priority.
47c1fa2bc9SCharles Paul Hofer
48c1fa2bc9SCharles Paul Hofer    Description of argument(s):
49004ad3c9SJoy Onyerikwu    image_purpose                   The purpose that images must have to be
50004ad3c9SJoy Onyerikwu                                    checked for priority duplicates.
51c1fa2bc9SCharles Paul Hofer    """
52c1fa2bc9SCharles Paul Hofer
53c1fa2bc9SCharles Paul Hofer    taken_priorities = {}
54*20f38712SPatrick Williams    _, image_names = keyword.run_key(
55*20f38712SPatrick Williams        "Get Software Objects  " + "version_type=" + image_purpose
56*20f38712SPatrick Williams    )
57c1fa2bc9SCharles Paul Hofer
58c1fa2bc9SCharles Paul Hofer    for image_name in image_names:
59c1fa2bc9SCharles Paul Hofer        _, image = keyword.run_key("Get Host Software Property  " + image_name)
60c1fa2bc9SCharles Paul Hofer        if image["Activation"] != var.ACTIVE:
61c1fa2bc9SCharles Paul Hofer            continue
62c1fa2bc9SCharles Paul Hofer        image_priority = image["Priority"]
63c1fa2bc9SCharles Paul Hofer        if image_priority in taken_priorities:
64*20f38712SPatrick Williams            BuiltIn().fail(
65*20f38712SPatrick Williams                "Found active images with the same priority.\n"
66*20f38712SPatrick Williams                + gp.sprint_vars(image, taken_priorities[image_priority])
67*20f38712SPatrick Williams            )
68c1fa2bc9SCharles Paul Hofer        taken_priorities[image_priority] = image
69c1fa2bc9SCharles Paul Hofer
70c1fa2bc9SCharles Paul Hofer
71da24d0a0SCharles Paul Hoferdef get_non_running_bmc_software_object():
72da24d0a0SCharles Paul Hofer    r"""
73da24d0a0SCharles Paul Hofer    Get the URI to a BMC image from software that is not running on the BMC.
74da24d0a0SCharles Paul Hofer    """
75da24d0a0SCharles Paul Hofer
76da24d0a0SCharles Paul Hofer    # Get the version of the image currently running on the BMC.
77da24d0a0SCharles Paul Hofer    _, cur_img_version = keyword.run_key("Get BMC Version")
78da24d0a0SCharles Paul Hofer    # Remove the surrounding double quotes from the version.
79*20f38712SPatrick Williams    cur_img_version = cur_img_version.replace('"', "")
80da24d0a0SCharles Paul Hofer
81*20f38712SPatrick Williams    _, images = keyword.run_key(
82*20f38712SPatrick Williams        "Read Properties  " + var.SOFTWARE_VERSION_URI + "enumerate"
83*20f38712SPatrick Williams    )
84da24d0a0SCharles Paul Hofer
85da24d0a0SCharles Paul Hofer    for image_name in images:
86da24d0a0SCharles Paul Hofer        _, image_properties = keyword.run_key(
87*20f38712SPatrick Williams            "Get Host Software Property  " + image_name
88*20f38712SPatrick Williams        )
89*20f38712SPatrick Williams        if (
90*20f38712SPatrick Williams            "Purpose" in image_properties
91*20f38712SPatrick Williams            and "Version" in image_properties
92*20f38712SPatrick Williams            and image_properties["Purpose"] != var.VERSION_PURPOSE_HOST
93*20f38712SPatrick Williams            and image_properties["Version"] != cur_img_version
94*20f38712SPatrick Williams        ):
95da24d0a0SCharles Paul Hofer            return image_name
96da24d0a0SCharles Paul Hofer    BuiltIn().fail("Did not find any non-running BMC images.")
97da24d0a0SCharles Paul Hofer
98da24d0a0SCharles Paul Hofer
99de7d408dSCharles Paul Hoferdef delete_all_pnor_images():
100de7d408dSCharles Paul Hofer    r"""
101de7d408dSCharles Paul Hofer    Delete all PNOR images from the BMC.
102de7d408dSCharles Paul Hofer    """
103de7d408dSCharles Paul Hofer
1047eedb1ddSAdriana Kobylak    keyword.run_key("Initiate Host PowerOff")
1057eedb1ddSAdriana Kobylak
106*20f38712SPatrick Williams    status, images = keyword.run_key(
107*20f38712SPatrick Williams        "Get Software Objects  " + var.VERSION_PURPOSE_HOST
108*20f38712SPatrick Williams    )
109de7d408dSCharles Paul Hofer    for image_name in images:
110*20f38712SPatrick Williams        keyword.run_key(
111*20f38712SPatrick Williams            "Delete Image And Verify  "
112*20f38712SPatrick Williams            + image_name
113*20f38712SPatrick Williams            + "  "
114*20f38712SPatrick Williams            + var.VERSION_PURPOSE_HOST
115*20f38712SPatrick Williams        )
116de7d408dSCharles Paul Hofer
117de7d408dSCharles Paul Hofer
118de7d408dSCharles Paul Hoferdef wait_for_activation_state_change(version_id, initial_state):
119de7d408dSCharles Paul Hofer    r"""
120de7d408dSCharles Paul Hofer    Wait for the current activation state of ${version_id} to
121de7d408dSCharles Paul Hofer    change from the state provided by the calling function.
122de7d408dSCharles Paul Hofer
123de7d408dSCharles Paul Hofer    Description of argument(s):
124004ad3c9SJoy Onyerikwu    version_id                      The version ID whose state change we are
125004ad3c9SJoy Onyerikwu                                    waiting for.
126de7d408dSCharles Paul Hofer    initial_state                   The activation state we want to wait for.
127de7d408dSCharles Paul Hofer    """
128de7d408dSCharles Paul Hofer
129de7d408dSCharles Paul Hofer    keyword.run_key_u("Open Connection And Log In")
130de7d408dSCharles Paul Hofer    retry = 0
131290b8bd2SCharles Paul Hofer    num_read_errors = 0
132290b8bd2SCharles Paul Hofer    read_fail_threshold = 1
133*20f38712SPatrick Williams    while retry < 60:
134*20f38712SPatrick Williams        status, software_state = keyword.run_key(
135*20f38712SPatrick Williams            "Read Properties  " + var.SOFTWARE_VERSION_URI + str(version_id),
136*20f38712SPatrick Williams            ignore=1,
137*20f38712SPatrick Williams        )
138*20f38712SPatrick Williams        if status == "FAIL":
139290b8bd2SCharles Paul Hofer            num_read_errors += 1
140290b8bd2SCharles Paul Hofer            if num_read_errors > read_fail_threshold:
141*20f38712SPatrick Williams                message = "Read errors exceeds threshold:\n " + gp.sprint_vars(
142*20f38712SPatrick Williams                    num_read_errors, read_fail_threshold
143*20f38712SPatrick Williams                )
144290b8bd2SCharles Paul Hofer                BuiltIn().fail(message)
1454d26c008SCharles Paul Hofer            time.sleep(10)
146290b8bd2SCharles Paul Hofer            continue
147290b8bd2SCharles Paul Hofer
148de7d408dSCharles Paul Hofer        current_state = (software_state)["Activation"]
149*20f38712SPatrick Williams        if initial_state == current_state:
1504d26c008SCharles Paul Hofer            time.sleep(10)
151de7d408dSCharles Paul Hofer            retry += 1
152290b8bd2SCharles Paul Hofer            num_read_errors = 0
153de7d408dSCharles Paul Hofer        else:
154de7d408dSCharles Paul Hofer            return
155de7d408dSCharles Paul Hofer    return
156de7d408dSCharles Paul Hofer
157de7d408dSCharles Paul Hofer
158de7d408dSCharles Paul Hoferdef get_latest_file(dir_path):
159de7d408dSCharles Paul Hofer    r"""
160de7d408dSCharles Paul Hofer    Get the path to the latest uploaded file.
161de7d408dSCharles Paul Hofer
162de7d408dSCharles Paul Hofer    Description of argument(s):
163004ad3c9SJoy Onyerikwu    dir_path                        Path to the dir from which the name of the
164004ad3c9SJoy Onyerikwu                                    last updated file or folder will be
165004ad3c9SJoy Onyerikwu                                    returned to the calling function.
166de7d408dSCharles Paul Hofer    """
167de7d408dSCharles Paul Hofer
168*20f38712SPatrick Williams    stdout, stderr, rc = bsu.bmc_execute_command(
169*20f38712SPatrick Williams        "cd "
170*20f38712SPatrick Williams        + dir_path
171004ad3c9SJoy Onyerikwu        + "; stat -c '%Y %n' * |"
172*20f38712SPatrick Williams        + " sort -k1,1nr | head -n 1"
173*20f38712SPatrick Williams    )
1749b66897bSJoy Onyerikwu    return stdout.split(" ")[-1]
175de7d408dSCharles Paul Hofer
176de7d408dSCharles Paul Hofer
177de7d408dSCharles Paul Hoferdef get_version_tar(tar_file_path):
178de7d408dSCharles Paul Hofer    r"""
179de7d408dSCharles Paul Hofer    Read the image version from the MANIFEST inside the tarball.
180de7d408dSCharles Paul Hofer
181de7d408dSCharles Paul Hofer    Description of argument(s):
182de7d408dSCharles Paul Hofer    tar_file_path                   The path to a tar file that holds the image
183de7d408dSCharles Paul Hofer                                    version inside the MANIFEST.
184de7d408dSCharles Paul Hofer    """
185de7d408dSCharles Paul Hofer
1863c77d78fSSushil Singh    version = ""
187de7d408dSCharles Paul Hofer    tar = tarfile.open(tar_file_path)
188de7d408dSCharles Paul Hofer    for member in tar.getmembers():
18942ade549SGeorge Keishing        BuiltIn().log_to_console(member.name)
19042ade549SGeorge Keishing        if member.name != "MANIFEST":
19142ade549SGeorge Keishing            continue
192de7d408dSCharles Paul Hofer        f = tar.extractfile(member)
193de7d408dSCharles Paul Hofer        content = f.read()
19436efbc04SGeorge Keishing        if content.find(b"version=") == -1:
19536efbc04SGeorge Keishing            # This tar member does not contain the version.
19636efbc04SGeorge Keishing            continue
19742ade549SGeorge Keishing        content = content.decode("utf-8", "ignore").split("\n")
198de7d408dSCharles Paul Hofer        content = [x for x in content if "version=" in x]
199de7d408dSCharles Paul Hofer        version = content[0].split("=")[-1]
200de7d408dSCharles Paul Hofer        break
201de7d408dSCharles Paul Hofer    tar.close()
202de7d408dSCharles Paul Hofer    return version
203de7d408dSCharles Paul Hofer
204de7d408dSCharles Paul Hofer
205de7d408dSCharles Paul Hoferdef get_image_version(file_path):
206de7d408dSCharles Paul Hofer    r"""
207de7d408dSCharles Paul Hofer    Read the file for a version object.
208de7d408dSCharles Paul Hofer
209de7d408dSCharles Paul Hofer    Description of argument(s):
210004ad3c9SJoy Onyerikwu    file_path                       The path to a file that holds the image
211004ad3c9SJoy Onyerikwu                                    version.
212de7d408dSCharles Paul Hofer    """
213de7d408dSCharles Paul Hofer
214*20f38712SPatrick Williams    stdout, stderr, rc = bsu.bmc_execute_command(
215*20f38712SPatrick Williams        "cat " + file_path + ' | grep "version="', ignore_err=1
216*20f38712SPatrick Williams    )
2179b66897bSJoy Onyerikwu    return (stdout.split("\n")[0]).split("=")[-1]
218de7d408dSCharles Paul Hofer
219de7d408dSCharles Paul Hofer
220de7d408dSCharles Paul Hoferdef get_image_purpose(file_path):
221de7d408dSCharles Paul Hofer    r"""
222de7d408dSCharles Paul Hofer    Read the file for a purpose object.
223de7d408dSCharles Paul Hofer
224de7d408dSCharles Paul Hofer    Description of argument(s):
225004ad3c9SJoy Onyerikwu    file_path                       The path to a file that holds the image
226004ad3c9SJoy Onyerikwu                                    purpose.
227de7d408dSCharles Paul Hofer    """
228de7d408dSCharles Paul Hofer
229*20f38712SPatrick Williams    stdout, stderr, rc = bsu.bmc_execute_command(
230*20f38712SPatrick Williams        "cat " + file_path + ' | grep "purpose="', ignore_err=1
231*20f38712SPatrick Williams    )
2329b66897bSJoy Onyerikwu    return stdout.split("=")[-1]
233de7d408dSCharles Paul Hofer
234de7d408dSCharles Paul Hofer
235de7d408dSCharles Paul Hoferdef get_image_path(image_version):
236de7d408dSCharles Paul Hofer    r"""
237de7d408dSCharles Paul Hofer    Query the upload image dir for the presence of image matching
238de7d408dSCharles Paul Hofer    the version that was read from the MANIFEST before uploading
239de7d408dSCharles Paul Hofer    the image. Based on the purpose verify the activation object
240de7d408dSCharles Paul Hofer    exists and is either READY or INVALID.
241de7d408dSCharles Paul Hofer
242de7d408dSCharles Paul Hofer    Description of argument(s):
243004ad3c9SJoy Onyerikwu    image_version                   The version of the image that should match
244004ad3c9SJoy Onyerikwu                                    one of the images in the upload dir.
245de7d408dSCharles Paul Hofer    """
246de7d408dSCharles Paul Hofer
247*20f38712SPatrick Williams    stdout, stderr, rc = bsu.bmc_execute_command(
248*20f38712SPatrick Williams        "ls -d " + var.IMAGE_UPLOAD_DIR_PATH + "*/"
249*20f38712SPatrick Williams    )
250de7d408dSCharles Paul Hofer
2519b66897bSJoy Onyerikwu    image_list = stdout.split("\n")
252de7d408dSCharles Paul Hofer    retry = 0
253*20f38712SPatrick Williams    while retry < 10:
254de7d408dSCharles Paul Hofer        for i in range(0, len(image_list)):
255de7d408dSCharles Paul Hofer            version = get_image_version(image_list[i] + "MANIFEST")
256*20f38712SPatrick Williams            if version == image_version:
257de7d408dSCharles Paul Hofer                return image_list[i]
258de7d408dSCharles Paul Hofer        time.sleep(10)
259de7d408dSCharles Paul Hofer        retry += 1
260de7d408dSCharles Paul Hofer
261de7d408dSCharles Paul Hofer
262*20f38712SPatrick Williamsdef verify_image_upload(image_version, timeout=3):
263de7d408dSCharles Paul Hofer    r"""
264de7d408dSCharles Paul Hofer    Verify the image was uploaded correctly and that it created
265de7d408dSCharles Paul Hofer    a valid d-bus object. If the first check for the image
266de7d408dSCharles Paul Hofer    fails, try again until we reach the timeout.
267de7d408dSCharles Paul Hofer
268de7d408dSCharles Paul Hofer    Description of argument(s):
2699f74d3afSCharles Paul Hofer    image_version                   The version from the image's manifest file
270e0a81289SGeorge Keishing                                    (e.g. "v2.2-253-g00050f1").
271004ad3c9SJoy Onyerikwu    timeout                         How long, in minutes, to keep trying to
272004ad3c9SJoy Onyerikwu                                    find the image on the BMC. Default is 3 minutes.
273de7d408dSCharles Paul Hofer    """
274de7d408dSCharles Paul Hofer
275de7d408dSCharles Paul Hofer    image_path = get_image_path(image_version)
276de7d408dSCharles Paul Hofer    image_version_id = image_path.split("/")[-2]
277de7d408dSCharles Paul Hofer
278de7d408dSCharles Paul Hofer    keyword.run_key_u("Open Connection And Log In")
279de7d408dSCharles Paul Hofer    image_purpose = get_image_purpose(image_path + "MANIFEST")
280*20f38712SPatrick Williams    if (
281*20f38712SPatrick Williams        image_purpose == var.VERSION_PURPOSE_BMC
282*20f38712SPatrick Williams        or image_purpose == var.VERSION_PURPOSE_HOST
283*20f38712SPatrick Williams    ):
284de7d408dSCharles Paul Hofer        uri = var.SOFTWARE_VERSION_URI + image_version_id
285de7d408dSCharles Paul Hofer        ret_values = ""
286de7d408dSCharles Paul Hofer        for itr in range(timeout * 2):
287*20f38712SPatrick Williams            status, ret_values = keyword.run_key(
288*20f38712SPatrick Williams                "Read Attribute  " + uri + "  Activation"
289*20f38712SPatrick Williams            )
290de7d408dSCharles Paul Hofer
291*20f38712SPatrick Williams            if (
292*20f38712SPatrick Williams                (ret_values == var.READY)
293*20f38712SPatrick Williams                or (ret_values == var.INVALID)
294*20f38712SPatrick Williams                or (ret_values == var.ACTIVE)
295*20f38712SPatrick Williams            ):
296cef6199aSCharles Paul Hofer                return True, image_version_id
297de7d408dSCharles Paul Hofer            else:
298de7d408dSCharles Paul Hofer                time.sleep(30)
299de7d408dSCharles Paul Hofer
300de7d408dSCharles Paul Hofer        # If we exit the for loop, the timeout has been reached
301de7d408dSCharles Paul Hofer        gp.print_var(ret_values)
302cef6199aSCharles Paul Hofer        return False, None
303de7d408dSCharles Paul Hofer    else:
304de7d408dSCharles Paul Hofer        gp.print_var(image_purpose)
305cef6199aSCharles Paul Hofer        return False, None
306de7d408dSCharles Paul Hofer
307de7d408dSCharles Paul Hofer
308de7d408dSCharles Paul Hoferdef verify_image_not_in_bmc_uploads_dir(image_version, timeout=3):
309de7d408dSCharles Paul Hofer    r"""
310de7d408dSCharles Paul Hofer    Check that an image with the given version is not unpacked inside of the
311de7d408dSCharles Paul Hofer    BMCs image uploads directory. If no image is found, retry every 30 seconds
312de7d408dSCharles Paul Hofer    until the given timeout is hit, in case the BMC takes time
313de7d408dSCharles Paul Hofer    unpacking the image.
314de7d408dSCharles Paul Hofer
315de7d408dSCharles Paul Hofer    Description of argument(s):
316004ad3c9SJoy Onyerikwu    image_version                   The version of the image to look for on
317004ad3c9SJoy Onyerikwu                                    the BMC.
318004ad3c9SJoy Onyerikwu    timeout                         How long, in minutes, to try to find an
319004ad3c9SJoy Onyerikwu                                    image on the BMC. Default is 3 minutes.
320de7d408dSCharles Paul Hofer    """
321de7d408dSCharles Paul Hofer
322de7d408dSCharles Paul Hofer    for i in range(timeout * 2):
323*20f38712SPatrick Williams        stdout, stderr, rc = bsu.bmc_execute_command(
324*20f38712SPatrick Williams            "ls "
325*20f38712SPatrick Williams            + var.IMAGE_UPLOAD_DIR_PATH
326*20f38712SPatrick Williams            + "*/MANIFEST 2>/dev/null "
327004ad3c9SJoy Onyerikwu            + '| xargs grep -rl "version='
328*20f38712SPatrick Williams            + image_version
329*20f38712SPatrick Williams            + '"'
330*20f38712SPatrick Williams        )
331*20f38712SPatrick Williams        image_dir = os.path.dirname(stdout.split("\n")[0])
332*20f38712SPatrick Williams        if "" != image_dir:
333*20f38712SPatrick Williams            bsu.bmc_execute_command("rm -rf " + image_dir)
334*20f38712SPatrick Williams            BuiltIn().fail("Found invalid BMC Image: " + image_dir)
335de7d408dSCharles Paul Hofer        time.sleep(30)
336