X-Git-Url: https://osm.etsi.org/gitweb/?a=blobdiff_plain;f=osm_ro%2Fwim%2Fwan_link_actions.py;h=febbc31303dd0a83f92df55249d56b5f7fffe023;hb=HEAD;hp=034e41599c2ef4f1719b65eeb15f2fae2dfa51ff;hpb=6e816cd16f11eabea6eed9b6f17ce2adda7cf4b2;p=osm%2FRO.git diff --git a/osm_ro/wim/wan_link_actions.py b/osm_ro/wim/wan_link_actions.py deleted file mode 100644 index 034e4159..00000000 --- a/osm_ro/wim/wan_link_actions.py +++ /dev/null @@ -1,442 +0,0 @@ -# -*- coding: utf-8 -*- -## -# Copyright 2018 University of Bristol - High Performance Networks Research -# Group -# All Rights Reserved. -# -# Contributors: Anderson Bravalheri, Dimitrios Gkounis, Abubakar Siddique -# Muqaddas, Navdeep Uniyal, Reza Nejabati and Dimitra Simeonidou -# -# 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. -# -# For those usages not covered by the Apache License, Version 2.0 please -# contact with: -# -# Neither the name of the University of Bristol nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. -# -# This work has been performed in the context of DCMS UK 5G Testbeds -# & Trials Programme and in the framework of the Metro-Haul project - -# funded by the European Commission under Grant number 761727 through the -# Horizon 2020 and 5G-PPP programmes. -## -# pylint: disable=E1101,E0203,W0201 -import json -from pprint import pformat -from sys import exc_info -from time import time - -from six import reraise - -from ..utils import filter_dict_keys as filter_keys -from ..utils import merge_dicts, remove_none_items, safe_get, truncate -from .actions import CreateAction, DeleteAction, FindAction -from .errors import ( - InconsistentState, - NoRecordFound, - NoExternalPortFound -) -from wimconn import WimConnectorError - -INSTANCE_NET_STATUS_ERROR = ('DOWN', 'ERROR', 'VIM_ERROR', - 'DELETED', 'SCHEDULED_DELETION') -INSTANCE_NET_STATUS_PENDING = ('BUILD', 'INACTIVE', 'SCHEDULED_CREATION') -INSTANCE_VM_STATUS_ERROR = ('ERROR', 'VIM_ERROR', - 'DELETED', 'SCHEDULED_DELETION') - - -class RefreshMixin(object): - def refresh(self, connector, persistence): - """Ask the external WAN Infrastructure Manager system for updates on - the status of the task. - - Arguments: - connector: object with API for accessing the WAN - Infrastructure Manager system - persistence: abstraction layer for the database - """ - fields = ('wim_status', 'wim_info', 'error_msg') - result = dict.fromkeys(fields) - - try: - result.update( - connector - .get_connectivity_service_status(self.wim_internal_id)) - except WimConnectorError as ex: - self.logger.exception(ex) - result.update(wim_status='WIM_ERROR', error_msg=truncate(ex)) - - result = filter_keys(result, fields) - - action_changes = remove_none_items({ - 'extra': merge_dicts(self.extra, result), - 'status': 'BUILD' if result['wim_status'] == 'BUILD' else None, - 'error_msg': result['error_msg'], - 'modified_at': time()}) - link_changes = merge_dicts(result, status=result.pop('wim_status')) - # ^ Rename field: wim_status => status - - persistence.update_wan_link(self.item_id, - remove_none_items(link_changes)) - - self.save(persistence, **action_changes) - - return result - - -class WanLinkCreate(RefreshMixin, CreateAction): - def fail(self, persistence, reason, status='FAILED'): - changes = {'status': 'ERROR', 'error_msg': truncate(reason)} - persistence.update_wan_link(self.item_id, changes) - return super(WanLinkCreate, self).fail(persistence, reason, status) - - def process(self, connector, persistence, ovim): - """Process the current task. - First we check if all the dependencies are ready, - then we call ``execute`` to actually execute the action. - - Arguments: - connector: object with API for accessing the WAN - Infrastructure Manager system - persistence: abstraction layer for the database - ovim: instance of openvim, abstraction layer that enable - SDN-related operations - """ - wan_link = persistence.get_by_uuid('instance_wim_nets', self.item_id) - - # First we check if all the dependencies are solved - instance_nets = persistence.get_instance_nets( - wan_link['instance_scenario_id'], wan_link['sce_net_id']) - - try: - dependency_statuses = [n['status'] for n in instance_nets] - except KeyError: - self.logger.debug('`status` not found in\n\n%s\n\n', - json.dumps(instance_nets, indent=4)) - errored = [instance_nets[i] - for i, status in enumerate(dependency_statuses) - if status in INSTANCE_NET_STATUS_ERROR] - if errored: - return self.fail( - persistence, - 'Impossible to stablish WAN connectivity due to an issue ' - 'with the local networks:\n\t' + - '\n\t'.join('{uuid}: {status}'.format(**n) for n in errored)) - - pending = [instance_nets[i] - for i, status in enumerate(dependency_statuses) - if status in INSTANCE_NET_STATUS_PENDING] - if pending: - return self.defer( - persistence, - 'Still waiting for the local networks to be active:\n\t' + - '\n\t'.join('{uuid}: {status}'.format(**n) for n in pending)) - - return self.execute(connector, persistence, ovim, instance_nets) - - def _get_connection_point_info(self, persistence, ovim, instance_net): - """Retrieve information about the connection PoP <> WAN - - Arguments: - persistence: object that encapsulates persistence logic - (e.g. db connection) - ovim: object that encapsulates network management logic (openvim) - instance_net: record with the information about a local network - (inside a VIM). This network will be connected via a WAN link - to a different network in a distinct VIM. - This method is used to trace what would be the way this network - can be accessed from the outside world. - - Returns: - dict: Record representing the wan_port_mapping associated to the - given instance_net. The expected fields are: - **wim_id**, **datacenter_id**, **pop_switch_dpid** (the local - network is expected to be connected at this switch), - **pop_switch_port**, **wan_service_endpoint_id**, - **wan_service_mapping_info**. - """ - # First, we need to find a route from the datacenter to the outside - # world. For that, we can use the rules given in the datacenter - # configuration: - datacenter_id = instance_net['datacenter_id'] - datacenter = persistence.get_datacenter_by(datacenter_id) - rules = safe_get(datacenter, 'config.external_connections', {}) or {} - vim_info = instance_net.get('vim_info', {}) or {} - # Alternatively, we can look for it, using the SDN assist - external_port = (self._evaluate_rules(rules, vim_info) or - self._get_port_sdn(ovim, instance_net)) - - if not external_port: - raise NoExternalPortFound(instance_net) - - # Then, we find the WAN switch that is connected to this external port - try: - wim_account = persistence.get_wim_account_by( - uuid=self.wim_account_id) - - criteria = { - 'wim_id': wim_account['wim_id'], - 'pop_switch_dpid': external_port[0], - 'pop_switch_port': external_port[1], - 'datacenter_id': datacenter_id} - - wan_port_mapping = persistence.query_one( - FROM='wim_port_mappings', - WHERE=criteria) - except NoRecordFound: - ex = InconsistentState('No WIM port mapping found:' - 'wim_account: {}\ncriteria:\n{}'.format( - self.wim_account_id, pformat(criteria))) - reraise(ex.__class__, ex, exc_info()[2]) - - # It is important to return encapsulation information if present - mapping = merge_dicts( - wan_port_mapping.get('wan_service_mapping_info'), - filter_keys(vim_info, ('encapsulation_type', 'encapsulation_id')) - ) - - return merge_dicts(wan_port_mapping, wan_service_mapping_info=mapping) - - def _get_port_sdn(self, ovim, instance_net): - criteria = {'net_id': instance_net['sdn_net_id']} - try: - local_port_mapping = ovim.get_ports(filter=criteria) - - if local_port_mapping: - return (local_port_mapping[0]['switch_dpid'], - local_port_mapping[0]['switch_port']) - except: # noqa - self.logger.exception('Problems when calling OpenVIM') - - self.logger.debug('No ports found using criteria:\n%r\n.', criteria) - return None - - def _evaluate_rules(self, rules, vim_info): - """Given a ``vim_info`` dict from a ``instance_net`` record, evaluate - the set of rules provided during the VIM/datacenter registration to - determine an external port used to connect that VIM/datacenter to - other ones where different parts of the NS will be instantiated. - - For example, considering a VIM/datacenter is registered like the - following:: - - vim_record = { - "uuid": ... - ... # Other properties associated with the VIM/datacenter - "config": { - ... # Other configuration - "external_connections": [ - { - "condition": { - "provider:physical_network": "provider_net1", - ... # This method will look up all the keys listed here - # in the instance_nets.vim_info dict and compare the - # values. When all the values match, the associated - # vim_external_port will be selected. - }, - "vim_external_port": {"switch": "switchA", "port": "portB"} - }, - ... # The user can provide as many rules as needed, however - # only the first one to match will be applied. - ] - } - } - - When an ``instance_net`` record is instantiated in that datacenter with - the following information:: - - instance_net = { - "uuid": ... - ... - "vim_info": { - ... - "provider_physical_network": "provider_net1", - } - } - - Then, ``switchA`` and ``portB`` will be used to stablish the WAN - connection. - - Arguments: - rules (list): Set of dicts containing the keys ``condition`` and - ``vim_external_port``. This list should be extracted from - ``vim['config']['external_connections']`` (as stored in the - database). - vim_info (dict): Information given by the VIM Connector, against - which the rules will be evaluated. - - Returns: - tuple: switch id (local datacenter switch) and port or None if - the rule does not match. - """ - rule = next((r for r in rules if self._evaluate_rule(r, vim_info)), {}) - if 'vim_external_port' not in rule: - self.logger.debug('No external port found.\n' - 'rules:\n%r\nvim_info:\n%r\n\n', rules, vim_info) - return None - - return (rule['vim_external_port']['switch'], - rule['vim_external_port']['port']) - - @staticmethod - def _evaluate_rule(rule, vim_info): - """Evaluate the conditions from a single rule to ``vim_info`` and - determine if the rule should be applicable or not. - - Please check :obj:`~._evaluate_rules` for more information. - - Arguments: - rule (dict): Data structure containing the keys ``condition`` and - ``vim_external_port``. This should be one of the elements in - ``vim['config']['external_connections']`` (as stored in the - database). - vim_info (dict): Information given by the VIM Connector, against - which the rules will be evaluated. - - Returns: - True or False: If all the conditions are met. - """ - condition = rule.get('condition', {}) or {} - return all(safe_get(vim_info, k) == v for k, v in condition.items()) - - @staticmethod - def _derive_connection_point(wan_info): - point = {'service_endpoint_id': wan_info['wan_service_endpoint_id']} - # TODO: Cover other scenarios, e.g. VXLAN. - details = wan_info.get('wan_service_mapping_info', {}) - if details.get('encapsulation_type') == 'vlan': - point['service_endpoint_encapsulation_type'] = 'dot1q' - point['service_endpoint_encapsulation_info'] = { - 'vlan': details['encapsulation_id'] - } - else: - point['service_endpoint_encapsulation_type'] = 'none' - return point - - @staticmethod - def _derive_service_type(connection_points): - # TODO: add multipoint and L3 connectivity. - if len(connection_points) == 2: - return 'ELINE' - else: - raise NotImplementedError('Multipoint connectivity is not ' - 'supported yet.') - - def _update_persistent_data(self, persistence, service_uuid, conn_info): - """Store plugin/connector specific information in the database""" - persistence.update_wan_link(self.item_id, { - 'wim_internal_id': service_uuid, - 'wim_info': {'conn_info': conn_info}, - 'status': 'BUILD'}) - - def execute(self, connector, persistence, ovim, instance_nets): - """Actually execute the action, since now we are sure all the - dependencies are solved - """ - try: - wan_info = (self._get_connection_point_info(persistence, ovim, net) - for net in instance_nets) - connection_points = [self._derive_connection_point(w) - for w in wan_info] - - uuid, info = connector.create_connectivity_service( - self._derive_service_type(connection_points), - connection_points - # TODO: other properties, e.g. bandwidth - ) - except (WimConnectorError, InconsistentState, - NoExternalPortFound) as ex: - self.logger.exception(ex) - return self.fail( - persistence, - 'Impossible to stablish WAN connectivity.\n\t{}'.format(ex)) - - self.logger.debug('WAN connectivity established %s\n%s\n', - uuid, json.dumps(info, indent=4)) - self.wim_internal_id = uuid - self._update_persistent_data(persistence, uuid, info) - self.succeed(persistence) - return uuid - - -class WanLinkDelete(DeleteAction): - def succeed(self, persistence): - try: - persistence.update_wan_link(self.item_id, {'status': 'DELETED'}) - except NoRecordFound: - self.logger.debug('%s(%s) record already deleted', - self.item, self.item_id) - - return super(WanLinkDelete, self).succeed(persistence) - - def get_wan_link(self, persistence): - """Retrieve information about the wan_link - - It might be cached, or arrive from the database - """ - if self.extra.get('wan_link'): - # First try a cached version of the data - return self.extra['wan_link'] - - return persistence.get_by_uuid( - 'instance_wim_nets', self.item_id) - - def process(self, connector, persistence, ovim): - """Delete a WAN link previously created""" - wan_link = self.get_wan_link(persistence) - if 'ERROR' in (wan_link.get('status') or ''): - return self.fail( - persistence, - 'Impossible to delete WAN connectivity, ' - 'it was never successfully established:' - '\n\t{}'.format(wan_link['error_msg'])) - - internal_id = wan_link.get('wim_internal_id') or self.internal_id - - if not internal_id: - self.logger.debug('No wim_internal_id found in\n%s\n%s\n' - 'Assuming no network was created yet, ' - 'so no network have to be deleted.', - json.dumps(wan_link, indent=4), - json.dumps(self.as_dict(), indent=4)) - return self.succeed(persistence) - - try: - id = self.wim_internal_id - conn_info = safe_get(wan_link, 'wim_info.conn_info') - self.logger.debug('Connection Service %s (wan_link: %s):\n%s\n', - id, wan_link['uuid'], - json.dumps(conn_info, indent=4)) - result = connector.delete_connectivity_service(id, conn_info) - except (WimConnectorError, InconsistentState) as ex: - self.logger.exception(ex) - return self.fail( - persistence, - 'Impossible to delete WAN connectivity.\n\t{}'.format(ex)) - - self.logger.debug('WAN connectivity removed %s', result) - self.succeed(persistence) - - return result - - -class WanLinkFind(RefreshMixin, FindAction): - pass - - -ACTIONS = { - 'CREATE': WanLinkCreate, - 'DELETE': WanLinkDelete, - 'FIND': WanLinkFind, -}