blob: e0bac13492199825451dc23b6096c681bfc75ef9 [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 Haraszti89a27302016-12-08 16:53:06 -080022from scapy.layers.inet import ICMP, IP
Zsolt Haraszti348d1932016-12-10 01:10:07 -080023from scapy.layers.l2 import Ether, Dot1Q
Zsolt Haraszti89a27302016-12-08 16:53:06 -080024from twisted.internet.defer import DeferredQueue, inlineCallbacks
25from twisted.internet import reactor
26
Zsolt Harasztied091602016-12-08 13:36:38 -080027from zope.interface import implementer
28
Zsolt Haraszti89a27302016-12-08 16:53:06 -080029from common.frameio.frameio import BpfProgramFilter
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080030from voltha.registry import registry
Zsolt Harasztied091602016-12-08 13:36:38 -080031from voltha.adapters.interface import IAdapterInterface
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080032from voltha.core.logical_device_agent import mac_str_to_tuple
Zsolt Harasztied091602016-12-08 13:36:38 -080033from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
34from voltha.protos.device_pb2 import DeviceType, DeviceTypes
35from voltha.protos.health_pb2 import HealthStatus
Zsolt Haraszti89a27302016-12-08 16:53:06 -080036from voltha.protos.common_pb2 import LogLevel, ConnectStatus
37
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080038from voltha.protos.common_pb2 import OperStatus, AdminState
39
40from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
41from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_10GB_FD, \
42 OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
43 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
44
Zsolt Haraszti89a27302016-12-08 16:53:06 -080045from scapy.packet import Packet, bind_layers
46from scapy.fields import StrField
Zsolt Harasztied091602016-12-08 13:36:38 -080047
Zsolt Haraszti348d1932016-12-10 01:10:07 -080048from EOAM import EOAM_MULTICAST_ADDRESS
Zsolt Harasztied091602016-12-08 13:36:38 -080049
Zsolt Haraszti348d1932016-12-10 01:10:07 -080050log = structlog.get_logger()
Zsolt Harasztied091602016-12-08 13:36:38 -080051
Zsolt Haraszti89a27302016-12-08 16:53:06 -080052is_tibit_frame = BpfProgramFilter('ether[12:2] = 0x9001')
53
Zsolt Haraszti348d1932016-12-10 01:10:07 -080054# To be removed in favor of OAM
Zsolt Haraszti89a27302016-12-08 16:53:06 -080055class TBJSON(Packet):
56 """ TBJSON 'packet' layer. """
57 name = "TBJSON"
58 fields_desc = [StrField("data", default="")]
59
Zsolt Harasztied091602016-12-08 13:36:38 -080060@implementer(IAdapterInterface)
61class TibitOltAdapter(object):
62
63 name = 'tibit_olt'
64
65 supported_device_types = [
66 DeviceType(
67 id='tibit_olt',
68 adapter=name,
69 accepts_bulk_flow_update=True
70 )
71 ]
72
73 def __init__(self, adapter_agent, config):
74 self.adapter_agent = adapter_agent
75 self.config = config
76 self.descriptor = Adapter(
77 id=self.name,
78 vendor='Tibit Communications Inc.',
79 version='0.1',
80 config=AdapterConfig(log_level=LogLevel.INFO)
81 )
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080082 self.interface = registry('main').get_args().interface
Zsolt Haraszti89a27302016-12-08 16:53:06 -080083 self.io_port = None
84 self.incoming_queues = {} # mac_address -> DeferredQueue()
Zsolt Harasztied091602016-12-08 13:36:38 -080085
86 def start(self):
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -080087 log.debug('starting', interface=self.interface)
88 log.info('started', interface=self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -080089
90 def stop(self):
91 log.debug('stopping')
Zsolt Haraszti89a27302016-12-08 16:53:06 -080092 if self.io_port is not None:
93 registry('frameio').del_interface(self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -080094 log.info('stopped')
95
96 def adapter_descriptor(self):
97 return self.descriptor
98
99 def device_types(self):
100 return DeviceTypes(items=self.supported_device_types)
101
102 def health(self):
103 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
104
105 def change_master_state(self, master):
106 raise NotImplementedError()
107
108 def adopt_device(self, device):
109 log.info('adopt-device', device=device)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800110 self._activate_io_port()
111 reactor.callLater(0, self._launch_device_activation, device)
Zsolt Harasztied091602016-12-08 13:36:38 -0800112
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800113 def _activate_io_port(self):
114 if self.io_port is None:
115 self.io_port = registry('frameio').add_interface(
116 self.interface, self._rcv_io, is_tibit_frame)
117
118 @inlineCallbacks
119 def _launch_device_activation(self, device):
120
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800121 try:
122 log.debug('launch_dev_activation')
123 # prepare receive queue
124 self.incoming_queues[device.mac_address] = DeferredQueue(size=100)
125
126 # send out ping to OLT device
127 olt_mac = device.mac_address
128 ping_frame = self._make_ping_frame(mac_address=olt_mac)
129 self.io_port.send(ping_frame)
130
131 # wait till we receive a response
132 # TODO add timeout mechanism so we can signal if we cannot reach device
133 while True:
134 response = yield self.incoming_queues[olt_mac].get()
135 # verify response and if not the expected response
136 if 1: # TODO check if it is really what we expect, and wait if not
137 break
138
139 except Exception, e:
140 log.exception('launch device failed', e=e)
141
142 # if we got response, we can fill out the device info, mark the device
143 # reachable
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800144
145 device.root = True
146 device.vendor = 'Tibit stuff'
147 device.model = 'n/a'
148 device.hardware_version = 'n/a'
149 device.firmware_version = 'n/a'
150 device.software_version = '1.0'
151 device.serial_number = 'add junk here'
152 device.connect_status = ConnectStatus.REACHABLE
153 self.adapter_agent.update_device(device)
154
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800155 # then shortly after we create some ports for the device
156 log.info('create-port')
157 nni_port = Port(
158 port_no=2,
159 label='NNI facing Ethernet port',
160 type=Port.ETHERNET_NNI,
161 admin_state=AdminState.ENABLED,
162 oper_status=OperStatus.ACTIVE
163 )
164 self.adapter_agent.add_port(device.id, nni_port)
165 self.adapter_agent.add_port(device.id, Port(
166 port_no=1,
167 label='PON port',
168 type=Port.PON_OLT,
169 admin_state=AdminState.ENABLED,
170 oper_status=OperStatus.ACTIVE
171 ))
172
173 log.info('create-logical-device')
174 # then shortly after we create the logical device with one port
175 # that will correspond to the NNI port
176 logical_device_id = uuid4().hex[:12]
177 ld = LogicalDevice(
178 id=logical_device_id,
179 datapath_id=int('0x' + logical_device_id[:8], 16), # from id
180 desc=ofp_desc(
181 mfr_desc=device.vendor,
182 hw_desc=jdev['results']['device'],
183 sw_desc=jdev['results']['firmware'],
184 serial_num=uuid4().hex,
185 dp_desc='n/a'
186 ),
187 switch_features=ofp_switch_features(
188 n_buffers=256, # TODO fake for now
189 n_tables=2, # TODO ditto
190 capabilities=( # TODO and ditto
191 OFPC_FLOW_STATS
192 | OFPC_TABLE_STATS
193 | OFPC_PORT_STATS
194 | OFPC_GROUP_STATS
195 )
196 ),
197 root_device_id=device.id
198 )
199 self.adapter_agent.create_logical_device(ld)
200 cap = OFPPF_10GB_FD | OFPPF_FIBER
201 self.adapter_agent.add_logical_port(ld.id, LogicalPort(
202 id='nni',
203 ofp_port=ofp_port(
204 port_no=129,
205 hw_addr=mac_str_to_tuple(device.mac_address),
206 name='nni',
207 config=0,
208 state=OFPPS_LIVE,
209 curr=cap,
210 advertised=cap,
211 peer=cap,
212 curr_speed=OFPPF_10GB_FD,
213 max_speed=OFPPF_10GB_FD
214 ),
215 device_id=device.id,
216 device_port_no=nni_port.port_no,
217 root_port=True
218 ))
219
220 # and finally update to active
221 device = self.adapter_agent.get_device(device.id)
222 device.parent_id = ld.id
223 device.oper_status = OperStatus.ACTIVE
224 self.adapter_agent.update_device(device)
225
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800226 # Just transitioned to ACTIVE, wait a tenth of second
227 # before checking for ONUs
228 reactor.callLater(0.1, self._detect_onus, device)
229
230 @inlineCallbacks
231 def _detect_onus(self, device):
232 # send out get 'links' to the OLT device
233 olt_mac = device.mac_address
234 links_frame = self._make_links_frame(mac_address=olt_mac)
235 self.io_port.send(links_frame)
236 while True:
237 response = yield self.incoming_queues[olt_mac].get()
238 # verify response and if not the expected response
239 if 1: # TODO check if it is really what we expect, and wait if not
240 break
241
242 jdev = json.loads(response.data[5:])
243 for macid in jdev['results']:
244 if macid['macid'] is None:
245 log.info('MAC ID is NONE %s' % str(macid['macid']))
246 else:
247 log.info('activate-olt-for-onu-%s' % macid['macid'])
248 # TODO: report gemport and vlan_id
249 gemport, vlan_id = self._olt_side_onu_activation(int(macid['macid'][-3:]))
250 self.adapter_agent.child_device_detected(
251 parent_device_id=device.id,
252 parent_port_no=1,
253 child_device_type='tibit_onu',
254 proxy_address=Device.ProxyAddress(
255 device_id=device.id,
256 channel_id=vlan_id
257 ),
258 vlan=vlan_id
259 )
260
261 def _olt_side_onu_activation(self, seq):
262 """
263 This is where if this was a real OLT, the OLT-side activation for
264 the new ONU should be performed. By the time we return, the OLT shall
265 be able to provide tunneled (proxy) communication to the given ONU,
266 using the returned information.
267 """
268 gemport = seq + 1
269 vlan_id = seq + 100
270 return gemport, vlan_id
271
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800272 def _rcv_io(self, port, frame):
273
274 log.info('frame-recieved')
275
276 # extract source mac
277 response = Ether(frame)
278
279 # enqueue incoming parsed frame to rigth device
280 self.incoming_queues[response.src].put(response)
281
282 def _make_ping_frame(self, mac_address):
283 # TODO Nathan to make this to be an actual OLT ping
284 # Create a json packet
285 json_operation_str = '{\"operation\":\"version\"}'
286 frame = Ether()/TBJSON(data='json %s' % json_operation_str)
287 frame.type = int("9001", 16)
288 frame.dst = '00:0c:e2:31:25:00'
289 bind_layers(Ether, TBJSON, type=0x9001)
290 frame.show()
291 return str(frame)
292
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800293 def _make_links_frame(self, mac_address):
294 # Create a json packet
295 json_operation_str = '{\"operation\":\"links\"}'
296 frame = Ether()/TBJSON(data='json %s' % json_operation_str)
297 frame.type = int("9001", 16)
298 frame.dst = mac_address
299 bind_layers(Ether, TBJSON, type=0x9001)
300 return str(frame)
301
Zsolt Harasztied091602016-12-08 13:36:38 -0800302 def abandon_device(self, device):
303 raise NotImplementedError(0
304 )
305 def deactivate_device(self, device):
306 raise NotImplementedError()
307
308 def update_flows_bulk(self, device, flows, groups):
309 log.debug('bulk-flow-update', device_id=device.id,
310 flows=flows, groups=groups)
311
312 def update_flows_incrementally(self, device, flow_changes, group_changes):
313 raise NotImplementedError()
314
315 def send_proxied_message(self, proxy_address, msg):
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800316 log.info('send-proxied-message', proxy_address=proxy_address, msg=msg)
317 # TODO build device_id -> mac_address cache
318 device = self.adapter_agent.get_device(proxy_address.device_id)
319 frame = Ether(dst=device.mac_address) / \
320 Dot1Q(vlan=4090) / \
321 Dot1Q(vlan=proxy_address.channel_id) / \
322 msg
323 # frame = Ether(dst=EOAM_MULTICAST_ADDRESS) / msg
324 self.io_port.send(str(frame))
Zsolt Harasztied091602016-12-08 13:36:38 -0800325
326 def receive_proxied_message(self, proxy_address, msg):
327 raise NotImplementedError()