blob: 6084bc433cc8b6b151f0977bea14b75395bf6144 [file] [log] [blame]
Nathan Knuthab966e52017-01-30 07:48:13 -08001#
2# Copyright 2017 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"""
18DPoE ONU device adapter
19"""
20
21import json
22
23from uuid import uuid4
24
25import structlog
26from zope.interface import implementer
27
28from binascii import hexlify
29
30from scapy.layers.inet import ICMP, IP
31from scapy.layers.l2 import Ether, Dot1Q
32from twisted.internet.defer import DeferredQueue, inlineCallbacks
33from twisted.internet import reactor
34
35from voltha.core.flow_decomposer import *
36from voltha.core.logical_device_agent import mac_str_to_tuple
37
38from voltha.adapters.interface import IAdapterInterface
39from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
40from voltha.protos.device_pb2 import Port
41from voltha.protos.device_pb2 import DeviceType, DeviceTypes
42from voltha.protos.health_pb2 import HealthStatus
43from voltha.protos.common_pb2 import LogLevel, ConnectStatus
44from voltha.protos.common_pb2 import OperStatus, AdminState
45
46from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
47from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_10GB_FD, \
48 OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
49 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
50
51from scapy.packet import Packet, bind_layers
52from scapy.fields import StrField
53
54log = structlog.get_logger()
55
56from voltha.extensions.eoam.EOAM_TLV import AddStaticMacAddress, DeleteStaticMacAddress
57from voltha.extensions.eoam.EOAM_TLV import ClearStaticMacTable
58from voltha.extensions.eoam.EOAM_TLV import DeviceId
59from voltha.extensions.eoam.EOAM_TLV import ClauseSubtypeEnum
60from voltha.extensions.eoam.EOAM_TLV import RuleOperatorEnum
61from voltha.extensions.eoam.EOAM_TLV import FirmwareInfo
62
63from voltha.extensions.eoam.EOAM import EOAMPayload, CablelabsOUI
64from voltha.extensions.eoam.EOAM import DPoEOpcode_GetRequest, DPoEOpcode_SetRequest
65
66@implementer(IAdapterInterface)
67class DPoEOnuAdapter(object):
68
69 name = 'dpoe_onu'
70
71 supported_device_types = [
72 DeviceType(
73 id='dpoe_onu',
74 adapter=name,
75 accepts_bulk_flow_update=True
76 )
77 ]
78
79 def __init__(self, adapter_agent, config):
80 self.adapter_agent = adapter_agent
81 self.config = config
82 self.descriptor = Adapter(
83 id=self.name,
84 vendor='Sumitomo Electric, Inc.',
85 version='0.1',
86 config=AdapterConfig(log_level=LogLevel.INFO)
87 )
88 self.incoming_messages = DeferredQueue()
89
90 def start(self):
91 log.debug('starting')
92 log.info('started')
93
94 def stop(self):
95 log.debug('stopping')
96 log.info('stopped')
97
98 def adapter_descriptor(self):
99 return self.descriptor
100
101 def device_types(self):
102 return DeviceTypes(items=self.supported_device_types)
103
104 def health(self):
105 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
106
107 def change_master_state(self, master):
108 raise NotImplementedError()
109
Sergio Slobodrianec864c62017-03-09 11:41:43 -0500110 def update_pm_config(self, device, pm_configs):
111 raise NotImplementedError()
112
Nathan Knuthab966e52017-01-30 07:48:13 -0800113 def adopt_device(self, device):
114 log.info('adopt-device', device=device)
115 reactor.callLater(0.1, self._onu_device_activation, device)
116 return device
117
118 @inlineCallbacks
119 def _onu_device_activation(self, device):
120 # first we verify that we got parent reference and proxy info
121 assert device.parent_id
122 assert device.proxy_address.device_id
123 assert device.proxy_address.channel_id
124
125 # TODO: For now, pretend that we were able to contact the device and obtain
126 # additional information about it. Should add real message.
127 device.vendor = 'Sumitomo Electric, Inc.'
128 device.model = '10G EPON ONU'
129 device.hardware_version = 'fa161020'
130 device.firmware_version = '16.12.02'
131 device.software_version = '1.0'
132 device.serial_number = uuid4().hex
133 device.connect_status = ConnectStatus.REACHABLE
134 self.adapter_agent.update_device(device)
135
136 # then shortly after we create some ports for the device
137 uni_port = Port(
138 port_no=2,
139 label='UNI facing Ethernet port',
140 type=Port.ETHERNET_UNI,
141 admin_state=AdminState.ENABLED,
142 oper_status=OperStatus.ACTIVE
143 )
144 self.adapter_agent.add_port(device.id, uni_port)
145 self.adapter_agent.add_port(device.id, Port(
146 port_no=1,
147 label='PON port',
148 type=Port.PON_ONU,
149 admin_state=AdminState.ENABLED,
150 oper_status=OperStatus.ACTIVE,
151 peers=[
152 Port.PeerPort(
153 device_id=device.parent_id,
154 port_no=device.parent_port_no
155 )
156 ]
157 ))
158
159 # TODO adding vports to the logical device shall be done by agent?
160 # then we create the logical device port that corresponds to the UNI
161 # port of the device
162
163 # obtain logical device id
164 parent_device = self.adapter_agent.get_device(device.parent_id)
165 logical_device_id = parent_device.parent_id
166 assert logical_device_id
167
168 # we are going to use the proxy_address.channel_id as unique number
169 # and name for the virtual ports, as this is guaranteed to be unique
170 # in the context of the OLT port, so it is also unique in the context
171 # of the logical device
172 port_no = device.proxy_address.channel_id
173 cap = OFPPF_10GB_FD | OFPPF_FIBER
174 self.adapter_agent.add_logical_port(logical_device_id, LogicalPort(
175 id=str(port_no),
176 ofp_port=ofp_port(
177 port_no=port_no,
178 hw_addr=mac_str_to_tuple(device.mac_address),
179 name='uni-{}'.format(port_no),
180 config=0,
181 state=OFPPS_LIVE,
182 curr=cap,
183 advertised=cap,
184 peer=cap,
185 curr_speed=OFPPF_10GB_FD,
186 max_speed=OFPPF_10GB_FD
187 ),
188 device_id=device.id,
189 device_port_no=uni_port.port_no
190 ))
191
192 # simulate a proxied message sending and receving a reply
193 reply = yield self._message_exchange(device)
194
195 # and finally update to "ACTIVE"
196 device = self.adapter_agent.get_device(device.id)
197 device.oper_status = OperStatus.ACTIVE
198 self.adapter_agent.update_device(device)
199
200 def abandon_device(self, device):
201 raise NotImplementedError(0
202 )
Khen Nursimulud068d812017-03-06 11:44:18 -0500203 def disable_device(self, device):
204 raise NotImplementedError()
205
206 def reenable_device(self, device):
207 raise NotImplementedError()
208
209 def reboot_device(self, device):
210 raise NotImplementedError()
211
212 def delete_device(self, device):
213 raise NotImplementedError()
214
215 def get_device_details(self, device):
Nathan Knuthab966e52017-01-30 07:48:13 -0800216 raise NotImplementedError()
217
218 def update_flows_bulk(self, device, flows, groups):
219 log.info('########################################')
220 log.info('bulk-flow-update', device_id=device.id,
221 flows=flows, groups=groups)
222 assert len(groups.items) == 0, "Cannot yet deal with groups"
223
224 Clause = {v: k for k, v in ClauseSubtypeEnum.iteritems()}
225 Operator = {v: k for k, v in RuleOperatorEnum.iteritems()}
226
227 for flow in flows.items:
228 in_port = get_in_port(flow)
229 assert in_port is not None
230
231 precedence = 255 - min(flow.priority / 256, 255)
232
233 if in_port == 2:
234 log.info('#### Downstream Rule ####')
235 dn_req = EOAMPayload(body=CablelabsOUI() /
236 DPoEOpcode_SetRequest())
237
238 for field in get_ofb_fields(flow):
239
240 if field.type == ETH_TYPE:
241 _type = field.eth_type
242 log.info('#### field.type == ETH_TYPE ####',field_type=_type)
243
244 elif field.type == IP_PROTO:
245 _proto = field.ip_proto
246 log.info('#### field.type == IP_PROTO ####')
247 pass # construct ip_proto based condition here
248
249 elif field.type == IN_PORT:
250 _port = field.port
251 log.info('#### field.type == IN_PORT ####', port=_port)
252 pass # construct in_port based condition here
253
254 elif field.type == VLAN_VID:
255 _vlan_vid = field.vlan_vid & 0xfff
256 log.info('#### field.type == VLAN_VID ####', vlan=_vlan_vid)
257
258 elif field.type == VLAN_PCP:
259 _vlan_pcp = field.vlan_pcp
260 log.info('#### field.type == VLAN_PCP ####', pcp=_vlan_pcp)
261 pass # construct VLAN PCP based filter condition here
262
263 elif field.type == UDP_DST:
264 _udp_dst = field.udp_dst
265 log.info('#### field.type == UDP_DST ####')
266 pass # construct UDP SDT based filter here
267
268 elif field.type == IPV4_DST:
269 _ipv4_dst = field.ipv4_dst
270 log.info('#### field.type == IPV4_DST ####')
271 pass
272
273 else:
274 log.info('#### field.type == NOT IMPLEMENTED!! ####')
275 raise NotImplementedError('field.type={}'.format(
276 field.type))
277
278 for action in get_actions(flow):
279
280 if action.type == OUTPUT:
281 log.info('#### action.type == OUTPUT ####')
282
283 elif action.type == POP_VLAN:
284 log.info('#### action.type == POP_VLAN ####')
285 pass # construct vlan pop command here
286
287 elif action.type == PUSH_VLAN:
288 log.info('#### action.type == PUSH_VLAN ####')
289 if action.push.ethertype != 0x8100:
290 log.error('unhandled-tpid',
291 ethertype=action.push.ethertype)
292
293 elif action.type == SET_FIELD:
294 log.info('#### action.type == SET_FIELD ####')
295 assert (action.set_field.field.oxm_class ==
296 ofp.OFPXMC_OPENFLOW_BASIC)
297 field = action.set_field.field.ofb_field
298 if field.type == VLAN_VID:
299 pass
300 else:
301 log.error('unsupported-action-set-field-type',
302 field_type=field.type)
303 else:
304 log.error('UNSUPPORTED-ACTION-TYPE',
305 action_type=action.type)
306
307 # send message
308 log.info('ONU-send-proxied-message')
309# self.adapter_agent.send_proxied_message(device.proxy_address, dn_req)
310
311
312 elif in_port == 1:
313 # Upstream rule
314 log.info('#### Upstream Rule ####')
315
316 #### Loop through fields again...
317
318 for field in get_ofb_fields(flow):
319
320 if field.type == ETH_TYPE:
321 _type = field.eth_type
322 log.info('#### field.type == ETH_TYPE ####', in_port=in_port,
323 match=_type)
324
325 elif field.type == IP_PROTO:
326 _proto = field.ip_proto
327 log.info('#### field.type == IP_PROTO ####', in_port=in_port,
328 ip_proto=ip_proto)
329
330 elif field.type == IN_PORT:
331 _port = field.port
332 log.info('#### field.type == IN_PORT ####')
333 pass # construct in_port based condition here
334
335 elif field.type == VLAN_VID:
336 _vlan_vid = field.vlan_vid & 0xfff
337 log.info('#### field.type == VLAN_VID ####')
338
339 elif field.type == VLAN_PCP:
340 _vlan_pcp = field.vlan_pcp
341 log.info('#### field.type == VLAN_PCP ####')
342 pass # construct VLAN PCP based filter condition here
343
344 elif field.type == UDP_DST:
345 _udp_dst = field.udp_dst
346 log.info('#### field.type == UDP_DST ####')
347
348 else:
349 raise NotImplementedError('field.type={}'.format(
350 field.type))
351
352 for action in get_actions(flow):
353
354 if action.type == OUTPUT:
355 log.info('#### action.type == OUTPUT ####')
356
357 elif action.type == POP_VLAN:
358 log.info('#### action.type == POP_VLAN ####')
359 pass # construct vlan pop command here
360
361 elif action.type == PUSH_VLAN:
362 log.info('#### action.type == PUSH_VLAN ####')
363 if action.push.ethertype != 0x8100:
364 log.error('unhandled-ether-type',
365 ethertype=action.push.ethertype)
366
367 elif action.type == SET_FIELD:
368 log.info('#### action.type == SET_FIELD ####')
369 assert (action.set_field.field.oxm_class ==
370 ofp.OFPXMC_OPENFLOW_BASIC)
371 field = action.set_field.field.ofb_field
372 if field.type == VLAN_VID:
373 pass
374 else:
375 log.error('unsupported-action-set-field-type',
376 field_type=field.type)
377
378 else:
379 log.error('UNSUPPORTED-ACTION-TYPE',
380 action_type=action.type)
381
382 else:
383 raise Exception('Port should be 1 or 2 by our convention')
384
385 def update_flows_incrementally(self, device, flow_changes, group_changes):
386 raise NotImplementedError()
387
388 def send_proxied_message(self, proxy_address, msg):
389 raise NotImplementedError()
390
391 def receive_proxied_message(self, proxy_address, msg):
392 log.debug('receive-proxied-message',
393 proxy_address=proxy_address, msg=msg)
394 self.incoming_messages.put(msg)
395
396 @inlineCallbacks
397 def _message_exchange(self, device):
398
399 # register for receiving async messages
400 self.adapter_agent.register_for_proxied_messages(device.proxy_address)
401
402 # reset incoming message queue
403 while self.incoming_messages.pending:
404 _ = yield self.incoming_messages.get()
405
406 # construct message
407 msg = EOAMPayload(body=CablelabsOUI() /
408 DPoEOpcode_GetRequest() /
409 DeviceId()
410 )
411
412 # send message
413 log.info('ONU-send-proxied-message')
414 self.adapter_agent.send_proxied_message(device.proxy_address, msg)
415
416 # wait till we detect incoming message
417 yield self.incoming_messages.get()
418
419 # construct install of igmp query address
420 msg = EOAMPayload(body=CablelabsOUI() /
421 DPoEOpcode_SetRequest() /
422 AddStaticMacAddress(mac='01:00:5e:00:00:01')
423 )
424
425 # send message
426 log.info('ONU-send-proxied-message')
427 self.adapter_agent.send_proxied_message(device.proxy_address, msg)
428
429 # wait till we detect incoming message
430 yield self.incoming_messages.get()
431
432 # construct install of igmp query address
433 msg = EOAMPayload(body=CablelabsOUI() /
434 DPoEOpcode_GetRequest() /
435 FirmwareInfo()
436 )
437
438 # send message
439 log.info('ONU-send-proxied-message')
440 self.adapter_agent.send_proxied_message(device.proxy_address, msg)
441
442 # wait till we detect incoming message
443 yield self.incoming_messages.get()
444
445 # by returning we allow the device to be shown as active, which
446 # indirectly verified that message passing works
447
448 def receive_packet_out(self, logical_device_id, egress_port_no, msg):
449 log.info('packet-out', logical_device_id=logical_device_id,
450 egress_port_no=egress_port_no, msg_len=len(msg))
Peter Shafik9107f2e2017-05-02 15:54:39 -0400451
452 def receive_inter_adapter_message(self, msg):
453 raise NotImplementedError()