Add testplan and suite for PCEP performance testing
[integration/test.git] / csit / suites / bgpcep / throughpcep / cases.robot
diff --git a/csit/suites/bgpcep/throughpcep/cases.robot b/csit/suites/bgpcep/throughpcep/cases.robot
new file mode 100644 (file)
index 0000000..40cac48
--- /dev/null
@@ -0,0 +1,506 @@
+*** Settings ***
+Documentation     PCEP performance suite, uses restconf with configurable authentication.
+...
+...               Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
+...
+...               This program and the accompanying materials are made available under the
+...               terms of the Eclipse Public License v1.0 which accompanies this distribution,
+...               and is available at http://www.eclipse.org/legal/epl-v10.html
+...
+...
+...               General Overview:
+...
+...               This is a suite which has both scale and performance aspects.
+...               Given scale target, suite reports failures if functional error
+...               is detected, or if various time limits expire.
+...               For passing test cases, their duration is the performance metric.
+...
+...               ODL acts as a translation layer between PCEP capable devices
+...               and users employing RESTCONF.
+...               Performance measurement focuses on two different workflows.
+...
+...               The first workflow is initial synchronization, when ODL learns
+...               the state of PCEP topology as devices connect to it,
+...               while restconf user reads the state repeatedly.
+...               The second workflow is mass update, when restconf users issue RPCs
+...               to updale Layer Switched Paths on Path Computation Clients.
+...
+...               This suite uses pcc-mock (downloaded from Nexus) to simulate PCCs.
+...               It needs segment of bindable IP addresses,
+...               one for each simulated PCC; so running pcc-mock from remote machine
+...               is only viable when just single PCC is simulated.
+...               Testing with multiple PCCs works best when pcc-mock
+...               runs on the same VM as ODL, so 127.0.0.0/8 subnet can be used.
+...
+...               Library AuthStandalone is used directly for restconf reads
+...               in the first workflow. That library transparently handles several
+...               http authentication methods, based on credentials and pybot arguments.
+...
+...               In the second workflow, updater.py utility is used for issuing
+...               rapid restconf requests. It can use multiple worker threads,
+...               as http requests are blocking.
+...               Due to CPython interpreter itself being single threaded,
+...               amounts of threads above 8-16 are actually slightly slower
+...               (which may roughly correspond to network traffic
+...               being more limiting factor than CPU).
+...               This suite starts updater utility bound to single CPU,
+...               as this setup was the most performant in other tests.
+...
+...               In case of failed test case, other tests are skipped (unless
+...               this is overriden by [Setup]) to finish test run sooner.
+...
+...               Variables and test case names refer to Controller and Mininet,
+...               those are assumed to be separate remote VMs, one to host ODL,
+...               other to host tools.
+...               In case updater and pcc-mock are desired to run
+...               from separate machines, their parameters use Mininet
+...               values as default.
+...               If both updater VM and pcc-mock VM parameters are specified,
+...               Mininet parameters may be skipped.
+...
+...               Some launch scripts put restrictions on how pybot options
+...               can be specified, so there are utility variables to help with
+...               copying Controller related value to apply fo updater of pccmock.
+...               Having a tool co-located with ODL reduces network latency,
+...               but puts more pressure on CPU and memory on Controller VM.
+...
+...               In some environments, issues with TIME-WAIT prevent high restconf rates,
+...               so TCP reuse is temporarily allowed during the suite run, if possible
+...               (and if not disabled by UPDATERVM_ENABLE_TCP_RW_REUSE option value).
+...               See http://vincent.bernat.im/en/blog/2014-tcp-time-wait-state-linux.html
+...               This suite ignores possible failures when changing reuse.
+...
+...               Similarly, in some environments, handling of requests.Session object matters
+...               try changing RESTCONF_REUSE value to see if it helps.
+...
+...               Variables to override (only if needed) in pybot command:
+...               (Look into Variables table to see the default values.)
+...
+...               CONTROLLER: Numeric IP address of VM where ODL runs.
+...               CONTROLLER_USER: Username for ssh login to ODL VM.
+...               CONTROLLER_PASSWORD: Ssh password, empty means public keys are used instead.
+...               CONTROLLER_PROMPT: Substring to identify Linux prompt on ODL VM.
+...               CONTROLLER_WORKSPACE: Path to where files can be written on ODL VM.
+...               FIRST_PCC_IP: Set in case bind address is different from public pcc-mock VM address.
+...               LOG_NAME: Filename (without path) to save pcc-mock output into.
+...               LOG_PATH: Override if not the same as pccmock VM workspace.
+...               LSPS: Number of LSPs per PCC to simulate and test.
+...               MININET: Numeric IP address of VM to run pcc-mock and updater from by default.
+...               MININET_PASSWORD: Linux password to go with the username (empty means keys).
+...               MININET_PROMPT: Substring to identify Linux prompt on Mininet VM.
+...               MININET_USER: Linux username to SSH to on Mininet VM.
+...               MININET_WORKSPACE: Path to where files may be created on Mininet VM.
+...               MOCK_FILE: Filename to use for mock-pcc executable instead of the timestamped one.
+...               PCCDOWNLOAD_HOSTHEADER: Download server may check checks this header before showing content.
+...               PCCDOWNLOAD_URLBASE: URL to pcep-pcc-mock folder in Nexus (use numberic IP if DNS has problems).
+...               PCCMOCK_COLOCATED: If True, set PCCMOCKVM* to mirror CONTROLLER*
+...               PCCMOCKVM_IP: Override MININET for pcc-mock usage.
+...               PCCMOCKVM_*: Override corresponding MININET_* for pcc-mock usage.
+...               PCCS: Number of PCCs to simulate and test.
+...               PCEP_READY_VERIFY_TIMEOUT: Grace period for pcep-topology to appear. Lower if ODL is ready.
+...               RESTCONF_*: USER, PASSWORD and SCOPE to authenticate with, REUSE session.
+...               (Note: If SCOPE is not empty, token-based authentication is used.)
+...               UPDATER_COLOCATED: If True, overrides UPDATERVM_* parameters to point at Controller
+...               (The purpose is to provide an option without ability to unpack CONTROLLER value.)
+...               UPDATER_ODLADDRESS: Override if public CONTROLLER address is not best fit.
+...               UPDATER_REFRESH: Main updater thread may sleep this long. Balance precision with overhead.
+...               UPDATER_TIMEOUT: If updater stops itself if running more than this time.
+...               (Set this limit according to your performance target.)
+...               UPDATERVM_ENABLE_TCP_RW_REUSE: Set to false if changing Linux configuration is not desired.
+...               UPDATERVM_IP: Override MININET for updater.py usage.
+...               UPDATERVM_*: Override corresponding MININET_* for updater.py usage.
+Suite Setup       FailFast.Do_Not_Fail_Fast_From_Now_On
+Suite Teardown    Disconnect
+Test Setup        FailFast.Fail_This_Fast_On_Previous_Error
+Test Teardown     FailFast.Start_Failing_Fast_If_This_Failed
+Variables         ${CURDIR}/../../../variables/Variables.py
+Library           SSHLibrary    timeout=10s
+Library           RequestsLibrary
+Library           ${CURDIR}/../../../libraries/AuthStandalone.py
+Resource          ${CURDIR}/../../../libraries/Utils.robot
+Resource          ${CURDIR}/../../../libraries/FailFast.robot
+
+*** Variables ***
+# This table acts as an exhaustive list of variables users can modify on pybot invocation.
+# It also contains commented-out lines for variables defined elswhere.
+# Keep this list in alphabetical order.
+# ${CONTROLLER} is inherited from Variables.py
+# ${CONTROLLER_USER} is inherited from Variables.py
+# ${CONTROLLER_PASSWORD} is inherited from Variables.py
+${CONTROLLER_PROMPT}    ${DEFAULT_LINUX_PROMPT}    # from Variables.py
+${CONTROLLER_WORKSPACE}    /tmp
+${FIRST_PCC_IP}    ${PCCMOCKVM_IP}
+# ${LOG_FILE} is reserved for location of pybot-created log.html
+${LOG_NAME}       throughpcep.log
+${LOG_PATH}       ${PCCMOCKVM_WORKSPACE}
+${LSPS}           65535
+${MININET}        127.0.0.1
+# ${MININET_PASSWORD} is inherited from Variables.py
+${MININET_PROMPT}    ${DEFAULT_LINUX_PROMPT}    # from Variables.py
+${MININET_USER}    mininet
+${MININET_WORKSPACE}    /tmp
+${MOCK_FILE}      pcc-mock-ecexutable.jar
+${PCCDOWNLOAD_HOSTHEADER}    nexus.opendaylight.org
+${PCCDOWNLOAD_URLBASE}    http://${PCCDOWNLOAD_HOSTHEADER}/content/repositories/opendaylight.snapshot/org/opendaylight/bgpcep/pcep-pcc-mock/
+${PCCMOCK_COLOCATED}    False
+${PCCMOCKVM_IP}    ${MININET}
+${PCCMOCKVM_PASSWORD}    ${MININET_PASSWORD}
+${PCCMOCKVM_PROMPT}    ${MININET_PROMPT}
+${PCCMOCKVM_USER}    ${MININET_USER}
+${PCCMOCKVM_WORKSPACE}    ${MININET_WORKSPACE}
+${PCCS}           1
+${PCEP_READY_VERIFY_TIMEOUT}    300s
+# Yes, the default timeout is 5 minutes, as this suite might be started eagerly just after ODL starts booting up.
+${RESTCONF_PASSWORD}    ${PWD}    # from Variables.py
+${RESTCONF_REUSE}    True
+${RESTCONF_SCOPE}    ${EMPTY}
+${RESTCONF_USER}    ${USER}    # from Variables.py
+${UPDATER_COLOCATED}    False
+${UPDATER_ODLADDRESS}    ${CONTROLLER}
+${UPDATER_REFRESH}    0.1
+${UPDATER_TIMEOUT}    300
+${UPDATERVM_ENABLE_TCP_RW_REUSE}    True
+${UPDATERVM_IP}    ${MININET}
+${UPDATERVM_PASSWORD}    ${MININET_PASSWORD}
+${UPDATERVM_PROMPT}    ${MININET_PROMPT}
+${UPDATERVM_USER}    ${MININET_USER}
+${UPDATERVM_WORKSPACE}    ${MININET_WORKSPACE}
+
+*** TestCases ***
+Download_Pcc_Mock
+    [Documentation]    SSH login to pcc-mock VM, download latest pcc-mock executable from Nexus.
+    BuiltIn.Run_Keyword_If    ${PCCMOCK_COLOCATED}    Pccmock_From_Controller
+    SSHLibrary.Open_Connection    ${PCCMOCKVM_IP}    alias=pccmock
+    SSHLibrary.Set_Client_Configuration    timeout=10s
+    SSHLibrary.Set_Client_Configuration    prompt=${PCCMOCKVM_PROMPT}
+    Utils.Flexible_SSH_Login    ${PCCMOCKVM_USER}    ${PCCMOCKVM_PASSWORD}    delay=4s
+    ${curl_common} =    BuiltIn.Set_Variable    curl -s -H "Host:${PCCDOWNLOAD_HOSTHEADER}" ${PCCDOWNLOAD_URLBASE}
+    ${version} =    SSHLibrary.Execute_Command    ${curl_common}/maven-metadata.xml \| grep latest \| cut -d '>' -f 2 \| cut -d '<' -f 1
+    BuiltIn.Log    ${version}
+    ${namepart} =    SSHLibrary.Execute_Command    ${curl_common}/${version}/maven-metadata.xml \| grep value \| head -n 1 \| cut -d '>' -f 2 \| cut -d '<' -f 1
+    BuiltIn.Log    ${namepart}
+    BuiltIn.Set_Suite_Variable    ${filename}    pcep-pcc-mock-${namepart}-executable.jar
+    BuiltIn.Log    ${filename}
+    BuiltIn.Set_Suite_Variable    ${mocklocation}    ${PCCMOCKVM_WORKSPACE}/${MOCK_FILE}
+    # TODO: Debug to make wget -N work
+    ${response}    ${err}    ${return_code} =    SSHLibrary.Execute_Command    ${curl_common}/${version}/${filename} > ${mocklocation}    return_rc=True    return_stderr=True
+    BuiltIn.Log    ${err}
+    BuiltIn.Should_Be_Equal    ${return_code}    ${0}
+
+Put_Updater
+    [Documentation]    Open SSH session to updater VM, copy the utility there, including dependencies, also prepare direct http session.
+    BuiltIn.Run_Keyword_If    ${UPDATER_COLOCATED}    Updater_From_Controller
+    SSHLibrary.Open_Connection    ${UPDATERVM_IP}    alias=updater
+    SSHLibrary.Set_Client_Configuration    timeout=20s
+    SSHLibrary.Set_Client_Configuration    prompt=${UPDATERVM_PROMPT}
+    Utils.Flexible_SSH_Login    ${UPDATERVM_USER}    ${UPDATERVM_PASSWORD}    delay=4s
+    Require_Python
+    SSHLibrary.Put_File    ${CURDIR}/../../../../tools/pcep_updater/updater.py    ${UPDATERVM_WORKSPACE}/
+    SSHLibrary.Put_File    ${CURDIR}/../../../libraries/AuthStandalone.py    ${UPDATERVM_WORKSPACE}/
+    Assure_Library_Counter    workspace=${UPDATERVM_WORKSPACE}
+    Assure_Library_Ipaddr    workspace=${UPDATERVM_WORKSPACE}
+    # Done preparation of Updater VM, now use AuthStandalone to create session from robot VM too.
+    BuiltIn.Log_Many    ${RESTCONF_USER}    ${RESTCONF_PASSWORD}    ${RESTCONF_SCOPE}    ${CONTROLLER}
+    ${session} =    AuthStandalone.Init_Session    ${CONTROLLER}    ${RESTCONF_USER}    ${RESTCONF_PASSWORD}    ${RESTCONF_SCOPE}
+    BuiltIn.Set_Suite_Variable    ${rest_session}    ${session}
+    # TODO: Define http timeouts.
+
+Save_And_Enable_Tcp_Rw_Reuse
+    [Documentation]    If requested, temporarily enable TCP port reuse on Updater VM to allow for high rate of TCP connections. Do not start failing fast.
+    BuiltIn.Pass_Execution_If    not ${UPDATERVM_ENABLE_TCP_RW_REUSE}    Manipulation of tcp_rw_reuse is not requested.
+    ${old_value} =    SSHLibrary.Execute_Command    cat /proc/sys/net/ipv4/tcp_tw_reuse
+    # The next line may be skipped if the previous line failed.
+    BuiltIn.Set_Suite_Variable    ${tcp_rw_reuse}    ${old_value}
+    ${out}    ${rc} =    SSHLibrary.Execute_Command    sudo bash -c "echo 1 > /proc/sys/net/ipv4/tcp_tw_reuse"    return_rc=True
+    BuiltIn.Should_Be_Equal    ${rc}    ${0}
+    # Lack of sudo access should not prevent the rest of suite from trying without TCP reuse.
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Topology_Precondition
+    [Documentation]    Verify that within timeout, PCEP topology is present, with no PCC connected.
+    [Tags]    critical
+    Set_Hop    0
+    Builtin.Wait_Until_Keyword_Succeeds    ${PCEP_READY_VERIFY_TIMEOUT}    1s    Pcep_Off
+    # Yes, timeout is 5 minutes, as this suite might be started eagerly just after ODL starts booting up.
+
+Start_Pcc_Mock
+    [Documentation]    Launch pcc-mock on background so simulated PCCs start connecting to controller.
+    SSHLibrary.Switch_Connection    pccmock
+    ${command} =    BuiltIn.Set_Variable    java -jar ${mocklocation} --local-address ${FIRST_PCC_IP} --remote-address ${CONTROLLER} --pcc ${PCCS} --lsp ${LSPS} &> ${LOG_PATH}/${LOG_NAME}
+    BuiltIn.Log    ${command}
+    SSHLibrary.Write    ${command}
+    # The pccmock SSH session is left alive, but no data will be exchanged for a while.
+    # We need this connection to stay alive to send ctrl+c later.
+    # SSHLibrary.Start_Command would not do that for us.
+
+Topology_Intercondition
+    [Documentation]    Verify that within timeout, PCEP topology contains correct numbers of LSPs.
+    [Tags]    critical
+    ${localsize} =    Evaluate    int(${PCCS})*int(${LSPS})
+    Builtin.Set_Suite_Variable    ${size}    ${localsize}
+    BuiltIn.Log    ${size}
+    Builtin.Wait_Until_Keyword_Succeeds    120s    1s    Pcep_On
+    # TODO: Make timeout value scale with ${size}?
+
+Updater_1
+    [Documentation]    Run updater tool to change hops, using 1 blocking http thread.
+    [Tags]    critical
+    Updater    1
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_1
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    1
+
+Updater_2
+    [Documentation]    Run updater tool to change hops again, using 2 blocking http threads.
+    [Tags]    critical
+    Updater    2
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_2
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    2
+
+Updater_3
+    [Documentation]    Run updater tool to change hops again, using 4 blocking http threads.
+    [Tags]    critical
+    Updater    3
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_3
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    3
+
+Updater_4
+    [Documentation]    Run updater tool to change hops again, using 8 blocking http threads.
+    [Tags]    critical
+    Updater    4
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_4
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    4
+
+Updater_5
+    [Documentation]    Run updater tool to change hops again, using 16 blocking http threads.
+    [Tags]    critical
+    Updater    5
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_5
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    5
+
+Updater_6
+    [Documentation]    Run updater tool to change hops again, using 32 blocking http threads.
+    [Tags]    critical
+    Updater    6
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_6
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    6
+
+Updater_7
+    [Documentation]    Run updater tool to change hops again, using 64 blocking http threads.
+    [Tags]    critical
+    Updater    7
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_7
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    7
+
+Updater_8
+    [Documentation]    Run updater tool to change hops again, using 128 blocking http threads.
+    [Tags]    critical
+    Updater    8
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_8
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    8
+
+Updater_9
+    [Documentation]    Run updater tool to change hops again, using 256 blocking http threads.
+    [Tags]    critical
+    Updater    9
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_9
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    9
+
+Updater_10
+    [Documentation]    Run updater tool to change hops again, using 512 blocking http threads.
+    [Tags]    critical
+    Updater    10
+    [Teardown]    Do_Not_Start_Failing_If_This_Failed
+
+Verify_10
+    [Documentation]    Verify that within timeout, the correct number of new hops is in PCEP topology.
+    [Tags]    critical
+    Verify    10
+
+Stop_Pcc_Mock
+    [Documentation]    Send ctrl+c to pcc-mock, see prompt again within timeout.
+    [Setup]    Run_Even_When_Failing_Fast
+    # TODO: This should be in a library similar to BGPSpeaker.robot
+    SSHLibrary.Switch_Connection    pccmock
+    # FIXME: send_ctrl should be in some library.
+    ${command} =    BuiltIn.Evaluate    chr(int(3))
+    BuiltIn.Log    ${command}
+    SSHLibrary.Write    ${command}
+    ${response} =    SSHLibrary.Read_Until_Prompt
+    BuiltIn.Log    ${response}
+
+Download_Pccmock_Log
+    [Documentation]    Transfer pcc-mock output from pcc-mock VM to robot VM.
+    [Setup]    Run_Even_When_Failing_Fast
+    SSHLibrary.Get_File    ${LOG_PATH}/${LOG_NAME}    ${CURDIR}/${LOG_NAME}
+
+Topology_Postcondition
+    [Documentation]    Verify that within timeout, PCEP topology contains no PCCs again.
+    [Tags]    critical
+    [Setup]    Run_Even_When_Failing_Fast
+    Builtin.Wait_Until_Keyword_Succeeds    30s    1s    Pcep_Off_Again
+
+Restore_Tcp_Rw_Reuse
+    [Documentation]    If requested, restore the old value if enabling TCP reuse was successful on Updater VM.
+    [Setup]    Run_Even_When_Failing_Fast
+    BuiltIn.Pass_Execution_If    not ${UPDATERVM_ENABLE_TCP_RW_REUSE}    Manipulation of tcp_rw_reuse is not requested.
+    SSHLibrary.Switch_Connection    updater
+    BuiltIn.Variable_Should_Exist    ${tcp_rw_reuse}
+    ${out}    ${rc} =    SSHLibrary.Execute_Command    sudo bash -c "echo ${tcp_rw_reuse} > /proc/sys/net/ipv4/tcp_tw_reuse"    return_rc=True
+    BuiltIn.Should_Be_Equal    ${rc}    ${0}
+
+*** Keywords ***
+Pccmock_From_Controller
+    [Documentation]    Copy Controller values to Pccmock VM variables.
+    BuiltIn.Set_Suite_Variable    ${PCCMOCKVM_IP}    ${CONTROLLER}
+    BuiltIn.Set_Suite_Variable    ${PCCMOCKVM_PASSWORD}    ${CONTROLLER_PASSWORD}
+    BuiltIn.Set_Suite_Variable    ${PCCMOCKVM_PROMPT}    ${CONTROLLER_PROMPT}
+    BuiltIn.Set_Suite_Variable    ${PCCMOCKVM_WORKSPACE}    ${CONTROLLER_WORKSPACE}
+    BuiltIn.Set_Suite_Variable    ${LOG_PATH}    ${CONTROLLER_WORKSPACE}
+
+Updater_From_Controller
+    [Documentation]    Copy Controller values to Uprater VM variables.
+    BuiltIn.Set_Suite_Variable    ${UPDATERVM_IP}    ${CONTROLLER}
+    BuiltIn.Set_Suite_Variable    ${UPDATERVM_PASSWORD}    ${CONTROLLER_PASSWORD}
+    BuiltIn.Set_Suite_Variable    ${UPDATERVM_PROMPT}    ${CONTROLLER_PROMPT}
+    BuiltIn.Set_Suite_Variable    ${UPDATERVM_WORKSPACE}    ${CONTROLLER_WORKSPACE}
+
+Require_Python
+    [Documentation]    Verify current SSH connection leads to machine with python working. Fatal fail otherwise.
+    ${passed} =    Execute_Command_Passes    python --help
+    BuiltIn.Return_From_Keyword_If    ${passed}
+    BuiltIn.Fatal_Error    Python is not installed!
+
+Assure_Library_Counter
+    [Arguments]    ${workspace}=/tmp
+    [Documentation]    Tests whether Counter is present in collections on ssh-connected machine, Puts Counter.py to workspace if not.
+    ${passed} =    Execute_Command_Passes    bash -c 'cd "${workspace}" && python -c "from collections import Counter"'
+    # TODO: Move the bash-cd wrapper to separate keyword?
+    BuiltIn.Return_From_Keyword_If    ${passed}
+    SSHLibrary.Put_File    ${CURDIR}/../../../libraries/Counter.py    ${workspace}/
+
+Assure_Library_Ipaddr
+    [Arguments]    ${workspace}=/tmp
+    [Documentation]    Tests whether ipaddr module is present on ssh-connected machine, Puts ipaddr.py to workspace if not.
+    ${passed} =    Execute_Command_Passes    bash -c 'cd "${workspace}" && python -c "import ipaddr"'
+    BuiltIn.Return_From_Keyword_If    ${passed}
+    SSHLibrary.Put_File    ${CURDIR}/../../../libraries/ipaddr.py    ${workspace}/
+
+Execute_Command_Passes
+    [Arguments]    ${command}
+    [Documentation]    Execute command via SSH. If RC is nonzero, log everything. Retrun bool of command success.
+    ${stdout}    ${stderr}    ${rc} =    SSHLibrary.Execute_Command    ${command}    return_stderr=True    return_rc=True
+    BuiltIn.Return_From_Keyword_If    ${rc} == 0    True
+    BuiltIn.Log    ${stdout}
+    BuiltIn.Log    ${stderr}
+    BuiltIn.Log    ${rc}
+    [Return]    False
+
+Disconnect
+    [Documentation]    Explicitly close all SSH connections.
+    SSHLibrary.Close_All_Connections
+    # TODO: Make AuthStandalone session object closable?
+
+Get_Pcep_Topology_Data
+    [Documentation]    Use session object to download PCEP topology JSON. Check status and return Response object.
+    ${resp} =    AuthStandalone.Get_Using_Session    ${rest_session}    operational/network-topology:network-topology/topology/pcep-topology
+    # Not Logging content, as it may be huge.
+    BuiltIn.Should_Be_Equal    ${resp.status_code}    ${200}
+    [Return]    ${resp}
+
+Get_Pcep_Topology_Count
+    [Arguments]    ${pattern}
+    [Documentation]    Get topology data, return number of pattern matches.
+    ${resp} =    Get_Pcep_Topology_Data
+    # BuiltIn.Log    ${resp.text}
+    ${count} =    BuiltIn.Evaluate    len(re.findall('${pattern}', '''${resp.text}'''))    modules=re
+    BuiltIn.Log    ${count}
+    [Return]    ${count}
+
+Pcep_Off
+    [Documentation]    Get topology data, Log content and assert the exact JSON of empty topology.
+    ${resp} =    Get_Pcep_Topology_Data
+    # Used before topology had chance to grow huge. Be aware when creating a longevity suite from this.
+    BuiltIn.Log    ${resp.text}
+    BuiltIn.Should_Be_Equal    ${resp.text}    {"topology":[{"topology-id":"pcep-topology","topology-types":{"network-topology-pcep:topology-pcep":{}}}]}
+
+Pcep_On
+    [Documentation]    Get topology count of current hop, assert the number of matches.
+    # Suite variables ${size} and ${hop} are set elsewhere.
+    ${resp} =    Get_Pcep_Topology_Count    ${hop}
+    BuiltIn.Should_Be_Equal    ${resp}    ${size}
+
+Pcep_Off_Again
+    [Documentation]    Get topology count of final hop, assert there is none.
+    ...    This is more log friendly than Pcep_Off keyword, as it does not Log possibly large content.
+    Set_Hop    0
+    ${resp} =    Get_Pcep_Topology_Count    ${hop}
+    BuiltIn.Should_Be_Equal    ${resp}    ${0}
+
+Set_Hop
+    [Arguments]    ${iteration}
+    [Documentation]    Set pattern to match the currently expected hop.
+    ${i} =    BuiltIn.Evaluate    str(1 + int(${iteration}))
+    # Regular Expressions need a dot to be escaped to represent a dot.
+    BuiltIn.Set_Suite_Variable    ${hop}    ${i}\.${i}\.${i}\.${i}/32
+    BuiltIn.Log    ${hop}
+
+Updater
+    [Arguments]    ${iteration}
+    [Documentation]    Compute number of workers, call updater.py, assert its response.
+    SSHLibrary.Switch_Connection    pccmock
+    # In some systems, inactive SSH sessions get severed.
+    ${command} =    BuiltIn.Set_Variable    echo "still alive"
+    ${output} =    SSHLibrary.Execute_Command    bash -c '${command}'
+    # The previous line relies on a fact that Execute_Command spawns separate shels, so running pcc-mock is not affected.
+    ${workers} =    Evaluate    2**int(${iteration} - 1)
+    # TODO: Provide ${workers} explicitly as an argument to avoid math?
+    BuiltIn.Log    ${workers}
+    Set_Hop    ${iteration}
+    SSHLibrary.Switch_Connection    updater
+    ${response} =    SSHLibrary.Execute_Command    bash -c "cd ${UPDATERVM_WORKSPACE}; taskset 0x00000001 python updater.py --workers '${workers}' --odladdress '${UPDATER_ODLADDRESS}' --user '${RESTCONF_USER}' --password '${RESTCONF_PASSWORD}' --scope '${RESTCONF_SCOPE}' --pccaddress '${FIRST_PCC_IP}' --pccs '${PCCS}' --lsps '${LSPS}' --hop '${hop}' --timeout '${UPDATER_TIMEOUT}' --refresh '${UPDATER_REFRESH}' --reuse '${RESTCONF_REUSE}' 2>&1"
+    BuiltIn.Log    ${response}
+    ${expected} =    BuiltIn.Set_Variable    Counter({'pass': ${size}})
+    BuiltIn.Log    ${expected}
+    BuiltIn.Should_Contain    ${response}    ${expected}
+
+Verify
+    [Arguments]    ${iteration}
+    [Documentation]    Set hop and verify that within timeout, all LSPs in topology are updated.
+    Set_Hop    ${iteration}
+    Builtin.Wait_Until_Keyword_Succeeds    30s    1s    Pcep_On