示例#1
0
 def _handle_reconcile_logical_device(self, logical_device, reconcile):
     assert isinstance(logical_device, LogicalDevice)
     assert logical_device.id not in self.logical_device_agents
     log.info('reconcile', reconcile=reconcile)
     agent = yield LogicalDeviceAgent(
         self, logical_device).start(reconcile=reconcile)
     self.logical_device_agents[logical_device.id] = agent
示例#2
0
    def setUp(self):
        self.setup_mock_registry()

        self.flows = Flows(items=[])
        self.groups = FlowGroups(items=[])
        self.ld_ports = [
            LogicalPort(id='0',
                        device_id='olt',
                        device_port_no=0,
                        root_port=True,
                        ofp_port=ofp.ofp_port(port_no=0)),
            LogicalPort(id='1',
                        device_id='onu1',
                        device_port_no=0,
                        ofp_port=ofp.ofp_port(port_no=1)),
            LogicalPort(id='2',
                        device_id='onu2',
                        device_port_no=0,
                        ofp_port=ofp.ofp_port(port_no=2))
        ]

        self.devices = {
            'olt': Device(id='olt', root=True, parent_id='id'),
            'onu1': Device(id='onu1',
                           parent_id='olt',
                           parent_port_no=1,
                           vlan=101),
            'onu2': Device(id='onu2',
                           parent_id='olt',
                           parent_port_no=1,
                           vlan=102),
        }

        self.ports = {
            'olt': [
                Port(port_no=0, type=Port.ETHERNET_NNI, device_id='olt'),
                Port(port_no=1,
                     type=Port.PON_OLT,
                     device_id='olt',
                     peers=[
                         Port.PeerPort(device_id='onu1', port_no=1),
                         Port.PeerPort(device_id='onu2', port_no=1)
                     ])
            ],
            'onu1': [
                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu1'),
                Port(port_no=1,
                     type=Port.PON_ONU,
                     device_id='onu1',
                     peers=[
                         Port.PeerPort(device_id='olt', port_no=1),
                     ])
            ],
            'onu2': [
                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu2'),
                Port(port_no=1,
                     type=Port.PON_ONU,
                     device_id='onu2',
                     peers=[
                         Port.PeerPort(device_id='olt', port_no=1),
                     ])
            ],
        }

        self.device_flows = {'olt': Flows(), 'onu1': Flows(), 'onu2': Flows()}

        self.device_groups = {
            'olt': FlowGroups(),
            'onu1': FlowGroups(),
            'onu2': FlowGroups()
        }

        self.ld = LogicalDevice(id='id', root_device_id='olt')

        self.root_proxy = Mock()

        def get_devices(path):
            if path == '':
                return self.devices.values()
            if path.endswith('/ports'):
                return self.ports[path[:-len('/ports')]]
            elif path.find('/') == -1:
                return self.devices[path]
            else:
                raise Exception(
                    'Nothing to yield for path /devices/{}'.format(path))

        def update_devices(path, data):
            if path.endswith('/flows'):
                self.device_flows[path[:-len('/flows')]] = data
            elif path.endswith('/flow_groups'):
                self.device_groups[path[:-len('/flow_groups')]] = data
            else:
                raise NotImplementedError(
                    'not handling path /devices/{}'.format(path))

        self.root_proxy.get = lambda p: \
            get_devices(p[len('/devices/'):]) if p.startswith('/devices') \
                else None
        self.root_proxy.update = lambda p, d: \
            update_devices(p[len('/devices/'):], d) \
                if p.startswith('/devices') \
                else None
        self.ld_proxy = Mock()
        self.ld_proxy.get = lambda p: \
            self.ld_ports if p == '/ports' else (
                self.ld if p == '/' else None
            )

        self.flows_proxy = Mock()
        self.flows_proxy.get = lambda _: self.flows  # always '/' path

        def update_flows(_, flows):  # always '/' path
            self.flows = flows

        self.flows_proxy.update = update_flows

        self.groups_proxy = Mock()
        self.groups_proxy.get = lambda _: self.groups  # always '/' path

        def update_groups(_, groups):  # always '/' path
            self.groups = groups

        self.groups_proxy.update = update_groups

        self.core = Mock()
        self.core.get_proxy = lambda path: \
            self.root_proxy if path == '/' else (
                self.ld_proxy if path.endswith('id') else (
                    self.flows_proxy if path.endswith('flows') else
                    self.groups_proxy
                )
            )

        self.lda = LogicalDeviceAgent(self.core, self.ld)
