--- /dev/null
+*** Settings ***
+Library SSHLibrary
+Library RequestsLibrary
+Resource SSHKeywords.robot
+Resource ../variables/Variables.robot
+
+*** Variables ***
+${mount_point_url} /restconf/operational/network-topology:network-topology/topology/topology-netconf/
+${device_status} /restconf/operational/odl-netconf-callhome-server:netconf-callhome-server
+${whitelist} /restconf/config/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices
+${substring1} "netconf-node-topology:connection-status":"connected"
+${substring2} "node-id":"netopeer"
+${substring3} "netconf-node-topology:available-capabilities"
+
+*** Keywords ***
+Check Device status
+ [Arguments] ${status} ${id}=netopeer
+ [Documentation] Checks the operational device status.
+ @{expectedValues} Create List "unique-id":"${id}" "callhome-status:device-status":"${status}"
+ Run Keyword If '${status}'=='FAILED_NOT_ALLOWED' or '${status}'=='FAILED_AUTH_FAILURE' Remove Values From List ${expectedValues} "unique-id":"${id}"
+ Utils.Check For Elements At URI ${device_status} ${expectedValues}
+
+Get Netopeer Ready
+ [Documentation] Pulls the netopeer image from the docker repository. Points ODL(CallHome Server) IP in the files used by netopeer(CallHome Client).
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker pull sdnhub/netopeer return_stdout=True return_stderr=True
+ ... return_rc=True
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker images return_stdout=True return_stderr=True
+ ... return_rc=True
+ Reset Docker Compose Configuration
+
+Reset Docker Compose Configuration
+ [Documentation] Resets the docker compose configurations.
+ SSHLibrary.Put File ${CURDIR}/../variables/netconf/callhome/docker-compose.yaml .
+ SSHLibrary.Put File ${CURDIR}/../variables/netconf/callhome/datastore-server.xml .
+ SSHLibrary.Execute_Command sed -i -e 's/ODL_SYSTEM_IP/${ODL_SYSTEM_IP}/g' docker-compose.yaml
+ SSHLibrary.Execute_Command sed -i -e 's/ODL_SYSTEM_IP/${ODL_SYSTEM_IP}/g' datastore-server.xml
+
+Get Environment Ready
+ [Documentation] Get the scripts ready to set credentials and control whitelist maintained by the CallHome server.
+ SSHLibrary.Put File ${CURDIR}/../variables/netconf/callhome/whitelist_add.sh .
+ SSHLibrary.Put File ${CURDIR}/../variables/netconf/callhome/credentials_set.sh .
+ SSHLibrary.Execute_Command chmod +x whitelist_add.sh
+ SSHLibrary.Execute_Command chmod +x credentials_set.sh
+ SSHLibrary.Execute_Command sed -i -e 's/ODL_SYSTEM_IP/${ODL_SYSTEM_IP}/g' credentials_set.sh
+ SSHLibrary.Execute_Command sed -i -e 's/ODL_SYSTEM_IP/${ODL_SYSTEM_IP}/g' whitelist_add.sh
+
+Install Docker Compose on tools system
+ [Documentation] Install docker-compose on tools system.
+ ${netopeer_conn_id} = SSHKeywords.Open_Connection_To_Tools_System
+ Builtin.Set Suite Variable ${netopeer_conn_id}
+ SSHLibrary.Write sudo curl -L "https://github.com/docker/compose/releases/download/1.11.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
+ ${output}= Wait Until Keyword Succeeds 30s 2s SSHLibrary.Read_Until_Prompt
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command sudo chmod +x /usr/local/bin/docker-compose return_stdout=True return_stderr=True
+ ... return_rc=True
+
+Uninstall Docker Compose on tools system
+ [Documentation] Uninstall docker-compose on tools system
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command pip uninstall docker-compose return_stdout=True return_stderr=True
+ ... return_rc=True
+
+Suite Teardown
+ [Documentation] Tearing down the setup.
+ Uninstall Docker Compose on tools system
+ RequestsLibrary.Delete_All_Sessions
+ SSHLibrary.Close_All_Connections
+
+Test Teardown
+ [Documentation] Tears down the docker running netopeer and deletes entry from the whitelist.
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker-compose down return_stdout=True return_stderr=True
+ ... return_rc=True
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker ps -a return_stdout=True return_stderr=True
+ ... return_rc=True
+ ${resp} = RequestsLibrary.Delete_Request session ${whitelist}
+
+Suite Setup
+ [Documentation] Get the suite ready for callhome test cases.
+ RequestsLibrary.Create_Session session http://${ODL_SYSTEM_IP}:${RESTCONFPORT} auth=${AUTH}
+ Install Docker Compose on tools system
+ Get Environment Ready
+ Get Netopeer Ready
+ ${netconf_mount_expected_values} Create list ${substring1} ${substring2} ${substring3}
+ Set Suite Variable ${netconf_mount_expected_values}
--- /dev/null
+*** Settings ***
+Documentation Test suite to verify callhome functionality where the Call Home Server(CONTROLLER) is provisioned with device
+... certificates when docker-compose is invoked. Every test case does a SED operation to search and replace words
+... to cover the happy path and negative scenarios.
+Suite Setup Suite Setup
+Suite Teardown Suite Teardown
+Test Setup Reset Docker Compose Configuration
+Test Teardown Test Teardown
+Resource ../../../libraries/NetconfCallHome.robot
+
+*** Test Cases ***
+CallHome with Incorrect global Credentials
+ [Documentation] Incorrect global credentials should result to mount failure. FAILED_AUTH_FAILURE should be the device status.
+ SSHLibrary.Execute_Command sed -i -e 's/global root/global incorrect/g' docker-compose.yaml
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker-compose up -d return_stdout=True return_stderr=True
+ ... return_rc=True
+ Wait Until Keyword Succeeds 30s 2s NetconfCallHome.Check Device Status FAILED_AUTH_FAILURE
+ Wait Until Keyword Succeeds 30s 2s Run Keyword And Expect Error * Utils.Check For Elements At URI ${mount_point_url}
+ ... ${netconf_mount_expected_values}
+
+CallHome with Incorrect per-device Credentials
+ [Documentation] Incorrect per-device credentials should result to mount failure. FAILED_AUTH_FAILURE should be the device status.
+ SSHLibrary.Execute_Command sed -i -e 's/global root/per-device netopeer incorrect/g' docker-compose.yaml
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker-compose up -d return_stdout=True return_stderr=True
+ ... return_rc=True
+ Wait Until Keyword Succeeds 30s 2s NetconfCallHome.Check Device Status FAILED_AUTH_FAILURE
+ Wait Until Keyword Succeeds 30s 2s Run Keyword And Expect Error * Utils.Check For Elements At URI ${mount_point_url}
+ ... ${netconf_mount_expected_values}
+
+CallHome with Incorrect Node-id
+ [Documentation] CallHome from device that does not have an entry in per-device credential with result to mount point failure.
+ SSHLibrary.Execute_Command sed -i -e 's/global/per-device incorrect_hostname/g' docker-compose.yaml return_rc=True
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker-compose up -d return_stdout=True return_stderr=True
+ ... return_rc=True
+ Wait Until Keyword Succeeds 30s 2s NetconfCallHome.Check Device Status DISCONNECTED
+ Wait Until Keyword Succeeds 30s 2s Run Keyword And Expect Error * Utils.Check For Elements At URI ${mount_point_url}
+ ... ${netconf_mount_expected_values}
+
+CallHome with Rogue Devices
+ [Documentation] A Rogue Device will fail to callhome and wont be able to mount because the keys are not added in whitelist.
+ ... FAILED_NOT_ALLOWED should be the device status.
+ SSHLibrary.Execute_Command sed -i -e 's,\/root\/whitelist_add.sh \$\$\{HOSTNAME\}\;,,g' docker-compose.yaml
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker-compose up -d return_stdout=True return_stderr=True
+ ... return_rc=True
+ Wait Until Keyword Succeeds 30s 2s NetconfCallHome.Check Device Status FAILED_NOT_ALLOWED
+ Wait Until Keyword Succeeds 30s 2s Run Keyword And Expect Error * Utils.Check For Elements At URI ${mount_point_url}
+ ... ${netconf_mount_expected_values}
+
+Successful CallHome with correct global credentials
+ [Documentation] Device being in whitelist of the Call Home server along with correct global credentials will result to successful mount.
+ ... CONNECTED should be the device status.
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker-compose up -d return_stdout=True return_stderr=True
+ ... return_rc=True
+ Wait Until Keyword Succeeds 30s 2s NetconfCallHome.Check Device Status CONNECTED
+ Wait Until Keyword Succeeds 30s 2s Utils.Check For Elements At URI ${mount_point_url} ${netconf_mount_expected_values}
+
+Successful CallHome with correct per-device credentials
+ [Documentation] Device being in whitelist of the Call Home server along with correct per-device credentials will result to successful mount.
+ ... CONNECTED should be the device status.
+ SSHLibrary.Execute_Command sed -i -e 's/global/per-device netopeer/g' docker-compose.yaml
+ ${stdout} ${stderr} ${rc}= SSHLibrary.Execute Command docker-compose up -d return_stdout=True return_stderr=True
+ ... return_rc=True
+ Wait Until Keyword Succeeds 30s 2s NetconfCallHome.Check Device Status CONNECTED
+ Wait Until Keyword Succeeds 30s 2s Utils.Check For Elements At URI ${mount_point_url} ${netconf_mount_expected_values}
--- /dev/null
+# Place the suites in run order:
+integration/test/csit/suites/netconf/callhome
--- /dev/null
+#!/bin/bash
+
+# This script is called within the docker-compose to provision the controller with either
+# the global or per-device credentials.
+
+stderr() { echo "$@" 1>&2; }
+
+[ $# -eq 0 ] && { stderr "Usage: $0 ( -global | -per-device id ) username password(s)"; exit 1; }
+
+: ${1?"Usage: $0 -global | -per-device"}
+
+option=${1};
+
+if [[ "${option}" == "-per-device" ]]; then
+ : ${2?"Usage: $0 Device Id"}
+ : ${3?"Usage: $0 Device Username"}
+ : ${4?"Usage: $0 Device Password"}
+ devid=${2}
+ user=${3}
+ shift; shift; shift
+ pwds="$@"
+elif [[ "${option}" == "-global" ]]; then
+ : ${2?"Usage: $0 Global Username"}
+ : ${3?"Usage: $0 Global Password"}
+ devid=""
+ user=${2}
+ shift; shift
+ pwds="$@"
+else
+ stderr "$0: must supply -global or -per-device command line argument for global password changes, not '${option}''"
+ exit 1
+fi
+
+pwdsjson=""
+
+for pwd in $pwds; do
+ if [[ ! -z "$pwdsjson" ]]; then
+ pwdsjson+=","
+ fi
+ pwdsjson+="'$pwd'"
+done
+
+set -e
+controller=ODL_SYSTEM_IP
+port=8181
+basicauth="YWRtaW46YWRtaW4="
+
+baseurl="http://${controller}:${port}/restconf/config/odl-netconf-callhome-server:netconf-callhome-server"
+
+if [[ "${option}" == "-global" ]]; then
+ url="${baseurl}/global/credentials"
+else
+ url="${baseurl}/allowed-devices/device/${devid}/credentials"
+fi
+
+set +e
+read -r -d '' payload << EOM
+{
+ "credentials": {
+ "username": "${user}",
+ "passwords": [${pwdsjson}]
+ }
+}
+EOM
+set -e
+
+payload=$(echo "${payload}" | tr '\n' ' ' | tr -s " ")
+
+echo "PUT of user (${user}) and pwd (${pwd})"
+res=$(curl -s -X PUT \
+ -H "Authorization: Basic ${basicauth}" \
+ -H "Content-Type: application/json" \
+ -H "Cache-Control: no-cache" \
+ --data "${payload}" \
+ ${url})
+
+if [[ $res == *"error-message"* ]]; then
+ stderr "$0: ${res}"
+ exit 1
+fi
+
+echo "Getting user/pwd ..."
+
+res=$(curl -s -X GET \
+ -H "Authorization: Basic ${basicauth}" \
+ -H "Cache-Control: no-cache" \
+ ${url})
+echo ${res}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<datastores xmlns="urn:cesnet:tmc:datastores:file">
+ <running lock="">
+ <netconf xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
+ <ssh>
+ <call-home>
+ <applications>
+ <application>
+ <name>test</name>
+ <servers>
+ <server>
+ <address>ODL_SYSTEM_IP</address>
+ <port>6666</port>
+ </server>
+ </servers>
+ <host-keys>
+ <host-key>
+ <name>localhost</name>
+ </host-key>
+ </host-keys>
+ </application>
+ </applications>
+ </call-home>
+ </ssh>
+ </netconf>
+ </running>
+ <startup lock="">
+ <netconf xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-server">
+ <ssh>
+ <call-home>
+ <applications>
+ <application>
+ <name>test</name>
+ <servers>
+ <server>
+ <address>ODL_SYSTEM_IP</address>
+ <port>6666</port>
+ </server>
+ </servers>
+ <host-keys>
+ <host-key>
+ <name>localhost</name>
+ </host-key>
+ </host-keys>
+ </application>
+ </applications>
+ </call-home>
+ </ssh>
+ </netconf>
+ </startup>
+ <candidate modified="false" lock=""/>
+</datastores>
--- /dev/null
+# Controller needs to be provisoned with the unique-id of the device(netopeer) and public key.
+# The way this is done with docker compose is:
+# 1. A new docker instance is spawned and public key is generated.
+# 2. We create new pair of keys to ensure it is different than the snapshot of the image.
+# 3. Execute script to send REST to provision netopeer unique-id + public key + credentials in controller.
+# 4. Start netopeer.
+
+netopeer:
+ image: sdnhub/netopeer
+ hostname: netopeer
+ command: sh -c "apt-get update;apt-get install curl --force-yes -y;echo -e 'y/n' | ssh-keygen -q -N '' -f /etc/ssh/ssh_host_rsa_key;/root/whitelist_add.sh $${HOSTNAME};/root/credentials_set.sh -global root root;/root/netopeer/server/netopeer-server -v 3"
+ environment:
+ - controller_ip=ODL_SYSTEM_IP
+ volumes:
+ - ./datastore-server.xml:/usr/local/etc/netopeer/cfgnetopeer/datastore-server.xml
+ - ./whitelist_add.sh:/root/whitelist_add.sh
+ - ./credentials_set.sh:/root/credentials_set.sh
--- /dev/null
+#!/bin/bash
+
+# This script is called within the docker-compose to get the public RSA key of the
+# device(callhome client) and provision the controller.
+
+set -e
+
+key3="$(cat /etc/ssh/ssh_host_rsa_key.pub)"
+parts=($key3)
+hostkey=${parts[1]}
+id=$1
+controller=ODL_SYSTEM_IP
+echo "Adding key for ${id} to ${controller}"
+echo "Found host key: ${hostkey}"
+
+port=8181
+basicauth="YWRtaW46YWRtaW4="
+
+set +e
+read -r -d '' payload << EOM
+{
+ "device": [
+ {
+ "ssh-host-key": "${hostkey}",
+ "unique-id": "${id}"
+ }
+ ]
+}
+EOM
+set -e
+
+payload=$(echo "${payload}" | tr '\n' ' ' | tr -s " ")
+
+url="http://${controller}:${port}/restconf/config/odl-netconf-callhome-server:netconf-callhome-server/allowed-devices"
+
+echo "POST to whitelist"
+res=$(curl -s -X POST -H "Authorization: Basic ${basicauth}" \
+ -H "Content-Type: application/json" \
+ -H "Cache-Control: no-cache" \
+ -H "Postman-Token: 656d7e0d-2f48-5135-3569-06b2a27a709d" \
+ --data "${payload}" \
+ ${url})
+echo $res
+if [[ $res == *"data-exists"* ]]; then
+ echo "Whitelist already has that entry."
+fi