aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--requirements.txt1
-rw-r--r--resources/libraries/python/HTTPRequest.py258
-rw-r--r--resources/libraries/python/HoneycombSetup.py301
-rw-r--r--resources/libraries/python/HoneycombUtil.py97
-rw-r--r--resources/libraries/python/constants.py17
-rw-r--r--resources/libraries/robot/honeycomb.robot68
-rw-r--r--resources/templates/honeycomb/add_vpp_to_topology.xml9
-rw-r--r--resources/templates/honeycomb/config_topology.url1
-rw-r--r--resources/templates/honeycomb/config_topology_node.url1
-rw-r--r--resources/templates/honeycomb/vpp_version.url1
-rw-r--r--resources/topology_schemas/topology.sch.yaml18
-rw-r--r--tests/suites/honeycomb/sanity.robot37
12 files changed, 807 insertions, 2 deletions
diff --git a/requirements.txt b/requirements.txt
index 1fe928674a..6b6db32a3b 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -7,3 +7,4 @@ PyYAML==3.11
pykwalify==1.5.0
scapy==2.3.1
enum34==1.1.2
+requests==2.9.1
diff --git a/resources/libraries/python/HTTPRequest.py b/resources/libraries/python/HTTPRequest.py
new file mode 100644
index 0000000000..7b21f5a761
--- /dev/null
+++ b/resources/libraries/python/HTTPRequest.py
@@ -0,0 +1,258 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+"""Implements HTTP requests GET, PUT, POST, DELETE used in communication with
+honeycomb.
+"""
+
+from requests import request, RequestException, Timeout, TooManyRedirects, \
+ HTTPError, ConnectionError
+from requests.auth import HTTPBasicAuth
+
+from robot.api import logger
+from robot.api.deco import keyword
+
+
+HTTP_CODES = {"OK": 200,
+ "UNAUTHORIZED": 401,
+ "FORBIDDEN": 403,
+ "NOT_FOUND": 404,
+ "SERVICE_UNAVAILABLE": 503}
+
+
+class HTTPRequestError(Exception):
+ """Exception raised by HTTPRequest objects."""
+
+ def __init__(self, msg, enable_logging=True):
+ """Sets the exception message and enables / disables logging
+
+ It is not wanted to log errors when using these keywords together
+ with keywords like "Wait until keyword succeeds".
+
+ :param msg: Message to be displayed and logged
+ :param enable_logging: When True, logging is enabled, otherwise
+ logging is disabled.
+ :type msg: str
+ :type enable_logging: bool
+ """
+ super(HTTPRequestError, self).__init__()
+ self._msg = msg
+ self._repr_msg = self.__module__ + '.' + \
+ self.__class__.__name__ + ": " + self._msg
+
+ if enable_logging:
+ logger.error(self._msg)
+ logger.debug(self._repr_msg)
+
+ def __repr__(self):
+ return repr(self._repr_msg)
+
+ def __str__(self):
+ return str(self._repr_msg)
+
+
+class HTTPRequest(object):
+ """A class implementing HTTP requests."""
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def create_full_url(ip_addr, port, path):
+ """Creates full url including IP, port, and path to data.
+
+ :param ip_addr: Server IP
+ :param port: Communication port
+ :param path: Path to data
+ :type ip_addr: str
+ :type port: str or int
+ :type path: str
+ :return: full url
+ :rtype: str
+ """
+ return "http://{ip}:{port}{path}".format(ip=ip_addr, port=port,
+ path=path)
+
+ @staticmethod
+ def _http_request(method, node, path, enable_logging=True, **kwargs):
+ """Sends specified HTTP request and returns status code and
+ response content
+
+ :param method: The method to be performed on the resource identified by
+ the given request URI
+ :param node: honeycomb node
+ :param path: URL path, e.g. /index.html
+ :param enable_logging: used to suppress errors when checking
+ honeycomb state during suite setup and teardown
+ :param kwargs: named parameters accepted by request.request:
+ params -- (optional) Dictionary or bytes to be sent in the query
+ string for the Request.
+ data -- (optional) Dictionary, bytes, or file-like object to
+ send in the body of the Request.
+ json -- (optional) json data to send in the body of the Request.
+ headers -- (optional) Dictionary of HTTP Headers to send with
+ the Request.
+ cookies -- (optional) Dict or CookieJar object to send with the
+ Request.
+ files -- (optional) Dictionary of 'name': file-like-objects
+ (or {'name': ('filename', fileobj)}) for multipart encoding upload.
+ timeout (float or tuple) -- (optional) How long to wait for the
+ server to send data before giving up, as a float, or a (connect
+ timeout, read timeout) tuple.
+ allow_redirects (bool) -- (optional) Boolean. Set to True if POST/
+ PUT/DELETE redirect following is allowed.
+ proxies -- (optional) Dictionary mapping protocol to the URL of
+ the proxy.
+ verify -- (optional) whether the SSL cert will be verified.
+ A CA_BUNDLE path can also be provided. Defaults to True.
+ stream -- (optional) if False, the response content will be
+ immediately downloaded.
+ cert -- (optional) if String, path to ssl client cert file (.pem).
+ If Tuple, ('cert', 'key') pair.
+ :type method: str
+ :type node: dict
+ :type path: str
+ :type enable_logging: bool
+ :type kwargs: dict
+ :return: Status code and content of response
+ :rtype: tuple
+ :raises HTTPRequestError: If
+ 1. it is not possible to connect
+ 2. invalid HTTP response comes from server
+ 3. request exceeded the configured number of maximum re-directions
+ 4. request timed out
+ 5. there is any other unexpected HTTP request exception
+ """
+ timeout = kwargs["timeout"]
+ url = HTTPRequest.create_full_url(node['host'],
+ node['honeycomb']['port'],
+ path)
+ try:
+ auth = HTTPBasicAuth(node['honeycomb']['user'],
+ node['honeycomb']['passwd'])
+ rsp = request(method, url, auth=auth, **kwargs)
+ return rsp.status_code, rsp.content
+
+ except ConnectionError as err:
+ # Switching the logging on / off is needed only for
+ # "requests.ConnectionError"
+ if enable_logging:
+ raise HTTPRequestError("Not possible to connect to {0}\n".
+ format(url) + repr(err))
+ else:
+ raise HTTPRequestError("Not possible to connect to {0}\n".
+ format(url) + repr(err),
+ enable_logging=False)
+ except HTTPError as err:
+ raise HTTPRequestError("Invalid HTTP response from {0}\n".
+ format(url) + repr(err))
+ except TooManyRedirects as err:
+ raise HTTPRequestError("Request exceeded the configured number "
+ "of maximum re-directions\n" + repr(err))
+ except Timeout as err:
+ raise HTTPRequestError("Request timed out. Timeout is set to "
+ "{0}\n".format(timeout) + repr(err))
+ except RequestException as err:
+ raise HTTPRequestError("Unexpected HTTP request exception.\n" +
+ repr(err))
+
+ @staticmethod
+ @keyword(name="HTTP Get")
+ def get(node, path, headers=None, timeout=10, enable_logging=True):
+ """Sends a GET request and returns the response and status code.
+
+ :param node: honeycomb node
+ :param path: URL path, e.g. /index.html
+ :param headers: Dictionary of HTTP Headers to send with the Request.
+ :param timeout: How long to wait for the server to send data before
+ giving up, as a float, or a (connect timeout, read timeout) tuple.
+ :param enable_logging: Used to suppress errors when checking
+ honeycomb state during suite setup and teardown. When True, logging
+ is enabled, otherwise logging is disabled.
+ :type node: dict
+ :type path: str
+ :type headers: dict
+ :type timeout: float or tuple
+ :type enable_logging: bool
+ :return: Status code and content of response
+ :rtype: tuple
+ """
+ return HTTPRequest._http_request('GET', node, path,
+ enable_logging=enable_logging,
+ headers=headers, timeout=timeout)
+
+ @staticmethod
+ @keyword(name="HTTP Put")
+ def put(node, path, headers=None, payload=None, timeout=10):
+ """Sends a PUT request and returns the response and status code.
+
+ :param node: honeycomb node
+ :param path: URL path, e.g. /index.html
+ :param headers: Dictionary of HTTP Headers to send with the Request.
+ :param payload: Dictionary, bytes, or file-like object to send in
+ the body of the Request.
+ :param timeout: How long to wait for the server to send data before
+ giving up, as a float, or a (connect timeout, read timeout) tuple.
+ :type node: dict
+ :type path: str
+ :type headers: dict
+ :type payload: dict, bytes, or file-like object
+ :type timeout: float or tuple
+ :return: Status code and content of response
+ :rtype: tuple
+ """
+ return HTTPRequest._http_request('PUT', node, path, headers=headers,
+ data=payload, timeout=timeout)
+
+ @staticmethod
+ @keyword(name="HTTP Post")
+ def post(node, path, headers=None, payload=None, json=None, timeout=10):
+ """Sends a POST request and returns the response and status code.
+
+ :param node: honeycomb node
+ :param path: URL path, e.g. /index.html
+ :param headers: Dictionary of HTTP Headers to send with the Request.
+ :param payload: Dictionary, bytes, or file-like object to send in
+ the body of the Request.
+ :param json: json data to send in the body of the Request
+ :param timeout: How long to wait for the server to send data before
+ giving up, as a float, or a (connect timeout, read timeout) tuple.
+ :type node: dict
+ :type path: str
+ :type headers: dict
+ :type payload: dict, bytes, or file-like object
+ :type json: str
+ :type timeout: float or tuple
+ :return: Status code and content of response
+ :rtype: tuple
+ """
+ return HTTPRequest._http_request('POST', node, path, headers=headers,
+ data=payload, json=json,
+ timeout=timeout)
+
+ @staticmethod
+ @keyword(name="HTTP Delete")
+ def delete(node, path, timeout=10):
+ """Sends a DELETE request and returns the response and status code.
+
+ :param node: honeycomb node
+ :param path: URL path, e.g. /index.html
+ :param timeout: How long to wait for the server to send data before
+ giving up, as a float, or a (connect timeout, read timeout) tuple.
+ :type node: dict
+ :type path: str
+ :type timeout: float or tuple
+ :return: Status code and content of response
+ :rtype: tuple
+ """
+ return HTTPRequest._http_request('DELETE', node, path, timeout=timeout)
diff --git a/resources/libraries/python/HoneycombSetup.py b/resources/libraries/python/HoneycombSetup.py
new file mode 100644
index 0000000000..de05eff6ed
--- /dev/null
+++ b/resources/libraries/python/HoneycombSetup.py
@@ -0,0 +1,301 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+"""Implements keywords for Honeycomb setup."""
+
+import os.path
+from xml.etree import ElementTree as ET
+
+from robot.api import logger
+
+from resources.libraries.python.topology import NodeType
+from resources.libraries.python.ssh import SSH
+from resources.libraries.python.HTTPRequest import HTTPRequest, \
+ HTTPRequestError, HTTP_CODES
+from resources.libraries.python.constants import Constants as C
+
+
+class HoneycombError(Exception):
+ """Exception(s) raised by methods working with Honeycomb."""
+
+ def __init__(self, msg, enable_logging=True):
+ """Sets the exception message and enables / disables logging
+
+ It is not wanted to log errors when using these keywords together
+ with keywords like "Wait until keyword succeeds".
+
+ :param msg: Message to be displayed and logged
+ :param enable_logging: When True, logging is enabled, otherwise
+ logging is disabled.
+ :type msg: str
+ :type enable_logging: bool
+ """
+ super(HoneycombError, self).__init__()
+ self._msg = msg
+ self._repr_msg = self.__module__ + '.' + \
+ self.__class__.__name__ + ": " + self._msg
+ if enable_logging:
+ logger.error(self._msg)
+ logger.debug(self._repr_msg)
+
+ def __repr__(self):
+ return repr(self._repr_msg)
+
+ def __str__(self):
+ return str(self._repr_msg)
+
+
+class HoneycombSetup(object):
+ """Implements keywords for Honeycomb setup."""
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def start_honeycomb_on_all_duts(nodes):
+ """Start honeycomb on all DUT nodes in topology.
+
+ :param nodes: all nodes in topology
+ :type nodes: dict
+ """
+ logger.console("Starting honeycomb service")
+
+ for node in nodes.values():
+ if node['type'] == NodeType.DUT:
+ HoneycombSetup.start_honeycomb(node)
+
+ @staticmethod
+ def start_honeycomb(node):
+ """Start up honeycomb on DUT node.
+
+ :param node: DUT node with honeycomb
+ :type node: dict
+ :return: ret_code, stdout, stderr
+ :rtype: tuple
+ :raises HoneycombError: if Honeycomb fails to start.
+ """
+
+ ssh = SSH()
+ ssh.connect(node)
+ cmd = os.path.join(C.REMOTE_HC_DIR, "start")
+ (ret_code, stdout, stderr) = ssh.exec_command_sudo(cmd)
+ if int(ret_code) != 0:
+ logger.debug('stdout: {0}'.format(stdout))
+ logger.debug('stderr: {0}'.format(stderr))
+ raise HoneycombError('Node {0} failed to start honeycomb'.
+ format(node['host']))
+ return ret_code, stdout, stderr
+
+ @staticmethod
+ def stop_honeycomb_on_all_duts(nodes):
+ """Stop the honeycomb service on all DUTs.
+
+ :param nodes: nodes in topology
+ :type nodes: dict
+ :return: ret_code, stdout, stderr
+ :rtype: tuple
+ :raises HoneycombError: if Honeycomb failed to stop.
+ """
+ logger.console("Shutting down honeycomb service")
+ errors = []
+ for node in nodes.values():
+ if node['type'] == NodeType.DUT:
+
+ ssh = SSH()
+ ssh.connect(node)
+ cmd = os.path.join(C.REMOTE_HC_DIR, "stop")
+ (ret_code, stdout, stderr) = ssh.exec_command_sudo(cmd)
+ if int(ret_code) != 0:
+ logger.debug('stdout: {0}'.format(stdout))
+ logger.debug('stderr: {0}'.format(stderr))
+ errors.append(node['host'])
+ continue
+ logger.info("Honeycomb was successfully stopped on node {0}.".
+ format(node['host']))
+ if errors:
+ raise HoneycombError('Node(s) {0} failed to stop honeycomb.'.
+ format(errors))
+
+ @staticmethod
+ def check_honeycomb_startup_state(nodes):
+ """Check state of honeycomb service during startup.
+
+ Reads html path from template file vpp_version.url
+
+ Honeycomb node replies with connection refused or the following status
+ codes depending on startup progress: codes 200, 401, 403, 404, 503
+
+ :param nodes: nodes in topology
+ :type nodes: dict
+ :return: True if all GETs returned code 200(OK)
+ :rtype bool
+ """
+
+ url_file = os.path.join(C.RESOURCES_TPL_HC, "vpp_version.url")
+ with open(url_file) as template:
+ data = template.readline()
+
+ expected_status_codes = (HTTP_CODES["UNAUTHORIZED"],
+ HTTP_CODES["FORBIDDEN"],
+ HTTP_CODES["NOT_FOUND"],
+ HTTP_CODES["SERVICE_UNAVAILABLE"])
+
+ for node in nodes.values():
+ if node['type'] == NodeType.DUT:
+ status_code, _ = HTTPRequest.get(node, data, timeout=10,
+ enable_logging=False)
+ if status_code == HTTP_CODES["OK"]:
+ pass
+ elif status_code in expected_status_codes:
+ if status_code == HTTP_CODES["UNAUTHORIZED"]:
+ logger.info('Unauthorized. If this triggers keyword '
+ 'timeout, verify honeycomb '
+ 'username and password')
+ raise HoneycombError('Honeycomb on node {0} running but '
+ 'not yet ready.'.format(node['host']),
+ enable_logging=False)
+ else:
+ raise HoneycombError('Unexpected return code: {0}'.
+ format(status_code))
+ return True
+
+ @staticmethod
+ def check_honeycomb_shutdown_state(nodes):
+ """Check state of honeycomb service during shutdown.
+
+ Honeycomb node replies with connection refused or the following status
+ codes depending on shutdown progress: codes 200, 404
+
+ :param nodes: nodes in topology
+ :type nodes: dict
+ :return: True if all GETs fail to connect
+ :rtype bool
+ """
+
+ for node in nodes.values():
+ if node['type'] == NodeType.DUT:
+ try:
+ status_code, _ = HTTPRequest.get(node, '/index.html',
+ timeout=5,
+ enable_logging=False)
+ if status_code == HTTP_CODES["OK"]:
+ raise HoneycombError('Honeycomb on node {0} is still '
+ 'running'.format(node['host']),
+ enable_logging=False)
+ elif status_code == HTTP_CODES["NOT_FOUND"]:
+ raise HoneycombError('Honeycomb on node {0} is shutting'
+ ' down'.format(node['host']),
+ enable_logging=False)
+ else:
+ raise HoneycombError('Unexpected return code: {'
+ '0}'.format(status_code))
+ except HTTPRequestError:
+ logger.debug('Connection refused')
+
+ return True
+
+
+ @staticmethod
+ def add_vpp_to_honeycomb_network_topology(nodes, headers):
+ """Add vpp node to Honeycomb network topology.
+
+ :param nodes: all nodes in test topology
+ :param headers: headers to be used with PUT requests
+ :type nodes: dict
+ :type headers: dict
+ :return: status code and response from PUT requests
+ :rtype: tuple
+ :raises HoneycombError: if a node was not added to honeycomb topology
+
+ Reads HTML path from template file config_topology_node.url
+ Path to the node to be added, e.g.:
+ ("/restconf/config/network-topology:network-topology"
+ "/topology/topology-netconf/node/")
+ There must be "/" at the end, as generated node name is added
+ at the end.
+
+ Reads payload data from template file add_vpp_to_topology.xml
+ Information about node as XML structure, e.g.:
+ <node xmlns="urn:TBD:params:xml:ns:yang:network-topology">
+ <node-id>
+ {vpp_host}
+ </node-id>
+ <host xmlns="urn:opendaylight:netconf-node-topology">
+ {vpp_ip}
+ </host>
+ <port xmlns="urn:opendaylight:netconf-node-topology">
+ {vpp_port}
+ </port>
+ <username xmlns="urn:opendaylight:netconf-node-topology">
+ {user}
+ </username>
+ <password xmlns="urn:opendaylight:netconf-node-topology">
+ {passwd}
+ </password>
+ <tcp-only xmlns="urn:opendaylight:netconf-node-topology">
+ false
+ </tcp-only>
+ <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">
+ 0
+ </keepalive-delay>
+ </node>
+ NOTE: The placeholders:
+ {vpp_host}
+ {vpp_ip}
+ {vpp_port}
+ {user}
+ {passwd}
+ MUST be there as they are replaced by correct values.
+ """
+
+ with open(os.path.join(C.RESOURCES_TPL_HC, "config_topology_node.url"))\
+ as template:
+ path = template.readline()
+
+ try:
+ xml_data = ET.parse(os.path.join(C.RESOURCES_TPL_HC,
+ "add_vpp_to_topology.xml"))
+ except ET.ParseError as err:
+ raise HoneycombError(repr(err))
+ data = ET.tostring(xml_data.getroot())
+
+ status_codes = []
+ responses = []
+ for node_name, node in nodes.items():
+ if node['type'] == NodeType.DUT:
+ try:
+ payload = data.format(
+ vpp_host=node_name,
+ vpp_ip=node["host"],
+ vpp_port=node['honeycomb']["netconf_port"],
+ user=node['honeycomb']["user"],
+ passwd=node['honeycomb']["passwd"])
+ status_code, resp = HTTPRequest.put(
+ node=node,
+ path=path + '/' + node_name,
+ headers=headers,
+ payload=payload)
+ if status_code != HTTP_CODES["OK"]:
+ raise HoneycombError(
+ "VPP {0} was not added to topology. "
+ "Status code: {1}".format(node["host"],
+ status_code))
+
+ status_codes.append(status_code)
+ responses.append(resp)
+
+ except HTTPRequestError as err:
+ raise HoneycombError("VPP {0} was not added to topology.\n"
+ "{1}".format(node["host"], repr(err)))
+
+ return status_codes, responses
diff --git a/resources/libraries/python/HoneycombUtil.py b/resources/libraries/python/HoneycombUtil.py
new file mode 100644
index 0000000000..c4dc3a067a
--- /dev/null
+++ b/resources/libraries/python/HoneycombUtil.py
@@ -0,0 +1,97 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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.
+
+"""Implements keywords used with Honeycomb."""
+
+import os.path
+from json import loads
+
+from robot.api import logger
+
+from resources.libraries.python.topology import NodeType
+from resources.libraries.python.HTTPRequest import HTTPRequest
+from resources.libraries.python.constants import Constants as C
+
+
+class HoneycombUtil(object):
+ """Implements keywords used with Honeycomb."""
+
+ def __init__(self):
+ pass
+
+ def get_configured_topology(self, nodes):
+ """Retrieves topology node IDs from each honeycomb node.
+
+ :param nodes: all nodes in topology
+ :type nodes: dict
+ :return: list of string IDs such as ['vpp1', 'vpp2']
+ :rtype list
+ """
+
+ url_file = os.path.join(C.RESOURCES_TPL_HC, "config_topology.url")
+ with open(url_file) as template:
+ path = template.readline()
+
+ data = []
+ for node in nodes.values():
+ if node['type'] == NodeType.DUT:
+ _, ret = HTTPRequest.get(node, path)
+ logger.debug('return: {0}'.format(ret))
+ data.append(self.parse_json_response(ret, ("topology",
+ "node", "node-id")))
+
+ return data
+
+ def parse_json_response(self, response, path=None):
+ """Parse data from response string in JSON format according to given
+ path.
+
+ :param response: JSON formatted string
+ :param path: Path to navigate down the data structure
+ :type response: string
+ :type path: tuple
+ :return: JSON dictionary/list tree
+ :rtype: dict
+ """
+ data = loads(response)
+
+ if path:
+ data = self._parse_json_tree(data, path)
+ while isinstance(data, list) and len(data) == 1:
+ data = data[0]
+
+ return data
+
+ def _parse_json_tree(self, data, path):
+ """Retrieve data from python representation of JSON object.
+
+ :param data: parsed JSON dictionary tree
+ :param path: Path to navigate down the dictionary tree
+ :type data: dict
+ :type path: tuple
+ :return: data from specified path
+ :rtype: list or str
+ """
+
+ count = 0
+ for key in path:
+ if isinstance(data, dict):
+ data = data[key]
+ count += 1
+ elif isinstance(data, list):
+ result = []
+ for item in data:
+ result.append(self._parse_json_tree(item, path[count:]))
+ return result
+
+ return data
diff --git a/resources/libraries/python/constants.py b/resources/libraries/python/constants.py
index f9bbc46a95..b3a61da16a 100644
--- a/resources/libraries/python/constants.py
+++ b/resources/libraries/python/constants.py
@@ -10,10 +10,23 @@
# 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.
+
+
class Constants(object):
- #OpenVPP testing directory location at topology nodes
+ # OpenVPP testing directory location at topology nodes
REMOTE_FW_DIR = '/tmp/openvpp-testing'
+
+ # shell scripts location
RESOURCES_LIB_SH = 'resources/libraries/bash'
+
+ # vat templates location
RESOURCES_TPL_VAT = 'resources/templates/vat'
- #OpenVPP VAT binary name
+
+ # OpenVPP VAT binary name
VAT_BIN_NAME = 'vpp_api_test'
+
+ # Honeycomb directory location at topology nodes:
+ REMOTE_HC_DIR = '/opt/honeycomb/v3po-karaf-1.0.0-SNAPSHOT/bin'
+
+ # Honeycomb templates location
+ RESOURCES_TPL_HC = 'resources/templates/honeycomb'
diff --git a/resources/libraries/robot/honeycomb.robot b/resources/libraries/robot/honeycomb.robot
new file mode 100644
index 0000000000..98b8e23ae8
--- /dev/null
+++ b/resources/libraries/robot/honeycomb.robot
@@ -0,0 +1,68 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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 ***
+| Library | resources/libraries/python/HoneycombSetup.py
+| Library | resources/libraries/python/HoneycombUtil.py
+| Library | resources/libraries/python/HTTPRequest.py
+
+*** Keywords ***
+| Setup Honeycomb service
+| | [Documentation] | *Setup environment for honeycomb testing*
+| | ...
+| | ... | _Setup steps:_
+| | ... | - 1. Login to each honeycomb node using ssh
+| | ... | - 2. Startup honeycomb service
+| | ... | - 3. Monitor service startup using HTTP GET request loop
+| | ... | Expected sequence of HTTP replies: connection refused -> 404 -> 401 -> 503 -> 200 (pass)
+| | ... | - 4. Configure honeycomb nodes using HTTP PUT request
+| | ...
+| | ... | _Used global constants and variables:_
+| | ... | - RESOURCES_TPL_HC - path to honeycomb templates directory
+| | ... | - HTTPCodes - HTTP protocol status codes
+| | ... | - ${nodes} - dictionary of all nodes in topology.YAML file
+| | ...
+| | Start Honeycomb on all DUTs | ${nodes}
+| | Wait until keyword succeeds | 3min | 10sec | Check honeycomb startup state | ${nodes}
+| | &{Header}= | Create dictionary | Content-Type=application/xml
+| | Add VPP to honeycomb network topology | ${nodes} | ${header}
+
+| Stop honeycomb service
+| | [Documentation] | *Cleanup environment after honeycomb testing*
+| | ...
+| | ... | _Teardown steps:_
+| | ... | - 1. Login to each honeycomb node using ssh
+| | ... | - 2. Stop honeycomb service
+| | ... | - 3. Monitor service shutdown using HTTP GET request loop
+| | ... | Expected sequence of HTTP replies: 200 -> 404 -> connection refused (pass)
+| | ...
+| | ... | _Used global constants and variables:_
+| | ... | - RESOURCES_TPL_HC - path to honeycomb templates directory
+| | ... | - HTTPCodes - HTTP protocol status codes
+| | ... | - ${nodes} - dictionary of all nodes in topology.YAML file
+| | ...
+| | Stop honeycomb on all DUTs | ${nodes}
+| | Wait until keyword succeeds | 1m | 5s | Check honeycomb shutdown state | ${nodes}
+
+| Honeycomb checks VPP node configuration
+| | [Documentation] | *Check configuration of honeycomb nodes*
+| | ...
+| | ... | _Arguments:_
+| | ... | - None
+| | ...
+| | ... | _Return value:_
+| | ... | - None
+| | ...
+| | ${reply}= | Get configured topology | ${nodes}
+| | :FOR | ${item} | IN | @{reply}
+| | | Should match regexp | ${item} | ^DUT\\d{1,2}$ \ No newline at end of file
diff --git a/resources/templates/honeycomb/add_vpp_to_topology.xml b/resources/templates/honeycomb/add_vpp_to_topology.xml
new file mode 100644
index 0000000000..46a84f28e8
--- /dev/null
+++ b/resources/templates/honeycomb/add_vpp_to_topology.xml
@@ -0,0 +1,9 @@
+<node xmlns="urn:TBD:params:xml:ns:yang:network-topology">
+ <node-id>{vpp_host}</node-id>
+ <host xmlns="urn:opendaylight:netconf-node-topology">{vpp_ip}</host>
+ <port xmlns="urn:opendaylight:netconf-node-topology">{vpp_port}</port>
+ <username xmlns="urn:opendaylight:netconf-node-topology">{user}</username>
+ <password xmlns="urn:opendaylight:netconf-node-topology">{passwd}</password>
+ <tcp-only xmlns="urn:opendaylight:netconf-node-topology">false</tcp-only>
+ <keepalive-delay xmlns="urn:opendaylight:netconf-node-topology">0</keepalive-delay>
+</node> \ No newline at end of file
diff --git a/resources/templates/honeycomb/config_topology.url b/resources/templates/honeycomb/config_topology.url
new file mode 100644
index 0000000000..e76f47ce8c
--- /dev/null
+++ b/resources/templates/honeycomb/config_topology.url
@@ -0,0 +1 @@
+/restconf/config/network-topology:network-topology/topology/topology-netconf \ No newline at end of file
diff --git a/resources/templates/honeycomb/config_topology_node.url b/resources/templates/honeycomb/config_topology_node.url
new file mode 100644
index 0000000000..89b7aa851e
--- /dev/null
+++ b/resources/templates/honeycomb/config_topology_node.url
@@ -0,0 +1 @@
+/restconf/config/network-topology:network-topology/topology/topology-netconf/node \ No newline at end of file
diff --git a/resources/templates/honeycomb/vpp_version.url b/resources/templates/honeycomb/vpp_version.url
new file mode 100644
index 0000000000..59759be8ea
--- /dev/null
+++ b/resources/templates/honeycomb/vpp_version.url
@@ -0,0 +1 @@
+/restconf/operational/v3po:vpp-state/version \ No newline at end of file
diff --git a/resources/topology_schemas/topology.sch.yaml b/resources/topology_schemas/topology.sch.yaml
index 33b4e7bfc0..1c20055e0f 100644
--- a/resources/topology_schemas/topology.sch.yaml
+++ b/resources/topology_schemas/topology.sch.yaml
@@ -70,6 +70,22 @@ schema;type_interface_tg: &type_interface_tg
<<: *type_interface_mapping_driver
required: yes
+schema;type_honeycomb: &type_honeycomb
+ type: map
+ mapping: &type_honeycomb_mapping
+ user:
+ type: str
+ required: yes
+ passwd:
+ type: str
+ required: yes
+ port:
+ type: int
+ required: yes
+ netconf_port:
+ type: int
+ required: yes
+
schema;type_node: &type_node
type: map
mapping: &type_node_mapping
@@ -108,6 +124,8 @@ schema;type_dut:
type: map
mapping:
<<: *type_node_mapping
+ honeycomb:
+ <<: *type_honeycomb_mapping
type:
<<: *type_node_mapping_type
enum: [DUT]
diff --git a/tests/suites/honeycomb/sanity.robot b/tests/suites/honeycomb/sanity.robot
new file mode 100644
index 0000000000..5264f997f4
--- /dev/null
+++ b/tests/suites/honeycomb/sanity.robot
@@ -0,0 +1,37 @@
+# Copyright (c) 2016 Cisco and/or its affiliates.
+# 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 ***
+| Resource | resources/libraries/robot/honeycomb.robot
+| Suite Setup | Setup Honeycomb service
+| Suite Teardown | Stop Honeycomb service
+
+*** Test Cases ***
+| Honeycomb reports running configuration
+| | [Documentation] | *Check the contents of honeycomb configuration*
+| | ...
+| | ... | _Test steps:_
+| | ... | - 1. Send HTTP GET request to obtain configured topology from all honeycomb nodes
+| | ... | - 2. Retrieve configuration as JSON object
+| | ... | - 3. Parse JSON for VPP instance ID string
+| | ... | - 4. regex match ID string against expected pattern (vpp1, vpp2, vpp3,...)
+| | ...
+| | ... | _Pass criteria:_
+| | ... | The test passes if the ID strings of VPP instances on each honeycomb node match the expected pattern
+| | ...
+| | ... | _Used global constants and variables:_
+| | ... | - RESOURCES_TPL_HC - path to honeycomb templates directory
+| | ... | - nodes - dictionary of all nodes in topology.YAML file
+| | ...
+| | [Tags] | honeycomb_sanity
+| | Honeycomb checks VPP node configuration \ No newline at end of file