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