VOL-2642 Add a Makefile, tests, and virtualenv

Convert common python and robot into a CORDRobot python module that can
be installed via standard python tools (pip) and from PyPI

Uses a fork of https://github.com/rasjani/robotframework-importresource,
which has been backported to Python 3.5 (used in Ubuntu 16.04
executors).

Reformatted and moved keywords so resource files are scoped to a
specific topic.

Added tox tests for library consistency

- flake8
- pylint
- robotframework-lint
- Ran robot against installed library to verify it can be loaded and
  used

Added basic lint and tests to whole repo

Removed old tests:

- CORD <6.x era: SanityPhyPOD.robot, and onosUtils.py

Change-Id: I61265a9fb04034a086e20be1f7236a8793a218aa
diff --git a/cord-robot/CORDRobot/rf-resources/ATTWorkFlowDriver.resource b/cord-robot/CORDRobot/rf-resources/ATTWorkFlowDriver.resource
new file mode 100644
index 0000000..8f932f8
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/ATTWorkFlowDriver.resource
@@ -0,0 +1,107 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library to retrieve status fields from ATT WorkFlow Driver Service Instance List
+Library           Collections
+Library           String
+Library           OperatingSystem
+Library           XML
+Library           RequestsLibrary
+Library           CORDRobot
+
+*** Variable ***
+${ONU_STATE_VAR}    admin_onu_state
+
+*** Keywords ***
+Service Instance Status Check
+    [Arguments]    ${onu_device}
+    [Documentation]    Returns onu_state and authentication_state fields for an ONU device
+    ...    from att workflow driver
+    ${json_result}=    CORDRobot.ApiGet    ATT_SERVICEINSTANCES
+    Log    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict
+    ...    ${json_result_list}    serial_number    ${onu_device}
+    ${onu_state}=    Get From Dictionary    ${getJsonDict}    ${ONU_STATE_VAR}
+    ${authentication_state}=    Get From Dictionary    ${getJsonDict}    authentication_state
+    ${status_message}=    Get From Dictionary    ${getJsonDict}    status_message
+    [Return]    ${onu_state}    ${authentication_state}    ${status_message}
+
+Service Instance DHCP State Check
+    [Arguments]    ${onu_device}
+    [Documentation]    Returns DHCP state from att workflow driver for a particular ONU device
+    ${json_result}=    CORDRobot.ApiGet    ATT_SERVICEINSTANCES
+    Log    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict
+    ...    ${json_result_list}    serial_number    ${onu_device}
+    ${state}=    Get From Dictionary    ${getJsonDict}    dhcp_state
+    [Return]    ${state}
+
+Create Whitelist Entry
+    [Arguments]    ${entry_list}    ${list_index}
+    [Documentation]    Sends a POST to create an att whitelist in XOS
+    ${elist} =    Get Variable Value    ${entry_list}
+    ${entry_dictionary}=    CORDRobot.listToDict    ${elist}    ${list_index}
+    ${api_result}=    CORDRobot.ApiPost    ATT_WHITELIST    ${entry_dictionary}
+    Should Be True    ${api_result}
+    ${AttWhiteList_Id}=    Get From Dictionary    ${api_result}    id
+    Set Global Variable    ${AttWhiteList_Id}
+    [Return]    ${AttWhiteList_Id}
+
+Retrieve Whitelist Entry
+    [Arguments]    ${serial_number}
+    [Documentation]    Returns the whitelist entry per the ONU serial number
+    ${json_result}=    CORDRobot.ApiGet    ATT_WHITELIST
+    Log    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict
+    ...    ${json_result_list}    serial_number    ${serial_number}
+    ${id}=    Get From Dictionary    ${getJsonDict}    id
+    [Return]    ${id}
+
+Retrieve ATT Service Instance ID
+    [Arguments]    ${serial_number}
+    [Documentation]    Returns the whitelist entry per the ONU serial number
+    ${json_result}=    CORDRobot.ApiGet    ATT_SERVICEINSTANCES
+    Log    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict
+    ...    ${json_result_list}    serial_number    ${serial_number}
+    ${id}=    Get From Dictionary    ${getJsonDict}    id
+    [Return]    ${id}
+
+Delete Whitelist Entry
+    [Arguments]    ${id}
+    [Documentation]    Sends a DELETE to delete an att whitelist in XOS
+    ${api_result}=    CORDRobot.ApiChameleonDelete    ATT_WHITELIST    ${id}
+    Should Be True    ${api_result}
+
+Validate ATT Workflow Driver SI
+    [Documentation]    FIXME
+    [Arguments]    ${expected_status}    ${expected_auth_status}
+    ...    ${onu_device}    ${expected_status_message}=${EMPTY}
+    ${onu_state}    ${authentication_status}    ${status_message}
+    ...    Service Instance Status Check    ${onu_device}
+    Should Be Equal    ${onu_state}    ${expected_status}
+    Should Be Equal    ${authentication_status}    ${expected_auth_status}
+    Run Keyword If    '${expected_status_message}' != '${EMPTY}'
+    ...    Should Be Equal    ${status_message}    ${expected_status_message}
+
+Validate ATT Workflow Driver SI DHCP State
+    [Documentation]    FIXME
+    [Arguments]    ${expected_status}    ${onu_device}
+    ${dhcp_state}=    Service Instance DHCP State Check    ${onu_device}
+    Should Be Equal    ${dhcp_state}    ${expected_status}
diff --git a/cord-robot/CORDRobot/rf-resources/CIAB.resource b/cord-robot/CORDRobot/rf-resources/CIAB.resource
new file mode 100644
index 0000000..e765d2d
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/CIAB.resource
@@ -0,0 +1,76 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library for CORD-in-a-Box
+Library           SSHLibrary
+Resource          utils.resource
+
+*** Keywords ***
+Execute Command on CIAB Server in Specific VM
+    [Documentation]    SSHs into ${HOST} where CIAB is running and executes a
+    ...    command in the Prod Vagrant VM where all the containers are running
+    [Arguments]    ${system}    ${vm}    ${cmd}
+    ...    ${user}=${VM_USER}    ${password}=${VM_PASS}
+    ...    ${prompt}=$    ${use_key}=True    ${strip_line}=True
+    ${conn_id}=    SSHLibrary.Open Connection    ${system}    prompt=${prompt}    timeout=300s
+    Run Keyword If    '${use_key}' == 'False'
+    ...    SSHLibrary.Login    ${user}    ${pass}
+    ...    ELSE
+    ...    SSHLibrary.Login With Public Key    ${user}    %{HOME}/.ssh/${SSH_KEY}    any
+    SSHLibrary.Write    ssh ${vm}
+    SSHLibrary.Read Until Prompt
+    SSHLibrary.Write    ${cmd}
+    ${output}=    SSHLibrary.Read Until Prompt
+    SSHLibrary.Close Connection
+    ${output_1}=    Run Keyword If    '${strip_line}' == 'True'    Get Line    ${output}    0
+    ${output}=    Set Variable If    '${strip_line}' == 'True'    ${output_1}    ${output}
+    [Return]    ${output}
+
+Execute Command on Compute Node in CIAB
+    [Documentation]    SSHs into ${system} where CIAB is running and executes a
+    ...    command in the Prod Vagrant VM where all the containers are running
+    [Arguments]    ${system}    ${node}    ${hostname}    ${cmd}
+    ...    ${user}=${VM_USER}    ${password}=${VM_PASS}    ${prompt}=$    ${use_key}=True
+    ${conn_id}=    SSHLibrary.Open Connection    ${system}    prompt=${prompt}    timeout=300s
+    # FIXME: Truthy comparison of strings
+    Run Keyword If    '${use_key}' == 'False'
+    ...    SSHLibrary.Login    ${user}    ${pass}
+    ...    ELSE
+    ...    SSHLibrary.Login With Public Key    ${user}    %{HOME}/.ssh/${SSH_KEY}    any
+    SSHLibrary.Write    ssh ${node}
+    SSHLibrary.Read Until Prompt
+    SSHLibrary.Write    ssh root@${hostname}
+    SSHLibrary.Read Until    \#
+    SSHLibrary.Write    ${cmd}
+    ${output}=    SSHLibrary.Read Until    \#
+    SSHLibrary.Close Connection
+    [Return]    ${output}
+
+Get Docker Logs
+    ##In Ciab, all containers are run in the prod vm so we must log into that
+    [Documentation]    Retrieves the logs of a docker container running inside
+    ...    given ${system}
+    [Arguments]    ${system}    ${container_id}
+    ...    ${user}=${USER}    ${password}=${PASSWD}    ${prompt}=prod:~$
+    ${conn_id}=    SSHLibrary.Open Connection    ${system}    prompt=$    timeout=300s
+    SSHLibrary.Login With Public Key    ${USER}    %{HOME}/.ssh/${SSH_KEY}    any
+    #SSHLibrary.Login    ${HOST_USER}    ${HOST_PASSWORD}
+    SSHLibrary.Write    ssh head1
+    SSHLibrary.Read Until    ${prompt}
+    SSHLibrary.Write    docker logs -t ${container_id}
+    ${container_logs}=    SSHLibrary.Read Until    ${prompt}
+    SSHLibrary.Close Connection
+    Log    ${container_logs}
+    [Return]    ${container_logs}
diff --git a/cord-robot/CORDRobot/rf-resources/DHCP.resource b/cord-robot/CORDRobot/rf-resources/DHCP.resource
new file mode 100644
index 0000000..feb1cbb
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/DHCP.resource
@@ -0,0 +1,72 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library for DHCP server and client functions
+Library           OperatingSystem
+Resource          utils.resource
+
+*** Keywords ***
+Send Dhclient Request
+    [Documentation]    Executes a dhclient against a particular interface on the RG (src)
+    [Arguments]    ${iface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    ${result}=    Login And Run Command On Remote System
+    ...    dhclient -nw ${iface}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    [Return]    ${result}
+
+Send Dhclient Request K8S
+    [Documentation]    Run dhclient inside rg container in K8s
+    ${RG_CONTAINER}=    Wait Until Keyword Succeeds    60s    1s
+    ...    Run    kubectl -n voltha get pod|grep "^rg[0-]"|cut -d' ' -f1
+    Run    kubectl -n voltha exec ${RG_CONTAINER} -- sed -i 's/timeout 300;/timeout 30;/' /etc/dhcp/dhclient.conf
+    Run    kubectl -n voltha exec ${RG_CONTAINER} -- ifconfig eth0 0.0.0.0
+    Run    kubectl -n voltha exec ${RG_CONTAINER} -- dhclient
+
+Add Default Route to Dst Gateway
+    [Documentation]    Adds an entry to the routing table on the RG (src)
+    # FIXME - Argument order of iface/ip/user/pass should match other functions
+    [Arguments]    ${src_gateway}    ${dst_subnet}    ${iface}    ${ip}
+    ...    ${user}    ${pass}=${None}    ${container_type}=${None}    ${container_name}=${None}
+    ${result}=    Login And Run Command On Remote System
+    ...    ip route add ${dst_subnet} via ${src_gateway} dev ${iface}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    [Return]    ${result}
+
+Check IPv4 Address on DHCP Client
+    [Documentation]    Check if the sepcified interface has an IPv4 address assigned
+    # FIXME - should ip_should_exist have a default value?
+    [Arguments]    ${ip_should_exist}    ${iface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    ${output}=    Login And Run Command On Remote System
+    ...    ip address show ${iface}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    # FIXME - ipv4_regex not set if container_type != K8S?
+    ${ipv4_regex}=    Set Variable If    '${container_type}' != 'K8S'
+    ...    \\b([0-9]{1,3}\\.){3}[0-9]{1,3}\\b    \\b(172)\\.(18)(\\.([0-9]{1,3})){2}\\b
+    # FIXME - use a boolean rather than string comparison against truthy value
+    Run Keyword If    '${ip_should_exist}' == 'True'    Should Match Regexp
+    ...    ${output}    ${ipv4_regex}
+    Run Keyword If    '${ip_should_exist}' == 'False'    Should Not Match Regexp
+    ...    ${output}    ${ipv4_regex}
+
+Start DHCP Server on Remote Host
+    [Documentation]    Start the 'dhcpd' process on specified network interface
+    ...    on a remote host
+    [Arguments]    ${interface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    ${result}=    Login And Run Command On Remote System
+    ...    dhcpd -cf /etc/dhcp/dhcpd.conf ${interface}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
diff --git a/cord-robot/CORDRobot/rf-resources/Kubernetes.resource b/cord-robot/CORDRobot/rf-resources/Kubernetes.resource
new file mode 100644
index 0000000..14857dd
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/Kubernetes.resource
@@ -0,0 +1,101 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library of functions related to kubectl and helm
+Library           SSHLibrary
+Library           Collections
+Library           String
+Library           OperatingSystem
+Library           RequestsLibrary
+Library           CORDRobot
+Resource          utils.resource
+
+*** Keywords ***
+Helm Chart is Removed
+    [Documentation]    Verify the specified helm chart has been removed
+    [Arguments]    ${helm_chart}
+    ${rc}=    Run And Return Rc
+    ...    helm ls -q | grep ${helm_chart}
+    Should Be Equal As Integers    ${rc}    1
+
+Kubernetes PODs in Namespace are Removed
+    [Documentation]    Verify all Kubernetes pods in specified namespace have been removed
+    [Arguments]    ${namespace}
+    ${rc}    ${output}=    Run And Return Rc And Output
+    ...    kubectl get pods --no-headers -n ${namespace}
+    Should Contain    ${output}    No resources found
+
+Kubernetes PODs in Namespace are Running
+    [Documentation]    Verify the number of Kubernetes pods that are running
+    ...    in specified namespace is as expected
+    [Arguments]    ${namespace}    ${pod_num}
+    ${rc}    ${output}=    Run And Return Rc And Output
+    ...    kubectl get pods -n ${namespace} | grep -i running | grep 1/1 | wc -l
+    Should Be Equal As Integers    ${output}    ${pod_num}
+
+Reinstall Voltha
+    [Documentation]    Remove voltha helm chart and wait
+    Run    helm delete --purge voltha
+    Wait Until Keyword Succeeds    60s    10s
+    ...    Helm Chart is Removed    voltha
+    Wait Until Keyword Succeeds    120s    10s
+    ...    Kubernetes PODs in Namespace are Removed    voltha
+    Sleep    10s
+    Run    helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com/
+    # FIXME - HELM_CHARTS_DIR should a parameter
+    Run    cd ${HELM_CHARTS_DIR}; helm dep up voltha
+    # FIXME - KUBERNETES_YAML should a parameter
+    Run    helm install -n voltha -f ${KUBERNETES_YAML} ${HELM_CHARTS_DIR}/voltha
+    Wait Until Keyword Succeeds    60s    10s
+    ...    Kubernetes PODs in Namespace are Running    voltha    ${VOLTHA_POD_NUM}
+    Sleep    10s
+
+Get Current Datetime On Kubernetes Node
+    [Documentation]    Get UTC datetime in RFC3339ish format
+    [Arguments]    ${ip}    ${user}    ${pass}
+    ${result}=    Login And Run Command On Remote System
+    ...    date -u +"%Y-%m-%dT%H:%M:%S.%NZ"    ${ip}    ${user}    ${pass}
+    # FIXME - is this needed? Does date return multiple lines?
+    ${result}=    Get Line    ${result}    0
+    [Return]    ${result}
+
+Log Kubernetes Container Log Since Time
+    [Documentation]    Returns the output of kubectl logs of a pod since timestamp
+    [Arguments]    ${datetime}    ${pod_prefix}
+    # FIXME - rc var isn't checked and then overwritten in this set of commands
+    ${rc}    ${namespace}=    Run And Return Rc And Output
+    ...    kubectl get pods --all-namespaces | grep ' ${pod_prefix}' | head -1 | awk '{print $1}'
+    ${rc}    ${pod_name}=    Run And Return Rc And Output
+    ...    kubectl get pods --all-namespaces | grep ' ${pod_prefix}' | head -1 | awk '{print $2}'
+    ${rc}    ${output}=    Run Keyword If    '${pod_prefix}' == 'onos'
+    ...    Run And Return Rc And Output
+    ...    kubectl logs --timestamps -n ${namespace} --since-time=${datetime} ${pod_name} -c onos
+    ...    ELSE    Run And Return Rc And Output
+    ...    kubectl logs --timestamps -n ${namespace} --since-time=${datetime} ${pod_name}
+    Log    ${output}
+
+Log Kubernetes Containers Logs Since Time
+    [Documentation]    Given a datetime and list of containers, print logs for those containers
+    [Arguments]    ${datetime}    ${pod_list}
+    FOR    ${pod_prefix}    IN    @{pod_list}
+        Log Kubernetes Container Log Since Time    ${datetime}    ${pod_prefix}
+    END
+
+Get Kubernetes POD Name By Prefix
+    [Documentation]    Return the first POD name that starts with the specified prefix
+    [Arguments]    ${prefix}
+    ${rc}    ${output}=    Run And Return Rc And Outputi
+    ...    kubectl get pods --all-namespaces | grep '${prefix}' | head -1 | awk '{print $2}'
+    [Return]    ${output}
diff --git a/cord-robot/CORDRobot/rf-resources/Network.resource b/cord-robot/CORDRobot/rf-resources/Network.resource
new file mode 100644
index 0000000..227f084
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/Network.resource
@@ -0,0 +1,71 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library for interacting with network config, such as
+...               interfaces, VLANs, routes on a host
+Resource          utils.resource
+
+*** Keywords ***
+Add Ip Address on Interface on Host
+    [Documentation]    Add IP addresss on interface on remote host
+    [Arguments]    ${ip_address}    ${interface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Login And Run Command On Remote System
+    ...    ip addr add ${ip_address} dev ${interface}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+
+Delete IP Addresses from Interface on Remote Host
+    [Documentation]    Remove specified IP address on an interface on remote host
+    [Arguments]    ${interface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Login And Run Command On Remote System
+    ...    ip addr flush dev ${interface}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+
+Add Double Vlan Interface on Host
+    [Documentation]    Add double S/C-VLAN tagging on an interface on remote host
+    [Arguments]    ${interface}    ${stag}    ${ctag}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Login And Run Command On Remote System
+    ...    ip link add link ${interface} name ${interface}.${stag} type vlan id ${stag}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    Login And Run Command On Remote System
+    ...    ip link set ${interface}.${stag} up
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    Login And Run Command On Remote System
+    ...    ip link add link ${interface}.${stag} name ${interface}.${stag}.${ctag} type vlan id ${ctag}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    Login And Run Command On Remote System
+    ...    ip link set ${interface}.${stag}.${ctag} up
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    Login And Run Command On Remote System
+    ...    ifconfig ${interface}.${stag}.${ctag}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+
+Delete Interface on Remote Host
+    [Documentation]    Deleted interface (link) on remote host
+    [Arguments]    ${interface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Login And Run Command On Remote System
+    ...    ip link del ${interface}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+
+Add Route to Remote Host
+    [Documentation]    Add route on remote host given subnet and gateway
+    [Arguments]    ${subnet}    ${gateway}    ${interface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Login And Run Command On Remote System
+    ...    ip route add ${subnet} via ${gateway} dev ${interface}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
diff --git a/cord-robot/CORDRobot/rf-resources/OLT.resource b/cord-robot/CORDRobot/rf-resources/OLT.resource
new file mode 100644
index 0000000..bdfc1a1
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/OLT.resource
@@ -0,0 +1,87 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library of functions related to OLT
+Library           SSHLibrary
+Library           Collections
+Library           String
+Library           OperatingSystem
+Library           RequestsLibrary
+Library           CORDRobot
+
+*** Keywords ***
+Openolt is Up
+    [Documentation]    Verify that openolt process is started and ready to connect to voltha
+    [Arguments]    ${ip}    ${user}    ${pass}    ${prompt}=~#
+    Check Remote File Contents    True
+    ...    /var/log/openolt.log    oper_state: up
+    ...    ${ip}    ${user}    ${pass}    prompt=${prompt}
+
+OLT Status Check
+    [Documentation]    Returns "operational_status" and "admin_status" of a particular
+    ...    OLT device from "olt device list"
+    [Arguments]    ${olt_device}
+    ${json_result}=    CORDRobot.ApiGet    VOLT_DEVICE
+    Log    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict
+    ...    ${json_result_list}    host    ${olt_device}
+    ${operational_status}=    Get From Dictionary    ${getJsonDict}    oper_status
+    ${admin_status}=    Get From Dictionary    ${getJsonDict}    admin_state
+    [Return]    ${operational_status}    ${admin_status}
+
+Validate OLT States
+    [Documentation]    Check that OLT has the expected 'operational_status' and 'admin_status'
+    [Arguments]    ${expected_op_status}    ${expected_admin_status}    ${olt_device}
+    ${operational_status}    ${admin_status}    OLT Status Check    ${olt_device}
+    Should Be Equal    ${operational_status}    ${expected_op_status}
+    Should Be Equal    ${admin_status}    ${expected_admin_status}
+
+Get VOLTHA Status
+    [Documentation]    Obtain and log output of VOLTHA diagnostic commands
+    ${resp}=    CORD Get    ${VOLT_DEVICE}
+    ${jsondata}=    To Json    ${resp.content}
+    Log    ${jsondata}
+    ${length}=    Get Length    ${jsondata['items']}
+    FOR    ${INDEX}    IN RANGE    0    ${length}
+        ${value}=    Get From List    ${jsondata['items']}    ${INDEX}
+        ${olt_device_id}=    Get From Dictionary    ${value}    device_id
+        ${logical_device_id}=    Get From Dictionary    ${value}    of_id
+    END
+    Set Suite Variable    ${olt_device_id}
+    Set Suite Variable    ${logical_device_id}
+    CORDRobot.write_log_of_voltha_cli_comand    /tmp
+    ...    voltha_devices.log    devices
+    ...    host=${server_ip}
+    CORDRobot.write_log_of_voltha_cli_comand    /tmp
+    ...    logical_devices.log    logical_device ${logical_device_id}
+    ...    voltha_logical_ports.log    ports
+    ...    voltha_logical_flows.log    flow
+    ...    host=${server_ip}
+    CORDRobot.write_log_of_voltha_cli_comand    /tmp
+    ...    devices.log    device ${olt_device_id}
+    ...    voltha_olt_ports.log    ports
+    ...    voltha_olt_flows.log    flows
+    ...    host=${server_ip}
+    ${voltha_devices_log}=    Get Binary File    /tmp/voltha_devices.log
+    ${devices_flows}=    Get Binary File    /tmp/voltha_olt_flows.log
+    ${device_ports}=    Get Binary File    /tmp/voltha_olt_ports.log
+    ${logical_devices}=    Get Binary File    /tmp/voltha_logical_flows.log
+    ${l_device_ports}=    Get Binary File    /tmp/voltha_logical_ports.log
+    Log    ${voltha_devices_log}
+    Log    ${devices_flows}
+    Log    ${device_ports}
+    Log    ${logical_devices}
+    Log    ${l_device_ports}
diff --git a/cord-robot/CORDRobot/rf-resources/ONOS.resource b/cord-robot/CORDRobot/rf-resources/ONOS.resource
new file mode 100644
index 0000000..dfe519b
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/ONOS.resource
@@ -0,0 +1,118 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+# onos common functions
+
+*** Settings ***
+Documentation     Library for ONOS Functions
+Library           SSHLibrary
+
+*** Keywords ***
+Execute ONOS CLI Command
+    [Documentation]    Establishes an ssh connection to ONOS contoller and executes a command
+    [Arguments]    ${host}    ${port}    ${cmd}    ${user}=karaf    ${pass}=karaf
+    ${conn_id}=    SSHLibrary.Open Connection    ${host}    port=${port}    timeout=300s
+    SSHLibrary.Login    ${user}    ${pass}
+    @{result_values}    SSHLibrary.Execute Command    ${cmd}    return_rc=True
+    ...    return_stderr=True    return_stdout=True
+    ${output}    Set Variable    @{result_values}[0]
+    Log    ${output}
+    Should Be Empty    @{result_values}[1]
+    Should Be Equal As Integers    @{result_values}[2]    0
+    SSHLibrary.Close Connection
+    [Return]    ${output}
+
+Validate XConnect in ONOS
+    [Documentation]    Check if Fabric Crossconnnect matches exists value
+    [Arguments]    ${server_ip}    ${stag}    ${exists}=True    ${port}=30120
+    # FIXME: use Robot-based JSON manipulation, shorten line
+    ${rc}=    Run And Return RC
+    ...    http -a karaf:karaf GET http://${server_ip}:${port}/onos/segmentrouting/xconnect|jq -r '.xconnects[].vlanId'|grep ${stag}
+    Run Keyword If    '${exists}' == 'True'
+    ...    Should Be Equal As Integers    ${rc}    0
+    ...    ELSE
+    ...    Should Be Equal As Integers    ${rc}    1
+
+Get ONOS Status
+    [Documentation]    Obtain and log output of ONOS diagnostic commands
+    [Arguments]    ${server_ip}=${None}    ${server_port}=30115
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_apps.log    apps -a -s
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_apps}    Get Binary File    /tmp/onos_apps.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_devices.log    devices
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_devices}    Get Binary File    /tmp/onos_devices.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_ports.log    ports
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_ports}    Get Binary File    /tmp/onos_ports.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_flows.log    flows -s
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_flows}    Get Binary File    /tmp/onos_flows.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_meters.log    meters
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_meters}    Get Binary File    /tmp/onos_meters.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_volt_prog_subscribers.log    volt-programmed-subscribers
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_volt_prog_subscribers}    Get Binary File    /tmp/onos_volt_prog_subscribers.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_volt_prog_meters.log    volt-programmed-meters
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_volt_prog_meters}    Get Binary File    /tmp/onos_volt_prog_meters.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_volt_bp_meters.log    volt-bpmeter-mappings
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_volt_bp_meters}    Get Binary File    /tmp/onos_volt_bp_meters.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_dhcpl2.log    dhcpl2relay-allocations
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_dhcpl2}    Get Binary File    /tmp/onos_dhcpl2.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_aaa_users.log    aaa-users
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_aaa_users}    Get Binary File    /tmp/onos_aaa_users.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_netcfg.log    netcfg
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_netcfg}    Get Binary File    /tmp/onos_netcfg.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_groups.log    groups
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_groups}    Get Binary File    /tmp/onos_groups.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_hosts.log    hosts
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_hosts}    Get Binary File    /tmp/onos_hosts.log
+    CORDRobot.write_log_of_onos_cli_command    /tmp
+    ...    onos_links.log    links
+    ...    host=${server_ip}    port=${server_port}
+    ${onos_links}    Get Binary File    /tmp/onos_links.log
+    Log    ${onos_apps}
+    Log    ${onos_devices}
+    Log    ${onos_ports}
+    Log    ${onos_flows}
+    Log    ${onos_meters}
+    Log    ${onos_aaa_users}
+    Log    ${onos_volt_prog_subscribers}
+    Log    ${onos_volt_prog_meters}
+    Log    ${onos_volt_bp_meters}
+    Log    ${onos_hosts}
+    Log    ${onos_dhcpl2}
+    Log    ${onos_netcfg}
+    Log    ${onos_groups}
+    Log    ${onos_links}
diff --git a/cord-robot/CORDRobot/rf-resources/ONU.resource b/cord-robot/CORDRobot/rf-resources/ONU.resource
new file mode 100644
index 0000000..7ec8d98
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/ONU.resource
@@ -0,0 +1,69 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library to check the status in ONU List
+Library           Collections
+Library           String
+Library           OperatingSystem
+Library           XML
+Library           RequestsLibrary
+Library           CORDRobot
+
+*** Keywords ***
+ONU Status Check
+    [Documentation]    Returns "operational_status" and "admin_status" of
+    ...    a particular ONU device from "onu device list"
+    [Arguments]    ${onu_device}
+    ${json_result}=    CORDRobot.ApiGet    ONU_DEVICE
+    Log    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict    ${json_result_list}
+    ...    serial_number    ${onu_device}
+    ${operational_status}=    Get From Dictionary    ${getJsonDict}    oper_status
+    ${admin_status}=    Get From Dictionary    ${getJsonDict}    admin_state
+    [Return]    ${operational_status}    ${admin_status}
+
+Create ONU Device
+    [Documentation]    Sends a POST to create an att whitelist in XOS
+    [Arguments]    ${device_list}    ${list_index}
+    ${dlist} =    Get Variable Value    ${device_list}
+    ${onu_dictionary}=    CORDRobot.listToDict    ${dlist}    ${list_index}
+    ${api_result}=    CORDRobot.ApiPost    ONU_DEVICE    ${onu_dictionary}
+    Should Be True    ${api_result}
+
+Retrieve ONU Device
+    [Documentation]    Returns the onu device id based on the onu's serial number
+    [Arguments]    ${serial_number}
+    ${json_result}=    CORDRobot.ApiGet    ONU_DEVICE
+    Log    ${json_result}
+    Log To Console    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict    ${json_result_list}
+    ...    serial_number    ${serial_number}
+    ${id}=    Get From Dictionary    ${getJsonDict}    id
+    [Return]    ${id}
+
+Delete ONU Device
+    [Arguments]    ${id}
+    [Documentation]    Sends a DELETE to delete an onu device in XOS
+    ${api_result}=    CORDRobot.ApiChameleonDelete    ONU_DEVICE    ${id}
+    Should Be True    ${api_result}
+
+Validate ONU States
+    [Documentation]    Check that ONU has the expected 'operational_status' and 'admin_status'
+    [Arguments]    ${expected_op_status}    ${expected_admin_status}    ${onu_device}
+    ${operational_status}    ${admin_status}    ONU Status Check    ${onu_device}
+    Should Be Equal    ${operational_status}    ${expected_op_status}
+    Should Be Equal    ${admin_status}    ${expected_admin_status}
diff --git a/cord-robot/CORDRobot/rf-resources/Subscriber.resource b/cord-robot/CORDRobot/rf-resources/Subscriber.resource
new file mode 100644
index 0000000..232d87a
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/Subscriber.resource
@@ -0,0 +1,245 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library of functions related a subscriber (RG)
+Resource          ATTWorkFlowDriver.resource
+Resource          DHCP.resource
+Resource          Network.resource
+Resource          ONOS.resource
+Resource          utils.resource
+Resource          XOS.resource
+
+*** Keywords ***
+Subscriber Status Check
+    [Documentation]    Returns Status from Subscribers List for a particular ONU device
+    [Arguments]    ${onu_device}
+    ${json_result}=    CORDRobot.ApiGet    VOLT_SUBSCRIBER
+    Log    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict    ${json_result_list}
+    ...    onu_device    ${onu_device}
+    ${status}=    Get From Dictionary    ${getJsonDict}    status
+    [Return]    ${status}
+
+Validate Subscriber Status
+    [Documentation]    Check that a subscriber has the expected status
+    [Arguments]    ${expected_status}    ${onu_device}    ${accepted_status}=${EMPTY}
+    ${status}    Subscriber Status Check    ${onu_device}
+    Run Keyword If    '${accepted_status}' == '${EMPTY}'
+    ...    Should Be Equal    ${status}    ${expected_status}
+    ...    ELSE
+    ...    Should Contain Any    ${status}    ${expected_status}    ${accepted_status}
+
+Create Subscriber
+    [Documentation]    Sends a POST to create a subscriber in XOS
+    [Arguments]    ${subscriber_list}    ${list_index}
+    ${slist} =    Get Variable Value    ${subscriber_list}
+    ${subscriber_dictionary}=    CORDRobot.listToDict    ${slist}    ${list_index}
+    ${api_result}=    CORDRobot.ApiPost    VOLT_SUBSCRIBER    ${subscriber_dictionary}
+    Should Be True    ${api_result}
+    ${Subscriber_id}=    Get From Dictionary    ${api_result}    id
+    Set Global Variable    ${Subscriber_id}
+    [Return]    ${Subscriber_id}
+
+Retrieve Subscriber
+    [Documentation]    Returns the subscriber id based on the subscriber's C-Tag
+    [Arguments]    ${ctag}
+    ${json_result}=    CORDRobot.ApiGet    VOLT_SUBSCRIBER
+    Log    ${json_result}
+    ${json_result_list}=    Get From dictionary    ${json_result}    items
+    ${getJsonDict}=    CORDRobot.getDictFromListOfDict    ${json_result_list}
+    ...    c_tag    ${ctag}
+    ${id}=    Get From Dictionary    ${getJsonDict}    id
+    [Return]    ${id}
+
+Delete Subscriber
+    [Documentation]    Deletes a given subscriber based on its c_tag
+    [Arguments]    ${ctag}
+    ${id}=    Retrieve Subscriber    ${ctag}
+    ${api_result}=    CORDRobot.ApiChameleonDelete    VOLT_SUBSCRIBER    ${id}
+    Should Be True    ${api_result}
+
+Send EAPOL Message
+    [Documentation]    Executes a particular auth request on the RG via wpa_supplicant
+    ...    Requested packet should exist on src.
+    [Arguments]    ${iface}    ${conf_file}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Login And Run Command On Remote System
+    ...    rm -f /tmp/wpa.log; wpa_supplicant -B -i ${iface} -Dwired -c /etc/wpa_supplicant/${conf_file} -f /tmp/wpa.log
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+
+Validate Authentication
+    [Documentation]    Executes a particular auth request on the RG and verifies if it succeeds
+    ...    auth_pass determines if authentication should pass
+    [Arguments]    ${auth_pass}    ${iface}    ${conf_file}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Send EAPOL Message    ${iface}    ${conf_file}    ${ip}    ${user}    ${pass}
+    ...    ${container_type}    ${container_name}
+    # FIXME: Use an If/Else block, not Three separate checks, bools instead of truthy strings
+    Run Keyword If    '${auth_pass}' == 'True'
+    ...    Wait Until Keyword Succeeds    120s    2s
+    ...    Check Remote File Contents    True
+    ...    /tmp/wpa.log    authentication completed successfully
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    Run Keyword If    '${auth_pass}' == 'False'
+    ...    Sleep    20s
+    Run Keyword If    '${auth_pass}' == 'False'
+    ...    Check Remote File Contents    False
+    ...    /tmp/wpa.log    authentication completed successfully
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+
+Run Multicast Client
+    [Documentation]    Executes mcjoin (a simple multicast client) on the RG.
+    [Arguments]    ${iface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Login And Run Command On Remote System
+    ...    rm -f /tmp/mcjoin.log; timeout 10 mcjoin -c 5 -i eth0 > /tmp/mcjoin.log || true
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+
+Validate Multicast
+    [Documentation]    Executes a particular auth request on the RG and verifies
+    ...    if it succeeds. auth_pass determines if authentication should pass
+    [Arguments]    ${auth_pass}    ${iface}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    Run Multicast Client    ${iface}    ${ip}    ${user}    ${pass}
+    ...    ${container_type}    ${container_name}
+    Run Keyword If    '${auth_pass}' == 'True'
+    ...    Check Remote File Contents    True
+    ...    /tmp/mcjoin.log    Received total: 5 packets
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    Run Keyword If    '${auth_pass}' == 'False'
+    ...    Check Remote File Contents    True
+    ...    /tmp/mcjoin.log    Received total: 0 packets
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+
+Validate DHCP and Ping
+    [Documentation]    Check that DHCP address has been acquired and Ping works
+    [Arguments]    ${dhcp_should_pass}    ${ping_should_pass}
+    ...    ${src_iface}    ${s_tag}    ${c_tag}
+    ...    ${dst_dp_ip}    ${src_ip}    ${src_user}    ${src_pass}=${None}
+    ...    ${src_container_type}=${None}    ${src_container_name}=${None}
+    ...    ${dst_dp_iface}=${None}    ${dst_ip}=${None}
+    ...    ${dst_user}=${None}    ${dst_pass}=${None}
+    ...    ${dst_container_type}=${None}    ${dst_container_name}=${None}
+    Run Keyword If    '${dst_ip}' != '${None}'    Run Keywords
+    ...    Add Double Vlan Interface on Host    ${dst_dp_iface}    ${s_tag}    ${c_tag}
+    ...    ${dst_ip}    ${dst_user}    ${dst_pass}    ${dst_container_type}    ${dst_container_name}
+    ...    AND
+    ...    Add IP Address on Interface on Host
+    ...    ${dst_dp_ip}/24    ${dst_dp_iface}.${s_tag}.${c_tag}
+    ...    ${dst_ip}    ${dst_user}    ${dst_pass}    ${dst_container_type}    ${dst_container_name}
+    ...    AND
+    ...    Start DHCP Server on Remote Host    ${dst_dp_iface}.${s_tag}.${c_tag}    ${dst_ip}
+    ...    ${dst_user}    ${dst_pass}    ${dst_container_type}    ${dst_container_name}
+    Run Keyword If    '${src_container_type}' != 'K8S'
+    ...    Send Dhclient Request    ${src_iface}    ${src_ip}
+    ...    ${src_user}    ${src_pass}    ${src_container_type}    ${src_container_name}
+    ...    ELSE
+    ...    Send Dhclient Request K8S
+    Run Keyword If    '${dhcp_should_pass}' == 'True'
+    ...    Wait Until Keyword Succeeds    90s    5s
+    ...    Check IPv4 Address on DHCP Client    True    ${src_iface}    ${src_ip}
+    ...    ${src_user}    ${src_pass}    ${src_container_type}    ${src_container_name}
+    Run Keyword If    '${dhcp_should_pass}' == 'False'
+    ...    Sleep    15s
+    Run Keyword If    '${dhcp_should_pass}' == 'False'
+    ...    Check IPv4 Address on DHCP Client    False    ${src_iface}    ${src_ip}
+    ...    ${src_user}    ${src_pass}    ${src_container_type}    ${src_container_name}
+    Run Keyword If    '${ping_should_pass}' == 'True'
+    ...    Wait Until Keyword Succeeds    60s    2s
+    ...    Check Ping    True    ${dst_dp_ip}    ${src_iface}    ${src_ip}
+    ...    ${src_user}    ${src_pass}    ${src_container_type}    ${src_container_name}
+    ...    ELSE
+    ...    Wait Until Keyword Succeeds    60s    2s
+    ...    Check Ping    False    ${dst_dp_ip}    ${src_iface}    ${src_ip}
+    ...    ${src_user}    ${src_pass}    ${src_container_type}    ${src_container_name}
+
+Validate Subscriber Service Chain
+    [Documentation]    Check if serial number is list of subcribed_links_ids
+    [Arguments]    ${serial_no}    ${expected}=True
+    ${resp}=    CORD Get    ${VOLT_SUBSCRIBER}
+    ${jsondata}=    To Json    ${resp.content}
+    Log    ${jsondata}
+    ${length}=    Get Length    ${jsondata['items']}
+    FOR    ${INDEX}    IN RANGE    0    ${length}
+        ${value}=    Get From List    ${jsondata['items']}    ${INDEX}
+        ${sl}=    Get From Dictionary    ${value}    subscribed_links_ids
+        ${result}    ${slinks}=    Run Keyword And Ignore Error
+        ...    Get From List    ${sl}    0
+        ${sn}=    Get From Dictionary    ${value}    onu_device
+        Run Keyword If    '${sn}' == '${serial_no}'    Exit For Loop
+    END
+
+Validate Fabric CrossConnect SI
+    [Documentation]    Build list of s_tags in fabric crossconnect
+    [Arguments]    ${stag}    ${expected}=True
+    ${resp}=    CORD Get    ${FABRIC_CROSSCONNECT_SERVICEINSTANCES}
+    ${jsondata}=    To Json    ${resp.content}
+    Log    ${jsondata}
+    ${length}=    Get Length    ${jsondata['items']}
+    @{tags}=    Create List
+    FOR    ${INDEX}    IN RANGE    0    ${length}
+        ${value}=    Get From List    ${jsondata['items']}    ${INDEX}
+        ${tag}=    Get From Dictionary    ${value}    s_tag
+        Append To List    ${tags}    ${tag}
+    END
+
+Validate Subscriber Count
+    [Documentation]    Check if subscriber count matches passed value
+    [Arguments]    ${expected_no}
+    ${resp}=    CORD Get    ${VOLT_SUBSCRIBER}
+    ${jsondata}=    To Json    ${resp.content}
+    Log    ${jsondata}
+    ${length}=    Get Length    ${jsondata['items']}
+    Should Be Equal As Integers    ${length}    ${expected_no}
+
+Subscriber Ready to Authenticate
+    [Documentation]    Check if subscriber is in awaiting-auth state
+    [Arguments]    ${onu_device}
+    Wait Until Keyword Succeeds    60s    15s
+    ...    Validate ONU States    ACTIVE    ENABLED    ${onu_device}
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate ATT Workflow Driver SI    ENABLED    AWAITING    ${onu_device}
+    ...    ONU has been validated - Awaiting Authentication
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate Subscriber Status    awaiting-auth    ${onu_device}
+
+Subscriber Provisioned
+    [Documentation]    Check if subscriber has successfully authenticated
+    [Arguments]    ${server_ip}    ${onu_device}    ${stag}
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate ATT Workflow Driver SI    ENABLED    APPROVED    ${onu_device}
+    ...    ONU has been validated - Authentication succeeded
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate Subscriber Status    enabled    ${onu_device}
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate Subscriber Service Chain    ${onu_device}    True
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate XConnect in ONOS    ${server_ip}    ${stag}    True
+
+Subscriber Service Chain Created
+    [Documentation]    Check if subscriber service chain has been created
+    [Arguments]    ${onu_device}    ${stag}
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate ATT Workflow Driver SI    ENABLED    APPROVED    ${onu_device}
+    ...    ONU has been validated - Authentication succeeded
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate Subscriber Status    enabled    ${onu_device}
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate Subscriber Service Chain    ${onu_device}    True
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate Fabric CrossConnect SI    ${stag}    True
+    Wait Until Keyword Succeeds    60s    2s
+    ...    Validate XConnect in ONOS    ${server_ip}    ${stag}    True
diff --git a/cord-robot/CORDRobot/rf-resources/XOS.resource b/cord-robot/CORDRobot/rf-resources/XOS.resource
new file mode 100644
index 0000000..fd03ea9
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/XOS.resource
@@ -0,0 +1,86 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library for interacting with XOS
+Library           String
+Library           RequestsLibrary
+
+*** Keywords ***
+CORD Get
+    [Documentation]    Make a GET call to XOS
+    [Arguments]    ${service}
+    ${resp}=    Get Request    ${server_ip}    ${service}
+    Log    ${resp.content}
+    Should Be Equal As Strings    ${resp.status_code}    200
+    [Return]    ${resp}
+
+CORD Post
+    [Documentation]    Make a POST call to XOS
+    [Arguments]    ${service}    ${data}
+    ${data}=    Evaluate    json.dumps(${data})    json
+    ${resp}=    Post Request    ${SERVER_IP}    uri=${service}    data=${data}
+    Log    ${resp.content}
+    Should Be Equal As Strings    ${resp.status_code}    200
+    [Return]    ${resp}
+
+CORD Put
+    [Documentation]    Make a PUT call to XOS
+    [Arguments]    ${service}    ${data}    ${data_id}
+    ${data}=    Evaluate    json.dumps(${data})    json
+    ${resp}=    Put Request    ${SERVER_IP}    uri=${service}/${data_id}    data=${data}
+    Log    ${resp.content}
+    Should Be Equal As Strings    ${resp.status_code}    200
+    ${id}=    Get From Dictionary    ${resp.json()}    id
+    Set Suite Variable    ${id}
+    [Return]    ${resp}
+
+CORD Delete
+    [Documentation]    Make a DELETE call to XOS
+    [Arguments]    ${service}    ${data_id}
+    ${resp}=    Delete Request    ${SERVER_IP}    uri=${service}/${data_id}
+    Log    ${resp.content}
+    Should Be Equal As Strings    ${resp.status_code}    200
+    [Return]    ${resp}
+
+Get Service Owner Id
+    [Documentation]    Find the id of owner of an XOS service
+    [Arguments]    ${service}
+    ${resp}=    CORD Get    ${service}
+    ${jsondata}=    To Json    ${resp.content}
+    log    ${jsondata}
+    ${length}=    Get Length    ${jsondata['items']}
+    # FIXME: should this break after finding the first item?
+    FOR    ${INDEX}    IN RANGE    0    ${length}
+        ${value}=    Get From List    ${jsondata['items']}    ${INDEX}
+        ${id}=    Get From Dictionary    ${value}    id
+    END
+    [Return]    ${id}
+
+Clean Up Objects
+    [Documentation]    Delete all objects in XOS data model
+    [Arguments]    ${model_api}
+    @{ids}=    Create List
+    ${resp}=    CORD Get    ${model_api}
+    ${jsondata}=    To Json    ${resp.content}
+    Log    ${jsondata}
+    ${length}=    Get Length    ${jsondata['items']}
+    FOR    ${INDEX}    IN RANGE    0    ${length}
+        ${value}=    Get From List    ${jsondata['items']}    ${INDEX}
+        ${id}=    Get From Dictionary    ${value}    id
+        Append To List    ${ids}    ${id}
+    END
+    FOR    ${i}    IN    @{ids}
+        CORD Delete    ${model_api}    ${i}
+    END
diff --git a/cord-robot/CORDRobot/rf-resources/utils.resource b/cord-robot/CORDRobot/rf-resources/utils.resource
new file mode 100644
index 0000000..d85fa20
--- /dev/null
+++ b/cord-robot/CORDRobot/rf-resources/utils.resource
@@ -0,0 +1,222 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+*** Settings ***
+Documentation     Library for various utilities
+Library           SSHLibrary
+Library           String
+Library           DateTime
+Library           Process
+Library           Collections
+Library           RequestsLibrary
+
+*** Keywords ***
+Login To Remote System
+    [Documentation]    SSH into a remote host (and into a container on that host if container_type
+    ...    and container_name are specified) and returns connection ID
+    [Arguments]    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    ...    ${prompt}=~$    ${prompt_timeout}=15s    ${container_prompt}=#
+    ${conn_id}=    SSHLibrary.Open Connection    ${ip}    prompt=${prompt}    timeout=${prompt_timeout}
+    Run Keyword If    '${pass}' != '${None}'
+    ...    SSHLibrary.Login    ${user}    ${pass}
+    ...    ELSE
+    ...    SSHLibrary.Login With Public Key    ${user}    %{HOME}/.ssh/id_rsa
+    # Login to the lxc container
+    Run Keyword If    '${container_type}' == 'LXC'    Run Keywords
+    ...    SSHLibrary.Write    lxc exec ${container_name} /bin/bash
+    ...    AND
+    ...    SSHLibrary.Read Until    ${container_prompt}
+    ...    AND
+    ...    SSHLibrary.Set Client Configuration    prompt=${container_prompt}
+    # Login to the k8s container
+    # FIXME: This fails if /bin/bash isn't installed in the container, run on command
+    Run Keyword If    '${container_type}' == 'K8S'    Run Keywords
+    ...    SSHLibrary.Write    kubectl -n $(kubectl get pods --all-namespaces | grep ${container_name} | awk '{print $1}') exec ${container_name} -it /bin/bash
+    ...    AND
+    ...    SSHLibrary.Read Until    ${container_prompt}
+    ...    AND
+    ...    SSHLibrary.Set Client Configuration    prompt=${container_prompt}
+    # Try to switch to root user
+    # FIXME: Is is useful in the LXC/K8S cases?
+    ${conn}=    SSHLibrary.Get Connection    ${conn_id}
+    Run Keyword And Ignore Error
+    ...    SSHLibrary.Write    sudo -s
+    ${output}=    SSHLibrary.Read Until Regexp    \#|${conn.prompt}|password for ${user}:
+    Run Keyword If    'password for ${user}:' not in '''${output}'''
+    ...    Return From Keyword    ${conn_id}
+    SSHLibrary.Set Client Configuration    prompt=\#
+    SSHLibrary.Write    ${pass}
+    SSHLibrary.Read Until Prompt
+    [Return]    ${conn_id}
+
+Logout From Remote System
+    [Documentation]    Exit from the SSH session to a remote host
+    [Arguments]    ${conn_id}
+    SSHLibrary.Switch Connection    ${conn_id}
+    SSHLibrary.Close Connection
+
+Run Command On Remote System
+    [Documentation]    Executes a command on remote host and returns output
+    [Arguments]    ${cmd}    ${conn_id}    ${user}    ${pass}=${None}
+    ${conn}=    SSHLibrary.Get Connection    ${conn_id}
+    SSHLibrary.Switch Connection    ${conn_id}
+    SSHLibrary.Write    ${cmd}
+    ${output}=    SSHLibrary.Read Until Regexp    ${conn.prompt}|password for ${user}:
+    Run Keyword If    'password for ${user}:' not in '''${output}'''
+    ...    Return From Keyword    ${output}
+    SSHLibrary.Write    ${pass}
+    ${output}=    SSHlibrary.Read Until Prompt
+    [Return]    ${output}
+
+Login And Run Command On Remote System
+    [Documentation]    SSH into a remote host (and into a container on that host if container_type
+    ...    and container_name are specified), switch to root user, executes command, return output
+    [Arguments]    ${cmd}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    ...    ${prompt}=~$    ${prompt_timeout}=50s    ${container_prompt}=#
+    ${conn_id}    Login To Remote System    ${ip}    ${user}    ${pass}
+    ...    ${container_type}    ${container_name}
+    ...    ${prompt}    ${prompt_timeout}    ${container_prompt}
+    ${output}=    Run Command On Remote System    ${cmd}    ${conn_id}    ${user}    ${pass}
+    Log    ${output}
+    # FIXME: Look into persisting SSH connection rather than tearing it up/down repeatedly
+    Logout From Remote System    ${conn_id}
+    [Return]    ${output}
+
+Execute Command Locally
+    [Documentation]    Superfluous, use the 'Run' keyword instead which this wraps
+    [Arguments]    ${cmd}
+    ${output}=    Run    ${cmd}
+    [Return]    ${output}
+
+Get Docker Container ID
+    [Documentation]    Retrieves the id of the requested docker container running locally
+    [Arguments]    ${container_name}
+    ${container_id}=    Run    docker ps | grep ${container_name} | awk '{print $1}'
+    Log    ${container_id}
+    [Return]    ${container_id}
+
+Remove Value From List
+    [Documentation]    Removes a value from a dictionary
+    [Arguments]    ${list}    ${val}
+    ${length}=    Get Length    ${list}
+    FOR    ${INDEX}    IN RANGE    0    ${length}
+        Log    ${list[${INDEX}]}
+        ${value}=    Get Dictionary Values    ${list[${INDEX}]}
+        Log    ${value[0]}
+        Run Keyword If    '${value[0]}' == '${val}'    Remove From List    ${list}    ${INDEX}
+        Run Keyword If    '${value[0]}' == '${val}'    Exit For Loop
+    END
+
+Test Ping
+    [Documentation]    SSH's into src and attempts to ping dest.
+    ...    Status determines if ping should pass | fail
+    [Arguments]    ${status}    ${src}    ${user}    ${pass}
+    ...    ${dest}    ${interface}    ${prompt}=$    ${prompt_timeout}=60s
+    ${conn_id}=    SSHLibrary.Open Connection
+    ...    ${src}    prompt=${prompt}    timeout=${prompt_timeout}
+    SSHLibrary.Login    ${user}    ${pass}
+    ${result}=    SSHLibrary.Execute Command
+    ...    ping -I ${interface} -c 5 ${dest}
+    SSHLibrary.Close Connection
+    Log    ${result}
+    Run Keyword If    '${status}' == 'PASS'
+    ...    Should Contain    ${result}    64 bytes
+    Run Keyword If    '${status}' == 'PASS'
+    ...    Should Contain    ${result}    0% packet loss
+    Run Keyword If    '${status}' == 'PASS'
+    ...    Should Not Contain    ${result}    100% packet loss
+    Run Keyword If    '${status}' == 'PASS'
+    ...    Should Not Contain    ${result}    80% packet loss
+    Run Keyword If    '${status}' == 'PASS'
+    ...    Should Not Contain    ${result}    60% packet loss
+    Run Keyword If    '${status}' == 'PASS'
+    ...    Should Not Contain    ${result}    40% packet loss
+    Run Keyword If    '${status}' == 'PASS'
+    ...    Should Not Contain    ${result}    20% packet loss
+    Run Keyword If    '${status}' == 'PASS'
+    ...    Should Not Contain    ${result}    Destination Host Unreachable
+    Run Keyword If    '${status}' == 'FAIL'
+    ...    Should Not Contain    ${result}    64 bytes
+    Run Keyword If    '${status}' == 'FAIL'
+    ...    Should Contain    ${result}    100% packet loss
+    Log To Console    \n ${result}
+
+Check Ping Result
+    [Documentation]    Check the output of the 'ping' command
+    [Arguments]    ${reachable}    ${result}
+    Run Keyword If    '${reachable}' == 'True'
+    ...    Should Contain    ${result}    64 bytes
+    Run Keyword If    '${reachable}' == 'True'
+    ...    Should Contain Any    ${result}    0% packet loss    0.0% packet loss
+    Run Keyword If    '${reachable}' == 'True'
+    ...    Should Not Contain Any    ${result}    100% packet loss    100.0% packet loss
+    Run Keyword If    '${reachable}' == 'False'
+    ...    Should Not Contain    ${result}    64 bytes
+    Run Keyword If    '${reachable}' == 'False'
+    ...    Should Contain Any    ${result}    100% packet loss    100.0% packet loss
+
+Check Ping
+    [Documentation]    Run 'ping' on remote system and check output
+    [Arguments]    ${ping_should_pass}    ${dst_ip}    ${iface}    ${ip}
+    ...    ${user}    ${pass}=${None}    ${container_type}=${None}    ${container_name}=${None}
+    ${result}=    Login And Run Command On Remote System
+    ...    ping -I ${iface} -c 3 ${dst_ip}
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    Check Ping Result    ${ping_should_pass}    ${result}
+
+Check Remote System Reachability
+    [Documentation]    Check if the specified IP address is reachable or not
+    [Arguments]    ${reachable}    ${ip}
+    ${result}=    Run    ping -c 3 ${ip}
+    Check Ping Result    ${reachable}    ${result}
+
+Kill Linux Process
+    [Documentation]    Kill a process on a remote system
+    [Arguments]    ${process}    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}
+    ${rc}=    Login And Run Command On Remote System
+    ...    kill $(ps aux | grep '${process}' | awk '{print $2}'); echo $?
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}
+    Should Be Equal As Integers    ${rc}    0
+
+Check Remote File Contents
+    [Documentation]    Check if file on remote system matches a `grep` regex
+    [Arguments]    ${file_should_exist}    ${file}    ${pattern}
+    ...    ${ip}    ${user}    ${pass}=${None}
+    ...    ${container_type}=${None}    ${container_name}=${None}    ${prompt}=~$
+    ${output}=    Login And Run Command On Remote System
+    ...    cat ${file} | grep '${pattern}'
+    ...    ${ip}    ${user}    ${pass}    ${container_type}    ${container_name}    ${prompt}
+    # FIXME: Comparison against truthy value
+    Run Keyword If    '${file_should_exist}' == 'True'
+    ...    Should Contain    ${output}    ${pattern}
+    ...    ELSE
+    ...    Should Not Contain    ${output}    ${pattern}
+
+Set Deployment Config Variables
+    [Documentation]    Parses through the given deployment config and sets all the "src" and "dst" variables
+    ${source}=    Evaluate    ${hosts}.get("src")
+    ${length_src}=    Get Length    ${source}
+    ${src}=    Set Variable    src
+    FOR    ${INDEX}    IN RANGE    0    ${length_src}
+        Set Suite Variable    ${${src}${INDEX}}    ${source[${INDEX}]}
+    END
+    ${destination}=    Evaluate    ${hosts}.get("dst")
+    ${length_dst}=    Get Length    ${destination}
+    ${dst}=    Set Variable    dst
+    FOR    ${INDEX}    IN RANGE    0    ${length_dst}
+        Set Suite Variable    ${${dst}${INDEX}}    ${destination[${INDEX}]}
+    END