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