PNOR Code Update test implementation

Resolves openbmc/openbmc-test-automation#572

Change-Id: Idc1f31bdc6435e5d36f93b79de5e50a32a524bb3
Signed-off-by: Saqib Khan <khansa@us.ibm.com>
diff --git a/data/variables.py b/data/variables.py
index feed8bc..972a69f 100644
--- a/data/variables.py
+++ b/data/variables.py
@@ -42,8 +42,23 @@
 BMC_LOGGING_ENTRY = BMC_LOGGING_URI + 'entry/'
 
 # Software manager version
-SOFTWARE_VERSION_URI = '/xyz/openbmc_project/software/'
+SOFTWARE_VERSION = '/xyz/openbmc_project/software/'
 ACTIVE = 'xyz.openbmc_project.Software.Activation.Activations.Active'
+READY = 'xyz.openbmc_project.Software.Activation.Activations.Ready'
+INVALID = 'xyz.openbmc_project.Software.Activation.Activations.Invalid'
+ACTIVATING = 'xyz.openbmc_project.Software.Activation.Activations.Activating'
+NOTREADY = 'xyz.openbmc_project.Software.Activation.Activations.NotReady'
+FAILED = 'xyz.openbmc_project.Software.Activation.Activations.Failed'
+
+SOFTWARE_ACTIVATION = 'xyz.openbmc_project.Software.Activation'
+REQUESTED_ACTIVATION = SOFTWARE_ACTIVATION + '.RequestedActivations'
+REQUESTED_ACTIVE = REQUESTED_ACTIVATION + '.Active'
+REQUESTED_NONE = REQUESTED_ACTIVATION + '.None'
+
+SOFTWARE_PURPOSE = 'xyz.openbmc_project.Software.Version.VersionPurpose'
+VERSION_PURPOSE_HOST = SOFTWARE_PURPOSE + '.Host'
+VERSION_PURPOSE_BMC = SOFTWARE_PURPOSE + '.BMC'
+VERSION_PURPOSE_SYSTEM = SOFTWARE_PURPOSE + '.System'
 
 # Inventory URI
 HOST_INVENTORY_URI = '/xyz/openbmc_project/inventory/'