示例#3
0
class test_logical_device_agent(FlowHelpers):
    def setup_mock_registry(self):
        registry = Mock()
        logical_device_agent.registry = registry

    def setUp(self):
        self.setup_mock_registry()

        self.flows = Flows(items=[])
        self.groups = FlowGroups(items=[])
        self.ld_ports = [
            LogicalPort(id='0',
                        device_id='olt',
                        device_port_no=0,
                        root_port=True,
                        ofp_port=ofp.ofp_port(port_no=0)),
            LogicalPort(id='1',
                        device_id='onu1',
                        device_port_no=0,
                        ofp_port=ofp.ofp_port(port_no=1)),
            LogicalPort(id='2',
                        device_id='onu2',
                        device_port_no=0,
                        ofp_port=ofp.ofp_port(port_no=2))
        ]

        self.devices = {
            'olt': Device(id='olt', root=True, parent_id='id'),
            'onu1': Device(id='onu1',
                           parent_id='olt',
                           parent_port_no=1,
                           vlan=101),
            'onu2': Device(id='onu2',
                           parent_id='olt',
                           parent_port_no=1,
                           vlan=102),
        }

        self.ports = {
            'olt': [
                Port(port_no=0, type=Port.ETHERNET_NNI, device_id='olt'),
                Port(port_no=1,
                     type=Port.PON_OLT,
                     device_id='olt',
                     peers=[
                         Port.PeerPort(device_id='onu1', port_no=1),
                         Port.PeerPort(device_id='onu2', port_no=1)
                     ])
            ],
            'onu1': [
                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu1'),
                Port(port_no=1,
                     type=Port.PON_ONU,
                     device_id='onu1',
                     peers=[
                         Port.PeerPort(device_id='olt', port_no=1),
                     ])
            ],
            'onu2': [
                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu2'),
                Port(port_no=1,
                     type=Port.PON_ONU,
                     device_id='onu2',
                     peers=[
                         Port.PeerPort(device_id='olt', port_no=1),
                     ])
            ],
        }

        self.device_flows = {'olt': Flows(), 'onu1': Flows(), 'onu2': Flows()}

        self.device_groups = {
            'olt': FlowGroups(),
            'onu1': FlowGroups(),
            'onu2': FlowGroups()
        }

        self.ld = LogicalDevice(id='id', root_device_id='olt')

        self.root_proxy = Mock()

        def get_devices(path):
            if path == '':
                return self.devices.values()
            if path.endswith('/ports'):
                return self.ports[path[:-len('/ports')]]
            elif path.find('/') == -1:
                return self.devices[path]
            else:
                raise Exception(
                    'Nothing to yield for path /devices/{}'.format(path))

        def update_devices(path, data):
            if path.endswith('/flows'):
                self.device_flows[path[:-len('/flows')]] = data
            elif path.endswith('/flow_groups'):
                self.device_groups[path[:-len('/flow_groups')]] = data
            else:
                raise NotImplementedError(
                    'not handling path /devices/{}'.format(path))

        self.root_proxy.get = lambda p: \
            get_devices(p[len('/devices/'):]) if p.startswith('/devices') \
                else None
        self.root_proxy.update = lambda p, d: \
            update_devices(p[len('/devices/'):], d) \
                if p.startswith('/devices') \
                else None
        self.ld_proxy = Mock()
        self.ld_proxy.get = lambda p: \
            self.ld_ports if p == '/ports' else (
                self.ld if p == '/' else None
            )

        self.flows_proxy = Mock()
        self.flows_proxy.get = lambda _: self.flows  # always '/' path

        def update_flows(_, flows):  # always '/' path
            self.flows = flows

        self.flows_proxy.update = update_flows

        self.groups_proxy = Mock()
        self.groups_proxy.get = lambda _: self.groups  # always '/' path

        def update_groups(_, groups):  # always '/' path
            self.groups = groups

        self.groups_proxy.update = update_groups

        self.core = Mock()
        self.core.get_proxy = lambda path: \
            self.root_proxy if path == '/' else (
                self.ld_proxy if path.endswith('id') else (
                    self.flows_proxy if path.endswith('flows') else
                    self.groups_proxy
                )
            )

        self.lda = LogicalDeviceAgent(self.core, self.ld)

    def test_init(self):
        pass  # really just tests the setUp method

    # ~~~~~~~~~~~~~~~~~~~ TEST FLOW TABLE MANIPULATION ~~~~~~~~~~~~~~~~~~~~~~~~

    def test_add_flow(self):
        flow_mod = mk_simple_flow_mod(match_fields=[], actions=[])
        self.lda.update_flow_table(flow_mod)

        expected_flows = Flows(
            items=[flow_stats_entry_from_flow_mod_message(flow_mod)])
        self.assertFlowsEqual(self.flows, expected_flows)

    def test_add_redundant_flows(self):
        flow_mod = mk_simple_flow_mod(match_fields=[], actions=[])
        self.lda.update_flow_table(flow_mod)
        self.lda.update_flow_table(flow_mod)
        self.lda.update_flow_table(flow_mod)
        self.lda.update_flow_table(flow_mod)

        expected_flows = Flows(
            items=[flow_stats_entry_from_flow_mod_message(flow_mod)])
        self.assertFlowsEqual(self.flows, expected_flows)

    def test_add_different_flows(self):
        flow_mod1 = mk_simple_flow_mod(match_fields=[in_port(1)], actions=[])
        flow_mod2 = mk_simple_flow_mod(match_fields=[in_port(2)], actions=[])
        self.lda.update_flow_table(flow_mod1)
        self.lda.update_flow_table(flow_mod2)

        expected_flows = Flows(items=[
            flow_stats_entry_from_flow_mod_message(flow_mod1),
            flow_stats_entry_from_flow_mod_message(flow_mod2)
        ])
        self.assertFlowsEqual(self.flows, expected_flows)

    def test_delete_all_flows(self):
        for i in range(5):
            flow_mod = mk_simple_flow_mod(match_fields=[in_port(i)],
                                          actions=[output(i + 1)])
            self.lda.update_flow_table(flow_mod)
        self.assertEqual(len(self.flows.items), 5)

        self.lda.update_flow_table(
            mk_simple_flow_mod(command=ofp.OFPFC_DELETE,
                               out_port=ofp.OFPP_ANY,
                               out_group=ofp.OFPG_ANY,
                               match_fields=[],
                               actions=[]))
        self.assertEqual(len(self.flows.items), 0)

    def test_delete_specific_flows(self):
        for i in range(5):
            flow_mod = mk_simple_flow_mod(match_fields=[in_port(i)],
                                          actions=[output(i + 1)])
            self.lda.update_flow_table(flow_mod)
        self.assertEqual(len(self.flows.items), 5)

        self.lda.update_flow_table(
            mk_simple_flow_mod(command=ofp.OFPFC_DELETE_STRICT,
                               match_fields=[in_port(2)],
                               actions=[]))
        self.assertEqual(len(self.flows.items), 4)

    # ~~~~~~~~~~~~~~~~~~~ TEST GROUP TABLE MANIPULATION ~~~~~~~~~~~~~~~~~~~~~~~

    def test_add_group(self):
        group_mod = mk_multicast_group_mod(
            group_id=2,
            buckets=[
                ofp.ofp_bucket(actions=[pop_vlan(), output(1)]),
                ofp.ofp_bucket(actions=[pop_vlan(), output(2)]),
            ])
        self.lda.update_group_table(group_mod)

        expected_groups = FlowGroups(
            items=[group_entry_from_group_mod(group_mod)])
        self.assertEqual(self.groups, expected_groups)

    def test_add_redundant_groups(self):
        group_mod = mk_multicast_group_mod(
            group_id=2,
            buckets=[
                ofp.ofp_bucket(actions=[pop_vlan(), output(1)]),
                ofp.ofp_bucket(actions=[pop_vlan(), output(2)]),
            ])
        self.lda.update_group_table(group_mod)
        self.lda.update_group_table(group_mod)
        self.lda.update_group_table(group_mod)
        self.lda.update_group_table(group_mod)
        self.lda.update_group_table(group_mod)

        expected_groups = FlowGroups(
            items=[group_entry_from_group_mod(group_mod)])
        self.assertEqual(self.groups, expected_groups)

    def test_modify_group(self):
        group_mod = mk_multicast_group_mod(
            group_id=2,
            buckets=[
                ofp.ofp_bucket(actions=[pop_vlan(), output(1)]),
                ofp.ofp_bucket(actions=[pop_vlan(), output(2)]),
            ])
        self.lda.update_group_table(group_mod)

        group_mod = mk_multicast_group_mod(
            command=ofp.OFPGC_MODIFY,
            group_id=2,
            buckets=[
                ofp.ofp_bucket(actions=[pop_vlan(), output(i)])
                for i in range(1, 4)
            ])
        self.lda.update_group_table(group_mod)

        self.assertEqual(len(self.groups.items), 1)
        self.assertEqual(len(self.groups.items[0].desc.buckets), 3)

    def test_delete_all_groups(self):
        for i in range(10):
            group_mod = mk_multicast_group_mod(
                group_id=i,
                buckets=[
                    ofp.ofp_bucket(actions=[pop_vlan(), output(i)])
                    for j in range(1, 4)
                ])
            self.lda.update_group_table(group_mod)
        self.assertEqual(len(self.groups.items), 10)

        # now delete all
        self.lda.update_group_table(
            mk_multicast_group_mod(command=ofp.OFPGC_DELETE,
                                   group_id=ofp.OFPG_ALL,
                                   buckets=[]))
        self.assertEqual(len(self.groups.items), 0)

    def test_delete_specific_group(self):
        for i in range(10):
            group_mod = mk_multicast_group_mod(
                group_id=i,
                buckets=[
                    ofp.ofp_bucket(actions=[pop_vlan(), output(i)])
                    for j in range(1, 4)
                ])
            self.lda.update_group_table(group_mod)
        self.assertEqual(len(self.groups.items), 10)

        # now delete all
        self.lda.update_group_table(
            mk_multicast_group_mod(command=ofp.OFPGC_DELETE,
                                   group_id=3,
                                   buckets=[]))
        self.assertEqual(len(self.groups.items), 9)

    # ~~~~~~~~~~~~~~~~~~~~ DEFAULT RULES AND ROUTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def test_default_rules(self):
        rules = self.lda.get_all_default_rules()
        # no default olt downstream and no default group for each of 3 devs
        self.assertEqual(len(rules['olt'][0]), 0)
        self.assertEqual(len(rules['olt'][1]), 0)
        self.assertEqual(len(rules['onu1'][0]), 3)
        self.assertEqual(len(rules['onu1'][1]), 0)
        self.assertEqual(len(rules['onu2'][0]), 3)
        self.assertEqual(len(rules['onu2'][1]), 0)

    def test_routes(self):
        self.lda.get_all_default_rules()  # this will prepare the _routes
        routes = self.lda._routes
        self.assertEqual(len(routes), 4)
        self.assertEqual(set(routes.keys()),
                         set([(0, 1), (0, 2), (1, 0), (2, 0)]))

        # verify all routes
        route = routes[(0, 1)]
        self.assertEqual(len(route), 2)
        self.assertEqual(route[0].device, self.devices['olt'])
        self.assertEqual(route[0].ingress_port, self.ports['olt'][0])
        self.assertEqual(route[0].egress_port, self.ports['olt'][1])
        self.assertEqual(route[1].device, self.devices['onu1'])
        self.assertEqual(route[1].ingress_port, self.ports['onu1'][1])
        self.assertEqual(route[1].egress_port, self.ports['onu1'][0])

        route = routes[(0, 2)]
        self.assertEqual(len(route), 2)
        self.assertEqual(route[0].device, self.devices['olt'])
        self.assertEqual(route[0].ingress_port, self.ports['olt'][0])
        self.assertEqual(route[0].egress_port, self.ports['olt'][1])
        self.assertEqual(route[1].device, self.devices['onu2'])
        self.assertEqual(route[1].ingress_port, self.ports['onu2'][1])
        self.assertEqual(route[1].egress_port, self.ports['onu2'][0])

        route = routes[(1, 0)]
        self.assertEqual(len(route), 2)
        self.assertEqual(route[0].device, self.devices['onu1'])
        self.assertEqual(route[0].ingress_port, self.ports['onu1'][0])
        self.assertEqual(route[0].egress_port, self.ports['onu1'][1])
        self.assertEqual(route[1].device, self.devices['olt'])
        self.assertEqual(route[1].ingress_port, self.ports['olt'][1])
        self.assertEqual(route[1].egress_port, self.ports['olt'][0])

        route = routes[(2, 0)]
        self.assertEqual(len(route), 2)
        self.assertEqual(route[0].device, self.devices['onu2'])
        self.assertEqual(route[0].ingress_port, self.ports['onu2'][0])
        self.assertEqual(route[0].egress_port, self.ports['onu2'][1])
        self.assertEqual(route[1].device, self.devices['olt'])
        self.assertEqual(route[1].ingress_port, self.ports['olt'][1])
        self.assertEqual(route[1].egress_port, self.ports['olt'][0])

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~ FLOW DECOMP TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def test_eapol_flow_decomp_case(self):
        self.lda.update_flow_table(
            mk_simple_flow_mod(priority=1000,
                               match_fields=[in_port(1),
                                             eth_type(0x888e)],
                               actions=[output(ofp.OFPP_CONTROLLER)]))
        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 2)
        self.assertEqual(len(self.device_flows['onu1'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowsEqual(
            self.device_flows['olt'].items[0],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 1),
                             eth_type(0x888e)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[1],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             vlan_vid(4096 + 4000),
                             vlan_pcp(0),
                             metadata(1)
                         ],
                         actions=[pop_vlan(), output(1)]))

    def test_wildcarded_igmp_rule(self):
        self.lda.update_flow_table(
            mk_simple_flow_mod(priority=1000,
                               match_fields=[eth_type(0x800),
                                             ip_proto(2)],
                               actions=[output(ofp.OFPP_CONTROLLER)]))

        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 4)
        self.assertEqual(len(self.device_flows['onu1'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowsEqual(
            self.device_flows['olt'].items[0],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 1),
                             eth_type(0x800),
                             ip_proto(2)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[1],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             vlan_vid(4096 + 4000),
                             vlan_pcp(0),
                             metadata(1)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[2],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 2),
                             eth_type(0x800),
                             ip_proto(2)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[3],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             vlan_vid(4096 + 4000),
                             vlan_pcp(0),
                             metadata(2)
                         ],
                         actions=[pop_vlan(), output(1)]))

    def test_multicast_group_with_one_subscriber(self):
        self.lda.update_group_table(
            mk_multicast_group_mod(
                group_id=2,
                buckets=[
                    ofp.ofp_bucket(actions=[pop_vlan(), output(1)]),
                ]))
        self.lda.update_flow_table(
            mk_simple_flow_mod(priority=1000,
                               match_fields=[
                                   in_port(0),
                                   eth_type(0x800),
                                   vlan_vid(4096 + 140),
                                   ipv4_dst(0xe60a0a0a)
                               ],
                               actions=[group(2)]))
        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 1)
        self.assertEqual(len(self.device_flows['onu1'].items), 4)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowsEqual(
            self.device_flows['olt'].items[0],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             eth_type(0x800),
                             vlan_vid(4096 + 140),
                             ipv4_dst(0xe60a0a0a)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['onu1'].items[3],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             eth_type(0x800),
                             ipv4_dst(0xe60a0a0a)
                         ],
                         actions=[output(0)]))

    def test_multicast_group_with_two_subscribers(self):
        self.lda.update_group_table(
            mk_multicast_group_mod(
                group_id=2,
                buckets=[
                    ofp.ofp_bucket(actions=[pop_vlan(), output(1)]),
                    ofp.ofp_bucket(actions=[pop_vlan(), output(2)]),
                ]))
        self.lda.update_flow_table(
            mk_simple_flow_mod(priority=1000,
                               match_fields=[
                                   in_port(0),
                                   eth_type(0x800),
                                   vlan_vid(4096 + 140),
                                   ipv4_dst(0xe60a0a0a)
                               ],
                               actions=[group(2)]))
        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 1)
        self.assertEqual(len(self.device_flows['onu1'].items), 4)
        self.assertEqual(len(self.device_flows['onu2'].items), 4)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowsEqual(
            self.device_flows['olt'].items[0],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             eth_type(0x800),
                             vlan_vid(4096 + 140),
                             ipv4_dst(0xe60a0a0a)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['onu1'].items[3],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             eth_type(0x800),
                             ipv4_dst(0xe60a0a0a)
                         ],
                         actions=[output(0)]))
        self.assertFlowsEqual(
            self.device_flows['onu2'].items[3],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             eth_type(0x800),
                             ipv4_dst(0xe60a0a0a)
                         ],
                         actions=[output(0)]))

    def test_multicast_group_with_no_subscribers(self):
        self.lda.update_group_table(
            mk_multicast_group_mod(
                group_id=2,
                buckets=[]  # No subscribers
            ))
        self.lda.update_flow_table(
            mk_simple_flow_mod(priority=1000,
                               match_fields=[
                                   in_port(0),
                                   eth_type(0x800),
                                   vlan_vid(4096 + 140),
                                   ipv4_dst(0xe60a0a0a)
                               ],
                               actions=[group(2)]))
        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 0)
        self.assertEqual(len(self.device_flows['onu1'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowNotInFlows(
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             eth_type(0x800),
                             vlan_vid(4096 + 140),
                             ipv4_dst(0xe60a0a0a)
                         ],
                         actions=[pop_vlan(), output(1)]),
            self.device_flows['olt'])

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ COMPLEX TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def test_complex_flow_table_decomposition(self):

        # Various controller-bound rules
        for _in_port in (1, 2):
            self.lda.update_flow_table(
                mk_simple_flow_mod(
                    priority=2000,
                    match_fields=[in_port(_in_port),
                                  eth_type(0x888e)],
                    actions=[
                        push_vlan(0x8100),
                        set_field(vlan_vid(4096 + 4000)),
                        output(ofp.OFPP_CONTROLLER)
                    ]))
            self.lda.update_flow_table(
                mk_simple_flow_mod(priority=1000,
                                   match_fields=[
                                       in_port(_in_port),
                                       eth_type(0x800),
                                       ip_proto(2)
                                   ],
                                   actions=[output(ofp.OFPP_CONTROLLER)]))
            self.lda.update_flow_table(
                mk_simple_flow_mod(priority=1000,
                                   match_fields=[
                                       in_port(_in_port),
                                       eth_type(0x800),
                                       ip_proto(17),
                                       udp_src(68),
                                       udp_dst(67)
                                   ],
                                   actions=[output(ofp.OFPP_CONTROLLER)]))

        # Multicast channels
        mcast_setup = (
            (1, 0xe4010101, ()),
            (2, 0xe4010102, (1, )),
            (3, 0xe4010103, (2, )),
            (4, 0xe4010104, (1, 2)),
        )
        for group_id, mcast_addr, ports in mcast_setup:
            self.lda.update_group_table(
                mk_multicast_group_mod(
                    group_id=group_id,
                    buckets=[
                        ofp.ofp_bucket(
                            actions=[pop_vlan(), output(port)])
                        for port in ports
                    ]))
            self.lda.update_flow_table(
                mk_simple_flow_mod(priority=1000,
                                   match_fields=[
                                       in_port(0),
                                       eth_type(0x800),
                                       vlan_vid(4096 + 140),
                                       ipv4_dst(mcast_addr)
                                   ],
                                   actions=[group(group_id)]))

        # Unicast channels for each subscriber
        for port, c_vid in ((1, 101), (2, 102)):

            # Downstream flow 1 for both
            self.lda.update_flow_table(
                mk_simple_flow_mod(priority=500,
                                   match_fields=[
                                       in_port(0),
                                       vlan_vid(4096 + 1000),
                                       metadata(c_vid)
                                   ],
                                   actions=[pop_vlan()],
                                   next_table_id=1))

            # Downstream flow 2
            self.lda.update_flow_table(
                mk_simple_flow_mod(
                    priority=500,
                    match_fields=[in_port(0),
                                  vlan_vid(4096 + c_vid)],
                    actions=[set_field(vlan_vid(4096 + 0)),
                             output(port)]))

            # upstream flow 1 for the 0-tagged case
            self.lda.update_flow_table(
                mk_simple_flow_mod(
                    priority=500,
                    match_fields=[in_port(port),
                                  vlan_vid(4096 + 0)],
                    actions=[set_field(vlan_vid(4096 + c_vid))],
                    next_table_id=1))
            # ... and for the untagged case
            self.lda.update_flow_table(
                mk_simple_flow_mod(priority=500,
                                   match_fields=[in_port(port),
                                                 vlan_vid(0)],
                                   actions=[
                                       push_vlan(0x8100),
                                       set_field(vlan_vid(4096 + c_vid))
                                   ],
                                   next_table_id=1))

            # Upstream flow 2 for s-tag
            self.lda.update_flow_table(
                mk_simple_flow_mod(
                    priority=500,
                    match_fields=[in_port(port),
                                  vlan_vid(4096 + c_vid)],
                    actions=[
                        push_vlan(0x8100),
                        set_field(vlan_vid(4096 + 1000)),
                        output(0)
                    ]))

        self.assertEqual(len(self.flows.items), 20)
        self.assertEqual(len(self.groups.items), 4)

        # trigger flow table decomposition
        self.lda._flow_table_updated(self.flows)

        # now check device level flows
        self.assertEqual(len(self.device_flows['olt'].items), 15)
        self.assertEqual(len(self.device_flows['onu1'].items), 5)
        self.assertEqual(len(self.device_flows['onu2'].items), 5)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        # Flows installed on the OLT
        self.assertFlowsEqual(
            self.device_flows['olt'].items[0],
            mk_flow_stat(priority=2000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 1),
                             eth_type(0x888e)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[1],
            mk_flow_stat(priority=2000,
                         match_fields=[
                             in_port(0),
                             vlan_vid(4096 + 4000),
                             vlan_pcp(0),
                             metadata(1)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[2],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 1),
                             eth_type(0x800),
                             ip_proto(2)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[3],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             vlan_vid(4096 + 4000),
                             vlan_pcp(0),
                             metadata(1)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[4],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 1),
                             eth_type(0x800),
                             ip_proto(17),
                             udp_src(68),
                             udp_dst(67)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[5],
            mk_flow_stat(priority=2000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 2),
                             eth_type(0x888e)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[6],
            mk_flow_stat(priority=2000,
                         match_fields=[
                             in_port(0),
                             vlan_vid(4096 + 4000),
                             vlan_pcp(0),
                             metadata(2)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[7],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 2),
                             eth_type(0x800),
                             ip_proto(2)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[8],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             vlan_vid(4096 + 4000),
                             vlan_pcp(0),
                             metadata(2)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[9],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(1),
                             vlan_vid(4096 + 2),
                             eth_type(0x800),
                             ip_proto(17),
                             udp_src(68),
                             udp_dst(67)
                         ],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 4000)),
                             output(0)
                         ]))
        self.assertFlowNotInFlows(
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             eth_type(0x800),
                             vlan_vid(4096 + 140),
                             ipv4_dst(0xe4010101)
                         ],
                         actions=[pop_vlan(), output(1)]),
            self.device_flows['olt'])
        self.assertFlowsEqual(
            self.device_flows['olt'].items[10],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             eth_type(0x800),
                             vlan_vid(4096 + 140),
                             ipv4_dst(0xe4010102)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[11],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             eth_type(0x800),
                             vlan_vid(4096 + 140),
                             ipv4_dst(0xe4010103)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[12],
            mk_flow_stat(priority=1000,
                         match_fields=[
                             in_port(0),
                             eth_type(0x800),
                             vlan_vid(4096 + 140),
                             ipv4_dst(0xe4010104)
                         ],
                         actions=[pop_vlan(), output(1)]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[13],
            mk_flow_stat(priority=500,
                         match_fields=[in_port(1),
                                       vlan_vid(4096 + 101)],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 1000)),
                             output(0)
                         ]))
        self.assertFlowsEqual(
            self.device_flows['olt'].items[14],
            mk_flow_stat(priority=500,
                         match_fields=[in_port(1),
                                       vlan_vid(4096 + 102)],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 1000)),
                             output(0)
                         ]))

        # Flows installed on the ONU1
        self.assertFlowsEqual(
            self.device_flows['onu1'].items[0],
            mk_flow_stat(priority=500,
                         match_fields=[in_port(0),
                                       vlan_vid(4096 + 0)],
                         actions=[set_field(vlan_vid(4096 + 101)),
                                  output(1)]))
        self.assertFlowsEqual(
            self.device_flows['onu1'].items[2],
            mk_flow_stat(priority=500,
                         match_fields=[in_port(1),
                                       vlan_vid(4096 + 101)],
                         actions=[set_field(vlan_vid(4096 + 0)),
                                  output(0)]))
        self.assertFlowsEqual(
            self.device_flows['onu1'].items[1],
            mk_flow_stat(priority=500,
                         match_fields=[in_port(0), vlan_vid(0)],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 101)),
                             output(1)
                         ]))

        # Flows installed on the ONU2
        self.assertFlowsEqual(
            self.device_flows['onu2'].items[0],
            mk_flow_stat(priority=500,
                         match_fields=[in_port(0),
                                       vlan_vid(4096 + 0)],
                         actions=[set_field(vlan_vid(4096 + 102)),
                                  output(1)]))
        self.assertFlowsEqual(
            self.device_flows['onu2'].items[2],
            mk_flow_stat(priority=500,
                         match_fields=[in_port(1),
                                       vlan_vid(4096 + 102)],
                         actions=[set_field(vlan_vid(4096 + 0)),
                                  output(0)]))
        self.assertFlowsEqual(
            self.device_flows['onu2'].items[1],
            mk_flow_stat(priority=500,
                         match_fields=[in_port(0), vlan_vid(0)],
                         actions=[
                             push_vlan(0x8100),
                             set_field(vlan_vid(4096 + 102)),
                             output(1)
                         ]))
