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 gen_robot_keyword as keyword
18import gen_print as gp
19import variables as var
20from robot.libraries.BuiltIn import BuiltIn
21
22
23def verify_no_duplicate_image_priorities(image_purpose):
24    r"""
25    Check that there are no active images with the same purpose and priority.
26
27    Description of argument(s):
28    image_purpose  The purpose that images must have to be checked for
29                   priority duplicates.
30    """
31
32    taken_priorities = {}
33    _, image_names = keyword.run_key("Get Software Objects  "
34                                     + "version_type=" + image_purpose)
35
36    for image_name in image_names:
37        _, image = keyword.run_key("Get Host Software Property  " + image_name)
38        if image["Activation"] != var.ACTIVE:
39            continue
40        image_priority = image["Priority"]
41        if image_priority in taken_priorities:
42            BuiltIn().fail("Found active images with the same priority.\n"
43                           + gp.sprint_vars(image, taken_priorities[image_priority]))
44        taken_priorities[image_priority] = image
45
46
47def get_non_running_bmc_software_object():
48    r"""
49    Get the URI to a BMC image from software that is not running on the BMC.
50    """
51
52    # Get the version of the image currently running on the BMC.
53    _, cur_img_version = keyword.run_key("Get BMC Version")
54    # Remove the surrounding double quotes from the version.
55    cur_img_version = cur_img_version.replace('"', '')
56
57    _, images = keyword.run_key("Read Properties  "
58                                + var.SOFTWARE_VERSION_URI + "enumerate")
59
60    for image_name in images:
61        _, image_properties = keyword.run_key(
62            "Get Host Software Property  " + image_name)
63        if 'Purpose' in image_properties and 'Version' in image_properties \
64                and image_properties['Purpose'] != var.VERSION_PURPOSE_HOST \
65                and image_properties['Version'] != cur_img_version:
66            return image_name
67    BuiltIn().fail("Did not find any non-running BMC images.")
68
69
70def delete_all_pnor_images():
71    r"""
72    Delete all PNOR images from the BMC.
73    """
74
75    status, images = keyword.run_key("Get Software Objects  "
76                                     + var.VERSION_PURPOSE_HOST)
77    for image_name in images:
78        BuiltIn().log_to_console(image_name)
79        # Delete twice, in case the image is in the /tmp/images directory
80        keyword.run_key("Call Method  " + image_name
81                        + "  delete  data={\"data\":[]}")
82        keyword.run_key("Call Method  " + image_name
83                        + "  delete  data={\"data\":[]}")
84
85
86def wait_for_activation_state_change(version_id, initial_state):
87    r"""
88    Wait for the current activation state of ${version_id} to
89    change from the state provided by the calling function.
90
91    Description of argument(s):
92    version_id     The version ID whose state change we are waiting for.
93    initial_state  The activation state we want to wait for.
94    """
95
96    keyword.run_key_u("Open Connection And Log In")
97    retry = 0
98    num_read_errors = 0
99    read_fail_threshold = 1
100    while (retry < 30):
101        # TODO: Use retry option in run_key when available.
102        status, software_state = keyword.run_key("Read Properties  " +
103                                                 var.SOFTWARE_VERSION_URI +
104                                                 str(version_id),
105                                                 ignore=1)
106        if status == 'FAIL':
107            num_read_errors += 1
108            if num_read_errors > read_fail_threshold:
109                message = "Read errors exceeds threshold:\n " \
110                    + gp.sprint_vars(num_read_errors, read_fail_threshold)
111                BuiltIn().fail(message)
112            time.sleep(10)
113            continue
114
115        current_state = (software_state)["Activation"]
116        if (initial_state == current_state):
117            time.sleep(10)
118            retry += 1
119            num_read_errors = 0
120        else:
121            return
122    return
123
124
125def get_latest_file(dir_path):
126    r"""
127    Get the path to the latest uploaded file.
128
129    Description of argument(s):
130    dir_path    Path to the dir from which the name of the last
131                updated file or folder will be returned to the
132                calling function.
133    """
134
135    keyword.run_key_u("Open Connection And Log In")
136    status, ret_values =\
137        keyword.run_key("Execute Command On BMC  cd " + dir_path
138                        + "; stat -c '%Y %n' * | sort -k1,1nr | head -n 1", ignore=1)
139    return ret_values.split(" ")[-1]
140
141
142def get_version_tar(tar_file_path):
143    r"""
144    Read the image version from the MANIFEST inside the tarball.
145
146    Description of argument(s):
147    tar_file_path    The path to a tar file that holds the image
148                     version inside the MANIFEST.
149    """
150
151    tar = tarfile.open(tar_file_path)
152    for member in tar.getmembers():
153        f = tar.extractfile(member)
154        content = f.read()
155        if "version=" in content:
156            content = content.split("\n")
157            content = [x for x in content if "version=" in x]
158            version = content[0].split("=")[-1]
159            break
160    tar.close()
161    return version
162
163
164def get_image_version(file_path):
165    r"""
166    Read the file for a version object.
167
168    Description of argument(s):
169    file_path    The path to a file that holds the image version.
170    """
171
172    keyword.run_key_u("Open Connection And Log In")
173    status, ret_values =\
174        keyword.run_key("Execute Command On BMC  cat "
175                        + file_path + " | grep \"version=\"", ignore=1)
176    return (ret_values.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    keyword.run_key_u("Open Connection And Log In")
188    status, ret_values =\
189        keyword.run_key("Execute Command On BMC  cat "
190                        + file_path + " | grep \"purpose=\"", ignore=1)
191    return ret_values.split("=")[-1]
192
193
194def get_image_path(image_version):
195    r"""
196    Query the upload image dir for the presence of image matching
197    the version that was read from the MANIFEST before uploading
198    the image. Based on the purpose verify the activation object
199    exists and is either READY or INVALID.
200
201    Description of argument(s):
202    image_version    The version of the image that should match one
203                     of the images in the upload dir.
204    """
205
206    keyword.run_key_u("Open Connection And Log In")
207    status, image_list =\
208        keyword.run_key("Execute Command On BMC  ls -d "
209                        + var.IMAGE_UPLOAD_DIR_PATH + "*/")
210
211    image_list = image_list.split("\n")
212    retry = 0
213    while (retry < 10):
214        for i in range(0, len(image_list)):
215            version = get_image_version(image_list[i] + "MANIFEST")
216            if (version == image_version):
217                return image_list[i]
218        time.sleep(10)
219        retry += 1
220
221
222def verify_image_upload(image_version,
223                        timeout=3):
224    r"""
225    Verify the image was uploaded correctly and that it created
226    a valid d-bus object. If the first check for the image
227    fails, try again until we reach the timeout.
228
229    Description of argument(s):
230    image_version  The version from the image's manifest file
231                   (e.g. "IBM-witherspoon-redbud-ibm-OP9_v1.17_1.68").
232    timeout        How long, in minutes, to keep trying to find the
233                   image on the BMC. Default is 3 minutes.
234    """
235
236    image_path = get_image_path(image_version)
237    image_version_id = image_path.split("/")[-2]
238
239    keyword.run_key_u("Open Connection And Log In")
240    image_purpose = get_image_purpose(image_path + "MANIFEST")
241    if (image_purpose == var.VERSION_PURPOSE_BMC or
242            image_purpose == var.VERSION_PURPOSE_HOST):
243        uri = var.SOFTWARE_VERSION_URI + image_version_id
244        ret_values = ""
245        for itr in range(timeout * 2):
246            status, ret_values = \
247                keyword.run_key("Read Attribute  " + uri + "  Activation")
248
249            if ((ret_values == var.READY) or (ret_values == var.INVALID)
250                    or (ret_values == var.ACTIVE)):
251                return True, image_version_id
252            else:
253                time.sleep(30)
254
255        # If we exit the for loop, the timeout has been reached
256        gp.print_var(ret_values)
257        return False, None
258    else:
259        gp.print_var(image_purpose)
260        return False, None
261
262
263def verify_image_not_in_bmc_uploads_dir(image_version, timeout=3):
264    r"""
265    Check that an image with the given version is not unpacked inside of the
266    BMCs image uploads directory. If no image is found, retry every 30 seconds
267    until the given timeout is hit, in case the BMC takes time
268    unpacking the image.
269
270    Description of argument(s):
271    image_version  The version of the image to look for on the BMC.
272    timeout        How long, in minutes, to try to find an image on the BMC.
273                   Default is 3 minutes.
274    """
275
276    keyword.run_key('Open Connection And Log In')
277    for i in range(timeout * 2):
278        stat, grep_res = keyword.run_key('Execute Command On BMC  '
279                                         + 'ls ' + var.IMAGE_UPLOAD_DIR_PATH + '*/MANIFEST 2>/dev/null '
280                                         + '| xargs grep -rl "version=' + image_version + '"')
281        image_dir = os.path.dirname(grep_res.split('\n')[0])
282        if '' != image_dir:
283            keyword.run_key('Execute Command On BMC  rm -rf ' + image_dir)
284            BuiltIn().fail('Found invalid BMC Image: ' + image_dir)
285        time.sleep(30)
286