blob: b2b3f5bc855f4f2071a957e37919d758694b5461 [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 OLT device adapter
19"""
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080020import json
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080021from uuid import uuid4
22
Zsolt Haraszti80175202016-12-24 00:17:51 -080023import structlog
24from scapy.fields import StrField
Zsolt Haraszti348d1932016-12-10 01:10:07 -080025from scapy.layers.l2 import Ether, Dot1Q
Zsolt Haraszti80175202016-12-24 00:17:51 -080026from scapy.packet import Packet, bind_layers
Zsolt Haraszti89a27302016-12-08 16:53:06 -080027from twisted.internet import reactor
Zsolt Haraszti80175202016-12-24 00:17:51 -080028from twisted.internet.defer import DeferredQueue, inlineCallbacks
Zsolt Harasztied091602016-12-08 13:36:38 -080029from zope.interface import implementer
30
Zsolt Haraszti89a27302016-12-08 16:53:06 -080031from common.frameio.frameio import BpfProgramFilter
Zsolt Harasztied091602016-12-08 13:36:38 -080032from voltha.adapters.interface import IAdapterInterface
Nathan Knuth31c36962016-12-27 10:04:49 -080033from voltha.extensions.eoam.EOAM import EOAMPayload, DPoEOpcode_SetRequest
34from voltha.extensions.eoam.EOAM_TLV import DOLTObject, \
Zsolt Haraszti80175202016-12-24 00:17:51 -080035 PortIngressRuleClauseMatchLength02, PortIngressRuleResultForward, \
36 PortIngressRuleResultSet, PortIngressRuleResultInsert, \
37 PortIngressRuleTerminator, AddPortIngressRule, CablelabsOUI
Nathan Knuth31c36962016-12-27 10:04:49 -080038from voltha.extensions.eoam.EOAM_TLV import PortIngressRuleHeader
39from voltha.extensions.eoam.EOAM_TLV import ClauseSubtypeEnum as Clause
Zsolt Haraszti80175202016-12-24 00:17:51 -080040from voltha.core.flow_decomposer import *
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080041from voltha.core.logical_device_agent import mac_str_to_tuple
Zsolt Harasztied091602016-12-08 13:36:38 -080042from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
Zsolt Haraszti80175202016-12-24 00:17:51 -080043from voltha.protos.common_pb2 import LogLevel, ConnectStatus
44from voltha.protos.common_pb2 import OperStatus, AdminState
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080045from voltha.protos.device_pb2 import Device, Port
Zsolt Harasztied091602016-12-08 13:36:38 -080046from voltha.protos.device_pb2 import DeviceType, DeviceTypes
47from voltha.protos.health_pb2 import HealthStatus
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080048from 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
Zsolt Haraszti80175202016-12-24 00:17:51 -080052from voltha.registry import registry
Zsolt Haraszti348d1932016-12-10 01:10:07 -080053log = structlog.get_logger()
Zsolt Harasztied091602016-12-08 13:36:38 -080054
Nathan Knuth6e57f332016-12-22 15:49:20 -080055# Match on the MGMT VLAN, Priority 7
56TIBIT_MGMT_VLAN=4090
57TIBIT_MGMT_PRIORITY=7
58frame_match = 'ether[14:2] = 0x{:01x}{:03x}'.format(TIBIT_MGMT_PRIORITY << 1, TIBIT_MGMT_VLAN)
59is_tibit_frame = BpfProgramFilter(frame_match)
60#is_tibit_frame = lambda x: True
Zsolt Haraszti89a27302016-12-08 16:53:06 -080061
Zsolt Haraszti85f12852016-12-24 08:30:58 -080062
Zsolt Haraszti348d1932016-12-10 01:10:07 -080063# To be removed in favor of OAM
Zsolt Haraszti89a27302016-12-08 16:53:06 -080064class TBJSON(Packet):
65 """ TBJSON 'packet' layer. """
66 name = "TBJSON"
67 fields_desc = [StrField("data", default="")]
68
Nathan Knuth6e57f332016-12-22 15:49:20 -080069bind_layers(Ether, TBJSON, type=0x9001)
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080070
Zsolt Haraszti85f12852016-12-24 08:30:58 -080071
Zsolt Harasztied091602016-12-08 13:36:38 -080072@implementer(IAdapterInterface)
73class TibitOltAdapter(object):
74
75 name = 'tibit_olt'
76
77 supported_device_types = [
78 DeviceType(
79 id='tibit_olt',
80 adapter=name,
81 accepts_bulk_flow_update=True
82 )
83 ]
84
85 def __init__(self, adapter_agent, config):
86 self.adapter_agent = adapter_agent
87 self.config = config
88 self.descriptor = Adapter(
89 id=self.name,
90 vendor='Tibit Communications Inc.',
91 version='0.1',
92 config=AdapterConfig(log_level=LogLevel.INFO)
93 )
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080094 self.interface = registry('main').get_args().interface
Zsolt Haraszti89a27302016-12-08 16:53:06 -080095 self.io_port = None
Nathan Knuth6e57f332016-12-22 15:49:20 -080096 self.incoming_queues = {} # OLT mac_address -> DeferredQueue()
97 self.device_ids = {} # OLT mac_address -> device_id
Zsolt Harasztied091602016-12-08 13:36:38 -080098
99 def start(self):
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -0800100 log.debug('starting', interface=self.interface)
101 log.info('started', interface=self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -0800102
103 def stop(self):
104 log.debug('stopping')
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800105 if self.io_port is not None:
106 registry('frameio').del_interface(self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -0800107 log.info('stopped')
108
109 def adapter_descriptor(self):
110 return self.descriptor
111
112 def device_types(self):
113 return DeviceTypes(items=self.supported_device_types)
114
115 def health(self):
116 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
117
118 def change_master_state(self, master):
119 raise NotImplementedError()
120
121 def adopt_device(self, device):
122 log.info('adopt-device', device=device)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800123 self._activate_io_port()
124 reactor.callLater(0, self._launch_device_activation, device)
Zsolt Harasztied091602016-12-08 13:36:38 -0800125
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800126 def _activate_io_port(self):
127 if self.io_port is None:
128 self.io_port = registry('frameio').add_interface(
129 self.interface, self._rcv_io, is_tibit_frame)
130
131 @inlineCallbacks
132 def _launch_device_activation(self, device):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800133 try:
134 log.debug('launch_dev_activation')
135 # prepare receive queue
136 self.incoming_queues[device.mac_address] = DeferredQueue(size=100)
137
Nathan Knuth6e57f332016-12-22 15:49:20 -0800138 # add mac_address to device_ids table
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800139 olt_mac = device.mac_address
Nathan Knuth6e57f332016-12-22 15:49:20 -0800140 self.device_ids[olt_mac] = device.id
141
142 # send out ping to OLT device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800143 ping_frame = self._make_ping_frame(mac_address=olt_mac)
144 self.io_port.send(ping_frame)
145
146 # wait till we receive a response
Nathan Knuth6e57f332016-12-22 15:49:20 -0800147 ## TODO add timeout mechanism so we can signal if we cannot reach
148 ##device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800149 while True:
150 response = yield self.incoming_queues[olt_mac].get()
151 # verify response and if not the expected response
152 if 1: # TODO check if it is really what we expect, and wait if not
153 break
154
155 except Exception, e:
156 log.exception('launch device failed', e=e)
157
158 # if we got response, we can fill out the device info, mark the device
159 # reachable
Zsolt Haraszti80175202016-12-24 00:17:51 -0800160 # jdev = json.loads(response.data[5:])
161 jdev = json.loads(response.payload.payload.body.load)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800162 device.root = True
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800163 device.vendor = 'Tibit Communications, Inc.'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800164 device.model = jdev.get('results', {}).get('device', 'DEVICE_UNKNOWN')
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800165 device.hardware_version = jdev['results']['datecode']
166 device.firmware_version = jdev['results']['firmware']
167 device.software_version = jdev['results']['modelversion']
168 device.serial_number = jdev['results']['manufacturer']
Nathan Knuth6e57f332016-12-22 15:49:20 -0800169
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800170 device.connect_status = ConnectStatus.REACHABLE
171 self.adapter_agent.update_device(device)
172
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800173 # then shortly after we create some ports for the device
174 log.info('create-port')
175 nni_port = Port(
176 port_no=2,
177 label='NNI facing Ethernet port',
178 type=Port.ETHERNET_NNI,
179 admin_state=AdminState.ENABLED,
180 oper_status=OperStatus.ACTIVE
181 )
182 self.adapter_agent.add_port(device.id, nni_port)
183 self.adapter_agent.add_port(device.id, Port(
184 port_no=1,
185 label='PON port',
186 type=Port.PON_OLT,
187 admin_state=AdminState.ENABLED,
188 oper_status=OperStatus.ACTIVE
189 ))
190
191 log.info('create-logical-device')
192 # then shortly after we create the logical device with one port
193 # that will correspond to the NNI port
194 logical_device_id = uuid4().hex[:12]
195 ld = LogicalDevice(
196 id=logical_device_id,
197 datapath_id=int('0x' + logical_device_id[:8], 16), # from id
198 desc=ofp_desc(
199 mfr_desc=device.vendor,
200 hw_desc=jdev['results']['device'],
201 sw_desc=jdev['results']['firmware'],
202 serial_num=uuid4().hex,
203 dp_desc='n/a'
204 ),
205 switch_features=ofp_switch_features(
206 n_buffers=256, # TODO fake for now
207 n_tables=2, # TODO ditto
208 capabilities=( # TODO and ditto
209 OFPC_FLOW_STATS
210 | OFPC_TABLE_STATS
211 | OFPC_PORT_STATS
212 | OFPC_GROUP_STATS
213 )
214 ),
215 root_device_id=device.id
216 )
217 self.adapter_agent.create_logical_device(ld)
218 cap = OFPPF_10GB_FD | OFPPF_FIBER
219 self.adapter_agent.add_logical_port(ld.id, LogicalPort(
220 id='nni',
221 ofp_port=ofp_port(
222 port_no=129,
223 hw_addr=mac_str_to_tuple(device.mac_address),
224 name='nni',
225 config=0,
226 state=OFPPS_LIVE,
227 curr=cap,
228 advertised=cap,
229 peer=cap,
230 curr_speed=OFPPF_10GB_FD,
231 max_speed=OFPPF_10GB_FD
232 ),
233 device_id=device.id,
234 device_port_no=nni_port.port_no,
235 root_port=True
236 ))
237
238 # and finally update to active
239 device = self.adapter_agent.get_device(device.id)
240 device.parent_id = ld.id
241 device.oper_status = OperStatus.ACTIVE
242 self.adapter_agent.update_device(device)
243
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800244 # Just transitioned to ACTIVE, wait a tenth of second
245 # before checking for ONUs
246 reactor.callLater(0.1, self._detect_onus, device)
247
248 @inlineCallbacks
249 def _detect_onus(self, device):
250 # send out get 'links' to the OLT device
251 olt_mac = device.mac_address
252 links_frame = self._make_links_frame(mac_address=olt_mac)
253 self.io_port.send(links_frame)
254 while True:
255 response = yield self.incoming_queues[olt_mac].get()
256 # verify response and if not the expected response
257 if 1: # TODO check if it is really what we expect, and wait if not
258 break
259
Zsolt Haraszti80175202016-12-24 00:17:51 -0800260 jdev = json.loads(response.payload.payload.body.load)
Nathan Knuth6e57f332016-12-22 15:49:20 -0800261 tibit_mac = ''
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800262 for macid in jdev['results']:
263 if macid['macid'] is None:
264 log.info('MAC ID is NONE %s' % str(macid['macid']))
265 else:
Nathan Knuth6e57f332016-12-22 15:49:20 -0800266 tibit_mac = '000c' + macid.get('macid', 'e2000000')[4:]
267 log.info('activate-olt-for-onu-%s' % tibit_mac)
268
269 # Convert from string to colon separated form
270 tibit_mac = ':'.join(s.encode('hex') for s in tibit_mac.decode('hex'))
271
272 gemport, vlan_id = self._olt_side_onu_activation(int(macid['macid'][-4:-2], 16))
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800273 self.adapter_agent.child_device_detected(
274 parent_device_id=device.id,
275 parent_port_no=1,
276 child_device_type='tibit_onu',
Nathan Knuth6e57f332016-12-22 15:49:20 -0800277 mac_address = tibit_mac,
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800278 proxy_address=Device.ProxyAddress(
279 device_id=device.id,
280 channel_id=vlan_id
281 ),
282 vlan=vlan_id
283 )
284
Nathan Knuth6e57f332016-12-22 15:49:20 -0800285 def _olt_side_onu_activation(self, serial):
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800286 """
287 This is where if this was a real OLT, the OLT-side activation for
288 the new ONU should be performed. By the time we return, the OLT shall
289 be able to provide tunneled (proxy) communication to the given ONU,
290 using the returned information.
291 """
Nathan Knuth6e57f332016-12-22 15:49:20 -0800292 gemport = serial
293 vlan_id = serial + 200
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800294 return gemport, vlan_id
295
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800296 def _rcv_io(self, port, frame):
297
Nathan Knuth6e57f332016-12-22 15:49:20 -0800298 log.info('frame-received')
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800299
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800300 # make into frame to extract source mac
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800301 response = Ether(frame)
302
Nathan Knuth6e57f332016-12-22 15:49:20 -0800303 if response.haslayer(Dot1Q):
304 # All responses from the OLT should have a TIBIT_MGMT_VLAN.
305 # Responses from the ONUs should have a TIBIT_MGMT_VLAN followed by a ONU CTAG
306 if response.getlayer(Dot1Q).type == 0x8100:
307 ## Responses from the ONU
308 ## Since the type of the first layer is 0x8100,
309 ## then the frame must have an inner tag layer
310 olt_mac = response.src
311 device_id = self.device_ids[olt_mac]
312 channel_id = response[Dot1Q:2].vlan
313 log.info('received_channel_id', channel_id=channel_id,
314 device_id=device_id)
315
316 proxy_address=Device.ProxyAddress(
317 device_id=device_id,
318 channel_id=channel_id
319 )
320 # pop dot1q header(s)
321 msg = response.payload.payload
322 self.adapter_agent.receive_proxied_message(proxy_address, msg)
323 else:
324 ## Respones from the OLT
325 ## enqueue incoming parsed frame to right device
326 self.incoming_queues[response.src].put(response)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800327
328 def _make_ping_frame(self, mac_address):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800329 # Create a json packet
330 json_operation_str = '{\"operation\":\"version\"}'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800331 frame = Ether(dst=mac_address)/Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY)/TBJSON(data='json %s' % json_operation_str)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800332 return str(frame)
333
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800334 def _make_links_frame(self, mac_address):
335 # Create a json packet
336 json_operation_str = '{\"operation\":\"links\"}'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800337 frame = Ether(dst=mac_address)/Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY)/TBJSON(data='json %s' % json_operation_str)
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800338 return str(frame)
339
Zsolt Harasztied091602016-12-08 13:36:38 -0800340 def abandon_device(self, device):
341 raise NotImplementedError(0
342 )
343 def deactivate_device(self, device):
344 raise NotImplementedError()
345
346 def update_flows_bulk(self, device, flows, groups):
Zsolt Haraszti80175202016-12-24 00:17:51 -0800347 log.info('bulk-flow-update', device_id=device.id,
348 flows=flows, groups=groups)
349
350 assert len(groups.items) == 0, "Cannot yet deal with groups"
351
352 for flow in flows.items:
353 in_port = get_in_port(flow)
354 assert in_port is not None
355
356 precedence = 255 - min(flow.priority / 256, 255)
357
358 if in_port == 2:
359 # Downstream rule
360 pass # TODO still ignores
361
362 elif in_port == 1:
363 # Upstream rule
364 req = DOLTObject()
365 req /= PortIngressRuleHeader(precedence=precedence)
366
367 for field in get_ofb_fields(flow):
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800368
Zsolt Haraszti80175202016-12-24 00:17:51 -0800369 if field.type == ETH_TYPE:
370 _type = field.eth_type
371 req /= PortIngressRuleClauseMatchLength02(
372 fieldcode=3,
373 operator=1,
374 match0=(_type >> 8) & 0xff,
375 match1=_type & 0xff)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800376
Zsolt Haraszti80175202016-12-24 00:17:51 -0800377 elif field.type == IP_PROTO:
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800378 _proto = field.ip_proto
379 pass # construct ip_proto based condition here
380
381 elif field.type == IN_PORT:
382 _port = field.port
383 pass # construct in_port based condition here
384
385 elif field.type == VLAN_VID:
386 _vlan_vid = field.vlan_vid
387 pass # construct VLAN ID based filter condition here
388
389 elif field.type == VLAN_PCP:
390 _vlan_pcp = field.vlan_pcp
391 pass # construct VLAN PCP based filter condition here
392
393 elif field.type == UDP_DST:
394 _udp_dst = field.udp_dst
395 pass # construct UDP SDT based filter here
396
397 else:
398 raise NotImplementedError('field.type={}'.format(
399 field.type))
Zsolt Haraszti80175202016-12-24 00:17:51 -0800400
401 for action in get_actions(flow):
402
403 if action.type == OUTPUT:
404 req /= PortIngressRuleResultForward()
405
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800406 elif action.type == POP_VLAN:
407 pass # construct vlan pop command here
408
Zsolt Haraszti80175202016-12-24 00:17:51 -0800409 elif action.type == PUSH_VLAN:
410 if action.push.ethertype != 0x8100:
411 log.error('unhandled-ether-type',
412 ethertype=action.push.ethertype)
413 req /= PortIngressRuleResultInsert(fieldcode=7)
414
415 elif action.type == SET_FIELD:
416 assert (action.set_field.field.oxm_class ==
417 ofp.OFPXMC_OPENFLOW_BASIC)
418 field = action.set_field.field.ofb_field
419 if field.type == VLAN_VID:
420 req /= PortIngressRuleResultSet(
421 fieldcode=7, value=field.vlan_vid & 0xfff)
422 else:
423 log.error('unsupported-action-set-field-type',
424 field_type=field.type)
425
426 else:
427 log.error('unsupported-action-type',
428 action_type=action.type)
429
430 req /= PortIngressRuleTerminator()
431 req /= AddPortIngressRule()
432
433 msg = (
434 Ether(dst=device.mac_address) /
435 Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY) /
436 EOAMPayload(
437 body=CablelabsOUI() / DPoEOpcode_SetRequest() / req)
438 )
439
440 self.io_port.send(str(msg))
441
442 else:
443 raise Exception('Port should be 1 or 2 by our convention')
Zsolt Harasztied091602016-12-08 13:36:38 -0800444
445 def update_flows_incrementally(self, device, flow_changes, group_changes):
446 raise NotImplementedError()
447
448 def send_proxied_message(self, proxy_address, msg):
Nathan Knuth6e57f332016-12-22 15:49:20 -0800449 log.info('send-proxied-message', proxy_address=proxy_address)
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800450 # TODO build device_id -> mac_address cache
451 device = self.adapter_agent.get_device(proxy_address.device_id)
Nathan Knuth6e57f332016-12-22 15:49:20 -0800452 frame = Ether(dst='00:0c:e2:22:29:00') / \
453 Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY) / \
454 Dot1Q(vlan=proxy_address.channel_id, prio=TIBIT_MGMT_PRIORITY) / \
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800455 msg
Nathan Knuth6e57f332016-12-22 15:49:20 -0800456
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800457 self.io_port.send(str(frame))
Zsolt Harasztied091602016-12-08 13:36:38 -0800458
459 def receive_proxied_message(self, proxy_address, msg):
460 raise NotImplementedError()