示例#4
0
 def _handle_add_logical_device(self, logical_device):
     assert isinstance(logical_device, LogicalDevice)
     assert logical_device.id not in self.logical_device_agents
     agent = yield LogicalDeviceAgent(self, logical_device).start()
     self.logical_device_agents[logical_device.id] = agent
    def setUp(self):
        self.setup_mock_registry()

        self.flows = Flows(items=[])
        self.groups = FlowGroups(items=[])
        self.ld_ports = [
            LogicalPort(
                id='0',
                device_id='olt',
                device_port_no=0,
                root_port=True,
                ofp_port=ofp.ofp_port(port_no=0)
            ),
            LogicalPort(
                id='101',
                device_id='onu1',
                device_port_no=0,
                ofp_port=ofp.ofp_port(port_no=101)
            ),
            LogicalPort(
                id='201',
                device_id='onu2',
                device_port_no=0,
                ofp_port=ofp.ofp_port(port_no=201)
            )
        ]

        self.devices = {
            'olt': Device(
                id='olt', root=True, parent_id='id'),
            'onu1': Device(
                id='onu1', parent_id='olt', parent_port_no=1, vlan=101),
            'onu2': Device(
                id='onu2', parent_id='olt', parent_port_no=2, vlan=201),
        }

        self.ports = {
            'olt': [
                Port(port_no=0, type=Port.ETHERNET_NNI, device_id='olt'),
                Port(port_no=1, type=Port.PON_OLT, device_id='olt',
                     peers=[Port.PeerPort(device_id='onu1', port_no=1)]),
                Port(port_no=2, type=Port.PON_OLT, device_id='olt',
                     peers=[Port.PeerPort(device_id='onu2', port_no=1)])
            ],
            'onu1': [
                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu1'),
                Port(port_no=1, type=Port.PON_ONU, device_id='onu1',
                     peers=[Port.PeerPort(device_id='olt', port_no=1)])
            ],
            'onu2': [
                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu2'),
                Port(port_no=1, type=Port.PON_ONU, device_id='onu2',
                     peers=[Port.PeerPort(device_id='olt', port_no=2)])
            ],
        }

        self.device_flows = {
            'olt': Flows(),
            'onu1': Flows(),
            'onu2': Flows()
        }

        self.device_groups = {
            'olt': FlowGroups(),
            'onu1': FlowGroups(),
            'onu2': FlowGroups()
        }

        self.ld = LogicalDevice(id='id', root_device_id='olt')

        self.root_proxy = Mock()
        def get_devices(path):
            if path == '':
                return self.devices.values()
            if path.endswith('/ports'):
                return self.ports[path[:-len('/ports')]]
            elif path.find('/') == -1:
                return self.devices[path]
            else:
                raise Exception(
                    'Nothing to yield for path /devices/{}'.format(path))
        def update_devices(path, data):
            if path.endswith('/flows'):
                self.device_flows[path[:-len('/flows')]] = data
            elif path.endswith('/flow_groups'):
                self.device_groups[path[:-len('/flow_groups')]] = data
            else:
                raise NotImplementedError(
                    'not handling path /devices/{}'.format(path))

        self.root_proxy.get = lambda p: \
            get_devices(p[len('/devices/'):]) if p.startswith('/devices') \
                else None
        self.root_proxy.update = lambda p, d: \
            update_devices(p[len('/devices/'):], d) \
                if p.startswith('/devices') \
                else None
        self.ld_proxy = Mock()
        self.ld_proxy.get = lambda p: \
            self.ld_ports if p == '/ports' else (
                self.ld if p == '/' else None
            )

        self.flows_proxy = Mock()
        self.flows_proxy.get = lambda _: self.flows  # always '/' path
        def update_flows(_, flows):  # always '/' path
            self.flows = flows
        self.flows_proxy.update = update_flows

        self.groups_proxy = Mock()
        self.groups_proxy.get = lambda _: self.groups  # always '/' path
        def update_groups(_, groups):  # always '/' path
            self.groups = groups
        self.groups_proxy.update = update_groups

        self.core = Mock()
        self.core.get_proxy = lambda path: \
            self.root_proxy if path == '/' else (
                self.ld_proxy if path.endswith('id') else (
                    self.flows_proxy if path.endswith('flows') else
                    self.groups_proxy
                )
            )

        self.lda = LogicalDeviceAgent(self.core, self.ld)
