blob: f51bbd8643ee2db40ab14be6867da6ffb73d2b0a [file] [log] [blame]
#
# Copyright 2017 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.
#
"""
OMCI Message support
"""
from voltha.extensions.omci.omci import *
# abbreviations
OP = EntityOperations
class MEFrame(object):
"""Base class to help simplify Frame Creation"""
def __init__(self, entity_class, entity_id, data):
assert issubclass(entity_class, EntityClass), \
"'{}' must be a subclass of MEFrame".format(entity_class)
self.check_type(entity_id, int)
if not 0 <= entity_id <= 0xFFFF:
raise ValueError('entity_id should be 0..65535')
self._class = entity_class
self._entity_id = entity_id
self.data = data
# TODO: add a required attributes check list for various operations
# that each derive class can set as required. Then check these
# in the appropriate operation method in this class and assert
# if something is missing
def __str__(self):
return '{}: Entity_ID: {}, Data: {}'.\
format(type(self.entity_class), self._entity_id, self.data)
@staticmethod
def check_type(param, types):
if not isinstance(param, types):
raise TypeError("param '{}' should be a {}".format(param, types))
@property
def entity_class(self):
"""
The Entity Class for this ME
:return: (EntityClass) Entity class
"""
return self._class
@property
def entity_id(self):
"""
The Entity ID for this ME frame
:return: (int) Entity ID (0..0xFFFF)
"""
return self._entity_id
def create(self):
"""
Create a Create request frame for this ME
:return: (OmciFrame) OMCI Frame
"""
assert OP.Create in self.entity_class.mandatory_operations, \
"Set not allowed for '{}'".format(self.entity_class)
assert hasattr(self.entity_class, 'class_id'), 'class_id required for Create actions'
assert hasattr(self, 'entity_id'), 'entity_id required for Create actions'
assert hasattr(self, 'data'), 'data required for Create actions'
data = getattr(self, 'data')
MEFrame.check_type(data, dict)
assert len(data) > 0, 'No attributes supplied'
return OmciFrame(
transaction_id=None,
message_type=OmciCreate.message_id,
omci_message=OmciCreate(
entity_class=getattr(self.entity_class, 'class_id'),
entity_id=getattr(self, 'entity_id'),
data=data
))
def delete(self):
"""
Create a Delete request frame for this ME
:return: (OmciFrame) OMCI Frame
"""
assert OP.Delete in self.entity_class.mandatory_operations, \
"Delete not allowed for '{}'".format(self.entity_class)
return OmciFrame(
transaction_id=None,
message_type=OmciGet.message_id,
omci_message=OmciGet(
entity_class=getattr(self.entity_class, 'class_id'),
entity_id=getattr(self, 'entity_id')
))
def set(self):
"""
Create a Set request frame for this ME
:return: (OmciFrame) OMCI Frame
"""
assert OP.Set in self.entity_class.mandatory_operations, \
"Set not allowed for '{}'".format(self.entity_class)
assert hasattr(self, 'data'), 'data required for Set actions'
data = getattr(self, 'data')
MEFrame.check_type(data, dict)
assert len(data) > 0, 'No attributes supplied'
return OmciFrame(
transaction_id=None,
message_type=OmciSet.message_id,
omci_message=OmciSet(
entity_class=getattr(self.entity_class, 'class_id'),
entity_id=getattr(self, 'entity_id'),
attributes_mask=self.entity_class.mask_for(*data.keys()),
data=data
))
def get(self):
"""
Create a Get request frame for this ME
:return: (OmciFrame) OMCI Frame
"""
assert OP.Get in self.entity_class.mandatory_operations, \
"Get not allowed for '{}'".format(self.entity_class)
assert hasattr(self, 'data'), 'data required for Get actions'
data = getattr(self, 'data')
MEFrame.check_type(data, (list, set, dict))
assert len(data) > 0, 'No attributes supplied'
mask_set = data.keys() if isinstance(data, dict) else data
return OmciFrame(
transaction_id=None,
message_type=OmciGet.message_id,
omci_message=OmciGet(
entity_class=getattr(self.entity_class, 'class_id'),
entity_id=getattr(self, 'entity_id'),
attributes_mask=self.entity_class.mask_for(*mask_set)
))
@staticmethod
def _attr_to_data(attributes):
"""
Convert an object into the 'data' set or dictionary for get/set/create/delete
requests.
This method takes a 'string', 'list', or 'set' for get requests and
converts it to a 'set' of attributes.
For create/set requests a dictionary of attribute/value pairs is required
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, set, or dict can be provided. For create/set
operations, a dictionary should be provided. For delete
the attributes may be None since they are ignored.
:return: (set, dict) set for get/deletes, dict for create/set
"""
if isinstance(attributes, basestring):
# data = [str(attributes)]
data = set()
data.add(str(attributes))
elif isinstance(attributes, list):
assert all(isinstance(attr, basestring) for attr in attributes),\
'attribute list must be strings'
data = {str(attr) for attr in attributes}
assert len(data) == len(attributes), 'Attributes were not unique'
elif isinstance(attributes, set):
assert all(isinstance(attr, basestring) for attr in attributes),\
'attribute set must be strings'
data = {str(attr) for attr in attributes}
elif isinstance(attributes, (dict, type(None))):
data = attributes
else:
raise TypeError("Unsupported attributes type '{}'".format(type(attributes)))
return data
class CardholderFrame(MEFrame):
"""
This managed entity represents fixed equipment slot configuration
for the ONU
"""
def __init__(self, single, slot_number, attributes):
"""
:param single:(bool) True if the ONU is a single piece of integrated equipment,
False if the ONU contains pluggable equipment modules
:param slot_number: (int) slot number (0..254)
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified.
"""
# Validate
MEFrame.check_type(single, bool)
MEFrame.check_type(slot_number, int)
if not 0 <= slot_number <= 254:
raise ValueError('slot_number should be 0..254')
entity_id = 256 + slot_number if single else slot_number
super(CardholderFrame, self).__init__(Cardholder, entity_id,
MEFrame._attr_to_data(attributes))
class CircuitPackFrame(MEFrame):
"""
This managed entity models a real or virtual circuit pack that is equipped in
a real or virtual ONU slot.
"""
def __init__(self, entity_id, attributes):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. Its value is the same as that
of the cardholder managed entity containing this
circuit pack instance. (0..65535)
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified.
"""
super(CircuitPackFrame, self).__init__(CircuitPack, entity_id,
MEFrame._attr_to_data(attributes))
class IpHostConfigDataFrame(MEFrame):
"""
The IP host config data configures IPv4 based services offered on the ONU.
"""
def __init__(self, entity_id, attributes):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified.
"""
super(IpHostConfigDataFrame, self).__init__(IpHostConfigData,
entity_id,
MEFrame._attr_to_data(attributes))
class GemInterworkingTpFrame(MEFrame):
"""
An instance of this managed entity represents a point in the ONU where the
interworking of a bearer service (usually Ethernet) to the GEM layer takes
place.
"""
def __init__(self, entity_id,
gem_port_network_ctp_pointer=None,
interworking_option=None,
service_profile_pointer=None,
interworking_tp_pointer=None,
gal_profile_pointer=None,
attributes=None):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
:param gem_port_network_ctp_pointer: (int) This attribute points to an instance of
the GEM port network CTP. (0..65535)
:param interworking_option: (int) This attribute identifies the type
of non-GEM function that is being interworked.
The options are:
0 Circuit-emulated TDM
1 MAC bridged LAN
2 Reserved
3 Reserved
4 Video return path
5 IEEE 802.1p mapper
6 Downstream broadcast
7 MPLS PW TDM service
:param service_profile_pointer: (int) This attribute points to an instance of
a service profile.
CES service profile if interworking option = 0
MAC bridge service profile if interworking option = 1
Video return path service profile if interworking option = 4
IEEE 802.1p mapper service profile if interworking option = 5
Null pointer if interworking option = 6
CES service profile if interworking option = 7
:param interworking_tp_pointer: (int) This attribute is used for the circuit
emulation service and IEEE 802.1p mapper
service without a MAC bridge.
:param gal_profile_pointer: (int) This attribute points to an instance of
a service profile.
:param attributes: (basestring, list, set, dict) additional ME attributes.
not specifically specified as a parameter. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified..
"""
# Validate
self.check_type(gem_port_network_ctp_pointer, [int, type(None)])
self.check_type(interworking_option, [int, type(None)])
self.check_type(service_profile_pointer, [int, type(None)])
self.check_type(interworking_tp_pointer, [int, type(None)])
self.check_type(gal_profile_pointer, [int, type(None)])
if gem_port_network_ctp_pointer is not None and not 0 <= gem_port_network_ctp_pointer <= 0xFFFE: # TODO: Verify max
raise ValueError('gem_port_network_ctp_pointer should be 0..0xFFFE')
if interworking_option is not None and not 0 <= interworking_option <= 7:
raise ValueError('interworking_option should be 0..7')
if service_profile_pointer is not None and not 0 <= service_profile_pointer <= 0xFFFE: # TODO: Verify max
raise ValueError('service_profile_pointer should be 0..0xFFFE')
if interworking_tp_pointer is not None and not 0 <= interworking_tp_pointer <= 0xFFFE: # TODO: Verify max
raise ValueError('interworking_tp_pointer should be 0..0xFFFE')
if gal_profile_pointer is not None and not 0 <= gal_profile_pointer <= 0xFFFE: # TODO: Verify max
raise ValueError('gal_profile_pointer should be 0..0xFFFE')
data = MEFrame._attr_to_data(attributes)
if gem_port_network_ctp_pointer is not None or \
interworking_option is not None or \
service_profile_pointer is not None or \
interworking_tp_pointer is not None or \
gal_profile_pointer is not None:
data = data or dict()
if gem_port_network_ctp_pointer is not None:
data[gem_port_network_ctp_pointer] = gem_port_network_ctp_pointer
if interworking_option is not None:
data[interworking_option] = interworking_option
if service_profile_pointer is not None:
data[service_profile_pointer] = service_profile_pointer
if interworking_tp_pointer is not None:
data[interworking_tp_pointer] = interworking_tp_pointer
if gal_profile_pointer is not None:
data[gal_profile_pointer] = gal_profile_pointer
super(GemInterworkingTpFrame, self).__init__(GemInterworkingTp,
entity_id,
data)
class GemPortNetworkCtpFrame(MEFrame):
"""
This managed entity represents the termination of a GEM port on an ONU.
"""
def __init__(self, entity_id, port_id=None, tcont_id=None,
direction=None, upstream_tm=None, attributes=None):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
:param port_id: (int) This attribute is the port-ID of the GEM port associated
with this CTP
:param tcont_id: (int) This attribute points to a T-CONT instance
:param direction: (string) Data direction. Valid values are:
'upstream' - UNI-to-ANI
'downstream' - ANI-to-UNI
'bi-directional' - guess :-)
:param upstream_tm: (int) If the traffic management option attribute in
the ONU-G ME is 0 (priority controlled) or 2
(priority and rate controlled), this pointer
specifies the priority queue ME serving this GEM
port network CTP. If the traffic management
option attribute is 1 (rate controlled), this
attribute redundantly points to the T-CONT serving
this GEM port network CTP.
:param attributes: (basestring, list, set, dict) additional ME attributes.
not specifically specified as a parameter. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified.
"""
_directions = {"upstream": 1, "downstream": 2, "bi-directional": 3}
# Validate
self.check_type(port_id, [int, type(None)])
self.check_type(tcont_id, [int, type(None)])
self.check_type(direction, [basestring, type(None)])
self.check_type(upstream_tm, [int, type(None)])
if port_id is not None and not 0 <= port_id <= 0xFFFE: # TODO: Verify max
raise ValueError('port_id should be 0..0xFFFE')
if tcont_id is not None and not 0 <= tcont_id <= 0xFFFE: # TODO: Verify max
raise ValueError('tcont_id should be 0..0xFFFE')
if direction is not None and str(direction).lower() not in _directions:
raise ValueError('direction should one of {}'.format(_directions.keys()))
if upstream_tm is not None and not 0 <= upstream_tm <= 0xFFFE: # TODO: Verify max
raise ValueError('upstream_tm should be 0..0xFFFE')
data = MEFrame._attr_to_data(attributes)
if port_id is not None or tcont_id is not None or\
direction is not None or upstream_tm is not None:
data = data or dict()
if port_id is not None:
data[port_id] = port_id
if tcont_id is not None:
data[tcont_id] = tcont_id
if direction is not None:
data[direction] = direction
if upstream_tm is not None:
data[upstream_tm] = upstream_tm
super(GemPortNetworkCtpFrame, self).__init__(GemPortNetworkCtp,
entity_id,
data)
class Ieee8021pMapperServiceProfileFrame(MEFrame):
"""
This managed entity associates the priorities of IEEE 802.1p [IEEE
802.1D] priority tagged frames with specific connections.
"""
def __init__(self, entity_id, tp_pointer=None, interwork_tp_pointers=None):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
:param tp_pointer: (int) This attribute points to an instance of the
associated termination point. (0..65535)
:param interwork_tp_pointers: (list) List of 1 to 8 interworking termination
point IDs. The first entry is assigned
got p-bit priority 0. If less than 8 IDs
are provided, the last ID is used for
the remaining items.
"""
if tp_pointer is None and interwork_tp_pointers is None:
data = dict(
tp_pointer=OmciNullPointer,
interwork_tp_pointer_for_p_bit_priority_0=OmciNullPointer,
interwork_tp_pointer_for_p_bit_priority_1=OmciNullPointer,
interwork_tp_pointer_for_p_bit_priority_2=OmciNullPointer,
interwork_tp_pointer_for_p_bit_priority_3=OmciNullPointer,
interwork_tp_pointer_for_p_bit_priority_4=OmciNullPointer,
interwork_tp_pointer_for_p_bit_priority_5=OmciNullPointer,
interwork_tp_pointer_for_p_bit_priority_6=OmciNullPointer,
interwork_tp_pointer_for_p_bit_priority_7=OmciNullPointer
)
else:
self.check_type(tp_pointer, [list, type(None)])
self.check_type(interwork_tp_pointers, [list, type(None)])
data = dict()
if tp_pointer is not None:
data[tp_pointer] = tp_pointer
if interwork_tp_pointers is not None:
assert all(isinstance(tp, int) and 0 <= tp <= 0xFFFF
for tp in interwork_tp_pointers),\
'Interworking TP IDs must be 0..0xFFFF'
assert 1 <= len(interwork_tp_pointers) <= 8, \
'Invalid number of Interworking TP IDs. Must be 1..8'
data = dict()
for pbit in range(0, len(interwork_tp_pointers)):
data['interwork_tp_pointer_for_p_bit_priority_'.format(pbit)] = \
interwork_tp_pointers[pbit]
for pbit in range(len(interwork_tp_pointers), 7):
data['interwork_tp_pointer_for_p_bit_priority_'.format(pbit)] = \
interwork_tp_pointers[len(interwork_tp_pointers) - 1]
super(Ieee8021pMapperServiceProfileFrame, self).__init__(Ieee8021pMapperServiceProfile,
entity_id,
data)
class MacBridgePortConfigurationDataFrame(MEFrame):
"""
This managed entity represents the ONU as equipment.
"""
def __init__(self, entity_id, bridge_id_pointer=None, port_num=None,
tp_type=None, tp_pointer=None, attributes=None):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
:param bridge_id_pointer: (int) This attribute points to an instance of the
MAC bridge service profile. (0..65535)
:param port_num: (int) This attribute is the bridge port number. (0..255)
:param tp_type: (int) This attribute identifies the type of termination point
associated with this MAC bridge port. Valid values are:
1 Physical path termination point Ethernet UNI
2 Interworking VCC termination point
3 IEEE 802.1p mapper service profile
4 IP host config data or IPv6 host config data
5 GEM interworking termination point
6 Multicast GEM interworking termination point
7 Physical path termination point xDSL UNI part 1
8 Physical path termination point VDSL UNI
9 Ethernet flow termination point
10 Reserved
11 Virtual Ethernet interface point
12 Physical path termination point MoCA UNI
:param tp_pointer: (int) This attribute points to the termination point
associated with this MAC bridge por. (0..65535)
:param attributes: (basestring, list, set, dict) additional ME attributes.
not specifically specified as a parameter. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified.
"""
# Validate
self.check_type(bridge_id_pointer, [int, type(None)])
self.check_type(port_num, [int, type(None)])
self.check_type(tp_type, [int, type(None)])
self.check_type(tp_pointer, [int, type(None)])
if bridge_id_pointer is not None and not 0 <= bridge_id_pointer <= 0xFFFE: # TODO: Verify max
raise ValueError('bridge_id_pointer should be 0..0xFFFE')
if port_num is not None and not 0 <= port_num <= 255:
raise ValueError('port_num should be 0..255') # TODO: Verify min,max
if tp_type is not None and not 1 <= tp_type <= 12:
raise ValueError('service_profile_pointer should be 1..12')
if tp_pointer is not None and not 0 <= tp_pointer <= 0xFFFE: # TODO: Verify max
raise ValueError('interworking_tp_pointer should be 0..0xFFFE')
data = MEFrame._attr_to_data(attributes)
if bridge_id_pointer is not None or \
port_num is not None or \
tp_type is not None or \
tp_pointer is not None:
data = data or dict()
if bridge_id_pointer is not None:
data[bridge_id_pointer] = bridge_id_pointer
if port_num is not None:
data[port_num] = port_num
if tp_type is not None:
data[tp_type] = tp_type
if tp_pointer is not None:
data[tp_pointer] = tp_pointer
super(MacBridgePortConfigurationDataFrame, self).\
__init__(MacBridgePortConfigurationData, entity_id, data)
class OntGFrame(MEFrame):
"""
This managed entity represents the ONU as equipment.
"""
def __init__(self, attributes):
"""
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified.
"""
super(OntGFrame, self).__init__(OntG, 0,
MEFrame._attr_to_data(attributes))
class Ont2GFrame(MEFrame):
"""
This managed entity contains additional attributes associated with a PON ONU.
"""
def __init__(self, attributes):
"""
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified.
"""
# Only one managed entity instance (Entity ID=0)
super(Ont2GFrame, self).__init__(Ont2G, 0,
MEFrame._attr_to_data(attributes))
class PptpEthernetUniFrame(MEFrame):
"""
This managed entity represents the point at an Ethernet UNI where the physical path
terminates and Ethernet physical level functions are performed.
"""
def __init__(self, entity_id, attributes):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
:param attributes: (basestring, list, set, dict) attributes. For gets
a string, list, or set can be provided. For create/set
operations, a dictionary should be provided, for
deletes None may be specified.
"""
super(PptpEthernetUniFrame, self).__init__(PptpEthernetUni, entity_id,
MEFrame._attr_to_data(attributes))
class SoftwareImageFrame(MEFrame):
"""
This managed entity models an executable software image stored in the ONU.
"""
def __init__(self, entity_id):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
"""
super(SoftwareImageFrame, self).__init__(SoftwareImage, entity_id, None)
class TcontFrame(MEFrame):
"""
An instance of the traffic container managed entity T-CONT represents a
logical connection group associated with a G-PON PLOAM layer alloc-ID.
"""
def __init__(self, entity_id, alloc_id=None, policy=None):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
:param alloc_id: (int) This attribute links the T-CONT with the alloc-ID
assigned by the OLT in the assign_alloc-ID PLOAM
message (0..0xFFF)
:param policy: (int) This attribute indicates the T-CONT's traffic scheduling
policy. Valid values:
0 - Null
1 - Strict priority
2 - WRR - Weighted round robin
"""
# Validate
self.check_type(alloc_id, [int, type(None)])
self.check_type(policy, [int, type(None)])
if alloc_id is not None and not 0 <= alloc_id <= 0xFFF:
raise ValueError('alloc_id should be 0..0xFFF')
if policy is not None and not 0 <= policy <= 2:
raise ValueError('policy should be 0..2')
if alloc_id is None and policy is None:
data = None
else:
data = dict()
if alloc_id is not None:
data[alloc_id] = alloc_id
if policy is not None:
data[policy] = policy
super(TcontFrame, self).__init__(Tcont, entity_id, data)
class VlanTaggingFilterDataFrame(MEFrame):
"""
An instance of this managed entity represents a point in the ONU where the
interworking of a bearer service (usually Ethernet) to the GEM layer takes
place.
"""
def __init__(self, entity_id, vlan_tcis=None, forward_operation=None):
"""
:param entity_id: (int) This attribute uniquely identifies each instance of
this managed entity. (0..65535)
:param vlan_tcis: (list) This attribute is a list of provisioned TCI values
for the bridge port. (0..0xFFFF)
:param forward_operation: (int) What to do. See ITU spec for more information
"""
# Validate
self.check_type(vlan_tcis, [list, type(None)])
self.check_type(forward_operation, [int, type(None)])
if forward_operation is not None and not 0 <= forward_operation <= 0x21:
raise ValueError('forward_operation should be 0..0x21')
if vlan_tcis is None and forward_operation is None:
data = None
else:
data = dict()
if vlan_tcis is not None:
assert all(isinstance(tci, int) and 0 <= tci <= 0xFFFF
for tci in vlan_tcis), "VLAN TCI's are 0..0xFFFF"
for index in range(0, len(vlan_tcis)):
data['vlan_filter_{}'.format(index)] = vlan_tcis[index]
data['number_of_entries'] = len(vlan_tcis)
if forward_operation is not None:
data[forward_operation] = forward_operation
super(VlanTaggingFilterDataFrame, self).__init__(VlanTaggingFilterData,
entity_id,
data)
# TODO: Wednesday - Start with send_create_extended_vlan_tagging_operation_configuration_data