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