"Fossies" - the Fresh Open Source Software Archive

Member "neutron-14.0.3/neutron/tests/fullstack/test_trunk.py" (22 Oct 2019, 12057 Bytes) of package /linux/misc/openstack/neutron-14.0.3.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 # Copyright 2016 Red Hat, Inc.
    2 #
    3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    4 #    not use this file except in compliance with the License. You may obtain
    5 #    a copy of the License at
    6 #
    7 #         http://www.apache.org/licenses/LICENSE-2.0
    8 #
    9 #    Unless required by applicable law or agreed to in writing, software
   10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   12 #    License for the specific language governing permissions and limitations
   13 #    under the License.
   14 
   15 import functools
   16 
   17 import netaddr
   18 from neutron_lib import constants
   19 from oslo_utils import uuidutils
   20 
   21 from neutron.common import utils
   22 from neutron.services.trunk.drivers.openvswitch.agent import ovsdb_handler
   23 from neutron.services.trunk.drivers.openvswitch.agent import trunk_manager
   24 from neutron.services.trunk.drivers.openvswitch import utils as trunk_ovs_utils
   25 from neutron.tests.fullstack import base
   26 from neutron.tests.fullstack.resources import environment
   27 from neutron.tests.fullstack.resources import machine
   28 
   29 
   30 def trunk_bridge_does_not_exist(trunk_id):
   31     """Return true if trunk bridge for given ID does not exists."""
   32     bridge = trunk_manager.TrunkBridge(trunk_id)
   33     return not bridge.exists()
   34 
   35 
   36 def make_ip_network(port, network):
   37     """Make an IPNetwork object from port and network.
   38 
   39     Function returns IPNetwork object containing fixed IP address from port
   40     dictionary with prefixlen from network object.
   41 
   42     :param port: Port dictionary returned by Neutron API
   43     :param network: IPNetwork object in which the port's IP will be assigned.
   44     """
   45     ip_address = netaddr.IPAddress(
   46         port['fixed_ips'][0]['ip_address'])
   47     return netaddr.IPNetwork(
   48         (ip_address.value, network.prefixlen))
   49 
   50 
   51 class TrunkTestException(Exception):
   52     pass
   53 
   54 
   55 class Network(object):
   56     """A helper class to keep persistent info about assigned addresses."""
   57     def __init__(self, prefix, network_cidr, tag=None):
   58         self.prefix = prefix
   59         self.network = netaddr.IPNetwork(network_cidr)
   60         self.neutron_network = None
   61         self.neutron_subnet = None
   62         self.tag = tag
   63         # Currently, only vlan is supported. Pass via __init__ once more are
   64         # supported.
   65         self.segmentation_type = 'vlan'
   66 
   67     @property
   68     def cidr(self):
   69         return str(self.network.cidr)
   70 
   71     @property
   72     def gateway(self):
   73         """Return lowest possible IP in the given subnet."""
   74         return str(netaddr.IPAddress(self.network.first + 1))
   75 
   76     @property
   77     def id(self):
   78         return self.neutron_network['id']
   79 
   80     @property
   81     def name(self):
   82         return "%s-network" % self.prefix
   83 
   84     @property
   85     def subnet_name(self):
   86         return "%s-subnet" % self.prefix
   87 
   88 
   89 class TestTrunkPlugin(base.BaseFullStackTestCase):
   90     def setUp(self):
   91         host_desc = [environment.HostDescription(
   92             l3_agent=False,
   93             l2_agent_type=constants.AGENT_TYPE_OVS)]
   94         env_desc = environment.EnvironmentDescription(service_plugins='trunk')
   95         env = environment.Environment(env_desc, host_desc)
   96         super(TestTrunkPlugin, self).setUp(env)
   97 
   98         self.tenant_id = uuidutils.generate_uuid()
   99         self.trunk_network = Network('trunk', '10.0.0.0/24')
  100         self.vlan1_network = Network('vlan1', '192.168.0.0/24', tag=10)
  101         self.vlan2_network = Network('vlan2', '192.168.1.0/24', tag=20)
  102 
  103         self.host = self.environment.hosts[0]
  104 
  105         for network in (
  106                 self.trunk_network, self.vlan1_network, self.vlan2_network):
  107             self.create_network_and_subnet(network)
  108 
  109     def create_network_and_subnet(self, network):
  110         """Create network and subnet resources in Neutron based on network
  111            object.
  112 
  113         The resource names will be <prefix>-network and <prefix>-subnet, where
  114         prefix is taken from network object.
  115 
  116         :param network: Network object from this module.
  117         """
  118         network.neutron_network = self.safe_client.create_network(
  119             self.tenant_id, network.name)
  120         network.neutron_subnet = self.safe_client.create_subnet(
  121             self.tenant_id,
  122             network.id,
  123             cidr=network.cidr,
  124             gateway_ip=network.gateway,
  125             name=network.subnet_name,
  126             enable_dhcp=False)
  127 
  128     def create_vlan_aware_vm(self, trunk_network, vlan_networks):
  129         """Create a fake machine with one untagged port and subports
  130         according vlan_networks parameter.
  131 
  132         :param trunk_network: Instance of Network where trunk port should be
  133                               created.
  134         :param vlan_networks: List of Network instances where subports should
  135                               be created.
  136         """
  137         trunk_parent_port = self.safe_client.create_port(
  138             self.tenant_id, trunk_network.id)
  139 
  140         vlan_subports = [
  141             self.safe_client.create_port(self.tenant_id, vlan_network.id,
  142                 mac_address=trunk_parent_port['mac_address'])
  143             for vlan_network in vlan_networks]
  144 
  145         trunk = self.safe_client.create_trunk(
  146             self.tenant_id,
  147             name='mytrunk',
  148             port_id=trunk_parent_port['id'],
  149             sub_ports=[
  150                 {'port_id': vlan_subport['id'],
  151                  'segmentation_type': 'vlan',
  152                  'segmentation_id': vlan_network.tag}
  153                 for vlan_subport, vlan_network in zip(vlan_subports,
  154                                                       vlan_networks)
  155             ],
  156         )
  157 
  158         vm = self.useFixture(
  159             machine.FakeFullstackTrunkMachine(
  160                 trunk,
  161                 self.host,
  162                 trunk_network.id,
  163                 self.tenant_id,
  164                 self.safe_client,
  165                 neutron_port=trunk_parent_port,
  166                 bridge_name=trunk_ovs_utils.gen_trunk_br_name(trunk['id'])))
  167 
  168         for port, vlan_network in zip(vlan_subports, vlan_networks):
  169             ip_network = make_ip_network(port, vlan_network.network)
  170             vm.add_vlan_interface(
  171                 port['mac_address'], ip_network, vlan_network.tag)
  172         vm.block_until_boot()
  173 
  174         return vm
  175 
  176     def create_vm_in_network(self, network):
  177         """Create a fake machine in given network."""
  178         return self.useFixture(
  179             machine.FakeFullstackMachine(
  180                 self.host,
  181                 network.id,
  182                 self.tenant_id,
  183                 self.safe_client
  184             )
  185         )
  186 
  187     def add_subport_to_vm(self, vm, subport_network):
  188         """Add subport from subport_network to given vm.
  189 
  190         :param vm: FakeFullstackMachine instance to with subport should be
  191                    added.
  192         :param subport_network: Network object representing network containing
  193                                 port for subport.
  194         """
  195         subport = self.safe_client.create_port(
  196             self.tenant_id, subport_network.id,
  197             mac_address=vm.neutron_port['mac_address'])
  198         subport_spec = {
  199             'port_id': subport['id'],
  200             'segmentation_type': subport_network.segmentation_type,
  201             'segmentation_id': subport_network.tag
  202         }
  203 
  204         self.safe_client.trunk_add_subports(
  205             self.tenant_id, vm.trunk['id'], [subport_spec])
  206         ip_network = make_ip_network(subport, subport_network.network)
  207         vm.add_vlan_interface(
  208             subport['mac_address'], ip_network, subport_network.tag)
  209 
  210     # NOTE(slaweq): As is described in bug
  211     # https://bugs.launchpad.net/neutron/+bug/1687709 when more than one
  212     # different ovs-agent with enabled trunk driver is running at a time it
  213     # might lead to race contitions between them.
  214     # Because of that ovs_agent used for fullstack tests is monkeypatched and
  215     # loads trunk driver only if trunk service plugin is enabled.
  216     # That makes restriction that only a single set of tests with trunk-enabled
  217     # services will run at the same time.
  218     def test_trunk_lifecycle(self):
  219         """Test life-cycle of a fake VM with trunk port.
  220 
  221         This test uses 4 fake machines:
  222           - vlan_aware_vm (A) that is at the beginning connected to a trunk
  223             network and a vlan1 network.
  224           - trunk_network_vm (B) that is connected to the trunk network.
  225           - vlan1_network_vm (C) that is connected to the vlan1 network.
  226           - vlan2_network_vm (D) that is connected to a vlan2 network.
  227 
  228         Scenario steps:
  229           - all the vms from above are created
  230           - A can talk with B (over the trunk network)
  231           - A can talk with C (over the vlan1 network)
  232           - A can not talk with D (no leg on the vlan2 network)
  233 
  234           - subport from the vlan2 network is added to A
  235           - A can now talk with D (over the vlan2 network)
  236 
  237           - subport from the vlan1 network is removed from A
  238           - A can talk with B (over the trunk network)
  239           - A can not talk with C (no leg on the vlan1 network)
  240           - A can talk with D (over the vlan2 network)
  241 
  242           - A is deleted which leads to removal of trunk bridge
  243           - no leftovers like patch ports to the trunk bridge should remain on
  244             an integration bridge
  245         """
  246 
  247         vlan_aware_vm = self.create_vlan_aware_vm(
  248             self.trunk_network,
  249             [self.vlan1_network]
  250         )
  251         trunk_id = vlan_aware_vm.trunk['id']
  252 
  253         # Create helper vms with different networks
  254         trunk_network_vm = self.create_vm_in_network(self.trunk_network)
  255         vlan1_network_vm = self.create_vm_in_network(self.vlan1_network)
  256         vlan2_network_vm = self.create_vm_in_network(self.vlan2_network)
  257 
  258         for vm in trunk_network_vm, vlan1_network_vm, vlan2_network_vm:
  259             vm.block_until_boot()
  260 
  261         # Test connectivity to trunk and subport
  262         vlan_aware_vm.block_until_ping(trunk_network_vm.ip)
  263         vlan_aware_vm.block_until_ping(vlan1_network_vm.ip)
  264 
  265         # Subport for vlan2 hasn't been added yet
  266         vlan_aware_vm.block_until_no_ping(vlan2_network_vm.ip)
  267 
  268         # Add another subport and test
  269         self.add_subport_to_vm(vlan_aware_vm, self.vlan2_network)
  270         vlan_aware_vm.block_until_ping(vlan2_network_vm.ip)
  271 
  272         # Remove the first subport
  273         self.safe_client.trunk_remove_subports(
  274             self.tenant_id,
  275             trunk_id,
  276             [vlan_aware_vm.trunk['sub_ports'][0]])
  277 
  278         # vlan1_network_vm now shouldn't be able to talk to vlan_aware_vm
  279         vlan_aware_vm.block_until_no_ping(vlan1_network_vm.ip)
  280 
  281         # but trunk and vlan2 should be able to ping
  282         vlan_aware_vm.block_until_ping(trunk_network_vm.ip)
  283         vlan_aware_vm.block_until_ping(vlan2_network_vm.ip)
  284 
  285         # Delete vm and check that patch ports and trunk bridge are gone
  286         vlan_aware_vm.destroy()
  287         bridge_doesnt_exist_predicate = functools.partial(
  288             trunk_bridge_does_not_exist, trunk_id)
  289         utils.wait_until_true(
  290             bridge_doesnt_exist_predicate,
  291             exception=TrunkTestException(
  292                 'Trunk bridge with ID %s has not been removed' %
  293                 trunk_id)
  294         )
  295 
  296         integration_bridge = self.host.get_bridge(None)
  297         no_patch_ports_predicate = functools.partial(
  298             lambda bridge: not ovsdb_handler.bridge_has_service_port(bridge),
  299             integration_bridge,
  300         )
  301         try:
  302             utils.wait_until_true(no_patch_ports_predicate)
  303         except utils.WaitTimeout:
  304             # Create exception object after timeout to provide up-to-date list
  305             # of interfaces
  306             raise TrunkTestException(
  307                 "Integration bridge %s still has following ports while some of"
  308                 " them are patch ports for trunk that were supposed to be "
  309                 "removed: %s" % (
  310                     integration_bridge.br_name,
  311                     integration_bridge.get_iface_name_list()
  312                 )
  313             )