ADTRAN ONU: Move user-data flow create to OpenOMCI Task
Change-Id: I18b2c03fc9edc5458acb3e943e49087bc9ffec91
diff --git a/voltha/adapters/adtran_olt/README.md b/voltha/adapters/adtran_olt/README.md
index e2aaeaf..9e109a7 100644
--- a/voltha/adapters/adtran_olt/README.md
+++ b/voltha/adapters/adtran_olt/README.md
@@ -4,18 +4,19 @@
extension of the existing **preprovision_olt** command and these are placed after
entering two dashes '_--_'. The full syntax to use is.
-| Short | Long | Default | Notes |
-| :---: | :--------------: | :-----: | ----- |
-| -u | --nc_username | '' | NETCONF Username |
-| -p | --nc_password | '' | NETCONF Password |
-| -t | --nc_port | 830 | NETCONF TCP Port |
-| -U | --rc_username | '' | REST Username |
-| -P | --rc_password | '' | REST Password |
-| -T | --rc_port | 8081 | REST TCP Port |
-| -z | --zmq_port | 5656 | ZeroMQ OMCI Proxy Port |
-| -M | --multicast_vlan | 4000 | Multicast VLANs (comma-delimeted) |
-| -v | --untagged_vlan | 4092 | VLAN wrapper for untagged ONU frames |
-| -Z | --pio_port | 5657 | PIO Service ZeroMQ Port |
+| Short | Long | Default | Notes |
+| :---: | :----------------: | :--------: | ----- |
+| -u | --nc_username | '' | NETCONF Username |
+| -p | --nc_password | '' | NETCONF Password |
+| -t | --nc_port | 830 | NETCONF TCP Port |
+| -U | --rc_username | '' | REST Username |
+| -P | --rc_password | '' | REST Password |
+| -T | --rc_port | 8081 | REST TCP Port |
+| -z | --zmq_port | 5656 | ZeroMQ OMCI Proxy Port |
+| -M | --multicast_vlan | 4000 | Multicast VLANs (comma-delimited) |
+| -v | --untagged_vlan | 4092 | VLAN wrapper for untagged ONU frames |
+| -Z | --pio_port | 5657 | PIO Service ZeroMQ Port |
+| -X | --xpon_enable | False | Support BBF WT-386 xPON CLI/NBI provisioning |
For example, if your Adtran OLT is address 10.17.174.193 with the default TCP ports and
NETCONF credentials of admin/admin and REST credentials of ADMIN/ADMIN, the command line
@@ -42,13 +43,18 @@
preprovision_olt -t adtran_olt --host_and_port 10.17.174.193:830
```
-Currently the Adtran Device Adapter supports xPON provisioning and to enable PON ports, or activate ONUs, you
-must use the appropriate commands. In the VOLTHA v2.0 release (Q4 2018?), the xPON provisioning will be removed
-from VOLTHA and replaced with Technology Profiles.
-## REST Based Pre-Provisioning
+## xPON Provisioning Support
+
+Currently the Adtran Device Adapter supports xPON provisioning to enable PON ports, or activate ONUs, you
+must use the appropriate commands. In the VOLTHA v2.0 release (Q4 2018?), the xPON provisioning will be removed
+from VOLTHA and replaced with Technology Profiles. _By default, this provisioning is now disabled and you should
+use the '-X' extra-arguments provisioning command switch if you wish to use it_.
+
+### REST Based xPON Pre-Provisioning
In addition to CLI provisioning, the Adtran OLT Device Adapter can also be provisioned though the
-VOLTHA Northbound REST API.
+VOLTHA Northbound REST API. The following examples show curl commands when running with the **_Consul_**
+key-value store. Similar curl commands can be used when **_etcd_** is used as the key value store
```bash
VOLTHA_IP=localhost
@@ -94,14 +100,14 @@
Besides specifying the "ipv4_address" leaf, you can alternatively use the "host_and_port" leaf to
provide the IP Host address and the NetCONF port as in "10.17.174.228:830"
-## Enabling the Pre-Provisioned OLT
+### Enabling the Pre-Provisioned OLT
To enable the OLT, you need the retrieve the OLT Device ID and issue a POST request to the proper URL as in:
```bash
DEVICE_ID=$(jq .id /tmp/adtn-olt.json | sed 's/"//g')
curl -k -s -X POST https://${VOLTHA_IP}:${REST_PORT}/api/v1/local/devices/${DEVICE_ID}/enable
```
-### Other REST APIs
+#### Other REST APIs
To list out any devices, you can use the following command:
```bash
diff --git a/voltha/adapters/adtran_olt/adtran_device_handler.py b/voltha/adapters/adtran_olt/adtran_device_handler.py
index d506fa2..60522b1 100644
--- a/voltha/adapters/adtran_olt/adtran_device_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_device_handler.py
@@ -52,7 +52,7 @@
_DEFAULT_NETCONF_PORT = 830
_STARTUP_RETRY_TIMEOUT = 5 # 5 seconds delay after activate failed before we
-_DEFAULT_XPON_SUPPORTED = True # LOOK for the keywords 'xpon_support', SEBA
+_DEFAULT_XPON_SUPPORTED = False # LOOK for the keywords 'xpon_support', SEBA
# for areas to clean up once xPON is deprecated
@@ -278,7 +278,7 @@
default='{}'.format(DEFAULT_UTILITY_VLAN),
help='VLAN for Untagged Frames from ONUs')
parser.add_argument('--xpon_enable', '-X', action='store_true',
- default=not _DEFAULT_XPON_SUPPORTED,
+ default=_DEFAULT_XPON_SUPPORTED,
help='enable xPON (BBF WT-385) provisioning support')
try:
args = parser.parse_args(shlex.split(device.extra_args))
diff --git a/voltha/adapters/adtran_olt/adtran_olt.py b/voltha/adapters/adtran_olt/adtran_olt.py
index 19e8511..d906508 100644
--- a/voltha/adapters/adtran_olt/adtran_olt.py
+++ b/voltha/adapters/adtran_olt/adtran_olt.py
@@ -52,7 +52,7 @@
self.descriptor = Adapter(
id=self.name,
vendor='Adtran Inc.',
- version='0.21',
+ version='1.22',
config=AdapterConfig(log_level=LogLevel.INFO)
)
log.debug('adtran_olt.__init__', adapter_agent=adapter_agent)
diff --git a/voltha/adapters/adtran_olt/adtran_olt_handler.py b/voltha/adapters/adtran_olt/adtran_olt_handler.py
index bf4c654..788cb70 100644
--- a/voltha/adapters/adtran_olt/adtran_olt_handler.py
+++ b/voltha/adapters/adtran_olt/adtran_olt_handler.py
@@ -1309,7 +1309,7 @@
root=True,
serial_number=serial_number,
admin_state=AdminState.ENABLED,
- vlan=self.get_onu_vid(onu_id) # TODO: a hack, need a decent flow decomposer
+ # vlan=self.get_onu_vid(onu_id) # TODO: a hack, need a decent flow decomposer
)
assert serial_number is not None, 'ONU does not have a serial number'
diff --git a/voltha/adapters/adtran_onu/adtran_onu.py b/voltha/adapters/adtran_onu/adtran_onu.py
index 450273f..ba1384f 100755
--- a/voltha/adapters/adtran_onu/adtran_onu.py
+++ b/voltha/adapters/adtran_onu/adtran_onu.py
@@ -40,7 +40,7 @@
device_handler_class=AdtranOnuHandler,
name='adtran_onu',
vendor='Adtran Inc.',
- version='0.15',
+ version='1.16',
device_type='adtran_onu',
vendor_id='ADTN',
accepts_add_remove_flow_updates=False), # TODO: Support flow-mods
diff --git a/voltha/adapters/adtran_onu/adtran_onu_handler.py b/voltha/adapters/adtran_onu/adtran_onu_handler.py
index 693d9bd..86b9991 100644
--- a/voltha/adapters/adtran_onu/adtran_onu_handler.py
+++ b/voltha/adapters/adtran_onu/adtran_onu_handler.py
@@ -34,12 +34,14 @@
from voltha.extensions.omci.omci_me import *
import voltha.adapters.adtran_olt.adtranolt_platform as platform
+from voltha.adapters.adtran_onu.flow.flow_entry import FlowEntry
+from omci.adtn_install_flow import AdtnInstallFlowTask
+from omci.adtn_remove_flow import AdtnRemoveFlowTask
_ = third_party
_MAXIMUM_PORT = 17 # Only one PON and UNI port at this time
_ONU_REBOOT_MIN = 90 # IBONT 602 takes about 3 minutes
_ONU_REBOOT_RETRY = 10
-BRDCM_DEFAULT_VLAN = 4091
class AdtranOnuHandler(AdtranXPON):
@@ -73,6 +75,14 @@
self._deferred = None
self._event_deferred = None
+ # Flow entries
+ self._flows = dict()
+
+ # OMCI resources
+ # TODO: Some of these could be dynamically chosen
+ self.vlan_tcis_1 = 0x900
+ self.mac_bridge_service_profile_entity_id = self.vlan_tcis_1
+
# Assume no XPON support unless we get an vont-ani/ont-ani/venet create
self.xpon_support = False # xPON no longer available
@@ -144,9 +154,8 @@
assert isinstance(port_no_or_name, int), 'Invalid parameter type'
return self._unis.get(port_no_or_name)
- @property
- def pon_port(self):
- return self._pon
+ def pon_port(self, port_no=None):
+ return self._pon if port_no is None or port_no == self._pon.port_number else None
@property
def pon_ports(self):
@@ -331,141 +340,31 @@
@inlineCallbacks
def update_flow_table(self, flows):
- #
- # We need to proxy through the OLT to get to the ONU
- # Configuration from here should be using OMCI
- #
+ if len(flows) == 0:
+ returnValue('nop') # TODO: Do we need to delete all flows if empty?
+
self.log.debug('bulk-flow-update', flows=flows)
-
- import voltha.core.flow_decomposer as fd
- from voltha.protos.openflow_13_pb2 import OFPXMC_OPENFLOW_BASIC
-
- def is_downstream(port):
- return port == self._pon_port_number
-
- def is_upstream(port):
- return not is_downstream(port)
+ valid_flows = set()
for flow in flows:
- match = {
- 'in_port': None,
- # 'etype': None, # These remaining key-value
- # 'proto': None, # pairs are set if found in the
- # 'vlan_vid': None, # flow information
- # 'inner_vid': None,
- # 'vlan_pcp': None,
- # 'udp_dst': None,
- # 'udp_src': None,
- # 'ipv4_dst': None,
- # 'ipv4_src': None,
- }
- actions = {
- 'out_port': None,
- # 'push_tpid': None, # These remaining key-value
- # 'pop_vlan': None, (bool) # pairs are set if found in the
- # 'set_vlan_vid': None, # flow information
- }
- self.log.debug('bulk-flow-update', flow=flow)
- try:
- _out_port = fd.get_out_port(flow) # may be None
- self.log.debug('out-port', out_port=_out_port)
+ # Decode it
+ flow_entry = FlowEntry.create(flow, self)
- for field in fd.get_ofb_fields(flow):
- if field.type == fd.IN_PORT:
- assert match['in_port'] is None, \
- 'Only a single input port is supported'
- match['in_port'] = field.port
- self.log.debug('field-type-in-port', in_port=field.port)
+ # Already handled?
+ if flow_entry.flow_id in self._flows:
+ valid_flows.add(flow_entry.flow_id)
- elif field.type == fd.ETH_TYPE:
- match['etype'] = field.eth_type
- self.log.debug('field-type-eth-type', eth_type=field.eth_type)
-
- elif field.type == fd.IP_PROTO:
- match['proto'] = field.ip_proto
- self.log.debug('field-type-ip-proto', ip_proto=field.ip_proto)
-
- elif field.type == fd.VLAN_VID:
- match['vlan_vid'] = field.vlan_vid & 0xfff
- self.log.debug('field-type-vlan-vid', vlan=field.vlan_vid & 0xfff)
-
- elif field.type == fd.VLAN_PCP:
- match['vlan_pcp'] = field.vlan_pcp
- self.log.debug('field-type-vlan-pcp', pcp=field.vlan_pcp)
-
- elif field.type == fd.UDP_DST:
- match['udp_dst'] = field.udp_dst
- self.log.debug('field-type-udp-dst', udp_dst=field.udp_dst)
-
- elif field.type == fd.UDP_SRC:
- match['udp_src'] = field.udp_src
- self.log.debug('field-type-udp-src', udp_src=field.udp_src)
-
- elif field.type == fd.IPV4_DST:
- match['ipv4_dst'] = field.ipv4_dst
- self.log.debug('field-type-ipv4-dst', ipv4_dst=field.ipv4_dst)
-
- elif field.type == fd.IPV4_SRC:
- match['ipv4_src'] = field.ipv4_src
- self.log.debug('field-type-ipv4-src', ipv4_dst=field.ipv4_src)
-
- elif field.type == fd.METADATA:
- match['inner_vid'] = field.table_metadata
- self.log.debug('field-type-metadata', metadata=field.table_metadata)
-
- else:
- raise NotImplementedError('field.type={}'.format(field.type))
-
- for action in fd.get_actions(flow):
- if action.type == fd.OUTPUT:
- actions['out_port'] = action.output.port
- self.log.debug('action-type-output', output=action.output.port)
-
- elif action.type == fd.POP_VLAN:
- actions['pop_vlan'] = True
- self.log.debug('action-type-pop-vlan')
-
- elif action.type == fd.PUSH_VLAN:
- actions['push_tpid'] = action.push.ethertype
- self.log.debug('action-type-push-vlan',
- push_tpid=action.push.ethertype)
-
- if action.push.ethertype != 0x8100:
- self.log.error('unsupported-tpid',
- ethertype=action.push.ethertype,
- in_port=match['in_port'])
-
- elif action.type == fd.SET_FIELD:
- _field = action.set_field.field.ofb_field
- self.log.debug('action-type-set-field', field=_field)
- assert (action.set_field.field.oxm_class == OFPXMC_OPENFLOW_BASIC)
-
- if _field.type == fd.VLAN_VID:
- actions['set_vlan_vid'] = _field.vlan_vid & 0xfff
- self.log.debug('set-field-type-vlan-vid', actions['set_vlan_vid'])
- else:
- self.log.error('unsupported-action-set-field-type',
- field_type=_field.type,
- in_port=match['in_port'])
- else:
- self.log.error('unsupported-action-type', action_type=action.type,
- in_port=match['in_port'])
-
- assert match['in_port'] is not None, 'No input port specified'
- assert actions['out_port'] is not None, 'No output port specified'
-
- _is_upstream = is_upstream(match['in_port'])
-
- except Exception as e:
- self.log.exception('failed-to-decode-flow', e=e)
+ if flow_entry is None or flow_entry.flow_direction not in {FlowEntry.FlowDirection.UPSTREAM,
+ FlowEntry.FlowDirection.DOWNSTREAM}:
continue
+ is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+
# Ignore untagged upstream etherType flows. These are trapped at the
# OLT and the default flows during initial OMCI service download will
# send them to the Default VLAN (4091) port for us
#
- if _is_upstream and match.get('vlan_vid') is None \
- and match.get('etype') is not None:
+ if is_upstream and flow_entry.vlan_vid is None and flow_entry.etype is not None:
continue
# Also ignore upstream untagged/priority tag that sets priority tag
@@ -473,14 +372,50 @@
# priority tag data will be at a higher level. Also should ignore the
# corresponding priority-tagged to priority-tagged flow as well.
- if (match.get('vlan_vid') == 0 and action.get('set_vlan_vid') == 0) or \
- (match.get('vlan_vid') is None and action.get('set_vlan_vid') == 0
- and not _is_upstream):
+ if (flow_entry.vlan_vid == 0 and flow_entry.set_vlan_vid == 0) or \
+ (flow_entry.vlan_vid is None and flow_entry.set_vlan_vid == 0
+ and not is_upstream):
continue
- from omci.adtn_install_flow import AdtnInstallFlowTask
- task = AdtnInstallFlowTask(self.openomci, self, match, action, _is_upstream)
- self.openomci.onu_omci_device.task_runner.queue_task(task)
+ # Is it the first user-data flow downstream with a non-zero/non-None VID
+ # to match on? If so, use as the device VLAN
+ # TODO: When multicast is supported, skip the multicast VLAN here?
+
+ if not is_upstream and flow_entry.vlan_vid:
+ uni = self.uni_port(flow_entry.out_port)
+ if uni is not None:
+ uni.subscriber_vlan = flow_entry.vlan_vid
+
+ # Add it to hardware
+ try:
+ def failed(_reason, fid):
+ del self._flows[fid]
+
+ task = AdtnInstallFlowTask(self.openomci.omci_agent, self, flow_entry)
+ d = self.openomci.onu_omci_device.task_runner.queue_task(task)
+ d.addErrback(failed, flow_entry.flow_id)
+
+ valid_flows.add(flow_entry.flow_id)
+ self._flows[flow_entry.flow_id] = flow_entry
+
+ except Exception as e:
+ self.log.exception('flow-add', e=e, flow=flow_entry)
+
+ # Now check for flows that were missing in the bulk update
+ deleted_flows = set(self._flows.keys()) - valid_flows
+
+ for flow_id in deleted_flows:
+ try:
+ del_flow = self._flows[flow_id]
+
+ task = AdtnRemoveFlowTask(self.openomci.omci_agent, self, del_flow)
+ self.openomci.onu_omci_device.task_runner.queue_task(task)
+ # TODO: Change to success/failure callback checks later
+ # d.addCallback(success, flow_entry.flow_id)
+ del self._flows[flow_id]
+
+ except Exception as e:
+ self.log.exception('flow-remove', e=e, flow=self._flows[flow_id])
@inlineCallbacks
def reboot(self):
@@ -857,7 +792,7 @@
device = self.adapter_agent.get_device(self.device_id)
subscriber_vlan = device.vlan
- untagged_vlan = BRDCM_DEFAULT_VLAN # TODO: Need a better way to define this
+ untagged_vlan = OMCI.DEFAULT_UNTAGGED_VLAN
for entity_id, pptp in pptp_entities.items():
intf_id = self.proxy_address.channel_id
diff --git a/voltha/adapters/adtran_onu/flow/__init__.py b/voltha/adapters/adtran_onu/flow/__init__.py
new file mode 100644
index 0000000..b0fb0b2
--- /dev/null
+++ b/voltha/adapters/adtran_onu/flow/__init__.py
@@ -0,0 +1,13 @@
+# Copyright 2017-present Open Networking Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
diff --git a/voltha/adapters/adtran_onu/flow/flow_entry.py b/voltha/adapters/adtran_onu/flow/flow_entry.py
new file mode 100644
index 0000000..7a90daf
--- /dev/null
+++ b/voltha/adapters/adtran_onu/flow/flow_entry.py
@@ -0,0 +1,270 @@
+# Copyright 2017-present Adtran, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from enum import IntEnum
+import voltha.core.flow_decomposer as fd
+from voltha.core.flow_decomposer import *
+from voltha.protos.openflow_13_pb2 import OFPP_MAX
+
+log = structlog.get_logger()
+
+# IP Protocol numbers
+_supported_ip_protocols = [
+ 1, # ICMP
+ 2, # IGMP
+ 6, # TCP
+ 17, # UDP
+]
+
+
+class FlowEntry(object):
+ """
+ Provide a class that wraps the flow rule and also provides state/status for a FlowEntry.
+
+ When a new flow is sent, it is first decoded to check for any potential errors. If None are
+ found, the entry is created and it is analyzed to see if it can be combined to with any other flows
+ to create or modify an existing EVC.
+
+ Note: Since only E-LINE is supported, modification of an existing EVC is not performed.
+ """
+ class FlowDirection(IntEnum):
+ UPSTREAM = 0 # UNI port to ANI Port
+ DOWNSTREAM = 1 # ANI port to UNI Port
+ ANI = 2 # ANI port to ANI Port
+ UNI = 3 # UNI port to UNI Port
+ OTHER = 4 # Unable to determine
+
+ _flow_dir_map = {
+ (FlowDirection.UNI, FlowDirection.ANI): FlowDirection.UPSTREAM,
+ (FlowDirection.ANI, FlowDirection.UNI): FlowDirection.DOWNSTREAM
+ }
+ # Well known EtherTypes
+ class EtherType(IntEnum):
+ EAPOL = 0x888E
+ IPv4 = 0x0800
+ IPv6 = 0x86DD
+ ARP = 0x0806
+ LLDP = 0x88CC
+
+ # Well known IP Protocols
+ class IpProtocol(IntEnum):
+ IGMP = 2
+ UDP = 17
+
+ def __init__(self, flow, handler):
+ self._flow = flow # TODO: Drop this reference once debugging done
+ self._handler = handler
+ self.flow_id = flow.id
+ self._flow_direction = FlowEntry.FlowDirection.OTHER
+ self._is_multicast = False
+ self.tech_profile_id = None
+
+ # Selection properties
+ self.in_port = None
+ self.vlan_vid = None
+ self.vlan_pcp = None
+ self.etype = None
+ self.proto = None
+ self.ipv4_dst = None
+ self.udp_dst = None # UDP Port #
+ self.udp_src = None # UDP Port #
+ self.inner_vid = None
+
+ # Actions
+ self.out_port = None
+ self.pop_vlan = False
+ self.push_vlan_tpid = None
+ self.set_vlan_vid = None
+ self._name = self.create_flow_name()
+
+ def __str__(self):
+ return 'flow_entry: {}, in: {}, out: {}, vid: {}, inner:{}, eth: {}, IP: {}'.format(
+ self.name, self.in_port, self.out_port, self.vlan_vid, self.inner_vid,
+ self.etype, self.proto)
+
+ def __repr__(self):
+ return str(self)
+
+ @property
+ def name(self):
+ return self._name # TODO: Is a name really needed in production?
+
+ def create_flow_name(self):
+ return 'flow-{}-{}'.format(self.device_id, self.flow_id)
+
+ @property
+ def handler(self):
+ return self._handler
+
+ @property
+ def device_id(self):
+ return self.handler.device_id
+
+ @property
+ def flow_direction(self):
+ return self._flow_direction
+
+ @property
+ def is_multicast_flow(self):
+ return self._is_multicast
+
+ @staticmethod
+ def create(flow, handler):
+ """
+ Create the appropriate FlowEntry wrapper for the flow. This method returns a two
+ results.
+
+ The first result is the flow entry that was created. This could be a match to an
+ existing flow since it is a bulk update. None is returned only if no match to
+ an existing entry is found and decode failed (unsupported field)
+
+ :param flow: (Flow) Flow entry passed to VOLTHA adapter
+ :param handler: (DeviceHandler) handler for the device
+ :return: (FlowEntry) Created flow entry, None on decode failure
+ """
+ # Exit early if it already exists
+ try:
+ flow_entry = FlowEntry(flow, handler)
+
+ if not flow_entry.decode(flow):
+ return None
+
+ # TODO: Do we want to do the OMCI here ?
+
+ return flow_entry
+
+ except Exception as e:
+ log.exception('flow-entry-processing', e=e)
+ return None
+
+ def decode(self, flow):
+ """
+ Examine flow rules and extract appropriate settings
+ """
+ log.debug('start-decode')
+ status = self._decode_traffic_selector(flow) and self._decode_traffic_treatment(flow)
+
+ if status:
+ ani_ports = [pon.port_number for pon in self._handler.pon_ports]
+ uni_ports = [uni.port_number for uni in self._handler.uni_ports]
+
+ # Determine direction of the flow
+ def port_type(port_number):
+ if port_number in ani_ports:
+ return FlowEntry.FlowDirection.ANI
+
+ elif port_number in uni_ports:
+ return FlowEntry.FlowDirection.UNI
+
+ return FlowEntry.FlowDirection.OTHER
+
+ self._flow_direction = FlowEntry._flow_dir_map.get((port_type(self.in_port),
+ port_type(self.out_port)),
+ FlowEntry.FlowDirection.OTHER)
+ return status
+
+ def _decode_traffic_selector(self, flow):
+ """
+ Extract traffic selection settings
+ """
+ self.in_port = fd.get_in_port(flow)
+
+ if self.in_port > OFPP_MAX:
+ log.warn('logical-input-ports-not-supported')
+ return False
+
+ for field in fd.get_ofb_fields(flow):
+ if field.type == IN_PORT:
+ assert self.in_port == field.port, 'Multiple Input Ports found in flow rule'
+
+ elif field.type == VLAN_VID:
+ self.vlan_vid = field.vlan_vid & 0xfff
+ log.debug('*** field.type == VLAN_VID', value=field.vlan_vid, vlan_id=self.vlan_vid)
+ self._is_multicast = False # TODO: self.vlan_id in self._handler.multicast_vlans
+
+ elif field.type == VLAN_PCP:
+ log.debug('*** field.type == VLAN_PCP', value=field.vlan_pcp)
+ self.vlan_pcp = field.vlan_pcp
+
+ elif field.type == ETH_TYPE:
+ log.debug('*** field.type == ETH_TYPE', value=field.eth_type)
+ self.etype = field.eth_type
+
+ elif field.type == IP_PROTO:
+ log.debug('*** field.type == IP_PROTO', value=field.ip_proto)
+ self.proto = field.ip_proto
+
+ if self.proto not in _supported_ip_protocols:
+ log.error('Unsupported IP Protocol', ip_proto=self.proto)
+ return False
+
+ elif field.type == IPV4_DST:
+ log.debug('*** field.type == IPV4_DST', value=field.ipv4_dst)
+ self.ipv4_dst = field.ipv4_dst
+
+ elif field.type == UDP_DST:
+ log.debug('*** field.type == UDP_DST', value=field.udp_dst)
+ self.udp_dst = field.udp_dst
+
+ elif field.type == UDP_SRC:
+ log.debug('*** field.type == UDP_SRC', value=field.udp_src)
+ self.udp_src = field.udp_src
+
+ elif field.type == METADATA:
+ log.debug('*** field.type == METADATA', value=field.table_metadata)
+ self.inner_vid = field.table_metadata
+ log.debug('*** field.type == METADATA', value=field.table_metadata,
+ inner_vid=self.inner_vid)
+ else:
+ log.warn('unsupported-selection-field', type=field.type)
+ self._status_message = 'Unsupported field.type={}'.format(field.type)
+ return False
+
+ return True
+
+ def _decode_traffic_treatment(self, flow):
+ self.out_port = fd.get_out_port(flow)
+
+ if self.out_port > OFPP_MAX:
+ log.warn('logical-output-ports-not-supported')
+ return False
+
+ for act in fd.get_actions(flow):
+ if act.type == fd.OUTPUT:
+ assert self.out_port == act.output.port, 'Multiple Output Ports found in flow rule'
+ pass # Handled earlier
+
+ elif act.type == POP_VLAN:
+ log.debug('*** action.type == POP_VLAN')
+ self.pop_vlan = True
+
+ elif act.type == PUSH_VLAN:
+ log.debug('*** action.type == PUSH_VLAN', value=act.push)
+ tpid = act.push.ethertype
+ self.push_tpid = tpid
+ assert tpid == 0x8100, 'Only TPID 0x8100 is currently supported'
+
+ elif act.type == SET_FIELD:
+ log.debug('*** action.type == SET_FIELD', value=act.set_field.field)
+ assert (act.set_field.field.oxm_class == ofp.OFPXMC_OPENFLOW_BASIC)
+ field = act.set_field.field.ofb_field
+ if field.type == VLAN_VID:
+ self.set_vlan_vid = field.vlan_vid & 0xfff
+
+ else:
+ log.warn('unsupported-action', action=act)
+ self._status_message = 'Unsupported action.type={}'.format(act.type)
+ return False
+
+ return True
diff --git a/voltha/adapters/adtran_onu/omci/adtn_install_flow.py b/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
index f3e2cee..b5fcb40 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_install_flow.py
@@ -14,95 +14,68 @@
# limitations under the License.
#
from twisted.internet import reactor
-from twisted.internet.defer import inlineCallbacks, returnValue, TimeoutError, failure
+from twisted.internet.defer import inlineCallbacks, failure
from voltha.extensions.omci.omci_me import *
from voltha.extensions.omci.tasks.task import Task
from voltha.extensions.omci.omci_defs import *
+from voltha.adapters.adtran_onu.flow.flow_entry import FlowEntry
OP = EntityOperations
RC = ReasonCodes
-class ServiceDownloadFailure(Exception):
+class ServiceInstallFailure(Exception):
"""
- This error is raised by default when the download fails
- """
-
-
-class ServiceResourcesFailure(Exception):
- """
- This error is raised by when one or more resources required is not available
+ This error is raised by default when the flow-install fails
"""
class AdtnInstallFlowTask(Task):
"""
- OpenOMCI MIB Download Example - Service specific
-
- This task takes the legacy OMCI 'script' for provisioning the Adtran ONU
- and converts it to run as a Task on the OpenOMCI Task runner. This is
- in order to begin to decompose service instantiation in preparation for
- Technology Profile work.
-
- Once technology profiles are ready, some of this task may hang around or
- be moved into OpenOMCI if there are any very common settings/configs to do
- for any profile that may be provided in the v2.0 release
+ OpenOMCI MIB Flow Install Task
Currently, the only service tech profiles expected by v2.0 will be for AT&T
residential data service and DT residential data service.
"""
task_priority = Task.DEFAULT_PRIORITY + 10
- default_tpid = 0x8100
- default_gem_payload = 1518
- BRDCM_DEFAULT_VLAN = 4091
-
name = "ADTRAN MIB Install Flow Task"
- def __init__(self, omci_agent, handler, match, action, is_upstream):
+ def __init__(self, omci_agent, handler, flow_entry):
"""
Class initialization
:param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
:param handler: (AdtranOnuHandler) ONU Handler
- :param match: (dict) Flow match rules
- :param action: (dict) Flow action rules
- :param is_upstream: (bool) True if upstream flow is being installed
+ :param flow_entry: (FlowEntry) Flow to install
"""
super(AdtnInstallFlowTask, self).__init__(AdtnInstallFlowTask.name,
omci_agent,
handler.device_id,
- priority=AdtnInstallFlowTask.task_priority)
+ priority=AdtnInstallFlowTask.task_priority,
+ exclusive=False)
self._handler = handler
self._onu_device = omci_agent.get_device(handler.device_id)
self._local_deferred = None
-
- self._match = match
- self._action = action
+ self._flow_entry = flow_entry
# TODO: Cleanup below that is not needed
- self._vlan_tcis_1 = 0x900
- self._input_tpid = AdtnInstallFlowTask.default_tpid
- self._output_tpid = AdtnInstallFlowTask.default_tpid
+ is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+ uni_port = flow_entry.in_port if is_upstream else flow_entry.out_port
+ pon_port = flow_entry.out_port if is_upstream else flow_entry.in_port
- if self._handler.xpon_support:
- device = self._handler.adapter_agent.get_device(self.device_id)
- self._cvid = device.vlan
- else:
- # TODO: TCIS below is just a test, may need 0x900...as in the xPON mode
- self._vlan_tcis_1 = AdtnInstallFlowTask.BRDCM_DEFAULT_VLAN
- self._cvid = AdtnInstallFlowTask.BRDCM_DEFAULT_VLAN
+ self._uni = handler.uni_port(uni_port)
+ self._pon = handler.pon_port(pon_port)
# Entity IDs. IDs with values can probably be most anything for most ONUs,
# IDs set to None are discovered/set
#
# TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
#
- self._ieee_mapper_service_profile_entity_id = 0x100
- self._gal_enet_profile_entity_id = 0x100
+ self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
+ # self._hsi_mac_bridge_port_ani_entity_id = self._pon.hsi_mac_bridge_port_ani_entity_id
# Next to are specific
- self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
- self._vlan_config_entity_id = self._vlan_tcis_1
+ self._mac_bridge_service_profile_entity_id = handler.mac_bridge_service_profile_entity_id
def cancel_deferred(self):
super(AdtnInstallFlowTask, self).cancel_deferred()
@@ -155,15 +128,15 @@
elif status == RC.InstanceExists:
return False
- raise ServiceDownloadFailure('{} failed with a status of {}, error_mask: {}, failed_mask: {}, unsupported_mask: {}'
- .format(operation, status, error_mask, failed_mask, unsupported_mask))
+ raise ServiceInstallFailure('{} failed with a status of {}, error_mask: {}, failed_mask: {}, unsupported_mask: {}'
+ .format(operation, status, error_mask, failed_mask, unsupported_mask))
@inlineCallbacks
def perform_flow_install(self):
"""
Send the commands to configure the flow
"""
- self.log.info('perform-service-download')
+ self.log.info('perform-flow-install', vlan_vid=self._flow_entry.vlan_vid)
if self._handler.xpon_support:
self.deferred.callback('flow-install-nop') # xPON mode does not need this
@@ -171,105 +144,101 @@
def resources_available():
# TODO: Rework for non-xpon mode
return (len(self._handler.uni_ports) > 0 and
- len(self._handler.pon_port.tconts) and
- len(self._handler.pon_port.gem_ports) and
- self._action.get('set_vlan_vid') is not None)
+ len(self._pon.tconts) and
+ len(self._pon.gem_ports))
if self._handler.enabled and resources_available():
omci = self._onu_device.omci_cc
try:
# TODO: make this a member of the onu gem port or the uni port
- _mac_bridge_service_profile_entity_id = 0x201
- _mac_bridge_port_ani_entity_id = 0x2102 # TODO: can we just use the entity id from the anis list?
- _set_vlan_vid = self._action['set_vlan_vid']
+ vlan_vid = self._flow_entry.vlan_vid
- # Delete bridge ani side vlan filter
- msg = VlanTaggingFilterDataFrame(_mac_bridge_port_ani_entity_id)
- frame = msg.delete()
-
- results = yield omci.send(frame)
- self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
-
- # Re-Create bridge ani side vlan filter
- msg = VlanTaggingFilterDataFrame(
- _mac_bridge_port_ani_entity_id, # Entity ID
- vlan_tcis=[_set_vlan_vid], # VLAN IDs
- forward_operation=0x10
- )
- frame = msg.create()
-
- results = yield omci.send(frame)
- self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
+ # # Delete bridge ani side vlan filter
+ # msg = VlanTaggingFilterDataFrame(self._hsi_mac_bridge_port_ani_entity_id)
+ # frame = msg.delete()
+ #
+ # results = yield omci.send(frame)
+ # self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
+ #
+ # # Re-Create bridge ani side vlan filter
+ # msg = VlanTaggingFilterDataFrame(
+ # self._hsi_mac_bridge_port_ani_entity_id, # Entity ID
+ # vlan_tcis=[vlan_vid], # VLAN IDs
+ # forward_operation=0x10
+ # )
+ # frame = msg.create()
+ #
+ # results = yield omci.send(frame)
+ # self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
# Update uni side extended vlan filter
# filter for untagged
# probably for eapol
# TODO: magic 0x1000 / 4096?
# TODO: lots of magic
- attributes = dict(
- received_frame_vlan_tagging_operation_table=
- VlanTaggingOperation(
- filter_outer_priority=15,
- filter_outer_vid=4096,
- filter_outer_tpid_de=0,
-
- filter_inner_priority=15,
- filter_inner_vid=4096,
- filter_inner_tpid_de=0,
- filter_ether_type=0,
-
- treatment_tags_to_remove=0,
- treatment_outer_priority=15,
- treatment_outer_vid=0,
- treatment_outer_tpid_de=0,
-
- treatment_inner_priority=0,
- treatment_inner_vid=_set_vlan_vid,
- treatment_inner_tpid_de=4
- )
- )
- # TODO: Move this to a task
- msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
- _mac_bridge_service_profile_entity_id, # Bridge Entity ID
- attributes=attributes # See above
- )
- frame = msg.set()
-
- results = yield omci.send(frame)
- self.check_status_and_state(results,
- 'flow-set-ext-vlan-tagging-op-config-data-untagged')
+ # attributes = dict(
+ # # This table filters and tags upstream frames
+ # received_frame_vlan_tagging_operation_table=
+ # VlanTaggingOperation(
+ # filter_outer_priority=15, # This entry is not a double-tag rule (ignore out tag rules)
+ # filter_outer_vid=4096, # Do not filter on the outer VID value
+ # filter_outer_tpid_de=0, # Do not filter on the outer TPID field
+ #
+ # filter_inner_priority=15, # This is a no-tag rule, ignore all other VLAN tag filter fields
+ # filter_inner_vid=4096, # Do not filter on the inner VID
+ # filter_inner_tpid_de=0, # Do not filter on inner TPID field
+ # filter_ether_type=0, # Do not filter on EtherType
+ #
+ # treatment_tags_to_remove=0, # Remove 0 tags
+ #
+ # treatment_outer_priority=15, # Do not add an outer tag
+ # treatment_outer_vid=0, # n/a
+ # treatment_outer_tpid_de=0, # n/a
+ #
+ # treatment_inner_priority=0, # Add an inner tag and insert this value as the priority
+ # treatment_inner_vid=vlan_vid, # Push this tag onto the frame
+ # treatment_inner_tpid_de=4 # set TPID
+ # )
+ # )
+ # msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+ # self._mac_bridge_service_profile_entity_id, # Bridge Entity ID
+ # attributes=attributes # See above
+ # )
+ # frame = msg.set()
+ #
+ # results = yield omci.send(frame)
+ # self.check_status_and_state(results,
+ # 'flow-set-ext-vlan-tagging-op-config-data-untagged')
# Update uni side extended vlan filter
# filter for vlan 0
# TODO: lots of magic
+
attributes = dict(
+ # This table filters and tags upstream frames
received_frame_vlan_tagging_operation_table=
VlanTaggingOperation(
- filter_outer_priority=15,
- # This entry is not a double-tag rule
- filter_outer_vid=4096, # Do not filter on the outer VID value
- filter_outer_tpid_de=0,
- # Do not filter on the outer TPID field
+ filter_outer_priority=15, # This entry is not a double-tag rule
+ filter_outer_vid=4096, # Do not filter on the outer VID value
+ filter_outer_tpid_de=0, # Do not filter on the outer TPID field
- filter_inner_priority=8, # Filter on inner vlan
- filter_inner_vid=0x0, # Look for vlan 0
- filter_inner_tpid_de=0, # Do not filter on inner TPID field
- filter_ether_type=0, # Do not filter on EtherType
+ filter_inner_priority=8, # Filter on inner vlan
+ filter_inner_vid=0x0, # Look for vlan 0
+ filter_inner_tpid_de=0, # Do not filter on inner TPID field
+ filter_ether_type=0, # Do not filter on EtherType
- treatment_tags_to_remove=1,
- treatment_outer_priority=15,
- treatment_outer_vid=0,
- treatment_outer_tpid_de=0,
+ treatment_tags_to_remove=1, # Remove 1 tags
+ treatment_outer_priority=15, # Do not add an outer tag
+ treatment_outer_vid=0, # n/a
+ treatment_outer_tpid_de=0, # n/a
- treatment_inner_priority=8,
- # Add an inner tag and insert this value as the priority
- treatment_inner_vid=_set_vlan_vid,
- # use this value as the VID in the inner VLAN tag
- treatment_inner_tpid_de=4, # set TPID
+ treatment_inner_priority=8, # Add an inner tag and insert this value as the priority
+ treatment_inner_vid=vlan_vid, # use this value as the VID in the inner VLAN tag
+ treatment_inner_tpid_de=4, # set TPID to 0x8100
)
)
msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
- _mac_bridge_service_profile_entity_id, # Bridge Entity ID
+ self._mac_bridge_service_profile_entity_id, # Bridge Entity ID
attributes=attributes # See above
)
frame = msg.set()
@@ -286,24 +255,5 @@
else:
# TODO: Provide better error reason, what was missing...
- e = ServiceResourcesFailure('Required resources are not available')
+ e = ServiceInstallFailure('Required resources are not available')
self.deferred.errback(failure.Failure(e))
-
- def check_status_and_state(self, result, operation=''):
- from voltha.extensions.omci.omci_defs import ReasonCodes
- self.log.debug('function-entry')
- omci_msg = result.fields['omci_message'].fields
- status = omci_msg['success_code']
- error_mask = omci_msg.get('parameter_error_attributes_mask', 'n/a')
- failed_mask = omci_msg.get('failed_attributes_mask', 'n/a')
- unsupported_mask = omci_msg.get('unsupported_attributes_mask', 'n/a')
-
- self.log.debug("OMCI Result:", operation, omci_msg=omci_msg, status=status,
- error_mask=error_mask,
- failed_mask=failed_mask, unsupported_mask=unsupported_mask)
-
- if status == ReasonCodes.Success:
- return True
-
- elif status == ReasonCodes.InstanceExists:
- return False
diff --git a/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py b/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
index 6c853e1..136e2e2 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_mib_download_task.py
@@ -55,7 +55,7 @@
default_tpid = 0x8100
default_gem_payload = 1518
- name = "ADTRAN MIB Download Example Task"
+ name = "ADTRAN MIB Download Task"
def __init__(self, omci_agent, handler):
"""
@@ -67,7 +67,8 @@
super(AdtnMibDownloadTask, self).__init__(AdtnMibDownloadTask.name,
omci_agent,
handler.device_id,
- priority=AdtnMibDownloadTask.task_priority)
+ priority=AdtnMibDownloadTask.task_priority,
+ exclusive=True)
self._handler = handler
self._onu_device = omci_agent.get_device(handler.device_id)
self._local_deferred = None
@@ -79,21 +80,22 @@
self._pon_port_num = 0
self._uni_port_num = 0 # TODO Both port numbers are the same, is this correct? See MacBridgePortConfigurationDataFrame
- self._vlan_tcis_1 = 0x900
+ self._pon = handler.pon_port()
+ self._vlan_tcis_1 = self._handler.vlan_tcis_1
# Entity IDs. IDs with values can probably be most anything for most ONUs,
# IDs set to None are discovered/set
#
# TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
#
- self._mac_bridge_service_profile_entity_id = 0x100
- self._ieee_mapper_service_profile_entity_id = 0x100
- self._mac_bridge_port_ani_entity_id = 0x100
+ self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
+ self._mac_bridge_port_ani_entity_id = self._pon.hsi_mac_bridge_port_ani_entity_id
self._gal_enet_profile_entity_id = 0x100
- # Next to are specific
+ # Next to are specific TODO: UNI lookups here or uni specific install !!!
self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
- self._vlan_config_entity_id = self._vlan_tcis_1
+ self._mac_bridge_service_profile_entity_id = \
+ self._handler.mac_bridge_service_profile_entity_id
def cancel_deferred(self):
super(AdtnMibDownloadTask, self).cancel_deferred()
@@ -172,17 +174,17 @@
# Lock the UNI ports to prevent any alarms during initial configuration
# of the ONU
self.strobe_watchdog()
- yield self.enable_unis(self._handler.uni_ports, True)
+ # yield self.enable_unis(self._handler.uni_ports, True)
# Provision the initial bridge configuration
yield self.perform_initial_bridge_setup()
- # If here, we are done
+ # If here, we are done with the generic MIB download
device = self._handler.adapter_agent.get_device(self.device_id)
device.reason = 'Initial OMCI Download Complete'
self._handler.adapter_agent.update_device(device)
- self.deferred.callback('TODO: What should we return to the caller?')
+ self.deferred.callback('MIB Download - success')
except TimeoutError as e:
self.deferred.errback(failure.Failure(e))
@@ -207,7 +209,6 @@
# - MAC Bridge Port Configuration Data (PON & UNI)
# References:
# - Nothing
-
attributes = {
'spanning_tree_ind': False
}
@@ -251,15 +252,15 @@
frame = MacBridgePortConfigurationDataFrame(
self._mac_bridge_port_ani_entity_id, # Entity ID
bridge_id_pointer=self._mac_bridge_service_profile_entity_id, # Bridge Entity ID
- # TODO: The PORT number for this port and the UNI port are the same. Is this correct?
+ # TODO: The PORT number for this port and the UNI port are the same. Correct?
port_num=self._pon_port_num, # Port ID
tp_type=3, # TP Type (IEEE 802.1p mapper service)
tp_pointer=self._ieee_mapper_service_profile_entity_id # TP ID, 8021p mapper ID
).create()
results = yield omci_cc.send(frame)
- self.check_status_and_state(results, 'create-mac-bridge-port-configuration-data-part-1')
+ self.check_status_and_state(results, 'create-mac-bridge-port-config-data-part-1')
- ################################################################################
+ #############################################################
# VLAN Tagging Filter config
#
# EntityID will be referenced by:
@@ -269,15 +270,15 @@
#
# Set anything, this request will not be used when using Extended Vlan
- frame = VlanTaggingFilterDataFrame(
- self._mac_bridge_port_ani_entity_id, # Entity ID
- vlan_tcis=[self._vlan_tcis_1], # VLAN IDs
- forward_operation=0x10
- ).create()
- results = yield omci_cc.send(frame)
- self.check_status_and_state(results, 'create-vlan-tagging-filter-data')
+ # frame = VlanTaggingFilterDataFrame(
+ # self._mac_bridge_port_ani_entity_id, # Entity ID
+ # vlan_tcis=[self._vlan_tcis_1], # VLAN IDs
+ # forward_operation=0x10
+ # ).create()
+ # results = yield omci_cc.send(frame)
+ # self.check_status_and_state(results, 'create-vlan-tagging-filter-data')
- ########################################################################################
+ #############################################################
# Create GalEthernetProfile - Once per ONU/PON interface
#
# EntityID will be referenced by:
@@ -292,9 +293,9 @@
results = yield omci_cc.send(frame)
self.check_status_and_state(results, 'create-gal-ethernet-profile')
- ################################################################################
- # UNI Specific #
- ################################################################################
+ ##################################################
+ # UNI Specific #
+ ##################################################
# MAC Bridge Port config
# This configuration is for Ethernet UNI
#
@@ -312,10 +313,10 @@
tp_pointer=self._ethernet_uni_entity_id # TP ID, 8021p mapper Id
).create()
results = yield omci_cc.send(frame)
- self.check_status_and_state(results, 'create-mac-bridge-port-configuration-data-part-2')
+ self.check_status_and_state(results, 'create-mac-bridge-port-config-data-part-2')
- except TimeoutError as e:
- self.log.warn('rx-timeout-1', frame=frame)
+ except TimeoutError as _e:
+ self.log.warn('rx-timeout-download', frame=hexlify(frame))
raise
except Exception as e:
@@ -336,7 +337,7 @@
frame = None
for uni in unis:
- ################################################################################
+ ##################################################################
# Lock/Unlock UNI - 0 to Unlock, 1 to lock
#
# EntityID is referenced by:
@@ -351,7 +352,7 @@
self.check_status_and_state(results, 'set-pptp-ethernet-uni-lock-restore')
except TimeoutError:
- self.log.warn('rx-timeout', frame=frame)
+ self.log.warn('rx-timeout-uni-enable', frame=hexlify(frame))
raise
except Exception as e:
diff --git a/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py b/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py
new file mode 100644
index 0000000..9ca1c19
--- /dev/null
+++ b/voltha/adapters/adtran_onu/omci/adtn_remove_flow.py
@@ -0,0 +1,226 @@
+#
+# Copyright 2018 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+from twisted.internet import reactor
+from twisted.internet.defer import inlineCallbacks, failure
+from voltha.extensions.omci.omci_me import *
+from voltha.extensions.omci.tasks.task import Task
+from voltha.extensions.omci.omci_defs import *
+from voltha.adapters.adtran_onu.flow.flow_entry import FlowEntry
+from voltha.adapters.adtran_onu.omci.omci import OMCI
+
+OP = EntityOperations
+RC = ReasonCodes
+
+
+class ServiceRemovalFailure(Exception):
+ """
+ This error is raised by default when the flow-install fails
+ """
+
+
+class AdtnRemoveFlowTask(Task):
+ """
+ OpenOMCI MIB Flow Remove Task
+
+ Currently, the only service tech profiles expected by v2.0 will be for AT&T
+ residential data service and DT residential data service.
+ """
+ task_priority = Task.DEFAULT_PRIORITY + 10
+ default_tpid = 0x8100 # TODO: Locate to a better location
+
+ name = "ADTRAN MIB Install Flow Task"
+
+ def __init__(self, omci_agent, handler, flow_entry):
+ """
+ Class initialization
+
+ :param omci_agent: (OmciAdapterAgent) OMCI Adapter agent
+ :param handler: (AdtranOnuHandler) ONU Handler
+ :param flow_entry: (FlowEntry) Flow to install
+ """
+ super(AdtnRemoveFlowTask, self).__init__(AdtnRemoveFlowTask.name,
+ omci_agent,
+ handler.device_id,
+ priority=AdtnRemoveFlowTask.task_priority,
+ exclusive=False)
+ self._handler = handler
+ self._onu_device = omci_agent.get_device(handler.device_id)
+ self._local_deferred = None
+ self._flow_entry = flow_entry
+
+ # TODO: Cleanup below that is not needed
+ # self._vlan_tcis_1 = 0x900
+ # self._input_tpid = AdtnRemoveFlowTask.default_tpid
+ # self._output_tpid = AdtnRemoveFlowTask.default_tpid
+
+ is_upstream = flow_entry.flow_direction == FlowEntry.FlowDirection.UPSTREAM
+ # uni_port = flow_entry.in_port if is_upstream else flow_entry.out_port
+ pon_port = flow_entry.out_port if is_upstream else flow_entry.in_port
+
+ # self._uni = handler.uni_port(uni_port)
+ self._pon = handler.pon_port(pon_port)
+
+ self._vid = OMCI.DEFAULT_UNTAGGED_VLAN
+
+ # Entity IDs. IDs with values can probably be most anything for most ONUs,
+ # IDs set to None are discovered/set
+ #
+ # TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
+ #
+ self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
+ self._mac_bridge_port_ani_entity_id = self._pon.hsi_mac_bridge_port_ani_entity_id
+
+ # Next to are specific
+ self._mac_bridge_service_profile_entity_id = handler.mac_bridge_service_profile_entity_id
+
+ def cancel_deferred(self):
+ super(AdtnRemoveFlowTask, self).cancel_deferred()
+
+ d, self._local_deferred = self._local_deferred, None
+ try:
+ if d is not None and not d.called:
+ d.cancel()
+ except:
+ pass
+
+ def start(self):
+ """
+ Start the flow installation
+ """
+ super(AdtnRemoveFlowTask, self).start()
+ self._local_deferred = reactor.callLater(0, self.perform_flow_removal)
+
+ def stop(self):
+ """
+ Shutdown flow install task
+ """
+ self.log.debug('stopping')
+
+ self.cancel_deferred()
+ super(AdtnRemoveFlowTask, self).stop()
+
+ def check_status_and_state(self, results, operation=''):
+ """
+ Check the results of an OMCI response. An exception is thrown
+ if the task was cancelled or an error was detected.
+
+ :param results: (OmciFrame) OMCI Response frame
+ :param operation: (str) what operation was being performed
+ :return: True if successful, False if the entity existed (already created)
+ """
+ omci_msg = results.fields['omci_message'].fields
+ status = omci_msg['success_code']
+ error_mask = omci_msg.get('parameter_error_attributes_mask', 'n/a')
+ failed_mask = omci_msg.get('failed_attributes_mask', 'n/a')
+ unsupported_mask = omci_msg.get('unsupported_attributes_mask', 'n/a')
+
+ self.log.debug(operation, status=status, error_mask=error_mask,
+ failed_mask=failed_mask, unsupported_mask=unsupported_mask)
+
+ if status == RC.Success:
+ self.strobe_watchdog()
+ return True
+
+ elif status == RC.InstanceExists:
+ return False
+
+ raise ServiceRemovalFailure(
+ '{} failed with a status of {}, error_mask: {}, failed_mask: {}, unsupported_mask: {}'
+ .format(operation, status, error_mask, failed_mask, unsupported_mask))
+
+ @inlineCallbacks
+ def perform_flow_removal(self):
+ """
+ Send the commands to configure the flow
+ """
+ self.log.info('perform-flow-removall')
+
+ if self._handler.xpon_support:
+ self.deferred.callback('flow-removal-nop') # xPON mode does not need this
+
+ def resources_available():
+ # TODO: Rework for non-xpon mode
+ return (len(self._handler.uni_ports) > 0 and
+ len(self._pon.tconts) and
+ len(self._pon.gem_ports))
+
+ if self._handler.enabled and resources_available():
+ omci = self._onu_device.omci_cc
+ try:
+ # TODO: make this a member of the onu gem port or the uni port
+ set_vlan_vid = self._flow_entry.set_vlan_vid
+
+ # # Delete bridge ani side vlan filter
+ # msg = VlanTaggingFilterDataFrame(self._mac_bridge_port_ani_entity_id)
+ # frame = msg.delete()
+ #
+ # results = yield omci.send(frame)
+ # self.check_status_and_state(results, 'flow-delete-vlan-tagging-filter-data')
+ #
+ # # Re-Create bridge ani side vlan filter
+ # msg = VlanTaggingFilterDataFrame(
+ # self._mac_bridge_port_ani_entity_id, # Entity ID
+ # vlan_tcis=[self._vlan_tcis_1], # VLAN IDs
+ # forward_operation=0x10
+ # )
+ # frame = msg.create()
+ # results = yield omci.send(frame)
+ # self.check_status_and_state(results, 'flow-create-vlan-tagging-filter-data')
+
+ # Update uni side extended vlan filter
+ attributes = dict(
+ received_frame_vlan_tagging_operation_table=
+ VlanTaggingOperation(
+ filter_outer_priority=15, # This entry is not a double-tag rule
+ filter_outer_vid=4096, # Do not filter on the outer VID value
+ filter_outer_tpid_de=0, # Do not filter on the outer TPID field
+
+ filter_inner_priority=15, # This is a no-tag rule, ignore all other VLAN tag filter fields
+ filter_inner_vid=0x1000, # Do not filter on the inner VID
+ filter_inner_tpid_de=0, # Do not filter on inner TPID field
+
+ filter_ether_type=0, # Do not filter on EtherType
+ treatment_tags_to_remove=0, # Remove 0 tags
+
+ treatment_outer_priority=15, # Do not add an outer tag
+ treatment_outer_vid=0, # n/a
+ treatment_outer_tpid_de=0, # n/a
+
+ treatment_inner_priority=0, # Add an inner tag and insert this value as the priority
+ treatment_inner_vid=self._vid, # use this value as the VID in the inner VLAN tag
+ treatment_inner_tpid_de=4, # set TPID
+ )
+ )
+ msg = ExtendedVlanTaggingOperationConfigurationDataFrame(
+ self._mac_bridge_service_profile_entity_id, # Bridge Entity ID
+ attributes=attributes # See above
+ )
+ frame = msg.set()
+ results = yield omci.send(frame)
+ self.check_status_and_state(results,
+ 'flow-set-ext-vlan-tagging-op-config-data-untagged')
+
+ self.deferred.callback('flow-remove-success')
+
+ except Exception as e:
+ # TODO: Better context info for this exception output...
+ self.log.exception('failed-to-remove-flow', e=e)
+ self.deferred.errback(failure.Failure(e))
+
+ else:
+ # TODO: Provide better error reason, what was missing...
+ e = ServiceRemovalFailure('Required resources are not available')
+ self.deferred.errback(failure.Failure(e))
diff --git a/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py b/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
index e62cb6e..6dc510a 100644
--- a/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
+++ b/voltha/adapters/adtran_onu/omci/adtn_service_download_task.py
@@ -18,6 +18,7 @@
from voltha.extensions.omci.omci_me import *
from voltha.extensions.omci.tasks.task import Task
from voltha.extensions.omci.omci_defs import *
+from voltha.adapters.adtran_onu.omci.omci import OMCI
OP = EntityOperations
RC = ReasonCodes
@@ -52,11 +53,8 @@
residential data service and DT residential data service.
"""
task_priority = Task.DEFAULT_PRIORITY + 10
- default_tpid = 0x8100
- default_gem_payload = 1518
- BRDCM_DEFAULT_VLAN = 4091
-
- name = "ADTRAN MIB Download Example Task"
+ default_tpid = 0x8100 # TODO: Move to a better location
+ name = "ADTRAN Service Download Task"
def __init__(self, omci_agent, handler):
"""
@@ -68,34 +66,36 @@
super(AdtnServiceDownloadTask, self).__init__(AdtnServiceDownloadTask.name,
omci_agent,
handler.device_id,
- priority=AdtnServiceDownloadTask.task_priority)
+ priority=AdtnServiceDownloadTask.task_priority,
+ exclusive=True)
self._handler = handler
self._onu_device = omci_agent.get_device(handler.device_id)
self._local_deferred = None
+ self._pon = handler.pon_port()
- self._vlan_tcis_1 = 0x900
+ # self._vlan_tcis_1 = self._handler.vlan_tcis_1
self._input_tpid = AdtnServiceDownloadTask.default_tpid
self._output_tpid = AdtnServiceDownloadTask.default_tpid
if self._handler.xpon_support:
device = self._handler.adapter_agent.get_device(self.device_id)
- self._cvid = device.vlan
+ self._vid = device.vlan
else:
# TODO: TCIS below is just a test, may need 0x900...as in the xPON mode
- self._vlan_tcis_1 = AdtnServiceDownloadTask.BRDCM_DEFAULT_VLAN
- self._cvid = AdtnServiceDownloadTask.BRDCM_DEFAULT_VLAN
+ # self._vlan_tcis_1 = OMCI.DEFAULT_UNTAGGED_VLAN
+ self._vid = OMCI.DEFAULT_UNTAGGED_VLAN
# Entity IDs. IDs with values can probably be most anything for most ONUs,
# IDs set to None are discovered/set
#
# TODO: Probably need to store many of these in the appropriate object (UNI, PON,...)
#
- self._ieee_mapper_service_profile_entity_id = 0x100
+ self._ieee_mapper_service_profile_entity_id = self._pon.hsi_8021p_mapper_entity_id
self._gal_enet_profile_entity_id = 0x100
# Next to are specific
self._ethernet_uni_entity_id = self._handler.uni_ports[0].entity_id
- self._vlan_config_entity_id = self._vlan_tcis_1
+ self._mac_bridge_service_profile_entity_id = self._handler.mac_bridge_service_profile_entity_id
def cancel_deferred(self):
super(AdtnServiceDownloadTask, self).cancel_deferred()
@@ -160,7 +160,6 @@
have been defined.
"""
self.log.info('perform-service-download')
-
device = self._handler.adapter_agent.get_device(self.device_id)
def resources_available():
@@ -168,12 +167,12 @@
if self._handler.xpon_support:
return (device.vlan > 0 and
len(self._handler.uni_ports) > 0 and
- len(self._handler.pon_port.tconts) and
- len(self._handler.pon_port.gem_ports))
+ len(self._pon.tconts) and
+ len(self._pon.gem_ports))
else:
return (len(self._handler.uni_ports) > 0 and
- len(self._handler.pon_port.tconts) and
- len(self._handler.pon_port.gem_ports))
+ len(self._pon.tconts) and
+ len(self._pon.gem_ports))
if self._handler.enabled and resources_available():
device.reason = 'Performing Service OMCI Download'
@@ -191,14 +190,12 @@
# If here, we are done
device = self._handler.adapter_agent.get_device(self.device_id)
-
device.reason = ''
self._handler.adapter_agent.update_device(device)
self.deferred.callback('service-download-success')
except TimeoutError as e:
self.deferred.errback(failure.Failure(e))
-
else:
# TODO: Provide better error reason, what was missing...
e = ServiceResourcesFailure('Required resources are not available')
@@ -221,7 +218,7 @@
tcont_idents = self._onu_device.query_mib(Tcont.class_id)
self.log.debug('tcont-idents', tcont_idents=tcont_idents)
- for tcont in self._handler.pon_port.tconts.itervalues():
+ for tcont in self._pon.tconts.itervalues():
free_entity_id = next((k for k, v in tcont_idents.items()
if isinstance(k, int) and
v.get('attributes', {}).get('alloc_id', 0) == 0xFFFF), None)
@@ -258,7 +255,7 @@
# - GalEthernetProfile
#
- for gem_port in self._handler.pon_port.gem_ports.itervalues():
+ for gem_port in self._pon.gem_ports.itervalues():
tcont = gem_port.tcont
if tcont is None:
self.log.error('unknown-tcont-reference', gem_id=gem_port.gem_id)
@@ -278,7 +275,7 @@
# - Gem Interworking TPs are set here
#
# TODO: All p-bits currently go to the one and only GEMPORT ID for now
- gem_ports = self._handler.pon_port.gem_ports
+ gem_ports = self._pon.gem_ports
gem_entity_ids = [gem_port.entity_id for _, gem_port in gem_ports.items()] \
if len(gem_ports) else [OmciNullPointer]
@@ -305,7 +302,7 @@
)
frame = ExtendedVlanTaggingOperationConfigurationDataFrame(
- self._vlan_config_entity_id,
+ self._mac_bridge_service_profile_entity_id,
attributes=attributes
).create()
results = yield omci_cc.send(frame)
@@ -324,7 +321,7 @@
downstream_mode=0, # inverse of upstream
)
frame = ExtendedVlanTaggingOperationConfigurationDataFrame(
- self._vlan_config_entity_id,
+ self._mac_bridge_service_profile_entity_id,
attributes=attributes
).set()
results = yield omci_cc.send(frame)
@@ -355,56 +352,21 @@
treatment_outer_tpid_de=0, # n/a
treatment_inner_priority=0, # Add an inner tag and insert this value as the priority
- treatment_inner_vid=self._cvid, # use this value as the VID in the inner VLAN tag
+ treatment_inner_vid=self._vid, # use this value as the VID in the inner VLAN tag
treatment_inner_tpid_de=4, # set TPID
)
)
frame = ExtendedVlanTaggingOperationConfigurationDataFrame(
- self._vlan_config_entity_id, # Entity ID
- attributes=attributes # See above
+ self._mac_bridge_service_profile_entity_id, # Entity ID
+ attributes=attributes # See above
).set()
results = yield omci_cc.send(frame)
self.check_status_and_state(results, 'set-extended-vlan-tagging-operation-configuration-data-untagged')
- ################################################################################
- ################################################################################
- ################################################################################
- # BP: This is for AT&T RG's #
- # TODO: CB: NOTE: TRY THIS ONCE OTHER SEQUENCES WORK
- #
- # Set AR - ExtendedVlanTaggingOperationConfigData
- # 514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid
- # results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(
- # 0x900, # Entity ID
- # 8, # Filter Inner Priority, do not filter on Inner Priority
- # 0, # Filter Inner VID, this will be 0 in CORD
- # 0, # Filter Inner TPID DE
- # 1, # Treatment tags, number of tags to remove
- # 8, # Treatment inner priority, copy Inner Priority
- # 2) # Treatment inner VID, this will be 2 in CORD
- # Set AR - ExtendedVlanTaggingOperationConfigData
- # 514 - RxVlanTaggingOperationTable - add VLAN <cvid> to priority tagged pkts - c-vid
- # results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_single_tag(
- # 0x200, # Entity ID
- # 8, # Filter Inner Priority
- # 0, # Filter Inner VID
- # 0, # Filter Inner TPID DE
- # 1, # Treatment tags to remove
- # 8, # Treatment inner priority
- # cvid) # Treatment inner VID
- # Set AR - ExtendedVlanTaggingOperationConfigData
- # 514 - RxVlanTaggingOperationTable - add VLAN <cvid> to untagged pkts - c-vid
- # results = yield omci.send_set_extended_vlan_tagging_operation_vlan_configuration_data_untagged(
- # 0x100, # Entity ID BP: Oldvalue 0x202
- # 0x1000, # Filter Inner VID BP: Oldvalue 0x1000
- # cvid) # Treatment inner VID BP: cvid
- # success = results.fields['omci_message'].fields['success_code'] == 0
- # error_mask = results.fields['omci_message'].fields['parameter_error_attributes_mask']
-
###############################################################################
except TimeoutError as e:
- self.log.warn('rx-timeout-2', frame=frame)
+ self.log.warn('rx-timeout-download', frame=hexlify(frame))
raise
except Exception as e:
@@ -440,7 +402,7 @@
self.check_status_and_state(results, 'set-pptp-ethernet-uni-lock-restore')
except TimeoutError:
- self.log.warn('rx-timeout', frame=frame)
+ self.log.warn('rx-timeout-unis', frame=hexlify(frame))
raise
except Exception as e:
diff --git a/voltha/adapters/adtran_onu/omci/omci.py b/voltha/adapters/adtran_onu/omci/omci.py
index 9e1ba37..15244b8 100644
--- a/voltha/adapters/adtran_onu/omci/omci.py
+++ b/voltha/adapters/adtran_onu/omci/omci.py
@@ -11,7 +11,6 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-
import structlog
from twisted.internet.defer import inlineCallbacks, returnValue, TimeoutError
from twisted.internet import reactor
@@ -33,6 +32,8 @@
"""
OpenOMCI Support
"""
+ DEFAULT_UNTAGGED_VLAN = 4091 # To be equivalent to BroadCom Defaults
+
def __init__(self, handler, omci_agent):
self.log = structlog.get_logger(device_id=handler.device_id)
self._handler = handler
@@ -43,7 +44,6 @@
self._resync_deferred = None # For TCont/GEM use
self._bridge_initialized = False
self._in_sync_reached = False
-
self._omcc_version = OMCCVersion.Unknown
self._total_tcont_count = 0 # From ANI-G ME
self._qos_flexibility = 0 # From ONT2_G ME
@@ -334,23 +334,26 @@
if self._mib_downloaded:
self._service_downloaded = True
else:
- # Now try the services
+ # Now try the services (HSI, ...) specific download
self._mib_downloaded = True
- reactor.callLater(0, self.capabilities_handler)
+ reactor.callLater(0, self.capabilities_handler, None, None)
self._mib_download_task = None
- def failure(_reason):
+ def failure(reason):
+ self.log.error('mib-download-failure', reason=reason)
self._mib_download_task = None
- # TODO: Handle failure, retry for now?
self._mib_download_deferred = reactor.callLater(_STARTUP_RETRY_WAIT,
- self.capabilities_handler)
+ self.capabilities_handler,
+ None, None)
if not self._mib_downloaded:
- self._mib_download_task = AdtnMibDownloadTask(self.omci_agent, self._handler)
+ self._mib_download_task = AdtnMibDownloadTask(self.omci_agent,
+ self._handler)
else:
- self._mib_download_task = AdtnServiceDownloadTask(self.omci_agent, self._handler)
-
- self._mib_download_deferred = self._onu_omci_device.task_runner.queue_task(self._mib_download_task)
+ self._mib_download_task = AdtnServiceDownloadTask(self.omci_agent,
+ self._handler)
+ self._mib_download_deferred = \
+ self._onu_omci_device.task_runner.queue_task(self._mib_download_task)
self._mib_download_deferred.addCallbacks(success, failure)
def onu_is_reachable(self, _topic, msg):
diff --git a/voltha/adapters/adtran_onu/pon_port.py b/voltha/adapters/adtran_onu/pon_port.py
index 49b2235..4962f4e 100644
--- a/voltha/adapters/adtran_onu/pon_port.py
+++ b/voltha/adapters/adtran_onu/pon_port.py
@@ -41,6 +41,11 @@
self._gem_ports = {} # gem-id -> GemPort
self._tconts = {} # alloc-id -> TCont
+ # OMCI resources
+ # TODO: These could be dynamically chosen (can be most any value)
+ self.hsi_8021p_mapper_entity_id = 0x100
+ self.hsi_mac_bridge_port_ani_entity_id = 0x100
+
def __str__(self):
return "PonPort" # TODO: Encode current state
diff --git a/voltha/adapters/adtran_onu/uni_port.py b/voltha/adapters/adtran_onu/uni_port.py
index 0c53b2b..2259058 100644
--- a/voltha/adapters/adtran_onu/uni_port.py
+++ b/voltha/adapters/adtran_onu/uni_port.py
@@ -20,12 +20,11 @@
from voltha.protos.logical_device_pb2 import LogicalPort
from voltha.protos.openflow_13_pb2 import OFPPS_LIVE, OFPPF_FIBER
from voltha.protos.openflow_13_pb2 import ofp_port
+from omci.omci import OMCI
class UniPort(object):
"""Wraps southbound-port(s) support for ONU"""
- DEFAULT_UNTAGGED_VLAN = 4091 # TODO: BroadCom Default. Need a better way to define this
-
def __init__(self, handler, name, port_no, ofp_port_no, subscriber_vlan=None,
untagged_vlan=None):
self.log = structlog.get_logger(device_id=handler.device_id,
@@ -44,6 +43,7 @@
self._admin_state = AdminState.ENABLED
self._oper_status = OperStatus.ACTIVE
+
# TODO Add state, stats, alarm reference, ...
pass
@@ -144,6 +144,12 @@
"""
return self._subscriber_vlan
+ @subscriber_vlan.setter
+ def subscriber_vlan(self, value):
+ if value:
+ if self._subscriber_vlan is None or self._subscriber_vlan != value:
+ self._subscriber_vlan = value
+
@property
def logical_port_number(self):
"""
@@ -178,7 +184,7 @@
# port number. UNI-1, UNI 1, and UNI 3-2-1 are the same
port_no = int(venet_info['name'].replace(' ', '-').split('-')[-1:][0])
subscriber_vlan = port_no
- untagged_vlan = UniPort.DEFAULT_UNTAGGED_VLAN
+ untagged_vlan = OMCI.DEFAULT_UNTAGGED_VLAN
try:
# Subscriber VLAN and Untagged vlan are comma separated
parts = venet_info['description'].split(',')