class test_multipon_logical_device_agent(FlowHelpers):

    def setup_mock_registry(self):
        registry = Mock()
        logical_device_agent.registry = registry

    def setUp(self):
        self.setup_mock_registry()

        self.flows = Flows(items=[])
        self.groups = FlowGroups(items=[])
        self.ld_ports = [
            LogicalPort(
                id='0',
                device_id='olt',
                device_port_no=0,
                root_port=True,
                ofp_port=ofp.ofp_port(port_no=0)
            ),
            LogicalPort(
                id='101',
                device_id='onu1',
                device_port_no=0,
                ofp_port=ofp.ofp_port(port_no=101)
            ),
            LogicalPort(
                id='201',
                device_id='onu2',
                device_port_no=0,
                ofp_port=ofp.ofp_port(port_no=201)
            )
        ]

        self.devices = {
            'olt': Device(
                id='olt', root=True, parent_id='id'),
            'onu1': Device(
                id='onu1', parent_id='olt', parent_port_no=1, vlan=101),
            'onu2': Device(
                id='onu2', parent_id='olt', parent_port_no=2, vlan=201),
        }

        self.ports = {
            'olt': [
                Port(port_no=0, type=Port.ETHERNET_NNI, device_id='olt'),
                Port(port_no=1, type=Port.PON_OLT, device_id='olt',
                     peers=[Port.PeerPort(device_id='onu1', port_no=1)]),
                Port(port_no=2, type=Port.PON_OLT, device_id='olt',
                     peers=[Port.PeerPort(device_id='onu2', port_no=1)])
            ],
            'onu1': [
                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu1'),
                Port(port_no=1, type=Port.PON_ONU, device_id='onu1',
                     peers=[Port.PeerPort(device_id='olt', port_no=1)])
            ],
            'onu2': [
                Port(port_no=0, type=Port.ETHERNET_UNI, device_id='onu2'),
                Port(port_no=1, type=Port.PON_ONU, device_id='onu2',
                     peers=[Port.PeerPort(device_id='olt', port_no=2)])
            ],
        }

        self.device_flows = {
            'olt': Flows(),
            'onu1': Flows(),
            'onu2': Flows()
        }

        self.device_groups = {
            'olt': FlowGroups(),
            'onu1': FlowGroups(),
            'onu2': FlowGroups()
        }

        self.ld = LogicalDevice(id='id', root_device_id='olt')

        self.root_proxy = Mock()
        def get_devices(path):
            if path == '':
                return self.devices.values()
            if path.endswith('/ports'):
                return self.ports[path[:-len('/ports')]]
            elif path.find('/') == -1:
                return self.devices[path]
            else:
                raise Exception(
                    'Nothing to yield for path /devices/{}'.format(path))
        def update_devices(path, data):
            if path.endswith('/flows'):
                self.device_flows[path[:-len('/flows')]] = data
            elif path.endswith('/flow_groups'):
                self.device_groups[path[:-len('/flow_groups')]] = data
            else:
                raise NotImplementedError(
                    'not handling path /devices/{}'.format(path))

        self.root_proxy.get = lambda p: \
            get_devices(p[len('/devices/'):]) if p.startswith('/devices') \
                else None
        self.root_proxy.update = lambda p, d: \
            update_devices(p[len('/devices/'):], d) \
                if p.startswith('/devices') \
                else None
        self.ld_proxy = Mock()
        self.ld_proxy.get = lambda p: \
            self.ld_ports if p == '/ports' else (
                self.ld if p == '/' else None
            )

        self.flows_proxy = Mock()
        self.flows_proxy.get = lambda _: self.flows  # always '/' path
        def update_flows(_, flows):  # always '/' path
            self.flows = flows
        self.flows_proxy.update = update_flows

        self.groups_proxy = Mock()
        self.groups_proxy.get = lambda _: self.groups  # always '/' path
        def update_groups(_, groups):  # always '/' path
            self.groups = groups
        self.groups_proxy.update = update_groups

        self.core = Mock()
        self.core.get_proxy = lambda path: \
            self.root_proxy if path == '/' else (
                self.ld_proxy if path.endswith('id') else (
                    self.flows_proxy if path.endswith('flows') else
                    self.groups_proxy
                )
            )

        self.lda = LogicalDeviceAgent(self.core, self.ld)

    def test_init(self):
        pass  # really just tests the setUp method

    # ~~~~~~~~~~~~~~~~~~~~ DEFAULT RULES AND ROUTES ~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def test_default_rules(self):
        rules = self.lda.get_all_default_rules()
        # no default olt downstream and no default group for each of 3 devs
        self.assertEqual(len(rules['olt'][0]), 0)
        self.assertEqual(len(rules['olt'][1]), 0)
        self.assertEqual(len(rules['onu1'][0]), 3)
        self.assertEqual(len(rules['onu1'][1]), 0)
        self.assertEqual(len(rules['onu2'][0]), 3)
        self.assertEqual(len(rules['onu2'][1]), 0)

    def test_routes(self):
        self.lda.get_all_default_rules()  # this will prepare the _routes
        routes = self.lda._routes
        self.assertEqual(len(routes), 4)
        self.assertEqual(set(routes.keys()),
                         set([(0, 101), (0, 201), (101, 0), (201, 0)]))

        # verify all routes
        route = routes[(0, 101)]
        self.assertEqual(len(route), 2)
        self.assertEqual(route[0].device, self.devices['olt'])
        self.assertEqual(route[0].ingress_port, self.ports['olt'][0])
        self.assertEqual(route[0].egress_port, self.ports['olt'][1])
        self.assertEqual(route[1].device, self.devices['onu1'])
        self.assertEqual(route[1].ingress_port, self.ports['onu1'][1])
        self.assertEqual(route[1].egress_port, self.ports['onu1'][0])

        route = routes[(0, 201)]
        self.assertEqual(len(route), 2)
        self.assertEqual(route[0].device, self.devices['olt'])
        self.assertEqual(route[0].ingress_port, self.ports['olt'][0])
        self.assertEqual(route[0].egress_port, self.ports['olt'][2])
        self.assertEqual(route[1].device, self.devices['onu2'])
        self.assertEqual(route[1].ingress_port, self.ports['onu2'][1])
        self.assertEqual(route[1].egress_port, self.ports['onu2'][0])

        route = routes[(101, 0)]
        self.assertEqual(len(route), 2)
        self.assertEqual(route[0].device, self.devices['onu1'])
        self.assertEqual(route[0].ingress_port, self.ports['onu1'][0])
        self.assertEqual(route[0].egress_port, self.ports['onu1'][1])
        self.assertEqual(route[1].device, self.devices['olt'])
        self.assertEqual(route[1].ingress_port, self.ports['olt'][1])
        self.assertEqual(route[1].egress_port, self.ports['olt'][0])

        route = routes[(201, 0)]
        self.assertEqual(len(route), 2)
        self.assertEqual(route[0].device, self.devices['onu2'])
        self.assertEqual(route[0].ingress_port, self.ports['onu2'][0])
        self.assertEqual(route[0].egress_port, self.ports['onu2'][1])
        self.assertEqual(route[1].device, self.devices['olt'])
        self.assertEqual(route[1].ingress_port, self.ports['olt'][2])
        self.assertEqual(route[1].egress_port, self.ports['olt'][0])

    # ~~~~~~~~~~~~~~~~~~~~~~~~~~ FLOW DECOMP TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def test_eapol_flow_decomp_case(self):
        self.lda.update_flow_table(mk_simple_flow_mod(
            priority=1000,
            match_fields=[in_port(201), eth_type(0x888e)],
            actions=[output(ofp.OFPP_CONTROLLER)]
        ))
        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 2)
        self.assertEqual(len(self.device_flows['onu1'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x888e)],
            actions=[
                push_vlan(0x8100),
                set_field(vlan_vid(4096 + 4000)),
                output(0)
            ]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(201)],
            actions=[
                pop_vlan(),
                output(2)
            ]
        ))

    def test_wildcarded_igmp_rule(self):
        self.lda.update_flow_table(mk_simple_flow_mod(
            priority=1000,
            match_fields=[eth_type(0x800), ip_proto(2)],
            actions=[output(ofp.OFPP_CONTROLLER)]
        ))

        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 4)
        self.assertEqual(len(self.device_flows['onu1'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(2), vlan_vid(4096 + 101), eth_type(0x800), ip_proto(2)],
            actions=[
                push_vlan(0x8100),
                set_field(vlan_vid(4096 + 4000)),
                output(0)
            ]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(101)],
            actions=[
                pop_vlan(),
                output(2)
            ]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[2], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x800), ip_proto(2)],
            actions=[
                push_vlan(0x8100),
                set_field(vlan_vid(4096 + 4000)),
                output(0)
            ]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[3], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(201)],
            actions=[
                pop_vlan(),
                output(2)
            ]
        ))

    def test_multicast_group_with_one_subscriber(self):
        self.lda.update_group_table(mk_multicast_group_mod(
            group_id=2,
            buckets=[
                ofp.ofp_bucket(actions=[
                    pop_vlan(),
                    output(201)
                ]),
            ]
        ))
        self.lda.update_flow_table(mk_simple_flow_mod(
            priority=1000,
            match_fields=[
                in_port(0),
                eth_type(0x800),
                vlan_vid(4096 + 140),
                ipv4_dst(0xe60a0a0a)
            ],
            actions=[group(2)]
        ))
        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 1)
        self.assertEqual(len(self.device_flows['onu1'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 4)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
                          ipv4_dst(0xe60a0a0a)],
            actions=[
                pop_vlan(),
                output(2)
            ]
        ))
        self.assertFlowsEqual(self.device_flows['onu2'].items[3], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe60a0a0a)],
            actions=[
                output(0)
            ]
        ))

    def test_multicast_group_with_no_subscribers(self):
        self.lda.update_group_table(mk_multicast_group_mod(
            group_id=2,
            buckets=[]  # No subscribers
        ))
        self.lda.update_flow_table(mk_simple_flow_mod(
            priority=1000,
            match_fields=[
                in_port(0),
                eth_type(0x800),
                vlan_vid(4096 + 140),
                ipv4_dst(0xe60a0a0a)
            ],
            actions=[group(2)]
        ))
        self.lda._flow_table_updated(self.flows)
        self.assertEqual(len(self.device_flows['olt'].items), 1)
        self.assertEqual(len(self.device_flows['onu1'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_flows['onu2'].items), 3)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
                          ipv4_dst(0xe60a0a0a)],
            actions=[
                pop_vlan(),
                output(2)
            ]
        ))
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ COMPLEX TESTS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

    def test_complex_flow_table_decomposition(self):

        # Various controller-bound rules
        for _in_port in (101, 201):
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=2000,
                match_fields=[in_port(_in_port), eth_type(0x888e)],
                actions=[
                    push_vlan(0x8100),
                    set_field(vlan_vid(4096 + 4000)),
                    output(ofp.OFPP_CONTROLLER)
                ]
            ))
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=1000,
                match_fields=[in_port(_in_port), eth_type(0x800), ip_proto(2)],
                actions=[output(ofp.OFPP_CONTROLLER)]
            ))
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=1000,
                match_fields=[in_port(_in_port), eth_type(0x800), ip_proto(17),
                              udp_src(68), udp_dst(67)],
                actions=[output(ofp.OFPP_CONTROLLER)]
            ))

        # Multicast channels
        mcast_setup = (
            (1, 0xe4010101, ()),
            (2, 0xe4010102, (101,)),
            (3, 0xe4010103, (201,)),
            (4, 0xe4010104, (101, 201)),
        )
        for group_id, mcast_addr, ports in mcast_setup:
            self.lda.update_group_table(mk_multicast_group_mod(
                group_id=group_id,
                buckets=[
                    ofp.ofp_bucket(actions=[
                        pop_vlan(),
                        output(port)
                    ]) for port in ports
                ]))
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=1000,
                match_fields=[
                    in_port(0),
                    eth_type(0x800),
                    vlan_vid(4096 + 140),
                    ipv4_dst(mcast_addr)
                ],
                actions=[
                    group(group_id)
                ]
            ))

        # Unicast channels for each subscriber
        for port, c_vid in ((101, 101), (201, 201)):

            # Downstream flow 1 for nni to pon
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=500,
                match_fields=[
                    in_port(0),
                    vlan_vid(4096 + 1000),
                    metadata(c_vid)
                ],
                actions=[pop_vlan()],
                next_table_id=1
            ))

            # Downstream flow 2
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=500,
                match_fields=[in_port(0), vlan_vid(4096 + c_vid)],
                actions=[set_field(vlan_vid(4096 + 0)), output(port)]
            ))

            # upstream flow 1 for the 0-tagged case
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=500,
                match_fields=[in_port(port), vlan_vid(4096 + 0)],
                actions=[set_field(vlan_vid(4096 + c_vid))],
                next_table_id=1
            ))
            # ... and for the untagged case
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=500,
                match_fields=[in_port(port), vlan_vid(0)],
                actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + c_vid))],
                next_table_id=1
            ))

            # Upstream flow 2 for s-tag
            self.lda.update_flow_table(mk_simple_flow_mod(
                priority=500,
                match_fields=[in_port(port), vlan_vid(4096 + c_vid)],
                actions=[
                    push_vlan(0x8100),
                    set_field(vlan_vid(4096 + 1000)),
                    output(0)
                ]
            ))

        self.assertEqual(len(self.flows.items), 20)
        self.assertEqual(len(self.groups.items), 4)

        # trigger flow table decomposition
        self.lda._flow_table_updated(self.flows)

        # now check device level flows
        self.assertEqual(len(self.device_flows['olt'].items), 18)
        self.assertEqual(len(self.device_flows['onu1'].items), 5)
        self.assertEqual(len(self.device_flows['onu2'].items), 5)
        self.assertEqual(len(self.device_groups['olt'].items), 0)
        self.assertEqual(len(self.device_groups['onu1'].items), 0)
        self.assertEqual(len(self.device_groups['onu2'].items), 0)

        # Flows installed on the OLT
        self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
            priority=2000,
            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(101)],
            actions=[pop_vlan(), output(1)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[0], mk_flow_stat(
            priority=2000,
            match_fields=[in_port(1), vlan_vid(4096 + 101), eth_type(0x888e)],
            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                     output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[2], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(1), vlan_vid(4096 + 101), eth_type(0x800), ip_proto(2)],
            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                     output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[3], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(101)],
            actions=[pop_vlan(), output(1)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[4], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(1), vlan_vid(4096 + 101), eth_type(0x800), ip_proto(17),
                          udp_src(68), udp_dst(67)],
            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                     output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[5], mk_flow_stat(
            priority=2000,
            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x888e)],
            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                     output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[6], mk_flow_stat(
            priority=2000,
            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(201)],
            actions=[pop_vlan(), output(2)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[7], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x800), ip_proto(2)],
            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                     output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[8], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), vlan_vid(4096 + 4000), vlan_pcp(0), metadata(201)],
            actions=[pop_vlan(), output(2)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[9], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(2), vlan_vid(4096 + 201), eth_type(0x800), ip_proto(17),
                          udp_src(68), udp_dst(67)],
            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
                     output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[10], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
                          ipv4_dst(0xe4010101)],
            actions=[pop_vlan(), output(2)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[11], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
                          ipv4_dst(0xe4010102)],
            actions=[pop_vlan(), output(2)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[12], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
                          ipv4_dst(0xe4010103)],
            actions=[pop_vlan(), output(2)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[13], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(0), eth_type(0x800), vlan_vid(4096 + 140),
                          ipv4_dst(0xe4010104)],
            actions=[pop_vlan(), output(2)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[14], mk_flow_stat(
            priority=500,
            match_fields=[in_port(0), vlan_vid(4096 + 1000), metadata(101)],
            actions=[pop_vlan(), output(1)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[15], mk_flow_stat(
            priority=500,
            match_fields=[in_port(1), vlan_vid(4096 + 101)],
            actions=[
                push_vlan(0x8100), set_field(vlan_vid(4096 + 1000)), output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[16], mk_flow_stat(
            priority=500,
            match_fields=[in_port(0), vlan_vid(4096 + 1000), metadata(201)],
            actions=[pop_vlan(), output(2)]
        ))
        self.assertFlowsEqual(self.device_flows['olt'].items[17], mk_flow_stat(
            priority=500,
            match_fields=[in_port(2), vlan_vid(4096 + 201)],
            actions=[
                push_vlan(0x8100), set_field(vlan_vid(4096 + 1000)), output(0)]
        ))

        # Flows installed on the ONU1
        self.assertFlowsEqual(self.device_flows['onu1'].items[0], mk_flow_stat(
            priority=500,
            match_fields=[in_port(0), vlan_vid(4096 + 0)],
            actions=[
                set_field(vlan_vid(4096 + 101)), output(1)]
        ))
        self.assertFlowsEqual(self.device_flows['onu1'].items[3], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe4010102)],
            actions=[output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['onu1'].items[4], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe4010104)],
            actions=[output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['onu1'].items[2], mk_flow_stat(
            priority=500,
            match_fields=[in_port(1), vlan_vid(4096 + 101)],
            actions=[set_field(vlan_vid(4096 + 0)), output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['onu1'].items[1], mk_flow_stat(
            priority=500,
            match_fields=[in_port(0), vlan_vid(0)],
            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 101)),
                     output(1)]
        ))

        # Flows installed on the ONU2
        self.assertFlowsEqual(self.device_flows['onu2'].items[0], mk_flow_stat(
            priority=500,
            match_fields=[in_port(0), vlan_vid(4096 + 0)],
            actions=[
                set_field(vlan_vid(4096 + 201)), output(1)]
        ))
        self.assertFlowsEqual(self.device_flows['onu2'].items[3], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe4010103)],
            actions=[output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['onu2'].items[4], mk_flow_stat(
            priority=1000,
            match_fields=[in_port(1), eth_type(0x800), ipv4_dst(0xe4010104)],
            actions=[output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['onu2'].items[2], mk_flow_stat(
            priority=500,
            match_fields=[in_port(1), vlan_vid(4096 + 201)],
            actions=[set_field(vlan_vid(4096 + 0)), output(0)]
        ))
        self.assertFlowsEqual(self.device_flows['onu2'].items[1], mk_flow_stat(
            priority=500,
            match_fields=[in_port(0), vlan_vid(0)],
            actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 201)),
                     output(1)]
        ))