blob: 9bd267a299e59b302fcea57e9fd900d25dc2c967 [file] [log] [blame]
Zsolt Haraszti656ecc62016-12-28 15:08:23 -08001#
2# Copyright 2016 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Fully simulated OLT/ONU adapter.
19"""
20from uuid import uuid4
21
22import grpc
23import structlog
24from scapy.layers.l2 import Ether, Dot1Q
25from twisted.internet import reactor
26from zope.interface import implementer
27
28from common.frameio.frameio import BpfProgramFilter, hexify
29from voltha.adapters.interface import IAdapterInterface
30from voltha.core.logical_device_agent import mac_str_to_tuple
31from voltha.protos import third_party
32from voltha.protos import ponsim_pb2
33from voltha.protos.adapter_pb2 import Adapter
34from voltha.protos.adapter_pb2 import AdapterConfig
35from voltha.protos.common_pb2 import LogLevel, OperStatus, ConnectStatus, \
36 AdminState
37from voltha.protos.device_pb2 import DeviceType, DeviceTypes, Port, Device
38from voltha.protos.health_pb2 import HealthStatus
39from google.protobuf.empty_pb2 import Empty
40
41from voltha.protos.logical_device_pb2 import LogicalPort, LogicalDevice
42from voltha.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER, OFPPF_1GB_FD, \
43 OFPC_GROUP_STATS, OFPC_PORT_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS, \
44 ofp_switch_features, ofp_desc
45from voltha.protos.openflow_13_pb2 import ofp_port
46from voltha.protos.ponsim_pb2 import FlowTable
47from voltha.registry import registry
48
49_ = third_party
50log = structlog.get_logger()
51
52
53PACKET_IN_VLAN = 4000
54is_inband_frame = BpfProgramFilter('(ether[14:2] & 0xfff) = 0x{:03x}'.format(
55 PACKET_IN_VLAN))
56
57
58@implementer(IAdapterInterface)
59class PonSimOltAdapter(object):
60
61 name = 'ponsim_olt'
62
63 supported_device_types = [
64 DeviceType(
65 id=name,
66 adapter=name,
67 accepts_bulk_flow_update=True
68 )
69 ]
70
71 def __init__(self, adapter_agent, config):
72 self.adapter_agent = adapter_agent
73 self.config = config
74 self.descriptor = Adapter(
75 id=self.name,
76 vendor='Voltha project',
77 version='0.4',
78 config=AdapterConfig(log_level=LogLevel.INFO)
79 )
80 self.devices_handlers = dict() # device_id -> PonSimOltHandler()
81 self.logical_device_id_to_root_device_id = dict()
82
83 def start(self):
84 log.debug('starting')
85 log.info('started')
86
87 def stop(self):
88 log.debug('stopping')
89 log.info('stopped')
90
91 def adapter_descriptor(self):
92 return self.descriptor
93
94 def device_types(self):
95 return DeviceTypes(items=self.supported_device_types)
96
97 def health(self):
98 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
99
100 def change_master_state(self, master):
101 raise NotImplementedError()
102
103 def adopt_device(self, device):
104 self.devices_handlers[device.id] = PonSimOltHandler(self, device.id)
105 reactor.callLater(0, self.devices_handlers[device.id].activate, device)
106 return device
107
108 def abandon_device(self, device):
109 raise NotImplementedError()
110
111 def deactivate_device(self, device):
112 raise NotImplementedError()
113
114 def update_flows_bulk(self, device, flows, groups):
115 log.info('bulk-flow-update', device_id=device.id,
116 flows=flows, groups=groups)
117 assert len(groups.items) == 0
118 handler = self.devices_handlers[device.id]
119 return handler.update_flow_table(flows.items)
120
121 def update_flows_incrementally(self, device, flow_changes, group_changes):
122 raise NotImplementedError()
123
124 def send_proxied_message(self, proxy_address, msg):
125 log.info('send-proxied-message', proxy_address=proxy_address, msg=msg)
126 handler = self.devices_handlers[proxy_address.device_id]
127 handler.send_proxied_message(proxy_address, msg)
128
129 def receive_proxied_message(self, proxy_address, msg):
130 raise NotImplementedError()
131
132 def receive_packet_out(self, logical_device_id, egress_port_no, msg):
133
134 def ldi_to_di(ldi):
135 di = self.logical_device_id_to_root_device_id.get(ldi)
136 if di is None:
137 logical_device = self.adapter_agent.get_logical_device(ldi)
138 di = logical_device.root_device_id
139 self.logical_device_id_to_root_device_id[ldi] = di
140 return di
141
142 device_id = ldi_to_di(logical_device_id)
143 handler = self.devices_handlers[device_id]
144 handler.packet_out(egress_port_no, msg)
145
146
147class PonSimOltHandler(object):
148
149 def __init__(self, adapter, device_id):
150 self.adapter = adapter
151 self.adapter_agent = adapter.adapter_agent
152 self.device_id = device_id
153 self.log = structlog.get_logger(device_id=device_id)
154 self.channel = None
155 self.io_port = None
156 self.logical_device_id = None
157 self.interface = registry('main').get_args().interface
158
159 def __del__(self):
160 if self.io_port is not None:
161 registry('frameio').del_interface(self.interface)
162
163 def get_channel(self):
164 if self.channel is None:
165 device = self.adapter_agent.get_device(self.device_id)
166 self.channel = grpc.insecure_channel(device.host_and_port)
167 return self.channel
168
169 def activate(self, device):
170 self.log.info('activating')
171
172 if not device.host_and_port:
173 device.oper_status = OperStatus.FAILED
174 device.reason = 'No host_and_port field provided'
175 self.adapter_agent.update_device(device)
176 return
177
178 stub = ponsim_pb2.PonSimStub(self.get_channel())
179 info = stub.GetDeviceInfo(Empty())
180 log.info('got-info', info=info)
181
182 device.root = True
183 device.vendor = 'ponsim'
184 device.model ='n/a'
185 device.serial_number = device.host_and_port
186 device.connect_status = ConnectStatus.REACHABLE
187 self.adapter_agent.update_device(device)
188
189 nni_port = Port(
190 port_no=2,
191 label='NNI facing Ethernet port',
192 type=Port.ETHERNET_NNI,
193 admin_state=AdminState.ENABLED,
194 oper_status=OperStatus.ACTIVE
195 )
196 self.adapter_agent.add_port(device.id, nni_port)
197 self.adapter_agent.add_port(device.id, Port(
198 port_no=1,
199 label='PON port',
200 type=Port.PON_OLT,
201 admin_state=AdminState.ENABLED,
202 oper_status=OperStatus.ACTIVE
203 ))
204
205 ld = LogicalDevice(
206 # not setting id and datapth_id will let the adapter agent pick id
207 desc=ofp_desc(
208 mfr_desc='cord porject',
209 hw_desc='simualted pon',
210 sw_desc='simualted pon',
211 serial_num=uuid4().hex,
212 dp_desc='n/a'
213 ),
214 switch_features=ofp_switch_features(
215 n_buffers=256, # TODO fake for now
216 n_tables=2, # TODO ditto
217 capabilities=( # TODO and ditto
218 OFPC_FLOW_STATS
219 | OFPC_TABLE_STATS
220 | OFPC_PORT_STATS
221 | OFPC_GROUP_STATS
222 )
223 ),
224 root_device_id=device.id
225 )
226 ld_initialized = self.adapter_agent.create_logical_device(ld)
227 cap = OFPPF_1GB_FD | OFPPF_FIBER
228 self.adapter_agent.add_logical_port(ld_initialized.id, LogicalPort(
229 id='nni',
230 ofp_port=ofp_port(
231 port_no=info.nni_port,
232 hw_addr=mac_str_to_tuple('00:00:00:00:00:%02x' % info.nni_port),
233 name='nni',
234 config=0,
235 state=OFPPS_LIVE,
236 curr=cap,
237 advertised=cap,
238 peer=cap,
239 curr_speed=OFPPF_1GB_FD,
240 max_speed=OFPPF_1GB_FD
241 ),
242 device_id=device.id,
243 device_port_no=nni_port.port_no,
244 root_port=True
245 ))
246
247 device = self.adapter_agent.get_device(device.id)
248 device.parent_id = ld_initialized.id
249 device.oper_status = OperStatus.ACTIVE
250 self.adapter_agent.update_device(device)
251 self.logical_device_id = ld_initialized.id
252
253 # register ONUS per uni port
254 for port_no in info.uni_ports:
255 vlan_id = port_no
256 self.adapter_agent.child_device_detected(
257 parent_device_id=device.id,
258 parent_port_no=1,
259 child_device_type='ponsim_onu',
260 proxy_address=Device.ProxyAddress(
261 device_id=device.id,
262 channel_id=vlan_id
263 ),
264 vlan=vlan_id
265 )
266
267 # finally, open the frameio port to receive in-band packet_in messages
268 self.log.info('registering-frameio')
269 self.io_port = registry('frameio').add_interface(
270 self.interface, self.rcv_io, is_inband_frame)
271 self.log.info('registered-frameio')
272
273 def rcv_io(self, port, frame):
274 self.log.info('reveived', iface_name=port.iface_name,
275 frame_len=len(frame))
276 pkt = Ether(frame)
277 if pkt.haslayer(Dot1Q):
278 outer_shim = pkt.getlayer(Dot1Q)
279 if isinstance(outer_shim.payload, Dot1Q):
280 inner_shim = outer_shim.payload
281 cvid = inner_shim.vlan
282 logical_port = cvid
283 popped_frame = (
284 Ether(src=pkt.src, dst=pkt.dst, type=inner_shim.type) /
285 inner_shim.payload
286 )
287 kw = dict(
288 logical_device_id=self.logical_device_id,
289 logical_port_no=logical_port,
290 )
291 self.log.info('sending-packet-in', **kw)
292 self.adapter_agent.send_packet_in(
293 packet=str(popped_frame), **kw)
294
295 def update_flow_table(self, flows):
296 stub = ponsim_pb2.PonSimStub(self.get_channel())
297 self.log.info('pushing-olt-flow-table')
298 stub.UpdateFlowTable(FlowTable(
299 port=0,
300 flows=flows
301 ))
302 self.log.info('success')
303
304 def send_proxied_message(self, proxy_address, msg):
305 self.log.info('sending-proxied-message')
306 if isinstance(msg, FlowTable):
307 stub = ponsim_pb2.PonSimStub(self.get_channel())
308 self.log.info('pushing-onu-flow-table', port=msg.port)
309 res = stub.UpdateFlowTable(msg)
310 self.adapter_agent.receive_proxied_message(proxy_address, res)
311
312 def packet_out(self, egress_port, msg):
313 self.log.info('sending-packet-out', egress_port=egress_port,
314 msg=hexify(msg))
315 pkt = Ether(msg)
316 out_pkt = (
317 Ether(src=pkt.src, dst=pkt.dst) /
318 Dot1Q(vlan=4000) /
319 Dot1Q(vlan=egress_port, type=pkt.type) /
320 pkt.payload
321 )
322 self.io_port.send(str(out_pkt))