diff --git a/extended/code_update/code_update.py b/extended/code_update/code_update.py
new file mode 100644
index 0000000..5f29936
--- /dev/null
+++ b/extended/code_update/code_update.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+
+r"""
+This module is the python counterpart to code_update.robot.
+"""
+
+import os
+import sys
+import re
+import string
+import tarfile
+import time
+
+robot_pgm_dir_path = os.path.dirname(__file__) + os.sep
+repo_lib_path = re.sub('/extended/code_update/', '/lib', robot_pgm_dir_path)
+repo_data_path = re.sub('/extended/code_update/', '/data', robot_pgm_dir_path)
+sys.path.append(repo_lib_path)
+sys.path.append(repo_data_path)
+
+import gen_robot_keyword as keyword
+import gen_print as gp
+import gen_valid as gv
+import variables as var
+from robot.libraries.BuiltIn import BuiltIn
+
+
+###############################################################################
+def wait_for_activation_state_change(version_id, initial_state):
+
+    r"""
+    Wait for the current activation state of ${version_id} to
+    change from the state provided by the calling function.
+
+    Description of argument(s):
+    version_id     The version ID whose state change we are waiting for.
+    initial_state  The activation state we want to wait for.
+    """
+
+    keyword.run_key_u("Open Connection And Log In")
+    retry = 0
+    while (retry < 20):
+        status, software_state = keyword.run_key("Read Properties  " +
+                                        var.SOFTWARE_VERSION + str(version_id))
+        current_state = (software_state)["Activation"]
+        if (initial_state == current_state):
+            time.sleep(60)
+            retry += 1
+        else:
+            return
+    return
+
+###############################################################################
diff --git a/extended/code_update/code_update.robot b/extended/code_update/code_update.robot
new file mode 100644
index 0000000..1c9e4f4
--- /dev/null
+++ b/extended/code_update/code_update.robot
@@ -0,0 +1,90 @@
+*** Settings ***
+Documentation     Code update to a target BMC.
+...               Execution Method:
+...               python -m robot -v OPENBMC_HOST:<hostname>
+...               -v IMAGE_FILE_PATH:<path/*.tar>  code_update.robot
+...
+...               Code update method BMC
+...               Update work flow sequence:
+...                 - Upload image via REST
+...                 - Verify that the file exists on the BMC
+...                 - Check software "Activation" status to be "Ready"
+...                 - Set "Requested Activation" to "Active"
+...                 - Wait for code update to complete
+...                 - Verify the new version
+
+#TODO: Move test_uploadimage.py to lib/
+Library           ../test_uploadimage.py
+Library           code_update.py
+Library           OperatingSystem
+Variables         ../../data/variables.py
+Resource          code_update_utils.robot
+Resource          ../lib/rest_client.robot
+Resource          ../lib/openbmc_ffdc.robot
+
+Test Teardown     Code Update Teardown
+
+*** Variables ***
+
+${QUIET}                          ${1}
+${version_id}                     ${EMPTY}
+${upload_dir_path}                /tmp/images/
+${image_version}                  ${EMPTY}
+${image_purpose}                  ${EMPTY}
+${activation_state}               ${EMPTY}
+${requested_state}                ${EMPTY}
+${IMAGE_FILE_PATH}                ${EMPTY}
+
+*** Test Cases ***
+
+REST PNOR Code Update
+    [Documentation]  Do a PNOR code update by uploading image on BMC via REST.
+    [Tags]  REST_PNOR_Code_Update
+
+    OperatingSystem.File Should Exist  ${IMAGE_FILE_PATH}
+    ${IMAGE_VERSION}=  Get Version Tar  ${IMAGE_FILE_PATH}
+
+    ${image_data}=  OperatingSystem.Get Binary File  ${IMAGE_FILE_PATH}
+    Upload Image To BMC  /upload/image  data=${image_data}
+    ${ret}=  Verify Image Upload
+    Should Be True  ${ret}
+
+    # Verify the image is 'READY' to be activated.
+    ${software_state}=  Read Properties  ${SOFTWARE_VERSION}${version_id}
+    Should Be Equal As Strings  &{software_state}[Activation]  ${READY}
+
+    # Request the image to be activated.
+    ${args}=  Create Dictionary  data=${REQUESTED_ACTIVE}
+    Write Attribute  ${SOFTWARE_VERSION}${version_id}
+    ...  RequestedActivation  data=${args}
+    ${software_state}=  Read Properties  ${SOFTWARE_VERSION}${version_id}
+    Should Be Equal As Strings  &{software_state}[RequestedActivation]
+    ...  ${REQUESTED_ACTIVE}
+
+    # Verify code update was successful and Activation state is Active.
+    Wait For Activation State Change  ${version_id}  ${ACTIVATING}
+    ${software_state}=  Read Properties  ${SOFTWARE_VERSION}${version_id}
+    Should Be Equal As Strings  &{software_state}[Activation]  ${ACTIVE}
+
+*** Keywords ***
+
+Code Update Teardown
+    [Documentation]  Do code update test case teardown.
+
+    #TODO: Use the Delete interface instead once delivered
+    Open Connection And Log In
+    Execute Command On BMC  rm -rf /tmp/images/*
+
+    Close All Connections
+    FFDC On Test Case Fail
+
+Get PNOR Extended Version
+    [Documentation]  Return the PNOR extended version.
+    ...              Description of arguments:
+    ...              path  Path of the MANIFEST file
+    [Arguments]      ${path}
+
+    Open Connection And Log In
+    ${version}= Execute Command On BMC
+    ...  "grep \"extended_version=\" " + ${path}
+    [return] ${version.split(",")}
diff --git a/extended/test_uploadimage.py b/extended/test_uploadimage.py
index e4ac71b..024c4f0 100644
--- a/extended/test_uploadimage.py
+++ b/extended/test_uploadimage.py
@@ -83,8 +83,8 @@
     grk.run_key_u("Open Connection And Log In")
     status, ret_values =\
             grk.run_key("Execute Command On BMC  cat "
-            + file_path + " | grep \"version=\"")
-    return ret_values.split("=")[-1]
+            + file_path + " | grep \"version=\"", ignore=1)
+    return (ret_values.split("\n")[0]).split("=")[-1]
 
 ###############################################################################
 
@@ -102,7 +102,7 @@
     grk.run_key_u("Open Connection And Log In")
     status, ret_values =\
             grk.run_key("Execute Command On BMC  cat "
-            + file_path + " | grep \"purpose=\"")
+            + file_path + " | grep \"purpose=\"", ignore=1)
     return ret_values.split("=")[-1]
 
 ###############################################################################
@@ -122,7 +122,7 @@
                      of the images in the upload dir.
     """
 
-    upload_dir = BuiltIn().get_variable_value("${UPLOAD_DIR_PATH}")
+    upload_dir = BuiltIn().get_variable_value("${upload_dir_path}")
     grk.run_key_u("Open Connection And Log In")
     status, image_list =\
             grk.run_key("Execute Command On BMC  ls -d " + upload_dir
@@ -149,14 +149,16 @@
     a valid d-bus object
     """
 
-    image_version = BuiltIn().get_variable_value("${IMAGE_VERSION}")
+    image_version = BuiltIn().get_variable_value("${image_version}")
     image_path = get_image_path(image_version)
     image_version_id = image_path.split("/")[-2]
+    BuiltIn().set_global_variable("${version_id}", image_version_id)
 
     grk.run_key_u("Open Connection And Log In")
     image_purpose = get_image_purpose(image_path + "MANIFEST")
-    if (image_purpose == "bmc" or image_purpose == "host"):
-        uri = var.SOFTWARE_VERSION_URI + "/" + image_version_id
+    if (image_purpose == var.VERSION_PURPOSE_BMC or
+        image_purpose == var.VERSION_PURPOSE_HOST):
+        uri = var.SOFTWARE_VERSION + image_version_id
         status, ret_values =\
         grk.run_key("Read Attribute  " + uri + "  Activation")
 
@@ -167,7 +169,7 @@
             gp.print_var(ret_values)
             return False
     else:
-        gp.print_var(versionPurpose)
+        gp.print_var(image_purpose)
         return False
 
 ###############################################################################
diff --git a/extended/test_uploadimage.robot b/extended/test_uploadimage.robot
index 008596c..3b50c46 100644
--- a/extended/test_uploadimage.robot
+++ b/extended/test_uploadimage.robot
@@ -24,9 +24,9 @@
 
 *** Variables ***
 ${timeout}            10
-${UPLOAD_DIR_PATH}    /tmp/images/
+${upload_dir_path}    /tmp/images/
 ${QUIET}              ${1}
-${IMAGE_VERSION}      ${EMPTY}
+${image_version}      ${EMPTY}
 
 *** Test Cases ***
 
@@ -37,7 +37,7 @@
     OperatingSystem.File Should Exist  ${IMAGE_FILE_PATH}
     ${IMAGE_VERSION}=  Get Version Tar  ${IMAGE_FILE_PATH}
     ${image_data}=  OperatingSystem.Get Binary File  ${IMAGE_FILE_PATH}
-    Upload Post Request  /upload/image  data=${image_data}
+    Upload Image To BMC  /upload/image  data=${image_data}
     ${ret}=  Verify Image Upload
     Should Be True  True == ${ret}
 
@@ -48,12 +48,12 @@
     @{image}=  Create List  ${TFTP_FILE_NAME}  ${TFTP_SERVER}
     ${data}=  Create Dictionary  data=@{image}
     ${resp}=  OpenBMC Post Request
-    ...  ${SOFTWARE_VERSION_URI}/action/DownloadViaTFTP  data=${data}
+    ...  ${SOFTWARE_VERSION}/action/DownloadViaTFTP  data=${data}
     Should Be Equal As Strings  ${resp.status_code}  ${HTTP_OK}
     Sleep  1 minute
-    ${upload_file}=  Get Latest File  ${UPLOAD_DIR_PATH}
-    ${IMAGE_VERSION}=  Get Image Version
-    ...  ${UPLOAD_DIR_PATH}${upload_file}/MANIFEST
+    ${upload_file}=  Get Latest File  ${upload_dir_path}
+    ${image_version}=  Get Image Version
+    ...  ${upload_dir_path}${upload_file}/MANIFEST
     ${ret}=  Verify Image Upload
     Should Be True  True == ${ret}
 
@@ -116,12 +116,15 @@
 *** Keywords ***
 
 Upload Image Teardown
-    [Documentation]  Log FFDC if test suite fails and collect SOL log for
-    ...              debugging purposes.
+    [Documentation]  Log FFDC if test fails for debugging purposes.
+
+    Open Connection And Log In
+    Execute Command On BMC  rm -rf /tmp/images/*
 
     Close All Connections
     FFDC On Test Case Fail
 
+
 Upload Post Request
     [Arguments]  ${uri}  ${timeout}=10  ${quiet}=${QUIET}  &{kwargs}
 
@@ -159,4 +162,3 @@
     ${version}=  Get Version Tar  bad_image.tar
     OperatingSystem.Remove File  bad_image.tar
     [Return]  ${version}
-
diff --git a/lib/rest_client.robot b/lib/rest_client.robot
index ff3142a..b3c0056 100644
--- a/lib/rest_client.robot
+++ b/lib/rest_client.robot
@@ -196,3 +196,25 @@
     ${resp}=  OpenBmc Post Request  ${base_uri}/action/${method}
     ...  timeout=${timeout}  quiet=${quiet}  &{kwargs}
     [Return]     ${resp}
+
+Upload Image To BMC
+    [Arguments]  ${uri}  ${timeout}=10  ${quiet}=${QUIET}  &{kwargs}
+
+    # Description of argument(s):
+    # uri             URI for uploading image via REST e.g. "/upload/image".
+    # timeout         Time allocated for the REST command to return status
+    #                 (specified in Robot Framework Time Format e.g. "3 mins").
+    # quiet           If enabled turns off logging to console.
+    # kwargs          A dictionary keys/values to be passed directly to
+    #                 Post Request.
+
+    Initialize OpenBMC  ${timeout}  quiet=${quiet}
+    ${base_uri}=  Catenate  SEPARATOR=  ${DBUS_PREFIX}  ${uri}
+    ${headers}=  Create Dictionary  Content-Type=application/octet-stream
+    ...  Accept=application/octet-stream
+    Set To Dictionary  ${kwargs}  headers  ${headers}
+    Run Keyword If  '${quiet}' == '${0}'  Log Request  method=Post
+    ...  base_uri=${base_uri}  args=&{kwargs}
+    ${ret}=  Post Request  openbmc  ${base_uri}  &{kwargs}  timeout=${timeout}
+    Run Keyword If  '${quiet}' == '${0}'  Log Response  ${ret}
+    Should Be Equal As Strings  ${ret.status_code}  ${HTTP_OK}