blob: cb8dd4a7bd181e3144288ac0c50714c70662eac3 [file] [log] [blame]
Chip Bolingf5af85d2019-02-12 15:36:17 -06001# Copyright 2017-present Adtran, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import structlog
16from evc import EVC
17from evc_map import EVCMap
18from enum import IntEnum
19from utility_evc import UtilityEVC
20import pyvoltha.common.openflow.utils as fd
21from pyvoltha.protos.openflow_13_pb2 import OFPP_MAX, OFPP_CONTROLLER, OFPVID_PRESENT, OFPXMC_OPENFLOW_BASIC
22from twisted.internet.defer import returnValue, inlineCallbacks, gatherResults
23
24log = structlog.get_logger()
25
26# IP Protocol numbers
27_supported_ip_protocols = [
28 1, # ICMP
29 2, # IGMP
30 6, # TCP
31 17, # UDP
32]
33
34
35class FlowEntry(object):
36 """
37 Provide a class that wraps the flow rule and also provides state/status for a FlowEntry.
38
39 When a new flow is sent, it is first decoded to check for any potential errors. If None are
40 found, the entry is created and it is analyzed to see if it can be combined to with any other flows
41 to create or modify an existing EVC.
42
43 Note: Since only E-LINE is supported, modification of an existing EVC is not performed.
44 """
45 class PortType(IntEnum):
46 NNI = 0 # NNI Port
47 UNI = 1 # UNI Port
48 PON = 2 # PON Port (all UNIs on PON)
49 CONTROLLER = 3 # Controller port (packet in/out)
50
51 class FlowDirection(IntEnum):
52 UPSTREAM = 0 # UNI port to NNI Port
53 DOWNSTREAM = 1 # NNI port to UNI Port
54 CONTROLLER_UNI = 2 # Trap packet on UNI and send to controller
55 NNI_PON = 3 # NNI port to PON Port (all UNIs) - Utility VLAN & multicast
56
57 # The following are not yet supported
58 CONTROLLER_NNI = 4 # Trap packet on NNI and send to controller
59 CONTROLLER_PON = 5 # Trap packet on all UNIs of a PON and send to controller
60 NNI_NNI = 6 # NNI port to NNI Port
61 UNI_UNI = 7 # UNI port to UNI Port
62 OTHER = 9 # Unable to determine
63
64 upstream_flow_types = {FlowDirection.UPSTREAM, FlowDirection.CONTROLLER_UNI}
65 downstream_flow_types = {FlowDirection.DOWNSTREAM, FlowDirection.NNI_PON}
66
67 LEGACY_CONTROL_VLAN = 4000
68
69 # Well known EtherTypes
70 class EtherType(IntEnum):
71 EAPOL = 0x888E
72 IPv4 = 0x0800
73 IPv6 = 0x86DD
74 ARP = 0x0806
75 LLDP = 0x88CC
76
77 # Well known IP Protocols
78 class IpProtocol(IntEnum):
79 IGMP = 2
80 UDP = 17
81
82 def __init__(self, flow, handler):
83 self._flow = flow
84 self._handler = handler
85 self.flow_id = flow.id
86 self.evc = None # EVC this flow is part of
87 self.evc_map = None # EVC-MAP this flow is part of
88 self._flow_direction = FlowEntry.FlowDirection.OTHER
89 self._logical_port = None # Currently ONU VID is logical port if not doing xPON
90 self._is_multicast = False
91 self._is_acl_flow = False
92 self._bandwidth = None
93
94 # A value used to locate possible related flow entries
95 self.signature = None
96 self.downstream_signature = None # Valid for upstream EVC-MAP Flows
97
98 # Selection properties
99 self.in_port = None
100 self.vlan_id = None
101 self.pcp = None
102 self.eth_type = None
103 self.ip_protocol = None
104 self.ipv4_dst = None
105 self.udp_dst = None # UDP Port #
106 self.udp_src = None # UDP Port #
107 self.inner_vid = None
108
109 # Actions
110 self.output = None
111 self.pop_vlan = False
112 self.push_vlan_tpid = None
113 self.push_vlan_id = None
114
115 self._name = self.create_flow_name()
116
117 def __str__(self):
118 return 'flow_entry: {}, in: {}, out: {}, vid: {}, inner:{}, eth: {}, IP: {}'.format(
119 self.name, self.in_port, self.output, self.vlan_id, self.inner_vid,
120 self.eth_type, self.ip_protocol)
121
122 def __repr__(self):
123 return str(self)
124
125 @property
126 def name(self):
127 return self._name # TODO: Is a name really needed in production?
128
129 def create_flow_name(self):
130 return 'flow-{}-{}'.format(self.device_id, self.flow_id)
131
132 @property
133 def flow(self):
134 return self._flow
135
136 @property
137 def handler(self):
138 return self._handler
139
140 @property
141 def device_id(self):
142 return self.handler.device_id
143
144 @property
145 def bandwidth(self):
146 """ Bandwidth in Mbps (if any) """
147 return self._bandwidth
148
149 @property
150 def flow_direction(self):
151 return self._flow_direction
152
153 @property
154 def is_multicast_flow(self):
155 return self._is_multicast
156
157 @property
158 def is_acl_flow(self):
159 return self._is_acl_flow or self._needs_acl_support
160
161 @property
162 def logical_port(self):
163 return self._logical_port # NNI or UNI Logical Port
164
165 @staticmethod
166 def create(flow, handler):
167 """
168 Create the appropriate FlowEntry wrapper for the flow. This method returns a two
169 results.
170
171 The first result is the flow entry that was created. This could be a match to an
172 existing flow since it is a bulk update. None is returned only if no match to
173 an existing entry is found and decode failed (unsupported field)
174
175 The second result is the EVC this flow should be added to. This could be an
176 existing flow (so your adding another EVC-MAP) or a brand new EVC (no existing
177 EVC-MAPs). None is returned if there are not a valid EVC that can be created YET.
178
179 :param flow: (Flow) Flow entry passed to VOLTHA adapter
180 :param handler: (AdtranDeviceHandler) handler for the device
181 :return: (FlowEntry, EVC)
182 """
183 # Exit early if it already exists
184 try:
185 flow_entry = FlowEntry(flow, handler)
186
187 ######################################################################
188 # Decode the flow entry
189 if not flow_entry._decode(flow):
190 # TODO: When we support individual flow mods, we will need to return
191 # this flow back always
192 return None, None
193
194 ######################################################################
195 # Initialize flow_entry database (dicts) if needed and determine if
196 # the flows have already been handled.
197 downstream_sig_table = handler.downstream_flows
198 upstream_flow_table = handler.upstream_flows
199
200 log.debug('flow-entry-decoded', flow=flow_entry, signature=flow_entry.signature,
201 downstream_signature=flow_entry.downstream_signature)
202
203 if flow_entry.flow_direction in FlowEntry.upstream_flow_types and\
204 flow_entry.flow_id in upstream_flow_table:
205 log.debug('flow-entry-upstream-exists', flow=flow_entry)
206 return flow_entry, None
207
208 if flow_entry.flow_direction in FlowEntry.downstream_flow_types:
209 sig_table = downstream_sig_table.get(flow_entry.signature)
210 if sig_table is not None and flow_entry in sig_table.flows:
211 log.debug('flow-entry-downstream-exists', flow=flow_entry)
212 return flow_entry, None
213
214 ######################################################################
215 # Look for any matching flows in the other direction that might help
216 # make an EVC and then save it off in the device specific flow table
217 #
218 # TODO: For now, only support for E-LINE services between NNI and UNI
219 downstream_flow = None
220 upstream_flows = None
221 downstream_sig = None
222
223 if flow_entry._is_multicast: # Uni-directional flow
224 assert flow_entry._flow_direction in FlowEntry.downstream_flow_types, \
225 'Only downstream Multicast supported'
226 downstream_flow = flow_entry
227 downstream_sig = flow_entry.signature
228 upstream_flows = []
229
230 elif flow_entry.flow_direction in FlowEntry.downstream_flow_types:
231 downstream_flow = flow_entry
232 downstream_sig = flow_entry.signature
233
234 elif flow_entry.flow_direction in FlowEntry.upstream_flow_types:
235 downstream_sig = flow_entry.downstream_signature
236
237 if downstream_sig is None:
238 # TODO: When we support individual flow mods, we will need to return
239 # this flow back always
240 log.debug('flow-entry-empty-downstream', flow=flow_entry)
241 return None, None
242
243 # Make sure a slot exists for the downstream signature and get its flow table
244 downstream_sig_table = downstream_sig_table.add(downstream_sig)
245 evc = downstream_sig_table.evc
246
247 # Save the new flow_entry to proper flow table
248 if flow_entry.flow_direction in FlowEntry.upstream_flow_types:
249 upstream_flow_table.add(flow_entry)
250 downstream_flow = evc.flow_entry if evc is not None else \
251 next((_flow for _flow in downstream_sig_table.flows.itervalues()
252 if isinstance(_flow, FlowEntry)), None)
253
254 elif flow_entry.flow_direction in FlowEntry.downstream_flow_types:
255 downstream_sig_table.flows.add(flow_entry)
256
257 # Now find all the upstream flows
258 if downstream_flow is not None:
259 upstream_flows = [_flow for _flow in upstream_flow_table.itervalues()
260 if _flow.downstream_signature == downstream_flow.signature]
261 if len(upstream_flows) == 0 and not downstream_flow.is_multicast_flow:
262 upstream_flows = None
263
264 log.debug('flow-entry-search-results', flow=flow_entry,
265 downstream_flow=downstream_flow, upstream_flows=upstream_flows)
266
267 ######################################################################
268 # Compute EVC and and maps
269 evc = FlowEntry._create_evc_and_maps(evc, downstream_flow, upstream_flows)
270
271 # Save off EVC (if we have one) for this flow if it is new
272 if evc is not None and evc.valid and downstream_sig_table.evc is None:
273 downstream_sig_table.evc = evc
274
275 return flow_entry, evc
276
277 except Exception as e:
278 log.exception('flow-entry-processing', e=e)
279 return None, None
280
281 @staticmethod
282 def _create_evc_and_maps(evc, downstream_flow, upstream_flows):
283 """
284 Give a set of flows, find (or create) the EVC and any needed EVC-MAPs
285
286 :param evc: (EVC) Existing EVC for downstream flow. May be null if not created
287 :param downstream_flow: (FlowEntry) NNI -> UNI flow (provides much of the EVC values)
288 :param upstream_flows: (list of FlowEntry) UNI -> NNI flows (provides much of the EVC-MAP values)
289
290 :return: EVC object
291 """
292 log.debug('flow-evc-and-maps', downstream_flow=downstream_flow,
293 upstream_flows=upstream_flows)
294
295 if (evc is None and downstream_flow is None) or upstream_flows is None:
296 return None
297
298 # Get any existing EVC if a flow is already created
299 if downstream_flow.evc is None:
300 if evc is not None:
301 downstream_flow.evc = evc
302
303 elif downstream_flow.is_multicast_flow:
304 from mcast import MCastEVC
305 downstream_flow.evc = MCastEVC.create(downstream_flow)
306
307 elif downstream_flow.is_acl_flow:
308 downstream_flow.evc = downstream_flow.get_utility_evc()
309 else:
310 downstream_flow.evc = EVC(downstream_flow)
311
312 if not downstream_flow.evc.valid:
313 log.debug('flow-evc-and-maps-downstream-invalid',
314 downstream_flow=downstream_flow,
315 upstream_flows=upstream_flows)
316 return None
317
318 # Create EVC-MAPs. Note upstream_flows is empty list for multicast
319 # For Packet In/Out support. The upstream flows for will have matching
320 # signatures. So the first one to get created should create the EVC and
321 # if it needs and ACL, do so then. The second one should just reference
322 # the first map.
323 #
324 # If the second has and ACL, then it should add it to the map.
325 # TODO: What to do if the second (or third, ...) is the data one.
326 # What should it do then?
327 sig_map_map = {f.signature: f.evc_map for f in upstream_flows
328 if f.evc_map is not None}
329
330 for flow in upstream_flows:
331 if flow.evc_map is None:
332 if flow.signature in sig_map_map:
333 # Found an explicitly matching existing EVC-MAP. Add flow to this EVC-MAP
334 flow.evc_map = sig_map_map[flow.signature].add_flow(flow, downstream_flow.evc)
335 else:
336 # May need to create a MAP or search for an existing ACL/user EVC-Map
337 # upstream_flow_table = _existing_upstream_flow_entries[flow.device_id]
338 upstream_flow_table = flow.handler.upstream_flows
339 existing_flow = EVCMap.find_matching_ingress_flow(flow, upstream_flow_table)
340
341 if existing_flow is None:
342 flow.evc_map = EVCMap.create_ingress_map(flow, downstream_flow.evc)
343 else:
344 flow.evc_map = existing_flow.add_flow(flow, downstream_flow.evc)
345
346 all_maps_valid = all(flow.evc_map.valid for flow in upstream_flows) \
347 or downstream_flow.is_multicast_flow
348
349 log.debug('flow-evc-and-maps-downstream',
350 downstream_flow=downstream_flow,
351 upstream_flows=upstream_flows, all_valid=all_maps_valid)
352
353 return downstream_flow.evc if all_maps_valid else None
354
355 def get_utility_evc(self, use_default_vlan_id=False):
356 assert self.is_acl_flow, 'Utility evcs are for acl flows only'
357 return UtilityEVC.create(self, use_default_vlan_id)
358
359 @property
360 def _needs_acl_support(self):
361 if self.ipv4_dst is not None: # In case MCAST downstream has ACL on it
362 return False
363
364 return self.eth_type is not None or self.ip_protocol is not None or\
365 self.ipv4_dst is not None or self.udp_dst is not None or self.udp_src is not None
366
367 def _decode(self, flow):
368 """
369 Examine flow rules and extract appropriate settings
370 """
371 log.debug('start-decode')
372 status = self._decode_traffic_selector(flow) and self._decode_traffic_treatment(flow)
373
374 # Determine direction of the flow and apply appropriate modifications
375 # to the decoded flows
376 if status:
377 if not self._decode_flow_direction():
378 return False
379
380 if self._flow_direction in FlowEntry.downstream_flow_types:
381 status = self._apply_downstream_mods()
382
383 elif self._flow_direction in FlowEntry.upstream_flow_types:
384 status = self._apply_upstream_mods()
385
386 else:
387 # TODO: Need to code this - Perhaps this is an NNI_PON for Multicast support?
388 log.error('unsupported-flow-direction')
389 status = False
390
391 log.debug('flow-evc-decode', direction=self._flow_direction, is_acl=self._is_acl_flow,
392 inner_vid=self.inner_vid, vlan_id=self.vlan_id, pop_vlan=self.pop_vlan,
393 push_vid=self.push_vlan_id, status=status)
394
395 # Create a signature that will help locate related flow entries on a device.
396 if status:
397 # These are not exact, just ones that may be put together to make an EVC. The
398 # basic rules are:
399 #
400 # 1 - Port numbers in increasing order
401 ports = [self.in_port, self.output]
402 ports.sort()
403 assert len(ports) == 2, 'Invalid port count: {}'.format(len(ports))
404
405 # 3 - The outer VID
406 # 4 - The inner VID. Wildcard if downstream
407 if self.push_vlan_id is None:
408 outer = self.vlan_id
409 inner = self.inner_vid
410 else:
411 outer = self.push_vlan_id
412 inner = self.vlan_id
413
414 upstream_sig = '{}'.format(ports[0])
415 downstream_sig = '{}'.format(ports[0])
416 upstream_sig += '.{}'.format(ports[1])
417 downstream_sig += '.{}'.format(ports[1] if self.handler.is_nni_port(ports[1]) else '*')
418
419 upstream_sig += '.{}.{}'.format(outer, inner)
420 downstream_sig += '.{}.*'.format(outer)
421
422 if self._flow_direction in FlowEntry.downstream_flow_types:
423 self.signature = downstream_sig
424
425 elif self._flow_direction in FlowEntry.upstream_flow_types:
426 self.signature = upstream_sig
427 self.downstream_signature = downstream_sig
428
429 else:
430 log.error('unsupported-flow')
431 status = False
432
433 log.debug('flow-evc-decode', upstream_sig=self.signature, downstream_sig=self.downstream_signature)
434 return status
435
436 def _decode_traffic_selector(self, flow):
437 """
438 Extract EVC related traffic selection settings
439 """
440 self.in_port = fd.get_in_port(flow)
441
442 if self.in_port > OFPP_MAX:
443 log.warn('logical-input-ports-not-supported', in_port=self.in_port)
444 return False
445
446 for field in fd.get_ofb_fields(flow):
447 if field.type == fd.IN_PORT:
448 if self._handler.is_nni_port(self.in_port) or self._handler.is_uni_port(self.in_port):
449 self._logical_port = self.in_port
450
451 elif field.type == fd.VLAN_VID:
452 if field.vlan_vid >= OFPVID_PRESENT + 4095:
453 self.vlan_id = None # pre-ONOS v1.13.5 or old EAPOL Rule
454 else:
455 self.vlan_id = field.vlan_vid & 0xfff
456
457 log.debug('*** field.type == VLAN_VID', value=field.vlan_vid, vlan_id=self.vlan_id)
458
459 elif field.type == fd.VLAN_PCP:
460 log.debug('*** field.type == VLAN_PCP', value=field.vlan_pcp)
461 self.pcp = field.vlan_pcp
462
463 elif field.type == fd.ETH_TYPE:
464 log.debug('*** field.type == ETH_TYPE', value=field.eth_type)
465 self.eth_type = field.eth_type
466
467 elif field.type == fd.IP_PROTO:
468 log.debug('*** field.type == IP_PROTO', value=field.ip_proto)
469 self.ip_protocol = field.ip_proto
470
471 if self.ip_protocol not in _supported_ip_protocols:
472 log.error('Unsupported IP Protocol', protocol=self.ip_protocol)
473 return False
474
475 elif field.type == fd.IPV4_DST:
476 log.debug('*** field.type == IPV4_DST', value=field.ipv4_dst)
477 self.ipv4_dst = field.ipv4_dst
478
479 elif field.type == fd.UDP_DST:
480 log.debug('*** field.type == UDP_DST', value=field.udp_dst)
481 self.udp_dst = field.udp_dst
482
483 elif field.type == fd.UDP_SRC:
484 log.debug('*** field.type == UDP_SRC', value=field.udp_src)
485 self.udp_src = field.udp_src
486
487 elif field.type == fd.METADATA:
488 if self._handler.is_nni_port(self.in_port):
489 # Downstream flow
490 log.debug('*** field.type == METADATA', value=field.table_metadata)
491
492 if field.table_metadata > 4095:
493 # ONOS v1.13.5 or later. c-vid in upper 32-bits
494 vid = field.table_metadata & 0x0FFF
495 if vid > 0:
496 self.inner_vid = vid # CTag is never '0'
497
498 elif field.table_metadata > 0:
499 # Pre-ONOS v1.13.5 (vid without the 4096 offset)
500 self.inner_vid = field.table_metadata
501
502 else:
503 # Upstream flow
504 pass # Not used upstream at this time
505
506 log.debug('*** field.type == METADATA', value=field.table_metadata,
507 inner_vid=self.inner_vid)
508 else:
509 log.warn('unsupported-selection-field', type=field.type)
510 self._status_message = 'Unsupported field.type={}'.format(field.type)
511 return False
512
513 return True
514
515 def _decode_traffic_treatment(self, flow):
516 # Loop through traffic treatment
517 for act in fd.get_actions(flow):
518 if act.type == fd.OUTPUT:
519 self.output = act.output.port
520
521 elif act.type == fd.POP_VLAN:
522 log.debug('*** action.type == POP_VLAN')
523 self.pop_vlan = True
524
525 elif act.type == fd.PUSH_VLAN:
526 log.debug('*** action.type == PUSH_VLAN', value=act.push)
527 tpid = act.push.ethertype
528 self.push_vlan_tpid = tpid
529
530 elif act.type == fd.SET_FIELD:
531 log.debug('*** action.type == SET_FIELD', value=act.set_field.field)
532 assert (act.set_field.field.oxm_class == OFPXMC_OPENFLOW_BASIC)
533 field = act.set_field.field.ofb_field
534
535 if field.type == fd.VLAN_VID:
536 self.push_vlan_id = field.vlan_vid & 0xfff
537 else:
538 log.debug('unsupported-set-field')
539 else:
540 log.warn('unsupported-action', action=act)
541 self._status_message = 'Unsupported action.type={}'.format(act.type)
542 return False
543
544 return True
545
546 def _decode_flow_direction(self):
547 # Determine direction of the flow
548 def port_type(port_number):
549 if port_number in self._handler.northbound_ports:
550 return FlowEntry.PortType.NNI
551
552 elif port_number in self._handler.southbound_ports:
553 return FlowEntry.PortType.PON
554
555 elif port_number <= OFPP_MAX:
556 return FlowEntry.PortType.UNI
557
558 elif port_number in {OFPP_CONTROLLER, 0xFFFFFFFD}: # OFPP_CONTROLLER is wrong in proto-file
559 return FlowEntry.PortType.CONTROLLER
560
561 return FlowEntry.PortType.OTHER
562
563 flow_dir_map = {
564 (FlowEntry.PortType.UNI, FlowEntry.PortType.NNI): FlowEntry.FlowDirection.UPSTREAM,
565 (FlowEntry.PortType.NNI, FlowEntry.PortType.UNI): FlowEntry.FlowDirection.DOWNSTREAM,
566 (FlowEntry.PortType.UNI, FlowEntry.PortType.CONTROLLER): FlowEntry.FlowDirection.CONTROLLER_UNI,
567 (FlowEntry.PortType.NNI, FlowEntry.PortType.PON): FlowEntry.FlowDirection.NNI_PON,
568 # The following are not yet supported
569 # (FlowEntry.PortType.NNI, FlowEntry.PortType.CONTROLLER): FlowEntry.FlowDirection.CONTROLLER_NNI,
570 # (FlowEntry.PortType.PON, FlowEntry.PortType.CONTROLLER): FlowEntry.FlowDirection.CONTROLLER_PON,
571 # (FlowEntry.PortType.NNI, FlowEntry.PortType.NNI): FlowEntry.FlowDirection.NNI_NNI,
572 # (FlowEntry.PortType.UNI, FlowEntry.PortType.UNI): FlowEntry.FlowDirection.UNI_UNI,
573 }
574 self._flow_direction = flow_dir_map.get((port_type(self.in_port), port_type(self.output)),
575 FlowEntry.FlowDirection.OTHER)
576 return self._flow_direction != FlowEntry.FlowDirection.OTHER
577
578 def _apply_downstream_mods(self):
579 # This is a downstream flow. It could be any one of the following:
580 #
581 # Legacy control VLAN:
582 # This is the old VLAN 4000 that was used to attach EAPOL and other
583 # controller flows to. Eventually these will change to CONTROLLER_UNI
584 # flows. For these, use the 'utility' VLAN instead so 4000 if available
585 # for other uses (AT&T uses it for downstream multicast video).
586 #
587 # Multicast VLAN:
588 # This is downstream multicast data.
589 # TODO: Test this to see if this needs to be in a separate NNI_PON mod-method
590 #
591 # User Data flow:
592 # This is for user data. Eventually we may need to support ACLs?
593 #
594 # May be for to controller flow downstream (no ethType)
595 if self.vlan_id == FlowEntry.LEGACY_CONTROL_VLAN and self.eth_type is None and self.pcp == 0:
596 return False # Do not install this flow. Utility VLAN is in charge
597
598 elif self.flow_direction == FlowEntry.FlowDirection.NNI_PON and \
599 self.vlan_id == self.handler.utility_vlan:
600 # Utility VLAN downstream flow/EVC
601 self._is_acl_flow = True
602
603 elif self.vlan_id in self._handler.multicast_vlans:
604 # multicast (ethType = IP) # TODO: May need to be an NNI_PON flow
605 self._is_multicast = True
606 self._is_acl_flow = True
607
608 else:
609 # Currently do not support ACLs on user data flows downstream
610 assert not self._needs_acl_support # User data, no special modifications needed at this time
611
612 return True
613
614 def _apply_upstream_mods(self):
615 #
616 # This is an upstream flow. It could be any of the following
617 #
618 # ACL/Packet capture:
619 # This is either a legacy (FlowDirection.UPSTREAM) or a new one
620 # that specifies an output port of controller (FlowDirection.CONTROLLER_UNI).
621 # Either way, these need to be placed on the Utility VLAN if the ONU attached
622 # does not have a user-data flow (C-Tag). If there is a C-Tag available,
623 # then place it on that VLAN.
624 #
625 # Once a user-data flow is established, move any of the ONUs ACL flows
626 # over to that VLAN (this is handled elsewhere).
627 #
628 # User Data flows:
629 # No special modifications are needed
630 #
631 try:
632 # Do not handle PON level ACLs in this method
633 assert(self._flow_direction != FlowEntry.FlowDirection.CONTROLLER_PON)
634
635 # Is this a legacy (VLAN 4000) upstream to-controller flow
636 if self._needs_acl_support and FlowEntry.LEGACY_CONTROL_VLAN == self.push_vlan_id:
637 self._flow_direction = FlowEntry.FlowDirection.CONTROLLER_UNI
638 self._is_acl_flow = True
639 self.push_vlan_id = self.handler.utility_vlan
640
641 return True
642
643 except Exception as e:
644 # TODO: Need to support flow retry if the ONU is not yet activated !!!!
645 log.exception('tag-fixup', e=e)
646 return False
647
648 @staticmethod
649 def drop_missing_flows(handler, valid_flow_ids):
650 dl = []
651 try:
652 flow_table = handler.upstream_flows
653 flows_to_drop = [flow for flow_id, flow in flow_table.items()
654 if flow_id not in valid_flow_ids]
655 dl.extend([flow.remove() for flow in flows_to_drop])
656
657 for sig_table in handler.downstream_flows.itervalues():
658 flows_to_drop = [flow for flow_id, flow in sig_table.flows.items()
659 if isinstance(flow, FlowEntry) and flow_id not in valid_flow_ids]
660 dl.extend([flow.remove() for flow in flows_to_drop])
661
662 except Exception as _e:
663 pass
664
665 return gatherResults(dl, consumeErrors=True) if len(dl) > 0 else returnValue('no-flows-to-drop')
666
667 @inlineCallbacks
668 def remove(self):
669 """
670 Remove this flow entry from the list of existing entries and drop EVC
671 if needed
672 """
673 # Remove from exiting table list
674 flow_id = self.flow_id
675 flow_table = None
676
677 if self.flow_direction in FlowEntry.upstream_flow_types:
678 flow_table = self._handler.upstream_flows
679
680 elif self.flow_direction in FlowEntry.downstream_flow_types:
681 sig_table = self._handler.downstream_flows.get(self.signature)
682 flow_table = sig_table.flows if sig_table is not None else None
683
684 if flow_table is None or flow_id not in flow_table.keys():
685 returnValue('NOP')
686
687 # Remove from flow table and clean up flow table if empty
688 flow_table.remove(flow_id)
689 evc_map, self.evc_map = self.evc_map, None
690 evc = None
691
692 if self.flow_direction in FlowEntry.downstream_flow_types:
693 sig_table = self._handler.downstream_flows.get(self.signature)
694 if len(flow_table) == 0: # Only 'evc' entry present
695 evc = sig_table.evc
696 else:
697 assert sig_table.evc is not None, 'EVC flow re-assignment error'
698
699 # Remove flow from the hardware
700 try:
701 dl = []
702 if evc_map is not None:
703 dl.append(evc_map.delete(self))
704
705 if evc is not None:
706 dl.append(evc.delete())
707
708 yield gatherResults(dl, consumeErrors=True)
709
710 except Exception as e:
711 log.exception('removal', e=e)
712
713 if self.flow_direction in FlowEntry.downstream_flow_types:
714 # If this flow owns the EVC, assign it to a remaining flow
715 sig_table = self._handler.downstream_flows.get(self.signature)
716 flow_evc = sig_table.evc
717
718 if flow_evc is not None and flow_evc.flow_entry is not None and flow_id == flow_evc.flow_entry.flow_id:
719 flow_evc.flow_entry = next((_flow for _flow in flow_table.itervalues()
720 if isinstance(_flow, FlowEntry)
721 and _flow.flow_id != flow_id), None)
722
723 # If evc was deleted, remove the signature table since now flows exist with
724 # that signature
725 if evc is not None:
726 self._handler.downstream_flows.remove(self.signature)
727
728 self.evc = None
729 returnValue('Done')
730
731 @staticmethod
732 def find_evc_map_flows(onu):
733 """
734 For a given OLT, find all the EVC Maps for a specific ONU
735 :param onu: (Onu) onu
736 :return: (list) of matching flows
737 """
738 # EVCs are only in the downstream table, EVC Maps are in upstream
739 onu_ports = onu.uni_ports
740
741 all_flow_entries = onu.olt.upstream_flows
742 evc_maps = [flow_entry.evc_map for flow_entry in all_flow_entries.itervalues()
743 if flow_entry.in_port in onu_ports
744 and flow_entry.evc_map is not None
745 and flow_entry.evc_map.valid]
746
747 return evc_maps
748
749 @staticmethod
750 def sync_flows_by_onu(onu, reflow=False):
751 """
752 Check status of all flows on a per-ONU basis. Called when values
753 within the ONU are modified that may affect traffic.
754
755 :param onu: (Onu) ONU to examine
756 :param reflow: (boolean) Flag, if True, requests that the flow be sent to
757 hardware even if the values in hardware are
758 consistent with the current flow settings
759 """
760 evc_maps = FlowEntry.find_evc_map_flows(onu)
761 evcs = {}
762
763 for evc_map in evc_maps:
764 if reflow or evc_map.reflow_needed():
765 evc_map.needs_update = False
766
767 if not evc_map.installed:
768 evc = evc_map.evc
769 if evc is not None:
770 evcs[evc.name] = evc
771
772 for evc in evcs.itervalues():
773 evc.installed = False
774 evc.schedule_install(delay=2)
775
776 ######################################################
777 # Bulk operations
778
779 @staticmethod
780 def clear_all(handler):
781 """
782 Remove all flows for the device.
783
784 :param handler: voltha adapter device handler
785 """
786 handler.downstream_flows.clear_all()
787 handler.upstream_flows.clear_all()
788
789 @staticmethod
790 def get_packetout_info(handler, logical_port):
791 """
792 Find parameters needed to send packet out successfully to the OLT.
793
794 :param handler: voltha adapter device handler
795 :param logical_port: (int) logical port number for packet to go out.
796
797 :return: physical port number, ctag, stag, evcmap name
798 """
799 from adapters.adtran_olt.onu import Onu
800
801 for flow_entry in handler.upstream_flows.itervalues():
802 log.debug('get-packetout-info', flow_entry=flow_entry)
803
804 # match logical port
805 if flow_entry.evc_map is not None and flow_entry.evc_map.valid and \
806 flow_entry.logical_port == logical_port:
807 evc_map = flow_entry.evc_map
808 gem_ids_and_vid = evc_map.gem_ids_and_vid
809
810 # must have valid gem id
811 if len(gem_ids_and_vid) > 0:
812 for onu_id, gem_ids_with_vid in gem_ids_and_vid.iteritems():
813 log.debug('get-packetout-info', onu_id=onu_id,
814 gem_ids_with_vid=gem_ids_with_vid)
815 if len(gem_ids_with_vid) > 0:
816 gem_ids = gem_ids_with_vid[0]
817 ctag = gem_ids_with_vid[1]
818 gem_id = gem_ids[0] # TODO: always grab first in list
819 return flow_entry.in_port, ctag, Onu.gem_id_to_gvid(gem_id), \
820 evc_map.get_evcmap_name(onu_id, gem_id)
821 return None, None, None, None