blob: 5c8c7ebf6328ac91049a8c8a7a59b00cb458cb9f [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.
import structlog
from twisted.internet.defer import inlineCallbacks, returnValue
from voltha_protos.common_pb2 import AdminState, OperStatus
from voltha_protos.device_pb2 import Port
from pyvoltha.adapters.extensions.omci.tasks.task import Task
BRDCM_DEFAULT_VLAN = 4091
TASK_PRIORITY = Task.DEFAULT_PRIORITY + 10
DEFAULT_TPID = 0x8100
DEFAULT_GEM_PAYLOAD = 48
class PonPort(object):
"""Wraps northbound-port/ANI support for ONU"""
# TODO: possibly get from olt
MIN_GEM_ENTITY_ID = 0x408
MAX_GEM_ENTITY_ID = 0x4FF # TODO: This limits is internal to specific ONU. It should be more "discoverable"?
def __init__(self, handler, port_no):
self.log = structlog.get_logger(device_id=handler.device_id, port_no=port_no)
self.log.debug('function-entry')
self._enabled = False
self._valid = True
self._handler = handler
self._deferred = None
self._port = None
self._port_number = port_no
self._peers = []
self._next_entity_id = PonPort.MIN_GEM_ENTITY_ID
self._admin_state = AdminState.ENABLED
self._oper_status = OperStatus.ACTIVE
self._gem_ports = {} # gem-id -> GemPort
self._tconts = {} # alloc-id -> TCont
self.ieee_mapper_service_profile_entity_id = 0x8001
self.mac_bridge_port_ani_entity_id = 0x2102 # TODO: can we just use the entity id from the anis list?
def __str__(self):
return "PonPort - port_number: {}, next_entity_id: {}, num_gem_ports: {}, num_tconts: {}".format(
self._port_number, self._next_entity_id, len(self._gem_ports), len(self._tconts))
def __repr__(self):
return str(self)
@staticmethod
def create(handler, port_no):
log = structlog.get_logger(device_id=handler.device_id, port_no=port_no)
log.debug('function-entry')
port = PonPort(handler, port_no)
return port
def _start(self):
self.log.debug('function-entry')
self._cancel_deferred()
self._admin_state = AdminState.ENABLED
self._oper_status = OperStatus.ACTIVE
self._update_adapter_agent()
def _stop(self):
self.log.debug('function-entry')
self._cancel_deferred()
self._admin_state = AdminState.DISABLED
self._oper_status = OperStatus.UNKNOWN
self._update_adapter_agent()
# TODO: stop h/w sync
def _cancel_deferred(self):
self.log.debug('function-entry')
d1, self._deferred = self._deferred, None
for d in [d1]:
try:
if d is not None and not d.called:
d.cancel()
except:
pass
def delete(self):
self.log.debug('function-entry')
self.enabled = False
self._valid = False
self._handler = None
@property
def enabled(self):
self.log.debug('function-entry')
return self._enabled
@enabled.setter
def enabled(self, value):
self.log.debug('function-entry')
if self._enabled != value:
self._enabled = value
if value:
self._start()
else:
self._stop()
@property
def port_number(self):
self.log.debug('function-entry')
return self._port_number
@property
def next_gem_entity_id(self):
self.log.debug('function-entry')
entity_id = self._next_entity_id
self._next_entity_id = self._next_entity_id + 1
if self._next_entity_id > PonPort.MAX_GEM_ENTITY_ID:
self._next_entity_id = PonPort.MIN_GEM_ENTITY_ID
return entity_id
@property
def tconts(self):
self.log.debug('function-entry')
return self._tconts
@property
def gem_ports(self):
self.log.debug('function-entry')
return self._gem_ports
def get_port(self):
"""
Get the VOLTHA PORT object for this port
:return: VOLTHA Port object
"""
self.log.debug('function-entry')
self._port = Port(port_no=self.port_number,
label='PON port',
type=Port.PON_ONU,
admin_state=self._admin_state,
oper_status=self._oper_status,
peers=self._peers)
return self._port
def add_peer(self, parent_device_id, parent_port_no):
self.log.debug('add-peer-port', parent_device_id=parent_device_id, parent_port_no=parent_port_no)
new_peer = Port.PeerPort(device_id=parent_device_id, port_no=parent_port_no)
self._peers.extend([new_peer])
@inlineCallbacks
def _update_adapter_agent(self):
"""
Update the port status and state in the core
"""
self.log.debug('function-entry')
self.log.debug('update-adapter-agent', admin_state=self._admin_state,
oper_status=self._oper_status)
if self._port is not None:
self._port.admin_state = self._admin_state
self._port.oper_status = self._oper_status
# adapter_agent add_port also does an update of port status
try:
yield self._handler.core_proxy.port_state_update(self._handler.device_id, self._port.type,
self._port.port_no,self._port.oper_status)
except Exception as e:
self.log.exception('update-port', e=e)
def add_tcont(self, tcont, reflow=False):
"""
Creates/ a T-CONT with the given alloc-id
:param tcont: (TCont) Object that maintains the TCONT properties
:param reflow: (boolean) If true, force add (used during h/w resync)
:return: (deferred)
"""
self.log.debug('function-entry', tcont=tcont.alloc_id)
if not self._valid:
return # Deleting
if not reflow and tcont.alloc_id in self._tconts:
return # already created
self.log.info('add-tcont', tcont=tcont.alloc_id, reflow=reflow)
self._tconts[tcont.alloc_id] = tcont
def update_tcont_td(self, alloc_id, new_td):
self.log.debug('function-entry')
tcont = self._tconts.get(alloc_id)
if tcont is None:
return # not-found
tcont.traffic_descriptor = new_td
# TODO: Not yet implemented
#TODO: How does this affect ONU tcont settings?
#try:
# results = yield tcont.add_to_hardware(self._handler.omci)
#except Exception as e:
# self.log.exception('tcont', tcont=tcont, e=e)
# # May occur with xPON provisioning, use hw-resync to recover
# results = 'resync needed'
# returnValue(results)
@inlineCallbacks
def remove_tcont(self, alloc_id):
self.log.debug('function-entry')
tcont = self._tconts.get(alloc_id)
if tcont is None:
returnValue('nop')
try:
del self._tconts[alloc_id]
results = yield tcont.remove_from_hardware(self._handler.openomci.omci_cc)
returnValue(results)
except Exception as e:
self.log.exception('delete', e=e)
raise
def gem_port(self, gem_id, direction):
self.log.debug('function-entry')
return self._gem_ports.get((gem_id, direction))
@property
def gem_ids(self):
"""Get all GEM Port IDs used by this ONU"""
self.log.debug('function-entry')
return sorted([gem_id_and_direction[0] for gem_id_and_direction, gem in self._gem_ports.items()])
def add_gem_port(self, gem_port, reflow=False):
"""
Add a GEM Port to this ONU
:param gem_port: (GemPort) GEM Port to add
:param reflow: (boolean) If true, force add (used during h/w resync)
:return: (deferred)
"""
self.log.debug('function-entry', gem_port=gem_port.gem_id)
if not self._valid:
return # Deleting
if not reflow and (gem_port.gem_id, gem_port.direction) in self._gem_ports:
return # nop
# if this is actually a new gem port then issue the next entity_id
gem_port.entity_id = self.next_gem_entity_id
self.log.info('add-gem-port', gem_port=gem_port, reflow=reflow)
self._gem_ports[(gem_port.gem_id, gem_port.direction)] = gem_port
@inlineCallbacks
def remove_gem_id(self, gem_id, direction):
"""
Remove a GEM Port from this ONU
:param gem_id: (GemPort) GEM Port to remove
:param direction: Direction of the gem port
:return: deferred
"""
self.log.debug('function-entry', gem_id=gem_id)
gem_port = self._gem_ports.get((gem_id, direction))
if gem_port is None:
returnValue('nop')
try:
del self._gem_ports[(gem_id, direction)]
results = yield gem_port.remove_from_hardware(self._handler.openomci.omci_cc)
returnValue(results)
except Exception as ex:
self.log.exception('gem-port-delete', e=ex)
raise