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
This commit is contained in:
Alex Katz 2020-10-15 13:39:29 +03:00
parent 0cafacd668
commit 705164604a
7 changed files with 221 additions and 0 deletions

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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}'

110
tobiko/shell/files/_logs.py Normal file
View File

@ -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

View File

@ -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

View File

@ -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))