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