From 705164604a65e6cb7c9d08e57b17086d226d2ecd Mon Sep 17 00:00:00 2001 From: Alex Katz Date: Thu, 15 Oct 2020 13:39:29 +0300 Subject: [PATCH] Test nova to neutron port notification Nova should update neutron in case port is attached to the instance that has been booted recently. We should see the log with server id and port id on one of the controller nodes. Change-Id: Id0b0a10cf83ee8d2173dd25b7417f6f8f96af3ac --- tobiko/openstack/neutron/__init__.py | 2 + tobiko/openstack/neutron/_agent.py | 10 ++ tobiko/shell/files/__init__.py | 26 +++++ tobiko/shell/files/_exception.py | 26 +++++ tobiko/shell/files/_logs.py | 110 +++++++++++++++++++++ tobiko/shell/files/config.py | 16 +++ tobiko/tests/scenario/neutron/test_port.py | 31 ++++++ 7 files changed, 221 insertions(+) create mode 100644 tobiko/shell/files/__init__.py create mode 100644 tobiko/shell/files/_exception.py create mode 100644 tobiko/shell/files/_logs.py create mode 100644 tobiko/shell/files/config.py diff --git a/tobiko/openstack/neutron/__init__.py b/tobiko/openstack/neutron/__init__.py index 8f4b19bc1..dba2e872e 100644 --- a/tobiko/openstack/neutron/__init__.py +++ b/tobiko/openstack/neutron/__init__.py @@ -28,6 +28,8 @@ OVN_CONTROLLER = _agent.OVN_CONTROLLER OVN_METADATA_AGENT = _agent.OVN_METADATA_AGENT AgentNotFoundOnHost = _agent.AgentNotFoundOnHost skip_if_missing_networking_agents = _agent.skip_if_missing_networking_agents +skip_unless_is_ovn = _agent.skip_unless_is_ovn +skip_unless_is_ovs = _agent.skip_unless_is_ovs list_networking_agents = _agent.list_networking_agents diff --git a/tobiko/openstack/neutron/_agent.py b/tobiko/openstack/neutron/_agent.py index 3e3f0fc3d..dfca35a05 100644 --- a/tobiko/openstack/neutron/_agent.py +++ b/tobiko/openstack/neutron/_agent.py @@ -69,3 +69,13 @@ def skip_if_missing_networking_agents(binary: typing.Optional[str] = None, ', '.join("{!s}={!r}".format(k, v) for k, v in params.items())) return tobiko.skip_if(message, missing_networking_agents, count=count, **params) + + +def skip_unless_is_ovn(): + '''Skip the test if ovn_controller agent does not exist''' + return skip_if_missing_networking_agents(OVN_CONTROLLER) + + +def skip_unless_is_ovs(): + '''Skip the test if openvswitch agent does not exist''' + return skip_if_missing_networking_agents(OPENVSWITCH_AGENT) diff --git a/tobiko/shell/files/__init__.py b/tobiko/shell/files/__init__.py new file mode 100644 index 000000000..2d08d8bde --- /dev/null +++ b/tobiko/shell/files/__init__.py @@ -0,0 +1,26 @@ +# Copyright (c) 2020 Red Hat, Inc. +# +# All Rights Reserved. +# +# 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 + +from tobiko.shell.files import _exception +from tobiko.shell.files import _logs + + +LogParserError = _exception.LogParserError +LogFileNotFound = _exception.LogFileNotFound + +LogFile = _logs.LogFile +ClusterLogFile = _logs.ClusterLogFile diff --git a/tobiko/shell/files/_exception.py b/tobiko/shell/files/_exception.py new file mode 100644 index 000000000..a931e05e4 --- /dev/null +++ b/tobiko/shell/files/_exception.py @@ -0,0 +1,26 @@ +# Copyright (c) 2020 Red Hat, Inc. +# +# All Rights Reserved. +# +# 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 tobiko + + +class LogParserError(tobiko.TobikoException): + pass + + +class LogFileNotFound(LogParserError): + message = 'File {filename} was not found on {host}' diff --git a/tobiko/shell/files/_logs.py b/tobiko/shell/files/_logs.py new file mode 100644 index 000000000..7d719ad19 --- /dev/null +++ b/tobiko/shell/files/_logs.py @@ -0,0 +1,110 @@ +# Copyright (c) 2020 Red Hat, Inc. +# +# All Rights Reserved. +# +# 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 tobiko.shell import files +from tobiko.shell import sh +from tobiko.openstack import topology + + +class LogFile(object): + + def __init__(self, hostname, filename): + self.filename = filename + self.host = topology.get_openstack_node(hostname=hostname) + self._list_logfiles() + self.cmd = '' + self.found = [] + + def find(self, regex): + self._list_logfiles() + self.cmd = f"zgrep -Eh {regex}" + self.found = sh.execute(f'{self.cmd} {" ".join(self.logfiles)}', + ssh_client=self.host.ssh_client, + expect_exit_status=None, + sudo=True).stdout.split('\n') + try: + self.found.remove('') + except ValueError: + pass + return self.found + + def find_new(self): + self._list_logfiles() + if not self.cmd: + err_msg = 'find_new() method can be only executed after find()' + raise files.LogParserError(message=err_msg) + tmp = sh.execute(f'{self.cmd} {" ".join(self.logfiles)}', + ssh_client=self.host.ssh_client, + expect_exit_status=None, + sudo=True).stdout.split('\n') + found = [] + for log_string in tmp: + if log_string not in self.found and log_string != '': + found.append(log_string) + self.found.append(log_string) + return found + + def _list_logfiles(self): + file_path, file_name = os.path.split(self.filename) + result = sh.execute(f'find {file_path} -name {file_name}*', + ssh_client=self.host.ssh_client, + expect_exit_status=None, + sudo=True) + self.logfiles = set(result.stdout.split('\n')) + if '' in self.logfiles: + self.logfiles.remove('') + if self.logfiles == []: + raise files.LogFileNotFound(filename=str(self.filename), + host=str(self.host.name)) + + +class ClusterLogFile(object): + + def __init__(self, filename): + self.filename = filename + self.hostnames = [] + self.logfiles = [] + + def add_host(self, hostname): + if hostname in self.hostnames: + return + self.hostnames.append(hostname) + self.logfiles.append(LogFile(hostname, self.filename)) + + def add_group(self, group): + for host in topology.list_openstack_nodes(group=group): + self.add_host(host.name) + + def find(self, regex): + for logfile in self.logfiles: + logfile.find(regex) + return self.found + + def find_new(self): + new_lines = [] + for logfile in self.logfiles: + new_lines += logfile.find_new() + return new_lines + + @property + def found(self): + found = [] + for logfile in self.logfiles: + found += logfile.found + return found diff --git a/tobiko/shell/files/config.py b/tobiko/shell/files/config.py new file mode 100644 index 000000000..c7c83529a --- /dev/null +++ b/tobiko/shell/files/config.py @@ -0,0 +1,16 @@ +# Copyright (c) 2020 Red Hat, Inc. +# +# All Rights Reserved. +# +# 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 diff --git a/tobiko/tests/scenario/neutron/test_port.py b/tobiko/tests/scenario/neutron/test_port.py index b931be9de..58ebb20e4 100644 --- a/tobiko/tests/scenario/neutron/test_port.py +++ b/tobiko/tests/scenario/neutron/test_port.py @@ -18,9 +18,11 @@ import netaddr import testtools import tobiko +from tobiko.shell import files from tobiko.shell import ping from tobiko.shell import ip from tobiko.openstack import neutron +from tobiko.openstack import nova from tobiko.openstack import stacks @@ -98,3 +100,32 @@ class CentosServerL3HAPortTestWith(PortTest): class UbuntuServerL3HAPortTestWith(PortTest): #: Resources stack with floating IP and Nova server stack = tobiko.required_setup_fixture(stacks.L3haUbuntuServerStackFixture) + + +class PortLogsStack(stacks.CirrosServerStackFixture): + pass + + +@neutron.skip_unless_is_ovs() +class PortLogs(testtools.TestCase): + + stack = tobiko.required_setup_fixture(PortLogsStack) + + def test_nova_port_notification(self): + expected_logfile = '/var/log/containers/neutron/server.log' + logfile = files.ClusterLogFile(expected_logfile) + try: + logfile.add_group('controller') + except files.LogFileNotFound as ex: + tobiko.skip(str(ex)) + logfile.find(f'Nova.+event.+response.*{self.stack.server_id}') + nova.shutoff_server(self.stack.server_id) + nova.activate_server(self.stack.server_id) + new_events = logfile.find_new() + self.assertEqual(len(new_events), 2) + self.assertTrue( + any('network-vif-unplugged' in event for event in new_events)) + self.assertTrue( + any('network-vif-plugged' in event for event in new_events)) + self.assertTrue( + all(self.stack.port_id in event for event in new_events))