blob: 45d610f8c90ee3b0b2ca0bb75d15fc73a54285dd [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
Zsolt Haraszti80175202016-12-24 00:17:51 -080033from voltha.adapters.tibit_olt.EOAM import EOAMPayload, DPoEOpcode_SetRequest
34from voltha.adapters.tibit_olt.EOAM_TLV import DOLTObject, \
35 PortIngressRuleClauseMatchLength02, PortIngressRuleResultForward, \
36 PortIngressRuleResultSet, PortIngressRuleResultInsert, \
37 PortIngressRuleTerminator, AddPortIngressRule, CablelabsOUI
38from voltha.adapters.tibit_olt.EOAM_TLV import PortIngressRuleHeader
39from voltha.core.flow_decomposer import *
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080040from voltha.core.logical_device_agent import mac_str_to_tuple
Zsolt Harasztied091602016-12-08 13:36:38 -080041from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
Zsolt Haraszti80175202016-12-24 00:17:51 -080042from voltha.protos.common_pb2 import LogLevel, ConnectStatus
43from voltha.protos.common_pb2 import OperStatus, AdminState
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080044from voltha.protos.device_pb2 import Device, Port
Zsolt Harasztied091602016-12-08 13:36:38 -080045from voltha.protos.device_pb2 import DeviceType, DeviceTypes
46from voltha.protos.health_pb2 import HealthStatus
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080047from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
48from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_10GB_FD, \
49 OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
50 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
Zsolt Haraszti80175202016-12-24 00:17:51 -080051from voltha.registry import registry
Zsolt Harasztied091602016-12-08 13:36:38 -080052
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 Haraszti348d1932016-12-10 01:10:07 -080062# To be removed in favor of OAM
Zsolt Haraszti89a27302016-12-08 16:53:06 -080063class TBJSON(Packet):
64 """ TBJSON 'packet' layer. """
65 name = "TBJSON"
66 fields_desc = [StrField("data", default="")]
67
Nathan Knuth6e57f332016-12-22 15:49:20 -080068bind_layers(Ether, TBJSON, type=0x9001)
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080069
Zsolt Harasztied091602016-12-08 13:36:38 -080070@implementer(IAdapterInterface)
71class TibitOltAdapter(object):
72
73 name = 'tibit_olt'
74
75 supported_device_types = [
76 DeviceType(
77 id='tibit_olt',
78 adapter=name,
79 accepts_bulk_flow_update=True
80 )
81 ]
82
83 def __init__(self, adapter_agent, config):
84 self.adapter_agent = adapter_agent
85 self.config = config
86 self.descriptor = Adapter(
87 id=self.name,
88 vendor='Tibit Communications Inc.',
89 version='0.1',
90 config=AdapterConfig(log_level=LogLevel.INFO)
91 )
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080092 self.interface = registry('main').get_args().interface
Zsolt Haraszti89a27302016-12-08 16:53:06 -080093 self.io_port = None
Nathan Knuth6e57f332016-12-22 15:49:20 -080094 self.incoming_queues = {} # OLT mac_address -> DeferredQueue()
95 self.device_ids = {} # OLT mac_address -> device_id
Zsolt Harasztied091602016-12-08 13:36:38 -080096
97 def start(self):
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080098 log.debug('starting', interface=self.interface)
99 log.info('started', interface=self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -0800100
101 def stop(self):
102 log.debug('stopping')
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800103 if self.io_port is not None:
104 registry('frameio').del_interface(self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -0800105 log.info('stopped')
106
107 def adapter_descriptor(self):
108 return self.descriptor
109
110 def device_types(self):
111 return DeviceTypes(items=self.supported_device_types)
112
113 def health(self):
114 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
115
116 def change_master_state(self, master):
117 raise NotImplementedError()
118
119 def adopt_device(self, device):
120 log.info('adopt-device', device=device)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800121 self._activate_io_port()
122 reactor.callLater(0, self._launch_device_activation, device)
Zsolt Harasztied091602016-12-08 13:36:38 -0800123
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800124 def _activate_io_port(self):
125 if self.io_port is None:
126 self.io_port = registry('frameio').add_interface(
127 self.interface, self._rcv_io, is_tibit_frame)
128
129 @inlineCallbacks
130 def _launch_device_activation(self, device):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800131 try:
132 log.debug('launch_dev_activation')
133 # prepare receive queue
134 self.incoming_queues[device.mac_address] = DeferredQueue(size=100)
135
Nathan Knuth6e57f332016-12-22 15:49:20 -0800136 # add mac_address to device_ids table
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800137 olt_mac = device.mac_address
Nathan Knuth6e57f332016-12-22 15:49:20 -0800138 self.device_ids[olt_mac] = device.id
139
140 # send out ping to OLT device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800141 ping_frame = self._make_ping_frame(mac_address=olt_mac)
142 self.io_port.send(ping_frame)
143
144 # wait till we receive a response
Nathan Knuth6e57f332016-12-22 15:49:20 -0800145 ## TODO add timeout mechanism so we can signal if we cannot reach
146 ##device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800147 while True:
148 response = yield self.incoming_queues[olt_mac].get()
149 # verify response and if not the expected response
150 if 1: # TODO check if it is really what we expect, and wait if not
151 break
152
153 except Exception, e:
154 log.exception('launch device failed', e=e)
155
156 # if we got response, we can fill out the device info, mark the device
157 # reachable
Zsolt Haraszti80175202016-12-24 00:17:51 -0800158 # jdev = json.loads(response.data[5:])
159 jdev = json.loads(response.payload.payload.body.load)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800160 device.root = True
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800161 device.vendor = 'Tibit Communications, Inc.'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800162 device.model = jdev.get('results', {}).get('device', 'DEVICE_UNKNOWN')
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800163 device.hardware_version = jdev['results']['datecode']
164 device.firmware_version = jdev['results']['firmware']
165 device.software_version = jdev['results']['modelversion']
166 device.serial_number = jdev['results']['manufacturer']
Nathan Knuth6e57f332016-12-22 15:49:20 -0800167
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800168 device.connect_status = ConnectStatus.REACHABLE
169 self.adapter_agent.update_device(device)
170
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800171 # then shortly after we create some ports for the device
172 log.info('create-port')
173 nni_port = Port(
174 port_no=2,
175 label='NNI facing Ethernet port',
176 type=Port.ETHERNET_NNI,
177 admin_state=AdminState.ENABLED,
178 oper_status=OperStatus.ACTIVE
179 )
180 self.adapter_agent.add_port(device.id, nni_port)
181 self.adapter_agent.add_port(device.id, Port(
182 port_no=1,
183 label='PON port',
184 type=Port.PON_OLT,
185 admin_state=AdminState.ENABLED,
186 oper_status=OperStatus.ACTIVE
187 ))
188
189 log.info('create-logical-device')
190 # then shortly after we create the logical device with one port
191 # that will correspond to the NNI port
192 logical_device_id = uuid4().hex[:12]
193 ld = LogicalDevice(
194 id=logical_device_id,
195 datapath_id=int('0x' + logical_device_id[:8], 16), # from id
196 desc=ofp_desc(
197 mfr_desc=device.vendor,
198 hw_desc=jdev['results']['device'],
199 sw_desc=jdev['results']['firmware'],
200 serial_num=uuid4().hex,
201 dp_desc='n/a'
202 ),
203 switch_features=ofp_switch_features(
204 n_buffers=256, # TODO fake for now
205 n_tables=2, # TODO ditto
206 capabilities=( # TODO and ditto
207 OFPC_FLOW_STATS
208 | OFPC_TABLE_STATS
209 | OFPC_PORT_STATS
210 | OFPC_GROUP_STATS
211 )
212 ),
213 root_device_id=device.id
214 )
215 self.adapter_agent.create_logical_device(ld)
216 cap = OFPPF_10GB_FD | OFPPF_FIBER
217 self.adapter_agent.add_logical_port(ld.id, LogicalPort(
218 id='nni',
219 ofp_port=ofp_port(
220 port_no=129,
221 hw_addr=mac_str_to_tuple(device.mac_address),
222 name='nni',
223 config=0,
224 state=OFPPS_LIVE,
225 curr=cap,
226 advertised=cap,
227 peer=cap,
228 curr_speed=OFPPF_10GB_FD,
229 max_speed=OFPPF_10GB_FD
230 ),
231 device_id=device.id,
232 device_port_no=nni_port.port_no,
233 root_port=True
234 ))
235
236 # and finally update to active
237 device = self.adapter_agent.get_device(device.id)
238 device.parent_id = ld.id
239 device.oper_status = OperStatus.ACTIVE
240 self.adapter_agent.update_device(device)
241
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800242 # Just transitioned to ACTIVE, wait a tenth of second
243 # before checking for ONUs
244 reactor.callLater(0.1, self._detect_onus, device)
245
246 @inlineCallbacks
247 def _detect_onus(self, device):
248 # send out get 'links' to the OLT device
249 olt_mac = device.mac_address
250 links_frame = self._make_links_frame(mac_address=olt_mac)
251 self.io_port.send(links_frame)
252 while True:
253 response = yield self.incoming_queues[olt_mac].get()
254 # verify response and if not the expected response
255 if 1: # TODO check if it is really what we expect, and wait if not
256 break
257
Zsolt Haraszti80175202016-12-24 00:17:51 -0800258 jdev = json.loads(response.payload.payload.body.load)
Nathan Knuth6e57f332016-12-22 15:49:20 -0800259 tibit_mac = ''
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800260 for macid in jdev['results']:
261 if macid['macid'] is None:
262 log.info('MAC ID is NONE %s' % str(macid['macid']))
263 else:
Nathan Knuth6e57f332016-12-22 15:49:20 -0800264 tibit_mac = '000c' + macid.get('macid', 'e2000000')[4:]
265 log.info('activate-olt-for-onu-%s' % tibit_mac)
266
267 # Convert from string to colon separated form
268 tibit_mac = ':'.join(s.encode('hex') for s in tibit_mac.decode('hex'))
269
270 gemport, vlan_id = self._olt_side_onu_activation(int(macid['macid'][-4:-2], 16))
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800271 self.adapter_agent.child_device_detected(
272 parent_device_id=device.id,
273 parent_port_no=1,
274 child_device_type='tibit_onu',
Nathan Knuth6e57f332016-12-22 15:49:20 -0800275 mac_address = tibit_mac,
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800276 proxy_address=Device.ProxyAddress(
277 device_id=device.id,
278 channel_id=vlan_id
279 ),
280 vlan=vlan_id
281 )
282
Nathan Knuth6e57f332016-12-22 15:49:20 -0800283 def _olt_side_onu_activation(self, serial):
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800284 """
285 This is where if this was a real OLT, the OLT-side activation for
286 the new ONU should be performed. By the time we return, the OLT shall
287 be able to provide tunneled (proxy) communication to the given ONU,
288 using the returned information.
289 """
Nathan Knuth6e57f332016-12-22 15:49:20 -0800290 gemport = serial
291 vlan_id = serial + 200
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800292 return gemport, vlan_id
293
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800294 def _rcv_io(self, port, frame):
295
Nathan Knuth6e57f332016-12-22 15:49:20 -0800296 log.info('frame-received')
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800297
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800298 # make into frame to extract source mac
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800299 response = Ether(frame)
300
Nathan Knuth6e57f332016-12-22 15:49:20 -0800301 if response.haslayer(Dot1Q):
302 # All responses from the OLT should have a TIBIT_MGMT_VLAN.
303 # Responses from the ONUs should have a TIBIT_MGMT_VLAN followed by a ONU CTAG
304 if response.getlayer(Dot1Q).type == 0x8100:
305 ## Responses from the ONU
306 ## Since the type of the first layer is 0x8100,
307 ## then the frame must have an inner tag layer
308 olt_mac = response.src
309 device_id = self.device_ids[olt_mac]
310 channel_id = response[Dot1Q:2].vlan
311 log.info('received_channel_id', channel_id=channel_id,
312 device_id=device_id)
313
314 proxy_address=Device.ProxyAddress(
315 device_id=device_id,
316 channel_id=channel_id
317 )
318 # pop dot1q header(s)
319 msg = response.payload.payload
320 self.adapter_agent.receive_proxied_message(proxy_address, msg)
321 else:
322 ## Respones from the OLT
323 ## enqueue incoming parsed frame to right device
324 self.incoming_queues[response.src].put(response)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800325
326 def _make_ping_frame(self, mac_address):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800327 # Create a json packet
328 json_operation_str = '{\"operation\":\"version\"}'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800329 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 -0800330 return str(frame)
331
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800332 def _make_links_frame(self, mac_address):
333 # Create a json packet
334 json_operation_str = '{\"operation\":\"links\"}'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800335 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 -0800336 return str(frame)
337
Zsolt Harasztied091602016-12-08 13:36:38 -0800338 def abandon_device(self, device):
339 raise NotImplementedError(0
340 )
341 def deactivate_device(self, device):
342 raise NotImplementedError()
343
344 def update_flows_bulk(self, device, flows, groups):
Zsolt Haraszti80175202016-12-24 00:17:51 -0800345 log.info('bulk-flow-update', device_id=device.id,
346 flows=flows, groups=groups)
347
348 assert len(groups.items) == 0, "Cannot yet deal with groups"
349
350 for flow in flows.items:
351 in_port = get_in_port(flow)
352 assert in_port is not None
353
354 precedence = 255 - min(flow.priority / 256, 255)
355
356 if in_port == 2:
357 # Downstream rule
358 pass # TODO still ignores
359
360 elif in_port == 1:
361 # Upstream rule
362 req = DOLTObject()
363 req /= PortIngressRuleHeader(precedence=precedence)
364
365 for field in get_ofb_fields(flow):
366 if field.type == ETH_TYPE:
367 _type = field.eth_type
368 req /= PortIngressRuleClauseMatchLength02(
369 fieldcode=3,
370 operator=1,
371 match0=(_type >> 8) & 0xff,
372 match1=_type & 0xff)
373 elif field.type == IP_PROTO:
374 pass
375 # TODO etc
376
377 for action in get_actions(flow):
378
379 if action.type == OUTPUT:
380 req /= PortIngressRuleResultForward()
381
382 elif action.type == PUSH_VLAN:
383 if action.push.ethertype != 0x8100:
384 log.error('unhandled-ether-type',
385 ethertype=action.push.ethertype)
386 req /= PortIngressRuleResultInsert(fieldcode=7)
387
388 elif action.type == SET_FIELD:
389 assert (action.set_field.field.oxm_class ==
390 ofp.OFPXMC_OPENFLOW_BASIC)
391 field = action.set_field.field.ofb_field
392 if field.type == VLAN_VID:
393 req /= PortIngressRuleResultSet(
394 fieldcode=7, value=field.vlan_vid & 0xfff)
395 else:
396 log.error('unsupported-action-set-field-type',
397 field_type=field.type)
398
399 else:
400 log.error('unsupported-action-type',
401 action_type=action.type)
402
403 req /= PortIngressRuleTerminator()
404 req /= AddPortIngressRule()
405
406 msg = (
407 Ether(dst=device.mac_address) /
408 Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY) /
409 EOAMPayload(
410 body=CablelabsOUI() / DPoEOpcode_SetRequest() / req)
411 )
412
413 self.io_port.send(str(msg))
414
415 else:
416 raise Exception('Port should be 1 or 2 by our convention')
Zsolt Harasztied091602016-12-08 13:36:38 -0800417
418 def update_flows_incrementally(self, device, flow_changes, group_changes):
419 raise NotImplementedError()
420
421 def send_proxied_message(self, proxy_address, msg):
Nathan Knuth6e57f332016-12-22 15:49:20 -0800422 log.info('send-proxied-message', proxy_address=proxy_address)
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800423 # TODO build device_id -> mac_address cache
424 device = self.adapter_agent.get_device(proxy_address.device_id)
Nathan Knuth6e57f332016-12-22 15:49:20 -0800425 frame = Ether(dst='00:0c:e2:22:29:00') / \
426 Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY) / \
427 Dot1Q(vlan=proxy_address.channel_id, prio=TIBIT_MGMT_PRIORITY) / \
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800428 msg
Nathan Knuth6e57f332016-12-22 15:49:20 -0800429
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800430 self.io_port.send(str(frame))
Zsolt Harasztied091602016-12-08 13:36:38 -0800431
432 def receive_proxied_message(self, proxy_address, msg):
433 raise NotImplementedError()