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