blob: 6a0ff9ed7ba2cc4ab53e381bef509b4bcd567ad9 [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 Haraszti89a27302016-12-08 16:53:06 -080020import scapy
Zsolt Harasztied091602016-12-08 13:36:38 -080021import structlog
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080022import json
23
24from uuid import uuid4
25
Zsolt Haraszti89a27302016-12-08 16:53:06 -080026from scapy.layers.inet import ICMP, IP
Zsolt Haraszti348d1932016-12-10 01:10:07 -080027from scapy.layers.l2 import Ether, Dot1Q
Zsolt Haraszti89a27302016-12-08 16:53:06 -080028from twisted.internet.defer import DeferredQueue, inlineCallbacks
29from twisted.internet import reactor
30
Zsolt Harasztied091602016-12-08 13:36:38 -080031from zope.interface import implementer
32
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080033from common.utils.asleep import asleep
34
Zsolt Haraszti89a27302016-12-08 16:53:06 -080035from common.frameio.frameio import BpfProgramFilter
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080036from voltha.registry import registry
Zsolt Harasztied091602016-12-08 13:36:38 -080037from voltha.adapters.interface import IAdapterInterface
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080038from voltha.core.logical_device_agent import mac_str_to_tuple
Zsolt Harasztied091602016-12-08 13:36:38 -080039from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080040from voltha.protos.device_pb2 import Device, Port
Zsolt Harasztied091602016-12-08 13:36:38 -080041from voltha.protos.device_pb2 import DeviceType, DeviceTypes
42from voltha.protos.health_pb2 import HealthStatus
Zsolt Haraszti89a27302016-12-08 16:53:06 -080043from voltha.protos.common_pb2 import LogLevel, ConnectStatus
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080044from 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
Zsolt Haraszti89a27302016-12-08 16:53:06 -080051from scapy.packet import Packet, bind_layers
52from scapy.fields import StrField
Zsolt Harasztied091602016-12-08 13:36:38 -080053
Zsolt Haraszti348d1932016-12-10 01:10:07 -080054log = structlog.get_logger()
Zsolt Harasztied091602016-12-08 13:36:38 -080055
Nathan Knuth6e57f332016-12-22 15:49:20 -080056# Match on the MGMT VLAN, Priority 7
57TIBIT_MGMT_VLAN=4090
58TIBIT_MGMT_PRIORITY=7
59frame_match = 'ether[14:2] = 0x{:01x}{:03x}'.format(TIBIT_MGMT_PRIORITY << 1, TIBIT_MGMT_VLAN)
60is_tibit_frame = BpfProgramFilter(frame_match)
61#is_tibit_frame = lambda x: True
Zsolt Haraszti89a27302016-12-08 16:53:06 -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 Harasztied091602016-12-08 13:36:38 -080071@implementer(IAdapterInterface)
72class TibitOltAdapter(object):
73
74 name = 'tibit_olt'
75
76 supported_device_types = [
77 DeviceType(
78 id='tibit_olt',
79 adapter=name,
80 accepts_bulk_flow_update=True
81 )
82 ]
83
84 def __init__(self, adapter_agent, config):
85 self.adapter_agent = adapter_agent
86 self.config = config
87 self.descriptor = Adapter(
88 id=self.name,
89 vendor='Tibit Communications Inc.',
90 version='0.1',
91 config=AdapterConfig(log_level=LogLevel.INFO)
92 )
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080093 self.interface = registry('main').get_args().interface
Zsolt Haraszti89a27302016-12-08 16:53:06 -080094 self.io_port = None
Nathan Knuth6e57f332016-12-22 15:49:20 -080095 self.incoming_queues = {} # OLT mac_address -> DeferredQueue()
96 self.device_ids = {} # OLT mac_address -> device_id
Zsolt Harasztied091602016-12-08 13:36:38 -080097
98 def start(self):
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080099 log.debug('starting', interface=self.interface)
100 log.info('started', interface=self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -0800101
102 def stop(self):
103 log.debug('stopping')
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800104 if self.io_port is not None:
105 registry('frameio').del_interface(self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -0800106 log.info('stopped')
107
108 def adapter_descriptor(self):
109 return self.descriptor
110
111 def device_types(self):
112 return DeviceTypes(items=self.supported_device_types)
113
114 def health(self):
115 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
116
117 def change_master_state(self, master):
118 raise NotImplementedError()
119
120 def adopt_device(self, device):
121 log.info('adopt-device', device=device)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800122 self._activate_io_port()
123 reactor.callLater(0, self._launch_device_activation, device)
Zsolt Harasztied091602016-12-08 13:36:38 -0800124
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800125 def _activate_io_port(self):
126 if self.io_port is None:
127 self.io_port = registry('frameio').add_interface(
128 self.interface, self._rcv_io, is_tibit_frame)
129
130 @inlineCallbacks
131 def _launch_device_activation(self, device):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800132 try:
133 log.debug('launch_dev_activation')
134 # prepare receive queue
135 self.incoming_queues[device.mac_address] = DeferredQueue(size=100)
136
Nathan Knuth6e57f332016-12-22 15:49:20 -0800137 # add mac_address to device_ids table
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800138 olt_mac = device.mac_address
Nathan Knuth6e57f332016-12-22 15:49:20 -0800139 self.device_ids[olt_mac] = device.id
140
141 # send out ping to OLT device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800142 ping_frame = self._make_ping_frame(mac_address=olt_mac)
143 self.io_port.send(ping_frame)
144
145 # wait till we receive a response
Nathan Knuth6e57f332016-12-22 15:49:20 -0800146 ## TODO add timeout mechanism so we can signal if we cannot reach
147 ##device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800148 while True:
149 response = yield self.incoming_queues[olt_mac].get()
150 # verify response and if not the expected response
151 if 1: # TODO check if it is really what we expect, and wait if not
152 break
153
154 except Exception, e:
155 log.exception('launch device failed', e=e)
156
157 # if we got response, we can fill out the device info, mark the device
158 # reachable
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800159 jdev = json.loads(response.data[5:])
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
258 jdev = json.loads(response.data[5:])
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):
345 log.debug('bulk-flow-update', device_id=device.id,
346 flows=flows, groups=groups)
347
348 def update_flows_incrementally(self, device, flow_changes, group_changes):
349 raise NotImplementedError()
350
351 def send_proxied_message(self, proxy_address, msg):
Nathan Knuth6e57f332016-12-22 15:49:20 -0800352 log.info('send-proxied-message', proxy_address=proxy_address)
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800353 # TODO build device_id -> mac_address cache
354 device = self.adapter_agent.get_device(proxy_address.device_id)
Nathan Knuth6e57f332016-12-22 15:49:20 -0800355 frame = Ether(dst='00:0c:e2:22:29:00') / \
356 Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY) / \
357 Dot1Q(vlan=proxy_address.channel_id, prio=TIBIT_MGMT_PRIORITY) / \
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800358 msg
Nathan Knuth6e57f332016-12-22 15:49:20 -0800359
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800360 self.io_port.send(str(frame))
Zsolt Harasztied091602016-12-08 13:36:38 -0800361
362 def receive_proxied_message(self, proxy_address, msg):
363 raise NotImplementedError()