diff options
-rw-r--r-- | resources/libraries/python/honeycomb/Notifications.py | 167 | ||||
-rw-r--r-- | resources/libraries/robot/honeycomb/notifications.robot | 77 | ||||
-rw-r--r-- | resources/libraries/robot/honeycomb/tap.robot | 1 | ||||
-rw-r--r-- | tests/suites/honeycomb/9 - notification.robot | 57 |
4 files changed, 301 insertions, 1 deletions
diff --git a/resources/libraries/python/honeycomb/Notifications.py b/resources/libraries/python/honeycomb/Notifications.py new file mode 100644 index 0000000000..62cda16cf2 --- /dev/null +++ b/resources/libraries/python/honeycomb/Notifications.py @@ -0,0 +1,167 @@ +# 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. + +"""Implementation of keywords for managing Honeycomb notifications.""" + +from time import time + +import paramiko +from robot.api import logger +from interruptingcow import timeout + +from resources.libraries.python.honeycomb.HoneycombUtil import HoneycombError + + +class Notifications(object): + """Implements keywords for managing Honeycomb notifications. + + The keywords implemented in this class make it possible to: + - establish SSH session to Honeycomb host + - receive notifications from Honeycomb + - read received notifications + """ + + def __init__(self, hello, subscription): + """Initializer. + :param hello: Hello message to be sent to Honeycomb. + :param subscription: rpc command to subscribe to Honeycomb notifications + over Netconf. + :type hello: str + :type subscription: str + + Note: Passing the channel object as a robotframework argument closes + the channel. Class variables are used instead, + to persist the connection channel throughout the test case. + """ + + self.client = None + self.channel = None + self.hello = hello + self.subscription = subscription + + def create_session(self, node, time_out=10): + """Create an SSH session and connect to Honeycomb on the specified node. + + :param node: Honeycomb node. + :param time_out: Timeout value for the connection in seconds. + :type node: dict + :type time_out: int + """ + + start = time() + client = paramiko.SSHClient() + client.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + + client.connect(node['host'], + username=node['honeycomb']['user'], + password=node['honeycomb']['passwd'], + pkey=None, + port=node['honeycomb']['netconf_port'], + timeout=time_out, + ) + + logger.trace('Connect took {0} seconds'.format(time() - start)) + logger.debug('New ssh: {0}'.format(client)) + logger.debug('Connect peer: {0}'. + format(client.get_transport().getpeername())) + logger.debug(client) + + channel = client.get_transport().open_session() + channel.settimeout(time_out) + channel.get_pty() + channel.invoke_subsystem("netconf") + logger.debug(channel) + + self.client = client + self.channel = channel + + # read OpenDaylight's hello message and capability list + self._get_response( + size=131072, + time_out=time_out, + err="Timeout on getting hello message." + ) + + self.channel.send(self.hello) + if not self.channel.active: + raise HoneycombError("Channel closed on capabilities exchange.") + + def _get_response(self, size=4096, time_out=10, err="Unspecified Error."): + """Iteratively read data from the receive buffer and catenate together + until message ends with the message delimiter, or + until timeout is reached. + + :param size: Maximum number of bytes to read in one iteration. + :param time_out: Timeout value for getting the complete response. + :param err: Error message to provide when timeout is reached. + :type size:int + :type time_out:int + :type err:str + :return: Content of response. + :rtype: str + :raises HoneycombError: If the read process times out. + """ + + reply = '' + + try: + with timeout(time_out, exception=RuntimeError): + while not reply.endswith(']]>]]>'): + if self.channel.recv_ready(): + reply += self.channel.recv(size) + + except RuntimeError: + raise HoneycombError(err+" Content of buffer: {0}".format(reply)) + + logger.trace(reply) + return reply + + def add_notification_listener(self, time_out=10): + """Open a new channel on the SSH session, connect to Netconf subsystem + and subscribe to receive Honeycomb notifications. + + :param time_out: Timeout value for each read operation in seconds. + :type time_out: int + :raises HoneycombError: If subscription to notifications fails. + """ + + self.channel.send(self.subscription) + + reply = self._get_response( + time_out=time_out, + err="Timeout on notifications subscription." + ) + + if "<ok/>" not in reply: + raise HoneycombError("Notifications subscription failed with" + " message: {0}".format(reply)) + + logger.debug("Notifications subscription successful.") + + def get_notification(self, time_out=10): + """Read and return the next notification message. + + :param time_out: Timeout value for the read operation in seconds. + :type time_out: int + :return: Data received from buffer. + :rtype: str + """ + + logger.debug("Getting notification.") + + reply = self._get_response( + time_out=time_out, + err="Timeout on getting notification." + ) + + return reply diff --git a/resources/libraries/robot/honeycomb/notifications.robot b/resources/libraries/robot/honeycomb/notifications.robot new file mode 100644 index 0000000000..ded8d3a2c0 --- /dev/null +++ b/resources/libraries/robot/honeycomb/notifications.robot @@ -0,0 +1,77 @@ +# 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.honeycomb.Notifications | ${hello} +| ... | ${subscription} +| Documentation | Keywords used to test Honeycomb notifications over Netconf. + +*** Variables *** +# hello message sent to Honeycomb through Netconf +| ${hello}= | <hello xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> +| ... | <capabilities><capability>urn:ietf:params:netconf:base:1.0 +| ... | </capability></capabilities></hello>]]>]]> +# rpc call to add a subscription to Netconf notifications +| ${subscription}= | <netconf:rpc netconf:message-id="101" +| ... | xmlns:netconf="urn:ietf:params:xml:ns:netconf:base:1.0"> +| ... | <create-subscription +| ... | xmlns="urn:ietf:params:xml:ns:netconf:notification:1.0"> +| ... | <stream>honeycomb</stream></create-subscription></netconf:rpc>]]>]]> + +*** Keywords *** +| Notification listener is established +| | [Documentation] | Connects to Honeycomb notification service. +| | ... +| | ... | *Arguments:* +| | ... | - node - information about a DUT node. Type: dictionary +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Notification listener is established \| ${nodes['DUT1']} \| +| | [Arguments] | ${node} +| | Create session | ${node} +| | Add notification listener + +| Honeycomb should send interface state notification +| | [Documentation] | Reads notification from Honeycomb and verifies\ +| | ... | notification type, interface name and interface admin-state. +| | ... +| | ... | *Arguments:* +| | ... | - interface - name of the affected interface. Type: string +| | ... | - state - expected state of interface, 'up' or 'down'. Type: string +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Honeycomb should send interface state notification \ +| | ... | \| GigabitEthernet0/8/0 \| up \| +| | [Arguments] | ${interface} | ${state} +| | ${reply}= | Get notification +| | Should contain | ${reply} | <interface-state-change +| | Should contain | ${reply} | <name>${interface}</name> +| | Should contain | ${reply} | <admin-status>${state}</admin-status> + +| Honeycomb should send interface deleted notification +| | [Documentation] | Reads notification from Honeycomb and verifies\ +| | ... | notification type and interface name. +| | ... +| | ... | *Arguments:* +| | ... | - interface - name of the deleted interface. Type: string +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Honeycomb should send interface deleted notification \ +| | ... | \| temp_interface \| +| | [Arguments] | ${interface} +| | ${reply}= | Get notification +| | Should contain | ${reply} | <interface-deleted +| | Should contain | ${reply} | <name>${interface}</name> diff --git a/resources/libraries/robot/honeycomb/tap.robot b/resources/libraries/robot/honeycomb/tap.robot index f503b60090..2fffffa067 100644 --- a/resources/libraries/robot/honeycomb/tap.robot +++ b/resources/libraries/robot/honeycomb/tap.robot @@ -106,7 +106,6 @@ | | [Arguments] | ${node} | ${interface} | ${settings} | | ${vat_data}= | TAP Dump | ${node} | ${interface} | | Should be equal | ${vat_data['dev_name']} | ${settings['tap-name']} -| | ${vat_data}= | VPP Get interface data | ${node} # other settings not accessible through VAT commands | TAP configuration from Honeycomb should be empty diff --git a/tests/suites/honeycomb/9 - notification.robot b/tests/suites/honeycomb/9 - notification.robot new file mode 100644 index 0000000000..5769ea0e73 --- /dev/null +++ b/tests/suites/honeycomb/9 - notification.robot @@ -0,0 +1,57 @@ +# 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. + +*** Variables *** +# Node and interfaces to run tests on. +| ${node}= | ${nodes['DUT1']} +| ${interface}= | ${node['interfaces']['port1']['name']} +| ${tap_interface}= | tap_test +| &{tap_settings}= | tap-name=tap_test | mac=08:00:27:c0:5d:37 +| ... | device-instance=${1} + +*** Settings *** +| Resource | resources/libraries/robot/default.robot +| Resource | resources/libraries/robot/honeycomb/interfaces.robot +| Resource | resources/libraries/robot/honeycomb/tap.robot +| Resource | resources/libraries/robot/honeycomb/notifications.robot +| Suite Setup | Run keywords +| ... | Honeycomb sets interface state +| ... | ${node} | ${interface} | down | AND +| ... | Honeycomb creates TAP interface +| ... | ${node} | ${tap_interface} | ${tap_settings} +| Documentation | *Honeycomb notifications test suite.* +| Force Tags | honeycomb_sanity + +*** Test Cases *** +| Honeycomb sends notification on interface state change +| | [Documentation] | Check if Honeycomb sends a state-changed notification\ +| | ... | when the state of an interface is changed. +| | Given Interface state from Honeycomb should be +| | ... | ${node} | ${interface} | down +| | And Interface state from VAT should be | ${node} | ${interface} | down +| | And Notification listener is established | ${node} +| | When Honeycomb sets interface state | ${node} | ${interface} | up +| | Then Honeycomb should send interface state notification | ${interface} | up +| | When Honeycomb sets interface state | ${node} | ${interface} | down +| | And Honeycomb should send interface state notification | ${interface} | down + +| Honeycomb sends notification on interface deletion +| | [Documentation] | Check if Honeycomb sends an interface-deleted notification +| | ... | when an interface is deleted. +| | Given TAP configuration from Honeycomb should be +| | ... | ${node} | ${tap_interface} | ${tap_settings} +| | And TAP configuration from VAT should be +| | ... | ${node} | ${tap_interface} | ${tap_settings} +| | And Notification listener is established | ${node} +| | When Honeycomb removes TAP interface | ${node} | ${tap_interface} +| | Then Honeycomb should send interface deleted notification | ${tap_interface} |