From: selias Date: Wed, 6 Jul 2016 06:50:00 +0000 (+0200) Subject: CSIT-190 Add honeycomb interface management test X-Git-Url: https://gerrit.fd.io/r/gitweb?p=csit.git;a=commitdiff_plain;h=c10eda554529ac5433216a18d30e9964c6af7976 CSIT-190 Add honeycomb interface management test - add test for specific issue with interface management through netconf - add keywords used in test - update methods that manage Netconf communication - update notifications tests to reuse more code Change-Id: I11788e18f05c4965160f247c3811d270d2c91170 Signed-off-by: selias --- diff --git a/resources/libraries/python/honeycomb/Netconf.py b/resources/libraries/python/honeycomb/Netconf.py new file mode 100644 index 0000000000..3c98387334 --- /dev/null +++ b/resources/libraries/python/honeycomb/Netconf.py @@ -0,0 +1,170 @@ +# 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. + +"""Keywords used to connect to Honeycomb through Netconf, send messages + and receive replies.""" + +from time import time + +import paramiko +import socket +from robot.api import logger +from interruptingcow import timeout + +from resources.libraries.python.honeycomb.HoneycombUtil import HoneycombError + + +class Netconf(object): + """Implements methods for creating and managing Netconf sessions.""" + + def __init__(self, delimiter=']]>]]>'): + """Initializer. + + Note: Passing the channel object as a robotframework argument closes + the channel. Class variables are used instead, + to persist the connection channel throughout test cases. + """ + + self.client = None + self.channel = None + self.delimiter = delimiter + + def create_session(self, node, hello, time_out=10): + """Create an SSH session, connect to Honeycomb on the specified node, + open a communication channel to the Netconf subsystem and exchange hello + messages. + + :param node: Honeycomb node. + :param hello: Hello message and capability list to be sent to Honeycomb. + :param time_out: Timeout value for the connection in seconds. + :type node: dict + :type hello: str + :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.set_combine_stderr(True) + 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(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(self.delimiter) or \ + self.channel.recv_ready(): + try: + chunk = self.channel.recv(size) + if not chunk: + break + reply += chunk + if self.channel.exit_status_ready(): + logger.debug('Channel exit status ready.') + break + except socket.timeout: + raise HoneycombError("Socket timeout.", + enable_logging=False + ) + + except RuntimeError: + raise HoneycombError(err + " Content of buffer: {0}".format(reply), + enable_logging=False + ) + + logger.trace(reply) + return reply.replace(self.delimiter, "") + + def get_all_responses(self, size=4096, time_out=3): + """Read responses from the receive buffer and catenate together + until a read operation times out. + + :param size: Maximum number of bytes to read in one iteration. + :param time_out: Timeout value for getting the complete response. + :type size:int + :type time_out:int + :return: Content of response. + :rtype: str + """ + + response = "" + err = "Expected timeout occurred." + + while True: + try: + response += self.get_response(size, time_out, err) + except HoneycombError: + break + + return response + + def send(self, message): + """Sends provided message through the channel. + + :param message: Message to be sent to Honeycomb. + :type message: str + """ + + if not message.endswith(self.delimiter): + message += self.delimiter + + self.channel.send(message) diff --git a/resources/libraries/python/honeycomb/Notifications.py b/resources/libraries/python/honeycomb/Notifications.py index 62cda16cf2..512a6cbb08 100644 --- a/resources/libraries/python/honeycomb/Notifications.py +++ b/resources/libraries/python/honeycomb/Notifications.py @@ -13,131 +13,35 @@ """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 +from resources.libraries.python.honeycomb.Netconf import Netconf -class Notifications(object): - """Implements keywords for managing Honeycomb notifications. +class Notifications(Netconf): + """Implements keywords for receiving 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): + def add_notification_listener(self, subscription, time_out=10): """Open a new channel on the SSH session, connect to Netconf subsystem and subscribe to receive Honeycomb notifications. + :param subscription: RPC for subscription to notifications. :param time_out: Timeout value for each read operation in seconds. + :type subscription: str :type time_out: int :raises HoneycombError: If subscription to notifications fails. """ - self.channel.send(self.subscription) + logger.debug(subscription) + self.send(subscription) - reply = self._get_response( + reply = self.get_response( time_out=time_out, err="Timeout on notifications subscription." ) @@ -157,9 +61,10 @@ class Notifications(object): :rtype: str """ - logger.debug("Getting notification.") + logger.debug("Getting notification. Timeout set to {0} seconds." + .format(time_out)) - reply = self._get_response( + reply = self.get_response( time_out=time_out, err="Timeout on getting notification." ) diff --git a/resources/libraries/robot/honeycomb/netconf.robot b/resources/libraries/robot/honeycomb/netconf.robot new file mode 100644 index 0000000000..ce7b6a6b38 --- /dev/null +++ b/resources/libraries/robot/honeycomb/netconf.robot @@ -0,0 +1,56 @@ +# 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.Netconf +| Variables | tests/suites/honeycomb/resources/netconf/hello.py +| Documentation | Keywords for managing Netconf communication. + +*** Keywords *** +| Netconf session is established +| | [Documentation] | Open a communication channel on the Netconf session\ +| | ... | and exchange hello messages. +| | ... +| | ... | *Arguments:* +| | ... | - node - information about a DUT node. Type: dict +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Netconf session is established \| ${nodes['DUT1']} \| +| | [Arguments] | ${node} +| | Create session | ${node} | ${hello} + +| Error trigger is sent +| | [Documentation] | Send the specified error trigger through the channel. +| | ... +| | ... | *Arguments:* +| | ... | - trigger - RPC sequence that triggers a specific error. Type: string +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Error trigger is sent \| _some data_]]>]]> \| +| | [Arguments] | ${trigger} +| | Send | ${trigger} + +| Replies should not contain RPC errors +| | [Documentation] | Read response received through the channel, and check if\ +| | ... | it is an error report. +| | ... +| | ... | *Arguments:* +| | ... | none +| | ... +| | ... | *Example:* +| | ... +| | ... | \| Replies should not contain RPC errors \| +| | ${resp}= | Get all responses +| | should not contain | ${resp} | rpc-error diff --git a/resources/libraries/robot/honeycomb/notifications.robot b/resources/libraries/robot/honeycomb/notifications.robot index ded8d3a2c0..119a351e7d 100644 --- a/resources/libraries/robot/honeycomb/notifications.robot +++ b/resources/libraries/robot/honeycomb/notifications.robot @@ -12,35 +12,24 @@ # limitations under the License. *** Settings *** -| Library | resources.libraries.python.honeycomb.Notifications | ${hello} -| ... | ${subscription} +| Library | resources.libraries.python.honeycomb.Notifications +| Variables | tests/suites/honeycomb/resources/netconf/hello.py +| Variables | tests/suites/honeycomb/resources/netconf/subscription.py | Documentation | Keywords used to test Honeycomb notifications over Netconf. -*** Variables *** -# hello message sent to Honeycomb through Netconf -| ${hello}= | -| ... | urn:ietf:params:netconf:base:1.0 -| ... | ]]>]]> -# rpc call to add a subscription to Netconf notifications -| ${subscription}= | -| ... | -| ... | honeycomb]]>]]> - *** Keywords *** | Notification listener is established | | [Documentation] | Connects to Honeycomb notification service. | | ... | | ... | *Arguments:* -| | ... | - node - information about a DUT node. Type: dictionary +| | ... | - node - information about a DUT node. Type: dict | | ... | | ... | *Example:* | | ... | | ... | \| Notification listener is established \| ${nodes['DUT1']} \| | | [Arguments] | ${node} -| | Create session | ${node} -| | Add notification listener +| | Create session | ${node} | ${hello} +| | Add notification listener | ${subscription} | Honeycomb should send interface state notification | | [Documentation] | Reads notification from Honeycomb and verifies\ diff --git a/tests/suites/honeycomb/070_netconf.robot b/tests/suites/honeycomb/070_netconf.robot new file mode 100644 index 0000000000..998b550bf6 --- /dev/null +++ b/tests/suites/honeycomb/070_netconf.robot @@ -0,0 +1,29 @@ +# 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/default.robot +| Resource | resources/libraries/robot/honeycomb/netconf.robot +| Variables | tests/suites/honeycomb/resources/netconf/triggers.py +| Documentation | *Netconf test suite. Contains test cases that need to bypass\ +| ... | REST API.* +| Force Tags | honeycomb_sanity + +*** Test Cases *** +| Honeycomb can create and delete interfaces +| | [Documentation] | Repeatedly create and delete an interface through Netconf\ +| | ... | and check the reply for any errors. +| | Given Netconf session is established | ${node} +| | :FOR | ${index} | IN RANGE | 20 +| | | When Error trigger is sent | ${trigger_105} +| | | Then Replies should not contain RPC errors diff --git a/tests/suites/honeycomb/070_notification.robot b/tests/suites/honeycomb/071_notification.robot similarity index 100% rename from tests/suites/honeycomb/070_notification.robot rename to tests/suites/honeycomb/071_notification.robot diff --git a/tests/suites/honeycomb/resources/netconf/hello.py b/tests/suites/honeycomb/resources/netconf/hello.py new file mode 100644 index 0000000000..2cc89b387c --- /dev/null +++ b/tests/suites/honeycomb/resources/netconf/hello.py @@ -0,0 +1,22 @@ +# 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. + +"""Hello message with capabilities list for Netconf sessions.""" + +hello = u""" + + +urn:ietf:params:netconf:base:1.0 + + +]]>]]>""" \ No newline at end of file diff --git a/tests/suites/honeycomb/resources/netconf/subscription.py b/tests/suites/honeycomb/resources/netconf/subscription.py new file mode 100644 index 0000000000..ccf8c47022 --- /dev/null +++ b/tests/suites/honeycomb/resources/netconf/subscription.py @@ -0,0 +1,24 @@ +# 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. + +"""RPC call to add a subscription to Netconf notifications.""" + +subscription = u""" + + +honeycomb + + +]]>]]>""" diff --git a/tests/suites/honeycomb/resources/netconf/triggers.py b/tests/suites/honeycomb/resources/netconf/triggers.py new file mode 100644 index 0000000000..77596bd052 --- /dev/null +++ b/tests/suites/honeycomb/resources/netconf/triggers.py @@ -0,0 +1,336 @@ +# 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. + +"""Contains RPC sequences to trigger specific issues through Netconf.""" + +# Test data for issue: https://jira.fd.io/browse/HONEYCOMB-105 +# Creating and removing interfaces may result in duplicated interface indices. +trigger_105 = u""" + + + + + + + + +4fe335c8-6fdc-4654-b12c-d256e9b39229 + + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + +none + + + +4fe335c8-6fdc-4654-b12c-d256e9b39229 +neutron port +enabled + +client +/tmp/socket_4fe335c8-6fdc-4654-b12c-d256e9b39229 + +x:vhost-user +true + + + + + +]]>]]> + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + +none + + + +4fe335c8-6fdc-4654-b12c-d256e9b39229 + + + + + +]]>]]> + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + +none + + + +4fe335c8-6fdc-4654-b12c-d256e9b39229 +neutron port +enabled + +client +/tmp/socket_4fe335c8-6fdc-4654-b12c-d256e9b39229 + +x:vhost-user +true + + + + + +]]>]]> + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + +none + + + +d7611278-88ff-40e1-81e2-602e94e96fc7 +neutron port +enabled + +client +/tmp/socket_d7611278-88ff-40e1-81e2-602e94e96fc7 + +x:vhost-user +true + + + + + +]]>]]> + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + +none + + + +1f96a665-4351-4984-b1a8-dc6f54683123 +neutron port +enabled + +client +/tmp/socket_1f96a665-4351-4984-b1a8-dc6f54683123 + +x:vhost-user +true + + + + + +]]>]]> + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + +none + + + + +e86740a2-042c-4e64-a43b-cc224e0d5240 +true +true +true +true +false + + + + + + +]]>]]> + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + + + +]]>]]> + + + + + + + + +]]>]]>"""