From d4d2fbc30df45eadea724de8698023ec5ffbb6ba Mon Sep 17 00:00:00 2001 From: Lajos Katona Date: Mon, 21 Jan 2019 10:44:18 +0100 Subject: [PATCH] Fullstack test for placement sync Fullstack test for placement reporter service plugin. To test it in fullstack environment a new stupid placement process is started which answers to placement REST API requests. Change-Id: I9dd9f6c9e8c8fcf1be1e91d49a59a00c2eb24026 Depends-On: https://review.openstack.org/638666 Partial-Bug: #1578989 See-Also: https://review.openstack.org/502306 (nova spec) See-Also: https://review.openstack.org/508149 (neutron spec) --- neutron/tests/fullstack/resources/config.py | 30 ++- .../tests/fullstack/resources/environment.py | 16 +- neutron/tests/fullstack/resources/process.py | 22 ++ neutron/tests/fullstack/servers/__init__.py | 0 neutron/tests/fullstack/servers/placement.py | 146 ++++++++++++ .../fullstack/test_agent_bandwidth_report.py | 215 ++++++++++++------ 6 files changed, 354 insertions(+), 75 deletions(-) create mode 100644 neutron/tests/fullstack/servers/__init__.py create mode 100755 neutron/tests/fullstack/servers/placement.py diff --git a/neutron/tests/fullstack/resources/config.py b/neutron/tests/fullstack/resources/config.py index be873e8ae25..11f6419468a 100644 --- a/neutron/tests/fullstack/resources/config.py +++ b/neutron/tests/fullstack/resources/config.py @@ -92,7 +92,7 @@ class NeutronConfigFixture(ConfigFixture): 'lock_path': '$state_path/lock', }, 'agent': { - 'report_interval': str(env_desc.agent_down_time / 2.0) + 'report_interval': str(env_desc.agent_down_time // 2) }, }) policy_file = self._generate_policy_json() @@ -109,6 +109,18 @@ class NeutronConfigFixture(ConfigFixture): if env_desc.router_scheduler: self.config['DEFAULT']['router_scheduler_driver'] = ( env_desc.router_scheduler) + if env_desc.has_placement: + service_plugins = self.config['DEFAULT']['service_plugins'] + self.config['DEFAULT']['service_plugins'] = ( + '%s,%s' % (service_plugins, 'placement') + ) + self.config.update({ + 'placement': { + 'auth_type': 'noauth', + 'auth_section': 'http://127.0.0.1:%s/placement' % + env_desc.placement_port + } + }) net_helpers.set_local_port_range(CLIENT_CONN_PORT_START, CLIENT_CONN_PORT_END) @@ -289,6 +301,22 @@ class SRIOVConfigFixture(ConfigFixture): super(SRIOVConfigFixture, self)._setUp() +class PlacementConfigFixture(ConfigFixture): + + def __init__(self, env_desc, host_desc, temp_dir): + super(PlacementConfigFixture, self).__init__( + env_desc, host_desc, temp_dir, base_filename='placement.ini') + self.config.update({ + 'DEFAULT': { + 'debug': 'True', + 'placement_port': self.env_desc.placement_port + } + }) + + def _setUp(self): + super(PlacementConfigFixture, self)._setUp() + + class LinuxBridgeConfigFixture(ConfigFixture): def __init__(self, env_desc, host_desc, temp_dir, local_ip, diff --git a/neutron/tests/fullstack/resources/environment.py b/neutron/tests/fullstack/resources/environment.py index 0a9b2dfc36f..85edbfadaf6 100644 --- a/neutron/tests/fullstack/resources/environment.py +++ b/neutron/tests/fullstack/resources/environment.py @@ -38,7 +38,8 @@ class EnvironmentDescription(object): service_plugins='router', arp_responder=False, agent_down_time=75, router_scheduler=None, global_mtu=constants.DEFAULT_NETWORK_MTU, - debug_iptables=False, log=False, report_bandwidths=False): + debug_iptables=False, log=False, report_bandwidths=False, + has_placement=False, placement_port=None): self.network_type = network_type self.l2_pop = l2_pop self.qos = qos @@ -52,6 +53,8 @@ class EnvironmentDescription(object): self.service_plugins = service_plugins self.debug_iptables = debug_iptables self.report_bandwidths = report_bandwidths + self.has_placement = has_placement + self.placement_port = placement_port if self.qos: self.service_plugins += ',qos' if self.log: @@ -407,6 +410,17 @@ class Environment(fixtures.Fixture): self.env_desc, None, self.test_name, neutron_cfg_fixture, plugin_cfg_fixture)) + if self.env_desc.has_placement: + placement_cfg_fixture = self.useFixture( + config.PlacementConfigFixture(self.env_desc, self.hosts_desc, + self.temp_dir) + ) + self.placement = self.useFixture( + process.PlacementFixture( + self.env_desc, self.hosts_desc, self.test_name, + placement_cfg_fixture) + ) + self.hosts = [self._create_host(desc) for desc in self.hosts_desc] self.wait_until_env_is_up() diff --git a/neutron/tests/fullstack/resources/process.py b/neutron/tests/fullstack/resources/process.py index 1d6bc3e7d9b..4f575413a05 100644 --- a/neutron/tests/fullstack/resources/process.py +++ b/neutron/tests/fullstack/resources/process.py @@ -213,6 +213,28 @@ class OVSAgentFixture(ServiceFixture): kill_signal=signal.SIGTERM)) +class PlacementFixture(fixtures.Fixture): + + def __init__(self, env_desc, host_desc, test_name, placement_cfg_fixture): + super(PlacementFixture, self).__init__() + self.env_desc = env_desc + self.host_desc = host_desc + self.test_name = test_name + self.placement_cfg_fixture = placement_cfg_fixture + self.placement_config = self.placement_cfg_fixture.config + + def _setUp(self): + self.process_fixture = self.useFixture(ProcessFixture( + test_name=self.test_name, + process_name='placement', + exec_name=spawn.find_executable( + 'placement.py', path=os.path.join(fullstack_base.ROOTDIR, + 'servers') + ), + config_filenames=[self.placement_cfg_fixture.filename], + kill_signal=signal.SIGTERM)) + + class SRIOVAgentFixture(ServiceFixture): NEUTRON_SRIOV_AGENT = "neutron-sriov-nic-agent" diff --git a/neutron/tests/fullstack/servers/__init__.py b/neutron/tests/fullstack/servers/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/neutron/tests/fullstack/servers/placement.py b/neutron/tests/fullstack/servers/placement.py new file mode 100755 index 00000000000..3e90da30482 --- /dev/null +++ b/neutron/tests/fullstack/servers/placement.py @@ -0,0 +1,146 @@ +#!/usr/bin/env python +# Copyright (c) 2019 Ericsson +# +# 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. + +import copy +import sys +import uuid +from wsgiref import simple_server as wsgi_simple_server + +from oslo_config import cfg +from oslo_config import types +from oslo_log import log as logging +from oslo_serialization import jsonutils +from six.moves import urllib + +from neutron.common import config as common_config + +LOG = logging.getLogger(__name__) + + +PortType = types.Integer(1, 65535) +placement_opts = [ + cfg.Opt('placement_port', type=PortType) +] +cfg.CONF.register_opts(placement_opts) + + +class FakePlacement(object): + + rp_template = { + "uuid": None, + "generation": 0, + "parent_provider_uuid": None, + "name": None, + } + + def __init__(self): + self.host_rp_uuid = str(uuid.uuid4()) + host_rp = copy.deepcopy(self.rp_template) + host_rp['uuid'] = self.host_rp_uuid + self.resource_providers = { + self.host_rp_uuid: host_rp + } + + def get_resource_providers(self, **kwargs): + id = kwargs.get('id', None) + if not id: + return jsonutils.dumps( + { + 'resource_providers': + [self.resource_providers[self.host_rp_uuid]] + }) + else: + return jsonutils.dumps(self.resource_providers[id]) + + def put_traits(self, **kwargs): + # Return empty sting otherwise wsgiref goes mad + return '' + + def put_resource_providers(self, **kwargs): + id = kwargs.get('id', None) + req_body = kwargs.get('body', None) + if id: + rp_dict = copy.deepcopy(self.rp_template) + rp_dict['uuid'] = id + rp_dict['parent_provider_uuid'] = req_body['parent_provider_uuid'] + rp_dict['name'] = req_body['name'] + self.resource_providers[rp_dict['uuid']] = rp_dict + return jsonutils.dumps(rp_dict) + + def put_resource_providers_traits(self, **kwargs): + resp = kwargs['body'] + resp['resource_provider_generation'] += 1 + return jsonutils.dumps(resp) + + def put_resource_providers_inventories(self, **kwargs): + resp = kwargs['body'] + resp['resource_provider_generation'] += 1 + return jsonutils.dumps(resp) + + def build_method_name(self, action, path_info): + path_info = urllib.parse.urlparse(path_info).path + path_info_list = path_info.strip('/').split('/') + method_name = action.lower() + for path_chunk in path_info_list: + if any(s in path_chunk for s in ['placement', 'CUSTOM']): + continue + # If there is uuid in the path, that should be thrown out + try: + uuid.UUID(path_chunk) + except ValueError: + method_name += '_' + path_chunk + return method_name + + def wsgi_app(self, env, start_response): + response_headers = [('Content-Type', 'application/json')] + http_status = '200 OK' + + # Poor men's routing + meth_name = self.build_method_name(env['REQUEST_METHOD'], + env['PATH_INFO']) + params = {} + # Fetch params from url + try: + params['id'] = env['PATH_INFO'].split('/')[3] + except IndexError: + pass + # Fetch body + try: + request_body_size = int(env.get('CONTENT_LENGTH', 0)) + if request_body_size > 0: + req_body = env['wsgi.input'].read(request_body_size) + params['body'] = jsonutils.loads(req_body.decode('utf-8')) + except ValueError: + pass + LOG.debug('Request on %s (%s) with body: %s', + env['PATH_INFO'], env['REQUEST_METHOD'], params) + response = getattr(self, meth_name)(**params) + LOG.debug('Response from %s: %s', meth_name, response) + + response = response.encode('utf-8') + start_response(http_status, response_headers) + return [response] + + +if __name__ == "__main__": + common_config.init(sys.argv[1:]) + common_config.setup_logging() + placement_port = cfg.CONF.placement_port + + LOG.info("Placement fixture started on port: %s", placement_port) + + mock_placement = FakePlacement() + wsgi_simple_server.make_server( + '', placement_port, mock_placement.wsgi_app).serve_forever() diff --git a/neutron/tests/fullstack/test_agent_bandwidth_report.py b/neutron/tests/fullstack/test_agent_bandwidth_report.py index 7b659ab4794..631334214d8 100644 --- a/neutron/tests/fullstack/test_agent_bandwidth_report.py +++ b/neutron/tests/fullstack/test_agent_bandwidth_report.py @@ -25,6 +25,64 @@ from neutron.tests.unit import testlib_api load_tests = testlib_api.module_load_tests +BR_MAPPINGS = 'bridge_mappings' +DEV_MAPPINGS = 'device_mappings' + + +def _get_physnet_names_from_mapping(mapping): + physnets = [] + for pair in mapping.split(','): + physnets.append(pair.split(':')[0]) + return physnets + + +def _add_new_device_to_agent_config(l2_agent_config, mapping_key_name, + new_dev): + old_bw = l2_agent_config[constants.RP_BANDWIDTHS] + old_mappings = l2_agent_config[mapping_key_name] + if new_dev in old_bw or new_dev in old_mappings: + return + + new_mappings = 'physnetnew:%s' % new_dev + new_bw = '%s:%s:%s' % (new_dev, + f_const.MINIMUM_BANDWIDTH_EGRESS_KBPS, + f_const.MINIMUM_BANDWIDTH_INGRESS_KBPS) + l2_agent_config[mapping_key_name] = '%s,%s' % ( + old_mappings, new_mappings) + l2_agent_config[constants.RP_BANDWIDTHS] = '%s,%s' % ( + old_bw, new_bw) + + +def _change_agent_conf(l2_agent_config, l2_agent, + mapping_key_name, new_dev): + _add_new_device_to_agent_config(l2_agent_config, mapping_key_name, new_dev) + l2_agent.agent_cfg_fixture.write_config_to_configfile() + + +def _add_new_bridge_and_restart_agent(host): + l2_agent = host.l2_agent + l2_agent_config = l2_agent.agent_cfg_fixture.config + + if 'ovs' in host.agents: + new_dev = utils.get_rand_device_name(prefix='br-new') + _change_agent_conf( + l2_agent_config['ovs'], l2_agent, BR_MAPPINGS, new_dev) + physnets = _get_physnet_names_from_mapping( + l2_agent_config['ovs'][BR_MAPPINGS]) + br_phys_new = host.useFixture( + net_helpers.OVSBridgeFixture(new_dev)).bridge + host.connect_to_central_network_via_vlans(br_phys_new) + elif 'sriov' in host.agents: + new_dev = utils.get_rand_device_name(prefix='ens7') + _change_agent_conf( + l2_agent_config['sriov_nic'], l2_agent, + 'physical_device_mappings', new_dev) + physnets = _get_physnet_names_from_mapping( + l2_agent_config['sriov_nic']['physical_device_mappings']) + + l2_agent.restart() + return physnets + class TestAgentBandwidthReport(base.BaseFullStackTestCase): @@ -35,19 +93,17 @@ class TestAgentBandwidthReport(base.BaseFullStackTestCase): {'l2_agent_type': constants.AGENT_TYPE_NIC_SWITCH}) ] - BR_MAPPINGS = 'bridge_mappings' - DEV_MAPPINGS = 'device_mappings' - - def setUp(self): - host_desc = [environment.HostDescription( - l3_agent=False, - l2_agent_type=self.l2_agent_type)] - env_desc = environment.EnvironmentDescription( - network_type='vlan', - l2_pop=False, - report_bandwidths=True, - ) - env = environment.Environment(env_desc, host_desc) + def setUp(self, env=None): + if not env: + host_desc = [environment.HostDescription( + l3_agent=False, + l2_agent_type=self.l2_agent_type)] + env_desc = environment.EnvironmentDescription( + network_type='vlan', + l2_pop=False, + report_bandwidths=True, + ) + env = environment.Environment(env_desc, host_desc) super(TestAgentBandwidthReport, self).setUp(env) @@ -55,9 +111,9 @@ class TestAgentBandwidthReport(base.BaseFullStackTestCase): agent = self.client.show_agent(agent_id)['agent'] agent_configurations = agent['configurations'] if 'Open vSwitch' in agent['agent_type']: - mapping_key = self.BR_MAPPINGS + mapping_key = BR_MAPPINGS elif 'NIC Switch' in agent['agent_type']: - mapping_key = self.DEV_MAPPINGS + mapping_key = DEV_MAPPINGS else: return False @@ -71,7 +127,7 @@ class TestAgentBandwidthReport(base.BaseFullStackTestCase): agent_configurations): return False - if mapping_key == self.BR_MAPPINGS: + if mapping_key == BR_MAPPINGS: if (bridge_or_devices not in agent_configurations[constants.RP_BANDWIDTHS]): return False @@ -90,58 +146,6 @@ class TestAgentBandwidthReport(base.BaseFullStackTestCase): return False return True - def _get_physnet_names_from_mapping(self, mapping): - physnets = [] - for pair in mapping.split(','): - physnets.append(pair.split(':')[0]) - return physnets - - def _add_new_device_to_agent_config(self, l2_agent_config, - mapping_key_name, new_dev): - old_bw = l2_agent_config[constants.RP_BANDWIDTHS] - old_mappings = l2_agent_config[mapping_key_name] - if new_dev in old_bw or new_dev in old_mappings: - return - - new_mappings = 'physnetnew:%s' % new_dev - new_bw = '%s:%s:%s' % (new_dev, - f_const.MINIMUM_BANDWIDTH_EGRESS_KBPS, - f_const.MINIMUM_BANDWIDTH_INGRESS_KBPS) - l2_agent_config[mapping_key_name] = '%s,%s' % ( - old_mappings, new_mappings) - l2_agent_config[constants.RP_BANDWIDTHS] = '%s,%s' % ( - old_bw, new_bw) - - def _change_agent_conf_and_restart_agent(self, l2_agent_config, l2_agent, - mapping_key_name, new_dev): - self._add_new_device_to_agent_config( - l2_agent_config, mapping_key_name, new_dev) - l2_agent.agent_cfg_fixture.write_config_to_configfile() - - def _add_new_bridge_and_restart_agent(self, host): - l2_agent = host.l2_agent - l2_agent_config = l2_agent.agent_cfg_fixture.config - - if 'ovs' in host.agents: - new_dev = utils.get_rand_device_name(prefix='br-new') - self._change_agent_conf_and_restart_agent( - l2_agent_config['ovs'], l2_agent, self.BR_MAPPINGS, new_dev) - physnets = self._get_physnet_names_from_mapping( - l2_agent_config['ovs'][self.BR_MAPPINGS]) - br_phys_new = host.useFixture( - net_helpers.OVSBridgeFixture(new_dev)).bridge - host.connect_to_central_network_via_vlans(br_phys_new) - elif 'sriov' in host.agents: - new_dev = utils.get_rand_device_name(prefix='ens7') - self._change_agent_conf_and_restart_agent( - l2_agent_config['sriov_nic'], l2_agent, - 'physical_device_mappings', new_dev) - physnets = self._get_physnet_names_from_mapping( - l2_agent_config['sriov_nic']['physical_device_mappings']) - - l2_agent.restart() - return physnets - def test_agent_configurations(self): agents = self.client.list_agents() @@ -150,10 +154,10 @@ class TestAgentBandwidthReport(base.BaseFullStackTestCase): agent_config = self.environment.hosts[0].l2_agent.agent_config if 'ovs' in self.environment.hosts[0].agents: - physnets = self._get_physnet_names_from_mapping( - agent_config['ovs'][self.BR_MAPPINGS]) + physnets = _get_physnet_names_from_mapping( + agent_config['ovs'][BR_MAPPINGS]) elif 'sriov' in self.environment.hosts[0].agents: - physnets = self._get_physnet_names_from_mapping( + physnets = _get_physnet_names_from_mapping( agent_config['sriov_nic']['physical_device_mappings']) self.assertTrue( @@ -163,8 +167,7 @@ class TestAgentBandwidthReport(base.BaseFullStackTestCase): # Add new physnet with bandwidth value to agent config and check # if after agent restart and report_interval wait it is visible in # the configurations field. - physnets = self._add_new_bridge_and_restart_agent( - self.environment.hosts[0]) + physnets = _add_new_bridge_and_restart_agent(self.environment.hosts[0]) agents = self.client.list_agents() l2_agent = agents['agents'][0] @@ -178,3 +181,69 @@ class TestAgentBandwidthReport(base.BaseFullStackTestCase): predicate=check_agent_alive, timeout=float(report_interval) + 10, sleep=5) + + +class TestPlacementBandwidthReport(base.BaseFullStackTestCase): + + scenarios = [ + (constants.AGENT_TYPE_OVS, + {'l2_agent_type': constants.AGENT_TYPE_OVS, + 'mech_drivers': 'openvswitch,linuxbridge', + 'placement_port': '8080'}), + (constants.AGENT_TYPE_NIC_SWITCH, + {'l2_agent_type': constants.AGENT_TYPE_NIC_SWITCH, + 'mech_drivers': 'sriovnicswitch', + 'placement_port': '8081'}) + ] + + def setUp(self): + host_desc = [environment.HostDescription( + l3_agent=False, + l2_agent_type=self.l2_agent_type)] + env_desc = environment.EnvironmentDescription( + network_type='vlan', + l2_pop=False, + mech_drivers=self.mech_drivers, + report_bandwidths=True, + has_placement=True, + placement_port=self.placement_port + ) + env = environment.Environment(env_desc, host_desc) + super(TestPlacementBandwidthReport, self).setUp(env) + + def _check_agent_not_synced(self): + return not self._check_agent_synced() + + def _check_agent_synced(self): + agents = self.client.list_agents() + if (len(agents['agents']) == 1 and + agents['agents'][0]['resources_synced']): + return True + return False + + def test_configurations_are_synced_towards_placement(self): + neutron_config = self.environment.hosts[0].l2_agent.neutron_config + report_interval = int(neutron_config['agent']['report_interval']) + + check_agent_synced = functools.partial(self._check_agent_synced) + utils.wait_until_true( + predicate=check_agent_synced, + timeout=report_interval + 10, + sleep=1) + + self.environment.placement.process_fixture.stop() + _add_new_bridge_and_restart_agent(self.environment.hosts[0]) + + check_agent_not_synced = functools.partial( + self._check_agent_not_synced) + utils.wait_until_true( + predicate=check_agent_not_synced, + timeout=report_interval + 10, + sleep=1) + + self.environment.placement.process_fixture.start() + check_agent_synced = functools.partial(self._check_agent_synced) + utils.wait_until_true( + predicate=check_agent_synced, + timeout=report_interval + 10, + sleep=1)