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