blob: 02662fc6c8dd05aec8fcd458bd92065843b7e3ff [file] [log] [blame]
Zsolt Haraszti66862032016-11-28 14:28:39 -08001#
Zsolt Haraszti3eb27a52017-01-03 21:56:48 -08002# Copyright 2017 the original author or authors.
Zsolt Haraszti66862032016-11-28 14:28:39 -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"""
18Model that captures the current state of a logical device
19"""
Zsolt Haraszti66862032016-11-28 14:28:39 -080020from collections import OrderedDict
21
22import structlog
23
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -080024from common.event_bus import EventBusClient
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080025from common.frameio.frameio import hexify
Zsolt Haraszti66862032016-11-28 14:28:39 -080026from voltha.core.config.config_proxy import CallbackType
27from voltha.core.device_graph import DeviceGraph
28from voltha.core.flow_decomposer import FlowDecomposer, \
29 flow_stats_entry_from_flow_mod_message, group_entry_from_group_mod, \
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080030 mk_flow_stat, in_port, vlan_vid, vlan_pcp, pop_vlan, output, set_field, \
31 push_vlan
Zsolt Haraszti66862032016-11-28 14:28:39 -080032from voltha.protos import third_party
33from voltha.protos import openflow_13_pb2 as ofp
34from voltha.protos.device_pb2 import Port
Zsolt Haraszti217a12e2016-12-19 16:37:55 -080035from voltha.protos.logical_device_pb2 import LogicalPort
Zsolt Haraszti66862032016-11-28 14:28:39 -080036from voltha.protos.openflow_13_pb2 import Flows, FlowGroups
Zsolt Haraszti66862032016-11-28 14:28:39 -080037
Zsolt Haraszti66862032016-11-28 14:28:39 -080038_ = third_party
39
40def mac_str_to_tuple(mac):
41 return tuple(int(d, 16) for d in mac.split(':'))
42
43
44class LogicalDeviceAgent(FlowDecomposer, DeviceGraph):
45
46 def __init__(self, core, logical_device):
47 self.core = core
Zsolt Haraszti217a12e2016-12-19 16:37:55 -080048 self.local_handler = core.get_local_handler()
Zsolt Haraszti66862032016-11-28 14:28:39 -080049 self.logical_device_id = logical_device.id
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080050
Zsolt Haraszti66862032016-11-28 14:28:39 -080051 self.root_proxy = core.get_proxy('/')
52 self.flows_proxy = core.get_proxy(
53 '/logical_devices/{}/flows'.format(logical_device.id))
54 self.groups_proxy = core.get_proxy(
55 '/logical_devices/{}/flow_groups'.format(logical_device.id))
56 self.self_proxy = core.get_proxy(
57 '/logical_devices/{}'.format(logical_device.id))
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080058
Zsolt Haraszti66862032016-11-28 14:28:39 -080059 self.flows_proxy.register_callback(
60 CallbackType.POST_UPDATE, self._flow_table_updated)
61 self.groups_proxy.register_callback(
62 CallbackType.POST_UPDATE, self._group_table_updated)
63 self.self_proxy.register_callback(
Zsolt Haraszti217a12e2016-12-19 16:37:55 -080064 CallbackType.POST_ADD, self._port_added)
Zsolt Haraszti66862032016-11-28 14:28:39 -080065 self.self_proxy.register_callback(
Zsolt Haraszti217a12e2016-12-19 16:37:55 -080066 CallbackType.POST_REMOVE, self._port_removed)
Zsolt Haraszti66862032016-11-28 14:28:39 -080067
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -080068 self.event_bus = EventBusClient()
69 self.packet_in_subscription = self.event_bus.subscribe(
70 topic='packet-in:{}'.format(logical_device.id),
71 callback=self.handle_packet_in_event)
72
Zsolt Haraszti89a27302016-12-08 16:53:06 -080073 self.log = structlog.get_logger(logical_device_id=logical_device.id)
74
Zsolt Haraszti91730da2016-12-12 12:54:38 -080075 self._routes = None
76
Zsolt Haraszti66862032016-11-28 14:28:39 -080077 def start(self):
Zsolt Haraszti89a27302016-12-08 16:53:06 -080078 self.log.debug('starting')
79 self.log.info('started')
Zsolt Haraszti66862032016-11-28 14:28:39 -080080 return self
81
82 def stop(self):
Zsolt Haraszti89a27302016-12-08 16:53:06 -080083 self.log.debug('stopping')
Zsolt Harasztic5c5d102016-12-07 21:12:27 -080084 self.flows_proxy.unregister_callback(
85 CallbackType.POST_UPDATE, self._flow_table_updated)
86 self.groups_proxy.unregister_callback(
87 CallbackType.POST_UPDATE, self._group_table_updated)
88 self.self_proxy.unregister_callback(
89 CallbackType.POST_ADD, self._port_list_updated)
90 self.self_proxy.unregister_callback(
91 CallbackType.POST_REMOVE, self._port_list_updated)
Zsolt Haraszti89a27302016-12-08 16:53:06 -080092 self.log.info('stopped')
Zsolt Haraszti66862032016-11-28 14:28:39 -080093
94 def announce_flows_deleted(self, flows):
95 for f in flows:
96 self.announce_flow_deleted(f)
97
98 def announce_flow_deleted(self, flow):
99 if flow.flags & ofp.OFPFF_SEND_FLOW_REM:
100 raise NotImplementedError("announce_flow_deleted")
101
102 def signal_flow_mod_error(self, code, flow_mod):
103 pass # TODO
104
105 def signal_flow_removal(self, code, flow):
106 pass # TODO
107
108 def signal_group_mod_error(self, code, group_mod):
109 pass # TODO
110
111 def update_flow_table(self, flow_mod):
112
113 command = flow_mod.command
114
115 if command == ofp.OFPFC_ADD:
116 self.flow_add(flow_mod)
117
118 elif command == ofp.OFPFC_DELETE:
119 self.flow_delete(flow_mod)
120
121 elif command == ofp.OFPFC_DELETE_STRICT:
122 self.flow_delete_strict(flow_mod)
123
124 elif command == ofp.OFPFC_MODIFY:
125 self.flow_modify(flow_mod)
126
127 elif command == ofp.OFPFC_MODIFY_STRICT:
128 self.flow_modify_strict(flow_mod)
129
130 else:
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800131 self.log.warn('unhandled-flow-mod',
132 command=command, flow_mod=flow_mod)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800133
134 def update_group_table(self, group_mod):
135
136 command = group_mod.command
137
138 if command == ofp.OFPGC_DELETE:
139 self.group_delete(group_mod)
140
141 elif command == ofp.OFPGC_ADD:
142 self.group_add(group_mod)
143
144 elif command == ofp.OFPGC_MODIFY:
145 self.group_modify(group_mod)
146
147 else:
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800148 self.log.warn('unhandled-group-mod',
149 command=command, group_mod=group_mod)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800150
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800151 # ~~~~~~~~~~~~~~~~~~~~~~~~~ LOW LEVEL FLOW HANDLERS ~~~~~~~~~~~~~~~~~~~~~~~
Zsolt Haraszti66862032016-11-28 14:28:39 -0800152
153 def flow_add(self, mod):
154 assert isinstance(mod, ofp.ofp_flow_mod)
155 assert mod.cookie_mask == 0
156
157 # read from model
158 flows = list(self.flows_proxy.get('/').items)
159
160 changed = False
161 check_overlap = mod.flags & ofp.OFPFF_CHECK_OVERLAP
162 if check_overlap:
163 if self.find_overlapping_flows(flows, mod, True):
164 self.signal_flow_mod_error(
165 ofp.OFPFMFC_OVERLAP, mod)
166 else:
167 # free to add as new flow
168 flow = flow_stats_entry_from_flow_mod_message(mod)
169 flows.append(flow)
170 changed = True
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800171 self.log.debug('flow-added', flow=mod)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800172
173 else:
174 flow = flow_stats_entry_from_flow_mod_message(mod)
175 idx = self.find_flow(flows, flow)
176 if idx >= 0:
177 old_flow = flows[idx]
178 if not (mod.flags & ofp.OFPFF_RESET_COUNTS):
179 flow.byte_count = old_flow.byte_count
180 flow.packet_count = old_flow.packet_count
181 flows[idx] = flow
182 changed = True
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800183 self.log.debug('flow-updated', flow=flow)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800184
185 else:
186 flows.append(flow)
187 changed = True
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800188 self.log.debug('flow-added', flow=mod)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800189
190 # write back to model
191 if changed:
192 self.flows_proxy.update('/', Flows(items=flows))
193
194 def flow_delete(self, mod):
195 assert isinstance(mod, ofp.ofp_flow_mod)
196
197 # read from model
198 flows = list(self.flows_proxy.get('/').items)
199
200 # build a list of what to keep vs what to delete
201 to_keep = []
202 to_delete = []
203 for f in flows:
204 if self.flow_matches_spec(f, mod):
205 to_delete.append(f)
206 else:
207 to_keep.append(f)
208
209 # replace flow table with keepers
210 flows = to_keep
211
212 # write back
213 if to_delete:
214 self.flows_proxy.update('/', Flows(items=flows))
215
216 # send notifications for discarded flow as required by OpenFlow
217 self.announce_flows_deleted(to_delete)
218
219 def flow_delete_strict(self, mod):
220 assert isinstance(mod, ofp.ofp_flow_mod)
221
222 # read from model
223 flows = list(self.flows_proxy.get('/').items)
224 changed = False
225
226 flow = flow_stats_entry_from_flow_mod_message(mod)
227 idx = self.find_flow(flows, flow)
228 if (idx >= 0):
229 del flows[idx]
230 changed = True
231 else:
232 # TODO need to check what to do with this case
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800233 self.log.warn('flow-cannot-delete', flow=flow)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800234
235 if changed:
236 self.flows_proxy.update('/', Flows(items=flows))
237
238 def flow_modify(self, mod):
239 raise NotImplementedError()
240
241 def flow_modify_strict(self, mod):
242 raise NotImplementedError()
243
244 def find_overlapping_flows(self, flows, mod, return_on_first=False):
245 """
246 Return list of overlapping flow(s)
247 Two flows overlap if a packet may match both and if they have the
248 same priority.
249 :param mod: Flow request
250 :param return_on_first: if True, return with the first entry
251 :return:
252 """
253 return [] # TODO finish implementation
254
255 @classmethod
256 def find_flow(cls, flows, flow):
257 for i, f in enumerate(flows):
258 if cls.flow_match(f, flow):
259 return i
260 return -1
261
262 @staticmethod
263 def flow_match(f1, f2):
264 keys_matter = ('table_id', 'priority', 'flags', 'cookie', 'match')
265 for key in keys_matter:
266 if getattr(f1, key) != getattr(f2, key):
267 return False
268 return True
269
270 @classmethod
271 def flow_matches_spec(cls, flow, flow_mod):
272 """
273 Return True if given flow (ofp_flow_stats) is "covered" by the
274 wildcard flow_mod (ofp_flow_mod), taking into consideration of
275 both exact mactches as well as masks-based match fields if any.
276 Otherwise return False
277 :param flow: ofp_flow_stats
278 :param mod: ofp_flow_mod
279 :return: Bool
280 """
281
282 assert isinstance(flow, ofp.ofp_flow_stats)
283 assert isinstance(flow_mod, ofp.ofp_flow_mod)
284
285 # Check if flow.cookie is covered by mod.cookie and mod.cookie_mask
286 if (flow.cookie & flow_mod.cookie_mask) != \
287 (flow_mod.cookie & flow_mod.cookie_mask):
288 return False
289
290 # Check if flow.table_id is covered by flow_mod.table_id
291 if flow_mod.table_id != ofp.OFPTT_ALL and \
292 flow.table_id != flow_mod.table_id:
293 return False
294
295 # Check out_port
296 if flow_mod.out_port != ofp.OFPP_ANY and \
297 not cls.flow_has_out_port(flow, flow_mod.out_port):
298 return False
299
300 # Check out_group
301 if flow_mod.out_group != ofp.OFPG_ANY and \
302 not cls.flow_has_out_group(flow, flow_mod.out_group):
303 return False
304
305 # Priority is ignored
306
307 # Check match condition
308 # If the flow_mod match field is empty, that is a special case and
309 # indicates the flow entry matches
310 match = flow_mod.match
311 assert isinstance(match, ofp.ofp_match)
Zsolt Haraszti91730da2016-12-12 12:54:38 -0800312 if not match.oxm_fields:
Zsolt Haraszti66862032016-11-28 14:28:39 -0800313 # If we got this far and the match is empty in the flow spec,
314 # than the flow matches
315 return True
316 else:
317 raise NotImplementedError(
318 "flow_matches_spec(): No flow match analysis yet")
319
320 @staticmethod
321 def flow_has_out_port(flow, out_port):
322 """
323 Return True if flow has a output command with the given out_port
324 """
325 assert isinstance(flow, ofp.ofp_flow_stats)
326 for instruction in flow.instructions:
327 assert isinstance(instruction, ofp.ofp_instruction)
328 if instruction.type == ofp.OFPIT_APPLY_ACTIONS:
329 for action in instruction.actions.actions:
330 assert isinstance(action, ofp.ofp_action)
331 if action.type == ofp.OFPAT_OUTPUT and \
332 action.output.port == out_port:
333 return True
334
335 # otherwise...
336 return False
337
338 @staticmethod
339 def flow_has_out_group(flow, group_id):
340 """
341 Return True if flow has a output command with the given out_group
342 """
343 assert isinstance(flow, ofp.ofp_flow_stats)
344 for instruction in flow.instructions:
345 assert isinstance(instruction, ofp.ofp_instruction)
346 if instruction.type == ofp.OFPIT_APPLY_ACTIONS:
347 for action in instruction.actions.actions:
348 assert isinstance(action, ofp.ofp_action)
349 if action.type == ofp.OFPAT_GROUP and \
350 action.group.group_id == group_id:
351 return True
352
353 # otherwise...
354 return False
355
356 def flows_delete_by_group_id(self, flows, group_id):
357 """
358 Delete any flow(s) referring to given group_id
359 :param group_id:
360 :return: None
361 """
362 to_keep = []
363 to_delete = []
364 for f in flows:
365 if self.flow_has_out_group(f, group_id):
366 to_delete.append(f)
367 else:
368 to_keep.append(f)
369
370 # replace flow table with keepers
371 flows = to_keep
372
373 # send notification to deleted ones
374 self.announce_flows_deleted(to_delete)
375
376 return bool(to_delete), flows
377
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800378 # ~~~~~~~~~~~~~~~~~~~~~ LOW LEVEL GROUP HANDLERS ~~~~~~~~~~~~~~~~~~~~~~~~~~
Zsolt Haraszti66862032016-11-28 14:28:39 -0800379
380 def group_add(self, group_mod):
381 assert isinstance(group_mod, ofp.ofp_group_mod)
382
383 groups = OrderedDict((g.desc.group_id, g)
384 for g in self.groups_proxy.get('/').items)
385 changed = False
386
387 if group_mod.group_id in groups:
388 self.signal_group_mod_error(ofp.OFPGMFC_GROUP_EXISTS, group_mod)
389 else:
390 group_entry = group_entry_from_group_mod(group_mod)
391 groups[group_mod.group_id] = group_entry
392 changed = True
393
394 if changed:
395 self.groups_proxy.update('/', FlowGroups(items=groups.values()))
396
397 def group_delete(self, group_mod):
398 assert isinstance(group_mod, ofp.ofp_group_mod)
399
400 groups = OrderedDict((g.desc.group_id, g)
401 for g in self.groups_proxy.get('/').items)
402 groups_changed = False
403 flows_changed = False
404
405 group_id = group_mod.group_id
406 if group_id == ofp.OFPG_ALL:
407 # TODO we must delete all flows that point to this group and
408 # signal controller as requested by flow's flag
409 groups = OrderedDict()
410 groups_changed = True
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800411 self.log.debug('all-groups-deleted')
Zsolt Haraszti66862032016-11-28 14:28:39 -0800412
413 else:
414 if group_id not in groups:
415 # per openflow spec, this is not an error
416 pass
417
418 else:
419 flows = list(self.flows_proxy.get('/').items)
Zsolt Haraszti85f12852016-12-24 08:30:58 -0800420 flows_changed, flows = self.flows_delete_by_group_id(
421 flows, group_id)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800422 del groups[group_id]
423 groups_changed = True
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800424 self.log.debug('group-deleted', group_id=group_id)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800425
426 if groups_changed:
427 self.groups_proxy.update('/', FlowGroups(items=groups.values()))
428 if flows_changed:
429 self.flows_proxy.update('/', Flows(items=flows))
430
431 def group_modify(self, group_mod):
432 assert isinstance(group_mod, ofp.ofp_group_mod)
433
434 groups = OrderedDict((g.desc.group_id, g)
435 for g in self.groups_proxy.get('/').items)
436 changed = False
437
438 if group_mod.group_id not in groups:
439 self.signal_group_mod_error(
440 ofp.OFPGMFC_INVALID_GROUP, group_mod)
441 else:
442 # replace existing group entry with new group definition
443 group_entry = group_entry_from_group_mod(group_mod)
444 groups[group_mod.group_id] = group_entry
445 changed = True
446
447 if changed:
448 self.groups_proxy.update('/', FlowGroups(items=groups.values()))
449
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800450 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PACKET_OUT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Zsolt Haraszti66862032016-11-28 14:28:39 -0800451
452 def packet_out(self, ofp_packet_out):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800453 self.log.info('packet-out', packet=ofp_packet_out)
454 topic = 'packet-out:{}'.format(self.logical_device_id)
455 self.event_bus.publish(topic, ofp_packet_out)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800456
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800457 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PACKET_IN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
458
459 def handle_packet_in_event(self, _, msg):
460 self.log.debug('handle-packet-in', msg=msg)
461 logical_port_no, packet = msg
462 packet_in = ofp.ofp_packet_in(
463 # buffer_id=0,
464 reason=ofp.OFPR_ACTION,
465 # table_id=0,
466 # cookie=0,
467 match=ofp.ofp_match(
468 type=ofp.OFPMT_OXM,
469 oxm_fields=[
470 ofp.ofp_oxm_field(
471 oxm_class=ofp.OFPXMC_OPENFLOW_BASIC,
472 ofb_field=in_port(logical_port_no)
473 )
474 ]
475 ),
476 data=packet
477 )
478 self.packet_in(packet_in)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800479
480 def packet_in(self, ofp_packet_in):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800481 self.log.info('packet-in', logical_device_id=self.logical_device_id,
482 pkt=ofp_packet_in, data=hexify(ofp_packet_in.data))
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800483 self.local_handler.send_packet_in(
484 self.logical_device_id, ofp_packet_in)
Zsolt Haraszti66862032016-11-28 14:28:39 -0800485
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800486 # ~~~~~~~~~~~~~~~~~~~~~ FLOW TABLE UPDATE HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~
Zsolt Haraszti66862032016-11-28 14:28:39 -0800487
488 def _flow_table_updated(self, flows):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800489 self.log.debug('flow-table-updated',
Zsolt Haraszti66862032016-11-28 14:28:39 -0800490 logical_device_id=self.logical_device_id, flows=flows)
491
492 # TODO we have to evolve this into a policy-based, event based pattern
493 # This is a raw implementation of the specific use-case with certain
494 # built-in assumptions, and not yet device vendor specific. The policy-
495 # based refinement will be introduced that later.
496
497 groups = self.groups_proxy.get('/').items
498 device_rules_map = self.decompose_rules(flows.items, groups)
499 for device_id, (flows, groups) in device_rules_map.iteritems():
500 self.root_proxy.update('/devices/{}/flows'.format(device_id),
501 Flows(items=flows.values()))
502 self.root_proxy.update('/devices/{}/flow_groups'.format(device_id),
503 FlowGroups(items=groups.values()))
504
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800505 # ~~~~~~~~~~~~~~~~~~~~ GROUP TABLE UPDATE HANDLING ~~~~~~~~~~~~~~~~~~~~~~~~
Zsolt Haraszti66862032016-11-28 14:28:39 -0800506
507 def _group_table_updated(self, flow_groups):
Zsolt Haraszti89a27302016-12-08 16:53:06 -0800508 self.log.debug('group-table-updated',
Zsolt Haraszti66862032016-11-28 14:28:39 -0800509 logical_device_id=self.logical_device_id,
510 flow_groups=flow_groups)
511
512 flows = self.flows_proxy.get('/').items
513 device_flows_map = self.decompose_rules(flows, flow_groups.items)
514 for device_id, (flows, groups) in device_flows_map.iteritems():
515 self.root_proxy.update('/devices/{}/flows'.format(device_id),
516 Flows(items=flows.values()))
517 self.root_proxy.update('/devices/{}/flow_groups'.format(device_id),
518 FlowGroups(items=groups.values()))
519
Zsolt Haraszti8925d1f2016-12-21 00:45:19 -0800520 # ~~~~~~~~~~~~~~~~~~~ APIs NEEDED BY FLOW DECOMPOSER ~~~~~~~~~~~~~~~~~~~~~~
Zsolt Haraszti66862032016-11-28 14:28:39 -0800521
Zsolt Haraszti217a12e2016-12-19 16:37:55 -0800522 def _port_added(self, port):
523 assert isinstance(port, LogicalPort)
524 self._port_list_updated(port)
525 self.local_handler.send_port_change_event(
526 device_id=self.logical_device_id,
527 port_status=ofp.ofp_port_status(
528 reason=ofp.OFPPR_ADD,
529 desc=port.ofp_port
530 )
531 )
532
533 def _port_removed(self, port):
534 assert isinstance(port, LogicalPort)
535 self._port_list_updated(port)
536 self.local_handler.send_port_change_event(
537 device_id=self.logical_device_id,
538 port_status=ofp.ofp_port_status(
539 reason=ofp.OFPPR_DELETE,
540 desc=port.ofp_port
541 )
542 )
543
544 # TODO not yet hooked up
545 def _port_changed(self, port):
546 assert isinstance(port, LogicalPort)
547 self.local_handler.send_port_change_event(
548 device_id=self.logical_device_id,
549 port_status=ofp.ofp_port_status(
550 reason=ofp.OFPPR_MODIFY,
551 desc=port.ofp_port
552 )
553 )
554
Zsolt Haraszti66862032016-11-28 14:28:39 -0800555 def _port_list_updated(self, _):
556 # invalidate the graph and the route table
557 self._invalidate_cached_tables()
558
559 def _invalidate_cached_tables(self):
560 self._routes = None
561 self._default_rules = None
562 self._nni_logical_port_no = None
563
564 def _assure_cached_tables_up_to_date(self):
565 if self._routes is None:
566 logical_ports = self.self_proxy.get('/ports')
567 graph, self._routes = self.compute_routes(
568 self.root_proxy, logical_ports)
569 self._default_rules = self._generate_default_rules(graph)
570 root_ports = [p for p in logical_ports if p.root_port]
571 assert len(root_ports) == 1
572 self._nni_logical_port_no = root_ports[0].ofp_port.port_no
573
574
575 def _generate_default_rules(self, graph):
576
577 def root_device_default_rules(device):
578 ports = self.root_proxy.get('/devices/{}/ports'.format(device.id))
579 upstream_ports = [
580 port for port in ports if port.type == Port.ETHERNET_NNI
581 ]
582 assert len(upstream_ports) == 1
583 downstream_ports = [
584 port for port in ports if port.type == Port.PON_OLT
585 ]
586 assert len(downstream_ports) == 1, \
587 'Initially, we only handle one PON port'
588 flows = OrderedDict((f.id, f) for f in [
589 mk_flow_stat(
590 priority=2000,
591 match_fields=[
592 in_port(upstream_ports[0].port_no),
593 vlan_vid(ofp.OFPVID_PRESENT | 4000),
594 vlan_pcp(0)
595 ],
596 actions=[
597 pop_vlan(),
598 output(downstream_ports[0].port_no)
599 ]
600 )
601 ])
602 groups = OrderedDict()
603 return flows, groups
604
605 def leaf_device_default_rules(device):
606 ports = self.root_proxy.get('/devices/{}/ports'.format(device.id))
607 upstream_ports = [
608 port for port in ports if port.type == Port.PON_ONU
609 ]
610 assert len(upstream_ports) == 1
611 downstream_ports = [
612 port for port in ports if port.type == Port.ETHERNET_UNI
613 ]
614 assert len(downstream_ports) == 1
615 flows = OrderedDict((f.id, f) for f in [
616 mk_flow_stat(
Zsolt Harasztic69bd212016-12-13 15:13:41 -0800617 priority=500,
Zsolt Haraszti66862032016-11-28 14:28:39 -0800618 match_fields=[
619 in_port(downstream_ports[0].port_no),
620 vlan_vid(ofp.OFPVID_PRESENT | 0)
621 ],
622 actions=[
623 set_field(vlan_vid(ofp.OFPVID_PRESENT | device.vlan)),
624 output(upstream_ports[0].port_no)
625 ]
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800626 ),
627 mk_flow_stat(
628 priority=500,
629 match_fields=[
630 in_port(downstream_ports[0].port_no),
631 vlan_vid(0)
632 ],
633 actions=[
634 push_vlan(0x8100),
635 set_field(vlan_vid(ofp.OFPVID_PRESENT | device.vlan)),
636 output(upstream_ports[0].port_no)
637 ]
638 ),
639 mk_flow_stat(
640 priority=500,
641 match_fields=[
642 in_port(upstream_ports[0].port_no),
643 vlan_vid(ofp.OFPVID_PRESENT | device.vlan)
644 ],
645 actions=[
646 set_field(vlan_vid(ofp.OFPVID_PRESENT | 0)),
647 output(downstream_ports[0].port_no)
648 ]
649 ),
Zsolt Haraszti66862032016-11-28 14:28:39 -0800650 ])
651 groups = OrderedDict()
652 return flows, groups
653
654 root_device_id = self.self_proxy.get('/').root_device_id
655 rules = {}
656 for node_key in graph.nodes():
657 node = graph.node[node_key]
658 device = node.get('device', None)
659 if device is None:
660 continue
661 if device.id == root_device_id:
662 rules[device.id] = root_device_default_rules(device)
663 else:
664 rules[device.id] = leaf_device_default_rules(device)
665 return rules
666
667 def get_route(self, ingress_port_no, egress_port_no):
668 self._assure_cached_tables_up_to_date()
Zsolt Haraszti91730da2016-12-12 12:54:38 -0800669 if egress_port_no is not None and \
670 (egress_port_no & 0x7fffffff) == ofp.OFPP_CONTROLLER:
Zsolt Haraszti66862032016-11-28 14:28:39 -0800671 # treat it as if the output port is the NNI of the OLT
672 egress_port_no = self._nni_logical_port_no
Zsolt Haraszti91730da2016-12-12 12:54:38 -0800673
674 # If ingress_port is not specified (None), it may be a wildcarded
675 # route if egress_port is OFPP_CONTROLLER or _nni_logical_port,
676 # in which case we need to create a half-route where only the egress
677 # hop is filled, the first hope is None
678 if ingress_port_no is None and \
679 egress_port_no == self._nni_logical_port_no:
680 # We can use the 2nd hop of any upstream route, so just find the
681 # first upstream:
682 for (ingress, egress), route in self._routes.iteritems():
683 if egress == self._nni_logical_port_no:
684 return [None, route[1]]
685 raise Exception('not a single upstream route')
686
687 # If egress_port is not specified (None), we can also can return a
688 # "half" route
Zsolt Harasztiafafff32016-12-12 23:14:09 -0800689 if egress_port_no is None:
Zsolt Haraszti91730da2016-12-12 12:54:38 -0800690 for (ingress, egress), route in self._routes.iteritems():
Zsolt Harasztiafafff32016-12-12 23:14:09 -0800691 if ingress == ingress_port_no:
Zsolt Haraszti91730da2016-12-12 12:54:38 -0800692 return [route[0], None]
693 raise Exception('not a single downstream route')
694
Zsolt Haraszti66862032016-11-28 14:28:39 -0800695 return self._routes[(ingress_port_no, egress_port_no)]
696
697 def get_all_default_rules(self):
698 self._assure_cached_tables_up_to_date()
699 return self._default_rules