blob: 49a2bf91f6eefa492cec80e4f5bf19c80f307dd2 [file] [log] [blame]
# Copyright 2017-present Open Networking Foundation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from unittest import main
from mock import Mock
from tests.utests.voltha.core.flow_helpers import FlowHelpers
from voltha.core import logical_device_agent
from voltha.core.flow_decomposer import *
from voltha.core.logical_device_agent import LogicalDeviceAgent
from voltha.protos.device_pb2 import Device, Port
from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
from voltha.protos.openflow_13_pb2 import Flows, FlowGroups
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)],
meter_id=1,
metadata=32
))
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_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), eth_type(0x888e)],
actions=[
output(2147483645)
],
meter_id=1,
metadata=32
))
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(1)
]
))
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(2)
]
), self.device_flows['olt'])
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 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)
],
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), 19)
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), 10)
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), eth_type(0x888e)],
actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
output(2147483645)]
))
self.assertFlowsEqual(self.device_flows['olt'].items[1], mk_flow_stat(
priority=1000,
match_fields=[in_port(1), eth_type(0x800), ip_proto(2)],
actions=[output(2147483645)]
))
self.assertFlowsEqual(self.device_flows['olt'].items[2], mk_flow_stat(
priority=1000,
match_fields=[in_port(1), eth_type(0x800), ip_proto(17),
udp_src(68), udp_dst(67)],
actions=[output(2147483645)]
))
self.assertFlowsEqual(self.device_flows['olt'].items[3], mk_flow_stat(
priority=2000,
match_fields=[in_port(2), eth_type(0x888e)],
actions=[push_vlan(0x8100), set_field(vlan_vid(4096 + 4000)),
output(2147483645)]
))
self.assertFlowsEqual(self.device_flows['olt'].items[4], mk_flow_stat(
priority=1000,
match_fields=[in_port(2), eth_type(0x800), ip_proto(2)],
actions=[output(2147483645)]
))
self.assertFlowsEqual(self.device_flows['olt'].items[5], mk_flow_stat(
priority=1000,
match_fields=[in_port(2), eth_type(0x800), ip_proto(17),
udp_src(68), udp_dst(67)],
actions=[output(2147483645)]
))
self.assertFlowsEqual(self.device_flows['olt'].items[6], 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[9], mk_flow_stat(
priority=500,
match_fields=[in_port(0), vlan_vid(4096 + 1000)],
actions=[pop_vlan(), output(2)]
))
# 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 + 201)), output(1)]
))
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)]
))
if __name__ == '__main__':
main()