Test to do repeated, random boots

Utilizes additional keywords:
    Validate Connection
    Intersect Lists
    Search List

Change-Id: I29a2a4805bb685a0519d4e0f7eec5ab1568edca0
Signed-off-by: Gunnar Mills <gmills@us.ibm.com>
diff --git a/lib/connection_client.robot b/lib/connection_client.robot
index 5616071..7e88882 100755
--- a/lib/connection_client.robot
+++ b/lib/connection_client.robot
@@ -68,3 +68,69 @@
 
     ${https_num}=   Convert To Integer    ${HTTPS_PORT}
     Set Global Variable     ${AUTH_URI}    https://${OPENBMC_HOST}:${https_num}
+
+Validate Or Open Connection
+    [Documentation]  Checks for an open connection to a host or alias.
+    [Arguments]  ${alias}=None  ${host}=${EMPTY}  &{connection_args}
+
+    # alias            The alias of the connection to validate.
+    # host             The DNS name or IP of the host to validate.
+    # connection_args  A dictionary of arguments to pass to Open Conection
+    #                  and Log In (see above) if the connection is not open. May
+    #                  contain, but does not need to contain, the host or alias.
+
+    # Check to make sure we have an alias or host to search for.
+    Run Keyword If  '${host}' == '${EMPTY}'  Should Not Be Equal  ${alias}  None
+    ...  msg=Need to provide a host or an alias.  values=False
+
+    # Search the dictionary to see if it includes the host and alias.
+    ${host_exists}=  Run Keyword and Return Status
+    ...              Dictionary Should Contain Key  ${connection_args}  host
+    ${alias_exists}=  Run Keyword and Return Status
+    ...               Dictionary Should Contain Key  ${connection_args}  alias
+
+    # Add the alias and host back into the dictionary of connection arguments,
+    # if needed.
+    Run Keyword If  '${host}' != '${EMPTY}' and ${host_exists} == ${FALSE}
+    ...             Set to Dictionary  ${connection_args}  host  ${host}
+    Run Keyword If  '${alias}' != 'None' and ${alias_exists} == ${FALSE}
+    ...             Set to Dictionary  ${connection_args}  alias  ${alias}
+
+    @{open_connections}=  Get Connections
+    # If there are no open connections, open one and return.
+    Run Keyword If  '${open_connections}' == '[]'
+    ...             Open Connection and Log In  &{connection_args}
+    Return From Keyword If  '${open_connections}' == '[]'
+
+    # Connect to the alias or host that matches. If both are given, only connect
+    # to a connection that has both.
+    :FOR  ${connection}  IN  @{open_connections}
+    \  Log  ${connection}
+    \  ${alias_match}=  Evaluate  '${alias}' == '${connection.alias}'
+    \  ${host_match}=  Evaluate  '${host}' == '${connection.host}'
+    \  ${given_alias}=  Evaluate  '${alias}' != 'None'
+    \  ${no_alias}=  Evaluate  '${alias}' == 'None'
+    \  ${given_host}=  Evaluate  '${host}' != '${EMPTY}'
+    \  ${no_host}=  Evaluate  '${host}' == '${EMPTY}'
+    \  Run Keyword If
+    ...    ${given_alias} and ${given_host} and ${alias_match} and ${host_match}
+    ...    Run Keywords
+    ...      Switch Connection  ${alias}  AND
+    ...      Log to Console  Found connection. Switched to ${alias} ${host}  AND
+    ...      Return From Keyword If  ${alias_match} and ${host_match}
+    ...    ELSE  Run Keyword If
+    ...      ${given_alias} and ${no_host} and ${alias_match}
+    ...      Run Keywords
+    ...        Switch Connection  ${alias}  AND
+    ...        Log to Console  Found connection. Switched to: ${alias}  AND
+    ...        Return From Keyword If  ${alias_match}
+    ...    ELSE  Run Keyword If
+    ...       ${given_host} and ${no_alias} and ${host_match}
+    ...       Run Keywords
+    ...         Switch Connection  ${connection.index}  AND
+    ...         Log to Console  Found Connection. Switched to: ${host}  AND
+    ...         Return From Keyword If  ${host_match}
+
+    # If no connections are found, open a connection with the provided args.
+    Log  No connection with provided arguments.  Opening a connection.
+    Open Connection and Log In  &{connection_args}
diff --git a/lib/dvt/obmc_call_points.robot b/lib/dvt/obmc_call_points.robot
new file mode 100644
index 0000000..1d8db7c
--- /dev/null
+++ b/lib/dvt/obmc_call_points.robot
@@ -0,0 +1,36 @@
+*** Settings ***
+Documentation  This module contains keywords within tests/obmc_boot_test that
+...  are points at which to call plug-ins.
+
+Resource  obmc_driver_vars.txt
+
+*** Keywords ***
+Call Point Setup
+    [Documentation]  Call any plugins that have a cp_setup program
+    [Teardown]  Log to Console  **Plugin** end call point: cp_setup${\n}
+
+    Log to Console  ${\n}**Plugin** start call point: cp_setup
+
+Call Point Pre Boot
+    [Documentation]  Call any plugins that have a cp_pre_boot program
+    [Teardown]  Log to Console  **Plugin** end call point: cp_pre_boot${\n}
+
+    Log to Console  ${\n}**Plugin** start call point: cp_pre_boot
+
+Call Point Post Boot
+    [Documentation]  Call any plugins that have a cp_post_boot program
+    [Teardown]  Log to Console  **Plugin** end call point: cp_post_boot${\n}
+
+    Log to Console  ${\n}**Plugin** start call point: cp_post_boot
+
+Call Point FFDC
+    [Documentation]  Call any plugins that have a cp_ffdc program
+    [Teardown]  Log to Console  **Plugin** end call point: cp_ffdc${\n}
+
+    Log to Console  ${\n}**Plugin** start call point: cp_ffdc
+
+Call Point Stop Check
+    [Documentation]  Call any plugins that have a cp_stop_check program
+    [Teardown]  Log to Console  **Plugin** end call point: cp_stop_check${\n}
+
+    Log to Console  ${\n}**Plugin** start call point: cp_stop_check
diff --git a/lib/dvt/obmc_driver_vars.txt b/lib/dvt/obmc_driver_vars.txt
new file mode 100644
index 0000000..41bc6eb
--- /dev/null
+++ b/lib/dvt/obmc_driver_vars.txt
@@ -0,0 +1,35 @@
+*** Settings ***
+Documentation  This file includes all the varaibles used by obmc_boot_test.robot
+
+*** Variables ***
+# Alias of the master connection to the BMC
+${master_alias}  master
+
+# The count of the IPL we're currently doing - used in the FOR loop
+${IPL_COUNT}  ${0}
+
+# The total number of IPLs we plan on doing
+${IPL_TOTAL}  ${3}
+
+# The total number of IPLs that have passed and failed
+${IPL_PASSED}  ${0}
+${IPL_FAILED}  ${0}
+
+# The status of the last IPL that finished
+${IPL_STATUS}  ${EMPTY}
+
+# A list of the last 10 IPLs that have been performed (see Log Last Ten)
+@{LAST_TEN}
+
+# A list of keywords of valid IPLs that can be performed
+@{VALID_POWER_ON}  BMC Power On
+@{VALID_POWER_OFF}  BMC Power Off
+#@{VALID_ACCYCLE}
+#@{VALID_REBOOT}
+
+# The master list of all IPLs possible, for reference. (Currently not in use).
+@{MASTER_IPL_LIST}  BMC Power On  BMC Power Off
+
+# The list of available IPLs - Modifying this will limit what IPLs can be done.
+# This list is also used by: Setup Run Table, Log Run Table
+@{AVAIL_IPLS}  BMC Power On  BMC Power Off
diff --git a/lib/list_utils.robot b/lib/list_utils.robot
new file mode 100644
index 0000000..472294b
--- /dev/null
+++ b/lib/list_utils.robot
@@ -0,0 +1,32 @@
+*** Settings ***
+Documentation  This module contains keywords for list manipulation.
+Library  Collections
+
+*** Keywords ***
+Intersect Lists
+    [Documentation]  Intersects the two lists passed in. Returns a list of
+    ...  values common to both lists with no duplicates.
+    [Arguments]  ${list1}  ${list2}
+
+    # list1      The first list to intersect.
+    # list2      The second list to intersect.
+
+    ${length1}=  Get Length  ${list1}
+    ${length2}=  Get Length  ${list2}
+
+    @{intersected_list}  Create List
+
+    @{larger_list}=  Set Variable If  ${length1} >= ${length2}  ${list1}
+    ...                               ${length1} < ${length2}  ${list2}
+    @{smaller_list}=  Set Variable If  ${length1} >= ${length2}  ${list2}
+    ...                                ${length1} < ${length2}  ${list1}
+
+    :FOR  ${element}  IN  @{larger_list}
+    \  ${rc}=  Run Keyword and Return Status  List Should Contain Value  ${smaller_list}
+    ...  ${element}
+    \  Run Keyword If  '${rc}' == 'True'  Append to List  ${intersected_list}
+    ...  ${element}
+
+    @{intersected_list}=  Remove Duplicates  ${intersected_list}
+
+    [return]  @{intersected_list}
diff --git a/tests/obmc_boot_test.robot b/tests/obmc_boot_test.robot
new file mode 100644
index 0000000..b7664bd
--- /dev/null
+++ b/tests/obmc_boot_test.robot
@@ -0,0 +1,186 @@
+*** Settings ***
+Documentation  Does random repeated IPLs based on the state of the machine. The
+...  number of repetitions is designated by ${IPL_TOTAL}. Keyword names that are
+...  listed in @{AVAIL_IPLS} become the selection of possible IPLs for the test.
+
+Resource  ../lib/boot/boot_resource_master.robot
+Resource  ../lib/dvt/obmc_call_points.robot
+Resource  ../lib/dvt/obmc_driver_vars.txt
+Resource  ../lib/list_utils.robot
+
+*** Test Cases ***
+Repeated Testing
+    [Documentation]  Performs random, repeated IPLs.
+
+    # Call the Main keyword to prevent any dots from appearing in the console
+    # due to top level keywords.
+    Main
+
+*** Keywords ***
+Main
+    Log to Console  ${SUITE NAME}
+
+    Do Test Setup
+    Call Point Setup
+
+    Log  Doing ${IPL_TOTAL} IPLs  console=True
+
+    :FOR  ${IPL_COUNT}  IN RANGE  ${IPL_TOTAL}
+    \  Log  ${\n}***Starting IPL ${IPL_COUNT+1} of ${IPL_TOTAL}***  console=True
+    \  Validate Or Open Connection  alias=${master_alias}
+    \  ${cur_state}=  Get Power State
+    \  ${next_IPL}=  Select IPL  ${cur_state}
+    \  Call Point Pre Boot
+    \  Log  We are doing a ${next_IPL}${\n}  console=True
+    \  Update Last Ten  ${next_IPL}
+    \  Run Keyword and Continue On Failure  Run IPL  ${next_IPL}
+    \  Call Point Post Boot
+    \  Run Keyword If  '${IPL_STATUS}' == 'PASS'
+    ...  Log  IPL_SUCCESS: "${next_IPL}" succeeded.  console=True
+    ...  ELSE  Log  IPL_FAILED: ${next_IPL} failed.  console=True
+    \  Update Run Table Values  ${next_IPL}
+    \  Log  FFDC Dump requested!  console=True
+    \  Log  ***Beginning dump of FFDC***  console=True
+    \  Call Point FFDC
+    \  Log Defect Information
+    \  Log Last Ten IPLs
+    \  Log FFDC Files
+    \  Log  ***Finished dumping of FFDC***  console=True
+    \  Call Point Stop Check
+    \  Log FFDC Summary
+    \  Log Run Table
+    \  Log  ${\n}***Finished IPL ${IPL_COUNT+1} of ${IPL_TOTAL}***  console=True
+
+Do Test Setup
+    [Documentation]  Do any setup that needs to be done before running a series
+    ...  of IPLs.
+
+    Should Not Be Empty  ${AVAIL_IPLS}
+
+    Setup Run Table
+    Log  ***Start of status file for ${OPENBMC_HOST}***  console=True
+
+Setup Run Table
+    [Documentation]  For each available IPL, create a variable that stores the
+    ...  number of passes and fails for each IPL.
+
+    Log to Console  Setting up run table.
+
+    :FOR  ${ipl}  IN  @{AVAIL_IPLS}
+    \  Set Global Variable  ${${ipl}_PASS}  ${0}
+    \  Set Global Variable  ${${ipl}_FAIL}  ${0}
+
+Select IPL
+    [Documentation]  Contains all of the logic for which IPLs can be chosen
+    ...  given the inputted state. Returns the chosen IPL.
+    [Arguments]  ${cur_state}
+
+    # cur_state      The power state of the machine, either zero or one.
+
+    ${ipl}=  Run Keyword If  ${cur_state} == ${0}  Select Power On
+    ...  ELSE  Run Keyword If  ${cur_state} == ${1}  Select Power Off
+    ...  ELSE  Run Keywords  Log to Console
+    ...  **ERROR** BMC not in state to power on or off: "${cur_state}"  AND
+    ...  Fatal Error
+
+    [return]  ${ipl}
+
+Select Power On
+    [Documentation]  Randomly chooses an IPL from the list of Power On IPLs.
+
+    @{power_on_choices}=  Intersect Lists  ${VALID_POWER_ON}  ${AVAIL_IPLS}
+
+    ${length}=  Get Length  ${power_on_choices}
+
+    # Currently selects the first IPL in the list of options, rather than
+    # selecting randomly.
+    ${chosen}=  Set Variable  @{power_on_choices}[0]
+
+    [return]  ${chosen}
+
+Select Power Off
+    [Documentation]  Randomly chooses an IPL from the list of Power Off IPLs.
+
+    @{power_off_choices}=  Intersect Lists  ${VALID_POWER_OFF}  ${AVAIL_IPLS}
+
+    ${length}=  Get Length  ${power_off_choices}
+
+    # Currently selects the first IPL in the list of options, rather than
+    # selecting randomly.
+    ${chosen}=  Set Variable  @{power_off_choices}[0]
+
+    [return]  ${chosen}
+
+Run IPL
+    [Documentation]  Runs the selected IPL and marks the status when complete.
+    [Arguments]  ${ipl_keyword}
+    [Teardown]  Set Global Variable  ${IPL_STATUS}  ${KEYWORD STATUS}
+
+    # ipl_keyword      The name of the IPL to run, which corresponds to the
+    #                  keyword to run. (i.e "BMC Power On")
+
+    Run Keyword  ${ipl_keyword}
+
+Log Defect Information
+    [Documentation]  Logs information needed for a defect. This information
+    ...  can also be found within the FFDC gathered.
+
+    Log  Copy this data to the defect:  console=True
+
+Log Last Ten IPLs
+    [Documentation]  Logs the last ten IPLs that were performed with their
+    ...  starting time stamp.
+
+    Log  ${\n}----------------------------------${\n}Last 10 IPLs${\n}
+    ...  console=True
+    :FOR  ${ipl}  IN  @{LAST_TEN}
+    \  Log  ${ipl}  console=True
+    Log  ----------------------------------${\n}  console=True
+
+Log FFDC Files
+    [Documentation]  Logs the files outputted during FFDC gathering.
+    Log  This is where the list of FFDC files would be.  console=True
+
+Log FFDC Summary
+    [Documentation]  Logs finding from within the FFDC files gathered.
+    Log  This is where the FFDC summary would go.  console=True
+
+Log Run Table
+    [Documentation]  Logs the table of IPLs that have passed and failed based on
+    ...  the available IPLs, as well as the total passes and failures.
+
+    Log  ${\n}IPL type${space*14}Pass${space*3}Fail  console=True
+    Log  ==================================  console=True
+    :FOR  ${ipl}  IN  @{AVAIL_IPLS}
+    \  ${length}=  Get Length  ${ipl}
+    \  ${space_num}=  Set Variable  ${24-${length}}
+    \  Log  ${ipl}${space*${space_num}}${${ipl}_PASS}${space*5}${${ipl}_FAIL}
+    ...  console=True
+    Log  ==================================  console=True
+    Log  Totals:${space*17}${IPL_PASSED}${space*5}${IPL_FAILED}${\n}
+    ...  console=True
+
+Update Run Table Values
+    [Documentation]  Updates the table of IPLs that have passed and failed. See
+    ...  the "Log Run Table" keyword for more information.
+    [Arguments]  ${last_ipl}
+
+    # last_ipl      The name of the last IPL that ran (i.e "BMC Power On").
+
+    ${cur_value}=  Get Variable Value  ${${last_ipl}_${IPL_STATUS}}
+    Set Global Variable  ${${last_ipl}_${IPL_STATUS}}  ${cur_value+1}
+    ${total_value}=  Run Keyword If  '${IPL_STATUS}' == 'PASS'
+    ...              Get Variable Value  ${IPL_PASSED}
+    ...              ELSE  Get Variable Value  ${IPL_FAILED}
+    Run Keyword If  '${IPL_STATUS}' == 'PASS'
+    ...             Set Global Variable  ${IPL_PASSED}  ${total_value+1}
+    ...             ELSE  Set Global Variable  ${IPL_FAILED}  ${total_value+1}
+
+Update Last Ten
+    [Documentation]  Updates the list of last ten IPLs
+    [Arguments]  ${last_ipl}
+
+    # last_ipl      The name of the last IPL that ran (i.e. "BMC Power On")
+
+    ${time}=  Get Time
+    Append to List  ${LAST_TEN}  ${time} - Doing "${last_ipl}"