From ce1c9c84356a8aa6abad9513819a21f4c63ae4c0 Mon Sep 17 00:00:00 2001 From: tierno Date: Wed, 4 Mar 2020 20:09:42 +0000 Subject: [PATCH] adding SDN opendaylight openflow plugin Change-Id: I457a63cf2bdd13cfa49f95ff344fa912157b5115 Signed-off-by: tierno --- Dockerfile-local | 1 + RO-SDN-odl_openflow/Makefile | 24 + RO-SDN-odl_openflow/osm_rosdn_odlof/odl_of.py | 534 ++++++++++++++++++ .../osm_rosdn_odlof/sdnconn_odlof.py | 41 ++ RO-SDN-odl_openflow/requirements.txt | 18 + RO-SDN-odl_openflow/setup.py | 55 ++ RO-SDN-odl_openflow/stdeb.cfg | 19 + RO-SDN-odl_openflow/tox.ini | 43 ++ 8 files changed, 735 insertions(+) create mode 100644 RO-SDN-odl_openflow/Makefile create mode 100644 RO-SDN-odl_openflow/osm_rosdn_odlof/odl_of.py create mode 100644 RO-SDN-odl_openflow/osm_rosdn_odlof/sdnconn_odlof.py create mode 100644 RO-SDN-odl_openflow/requirements.txt create mode 100644 RO-SDN-odl_openflow/setup.py create mode 100644 RO-SDN-odl_openflow/stdeb.cfg create mode 100644 RO-SDN-odl_openflow/tox.ini diff --git a/Dockerfile-local b/Dockerfile-local index 8c7e83c9..725b2d8d 100644 --- a/Dockerfile-local +++ b/Dockerfile-local @@ -57,6 +57,7 @@ RUN /root/RO/RO/osm_ro/scripts/install-osm-im.sh --develop && \ python3 -m pip install -e /root/RO/RO-SDN-tapi && \ python3 -m pip install -e /root/RO/RO-SDN-onos_vpls && \ python3 -m pip install -e /root/RO/RO-SDN-onos_openflow && \ + python3 -m pip install -e /root/RO/RO-SDN-odl_openflow && \ python3 -m pip install -e /root/RO/RO-SDN-floodlight_openflow && \ python3 -m pip install -e /root/RO/RO-SDN-arista && \ rm -rf /root/.cache && \ diff --git a/RO-SDN-odl_openflow/Makefile b/RO-SDN-odl_openflow/Makefile new file mode 100644 index 00000000..d1d3543d --- /dev/null +++ b/RO-SDN-odl_openflow/Makefile @@ -0,0 +1,24 @@ +## +# 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. +## + +all: clean package + +clean: + rm -rf dist deb_dist osm_rosdn_odlof-*.tar.gz osm_rosdn_odlof.egg-info .eggs + +package: + python3 setup.py --command-packages=stdeb.command sdist_dsc + cd deb_dist/osm-rosdn-odlof*/ && dpkg-buildpackage -rfakeroot -uc -us + diff --git a/RO-SDN-odl_openflow/osm_rosdn_odlof/odl_of.py b/RO-SDN-odl_openflow/osm_rosdn_odlof/odl_of.py new file mode 100644 index 00000000..1cfd0a40 --- /dev/null +++ b/RO-SDN-odl_openflow/osm_rosdn_odlof/odl_of.py @@ -0,0 +1,534 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +## +# Copyright 2015 Telefonica Investigacion y Desarrollo, S.A.U. +# This file is part of openvim +# All Rights Reserved. +# +# 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: nfvlabs@tid.es +## + +""" +Implement the plugging for OpenDayLight openflow controller +It creates the class OF_conn to create dataplane connections +with static rules based on packet destination MAC address +""" + +import json +import requests +import base64 +import logging +from osm_ro.wim.openflow_conn import OpenflowConn, OpenflowConnException, OpenflowConnConnectionException, \ + OpenflowConnUnexpectedResponse, OpenflowConnAuthException, OpenflowConnNotFoundException, \ + OpenflowConnConflictException, OpenflowConnNotSupportedException, OpenflowConnNotImplemented + +__author__ = "Pablo Montes, Alfonso Tierno" +__date__ = "$28-oct-2014 12:07:15$" + + +class OfConnOdl(OpenflowConn): + """OpenDayLight connector. No MAC learning is used""" + + def __init__(self, params): + """ Constructor. + Params: dictionary with the following keys: + of_dpid: DPID to use for this controller + of_url: must be [http://HOST:PORT/] + of_user: user credentials, can be missing or None + of_password: password credentials + of_debug: debug level for logging. Default to ERROR + other keys are ignored + Raise an exception if same parameter is missing or wrong + """ + + OpenflowConn.__init__(self, params) + + # check params + url = params.get("of_url") + if not url: + raise ValueError("'url' must be provided") + if not url.startswith("http"): + url = "http://" + url + if not url.endswith("/"): + url = url + "/" + self.url = url + "onos/v1/" + + # internal variables + self.name = "OpenDayLight" + self.headers = {'content-type': 'application/json', 'Accept': 'application/json'} + self.auth = None + self.pp2ofi = {} # From Physical Port to OpenFlow Index + self.ofi2pp = {} # From OpenFlow Index to Physical Port + + self.dpid = str(params["of_dpid"]) + self.id = 'openflow:'+str(int(self.dpid.replace(':', ''), 16)) + if params and params.get("of_user"): + of_password=params.get("of_password", "") + self.auth = base64.b64encode(bytes(params["of_user"] + ":" + of_password, "utf-8")) + self.auth = self.auth.decode() + self.headers['authorization'] = 'Basic ' + self.auth + + self.logger = logging.getLogger('openmano.sdnconn.onosof') + # self.logger.setLevel(getattr(logging, params.get("of_debug", "ERROR"))) + self.logger.debug("odlof plugin initialized") + + def get_of_switches(self): + """ + Obtain a a list of switches or DPID detected by this controller + :return: list length, and a list where each element a tuple pair (DPID, IP address) + Raise an OpenflowconnConnectionException exception if fails with text_error + """ + try: + of_response = requests.get(self.url + "restconf/operational/opendaylight-inventory:nodes", + headers=self.headers) + error_text = "Openflow response {}: {}".format(of_response.status_code, of_response.text) + if of_response.status_code != 200: + self.logger.warning("get_of_switches " + error_text) + raise OpenflowconnUnexpectedResponse("Error get_of_switches " + error_text) + + self.logger.debug("get_of_switches " + error_text) + info = of_response.json() + + if not isinstance(info, dict): + self.logger.error("get_of_switches. Unexpected response, not a dict: %s", str(info)) + raise OpenflowconnUnexpectedResponse("Unexpected response, not a dict. Wrong version?") + + nodes = info.get('nodes') + if type(nodes) is not dict: + self.logger.error("get_of_switches. Unexpected response at 'nodes', not found or not a dict: %s", + str(type(info))) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'nodes', not found or not a dict." + " Wrong version?") + + node_list = nodes.get('node') + if type(node_list) is not list: + self.logger.error("get_of_switches. Unexpected response, at 'nodes':'node', " + "not found or not a list: %s", str(type(node_list))) + raise OpenflowconnUnexpectedResponse("Unexpected response, at 'nodes':'node', not found " + "or not a list. Wrong version?") + + switch_list = [] + for node in node_list: + node_id = node.get('id') + if node_id is None: + self.logger.error("get_of_switches. Unexpected response at 'nodes':'node'[]:'id', not found: %s", + str(node)) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:'id', not found. " + "Wrong version?") + + if node_id == 'controller-config': + continue + + node_ip_address = node.get('flow-node-inventory:ip-address') + if node_ip_address is None: + self.logger.error("get_of_switches. Unexpected response at 'nodes':'node'[]:'flow-node-inventory:" + "ip-address', not found: %s", str(node)) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:" + "'flow-node-inventory:ip-address', not found. Wrong version?") + + node_id_hex = hex(int(node_id.split(':')[1])).split('x')[1].zfill(16) + switch_list.append((':'.join(a+b for a,b in zip(node_id_hex[::2], node_id_hex[1::2])), node_ip_address)) + return switch_list + + except requests.exceptions.RequestException as e: + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("get_of_switches " + error_text) + raise OpenflowconnConnectionException(error_text) + except ValueError as e: + # ValueError in the case that JSON can not be decoded + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("get_of_switches " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + + def obtain_port_correspondence(self): + """ + Obtain the correspondence between physical and openflow port names + :return: dictionary: with physical name as key, openflow name as value, + Raise a OpenflowconnConnectionException expection in case of failure + """ + try: + of_response = requests.get(self.url + "restconf/operational/opendaylight-inventory:nodes", + headers=self.headers) + error_text = "Openflow response {}: {}".format(of_response.status_code, of_response.text) + if of_response.status_code != 200: + self.logger.warning("obtain_port_correspondence " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + self.logger.debug("obtain_port_correspondence " + error_text) + info = of_response.json() + + if not isinstance(info, dict): + self.logger.error("obtain_port_correspondence. Unexpected response not a dict: %s", str(info)) + raise OpenflowconnUnexpectedResponse("Unexpected openflow response, not a dict. Wrong version?") + + nodes = info.get('nodes') + if not isinstance(nodes, dict): + self.logger.error("obtain_port_correspondence. Unexpected response at 'nodes', " + "not found or not a dict: %s", str(type(nodes))) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'nodes',not found or not a dict. " + "Wrong version?") + + node_list = nodes.get('node') + if not isinstance(node_list, list): + self.logger.error("obtain_port_correspondence. Unexpected response, at 'nodes':'node', " + "not found or not a list: %s", str(type(node_list))) + raise OpenflowconnUnexpectedResponse("Unexpected response, at 'nodes':'node', not found or not a list." + " Wrong version?") + + for node in node_list: + node_id = node.get('id') + if node_id is None: + self.logger.error("obtain_port_correspondence. Unexpected response at 'nodes':'node'[]:'id', " + "not found: %s", str(node)) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:'id', not found. " + "Wrong version?") + + if node_id == 'controller-config': + continue + + # Figure out if this is the appropriate switch. The 'id' is 'openflow:' plus the decimal value + # of the dpid + # In case this is not the desired switch, continue + if self.id != node_id: + continue + + node_connector_list = node.get('node-connector') + if not isinstance(node_connector_list, list): + self.logger.error("obtain_port_correspondence. Unexpected response at " + "'nodes':'node'[]:'node-connector', not found or not a list: %s", str(node)) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:'node-connector', " + "not found or not a list. Wrong version?") + + for node_connector in node_connector_list: + self.pp2ofi[str(node_connector['flow-node-inventory:name'])] = str(node_connector['id']) + self.ofi2pp[node_connector['id']] = str(node_connector['flow-node-inventory:name']) + + node_ip_address = node.get('flow-node-inventory:ip-address') + if node_ip_address is None: + self.logger.error("obtain_port_correspondence. Unexpected response at 'nodes':'node'[]:" + "'flow-node-inventory:ip-address', not found: %s", str(node)) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'nodes':'node'[]:" + "'flow-node-inventory:ip-address', not found. Wrong version?") + + # If we found the appropriate dpid no need to continue in the for loop + break + + # print self.name, ": obtain_port_correspondence ports:", self.pp2ofi + return self.pp2ofi + except requests.exceptions.RequestException as e: + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("obtain_port_correspondence " + error_text) + raise OpenflowconnConnectionException(error_text) + except ValueError as e: + # ValueError in the case that JSON can not be decoded + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("obtain_port_correspondence " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + + def get_of_rules(self, translate_of_ports=True): + """ + Obtain the rules inserted at openflow controller + :param translate_of_ports: + :return: list where each item is a dictionary with the following content: + priority: rule priority + name: rule name (present also as the master dict key) + ingress_port: match input port of the rule + dst_mac: match destination mac address of the rule, can be missing or None if not apply + vlan_id: match vlan tag of the rule, can be missing or None if not apply + actions: list of actions, composed by a pair tuples: + (vlan, None/int): for stripping/setting a vlan tag + (out, port): send to this port + switch: DPID, all + Raise a OpenflowconnConnectionException exception in case of failure + + """ + + try: + # get rules + if len(self.ofi2pp) == 0: + self.obtain_port_correspondence() + + of_response = requests.get(self.url + "restconf/config/opendaylight-inventory:nodes/node/" + self.id + + "/table/0", headers=self.headers) + error_text = "Openflow response {}: {}".format(of_response.status_code, of_response.text) + + # The configured page does not exist if there are no rules installed. In that case we return an empty dict + if of_response.status_code == 404: + return [] + + elif of_response.status_code != 200: + self.logger.warning("get_of_rules " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + + self.logger.debug("get_of_rules " + error_text) + + info = of_response.json() + + if not isinstance(info, dict): + self.logger.error("get_of_rules. Unexpected response not a dict: %s", str(info)) + raise OpenflowconnUnexpectedResponse("Unexpected openflow response, not a dict. Wrong version?") + + table = info.get('flow-node-inventory:table') + if not isinstance(table, list): + self.logger.error("get_of_rules. Unexpected response at 'flow-node-inventory:table', " + "not a list: %s", str(type(table))) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'flow-node-inventory:table', not a list. " + "Wrong version?") + + flow_list = table[0].get('flow') + if flow_list is None: + return [] + + if not isinstance(flow_list, list): + self.logger.error("get_of_rules. Unexpected response at 'flow-node-inventory:table'[0]:'flow', not a " + "list: %s", str(type(flow_list))) + raise OpenflowconnUnexpectedResponse("Unexpected response at 'flow-node-inventory:table'[0]:'flow', " + "not a list. Wrong version?") + + # TODO translate ports according to translate_of_ports parameter + + rules = [] # Response list + for flow in flow_list: + if not ('id' in flow and 'match' in flow and 'instructions' in flow and + 'instruction' in flow['instructions'] and + 'apply-actions' in flow['instructions']['instruction'][0] and + 'action' in flow['instructions']['instruction'][0]['apply-actions']): + raise OpenflowconnUnexpectedResponse("unexpected openflow response, one or more elements are " + "missing. Wrong version?") + + flow['instructions']['instruction'][0]['apply-actions']['action'] + + rule = dict() + rule['switch'] = self.dpid + rule['priority'] = flow.get('priority') + # rule['name'] = flow['id'] + # rule['cookie'] = flow['cookie'] + if 'in-port' in flow['match']: + in_port = flow['match']['in-port'] + if in_port not in self.ofi2pp: + raise OpenflowconnUnexpectedResponse("Error: Ingress port {} is not in switch port list". + format(in_port)) + + if translate_of_ports: + in_port = self.ofi2pp[in_port] + + rule['ingress_port'] = in_port + + if 'vlan-match' in flow['match'] and 'vlan-id' in flow['match']['vlan-match'] and \ + 'vlan-id' in flow['match']['vlan-match']['vlan-id'] and \ + 'vlan-id-present' in flow['match']['vlan-match']['vlan-id'] and \ + flow['match']['vlan-match']['vlan-id']['vlan-id-present'] == True: + rule['vlan_id'] = flow['match']['vlan-match']['vlan-id']['vlan-id'] + + if 'ethernet-match' in flow['match'] and 'ethernet-destination' in flow['match']['ethernet-match'] \ + and 'address' in flow['match']['ethernet-match']['ethernet-destination']: + rule['dst_mac'] = flow['match']['ethernet-match']['ethernet-destination']['address'] + + instructions = flow['instructions']['instruction'][0]['apply-actions']['action'] + + max_index = 0 + for instruction in instructions: + if instruction['order'] > max_index: + max_index = instruction['order'] + + actions = [None]*(max_index+1) + for instruction in instructions: + if 'output-action' in instruction: + if 'output-node-connector' not in instruction['output-action']: + raise OpenflowconnUnexpectedResponse("unexpected openflow response, one or more elementa " + "are missing. Wrong version?") + + out_port = instruction['output-action']['output-node-connector'] + if out_port not in self.ofi2pp: + raise OpenflowconnUnexpectedResponse("Error: Output port {} is not in switch port list". + format(out_port)) + + if translate_of_ports: + out_port = self.ofi2pp[out_port] + + actions[instruction['order']] = ('out', out_port) + + elif 'strip-vlan-action' in instruction: + actions[instruction['order']] = ('vlan', None) + + elif 'set-field' in instruction: + if not ('vlan-match' in instruction['set-field'] and + 'vlan-id' in instruction['set-field']['vlan-match'] and + 'vlan-id' in instruction['set-field']['vlan-match']['vlan-id']): + raise OpenflowconnUnexpectedResponse("unexpected openflow response, one or more elements " + "are missing. Wrong version?") + + actions[instruction['order']] = ('vlan', + instruction['set-field']['vlan-match']['vlan-id']['vlan-id']) + + actions = [x for x in actions if x is not None] + + rule['actions'] = list(actions) + rules.append(rule) + + return rules + except requests.exceptions.RequestException as e: + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("get_of_rules " + error_text) + raise OpenflowconnConnectionException(error_text) + except ValueError as e: + # ValueError in the case that JSON can not be decoded + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("get_of_rules " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + + def del_flow(self, flow_name): + """ + Delete an existing rule + :param flow_name: flow_name, this is the rule name + :return: Raise a OpenflowconnConnectionException expection in case of failure + """ + + try: + of_response = requests.delete(self.url + "restconf/config/opendaylight-inventory:nodes/node/" + self.id + + "/table/0/flow/" + flow_name, headers=self.headers) + error_text = "Openflow response {}: {}".format(of_response.status_code, of_response.text) + if of_response.status_code != 200: + self.logger.warning("del_flow " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + self.logger.debug("del_flow OK " + error_text) + return None + except requests.exceptions.RequestException as e: + # raise an exception in case of contection error + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("del_flow " + error_text) + raise OpenflowconnConnectionException(error_text) + + def new_flow(self, data): + """ + Insert a new static rule + :param data: dictionary with the following content: + priority: rule priority + name: rule name + ingress_port: match input port of the rule + dst_mac: match destination mac address of the rule, missing or None if not apply + vlan_id: match vlan tag of the rule, missing or None if not apply + actions: list of actions, composed by a pair tuples with these posibilities: + ('vlan', None/int): for stripping/setting a vlan tag + ('out', port): send to this port + :return: Raise a OpenflowconnConnectionException exception in case of failure + """ + + try: + self.logger.debug("new_flow data: {}".format(data)) + if len(self.pp2ofi) == 0: + self.obtain_port_correspondence() + + # We have to build the data for the opendaylight call from the generic data + flow = { + 'id': data['name'], + 'flow-name': data['name'], + 'idle-timeout': 0, + 'hard-timeout': 0, + 'table_id': 0, + 'priority': data.get('priority'), + 'match': {} + } + sdata = {'flow-node-inventory:flow': [flow]} + if not data['ingress_port'] in self.pp2ofi: + error_text = 'Error. Port ' + data['ingress_port'] + ' is not present in the switch' + self.logger.warning("new_flow " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + flow['match']['in-port'] = self.pp2ofi[data['ingress_port']] + if data.get('dst_mac'): + flow['match']['ethernet-match'] = { + 'ethernet-destination': {'address': data['dst_mac']} + } + if data.get('vlan_id'): + flow['match']['vlan-match'] = { + 'vlan-id': { + 'vlan-id-present': True, + 'vlan-id': int(data['vlan_id']) + } + } + actions = [] + flow['instructions'] = { + 'instruction': [{ + 'order': 1, + 'apply-actions': {'action': actions} + }] + } + + order = 0 + for action in data['actions']: + new_action = {'order': order} + if action[0] == "vlan": + if action[1] is None: + # strip vlan + new_action['strip-vlan-action'] = {} + else: + new_action['set-field'] = { + 'vlan-match': { + 'vlan-id': { + 'vlan-id-present': True, + 'vlan-id': int(action[1]) + } + } + } + elif action[0] == 'out': + new_action['output-action'] = {} + if not action[1] in self.pp2ofi: + error_msg = 'Port ' + action[1] + ' is not present in the switch' + raise OpenflowconnUnexpectedResponse(error_msg) + + new_action['output-action']['output-node-connector'] = self.pp2ofi[action[1]] + else: + error_msg = "Unknown item '%s' in action list".format(action[0]) + self.logger.error("new_flow " + error_msg) + raise OpenflowconnUnexpectedResponse(error_msg) + + actions.append(new_action) + order += 1 + + # print json.dumps(sdata) + of_response = requests.put(self.url + "restconf/config/opendaylight-inventory:nodes/node/" + self.id + + "/table/0/flow/" + data['name'], headers=self.headers, data=json.dumps(sdata)) + error_text = "Openflow response {}: {}".format(of_response.status_code, of_response.text) + if of_response.status_code != 200: + self.logger.warning("new_flow " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + self.logger.debug("new_flow OK " + error_text) + return None + + except requests.exceptions.RequestException as e: + # raise an exception in case of contection error + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("new_flow " + error_text) + raise OpenflowconnConnectionException(error_text) + + def clear_all_flows(self): + """ + Delete all existing rules + :return: Raise a OpenflowconnConnectionException expection in case of failure + """ + try: + of_response = requests.delete(self.url + "restconf/config/opendaylight-inventory:nodes/node/" + self.id + + "/table/0", headers=self.headers) + error_text = "Openflow response {}: {}".format(of_response.status_code, of_response.text) + if of_response.status_code != 200 and of_response.status_code != 404: # HTTP_Not_Found + self.logger.warning("clear_all_flows " + error_text) + raise OpenflowconnUnexpectedResponse(error_text) + self.logger.debug("clear_all_flows OK " + error_text) + return None + except requests.exceptions.RequestException as e: + error_text = type(e).__name__ + ": " + str(e) + self.logger.error("clear_all_flows " + error_text) + raise OpenflowconnConnectionException(error_text) diff --git a/RO-SDN-odl_openflow/osm_rosdn_odlof/sdnconn_odlof.py b/RO-SDN-odl_openflow/osm_rosdn_odlof/sdnconn_odlof.py new file mode 100644 index 00000000..2850be41 --- /dev/null +++ b/RO-SDN-odl_openflow/osm_rosdn_odlof/sdnconn_odlof.py @@ -0,0 +1,41 @@ +## +# Copyright 2019 Telefonica Investigacion y Desarrollo, S.A.U. +# All Rights Reserved. +# +# 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. +# +## +"""The SdnConnectorOdlOf connector is responsible for creating services using pro active operflow rules. +""" + +import logging +from osm_ro.wim.openflow_conn import SdnConnectorOpenFlow +from .odl_of import OfConnOdl + + +class SdnConnectorOdlOf(SdnConnectorOpenFlow): + + def __init__(self, wim, wim_account, config=None, logger=None): + """Creates a connectivity based on pro-active openflow rules + """ + self.logger = logging.getLogger('openmano.sdnconn.odlof') + super().__init__(wim, wim_account, config, logger) + of_params = { + "of_url": wim["wim_url"], + "of_dpid": config.get("dpid") or config.get("switch_id"), + "of_user": wim_account["user"], + "of_password": wim_account["password"], + } + self.openflow_conn = OfConnOdl(of_params) + super().__init__(wim, wim_account, config, logger, self.openflow_conn) diff --git a/RO-SDN-odl_openflow/requirements.txt b/RO-SDN-odl_openflow/requirements.txt new file mode 100644 index 00000000..a6f6d655 --- /dev/null +++ b/RO-SDN-odl_openflow/requirements.txt @@ -0,0 +1,18 @@ +## +# 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. +## + +requests +git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro&subdirectory=RO + diff --git a/RO-SDN-odl_openflow/setup.py b/RO-SDN-odl_openflow/setup.py new file mode 100644 index 00000000..cdebc414 --- /dev/null +++ b/RO-SDN-odl_openflow/setup.py @@ -0,0 +1,55 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +## +# 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 setuptools import setup + +_name = "osm_rosdn_odlof" + +README = """ +=========== +osm-rosdn_odlof +=========== + +osm-ro plugin for OpenDayLight SDN using pre-computed openflow rules +""" + +setup( + name=_name, + description='OSM RO plugin for SDN with odl openflow rules', + long_description=README, + version_command=('git describe --match v* --tags --long --dirty', 'pep440-git-full'), + # version=VERSION, + # python_requires='>3.5.0', + author='ETSI OSM', + author_email='alfonso.tiernosepulveda@telefonica.com', + maintainer='Alfonso Tierno', + maintainer_email='alfonso.tiernosepulveda@telefonica.com', + url='https://osm.etsi.org/gitweb/?p=osm/RO.git;a=summary', + license='Apache 2.0', + + packages=[_name], + include_package_data=True, + install_requires=[ + "requests", + "osm-ro @ git+https://osm.etsi.org/gerrit/osm/RO.git#egg=osm-ro&subdirectory=RO" + ], + setup_requires=['setuptools-version-command'], + entry_points={ + 'osm_rosdn.plugins': ['rosdn_odlof = osm_rosdn_odlof.sdnconn_odlof:SdnConnectorOdlOf'], + }, +) diff --git a/RO-SDN-odl_openflow/stdeb.cfg b/RO-SDN-odl_openflow/stdeb.cfg new file mode 100644 index 00000000..0c718e4f --- /dev/null +++ b/RO-SDN-odl_openflow/stdeb.cfg @@ -0,0 +1,19 @@ +# +# 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. +# + +[DEFAULT] +X-Python3-Version : >= 3.5 +Depends3: python3-requests, python3-osm-ro + diff --git a/RO-SDN-odl_openflow/tox.ini b/RO-SDN-odl_openflow/tox.ini new file mode 100644 index 00000000..77b9ba07 --- /dev/null +++ b/RO-SDN-odl_openflow/tox.ini @@ -0,0 +1,43 @@ +## +# 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. +## + +[tox] +envlist = flake8 +toxworkdir={toxinidir}/.tox + +[testenv] +basepython = python3 +install_command = python3 -m pip install -U {opts} {packages} +# deps = -r{toxinidir}/test-requirements.txt +commands=python3 -m unittest discover -v + +[testenv:flake8] +basepython = python3 +deps = flake8 + -r{toxinidir}/requirements.txt +install_command = python3 -m pip install -U {opts} {packages} +commands = flake8 osm_rosdn_odlof --max-line-length 120 \ + --exclude .svn,CVS,.gz,.git,__pycache__,.tox,local,temp --ignore W291,W293,E226,W504 + +[testenv:unittest] +basepython = python3 +commands = python3 -m unittest osm_rosdn_odlof.tests + +[testenv:build] +basepython = python3 +deps = stdeb + setuptools-version-command +commands = python3 setup.py --command-packages=stdeb.command bdist_deb + -- 2.25.1