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/CORDDictUtils.py b/cord-robot/CORDRobot/CORDDictUtils.py
new file mode 100644
index 0000000..ea13212
--- /dev/null
+++ b/cord-robot/CORDRobot/CORDDictUtils.py
@@ -0,0 +1,330 @@
+# 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.
+
+from __future__ import absolute_import, print_function
+
+import json
+import uuid
+import random
+import yaml
+import glob
+import string
+
+
+class CORDDictUtils(object):
+ @staticmethod
+ def listToDict(alist, intListIndex):
+ dictInfo = alist[int(intListIndex)]
+ return dictInfo
+
+ @staticmethod
+ def jsonToList(strFile, strListName):
+ data = json.loads(open(strFile).read())
+ # print "data...",data
+ dataList = data[strListName]
+ return dataList
+
+ def readFile(self, path, single=True):
+ dataDict = {}
+ for fileName in glob.glob(path):
+ print("Reading ", fileName)
+ data = open(fileName).read()
+ dataDict[fileName] = data
+ if bool(single):
+ return data
+ if not dataDict:
+ print("Failed to find the file!")
+ return None
+ return dataDict
+
+ def readFiles(self, path):
+ return self.readFile(path, single=False)
+
+ """
+ @method compare_dict
+ @Description: validates if contents of dict1 exists in dict2
+ @params: dict1 = input_data entered through api
+ dict2 = retrieved data from GET method
+ returns True if contents of dict1 exists in dict2
+ """
+
+ def compare_dict(self, dict1, dict2):
+ print("input data", dict1)
+ print("get data", dict2)
+ if dict1 is None or dict2 is None:
+ return False
+ if not isinstance(dict1, dict) or not isinstance(dict2, dict):
+ return False
+ if dict1 == {}:
+ return True
+ return self.compare_dict_recursive(dict1, dict2)
+
+ """
+ @method compare_dict_recursive
+ @Description: recursive function to validate if dict1 is a subset of dict2
+ returns True if contents of dict1 exists in dict2
+ """
+
+ def compare_dict_recursive(self, dict1, dict2):
+ for key1, value1 in dict1.items():
+ if key1 not in dict2.keys():
+ print("Missing key", key1, "in dict2")
+ return False
+ value2 = dict2[key1]
+ if isinstance(value1, dict) and isinstance(value2, dict):
+ if not self.compare_dict_recursive(value1, value2):
+ return False
+ else:
+ if value2 != value1:
+ print("Values of key", key1, "in two dicts are not equal")
+ return False
+ return True
+
+ """
+ @method compare_list_of_dicts
+ @Description: validates if contents of dicts in list1 exists in dicts of list2
+ returns True if for each dict in list1, there's a dict in list2 that contains its content
+ """
+
+ def compare_list_of_dicts(self, list1, list2):
+ for dict1 in list1:
+ if dict1 == {}:
+ continue
+ key = dict1.keys()[0]
+ value = dict1[key]
+ dict2 = self.getDictFromListOfDict(list2, key, value)
+ if dict2 == {}:
+ print(
+ "Comparison failed: no dictionaries found in list2 with key",
+ key,
+ "and value",
+ value,
+ )
+ return False
+ if not self.compare_dict(dict1, dict2):
+ print(
+ "Comparison failed: dictionary",
+ dict1,
+ "is not a subset of dictionary",
+ dict2,
+ )
+ return False
+ return True
+
+ """
+ @method search_dictionary
+ @Description: Searches for a key in the provided nested dictionary
+ @params: input_dict = dictionary to be searched
+ search_key = name of the key to be searched for
+ returns two values: search_key value and status of the search.
+ True if found (False when not found)
+
+ """
+
+ def search_dictionary(self, input_dict, search_key):
+ input_keys = input_dict.keys()
+ key_value = ""
+ found = False
+ for key in input_keys:
+ if key == search_key:
+ key_value = input_dict[key]
+ found = True
+ break
+ elif isinstance(input_dict[key], dict):
+ key_value, found = self.search_dictionary(
+ input_dict[key], search_key)
+ if found:
+ break
+ elif isinstance(input_dict[key], list):
+ if not input_dict[key]:
+ found = False
+ break
+ for item in input_dict[key]:
+ if isinstance(item, dict):
+ key_value, found = self.search_dictionary(
+ item, search_key)
+ if found:
+ break
+ return key_value, found
+
+ """
+ @method getDictFromListOfDict
+ return key_value,found
+ @Description: Searches for the dictionary in the provided list of dictionaries
+ that matches the value of the key provided
+ @params : List of dictionaries(getResponse Data from the URL),
+ SearchKey - Key that needs to be searched for (ex: account_num)
+ searchKeyValue - Value of the searchKey (ex: 21)
+ @Returns: Dictionary returned when match found for searchKey with the corresponding
+ searchKeyValue provided
+ """
+
+ def getDictFromListOfDict(self, getJsonDataList,
+ searchKey, searchKeyValue):
+ return_dict = {}
+ result = ""
+ for data in getJsonDataList:
+ print("data", data)
+ return_dict = {}
+ found = False
+ input_keys = data.keys()
+ for key in input_keys:
+ if key == searchKey and str(data[key]) == str(searchKeyValue):
+ found = True
+ return_dict = data
+ print("return_dict", return_dict)
+ break
+ elif isinstance(data[key], dict):
+ result, found = self.search_dictionary(
+ data[key], searchKey)
+ if found and str(result) == str(searchKeyValue):
+ return_dict = data
+ break
+ elif isinstance(data[key], list):
+ for item in data[key]:
+ if isinstance(item, dict):
+ result, found = self.search_dictionary(
+ data[key], searchKey)
+ if found and str(
+ result) == str(searchKeyValue):
+ return_dict = data
+ break
+ if return_dict:
+ break
+ return return_dict
+
+ """
+ @method getFieldValueFromDict
+ @params : search_dict - Dictionary to be searched
+ field - Key to be searched for (ex: account_num)
+ @Returns: Returns the value of the Key that was provided
+ """
+
+ def getFieldValueFromDict(self, search_dict, field):
+ results = ""
+ found = False
+ input_keys = search_dict.keys()
+ for key in input_keys:
+ print("key...", key)
+ if key == field:
+ results = search_dict[key]
+ if not results:
+ found = True
+ break
+ elif isinstance(search_dict[key], dict):
+ results, found = self.search_dictionary(
+ search_dict[key], field)
+ if found:
+ break
+ elif isinstance(search_dict[key], list):
+ if not search_dict[key]:
+ found = False
+ continue
+ for item in search_dict[key]:
+ if isinstance(item, dict):
+ results, found = self.search_dictionary(item, field)
+ if found:
+ break
+ if results:
+ break
+
+ return results
+
+ def setFieldValueInDict(self, input_dict, field, field_value):
+ input_dict[field] = field_value
+ return input_dict
+
+ """
+ @method getAllFieldValues
+ @params : getJsonDataDictList - List of dictionaries to be searched
+ fieldName - Key to be searched for (ex: instance_id)
+ @Returns: Returns the unique value of the Key that was provided
+ """
+
+ def getAllFieldValues(self, getJsonDataDictList, fieldName):
+ value_list = []
+ # uniqValue = "" - this is unused, commented out
+ uniq_list = []
+ for data in getJsonDataDictList:
+ fieldValue = ""
+ fieldValue = self.getFieldValueFromDict(data, fieldName)
+ value_list.append(fieldValue)
+ uniq_list = sorted(set(value_list))
+ if len(uniq_list) == 1:
+ pass # see above, unused?
+ # uniqValue = uniq_list[0]
+ else:
+ print("list of values found for ", fieldName, ";", uniq_list)
+ return fieldValue
+
+ def generate_uuid(self):
+ return uuid.uuid4()
+
+ def generate_random_number_from_blacklist(
+ self, blacklist, min=100, max=500, typeTag=False
+ ):
+ num = None
+ while num in blacklist or num is None:
+ num = random.randrange(int(min), int(max))
+ if typeTag:
+ return num
+ else:
+ return str(num)
+
+ def get_dynamic_resources(self, inputfile, resource):
+ resourceNames = []
+ names = {}
+ dnames = []
+ with open(inputfile, "r") as f:
+ contents = yaml.load(f)
+ resources = contents[resource]
+ for i in resources:
+ resourceNames.append(i["name"])
+ for i in resourceNames:
+ names["name"] = i
+ dnames.append(names.copy())
+ return dnames
+
+ def generate_random_value(
+ self, value, max_length=10, min_int=1, max_int=10000):
+ if value == "string":
+ return "".join(
+ random.choice(string.ascii_lowercase + string.digits)
+ for _ in range(max_length)
+ )
+ if value == "bool":
+ return random.choice([True, False])
+ if value == "int32" or value == "uint32":
+ return random.randint(min_int, max_int)
+ if value == "float":
+ return random.uniform(1, 10)
+ if value == "role":
+ return "admin"
+ if value == "direction":
+ return random.choice(["in", "out"])
+ if value == "flavor":
+ return random.choice(["m1.large", "m1.medium", "m1.small"])
+ if value == "vlan_tag":
+ return random.choice(["555", "1-4096", "ANY"])
+ if value == "ip_address":
+ return ".".join(str(random.randint(0, 255)) for _ in range(4))
+ else:
+ return None
+
+ def generate_random_slice_name(self):
+ random_name = "".join(
+ random.choice(
+ string.ascii_lowercase +
+ string.digits) for _ in range(10))
+ return "testloginbase" + random_name
diff --git a/cord-robot/CORDRobot/__init__.py b/cord-robot/CORDRobot/__init__.py
new file mode 100644
index 0000000..8c357f6
--- /dev/null
+++ b/cord-robot/CORDRobot/__init__.py
@@ -0,0 +1,36 @@
+# 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.
+
+from __future__ import absolute_import
+
+import os
+
+from .CORDDictUtils import CORDDictUtils
+from .restApi import restApi
+from .testCaseUtils import TestCaseUtils
+
+
+# return the library version
+def _version_():
+ with open(os.path.join(os.path.dirname(__file__), "VERSION")) as f:
+ return f.read().strip()
+
+
+# Inherit all the other sub-classes
+class CORDRobot(CORDDictUtils, restApi, TestCaseUtils):
+
+ ROBOT_LIBRARY_SCOPE = "GLOBAL"
+
+ def cr_version(self):
+ return _version_()
diff --git a/cord-robot/CORDRobot/restApi.py b/cord-robot/CORDRobot/restApi.py
new file mode 100644
index 0000000..afec6a6
--- /dev/null
+++ b/cord-robot/CORDRobot/restApi.py
@@ -0,0 +1,210 @@
+# 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.
+
+from __future__ import absolute_import, print_function
+
+import requests
+import json
+import os
+
+# These are the default values used with XOS
+restApiDefaults = {
+ 'ATT_SERVICE': '/xosapi/v1/att-workflow-driver/attworkflowdriverservices',
+ 'ATT_SERVICEINSTANCES': '/xosapi/v1/att-workflow-driver/attworkflowdriverserviceinstances',
+ 'ATT_WHITELIST': '/xosapi/v1/att-workflow-driver/attworkflowdriverwhitelistentries',
+ 'BNG_MAP': '/xosapi/v1/fabric-crossconnect/bngportmappings',
+ 'CH_CORE_DEPLOYMENTS': '/xosapi/v1/core/deployments',
+ 'CH_CORE_FLAVORS': '/xosapi/v1/core/flavors',
+ 'CH_CORE_IMAGES': '/xosapi/v1/core/images',
+ 'CH_CORE_INSTANCES': '/xosapi/v1/core/instances',
+ 'CH_CORE_NETWORKS': '/xosapi/v1/core/networks',
+ 'CH_CORE_NETWORK_SLICES': '/xosapi/v1/core/networkslices',
+ 'CH_CORE_NETWORK_TEMPLATES': '/xosapi/v1/core/networktemplates',
+ 'CH_CORE_NODES': '/xosapi/v1/core/nodes',
+ 'CH_CORE_PORTS': '/xosapi/v1/core/ports',
+ 'CH_CORE_SERVICELINK': '/xosapi/v1/core/serviceinstancelinks',
+ 'CH_CORE_SERVICES': '/xosapi/v1/core/services',
+ 'CH_CORE_SERVICES': '/xosapi/v1/core/services',
+ 'CH_CORE_SITEDEPLOYMENTS': '/xosapi/v1/core/sitedeployments',
+ 'CH_CORE_SITES': '/xosapi/v1/core/sites',
+ 'CH_CORE_SLICES': '/xosapi/v1/core/slices',
+ 'CH_CORE_SLICES': '/xosapi/v1/core/slices',
+ 'CH_CORE_USERS': '/xosapi/v1/core/users',
+ 'CORE_DEPLOYMENTS': '/api/core/deployments/',
+ 'CORE_FLAVORS': '/api/core/flavors/',
+ 'CORE_IMAGES': '/api/core/images/',
+ 'CORE_INSTANCES': '/api/core/instances/',
+ 'CORE_NODES': '/api/core/nodes/',
+ 'CORE_SANITY_INSTANCES': '/api/core/instances/?no_hyperlinks=1',
+ 'CORE_SANITY_NODES': '/api/core/nodes/?no_hyperlinks=1',
+ 'CORE_SANITY_SLICES': '/api/core/slices/?no_hyperlinks=1',
+ 'CORE_SERVICES': '/api/core/services/',
+ 'CORE_SITEDEPLOYMENTS': '/api/core/sitedeployments',
+ 'CORE_SITES': '/api/core/sites/',
+ 'CORE_SLICES': '/api/core/slices/',
+ 'CORE_USERS': '/api/core/users/',
+ 'FABRIC_CROSSCONNECT_SERVICEINSTANCES': '/xosapi/v1/fabric-crossconnect/fabriccrossconnectserviceinstances',
+ 'FABRIC_SWITCH': '/xosapi/v1/fabric/switches',
+ 'HWVSG_TENANT': '/xosapi/v1/vsg-hw/vsghwserviceinstances',
+ 'ONU_DEVICE': '/xosapi/v1/volt/onudevices',
+ 'OSS_SERVICE': '/xosapi/v1/hippie-oss/hippieossservices',
+ 'OSS_SERVICEINSTANCE': '/xosapi/v1/hippie-oss/hippieossserviceinstances',
+ 'OSS_VOLT': '/xosapi/v1/core/servicedependencys',
+ 'PON_PORT': '/xosapi/v1/volt/ponports',
+ 'PORT_INTERFACE': '/xosapi/v1/fabric/portinterfaces',
+ 'SERVER_IP': '127.0.0.1',
+ 'SERVER_PORT': '30006',
+ 'SWITCH_PORT': '/xosapi/v1/fabric/switchports',
+ 'TENANT_SUBSCRIBER': '/api/tenant/cord/subscriber/',
+ 'TENANT_VOLT': '/api/tenant/cord/volt/',
+ 'UTILS_LOGIN': '/api/utility/login/',
+ 'UTILS_SYNCHRONIZER': '/api/utility/synchronizer/',
+ 'VOLT_DEVICE': '/xosapi/v1/volt/oltdevices',
+ 'VOLT_SERVICE': '/xosapi/v1/volt/voltservices',
+ 'VOLT_SUBSCRIBER': '/xosapi/v1/rcord/rcordsubscribers',
+ 'VOLT_TENANT': '/xosapi/v1/volt/voltserviceinstances',
+ 'VSG_TENANT': '/xosapi/v1/vsg/vsgserviceinstances',
+ 'XOS_PASSWD': 'letmein',
+ 'XOS_USER': 'admin@opencord.org',
+}
+
+jsonHeader = {"Content-Type": "application/json"}
+
+
+class restApi():
+ """
+ Functions for testing CORD API with POST, GET, PUT, DELETE method
+ """
+
+ def getEnvOrDefault(self, key):
+ """
+ Find a variable in environment, or use Default value
+ """
+ if key in os.environ:
+ value = os.environ[key]
+ elif key in restApiDefaults:
+ value = restApiDefaults[key]
+ else:
+ print("Unable to find '%s' in environment or defaults!" % key)
+ value = None
+
+ return value
+
+ def getURL(self, key):
+ """
+ Get REST API suffix from key and return the full URL
+ """
+ urlSuffix = self.getEnvOrDefault(key)
+ url = "http://" + self.getEnvOrDefault("SERVER_IP") + ":" + self.getEnvOrDefault("SERVER_PORT") + urlSuffix
+ return url
+
+ def checkResult(self, resp, expectedStatus):
+ """
+ Check if the status code in resp equals to the expected number.
+ Return True or False based on the check result.
+ """
+ if resp.status_code == expectedStatus:
+ print("Test passed: " + str(resp.status_code) + ": " + resp.text)
+ return True
+ else:
+ print("Test failed: " + str(resp.status_code) + ": " + resp.text)
+ return False
+
+ def ApiPost(self, key, jsonData):
+ url = self.getURL(key)
+ data = json.dumps(jsonData)
+ print("url, data..", url, data)
+ resp = requests.post(
+ url, data=data, headers=jsonHeader,
+ auth=(self.getEnvOrDefault("XOS_USER"), self.getEnvOrDefault("XOS_PASSWD"))
+ )
+ print("requests.codes.....", requests.codes.created)
+ passed = self.checkResult(resp, requests.codes.created) or self.checkResult(
+ resp, requests.codes.ok
+ )
+ return passed
+
+ def ApiPostReturnJson(self, key, jsonData):
+ url = self.getURL(key)
+ data = json.dumps(jsonData)
+ print("url, data..", url, data)
+ resp = requests.post(
+ url, data=data, headers=jsonHeader,
+ auth=(self.getEnvOrDefault("XOS_USER"), self.getEnvOrDefault("XOS_PASSWD"))
+ )
+ print("requests.codes.....", requests.codes.created)
+ print("posted data...", resp.json())
+ passed = self.checkResult(resp, requests.codes.created) or self.checkResult(
+ resp, requests.codes.ok
+ )
+ return passed, resp.json()
+
+ def ApiGet(self, key, urlSuffix=""):
+ url = self.getURL(key) + str(urlSuffix)
+ print("get url...", url)
+ resp = requests.get(url, auth=(self.getEnvOrDefault("XOS_USER"), self.getEnvOrDefault("XOS_PASSWD")))
+ passed = self.checkResult(resp, requests.codes.ok)
+ if not passed:
+ return None
+ else:
+ return resp.json()
+
+ def ApiChameleonGet(self, key, urlSuffix=""):
+ url = self.getURL(key) + "/" + str(urlSuffix)
+ print("get url...", url)
+ resp = requests.get(url, auth=(self.getEnvOrDefault("XOS_USER"), self.getEnvOrDefault("XOS_PASSWD")))
+ passed = self.checkResult(resp, requests.codes.ok)
+ if not passed:
+ return None
+ else:
+ return resp.json()
+
+ def ApiPut(self, key, jsonData, urlSuffix=""):
+ print("urlSuffix....", type(urlSuffix))
+ url = self.getURL(key) + str(urlSuffix) + "/"
+ data = json.dumps(jsonData)
+ resp = requests.put(
+ url, data=data, headers=jsonHeader,
+ auth=(self.getEnvOrDefault("XOS_USER"), self.getEnvOrDefault("XOS_PASSWD"))
+ )
+ passed = self.checkResult(resp, requests.codes.ok)
+ return passed
+
+ def ApiChameleonPut(self, key, jsonData, urlSuffix=""):
+ print("urlSuffix....", type(urlSuffix))
+ url = self.getURL(key) + "/" + str(urlSuffix)
+ print("url", url)
+ data = json.dumps(jsonData)
+ resp = requests.put(
+ url, data=data, headers=jsonHeader,
+ auth=(self.getEnvOrDefault("XOS_USER"), self.getEnvOrDefault("XOS_PASSWD"))
+ )
+ passed = self.checkResult(resp, requests.codes.ok)
+ return passed
+
+ def ApiDelete(self, key, urlSuffix=""):
+ url = self.getURL(key) + str(urlSuffix)
+ print("url", url)
+ resp = requests.delete(url, auth=(self.getEnvOrDefault("XOS_USER"), self.getEnvOrDefault("XOS_PASSWD")))
+ passed = self.checkResult(resp, requests.codes.no_content)
+ return passed
+
+ def ApiChameleonDelete(self, key, urlSuffix=""):
+ url = self.getURL(key) + "/" + str(urlSuffix)
+ print("url", url)
+ resp = requests.delete(url, auth=(self.getEnvOrDefault("XOS_USER"), self.getEnvOrDefault("XOS_PASSWD")))
+ passed = self.checkResult(resp, requests.codes.created) or self.checkResult(
+ resp, requests.codes.ok
+ )
+ return passed
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
diff --git a/cord-robot/CORDRobot/testCaseUtils.py b/cord-robot/CORDRobot/testCaseUtils.py
new file mode 100755
index 0000000..88f1913
--- /dev/null
+++ b/cord-robot/CORDRobot/testCaseUtils.py
@@ -0,0 +1,244 @@
+#
+# Copyright 2018 the original author or authors.
+#
+# 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.
+#
+
+"""
+Test Case Utils module
+"""
+
+from __future__ import absolute_import
+
+import time
+import subprocess
+import pexpect
+import sys
+
+
+class TestCaseUtils():
+
+ @staticmethod
+ def config_dirs(self, log_dir, root_dir=None, voltha_dir=None):
+ self.dirs["log"] = log_dir
+ self.dirs["root"] = root_dir
+ self.dirs["voltha"] = voltha_dir
+
+ def get_dir(self, directory):
+ return self.dirs.get(directory)
+
+ @staticmethod
+ def remove_leading_line(log_dir, log_file):
+ with open(log_dir + "/" + log_file, "r+") as FILE:
+ lines = FILE.readlines()
+ FILE.seek(0)
+ lines = lines[1:]
+ for line in lines:
+ FILE.write(line)
+ FILE.truncate()
+ FILE.close()
+
+ @staticmethod
+ def write_log_of_voltha_cli_comand(
+ log_dir,
+ log_file1,
+ cmd1,
+ log_file2=None,
+ cmd2=None,
+ log_file3=None,
+ cmd3=None,
+ host="localhost",
+ ):
+ output = open(log_dir + "/" + log_file1, "wb")
+ child = pexpect.spawn(
+ "ssh -p 30110 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no voltha@%s"
+ % host
+ )
+ child.expect(r"[pP]assword:")
+ child.sendline("admin")
+ child.expect(r"\((\x1b\[\d*;?\d+m){1,2}voltha(\x1b\[\d*;?\d+m){1,2}\)")
+ time.sleep(10)
+ child.sendline(cmd1)
+ i = child.expect(
+ [
+ r"\((\x1b\[\d*;?\d+m){1,2}voltha(\x1b\[\d*;?\d+m){1,2}\)",
+ r"\((\x1b\[\d*;?\d+m){1,2}.*device [0-9a-f]{16}(\x1b\[\d*;?\d+m){1,2}\)",
+ ]
+ )
+ if i == 0:
+ output.write(child.before)
+ output.close()
+ TestCaseUtils.remove_leading_line(log_dir, log_file1)
+ elif i == 1:
+ if log_file2 is not None and cmd2 is not None:
+ output = open(log_dir + "/" + log_file2, "wb")
+ child.sendline(cmd2)
+ child.expect(
+ r"\((\x1b\[\d*;?\d+m){1,2}.*device [0-9a-f]{16}(\x1b\[\d*;?\d+m){1,2}\)"
+ )
+ output.write(child.before)
+ output.close()
+ TestCaseUtils.remove_leading_line(log_dir, log_file2)
+ if log_file3 is not None and cmd3 is not None:
+ output = open(log_dir + "/" + log_file3, "wb")
+ child.sendline(cmd3)
+ child.expect(
+ r"\((\x1b\[\d*;?\d+m){1,2}.*device [0-9a-f]{16}(\x1b\[\d*;?\d+m){1,2}\)"
+ )
+ output.write(child.before)
+ output.close()
+ TestCaseUtils.remove_leading_line(log_dir, log_file3)
+ child.close()
+
+ @staticmethod
+ def write_log_of_onos_cli_command(log_dir, log_file, cmd, host="localhost", port=30115):
+ output = open(log_dir + "/" + log_file, "wb")
+ child = pexpect.spawn(
+ "ssh -p %s -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no karaf@%s"
+ % (port, host)
+ )
+ child.expect(r"[pP]assword:")
+ child.sendline("karaf")
+ # Expected prompt:
+ # onos> (ONOS 1.x)
+ # karaf@root > (ONOS 2.x)
+ child.expect([r'(\x1b\[\d*;?\d+m){1,2}onos> (\x1b\[\d*;?\d+m){1,2}', r'karaf@root >'])
+ child.sendline(cmd)
+ child.expect([r'(\x1b\[\d*;?\d+m){1,2}onos> (\x1b\[\d*;?\d+m){1,2}', r'karaf@root >'])
+
+ output.write(child.before)
+
+ output.close()
+ child.close()
+
+ def get_fields_from_grep_command(self, search_word, log_file):
+ grepCommand = "grep %s %s/%s" % (search_word, self.get_dir("log"), log_file)
+ statusLines = subprocess.getstatusoutput(grepCommand)[1]
+ return statusLines
+
+ @staticmethod
+ def parse_fields(status_line, delimiter):
+ statusList = status_line.split(delimiter)
+ return statusList
+
+ def print_log_file(self, log_file):
+ with open(self.get_dir("log") + "/" + log_file, "r+") as FILE:
+ lines = FILE.readlines()
+ print
+ for line in lines:
+ sys.stdout.write(line)
+
+ @staticmethod
+ def extract_pod_ip_addr(pod_name):
+ proc1 = subprocess.Popen(
+ ["/usr/bin/kubectl", "get", "svc", "--all-namespaces"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ proc2 = subprocess.Popen(
+ ["grep", "-e", pod_name],
+ stdin=proc1.stdout,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ proc3 = subprocess.Popen(
+ ["awk", "{print $4}"],
+ stdin=proc2.stdout,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+
+ proc1.stdout.close()
+ proc2.stdout.close()
+ out, err = proc3.communicate()
+ return out
+
+ @staticmethod
+ def extract_radius_ip_addr(pod_name):
+ proc1 = subprocess.Popen(
+ ["/usr/bin/kubectl", "describe", "pod", "-n", "voltha", pod_name],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ proc2 = subprocess.Popen(
+ ["grep", "^IP:"],
+ stdin=proc1.stdout,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ proc3 = subprocess.Popen(
+ ["awk", "{print $2}"],
+ stdin=proc2.stdout,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+
+ proc1.stdout.close()
+ proc2.stdout.close()
+ out, err = proc3.communicate()
+ return out
+
+ @staticmethod
+ def extract_pod_name(short_pod_name):
+ proc1 = subprocess.Popen(
+ ["/usr/bin/kubectl", "get", "pods", "--all-namespaces"],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ proc2 = subprocess.Popen(
+ ["grep", "-e", short_pod_name],
+ stdin=proc1.stdout,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+ proc3 = subprocess.Popen(
+ ["awk", "{print $2}"],
+ stdin=proc2.stdout,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ )
+
+ proc1.stdout.close()
+ proc2.stdout.close()
+ out, err = proc3.communicate()
+ return out
+
+ def modify_radius_ip_in_json_using_sed(self, new_ip_addr):
+ sedCommand = (
+ "sed -i '/radiusIp/c\\ \"radiusIp\":\"'%s'\",' %s/tests/atests/build/aaa_json"
+ % (new_ip_addr, self.get_dir("voltha"))
+ )
+ status = subprocess.getstatusoutput(sedCommand)[0]
+ return status
+
+ @staticmethod
+ def discover_rg_pod_name():
+ return TestCaseUtils.extract_pod_name("rg0").strip()
+
+ @staticmethod
+ def retrieve_authorized_users_device_id_and_port_number(status_line):
+ fields = TestCaseUtils.parse_fields(status_line, ",")
+ deviceField = fields[2].strip()
+ deviceStr, equal, deviceId = deviceField.partition("=")
+ device_Id = deviceId
+ portField = fields[4].strip()
+ portNumStr, equal, portNum = portField.partition("=")
+ portNumber = portNum
+ return device_Id, portNumber
+
+ def add_subscriber_access(self, device_id, port_number):
+ TestCaseUtils.write_log_of_onos_cli_command(
+ self.get_dir("log"),
+ "voltha_add_subscriber_access.log",
+ "volt-add-subscriber-access %s %s" % (device_id, port_number),
+ )