blob: 6b00c2e537979673c5680e4a260431e99b7afe71 [file] [log] [blame]
#
# 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 __future__ import absolute_import
import structlog
from twisted.internet.defer import inlineCallbacks, returnValue
from pyvoltha.adapters.extensions.omci.omci_me import GemInterworkingTpFrame, GemPortNetworkCtpFrame
from pyvoltha.adapters.extensions.omci.omci_defs import ReasonCodes
RC = ReasonCodes
class OnuGemPort(object):
"""
Broadcom ONU specific implementation
"""
def __init__(self, gem_id, uni_id, alloc_id,
entity_id=None,
direction="BIDIRECTIONAL",
encryption=False,
discard_config=None,
discard_policy=None,
max_q_size="auto",
pbit_map="0b00000011",
priority_q=3,
scheduling_policy="WRR",
weight=8,
omci_transport=False,
multicast=False,
tcont_ref=None,
traffic_class=None,
intf_ref=None,
untagged=False,
name=None,
handler=None):
self.log = structlog.get_logger(device_id=handler.device_id, uni_id=uni_id, gem_id=gem_id)
self.name = name
self.gem_id = gem_id
self.uni_id = uni_id
self._alloc_id = alloc_id
self.tcont_ref = tcont_ref
self.intf_ref = intf_ref
self.traffic_class = traffic_class
self._direction = None
self._encryption = encryption
self._discard_config = None
self._discard_policy = None
self._max_q_size = None
self._pbit_map = None
self._scheduling_policy = None
self._omci_transport = omci_transport
self.multicast = multicast
self.untagged = untagged
self._handler = handler
self.direction = direction
self.encryption = encryption
self.discard_config = discard_config
self.discard_policy = discard_policy
self.max_q_size = max_q_size
self.pbit_map = pbit_map
self.priority_q = priority_q
self.scheduling_policy = scheduling_policy
self.weight = weight
self._pon_id = None
self._onu_id = None
self._entity_id = entity_id
# Statistics
self.rx_packets = 0
self.rx_bytes = 0
self.tx_packets = 0
self.tx_bytes = 0
def __str__(self):
return "OnuGemPort - entity_id {}, alloc-id: {}, gem-id: {}".format(self.entity_id, self.alloc_id,
self.gem_id)
def __repr__(self):
return str(self)
@property
def pon_id(self):
return self._pon_id
@pon_id.setter
def pon_id(self, pon_id):
assert self._pon_id is None or self._pon_id == pon_id, 'PON-ID can only be set once'
self._pon_id = pon_id
@property
def onu_id(self):
return self._onu_id
@onu_id.setter
def onu_id(self, onu_id):
assert self._onu_id is None or self._onu_id == onu_id, 'ONU-ID can only be set once'
self._onu_id = onu_id
@property
def alloc_id(self):
return self._alloc_id
@property
def direction(self):
return self._direction
@direction.setter
def direction(self, direction):
# GEM Port CTP are configured separately in UPSTREAM and DOWNSTREAM.
# BIDIRECTIONAL is not supported.
assert direction == "UPSTREAM" or direction == "DOWNSTREAM" or \
direction == "BIDIRECTIONAL", "invalid-direction"
# OMCI framework expects string in lower-case. Tech-Profile sends in upper-case.
if direction == "UPSTREAM":
self._direction = "upstream"
elif direction == "DOWNSTREAM":
self._direction = "downstream"
elif direction == "BIDIRECTIONAL":
self._direction = "bi-directional"
@property
def tcont(self):
tcont_item = self._handler.pon_port.tconts.get(self.alloc_id)
return tcont_item
@property
def omci_transport(self):
return self._omci_transport
def to_dict(self):
return {
'port-id': self.gem_id,
'alloc-id': self.alloc_id,
'encryption': self._encryption,
'omci-transport': self.omci_transport
}
@property
def entity_id(self):
return self._entity_id
@entity_id.setter
def entity_id(self, value):
self._entity_id = value
@property
def encryption(self):
return self._encryption
@encryption.setter
def encryption(self, value):
# FIXME The encryption should come as boolean by default
value = eval(value)
assert isinstance(value, bool), 'encryption is a boolean'
if self._encryption != value:
self._encryption = value
@property
def discard_config(self):
return self._discard_config
@discard_config.setter
def discard_config(self, discard_config):
assert isinstance(discard_config, dict), "discard_config not dict"
assert 'max_probability' in discard_config, "max_probability missing"
assert 'max_threshold' in discard_config, "max_threshold missing"
assert 'min_threshold' in discard_config, "min_threshold missing"
self._discard_config = discard_config
@property
def discard_policy(self):
return self._discard_policy
@discard_policy.setter
def discard_policy(self, discard_policy):
dp = ("TailDrop", "WTailDrop", "RED", "WRED")
assert (isinstance(discard_policy, str))
assert (discard_policy in dp)
self._discard_policy = discard_policy
@property
def max_q_size(self):
return self._max_q_size
@max_q_size.setter
def max_q_size(self, max_q_size):
if isinstance(max_q_size, str):
assert (max_q_size == "auto")
else:
assert (isinstance(max_q_size, int))
self._max_q_size = max_q_size
@property
def pbit_map(self):
return self._pbit_map
@pbit_map.setter
def pbit_map(self, pbit_map):
assert (isinstance(pbit_map, str))
assert (len(pbit_map[2:]) == 8) # Example format of pbit_map: "0b00000101"
try:
_ = int(pbit_map[2], 2)
except ValueError:
raise Exception("pbit_map-not-binary-string-{}".format(pbit_map))
# remove '0b'
self._pbit_map = pbit_map[2:]
@property
def scheduling_policy(self):
return self._scheduling_policy
@scheduling_policy.setter
def scheduling_policy(self, scheduling_policy):
sp = ("WRR", "StrictPriority")
assert (isinstance(scheduling_policy, str))
assert (scheduling_policy in sp)
self._scheduling_policy = scheduling_policy
@staticmethod
def create(handler, gem_port):
return OnuGemPort(gem_id=gem_port['gemport_id'],
uni_id=gem_port['uni_id'],
alloc_id=gem_port['alloc_id_ref'],
direction=gem_port['direction'],
encryption=gem_port['encryption'], # aes_indicator,
discard_config=gem_port['discard_config'],
discard_policy=gem_port['discard_policy'],
max_q_size=gem_port['max_q_size'],
pbit_map=gem_port['pbit_map'],
priority_q=gem_port['priority_q'],
scheduling_policy=gem_port['scheduling_policy'],
weight=gem_port['weight'],
handler=handler,
untagged=False)
@inlineCallbacks
def add_to_hardware(self, omci,
tcont_entity_id,
ieee_mapper_service_profile_entity_id,
gal_enet_profile_entity_id,
ul_prior_q_entity_id,
dl_prior_q_entity_id):
self.log.debug('add-to-hardware', entity_id=self.entity_id, gem_id=self.gem_id,
tcont_entity_id=tcont_entity_id,
ieee_mapper_service_profile_entity_id=ieee_mapper_service_profile_entity_id,
gal_enet_profile_entity_id=gal_enet_profile_entity_id,
ul_prior_q_entity_id=ul_prior_q_entity_id,
dl_prior_q_entity_id=dl_prior_q_entity_id)
try:
direction = "downstream" if self.multicast else "bi-directional"
assert not self.multicast, 'MCAST is not supported yet'
attributes = dict()
attributes['priority_queue_pointer_downstream'] = dl_prior_q_entity_id
msg = GemPortNetworkCtpFrame(
self.entity_id, # same entity id as GEM port
port_id=self.gem_id,
tcont_id=tcont_entity_id,
direction=direction,
upstream_tm=ul_prior_q_entity_id,
attributes=attributes
)
frame = msg.create()
self.log.debug('openomci-msg', omci_msg=msg)
results = yield omci.send(frame)
self.check_status_and_state(results, 'create-gem-port-network-ctp')
except Exception as e:
self.log.exception('gemport-create', e=e)
raise
try:
# TODO: magic numbers here
msg = GemInterworkingTpFrame(
self.entity_id, # same entity id as GEM port
gem_port_network_ctp_pointer=self.entity_id,
interworking_option=5, # IEEE 802.1
service_profile_pointer=ieee_mapper_service_profile_entity_id,
interworking_tp_pointer=0x0,
pptp_counter=1,
gal_profile_pointer=gal_enet_profile_entity_id
)
frame = msg.create()
self.log.debug('openomci-msg', omci_msg=msg)
results = yield omci.send(frame)
self.check_status_and_state(results, 'create-gem-interworking-tp')
except Exception as e:
self.log.exception('interworking-create', e=e)
raise
returnValue(results)
@inlineCallbacks
def remove_from_hardware(self, omci):
self.log.debug('remove-from-hardware', gem_id=self.gem_id)
try:
msg = GemInterworkingTpFrame(self.entity_id)
frame = msg.delete()
self.log.debug('openomci-msg', omci_msg=msg)
results = yield omci.send(frame)
self.check_status_and_state(results, 'delete-gem-port-network-ctp')
except Exception as e:
self.log.exception('interworking-delete', e=e)
raise
try:
msg = GemPortNetworkCtpFrame(self.entity_id)
frame = msg.delete()
self.log.debug('openomci-msg', omci_msg=msg)
results = yield omci.send(frame)
self.check_status_and_state(results, 'delete-gem-interworking-tp')
except Exception as e:
self.log.exception('gemport-delete', e=e)
raise
returnValue(results)
def check_status_and_state(self, results, operation=''):
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("OMCI Result", operation=operation, omci_msg=omci_msg,
status=status, error_mask=error_mask,
failed_mask=failed_mask, unsupported_mask=unsupported_mask)
if status == RC.Success:
return True
elif status == RC.InstanceExists:
return False