blob: d1ee6d16a7d21bf03b11b78570dc23ef4b1433d0 [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 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 Harasztic5f740b2017-01-18 09:53:17 -080023import arrow
Zsolt Haraszti80175202016-12-24 00:17:51 -080024import structlog
25from scapy.fields import StrField
Zsolt Haraszti348d1932016-12-10 01:10:07 -080026from scapy.layers.l2 import Ether, Dot1Q
Zsolt Haraszti80175202016-12-24 00:17:51 -080027from scapy.packet import Packet, bind_layers
Zsolt Haraszti89a27302016-12-08 16:53:06 -080028from twisted.internet import reactor
Zsolt Haraszti80175202016-12-24 00:17:51 -080029from twisted.internet.defer import DeferredQueue, inlineCallbacks
Zsolt Harasztic5f740b2017-01-18 09:53:17 -080030from twisted.internet.task import LoopingCall
Zsolt Harasztied091602016-12-08 13:36:38 -080031from zope.interface import implementer
32
Zsolt Haraszti313c4be2016-12-27 11:06:53 -080033from common.frameio.frameio import BpfProgramFilter, hexify
Zsolt Harasztied091602016-12-08 13:36:38 -080034from voltha.adapters.interface import IAdapterInterface
Nathan Knuth31c36962016-12-27 10:04:49 -080035from voltha.extensions.eoam.EOAM import EOAMPayload, DPoEOpcode_SetRequest
36from voltha.extensions.eoam.EOAM_TLV import DOLTObject, \
Nathan Knuthd8285e62017-01-11 14:18:43 -060037 NetworkToNetworkPortObject, OLTUnicastLogicalLink, \
38 PortIngressRuleClauseMatchLength01, AddStaticMacAddress, \
39 PortIngressRuleClauseMatchLength02, PortIngressRuleResultForward, \
40 PortIngressRuleResultSet, PortIngressRuleResultInsert, \
41 PortIngressRuleResultCopy, PortIngressRuleResultReplace, \
42 PortIngressRuleResultDelete, PortIngressRuleResultOLTQueue, \
43 PortIngressRuleTerminator, AddPortIngressRule, CablelabsOUI, PonPortObject
Nathan Knuth31c36962016-12-27 10:04:49 -080044from voltha.extensions.eoam.EOAM_TLV import PortIngressRuleHeader
Nathan Knuthd390ceb2017-01-07 15:38:58 -080045from voltha.extensions.eoam.EOAM_TLV import ClauseSubtypeEnum
46from voltha.extensions.eoam.EOAM_TLV import RuleOperatorEnum
Zsolt Haraszti80175202016-12-24 00:17:51 -080047from voltha.core.flow_decomposer import *
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080048from voltha.core.logical_device_agent import mac_str_to_tuple
Zsolt Harasztied091602016-12-08 13:36:38 -080049from voltha.protos.adapter_pb2 import Adapter, AdapterConfig
Zsolt Haraszti80175202016-12-24 00:17:51 -080050from voltha.protos.common_pb2 import LogLevel, ConnectStatus
51from voltha.protos.common_pb2 import OperStatus, AdminState
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080052from voltha.protos.device_pb2 import Device, Port
Zsolt Harasztied091602016-12-08 13:36:38 -080053from voltha.protos.device_pb2 import DeviceType, DeviceTypes
Zsolt Harasztic5f740b2017-01-18 09:53:17 -080054from voltha.protos.events_pb2 import KpiEvent, MetricValuePairs
55from voltha.protos.events_pb2 import KpiEventType
Zsolt Harasztied091602016-12-08 13:36:38 -080056from voltha.protos.health_pb2 import HealthStatus
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -080057from voltha.protos.logical_device_pb2 import LogicalDevice, LogicalPort
58from voltha.protos.openflow_13_pb2 import ofp_desc, ofp_port, OFPPF_10GB_FD, \
Nathan Knuthd8285e62017-01-11 14:18:43 -060059 OFPPF_FIBER, OFPPS_LIVE, ofp_switch_features, OFPC_PORT_STATS, \
60 OFPC_GROUP_STATS, OFPC_TABLE_STATS, OFPC_FLOW_STATS
Zsolt Haraszti80175202016-12-24 00:17:51 -080061from voltha.registry import registry
Zsolt Haraszti348d1932016-12-10 01:10:07 -080062log = structlog.get_logger()
Zsolt Harasztied091602016-12-08 13:36:38 -080063
Nathan Knuth388eff32017-01-18 18:31:22 -060064TIBIT_ONU_LINK_INDEX = 2
65
Nathan Knuth6e57f332016-12-22 15:49:20 -080066# Match on the MGMT VLAN, Priority 7
Zsolt Haraszti313c4be2016-12-27 11:06:53 -080067TIBIT_MGMT_VLAN = 4090
68TIBIT_MGMT_PRIORITY = 7
69frame_match_case1 = 'ether[14:2] = 0x{:01x}{:03x}'.format(
70 TIBIT_MGMT_PRIORITY << 1, TIBIT_MGMT_VLAN)
71
72TIBIT_PACKET_IN_VLAN = 4000
73frame_match_case2 = '(ether[14:2] & 0xfff) = 0x{:03x}'.format(
74 TIBIT_PACKET_IN_VLAN)
75
Nathan Knuthfe2b2e02017-01-06 07:29:02 -080076TIBIT_PACKET_OUT_VLAN = 4000
77
Zsolt Haraszti313c4be2016-12-27 11:06:53 -080078is_tibit_frame = BpfProgramFilter('{} or {}'.format(
79 frame_match_case1, frame_match_case2))
80
Nathan Knuth6e57f332016-12-22 15:49:20 -080081#is_tibit_frame = lambda x: True
Zsolt Haraszti89a27302016-12-08 16:53:06 -080082
Nathan Knuthfe2b2e02017-01-06 07:29:02 -080083# Extract OLT MAC address: This is a good
84# example of getting the OLT mac address
85
86#for mac, device in self.device_ids.iteritems():
87# if device == dev_id:
88# olt_mac_address = mac
89# log.info('packet-out', olt_mac_address=olt_mac_address)
Zsolt Haraszti85f12852016-12-24 08:30:58 -080090
Zsolt Haraszti348d1932016-12-10 01:10:07 -080091# To be removed in favor of OAM
Zsolt Haraszti89a27302016-12-08 16:53:06 -080092class TBJSON(Packet):
93 """ TBJSON 'packet' layer. """
94 name = "TBJSON"
95 fields_desc = [StrField("data", default="")]
96
Nathan Knuth6e57f332016-12-22 15:49:20 -080097bind_layers(Ether, TBJSON, type=0x9001)
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -080098
Nathan Knuthd8285e62017-01-11 14:18:43 -060099SUMITOMO_ELECTRIC_INDUSTRIES_OUI=u"0025DC"
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800100
Zsolt Harasztied091602016-12-08 13:36:38 -0800101@implementer(IAdapterInterface)
102class TibitOltAdapter(object):
103
104 name = 'tibit_olt'
105
106 supported_device_types = [
107 DeviceType(
108 id='tibit_olt',
109 adapter=name,
110 accepts_bulk_flow_update=True
111 )
112 ]
113
114 def __init__(self, adapter_agent, config):
115 self.adapter_agent = adapter_agent
116 self.config = config
117 self.descriptor = Adapter(
118 id=self.name,
119 vendor='Tibit Communications Inc.',
120 version='0.1',
121 config=AdapterConfig(log_level=LogLevel.INFO)
122 )
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -0800123 self.interface = registry('main').get_args().interface
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800124 self.io_port = None
Nathan Knuth6e57f332016-12-22 15:49:20 -0800125 self.incoming_queues = {} # OLT mac_address -> DeferredQueue()
126 self.device_ids = {} # OLT mac_address -> device_id
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800127 self.vlan_to_device_ids = {} # c-vid -> (device_id, logical_device_id)
Zsolt Harasztied091602016-12-08 13:36:38 -0800128
129 def start(self):
Zsolt Harasztia17f3ec2016-12-08 14:55:49 -0800130 log.debug('starting', interface=self.interface)
131 log.info('started', interface=self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -0800132
133 def stop(self):
134 log.debug('stopping')
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800135 if self.io_port is not None:
136 registry('frameio').del_interface(self.interface)
Zsolt Harasztied091602016-12-08 13:36:38 -0800137 log.info('stopped')
138
139 def adapter_descriptor(self):
140 return self.descriptor
141
142 def device_types(self):
143 return DeviceTypes(items=self.supported_device_types)
144
145 def health(self):
146 return HealthStatus(state=HealthStatus.HealthState.HEALTHY)
147
148 def change_master_state(self, master):
149 raise NotImplementedError()
150
151 def adopt_device(self, device):
152 log.info('adopt-device', device=device)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800153 self._activate_io_port()
154 reactor.callLater(0, self._launch_device_activation, device)
Zsolt Harasztied091602016-12-08 13:36:38 -0800155
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800156 def _activate_io_port(self):
157 if self.io_port is None:
158 self.io_port = registry('frameio').add_interface(
159 self.interface, self._rcv_io, is_tibit_frame)
160
161 @inlineCallbacks
162 def _launch_device_activation(self, device):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800163 try:
164 log.debug('launch_dev_activation')
165 # prepare receive queue
166 self.incoming_queues[device.mac_address] = DeferredQueue(size=100)
167
Nathan Knuth6e57f332016-12-22 15:49:20 -0800168 # add mac_address to device_ids table
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800169 olt_mac = device.mac_address
Nathan Knuth6e57f332016-12-22 15:49:20 -0800170 self.device_ids[olt_mac] = device.id
171
172 # send out ping to OLT device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800173 ping_frame = self._make_ping_frame(mac_address=olt_mac)
174 self.io_port.send(ping_frame)
175
176 # wait till we receive a response
Nathan Knuth6e57f332016-12-22 15:49:20 -0800177 ## TODO add timeout mechanism so we can signal if we cannot reach
178 ##device
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800179 while True:
180 response = yield self.incoming_queues[olt_mac].get()
181 # verify response and if not the expected response
182 if 1: # TODO check if it is really what we expect, and wait if not
183 break
184
185 except Exception, e:
186 log.exception('launch device failed', e=e)
187
188 # if we got response, we can fill out the device info, mark the device
189 # reachable
Zsolt Haraszti80175202016-12-24 00:17:51 -0800190 jdev = json.loads(response.payload.payload.body.load)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800191 device.root = True
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800192 device.vendor = 'Tibit Communications, Inc.'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800193 device.model = jdev.get('results', {}).get('device', 'DEVICE_UNKNOWN')
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800194 device.hardware_version = jdev['results']['datecode']
195 device.firmware_version = jdev['results']['firmware']
196 device.software_version = jdev['results']['modelversion']
197 device.serial_number = jdev['results']['manufacturer']
Nathan Knuth6e57f332016-12-22 15:49:20 -0800198
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800199 device.connect_status = ConnectStatus.REACHABLE
200 self.adapter_agent.update_device(device)
201
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800202 # then shortly after we create some ports for the device
203 log.info('create-port')
204 nni_port = Port(
205 port_no=2,
206 label='NNI facing Ethernet port',
207 type=Port.ETHERNET_NNI,
208 admin_state=AdminState.ENABLED,
209 oper_status=OperStatus.ACTIVE
210 )
211 self.adapter_agent.add_port(device.id, nni_port)
212 self.adapter_agent.add_port(device.id, Port(
213 port_no=1,
214 label='PON port',
215 type=Port.PON_OLT,
216 admin_state=AdminState.ENABLED,
217 oper_status=OperStatus.ACTIVE
218 ))
219
220 log.info('create-logical-device')
221 # then shortly after we create the logical device with one port
222 # that will correspond to the NNI port
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800223 ld = LogicalDevice(
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800224 desc=ofp_desc(
225 mfr_desc=device.vendor,
226 hw_desc=jdev['results']['device'],
227 sw_desc=jdev['results']['firmware'],
228 serial_num=uuid4().hex,
229 dp_desc='n/a'
230 ),
231 switch_features=ofp_switch_features(
232 n_buffers=256, # TODO fake for now
233 n_tables=2, # TODO ditto
234 capabilities=( # TODO and ditto
235 OFPC_FLOW_STATS
236 | OFPC_TABLE_STATS
237 | OFPC_PORT_STATS
238 | OFPC_GROUP_STATS
239 )
240 ),
241 root_device_id=device.id
242 )
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800243 ld_initialized = self.adapter_agent.create_logical_device(ld)
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800244 cap = OFPPF_10GB_FD | OFPPF_FIBER
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800245 self.adapter_agent.add_logical_port(ld_initialized.id, LogicalPort(
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800246 id='nni',
247 ofp_port=ofp_port(
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800248 port_no=0,
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800249 hw_addr=mac_str_to_tuple(device.mac_address),
250 name='nni',
251 config=0,
252 state=OFPPS_LIVE,
253 curr=cap,
254 advertised=cap,
255 peer=cap,
256 curr_speed=OFPPF_10GB_FD,
257 max_speed=OFPPF_10GB_FD
258 ),
259 device_id=device.id,
260 device_port_no=nni_port.port_no,
261 root_port=True
262 ))
263
264 # and finally update to active
265 device = self.adapter_agent.get_device(device.id)
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800266 device.parent_id = ld_initialized.id
Zsolt Harasztiaa4626e2016-12-08 16:53:06 -0800267 device.oper_status = OperStatus.ACTIVE
268 self.adapter_agent.update_device(device)
269
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800270 # Just transitioned to ACTIVE, wait a tenth of second
271 # before checking for ONUs
272 reactor.callLater(0.1, self._detect_onus, device)
273
274 @inlineCallbacks
275 def _detect_onus(self, device):
276 # send out get 'links' to the OLT device
277 olt_mac = device.mac_address
278 links_frame = self._make_links_frame(mac_address=olt_mac)
279 self.io_port.send(links_frame)
280 while True:
281 response = yield self.incoming_queues[olt_mac].get()
282 # verify response and if not the expected response
283 if 1: # TODO check if it is really what we expect, and wait if not
284 break
285
Zsolt Haraszti80175202016-12-24 00:17:51 -0800286 jdev = json.loads(response.payload.payload.body.load)
Nathan Knuthd8285e62017-01-11 14:18:43 -0600287 onu_mac = ''
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800288 for macid in jdev['results']:
289 if macid['macid'] is None:
290 log.info('MAC ID is NONE %s' % str(macid['macid']))
Nathan Knuthd8285e62017-01-11 14:18:43 -0600291 elif macid['macid'][:6].upper() == SUMITOMO_ELECTRIC_INDUSTRIES_OUI:
292 onu_mac = macid['macid']
293 log.info('SUMITOMO mac address %s' % str(macid['macid']))
294 log.info('activate-olt-for-onu-%s' % onu_mac)
295 # Convert from string to colon separated form
296 onu_mac = ':'.join(s.encode('hex') for s in onu_mac.decode('hex'))
297 vlan_id = self._olt_side_onu_activation(int(macid['macid'][-4:-2], 16))
298 self.adapter_agent.child_device_detected(
299 parent_device_id=device.id,
300 parent_port_no=1,
301 child_device_type='dpoe_onu',
302 mac_address = onu_mac,
303 proxy_address=Device.ProxyAddress(
304 device_id=device.id,
305 channel_id=vlan_id
306 ),
307 vlan=vlan_id
308 )
309
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800310 else:
Nathan Knuthd8285e62017-01-11 14:18:43 -0600311 onu_mac = '000c' + macid.get('macid', 'e2000000')[4:]
312 log.info('activate-olt-for-onu-%s' % onu_mac)
313 # Convert from string to colon separated form
314 onu_mac = ':'.join(s.encode('hex') for s in onu_mac.decode('hex'))
315 vlan_id = self._olt_side_onu_activation(int(macid['macid'][-4:-2], 16))
316 self.adapter_agent.child_device_detected(
317 parent_device_id=device.id,
318 parent_port_no=1,
319 child_device_type='tibit_onu',
320 mac_address = onu_mac,
321 proxy_address=Device.ProxyAddress(
322 device_id=device.id,
323 channel_id=vlan_id
324 ),
325 vlan=vlan_id
326 )
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800327
Nathan Knuth388eff32017-01-18 18:31:22 -0600328 # also record the vlan_id -> (device_id, logical_device_id, linkid) for
329 # later use. The linkid is the macid returned.
330 self.vlan_to_device_ids[vlan_id] = (device.id, device.parent_id, macid.get('macid', 0))
331
332 # Give the ONUs a chance to arrive before starting metric collection
333 reactor.callLater(5.0, self.start_kpi_collection, device.id)
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800334
Nathan Knuth6e57f332016-12-22 15:49:20 -0800335 def _olt_side_onu_activation(self, serial):
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800336 """
337 This is where if this was a real OLT, the OLT-side activation for
338 the new ONU should be performed. By the time we return, the OLT shall
339 be able to provide tunneled (proxy) communication to the given ONU,
340 using the returned information.
341 """
Nathan Knuth6e57f332016-12-22 15:49:20 -0800342 vlan_id = serial + 200
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800343 return vlan_id
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800344
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800345 def _rcv_io(self, port, frame):
346
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800347 log.info('frame-received', frame=hexify(frame))
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800348
Zsolt Haraszti4ef0a9a2016-12-20 01:35:48 -0800349 # make into frame to extract source mac
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800350 response = Ether(frame)
351
Nathan Knuth6e57f332016-12-22 15:49:20 -0800352 if response.haslayer(Dot1Q):
Nathan Knuth6e57f332016-12-22 15:49:20 -0800353
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800354 # All OAM responses from the OLT should have a TIBIT_MGMT_VLAN.
355 # Responses from the ONUs should have a TIBIT_MGMT_VLAN followed by a ONU CTAG
356 # All packet-in frames will have the TIBIT_PACKET_IN_VLAN.
357 if response.getlayer(Dot1Q).type == 0x8100:
358
359 if response.getlayer(Dot1Q).vlan == TIBIT_PACKET_IN_VLAN:
360
361 inner_tag_and_rest = response.payload.payload
362
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800363 if isinstance(inner_tag_and_rest, Dot1Q):
364
365 cvid = inner_tag_and_rest.vlan
366
367 frame = Ether(src=response.src,
368 dst=response.dst,
369 type=inner_tag_and_rest.type) /\
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800370 inner_tag_and_rest.payload
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800371
372 _, logical_device_id = self.vlan_to_device_ids.get(cvid)
373 if logical_device_id is None:
374 log.error('invalid-cvid', cvid=cvid)
375 else:
376 self.adapter_agent.send_packet_in(
377 logical_device_id=logical_device_id,
378 logical_port_no=cvid, # C-VID encodes port no
379 packet=str(frame))
380
381 else:
382 log.error('packet-in-single-tagged',
383 frame=hexify(response))
384
385 else:
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800386 ## Mgmt responses received from the ONU
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800387 ## Since the type of the first layer is 0x8100,
388 ## then the frame must have an inner tag layer
389 olt_mac = response.src
390 device_id = self.device_ids[olt_mac]
391 channel_id = response[Dot1Q:2].vlan
392 log.info('received_channel_id', channel_id=channel_id,
393 device_id=device_id)
394
395 proxy_address=Device.ProxyAddress(
396 device_id=device_id,
397 channel_id=channel_id
398 )
399 # pop dot1q header(s)
400 msg = response.payload.payload
401 self.adapter_agent.receive_proxied_message(proxy_address, msg)
402
Nathan Knuth6e57f332016-12-22 15:49:20 -0800403 else:
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800404 ## Mgmt responses received from the OLT
Nathan Knuth6e57f332016-12-22 15:49:20 -0800405 ## enqueue incoming parsed frame to right device
Zsolt Haraszti313c4be2016-12-27 11:06:53 -0800406 log.info('received-dot1q-not-8100')
Nathan Knuth6e57f332016-12-22 15:49:20 -0800407 self.incoming_queues[response.src].put(response)
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800408
409 def _make_ping_frame(self, mac_address):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800410 # Create a json packet
411 json_operation_str = '{\"operation\":\"version\"}'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800412 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 -0800413 return str(frame)
414
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800415 def _make_links_frame(self, mac_address):
416 # Create a json packet
417 json_operation_str = '{\"operation\":\"links\"}'
Nathan Knuth6e57f332016-12-22 15:49:20 -0800418 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 -0800419 return str(frame)
420
Nathan Knuth388eff32017-01-18 18:31:22 -0600421 def _make_stats_frame(self, mac_address, itype, link):
422 # Create a json packet
423 json_operation_str = ('{\"operation\":\"stats\",\"parameters\":{\"itype\":\"%s\",\"iinst\",\"0\",\"macid\":\"%s\"}}' % (itype, link))
424 frame = Ether(dst=mac_address)/Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY)/TBJSON(data='json %s' % json_operation_str)
425 return str(frame)
426
Zsolt Harasztied091602016-12-08 13:36:38 -0800427 def abandon_device(self, device):
428 raise NotImplementedError(0
429 )
430 def deactivate_device(self, device):
431 raise NotImplementedError()
432
433 def update_flows_bulk(self, device, flows, groups):
Nathan Knuthe69ceb12017-01-04 21:13:39 -0800434 log.info('########################################')
Zsolt Haraszti80175202016-12-24 00:17:51 -0800435 log.info('bulk-flow-update', device_id=device.id,
436 flows=flows, groups=groups)
Zsolt Haraszti80175202016-12-24 00:17:51 -0800437 assert len(groups.items) == 0, "Cannot yet deal with groups"
438
Nathan Knuthf840dfb2017-01-12 18:15:14 -0800439 # extract ONU VID
440 vid_from_device_id = {v[0]: k for k,v in self.vlan_to_device_ids.iteritems()}
441 ONU_VID = vid_from_device_id[device.id]
Nathan Knuth5f4163e2017-01-11 18:21:10 -0600442
Nathan Knuthd390ceb2017-01-07 15:38:58 -0800443 Clause = {v: k for k, v in ClauseSubtypeEnum.iteritems()}
444 Operator = {v: k for k, v in RuleOperatorEnum.iteritems()}
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800445
Zsolt Haraszti80175202016-12-24 00:17:51 -0800446 for flow in flows.items:
Zsolt Haraszti80175202016-12-24 00:17:51 -0800447
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800448 try:
449 in_port = get_in_port(flow)
450 assert in_port is not None
Zsolt Haraszti80175202016-12-24 00:17:51 -0800451
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800452 precedence = 255 - min(flow.priority / 256, 255)
Nathan Knuthe69ceb12017-01-04 21:13:39 -0800453
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800454 if in_port == 2:
455 log.info('#### Downstream Rule ####')
456 dn_req = NetworkToNetworkPortObject()
457 dn_req /= PortIngressRuleHeader(precedence=precedence)
Nathan Knuthe69ceb12017-01-04 21:13:39 -0800458
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800459 for field in get_ofb_fields(flow):
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800460
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800461 if field.type == ETH_TYPE:
462 _type = field.eth_type
463 log.info('#### field.type == ETH_TYPE ####')
464 dn_req /= PortIngressRuleClauseMatchLength02(
465 fieldcode=Clause['L2 Type/Len'],
466 operator=Operator['=='],
467 match=_type)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800468
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800469 elif field.type == IP_PROTO:
470 _proto = field.ip_proto
471 log.info('#### field.type == IP_PROTO ####')
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800472
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800473 elif field.type == IN_PORT:
474 _port = field.port
475 log.info('#### field.type == IN_PORT ####', port=_port)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800476
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800477 elif field.type == VLAN_VID:
478 _vlan_vid = field.vlan_vid & 0xfff
479 log.info('#### field.type == VLAN_VID ####', vlan=_vlan_vid)
480 dn_req /= PortIngressRuleClauseMatchLength02(fieldcode=Clause['C-VLAN Tag'], fieldinstance=0,
481 operator=Operator['=='], match=_vlan_vid)
482 if (_vlan_vid != 140):
483 dn_req /= PortIngressRuleClauseMatchLength02(fieldcode=Clause['C-VLAN Tag'], fieldinstance=1,
Nathan Knuthf840dfb2017-01-12 18:15:14 -0800484 operator=Operator['=='], match=ONU_VID)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800485
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800486 elif field.type == VLAN_PCP:
487 _vlan_pcp = field.vlan_pcp
488 log.info('#### field.type == VLAN_PCP ####', pcp=_vlan_pcp)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800489
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800490 elif field.type == UDP_DST:
491 _udp_dst = field.udp_dst
492 log.info('#### field.type == UDP_DST ####')
Zsolt Haraszti6a5107c2017-01-09 23:42:41 -0800493
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800494 elif field.type == UDP_SRC:
495 _udp_src = field.udp_src
496 log.info('#### field.type == UDP_SRC ####')
Zsolt Haraszti80175202016-12-24 00:17:51 -0800497
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800498 elif field.type == IPV4_DST:
499 _ipv4_dst = field.ipv4_dst
500 log.info('#### field.type == IPV4_DST ####')
Zsolt Haraszti80175202016-12-24 00:17:51 -0800501
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800502 elif field.type == METADATA:
503 log.info('#### field.type == METADATA ####')
504 pass
Zsolt Haraszti80175202016-12-24 00:17:51 -0800505
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800506 else:
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800507 raise NotImplementedError('field.type={}'.format(
508 field.type))
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800509
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800510 for action in get_actions(flow):
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800511
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800512 if action.type == OUTPUT:
513 log.info('#### action.type == OUTPUT ####')
514 dn_req /= PortIngressRuleResultForward()
Nathan Knuthf840dfb2017-01-12 18:15:14 -0800515 serial = ONU_VID - 200
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800516 link = (0xe222 << 16) | (serial << 8)
517 dn_req /= PortIngressRuleResultOLTQueue(unicastvssn="TBIT",
518 unicastlink=link)
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800519
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800520 elif action.type == POP_VLAN:
521 log.info('#### action.type == POP_VLAN ####')
522 dn_req /= PortIngressRuleResultDelete(fieldcode=Clause['S-VLAN Tag'])
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800523
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800524 elif action.type == PUSH_VLAN:
525 log.info('#### action.type == PUSH_VLAN ####')
526 if action.push.ethertype != 0x8100:
527 log.error('unhandled-tpid',
528 ethertype=action.push.ethertype)
529 dn_req /= PortIngressRuleResultInsert(fieldcode=Clause['C-VLAN Tag'])
Nathan Knuthd8285e62017-01-11 14:18:43 -0600530
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800531 elif action.type == SET_FIELD:
532 log.info('#### action.type == SET_FIELD ####')
533 assert (action.set_field.field.oxm_class ==
534 ofp.OFPXMC_OPENFLOW_BASIC)
535 field = action.set_field.field.ofb_field
536 if field.type == VLAN_VID:
537 dn_req /= PortIngressRuleResultSet(
538 fieldcode=Clause['C-VLAN Tag'], value=field.vlan_vid & 0xfff)
539 else:
540 log.error('unsupported-action-set-field-type',
541 field_type=field.type)
Nathan Knuthd8285e62017-01-11 14:18:43 -0600542 else:
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800543 log.error('UNSUPPORTED-ACTION-TYPE',
544 action_type=action.type)
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800545
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800546 dn_req /= PortIngressRuleTerminator()
547 dn_req /= AddPortIngressRule()
548
549 msg = (
550 Ether(dst=device.mac_address) /
551 Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY) /
552 EOAMPayload(
553 body=CablelabsOUI() / DPoEOpcode_SetRequest() / dn_req)
554 )
555
556 self.io_port.send(str(msg))
557
558 elif in_port == 1:
559 # Upstream rule
560 log.info('#### Upstream Rule ####')
561
562 field_match_vlan_upstream_with_link = False
563 up_req_link = PortIngressRuleHeader(precedence=precedence)
564
565 up_req_pon = PonPortObject()
566 up_req_pon /= PortIngressRuleHeader(precedence=precedence)
567
568 for field in get_ofb_fields(flow):
569
570 if field.type == ETH_TYPE:
571 _type = field.eth_type
572 log.info('#### field.type == ETH_TYPE ####', in_port=in_port,
573 match=_type)
574 up_req_pon /= PortIngressRuleClauseMatchLength02(
575 fieldcode=Clause['L2 Type/Len'],
576 operator=Operator['=='],
577 match=_type)
578
579 up_req_link /= PortIngressRuleClauseMatchLength02(
580 fieldcode=Clause['L2 Type/Len'],
581 operator=Operator['=='],
582 match=_type)
583
584 elif field.type == IP_PROTO:
585 _proto = field.ip_proto
586 log.info('#### field.type == IP_PROTO ####', in_port=in_port,
587 ip_proto=_proto)
588
589 up_req_pon /= PortIngressRuleClauseMatchLength01(
590 fieldcode=Clause['IPv4/IPv6 Protocol Type'],
591 operator=Operator['=='], match=_proto)
592
593 up_req_link /= PortIngressRuleClauseMatchLength01(
594 fieldcode=Clause['IPv4/IPv6 Protocol Type'],
595 operator=Operator['=='], match=_proto)
596
597 elif field.type == IN_PORT:
598 _port = field.port
599 log.info('#### field.type == IN_PORT ####')
600
601 elif field.type == VLAN_VID:
602 _vlan_vid = field.vlan_vid & 0xfff
603 log.info('#### field.type == VLAN_VID ####')
604 up_req_pon /= PortIngressRuleClauseMatchLength02(
605 fieldcode=Clause['C-VLAN Tag'], fieldinstance=0,
606 operator=Operator['=='], match=_vlan_vid)
607
608 serial = _vlan_vid - 200
609 link = (0xe222 << 16) | (serial << 8)
610 up_req_link /= OLTUnicastLogicalLink(unicastvssn='TBIT', unicastlink=link)
611
612 up_req_link /= PortIngressRuleClauseMatchLength02(
613 fieldcode=Clause['C-VLAN Tag'], fieldinstance=0,
614 operator=Operator['=='], match=_vlan_vid)
615 field_match_vlan_upstream_with_link = True
616
617
618 elif field.type == VLAN_PCP:
619 _vlan_pcp = field.vlan_pcp
620 log.info('#### field.type == VLAN_PCP ####')
621
622 elif field.type == UDP_DST:
623 _udp_dst = field.udp_dst
624 log.info('#### field.type == UDP_DST ####')
625 up_req_pon /= (PortIngressRuleClauseMatchLength02(fieldcode=Clause['TCP/UDP source port'],
626 operator=Operator['=='], match=0x0044)/
627 PortIngressRuleClauseMatchLength02(fieldcode=Clause['TCP/UDP destination port'],
628 operator=Operator['=='], match=0x0043))
629
630 elif field.type == UDP_SRC:
631 _udp_src = field.udp_src
632 log.info('#### field.type == UDP_SRC ####')
633
634 else:
635 raise NotImplementedError('field.type={}'.format(
636 field.type))
637
638 for action in get_actions(flow):
639
640 if action.type == OUTPUT:
641 log.info('#### action.type == OUTPUT ####')
642 up_req_pon /= PortIngressRuleResultForward()
643 up_req_link /= PortIngressRuleResultForward()
644
645 elif action.type == POP_VLAN:
646 log.info('#### action.type == POP_VLAN ####')
647
648 elif action.type == PUSH_VLAN:
649 log.info('#### action.type == PUSH_VLAN ####')
650 if action.push.ethertype != 0x8100:
651 log.error('unhandled-ether-type',
652 ethertype=action.push.ethertype)
Nathan Knuthd8285e62017-01-11 14:18:43 -0600653 if field_match_vlan_upstream_with_link == True:
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800654 up_req_link /= PortIngressRuleResultInsert(fieldcode=Clause['C-VLAN Tag'],
655 fieldinstance=1)
656 else:
657 up_req_pon /= PortIngressRuleResultInsert(fieldcode=Clause['C-VLAN Tag'],
658 fieldinstance=0)
Nathan Knuthd8285e62017-01-11 14:18:43 -0600659
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800660 elif action.type == SET_FIELD:
661 log.info('#### action.type == SET_FIELD ####')
662 assert (action.set_field.field.oxm_class ==
663 ofp.OFPXMC_OPENFLOW_BASIC)
664 field = action.set_field.field.ofb_field
665 if field.type == VLAN_VID:
666 if field_match_vlan_upstream_with_link == True:
667 up_req_link /=(PortIngressRuleResultCopy(fieldcode=Clause['C-VLAN Tag'])/
668 PortIngressRuleResultReplace(fieldcode=Clause['C-VLAN Tag']))
669
670 up_req_pon /= PortIngressRuleResultSet(
671 fieldcode=Clause['C-VLAN Tag'], value=field.vlan_vid & 0xfff)
672 up_req_link /= PortIngressRuleResultSet(
673 fieldcode=Clause['C-VLAN Tag'], value=field.vlan_vid & 0xfff)
674 else:
675 log.error('unsupported-action-set-field-type',
676 field_type=field.type)
677
Zsolt Haraszti80175202016-12-24 00:17:51 -0800678 else:
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800679 log.error('UNSUPPORTED-ACTION-TYPE',
680 action_type=action.type)
Zsolt Haraszti80175202016-12-24 00:17:51 -0800681
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800682 if (field_match_vlan_upstream_with_link == True):
683 up_req = up_req_link
Zsolt Haraszti80175202016-12-24 00:17:51 -0800684 else:
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800685 up_req = up_req_pon
Zsolt Haraszti80175202016-12-24 00:17:51 -0800686
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800687 up_req /= PortIngressRuleTerminator()
688 up_req /= AddPortIngressRule()
689
690 msg = (
691 Ether(dst=device.mac_address) /
692 Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY) /
693 EOAMPayload(
694 body=CablelabsOUI() / DPoEOpcode_SetRequest() / up_req)
695 )
696
697 self.io_port.send(str(msg))
698
Nathan Knuthd8285e62017-01-11 14:18:43 -0600699 else:
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800700 raise Exception('Port should be 1 or 2 by our convention')
Nathan Knuthd8285e62017-01-11 14:18:43 -0600701
Nathan Knuth2976a3f2017-01-11 22:47:26 -0800702 except Exception, e:
703 log.exception('failed-to-install-flow', e=e, flow=flow)
Zsolt Harasztied091602016-12-08 13:36:38 -0800704
705 def update_flows_incrementally(self, device, flow_changes, group_changes):
706 raise NotImplementedError()
707
708 def send_proxied_message(self, proxy_address, msg):
Nathan Knuth6e57f332016-12-22 15:49:20 -0800709 log.info('send-proxied-message', proxy_address=proxy_address)
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800710 device = self.adapter_agent.get_device(proxy_address.device_id)
Nathan Knuthe69ceb12017-01-04 21:13:39 -0800711 frame = Ether(dst=device.mac_address) / \
Nathan Knuth6e57f332016-12-22 15:49:20 -0800712 Dot1Q(vlan=TIBIT_MGMT_VLAN, prio=TIBIT_MGMT_PRIORITY) / \
713 Dot1Q(vlan=proxy_address.channel_id, prio=TIBIT_MGMT_PRIORITY) / \
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800714 msg
Nathan Knuth6e57f332016-12-22 15:49:20 -0800715
Zsolt Haraszti348d1932016-12-10 01:10:07 -0800716 self.io_port.send(str(frame))
Zsolt Harasztied091602016-12-08 13:36:38 -0800717
718 def receive_proxied_message(self, proxy_address, msg):
719 raise NotImplementedError()
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800720
721 def receive_packet_out(self, logical_device_id, egress_port_no, msg):
722 log.info('packet-out', logical_device_id=logical_device_id,
723 egress_port_no=egress_port_no, msg_len=len(msg))
Nathan Knuthfe2b2e02017-01-06 07:29:02 -0800724
725 dev_id, logical_dev_id = self.vlan_to_device_ids[egress_port_no]
726 if logical_dev_id != logical_device_id:
727 raise Exception('Internal table mismatch')
728
729 tmp = Ether(msg)
730
731 frame = Ether(dst=tmp.dst, src=tmp.src) / \
732 Dot1Q(vlan=TIBIT_PACKET_OUT_VLAN) / \
733 Dot1Q(vlan=egress_port_no) / \
734 tmp.payload
735
736 self.io_port.send(str(frame))
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800737
738 def start_kpi_collection(self, device_id):
Nathan Knuthc99552d2017-01-19 11:23:32 -0600739 """ Periodic KPI metric collection from the device """
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800740 import random
741
Nathan Knuthc99552d2017-01-19 11:23:32 -0600742 # This is setup (for now) to be called from the adapter. Push
743 # architectures should be explored in the near future.
744 @inlineCallbacks
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800745 def _collect(device_id, prefix):
746
Nathan Knuth388eff32017-01-18 18:31:22 -0600747 pon_port_metrics = {}
748 links = []
749 olt_mac = next((mac for mac, device in self.device_ids.iteritems() if device == device_id), None)
750 links = [v[TIBIT_ONU_LINK_INDEX] for _,v in self.vlan_to_device_ids.iteritems()]
751
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800752 try:
Nathan Knuth388eff32017-01-18 18:31:22 -0600753 # Step 1: gather metrics from device
754 log.info('link stats frame', links=links)
755 for link in links:
756 stats_frame = self._make_stats_frame(mac_address=olt_mac, itype='olt', link=link)
757 self.io_port.send(stats_frame)
758
759 ## Add timeout mechanism so we can signal if we cannot reach
760 ## device
761 while True:
762 response = yield self.incoming_queues[olt_mac].get()
763 jdict = json.loads(response.payload.payload.body.load)
764 pon_port_metrics[link] = {k: int(v,16) for k,v in jdict['results'].iteritems()}
765 # verify response and if not the expected response
766 if 1: # TODO check if it is really what we expect, and wait if not
767 break
768
769 log.info('nni stats frame')
770 olt_nni_link = ''.join(l for l in olt_mac.split(':'))
771 stats_frame = self._make_stats_frame(mac_address=olt_mac, itype='eth', link=olt_nni_link)
772 self.io_port.send(stats_frame)
773
774 ## Add timeout mechanism so we can signal if we cannot reach
775 ## device
776 while True:
777 response = yield self.incoming_queues[olt_mac].get()
778 jdict = json.loads(response.payload.payload.body.load)
779 nni_port_metrics = {k: int(v,16) for k,v in jdict['results'].iteritems()}
780 # verify response and if not the expected response
781 if 1: # TODO check if it is really what we expect, and wait if not
782 break
783
Nathan Knuthc99552d2017-01-19 11:23:32 -0600784 olt_metrics = dict(
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800785 cpu_util=20 + 5 * random.random(),
786 buffer_util=10 + 10 * random.random()
787 )
788
789 # Step 2: prepare the KpiEvent for submission
790 # we can time-stamp them here (or could use time derived from OLT
791 ts = arrow.utcnow().timestamp
Nathan Knuthc99552d2017-01-19 11:23:32 -0600792 prefixes = {
793 # CPU Metrics (example)
794 prefix: MetricValuePairs(metrics=olt_metrics),
795 # OLT NNI port
796 prefix + '.nni': MetricValuePairs(metrics=nni_port_metrics)
797 }
798
799 for link in links:
800 # PON link ports
801 prefixes[prefix + '.pon.{}'.format(link)] = MetricValuePairs(metrics=pon_port_metrics[link])
802
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800803 kpi_event = KpiEvent(
804 type=KpiEventType.slice,
805 ts=ts,
Nathan Knuthc99552d2017-01-19 11:23:32 -0600806 prefixes=prefixes
Zsolt Harasztic5f740b2017-01-18 09:53:17 -0800807 )
808
809 # Step 3: submit
810 self.adapter_agent.submit_kpis(kpi_event)
811
812 except Exception as e:
813 log.exception('failed-to-submit-kpis', e=e)
814
815 prefix = 'voltha.{}.{}'.format(self.name, device_id)
816 lc = LoopingCall(_collect, device_id, prefix)
817 lc.start(interval=15) # TODO make this configurable
818