| # |
| # 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 pyvoltha.adapters.common.pon_resource_manager.resource_manager import PONResourceManager |
| from pyvoltha.common.utils.registry import registry |
| from pyvoltha.common.config.config_backend import ConsulStore |
| from pyvoltha.common.config.config_backend import EtcdStore |
| from openolt_flow_mgr import * |
| |
| from voltha_protos import openolt_pb2 |
| from openolt_platform import OpenOltPlatform |
| |
| |
| class OpenOltResourceMgr(object): |
| BASE_PATH_KV_STORE = "service/voltha/openolt/{}" # service/voltha/openolt/<device_id> |
| |
| def __init__(self, device_id, host_and_port, extra_args, device_info): |
| self.log = structlog.get_logger(id=device_id, |
| ip=host_and_port) |
| self.device_id = device_id |
| self.host_and_port = host_and_port |
| self.extra_args = extra_args |
| self.device_info = device_info |
| self.args = registry('main').get_args() |
| |
| # KV store's IP Address and PORT |
| if self.args.backend == 'etcd': |
| host, port = self.args.etcd.split(':', 1) |
| self.kv_store = EtcdStore(host, port, |
| OpenOltResourceMgr.BASE_PATH_KV_STORE.format(device_id)) |
| elif self.args.backend == 'consul': |
| host, port = self.args.consul.split(':', 1) |
| self.kv_store = ConsulStore(host, port, |
| OpenOltResourceMgr.BASE_PATH_KV_STORE.format(device_id)) |
| else: |
| self.log.error('Invalid-backend') |
| raise Exception("Invalid-backend-for-kv-store") |
| |
| ranges = dict() |
| resource_mgrs_by_tech = dict() |
| self.resource_mgrs = dict() |
| |
| # If a legacy driver returns protobuf without any ranges,s synthesize one from |
| # the legacy global per-device informaiton. This, in theory, is temporary until |
| # the legacy drivers are upgrade to support pool ranges. |
| if len(self.device_info.ranges) == 0: |
| arange = self.device_info.ranges.add() |
| arange.technology = self.device_info.technology |
| arange.intf_ids.extend(range(0, device_info.pon_ports)) |
| |
| pool = arange.pools.add() |
| pool.type = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.ONU_ID |
| pool.start = self.device_info.onu_id_start |
| pool.end = self.device_info.onu_id_end |
| pool.sharing = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.DEDICATED_PER_INTF |
| |
| pool = arange.pools.add() |
| pool.type = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.ALLOC_ID |
| pool.start = self.device_info.alloc_id_start |
| pool.end = self.device_info.alloc_id_end |
| pool.sharing = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH |
| |
| pool = arange.pools.add() |
| pool.type = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.GEMPORT_ID |
| pool.start = self.device_info.gemport_id_start |
| pool.end = self.device_info.gemport_id_end |
| pool.sharing = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH |
| |
| pool = arange.pools.add() |
| pool.type = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.FLOW_ID |
| pool.start = self.device_info.flow_id_start |
| pool.end = self.device_info.flow_id_end |
| pool.sharing = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH |
| |
| # Create a separate Resource Manager instance for each range. This assumes that |
| # each technology is represented by only a single range |
| global_resource_mgr = None |
| for arange in self.device_info.ranges: |
| technology = arange.technology |
| self.log.info("device-info", technology=technology) |
| ranges[technology] = arange |
| extra_args = self.extra_args + ' ' + PONResourceManager.OLT_MODEL_ARG + ' {}'.format(self.device_info.model) |
| resource_mgr = PONResourceManager(technology, |
| extra_args, self.device_id, self.args.backend, host, port) |
| resource_mgrs_by_tech[technology] = resource_mgr |
| if global_resource_mgr is None: |
| global_resource_mgr = resource_mgr |
| for intf_id in arange.intf_ids: |
| self.resource_mgrs[intf_id] = resource_mgrs_by_tech[technology] |
| self.initialize_device_resource_range_and_pool(resource_mgr, global_resource_mgr, arange) |
| |
| # After we have initialized resource ranges, initialize the |
| # resource pools accordingly. |
| for technology, resource_mgr in resource_mgrs_by_tech.iteritems(): |
| resource_mgr.init_device_resource_pool() |
| |
| def __del__(self): |
| self.log.info("clearing-device-resource-pool") |
| for key, resource_mgr in self.resource_mgrs.iteritems(): |
| resource_mgr.clear_device_resource_pool() |
| |
| def assert_pon_id_limit(self, pon_intf_id): |
| assert pon_intf_id in self.resource_mgrs |
| |
| def assert_onu_id_limit(self, pon_intf_id, onu_id): |
| self.assert_pon_id_limit(pon_intf_id) |
| self.resource_mgrs[pon_intf_id].assert_resource_limits(onu_id, PONResourceManager.ONU_ID) |
| |
| @property |
| def max_uni_id_per_onu(self): |
| return 0 #OpenOltPlatform.MAX_UNIS_PER_ONU-1, zero-based indexing Uncomment or override to make default multi-uni |
| |
| def assert_uni_id_limit(self, pon_intf_id, onu_id, uni_id): |
| self.assert_onu_id_limit(pon_intf_id, onu_id) |
| self.resource_mgrs[pon_intf_id].assert_resource_limits(uni_id, PONResourceManager.UNI_ID) |
| |
| def get_onu_id(self, pon_intf_id): |
| onu_id = self.resource_mgrs[pon_intf_id].get_resource_id( |
| pon_intf_id, PONResourceManager.ONU_ID, 1) |
| |
| if onu_id is not None: |
| pon_intf_onu_id = (pon_intf_id, onu_id) |
| self.resource_mgrs[pon_intf_id].init_resource_map( |
| pon_intf_onu_id) |
| |
| return onu_id |
| |
| def get_flow_id(self, pon_intf_id, onu_id, uni_id, flow_store_cookie, |
| flow_category=None): |
| pon_intf_onu_id = (pon_intf_id, onu_id, uni_id) |
| try: |
| flow_ids = self.resource_mgrs[pon_intf_id]. \ |
| get_current_flow_ids_for_onu(pon_intf_onu_id) |
| if flow_ids is not None: |
| for flow_id in flow_ids: |
| flows = self.get_flow_id_info(pon_intf_id, onu_id, uni_id, flow_id) |
| assert (isinstance(flows, list)) |
| for flow in flows: |
| |
| if flow_category is not None and \ |
| 'flow_category' in flow and \ |
| flow['flow_category'] == flow_category: |
| return flow_id |
| if flow['flow_store_cookie'] == flow_store_cookie: |
| return flow_id |
| except Exception as e: |
| self.log.error("error-retrieving-flow-info", e=e) |
| |
| flow_id = self.resource_mgrs[pon_intf_id].get_resource_id( |
| pon_intf_onu_id[0], PONResourceManager.FLOW_ID) |
| if flow_id is not None: |
| self.resource_mgrs[pon_intf_id].update_flow_id_for_onu( |
| pon_intf_onu_id, flow_id |
| ) |
| |
| return flow_id |
| |
| def get_flow_id_info(self, pon_intf_id, onu_id, uni_id, flow_id): |
| pon_intf_onu_id = (pon_intf_id, onu_id, uni_id) |
| return self.resource_mgrs[pon_intf_id].get_flow_id_info(pon_intf_onu_id, flow_id) |
| |
| def get_current_flow_ids_for_uni(self, pon_intf_id, onu_id, uni_id): |
| pon_intf_onu_id = (pon_intf_id, onu_id, uni_id) |
| return self.resource_mgrs[pon_intf_id].get_current_flow_ids_for_onu(pon_intf_onu_id) |
| |
| def update_flow_id_info_for_uni(self, pon_intf_id, onu_id, uni_id, flow_id, flow_data): |
| pon_intf_onu_id = (pon_intf_id, onu_id, uni_id) |
| return self.resource_mgrs[pon_intf_id].update_flow_id_info_for_onu( |
| pon_intf_onu_id, flow_id, flow_data) |
| |
| def get_alloc_id(self, pon_intf_onu_id): |
| # Derive the pon_intf from the pon_intf_onu_id tuple |
| pon_intf = pon_intf_onu_id[0] |
| alloc_id_list = self.resource_mgrs[pon_intf].get_current_alloc_ids_for_onu( |
| pon_intf_onu_id) |
| |
| if alloc_id_list and len(alloc_id_list) > 0: |
| # Since we support only one alloc_id for the ONU at the moment, |
| # return the first alloc_id in the list, if available, for that |
| # ONU. |
| return alloc_id_list[0] |
| |
| alloc_id = self.resource_mgrs[pon_intf].get_resource_id( |
| pon_intf_id=pon_intf, |
| resource_type=PONResourceManager.ALLOC_ID, |
| num_of_id=1 |
| ) |
| if alloc_id is None: |
| self.log.error("no-alloc-id-available") |
| return None |
| |
| # update the resource map on KV store with the list of alloc_id |
| # allocated for the pon_intf_onu_id tuple |
| self.resource_mgrs[pon_intf].update_alloc_ids_for_onu(pon_intf_onu_id, |
| list(alloc_id)) |
| |
| return alloc_id |
| |
| def get_current_gemport_ids_for_onu(self, pon_intf_onu_id): |
| pon_intf_id = pon_intf_onu_id[0] |
| return self.resource_mgrs[pon_intf_id].get_current_gemport_ids_for_onu(pon_intf_onu_id) |
| |
| def get_current_alloc_ids_for_onu(self, pon_intf_onu_id): |
| pon_intf_id = pon_intf_onu_id[0] |
| alloc_ids = self.resource_mgrs[pon_intf_id].get_current_alloc_ids_for_onu(pon_intf_onu_id) |
| if alloc_ids is None: |
| return None |
| # We support only one tcont at the moment |
| return alloc_ids[0] |
| |
| def update_gemports_ponport_to_onu_map_on_kv_store(self, gemport_list, pon_port, onu_id, uni_id): |
| for gemport in gemport_list: |
| pon_intf_gemport = (pon_port, gemport) |
| # This information is used when packet_indication is received and |
| # we need to derive the ONU Id for which the packet arrived based |
| # on the pon_intf and gemport available in the packet_indication |
| self.kv_store[str(pon_intf_gemport)] = ' '.join(map(str, (onu_id, uni_id))) |
| |
| def get_onu_uni_from_ponport_gemport(self, pon_port, gemport): |
| pon_intf_gemport = (pon_port, gemport) |
| return tuple(map(int, self.kv_store[str(pon_intf_gemport)].split(' '))) |
| |
| def get_gemport_id(self, pon_intf_onu_id, num_of_id=1): |
| # Derive the pon_intf and onu_id from the pon_intf_onu_id tuple |
| pon_intf = pon_intf_onu_id[0] |
| onu_id = pon_intf_onu_id[1] |
| uni_id = pon_intf_onu_id[2] |
| assert False, 'unused function' |
| |
| gemport_id_list = self.resource_mgrs[pon_intf].get_current_gemport_ids_for_onu( |
| pon_intf_onu_id) |
| if gemport_id_list and len(gemport_id_list) > 0: |
| return gemport_id_list |
| |
| gemport_id_list = self.resource_mgrs[pon_intf].get_resource_id( |
| pon_intf_id=pon_intf, |
| resource_type=PONResourceManager.GEMPORT_ID, |
| num_of_id=num_of_id |
| ) |
| |
| if gemport_id_list and len(gemport_id_list) == 0: |
| self.log.error("no-gemport-id-available") |
| return None |
| |
| # update the resource map on KV store with the list of gemport_id |
| # allocated for the pon_intf_onu_id tuple |
| self.resource_mgrs[pon_intf].update_gemport_ids_for_onu(pon_intf_onu_id, |
| gemport_id_list) |
| |
| self.update_gemports_ponport_to_onu_map_on_kv_store(gemport_id_list, |
| pon_intf, onu_id, uni_id) |
| return gemport_id_list |
| |
| def free_onu_id(self, pon_intf_id, onu_id): |
| _ = self.resource_mgrs[pon_intf_id].free_resource_id( |
| pon_intf_id, PONResourceManager.ONU_ID, onu_id) |
| |
| pon_intf_onu_id = (pon_intf_id, onu_id) |
| self.resource_mgrs[pon_intf_id].remove_resource_map( |
| pon_intf_onu_id) |
| |
| def free_flow_id_for_uni(self, pon_intf_id, onu_id, uni_id, flow_id): |
| self.resource_mgrs[pon_intf_id].free_resource_id( |
| pon_intf_id, PONResourceManager.FLOW_ID, flow_id) |
| pon_intf_onu_id = (pon_intf_id, onu_id, uni_id) |
| self.resource_mgrs[pon_intf_id].update_flow_id_for_onu(pon_intf_onu_id, |
| flow_id, False) |
| self.resource_mgrs[pon_intf_id].remove_flow_id_info(pon_intf_onu_id, |
| flow_id) |
| |
| def free_pon_resources_for_onu(self, pon_intf_id_onu_id): |
| |
| pon_intf_id = pon_intf_id_onu_id[0] |
| onu_id = pon_intf_id_onu_id[1] |
| alloc_ids = \ |
| self.resource_mgrs[pon_intf_id].get_current_alloc_ids_for_onu(pon_intf_id_onu_id) |
| self.resource_mgrs[pon_intf_id].free_resource_id(pon_intf_id, |
| PONResourceManager.ALLOC_ID, |
| alloc_ids) |
| |
| gemport_ids = \ |
| self.resource_mgrs[pon_intf_id].get_current_gemport_ids_for_onu(pon_intf_id_onu_id) |
| self.resource_mgrs[pon_intf_id].free_resource_id(pon_intf_id, |
| PONResourceManager.GEMPORT_ID, |
| gemport_ids) |
| |
| flow_ids = \ |
| self.resource_mgrs[pon_intf_id].get_current_flow_ids_for_onu(pon_intf_id_onu_id) |
| self.resource_mgrs[pon_intf_id].free_resource_id(pon_intf_id, |
| PONResourceManager.FLOW_ID, |
| flow_ids) |
| |
| self.resource_mgrs[pon_intf_id].free_resource_id(pon_intf_id, |
| PONResourceManager.ONU_ID, |
| onu_id) |
| |
| # Clear resource map associated with (pon_intf_id, gemport_id) tuple. |
| self.resource_mgrs[pon_intf_id].remove_resource_map(pon_intf_id_onu_id) |
| |
| # Clear the ONU Id associated with the (pon_intf_id, gemport_id) tuple. |
| for gemport_id in gemport_ids: |
| del self.kv_store[str((pon_intf_id, gemport_id))] |
| |
| def initialize_device_resource_range_and_pool(self, resource_mgr, global_resource_mgr, arange): |
| self.log.info("resource-range-pool-init", technology=resource_mgr.technology) |
| |
| # first load from KV profiles |
| status = resource_mgr.init_resource_ranges_from_kv_store() |
| if not status: |
| self.log.info("failed-to-load-resource-range-from-kv-store", technology=resource_mgr.technology) |
| |
| # Then apply device specific information. If KV doesn't exist |
| # or is broader than the device, the device's informationw ill |
| # dictate the range limits |
| self.log.info("using-device-info-to-init-pon-resource-ranges", technology=resource_mgr.technology) |
| |
| onu_id_start = self.device_info.onu_id_start |
| onu_id_end = self.device_info.onu_id_end |
| onu_id_shared = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.DEDICATED_PER_INTF |
| onu_id_shared_pool_id = None |
| alloc_id_start = self.device_info.alloc_id_start |
| alloc_id_end = self.device_info.alloc_id_end |
| alloc_id_shared = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH # TODO EdgeCore/BAL limitation |
| alloc_id_shared_pool_id = None |
| gemport_id_start = self.device_info.gemport_id_start |
| gemport_id_end = self.device_info.gemport_id_end |
| gemport_id_shared = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH # TODO EdgeCore/BAL limitation |
| gemport_id_shared_pool_id = None |
| flow_id_start = self.device_info.flow_id_start |
| flow_id_end = self.device_info.flow_id_end |
| flow_id_shared = openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH # TODO EdgeCore/BAL limitation |
| flow_id_shared_pool_id = None |
| |
| global_pool_id = 0 |
| for first_intf_pool_id in arange.intf_ids: |
| break |
| |
| for pool in arange.pools: |
| shared_pool_id = global_pool_id if pool.sharing == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH else \ |
| first_intf_pool_id if pool.sharing == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_SAME_TECH else \ |
| None |
| |
| if pool.type == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.ONU_ID: |
| onu_id_start = pool.start |
| onu_id_end = pool.end |
| onu_id_shared = pool.sharing |
| onu_id_shared_pool_id = shared_pool_id |
| elif pool.type == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.ALLOC_ID: |
| alloc_id_start = pool.start |
| alloc_id_end = pool.end |
| alloc_id_shared = pool.sharing |
| alloc_id_shared_pool_id = shared_pool_id |
| elif pool.type == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.GEMPORT_ID: |
| gemport_id_start = pool.start |
| gemport_id_end = pool.end |
| gemport_id_shared = pool.sharing |
| gemport_id_shared_pool_id = shared_pool_id |
| elif pool.type == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.FLOW_ID: |
| flow_id_start = pool.start |
| flow_id_end = pool.end |
| flow_id_shared = pool.sharing |
| flow_id_shared_pool_id = shared_pool_id |
| |
| self.log.info("device-info-init", technology=arange.technology, |
| onu_id_start=onu_id_start, onu_id_end=onu_id_end, onu_id_shared_pool_id=onu_id_shared_pool_id, |
| alloc_id_start=alloc_id_start, alloc_id_end=alloc_id_end, |
| alloc_id_shared_pool_id=alloc_id_shared_pool_id, |
| gemport_id_start=gemport_id_start, gemport_id_end=gemport_id_end, |
| gemport_id_shared_pool_id=gemport_id_shared_pool_id, |
| flow_id_start_idx=flow_id_start, |
| flow_id_end_idx=flow_id_end, |
| flow_id_shared_pool_id=flow_id_shared_pool_id, |
| intf_ids=arange.intf_ids, |
| uni_id_start_idx=0, |
| uni_id_end_idx=self.max_uni_id_per_onu) |
| |
| resource_mgr.init_default_pon_resource_ranges( |
| onu_id_start_idx=onu_id_start, |
| onu_id_end_idx=onu_id_end, |
| onu_id_shared_pool_id=onu_id_shared_pool_id, |
| alloc_id_start_idx=alloc_id_start, |
| alloc_id_end_idx=alloc_id_end, |
| alloc_id_shared_pool_id=alloc_id_shared_pool_id, |
| gemport_id_start_idx=gemport_id_start, |
| gemport_id_end_idx=gemport_id_end, |
| gemport_id_shared_pool_id=gemport_id_shared_pool_id, |
| flow_id_start_idx=flow_id_start, |
| flow_id_end_idx=flow_id_end, |
| flow_id_shared_pool_id=flow_id_shared_pool_id, |
| uni_id_start_idx=0, uni_id_end_idx=self.max_uni_id_per_onu, |
| num_of_pon_ports=self.device_info.pon_ports, |
| intf_ids=arange.intf_ids |
| ) |
| |
| # For global sharing, make sure to refresh both local and global resource manager instances' range |
| if global_resource_mgr is not self: |
| if onu_id_shared == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH: |
| global_resource_mgr.update_ranges(onu_id_start_idx=onu_id_start, onu_id_end_idx=onu_id_end) |
| resource_mgr.update_ranges(onu_id_start_idx=onu_id_start, onu_id_end_idx=onu_id_end, |
| onu_id_shared_resource_mgr=global_resource_mgr) |
| |
| if alloc_id_shared == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH: |
| global_resource_mgr.update_ranges(alloc_id_start_idx=alloc_id_start, alloc_id_end_idx=alloc_id_end) |
| resource_mgr.update_ranges(alloc_id_start_idx=alloc_id_start, alloc_id_end_idx=alloc_id_end, |
| alloc_id_shared_resource_mgr=global_resource_mgr) |
| |
| if gemport_id_shared == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH: |
| global_resource_mgr.update_ranges(gemport_id_start_idx=gemport_id_start, |
| gemport_id_end_idx=gemport_id_end) |
| resource_mgr.update_ranges(gemport_id_start_idx=gemport_id_start, gemport_id_end_idx=gemport_id_end, |
| gemport_id_shared_resource_mgr=global_resource_mgr) |
| |
| if flow_id_shared == openolt_pb2.DeviceInfo.DeviceResourceRanges.Pool.SHARED_BY_ALL_INTF_ALL_TECH: |
| global_resource_mgr.update_ranges(flow_id_start_idx=flow_id_start, |
| flow_id_end_idx=flow_id_end) |
| resource_mgr.update_ranges(flow_id_start_idx=flow_id_start, flow_id_end_idx=flow_id_end, |
| flow_id_shared_resource_mgr=global_resource_mgr) |
| |
| # Make sure loaded range fits the platform bit encoding ranges |
| resource_mgr.update_ranges(uni_id_start_idx=0, uni_id_end_idx=OpenOltPlatform.MAX_UNIS_PER_ONU-1) |