blob: 72a6f2a36a8d17b73c8bc00536f567c995a73a29 [file] [log] [blame]
Zsolt Harasztied091602016-12-08 13:36:38 -08001#
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -08002# Copyright 2017 the original author or authors.
Zsolt Harasztied091602016-12-08 13:36:38 -08003#
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 Harasztic5f740b2017-01-18 09:53:17 -080025import arrow
Zsolt Harasztied091602016-12-08 13:36:38 -080026import structlog
Zsolt Harasztic5f740b2017-01-18 09:53:17 -080027from twisted.internet.task import LoopingCall
Zsolt Harasztied091602016-12-08 13:36:38 -080028from zope.interface import implementer
29
Zsolt Haraszti348d1932016-12-10 01:10:07 -080030from scapy.layers.inet import ICMP, IP
31from scapy.layers.l2 import Ether
32from twisted.internet.defer import DeferredQueue, inlineCallbacks
33from twisted.internet import reactor
34
Zsolt Haraszti85f12852016-12-24 08:30:58 -080035from voltha.core.flow_decomposer import *
Zsolt Haraszti348d1932016-12-10 01:10:07 -080036from voltha.core.logical_device_agent import mac_str_to_tuple
Nathan Knutha1a11932017-01-12 16:59:58 -080037from common.frameio.frameio import BpfProgramFilter, hexify
Zsolt Harasztied091602016-12-08 13:36:38 -080038from voltha.adapters.interface import IAdapterInterface
39from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
Zsolt Haraszti348d1932016-12-10 01:10:07 -080040from voltha.protos.device_pb2 import Port
Zsolt Harasztied091602016-12-08 13:36:38 -080041from voltha.protos.device_pb2 import DeviceType, DeviceTypes
Zsolt Harasztic5f740b2017-01-18 09:53:17 -080042from voltha.protos.events_pb2 import KpiEventType
43from voltha.protos.events_pb2 import MetricValuePairs, KpiEvent
Zsolt Harasztied091602016-12-08 13:36:38 -080044from voltha.protos.health_pb2 import HealthStatus
Zsolt Haraszti348d1932016-12-10 01:10:07 -080045from voltha.protos.common_pb2 import LogLevel, ConnectStatus
Zsolt Haraszti348d1932016-12-10 01:10:07 -080046from voltha.protos.common_pb2 import OperStatus, AdminState
47
48from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
49from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_10GB_FD, \
50 OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
51 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
52
53from scapy.packet import Packet, bind_layers
54from scapy.fields import StrField
Zsolt Harasztied091602016-12-08 13:36:38 -080055
56log = structlog.get_logger()
57
Nathan Knuth31c36962016-12-27 10:04:49 -080058from voltha.extensions.eoam.EOAM_TLV import AddStaticMacAddress, DeleteStaticMacAddress
59from voltha.extensions.eoam.EOAM_TLV import ClearStaticMacTable
60from voltha.extensions.eoam.EOAM_TLV import DeviceId
Nathan Knuthd8285e62017-01-11 14:18:43 -060061from voltha.extensions.eoam.EOAM_TLV import ClauseSubtypeEnum
62from voltha.extensions.eoam.EOAM_TLV import RuleOperatorEnum
63
Nathan Knuth31c36962016-12-27 10:04:49 -080064from voltha.extensions.eoam.EOAM import EOAMPayload, CablelabsOUI
65from voltha.extensions.eoam.EOAM import DPoEOpcode_GetRequest, DPoEOpcode_SetRequest
Nathan Knuth5f4163e2017-01-11 18:21:10 -060066from voltha.extensions.eoam.EOAM import mcastIp2McastMac
Zsolt Harasztied091602016-12-08 13:36:38 -080067
68@implementer(IAdapterInterface)
69class TibitOnuAdapter(object):
70
71 name = 'tibit_onu'
72
73 supported_device_types = [
74 DeviceType(
75 id='tibit_onu',
76 adapter=name,
77 accepts_bulk_flow_update=True
78 )
79 ]
80
81 def __init__(self, adapter_agent, config):
82 self.adapter_agent = adapter_agent
83 self.config = config
84 self.descriptor = Adapter(
85 id=self.name,
86 vendor='Tibit Communications Inc.',
87 version='0.1',
88 config=AdapterConfig(log_level=LogLevel.INFO)
89 )
Zsolt Haraszti348d1932016-12-10 01:10:07 -080090 self.incoming_messages = DeferredQueue()
Zsolt Harasztied091602016-12-08 13:36:38 -080091
92 def start(self):
93 log.debug('starting')
94 log.info('started')
95
96 def stop(self):
97 log.debug('stopping')
98 log.info('stopped')
99
100 def adapter_descriptor(self):
101 return self.descriptor
102
103 def device_types(self):
104 return DeviceTypes(items=self.supported_device_types)
105
106 def health(self):
107 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
108
109 def change_master_state(self, master):
110 raise NotImplementedError()
111
Sergio Slobodrianec864c62017-03-09 11:41:43 -0500112 def update_pm_config(self, device, pm_configs):
113 raise NotImplementedError()
114
Zsolt Harasztied091602016-12-08 13:36:38 -0800115 def adopt_device(self, device):
116 log.info('adopt-device', device=device)
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800117 reactor.callLater(0.1, self._onu_device_activation, device)
Zsolt Harasztied091602016-12-08 13:36:38 -0800118 return device
119
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800120 @inlineCallbacks
121 def _onu_device_activation(self, device):
122 # first we verify that we got parent reference and proxy info
123 assert device.parent_id
124 assert device.proxy_address.device_id
125 assert device.proxy_address.channel_id
126
127 # TODO: For now, pretend that we were able to contact the device and obtain
128 # additional information about it. Should add real message.
129 device.vendor = 'Tibit Communications, Inc.'
130 device.model = '10G GPON ONU'
131 device.hardware_version = 'fa161020'
Nathan Knuthd8285e62017-01-11 14:18:43 -0600132 device.firmware_version = '16.12.02'
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800133 device.software_version = '1.0'
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800134 device.serial_number = uuid4().hex
135 device.connect_status = ConnectStatus.REACHABLE
136 self.adapter_agent.update_device(device)
137
138 # then shortly after we create some ports for the device
139 uni_port = Port(
140 port_no=2,
141 label='UNI facing Ethernet port',
142 type=Port.ETHERNET_UNI,
143 admin_state=AdminState.ENABLED,
144 oper_status=OperStatus.ACTIVE
145 )
146 self.adapter_agent.add_port(device.id, uni_port)
147 self.adapter_agent.add_port(device.id, Port(
148 port_no=1,
149 label='PON port',
150 type=Port.PON_ONU,
151 admin_state=AdminState.ENABLED,
152 oper_status=OperStatus.ACTIVE,
153 peers=[
154 Port.PeerPort(
155 device_id=device.parent_id,
156 port_no=device.parent_port_no
157 )
158 ]
159 ))
160
161 # TODO adding vports to the logical device shall be done by agent?
162 # then we create the logical device port that corresponds to the UNI
163 # port of the device
164
165 # obtain logical device id
166 parent_device = self.adapter_agent.get_device(device.parent_id)
167 logical_device_id = parent_device.parent_id
168 assert logical_device_id
169
170 # we are going to use the proxy_address.channel_id as unique number
171 # and name for the virtual ports, as this is guaranteed to be unique
172 # in the context of the OLT port, so it is also unique in the context
173 # of the logical device
174 port_no = device.proxy_address.channel_id
175 cap = OFPPF_10GB_FD | OFPPF_FIBER
176 self.adapter_agent.add_logical_port(logical_device_id, LogicalPort(
177 id=str(port_no),
178 ofp_port=ofp_port(
179 port_no=port_no,
180 hw_addr=mac_str_to_tuple(device.mac_address),
181 name='uni-{}'.format(port_no),
182 config=0,
183 state=OFPPS_LIVE,
184 curr=cap,
185 advertised=cap,
186 peer=cap,
187 curr_speed=OFPPF_10GB_FD,
188 max_speed=OFPPF_10GB_FD
189 ),
190 device_id=device.id,
191 device_port_no=uni_port.port_no
192 ))
193
Nathan Knutha1a11932017-01-12 16:59:58 -0800194
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800195 # simulate a proxied message sending and receving a reply
196 reply = yield self._message_exchange(device)
197
198 # and finally update to "ACTIVE"
199 device = self.adapter_agent.get_device(device.id)
200 device.oper_status = OperStatus.ACTIVE
201 self.adapter_agent.update_device(device)
202
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800203 self.start_kpi_collection(device.id)
204
Zsolt Harasztied091602016-12-08 13:36:38 -0800205 def abandon_device(self, device):
206 raise NotImplementedError(0
207 )
Khen Nursimulud068d812017-03-06 11:44:18 -0500208 def disable_device(self, device):
209 raise NotImplementedError()
210
211 def reenable_device(self, device):
212 raise NotImplementedError()
213
214 def reboot_device(self, device):
215 raise NotImplementedError()
216
217 def delete_device(self, device):
218 raise NotImplementedError()
219
220 def get_device_details(self, device):
Zsolt Harasztied091602016-12-08 13:36:38 -0800221 raise NotImplementedError()
222
223 def update_flows_bulk(self, device, flows, groups):
Nathan Knuthd8285e62017-01-11 14:18:43 -0600224 log.info('########################################')
225 log.info('bulk-flow-update', device_id=device.id,
226 flows=flows, groups=groups)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800227 assert len(groups.items) == 0, "Cannot yet deal with groups"
228
Nathan Knuthd8285e62017-01-11 14:18:43 -0600229 Clause = {v: k for k, v in ClauseSubtypeEnum.iteritems()}
230 Operator = {v: k for k, v in RuleOperatorEnum.iteritems()}
231
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800232 for flow in flows.items:
233 in_port = get_in_port(flow)
234 assert in_port is not None
235
Nathan Knuthd8285e62017-01-11 14:18:43 -0600236 precedence = 255 - min(flow.priority / 256, 255)
237
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800238 if in_port == 2:
Nathan Knuth5f4163e2017-01-11 18:21:10 -0600239 log.info('#### Upstream Rule ####')
Nathan Knuthd8285e62017-01-11 14:18:43 -0600240 dn_req = EOAMPayload(body=CablelabsOUI() /
241 DPoEOpcode_SetRequest())
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800242
243 for field in get_ofb_fields(flow):
Nathan Knuthd8285e62017-01-11 14:18:43 -0600244
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800245 if field.type == ETH_TYPE:
246 _type = field.eth_type
Nathan Knuthd8285e62017-01-11 14:18:43 -0600247 log.info('#### field.type == ETH_TYPE ####',field_type=_type)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800248
249 elif field.type == IP_PROTO:
250 _proto = field.ip_proto
Nathan Knuthd8285e62017-01-11 14:18:43 -0600251 log.info('#### field.type == IP_PROTO ####')
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800252
253 elif field.type == IN_PORT:
254 _port = field.port
Nathan Knuthd8285e62017-01-11 14:18:43 -0600255 log.info('#### field.type == IN_PORT ####', port=_port)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800256
257 elif field.type == VLAN_VID:
Nathan Knuthd8285e62017-01-11 14:18:43 -0600258 _vlan_vid = field.vlan_vid & 0xfff
259 log.info('#### field.type == VLAN_VID ####', vlan=_vlan_vid)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800260
261 elif field.type == VLAN_PCP:
262 _vlan_pcp = field.vlan_pcp
Nathan Knuthd8285e62017-01-11 14:18:43 -0600263 log.info('#### field.type == VLAN_PCP ####', pcp=_vlan_pcp)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800264
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800265 elif field.type == UDP_DST:
266 _udp_dst = field.udp_dst
Nathan Knuthd8285e62017-01-11 14:18:43 -0600267 log.info('#### field.type == UDP_DST ####')
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800268
Nathan Knuthd8285e62017-01-11 14:18:43 -0600269 elif field.type == IPV4_DST:
270 _ipv4_dst = field.ipv4_dst
Nathan Knuthd8285e62017-01-11 14:18:43 -0600271 log.info('#### field.type == IPV4_DST ####')
Nathan Knuthd8285e62017-01-11 14:18:43 -0600272
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 ####')
Nathan Knuthd8285e62017-01-11 14:18:43 -0600285
286 elif action.type == PUSH_VLAN:
287 log.info('#### action.type == PUSH_VLAN ####')
288 if action.push.ethertype != 0x8100:
289 log.error('unhandled-tpid',
290 ethertype=action.push.ethertype)
291
292 elif action.type == SET_FIELD:
293 log.info('#### action.type == SET_FIELD ####')
294 assert (action.set_field.field.oxm_class ==
295 ofp.OFPXMC_OPENFLOW_BASIC)
296 field = action.set_field.field.ofb_field
297 if field.type == VLAN_VID:
298 pass
299 else:
300 log.error('unsupported-action-set-field-type',
301 field_type=field.type)
302 else:
303 log.error('UNSUPPORTED-ACTION-TYPE',
304 action_type=action.type)
305
Nathan Knuthd8285e62017-01-11 14:18:43 -0600306 elif in_port == 1:
Nathan Knuth5f4163e2017-01-11 18:21:10 -0600307 log.info('#### Downstream Rule ####')
Nathan Knuthd8285e62017-01-11 14:18:43 -0600308
309 #### Loop through fields again...
310
311 for field in get_ofb_fields(flow):
312
313 if field.type == ETH_TYPE:
314 _type = field.eth_type
315 log.info('#### field.type == ETH_TYPE ####', in_port=in_port,
316 match=_type)
317
318 elif field.type == IP_PROTO:
319 _proto = field.ip_proto
320 log.info('#### field.type == IP_PROTO ####', in_port=in_port,
321 ip_proto=ip_proto)
322
323 elif field.type == IN_PORT:
324 _port = field.port
325 log.info('#### field.type == IN_PORT ####')
Nathan Knuthd8285e62017-01-11 14:18:43 -0600326
327 elif field.type == VLAN_VID:
328 _vlan_vid = field.vlan_vid & 0xfff
329 log.info('#### field.type == VLAN_VID ####')
330
331 elif field.type == VLAN_PCP:
332 _vlan_pcp = field.vlan_pcp
333 log.info('#### field.type == VLAN_PCP ####')
Nathan Knuthd8285e62017-01-11 14:18:43 -0600334
335 elif field.type == UDP_DST:
336 _udp_dst = field.udp_dst
337 log.info('#### field.type == UDP_DST ####')
338
Nathan Knuth5f4163e2017-01-11 18:21:10 -0600339 elif field.type == IPV4_DST:
340 _ipv4_dst = field.ipv4_dst
341 log.info('#### field.type == IPV4_DST ####')
342 a = int(hex(_ipv4_dst)[2:4], 16)
343 b = int(hex(_ipv4_dst)[4:6], 16)
344 c = int(hex(_ipv4_dst)[6:8], 16)
345 d = int(hex(_ipv4_dst)[8:], 16)
346 dn_req = EOAMPayload(body=CablelabsOUI() /
347 DPoEOpcode_SetRequest() /
348 AddStaticMacAddress(
349 mac=mcastIp2McastMac('%d.%d.%d.%d' % (a,b,c,d)))
350 )
351 # send message
352 log.info('ONU-send-proxied-message')
353 self.adapter_agent.send_proxied_message(device.proxy_address,
354 dn_req)
355
356
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800357 else:
358 raise NotImplementedError('field.type={}'.format(
359 field.type))
360
361 for action in get_actions(flow):
362
363 if action.type == OUTPUT:
Nathan Knuthd8285e62017-01-11 14:18:43 -0600364 log.info('#### action.type == OUTPUT ####')
365
366 elif action.type == POP_VLAN:
367 log.info('#### action.type == POP_VLAN ####')
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800368
369 elif action.type == PUSH_VLAN:
Nathan Knuthd8285e62017-01-11 14:18:43 -0600370 log.info('#### action.type == PUSH_VLAN ####')
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800371 if action.push.ethertype != 0x8100:
372 log.error('unhandled-ether-type',
373 ethertype=action.push.ethertype)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800374
375 elif action.type == SET_FIELD:
Nathan Knuthd8285e62017-01-11 14:18:43 -0600376 log.info('#### action.type == SET_FIELD ####')
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800377 assert (action.set_field.field.oxm_class ==
378 ofp.OFPXMC_OPENFLOW_BASIC)
379 field = action.set_field.field.ofb_field
380 if field.type == VLAN_VID:
Nathan Knuthd8285e62017-01-11 14:18:43 -0600381 pass
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800382 else:
383 log.error('unsupported-action-set-field-type',
384 field_type=field.type)
385
386 else:
Nathan Knuthd8285e62017-01-11 14:18:43 -0600387 log.error('UNSUPPORTED-ACTION-TYPE',
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800388 action_type=action.type)
389
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800390 else:
391 raise Exception('Port should be 1 or 2 by our convention')
392
Zsolt Harasztied091602016-12-08 13:36:38 -0800393 def update_flows_incrementally(self, device, flow_changes, group_changes):
394 raise NotImplementedError()
395
396 def send_proxied_message(self, proxy_address, msg):
397 raise NotImplementedError()
398
399 def receive_proxied_message(self, proxy_address, msg):
Nathan Knutha1a11932017-01-12 16:59:58 -0800400 log.info('receive-proxied-message',
401 proxy_address=proxy_address, msg=msg.show(dump=True))
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800402 self.incoming_messages.put(msg)
403
404 @inlineCallbacks
405 def _message_exchange(self, device):
406
407 # register for receiving async messages
408 self.adapter_agent.register_for_proxied_messages(device.proxy_address)
409
410 # reset incoming message queue
411 while self.incoming_messages.pending:
412 _ = yield self.incoming_messages.get()
413
414 # construct message
Nathan Knuth6e57f332016-12-22 15:49:20 -0800415 msg = EOAMPayload(body=CablelabsOUI() /
416 DPoEOpcode_GetRequest() /
417 DeviceId()
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800418 )
419
420 # send message
Nathan Knuth6e57f332016-12-22 15:49:20 -0800421 log.info('ONU-send-proxied-message')
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800422 self.adapter_agent.send_proxied_message(device.proxy_address, msg)
423
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800424 # wait till we detect incoming message
425 yield self.incoming_messages.get()
Nathan Knuthd8285e62017-01-11 14:18:43 -0600426
427 # construct install of igmp query address
428 msg = EOAMPayload(body=CablelabsOUI() /
429 DPoEOpcode_SetRequest() /
430 AddStaticMacAddress(mac='01:00:5e:00:00:01')
431 )
432
433 # send message
434 log.info('ONU-send-proxied-message')
435 self.adapter_agent.send_proxied_message(device.proxy_address, msg)
436
437 # wait till we detect incoming message
438 yield self.incoming_messages.get()
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800439
440 # by returning we allow the device to be shown as active, which
441 # indirectly verified that message passing works
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800442
443 def receive_packet_out(self, logical_device_id, egress_port_no, msg):
444 log.info('packet-out', logical_device_id=logical_device_id,
445 egress_port_no=egress_port_no, msg_len=len(msg))
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800446
447 def start_kpi_collection(self, device_id):
448
449 """TMP Simulate periodic KPI metric collection from the device"""
450 import random
451
452 @inlineCallbacks # pretend that we need to do async calls
453 def _collect(device_id, prefix):
454
455 try:
456 # Step 1: gather metrics from device (pretend it here) - examples
457 uni_port_metrics = yield dict(
458 tx_pkts=random.randint(0, 100),
459 rx_pkts=random.randint(0, 100),
460 tx_bytes=random.randint(0, 100000),
461 rx_bytes=random.randint(0, 100000),
462 )
463 pon_port_metrics = yield dict(
464 tx_pkts=uni_port_metrics['rx_pkts'],
465 rx_pkts=uni_port_metrics['tx_pkts'],
466 tx_bytes=uni_port_metrics['rx_bytes'],
467 rx_bytes=uni_port_metrics['tx_bytes'],
468 )
469 onu_metrics = yield dict(
470 cpu_util=20 + 5 * random.random(),
471 buffer_util=10 + 10 * random.random()
472 )
473
474 # Step 2: prepare the KpiEvent for submission
475 # we can time-stamp them here (or could use time derived from OLT
476 ts = arrow.utcnow().timestamp
477 kpi_event = KpiEvent(
478 type=KpiEventType.slice,
479 ts=ts,
480 prefixes={
481 # OLT-level
482 prefix: MetricValuePairs(metrics=onu_metrics),
483 # OLT NNI port
484 prefix + '.nni': MetricValuePairs(metrics=uni_port_metrics),
485 # OLT PON port
486 prefix + '.pon': MetricValuePairs(metrics=pon_port_metrics)
487 }
488 )
489
490 # Step 3: submit
491 self.adapter_agent.submit_kpis(kpi_event)
492
493 except Exception as e:
494 log.exception('failed-to-submit-kpis', e=e)
495
496 prefix = 'voltha.{}.{}'.format(self.name, device_id)
497 lc = LoopingCall(_collect, device_id, prefix)
498 lc.start(interval=15) # TODO make this configurable
499