--- /dev/null
+# -*- coding: utf-8 -*-
+
+##
+# 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.
+##
+
+"""
+This is the thread for the http server North API.
+Two thread will be launched, with normal and administrative permissions.
+"""
+import yaml
+from uuid import uuid4
+from http import HTTPStatus
+
+__author__ = "Alfonso Tierno"
+__date__ = "2019-10-22"
+__version__ = "0.1"
+version_date = "Oct 2019"
+
+
+class SdnException(Exception):
+ def __init__(self, message, http_code=HTTPStatus.BAD_REQUEST.value):
+ self.http_code = http_code
+ Exception.__init__(self, message)
+
+
+class Sdn():
+ running_info = {} # TODO OVIM move the info of running threads from config_dic to this static variable
+ of_module = {}
+
+ def __init__(self, db, plugins):
+ self.db = db
+ self.plugins = plugins
+
+ def start_service(self):
+ pass # TODO py3 needed to load wims and plugins
+
+ def stop_service(self):
+ pass # nothing needed
+
+ def show_network(self, uuid):
+ pass
+
+ def delete_network(self, uuid):
+ pass
+
+ def new_network(self, network):
+ pass
+
+ def get_openflow_rules(self, network_id=None):
+ """
+ Get openflow id from DB
+ :param network_id: Network id, if none all networks will be retrieved
+ :return: Return a list with Openflow rules per net
+ """
+ # ignore input data
+ if not network_id:
+
+ where_ = {}
+ else:
+ where_ = {"net_id": network_id}
+ result, content = self.db.get_table(
+ SELECT=("name", "net_id", "ofc_id", "priority", "vlan_id", "ingress_port", "src_mac", "dst_mac", "actions"),
+ WHERE=where_, FROM='of_flows')
+
+ if result < 0:
+ raise SdnException(str(content), -result)
+ return content
+
+ def edit_openflow_rules(self, network_id=None):
+
+ """
+ To make actions over the net. The action is to reinstall the openflow rules
+ network_id can be 'all'
+ :param network_id: Network id, if none all networks will be retrieved
+ :return : Number of nets updated
+ """
+
+ # ignore input data
+ if not network_id:
+ where_ = {}
+ else:
+ where_ = {"uuid": network_id}
+ result, content = self.db.get_table(SELECT=("uuid", "type"), WHERE=where_, FROM='nets')
+
+ if result < 0:
+ raise SdnException(str(content), -result)
+
+ for net in content:
+ if net["type"] != "ptp" and net["type"] != "data":
+ result -= 1
+ continue
+
+ try:
+ self.net_update_ofc_thread(net['uuid'])
+ except SdnException as e:
+ raise SdnException("Error updating network'{}' {}".format(net['uuid'], e),
+ HTTPStatus.INTERNAL_SERVER_ERROR.value)
+ except Exception as e:
+ raise SdnException("Error updating network '{}' {}".format(net['uuid'], e),
+ HTTPStatus.INTERNAL_SERVER_ERROR.value)
+
+ return result
+
+ def delete_openflow_rules(self, ofc_id=None):
+ """
+ To make actions over the net. The action is to delete ALL openflow rules
+ :return: return operation result
+ """
+
+ if not ofc_id:
+ if 'Default' in self.config['ofcs_thread']:
+ r, c = self.config['ofcs_thread']['Default'].insert_task("clear-all")
+ else:
+ raise SdnException("Default Openflow controller not not running", HTTPStatus.NOT_FOUND.value)
+
+ elif ofc_id in self.config['ofcs_thread']:
+ r, c = self.config['ofcs_thread'][ofc_id].insert_task("clear-all")
+
+ # ignore input data
+ if r < 0:
+ raise SdnException(str(c), -r)
+ else:
+ raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id),
+ HTTPStatus.NOT_FOUND.value)
+ return r
+
+ def get_openflow_ports(self, ofc_id=None):
+ """
+ Obtain switch ports names of openflow controller
+ :return: Return flow ports in DB
+ """
+ if not ofc_id:
+ if 'Default' in self.config['ofcs_thread']:
+ conn = self.config['ofcs_thread']['Default'].OF_connector
+ else:
+ raise SdnException("Default Openflow controller not not running", HTTPStatus.NOT_FOUND.value)
+
+ elif ofc_id in self.config['ofcs_thread']:
+ conn = self.config['ofcs_thread'][ofc_id].OF_connector
+ else:
+ raise SdnException("Openflow controller not found with ofc_id={}".format(ofc_id),
+ HTTPStatus.NOT_FOUND.value)
+ return conn.pp2ofi
+
+ def new_of_controller(self, ofc_data):
+ """
+ Create a new openflow controller into DB
+ :param ofc_data: Dict openflow controller data
+ :return: openflow controller dpid
+ """
+ db_wim = {
+ "uuid": str(uuid4()),
+ "name": ofc_data["name"],
+ "description": "",
+ "type": ofc_data["type"],
+ "wim_url": "{}:{}".format(ofc_data["ip"], ofc_data["port"]),
+ }
+ db_wim_account = {
+ "uuid": str(uuid4()),
+ "name": ofc_data["name"],
+ "wim_id": db_wim["uuid"],
+ "sdn": "true",
+ "user": ofc_data.get("user"),
+ "password": ofc_data.get("password"),
+ "config": yaml.safe_dump({"dpid": ofc_data["dpid"], "version": ofc_data.get("version")},
+ default_flow_style=True, width=256)
+ }
+ db_tables = [
+ {"wims": db_wim},
+ {"wim_accounts": db_wim_account},
+ ]
+ uuid_list = [db_wim["uuid"], db_wim_account["uuid"]]
+ self.db.new_rows(db_tables, uuid_list)
+ return db_wim_account["uuid"]
+
+ def edit_of_controller(self, of_id, ofc_data):
+ """
+ Edit an openflow controller entry from DB
+ :return:
+ """
+ if not ofc_data:
+ raise SdnException("No data received during uptade OF contorller",
+ http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value)
+
+ old_of_controller = self.show_of_controller(of_id)
+
+ if old_of_controller:
+ result, content = self.db.update_rows('ofcs', ofc_data, WHERE={'uuid': of_id}, log=False)
+ if result >= 0:
+ return ofc_data
+ else:
+ raise SdnException("Error uptating OF contorller with uuid {}".format(of_id),
+ http_code=-result)
+ else:
+ raise SdnException("Error uptating OF contorller with uuid {}".format(of_id),
+ http_code=HTTPStatus.INTERNAL_SERVER_ERROR.value)
+
+ def delete_of_controller(self, of_id):
+ """
+ Delete an openflow controller from DB.
+ :param of_id: openflow controller dpid
+ :return:
+ """
+ wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": of_id, "sdn": "true"})
+ if not wim_accounts:
+ raise SdnException("Cannot find sdn controller with id='{}'".format(of_id),
+ http_code=HTTPStatus.NOT_FOUND.value)
+ elif len(wim_accounts) > 1:
+ raise SdnException("Found more than one sdn controller with id='{}'".format(of_id),
+ http_code=HTTPStatus.CONFLICT.value)
+ self.db.delete_row(FROM='wim_accounts', WHERE={"uuid": of_id})
+ self.db.delete_row(FROM='wims', WHERE={"uuid": wim_accounts[0]["wim_id"]})
+ return of_id
+
+ def _format_of_controller(self, wim_account, wim=None):
+ of_data = {x: wim_account[x] for x in ("uuid", "name", "user")}
+ if isinstance(wim_account["config"], str):
+ config = yaml.load(wim_account["config"], Loader=yaml.Loader)
+ of_data["dpid"] = config.get("dpid")
+ of_data["version"] = config.get("version")
+ if wim:
+ ip, port = wim["wim_url"].split(":")
+ of_data["ip"] = ip
+ of_data["port"] = port
+ of_data["type"] = wim["type"]
+ return of_data
+
+ def show_of_controller(self, of_id):
+ """
+ Show an openflow controller by dpid from DB.
+ :param db_filter: List with where query parameters
+ :return:
+ """
+ wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": of_id, "sdn": "true"})
+ if not wim_accounts:
+ raise SdnException("Cannot find sdn controller with id='{}'".format(of_id),
+ http_code=HTTPStatus.NOT_FOUND.value)
+ elif len(wim_accounts) > 1:
+ raise SdnException("Found more than one sdn controller with id='{}'".format(of_id),
+ http_code=HTTPStatus.CONFLICT.value)
+ wims = self.db.get_rows(FROM='wims', WHERE={"uuid": wim_accounts[0]["wim_id"]})
+ return self._format_of_controller(wim_accounts[0], wims[0])
+
+ def get_of_controllers(self, filter=None):
+ """
+ Show an openflow controllers from DB.
+ :return:
+ """
+ filter = filter or {}
+ filter["sdn"] = "true"
+ wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE=filter)
+ return [self._format_of_controller(w) for w in wim_accounts]
+
+ def set_of_port_mapping(self, maps, sdn_id, switch_dpid, vim_id):
+ """
+ Create new port mapping entry
+ :param of_maps: List with port mapping information
+ # maps =[{"ofc_id": <ofc_id>,"region": datacenter region,"compute_node": compute uuid,"pci": pci adress,
+ "switch_dpid": swith dpid,"switch_port": port name,"switch_mac": mac}]
+ :param sdn_id: ofc id
+ :param switch_dpid: switch dpid
+ :param vim_id: datacenter
+ :return:
+ """
+ # get wim from wim_account
+ wim_accounts = self.db.get_rows(FROM='wim_accounts', WHERE={"uuid": sdn_id})
+ if not wim_accounts:
+ raise SdnException("Not found sdn id={}".format(sdn_id), http_code=HTTPStatus.NOT_FOUND.value)
+ wim_id = wim_accounts[0]["wim_id"]
+ db_wim_port_mappings = []
+ for map in maps:
+ new_map = {
+ 'wim_id': wim_id,
+ 'switch_dpid': switch_dpid,
+ "switch_port": map.get("switch_port"),
+ 'datacenter_id': vim_id,
+ "device_id": map.get("compute_node"),
+ "service_endpoint_id": switch_dpid + "-" + str(uuid4())
+ }
+ if map.get("pci"):
+ new_map["device_interface_id"] = map["pci"].lower()
+ config = {}
+ if map.get("switch_mac"):
+ config["switch_mac"] = map["switch_mac"]
+ if config:
+ new_map["service_mapping_info"] = yaml.safe_dump(config, default_flow_style=True, width=256)
+ db_wim_port_mappings.append(new_map)
+
+ db_tables = [
+ {"wim_port_mappings": db_wim_port_mappings},
+ ]
+ self.db.new_rows(db_tables, [])
+ return db_wim_port_mappings
+
+ def clear_of_port_mapping(self, db_filter=None):
+ """
+ Clear port mapping filtering using db_filter dict
+ :param db_filter: Parameter to filter during remove process
+ :return:
+ """
+ return self.db.delete_row(FROM='wim_port_mappings', WHERE=db_filter)
+
+ def get_of_port_mappings(self, db_filter=None):
+ """
+ Retrive port mapping from DB
+ :param db_filter:
+ :return:
+ """
+ maps = self.db.get_rows(WHERE=db_filter, FROM='wim_port_mappings')
+ for map in maps:
+ if map.get("service_mapping_info"):
+ map["service_mapping_info"] = yaml.load(map["service_mapping_info"], Loader=yaml.Loader)
+ else:
+ map["service_mapping_info"] = {}
+ return maps