blob: 1ab620d826a0a1de6d3df46cfaf54f6918eb21d6 [file] [log] [blame]
Zsolt Harasztied091602016-12-08 13:36:38 -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"""
18Tibit ONU device adapter
19"""
20
Zsolt Haraszti348d1932016-12-10 01:10:07 -080021import json
22
23from uuid import uuid4
24
Zsolt Harasztied091602016-12-08 13:36:38 -080025import structlog
26from zope.interface import implementer
27
Zsolt Haraszti348d1932016-12-10 01:10:07 -080028from scapy.layers.inet import ICMP, IP
29from scapy.layers.l2 import Ether
30from twisted.internet.defer import DeferredQueue, inlineCallbacks
31from twisted.internet import reactor
32
Zsolt Haraszti85f12852016-12-24 08:30:58 -080033from voltha.core.flow_decomposer import *
Zsolt Haraszti348d1932016-12-10 01:10:07 -080034from voltha.core.logical_device_agent import mac_str_to_tuple
35
Zsolt Harasztied091602016-12-08 13:36:38 -080036from voltha.adapters.interface import IAdapterInterface
37from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
Zsolt Haraszti348d1932016-12-10 01:10:07 -080038from voltha.protos.device_pb2 import Port
Zsolt Harasztied091602016-12-08 13:36:38 -080039from voltha.protos.device_pb2 import DeviceType, DeviceTypes
40from voltha.protos.health_pb2 import HealthStatus
Zsolt Haraszti348d1932016-12-10 01:10:07 -080041from voltha.protos.common_pb2 import LogLevel, ConnectStatus
Zsolt Haraszti348d1932016-12-10 01:10:07 -080042from voltha.protos.common_pb2 import OperStatus, AdminState
43
44from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
45from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_10GB_FD, \
46 OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
47 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
48
49from scapy.packet import Packet, bind_layers
50from scapy.fields import StrField
Zsolt Harasztied091602016-12-08 13:36:38 -080051
52log = structlog.get_logger()
53
Zsolt Haraszti348d1932016-12-10 01:10:07 -080054from EOAM_TLV import AddStaticMacAddress, DeleteStaticMacAddress
55from EOAM_TLV import ClearStaticMacTable
Nathan Knuth6e57f332016-12-22 15:49:20 -080056from EOAM_TLV import DeviceId
57from EOAM import EOAMPayload, CablelabsOUI
58from EOAM import DPoEOpcode_GetRequest, DPoEOpcode_SetRequest
Zsolt Harasztied091602016-12-08 13:36:38 -080059
60@implementer(IAdapterInterface)
61class TibitOnuAdapter(object):
62
63 name = 'tibit_onu'
64
65 supported_device_types = [
66 DeviceType(
67 id='tibit_onu',
68 adapter=name,
69 accepts_bulk_flow_update=True
70 )
71 ]
72
73 def __init__(self, adapter_agent, config):
74 self.adapter_agent = adapter_agent
75 self.config = config
76 self.descriptor = Adapter(
77 id=self.name,
78 vendor='Tibit Communications Inc.',
79 version='0.1',
80 config=AdapterConfig(log_level=LogLevel.INFO)
81 )
Zsolt Haraszti348d1932016-12-10 01:10:07 -080082 self.incoming_messages = DeferredQueue()
Zsolt Harasztied091602016-12-08 13:36:38 -080083
84 def start(self):
85 log.debug('starting')
86 log.info('started')
87
88 def stop(self):
89 log.debug('stopping')
90 log.info('stopped')
91
92 def adapter_descriptor(self):
93 return self.descriptor
94
95 def device_types(self):
96 return DeviceTypes(items=self.supported_device_types)
97
98 def health(self):
99 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
100
101 def change_master_state(self, master):
102 raise NotImplementedError()
103
104 def adopt_device(self, device):
105 log.info('adopt-device', device=device)
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800106 reactor.callLater(0.1, self._onu_device_activation, device)
Zsolt Harasztied091602016-12-08 13:36:38 -0800107 return device
108
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800109 @inlineCallbacks
110 def _onu_device_activation(self, device):
111 # first we verify that we got parent reference and proxy info
112 assert device.parent_id
113 assert device.proxy_address.device_id
114 assert device.proxy_address.channel_id
115
116 # TODO: For now, pretend that we were able to contact the device and obtain
117 # additional information about it. Should add real message.
118 device.vendor = 'Tibit Communications, Inc.'
119 device.model = '10G GPON ONU'
120 device.hardware_version = 'fa161020'
121 device.firmware_version = '16.10.01'
122 device.software_version = '1.0'
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800123 device.serial_number = uuid4().hex
124 device.connect_status = ConnectStatus.REACHABLE
125 self.adapter_agent.update_device(device)
126
127 # then shortly after we create some ports for the device
128 uni_port = Port(
129 port_no=2,
130 label='UNI facing Ethernet port',
131 type=Port.ETHERNET_UNI,
132 admin_state=AdminState.ENABLED,
133 oper_status=OperStatus.ACTIVE
134 )
135 self.adapter_agent.add_port(device.id, uni_port)
136 self.adapter_agent.add_port(device.id, Port(
137 port_no=1,
138 label='PON port',
139 type=Port.PON_ONU,
140 admin_state=AdminState.ENABLED,
141 oper_status=OperStatus.ACTIVE,
142 peers=[
143 Port.PeerPort(
144 device_id=device.parent_id,
145 port_no=device.parent_port_no
146 )
147 ]
148 ))
149
150 # TODO adding vports to the logical device shall be done by agent?
151 # then we create the logical device port that corresponds to the UNI
152 # port of the device
153
154 # obtain logical device id
155 parent_device = self.adapter_agent.get_device(device.parent_id)
156 logical_device_id = parent_device.parent_id
157 assert logical_device_id
158
159 # we are going to use the proxy_address.channel_id as unique number
160 # and name for the virtual ports, as this is guaranteed to be unique
161 # in the context of the OLT port, so it is also unique in the context
162 # of the logical device
163 port_no = device.proxy_address.channel_id
164 cap = OFPPF_10GB_FD | OFPPF_FIBER
165 self.adapter_agent.add_logical_port(logical_device_id, LogicalPort(
166 id=str(port_no),
167 ofp_port=ofp_port(
168 port_no=port_no,
169 hw_addr=mac_str_to_tuple(device.mac_address),
170 name='uni-{}'.format(port_no),
171 config=0,
172 state=OFPPS_LIVE,
173 curr=cap,
174 advertised=cap,
175 peer=cap,
176 curr_speed=OFPPF_10GB_FD,
177 max_speed=OFPPF_10GB_FD
178 ),
179 device_id=device.id,
180 device_port_no=uni_port.port_no
181 ))
182
183 # simulate a proxied message sending and receving a reply
184 reply = yield self._message_exchange(device)
185
186 # and finally update to "ACTIVE"
187 device = self.adapter_agent.get_device(device.id)
188 device.oper_status = OperStatus.ACTIVE
189 self.adapter_agent.update_device(device)
190
Zsolt Harasztied091602016-12-08 13:36:38 -0800191 def abandon_device(self, device):
192 raise NotImplementedError(0
193 )
194 def deactivate_device(self, device):
195 raise NotImplementedError()
196
197 def update_flows_bulk(self, device, flows, groups):
198 log.debug('bulk-flow-update', device_id=device.id,
199 flows=flows, groups=groups)
200
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800201 # sample code that analyzes the incoming flow table
202 assert len(groups.items) == 0, "Cannot yet deal with groups"
203
204 for flow in flows.items:
205 in_port = get_in_port(flow)
206 assert in_port is not None
207
208 if in_port == 2:
209
210 # Downstream rule
211
212 for field in get_ofb_fields(flow):
213 if field.type == ETH_TYPE:
214 _type = field.eth_type
215 pass # construct ether type based condition here
216
217 elif field.type == IP_PROTO:
218 _proto = field.ip_proto
219 pass # construct ip_proto based condition here
220
221 elif field.type == IN_PORT:
222 _port = field.port
223 pass # construct in_port based condition here
224
225 elif field.type == VLAN_VID:
226 _vlan_vid = field.vlan_vid
227 pass # construct VLAN ID based filter condition here
228
229 elif field.type == VLAN_PCP:
230 _vlan_pcp = field.vlan_pcp
231 pass # construct VLAN PCP based filter condition here
232
233 # TODO
234 else:
235 raise NotImplementedError('field.type={}'.format(
236 field.type))
237
238 for action in get_actions(flow):
239
240 if action.type == OUTPUT:
241 pass # construct packet emit rule here
242
243 elif action.type == PUSH_VLAN:
244 if action.push.ethertype != 0x8100:
245 log.error('unhandled-ether-type',
246 ethertype=action.push.ethertype)
247 pass # construct vlan push command here
248
249 elif action.type == POP_VLAN:
250 pass # construct vlan pop command here
251
252 elif action.type == SET_FIELD:
253 assert (action.set_field.field.oxm_class ==
254 ofp.OFPXMC_OPENFLOW_BASIC)
255 field = action.set_field.field.ofb_field
256 if field.type == VLAN_VID:
257 pass # construct vlan_id set command here
258 else:
259 log.error('unsupported-action-set-field-type',
260 field_type=field.type)
261
262 else:
263 log.error('unsupported-action-type',
264 action_type=action.type)
265
266 # final assembly of low level device flow rule and pushing it
267 # down to device
268 pass
269
270 elif in_port == 1:
271
272 # Upstream rule
273
274 for field in get_ofb_fields(flow):
275 if field.type == ETH_TYPE:
276 _type = field.eth_type
277 pass # construct ether type based condition here
278
279 elif field.type == IP_PROTO:
280 _proto = field.ip_proto
281 pass # construct ip_proto based condition here
282
283 elif field.type == IN_PORT:
284 _port = field.port
285 pass # construct in_port based condition here
286
287 elif field.type == VLAN_VID:
288 _vlan_vid = field.vlan_vid
289 pass # construct VLAN ID based filter condition here
290
291 elif field.type == VLAN_PCP:
292 _vlan_pcp = field.vlan_pcp
293 pass # construct VLAN PCP based filter condition here
294
295 elif field.type == IPV4_DST:
296 _ipv4_dst = field.ipv4_dst
297 pass # construct IPv4 DST address based condition
298
299 elif field.type == UDP_DST:
300 _udp_dst = field.udp_dst
301 pass # construct UDP SDT based filter here
302
303 # TODO
304 else:
305 raise NotImplementedError('field.type={}'.format(
306 field.type))
307
308 for action in get_actions(flow):
309
310 if action.type == OUTPUT:
311 pass # construct packet emit rule here
312
313 elif action.type == PUSH_VLAN:
314 if action.push.ethertype != 0x8100:
315 log.error('unhandled-ether-type',
316 ethertype=action.push.ethertype)
317 pass # construct vlan push command here
318
319 elif action.type == SET_FIELD:
320 assert (action.set_field.field.oxm_class ==
321 ofp.OFPXMC_OPENFLOW_BASIC)
322 field = action.set_field.field.ofb_field
323 if field.type == VLAN_VID:
324 pass # construct vlan_id set command here
325 else:
326 log.error('unsupported-action-set-field-type',
327 field_type=field.type)
328
329 else:
330 log.error('unsupported-action-type',
331 action_type=action.type)
332
333 # final assembly of low level device flow rule and pushing it
334 # down to device
335 pass
336
337 else:
338 raise Exception('Port should be 1 or 2 by our convention')
339
Zsolt Harasztied091602016-12-08 13:36:38 -0800340 def update_flows_incrementally(self, device, flow_changes, group_changes):
341 raise NotImplementedError()
342
343 def send_proxied_message(self, proxy_address, msg):
344 raise NotImplementedError()
345
346 def receive_proxied_message(self, proxy_address, msg):
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800347 log.debug('receive-proxied-message',
Zsolt Harasztied091602016-12-08 13:36:38 -0800348 proxy_address=proxy_address, msg=msg)
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800349 self.incoming_messages.put(msg)
350
351 @inlineCallbacks
352 def _message_exchange(self, device):
353
354 # register for receiving async messages
355 self.adapter_agent.register_for_proxied_messages(device.proxy_address)
356
357 # reset incoming message queue
358 while self.incoming_messages.pending:
359 _ = yield self.incoming_messages.get()
360
361 # construct message
Nathan Knuth6e57f332016-12-22 15:49:20 -0800362 msg = EOAMPayload(body=CablelabsOUI() /
363 DPoEOpcode_GetRequest() /
364 DeviceId()
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800365 )
366
367 # send message
Nathan Knuth6e57f332016-12-22 15:49:20 -0800368 log.info('ONU-send-proxied-message')
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800369 self.adapter_agent.send_proxied_message(device.proxy_address, msg)
370
371 log.info('ONU-log incoming messages BEFORE')
372 # wait till we detect incoming message
373 yield self.incoming_messages.get()
374 log.info('ONU-log incoming messages AFTER')
375
376 # by returning we allow the device to be shown as active, which
377 # indirectly verified that message passing works