--- /dev/null
+import json
+import logging
+import copy
+
+from mininet.node import OVSSwitch
+
+from flask import Flask
+from flask import Response, request
+from flask_restful import Api, Resource
+from mininet.link import Link
+import uuid
+
+
+class ChainApi(Resource):
+ """
+ The chain API is a component that is not used in OpenStack.
+ It is a custom built REST API that can be used to create network chains and loadbalancers.
+ """
+
+ def __init__(self, inc_ip, inc_port, manage):
+ # setup Flask
+ self.app = Flask(__name__)
+ self.api = Api(self.app)
+ self.ip = inc_ip
+ self.port = inc_port
+ self.manage = manage
+ self.playbook_file = '/tmp/son-emu-requests.log'
+ self.api.add_resource(ChainVersionsList, "/",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(ChainList, "/v1/chain/list",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(ChainVnfInterfaces, "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(ChainVnfDcStackInterfaces,
+ "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(BalanceHostList, "/v1/lb/list",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(BalanceHost, "/v1/lb/<vnf_src_name>/<vnf_src_interface>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(BalanceHostDcStack, "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(QueryTopology, "/v1/topo",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(Shutdown, "/shutdown")
+
+ @self.app.after_request
+ def add_access_control_header(response):
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ def _start_flask(self):
+ logging.info("Starting %s endpoint @ http://%s:%d" % ("ChainDummyApi", self.ip, self.port))
+ if self.app is not None:
+ self.app.before_request(self.dump_playbook)
+ self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+ def dump_playbook(self):
+ with self.manage.lock:
+ with open(self.playbook_file, 'a') as logfile:
+ if len(request.data) > 0:
+ data = "# CHAIN API\n"
+ data += "curl -X {type} -H \"Content-type: application/json\" -d '{data}' {url}".format(type=request.method,
+ data=request.data,
+ url=request.url)
+ logfile.write(data + "\n")
+
+
+class Shutdown(Resource):
+ def get(self):
+ logging.debug(("%s is beeing shut down") % (__name__))
+ func = request.environ.get('werkzeug.server.shutdown')
+ if func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ func()
+
+
+class ChainVersionsList(Resource):
+ '''
+ Entrypoint to find versions of the chain api.
+ '''
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ '''
+ :return: flask.Response containing the openstack like description of the chain api
+ '''
+ # at least let it look like an open stack function
+ try:
+ resp = """
+ {
+ "versions": [
+ {
+ "id": "v1",
+ "links": [
+ {
+ "href": "http://%s:%d/v1/",
+ "rel": "self"
+ }
+ ],
+ "status": "CURRENT",
+ "version": "1",
+ "min_version": "1",
+ "updated": "2013-07-23T11:33:21Z"
+ }
+ ]
+ }
+ """ % (self.api.ip, self.api.port)
+
+ return Response(resp, status=200, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not show list of versions." % __name__)
+ return ex.message, 500
+
+
+class ChainList(Resource):
+ '''
+ Will retrieve all chains including their paths.
+ '''
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ '''
+ :return: flask.Response containing all live chains
+ '''
+ # at least let it look like an open stack function
+ try:
+ resp = {"chains": list()}
+
+ for chain in self.api.manage.full_chain_data.values():
+ resp["chains"].append(chain)
+
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not list all network chains." % __name__)
+ return ex.message, 500
+
+
+class BalanceHostList(Resource):
+ '''
+ Will retrieve all loadbalance rules including their paths.
+ '''
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ '''
+ :return: flask.Response containing all live loadbalancer rules
+ '''
+ # at least let it look like an open stack function
+ try:
+ resp = {"loadbalancers": list()}
+
+ for lb in self.api.manage.full_lb_data.values():
+ resp["loadbalancers"].append(lb)
+
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not list all live loadbalancers." % __name__)
+ return ex.message, 500
+
+
+class ChainVnfInterfaces(Resource):
+ """
+ Handles requests targeted at: "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+ Requests are for tearing down or setting up a chain between two vnfs
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def put(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+ """
+ A put request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+ will create a chain between two interfaces at the specified vnfs.
+
+ Note:
+ Does not allow a custom path. Uses ``.post``
+ Internally just makes a POST request with no POST data!
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+ 501 if one of the VNF / intfs does not exist
+ :rtype: :class:`flask.Response`
+ """
+ return self.post(src_vnf, src_intfs, dst_vnf, dst_intfs)
+
+ def post(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+ """
+ A post request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+ will create a chain between two interfaces at the specified vnfs.
+ The POST data contains the path like this.
+ { "path": ["dc1.s1", "s1", "dc4.s1"]}
+ path specifies the destination vnf and interface and contains a list of switches
+ that the path traverses. The path may not contain single hop loops like:
+ [s1, s2, s1].
+ This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+ 501 if one of the VNF / intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+
+ if request.is_json:
+ path = request.json.get('path')
+ layer2 = request.json.get('layer2', True)
+ else:
+ path = None
+ layer2 = True
+
+ # check if both VNFs exist
+ if not self.api.manage.check_vnf_intf_pair(src_vnf, src_intfs):
+ return Response(u"VNF %s or intfs %s does not exist" % (src_vnf, src_intfs), status=501,
+ mimetype="application/json")
+ if not self.api.manage.check_vnf_intf_pair(dst_vnf, dst_intfs):
+ return Response(u"VNF %s or intfs %s does not exist" % (dst_vnf, dst_intfs), status=501,
+ mimetype="application/json")
+ try:
+ cookie = self.api.manage.network_action_start(src_vnf, dst_vnf, vnf_src_interface=src_intfs,
+ vnf_dst_interface=dst_intfs, bidirectional=True,
+ path=path, layer2=layer2)
+ resp = {'cookie': cookie}
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+ return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+ def delete(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+ """
+ A DELETE request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+ will delete a previously created chain.
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500\
+ also returns the cookie as dict {'cookie': value}
+ 501 if one of the VNF / intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ # check if both VNFs exist
+ # check if both VNFs exist
+ if not self.api.manage.check_vnf_intf_pair(src_vnf, src_intfs):
+ return Response(u"VNF %s or intfs %s does not exist" % (src_vnf, src_intfs), status=501,
+ mimetype="application/json")
+ if not self.api.manage.check_vnf_intf_pair(dst_vnf, dst_intfs):
+ return Response(u"VNF %s or intfs %s does not exist" % (dst_vnf, dst_intfs), status=501,
+ mimetype="application/json")
+ try:
+ cookie = self.api.manage.network_action_stop(src_vnf, dst_vnf, vnf_src_interface=src_intfs,
+ vnf_dst_interface=dst_intfs, bidirectional=True)
+ return Response(json.dumps(cookie), status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error deleting the chain.\n %s" % (__name__, e))
+ return Response(u"Error deleting the chain", status=500, mimetype="application/json")
+
+
+class ChainVnfDcStackInterfaces(Resource):
+ '''
+ Handles requests targeted at: "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+ Handles tearing down or setting up a chain between two vnfs for stacks.
+ '''
+
+ def __init__(self, api):
+ self.api = api
+
+ def put(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+ """
+ A PUT request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+ will set up chain.
+
+ :Note: PUT Requests can not set up custom paths!
+
+ :param src_dc: Name of the source datacenter
+ :type src_dc: `str`
+ :param src_stack: Name of the source stack
+ :type src_stack: `str`
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_dc: Name of the destination datacenter
+ :type dst_dc: ``str``
+ :param dst_stack: Name of the destination stack
+ :type dst_stack: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500\
+ also returns the cookie as dict {'cookie': value}
+ 501 if VNF or intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ # search for real names
+ real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+ if type(real_names) is not tuple:
+ # something went wrong
+ return real_names
+
+ container_src, container_dst, interface_src, interface_dst = real_names
+
+ # check if both VNFs exist
+ if not self.api.manage.check_vnf_intf_pair(container_src, interface_src):
+ return Response(u"VNF %s or intfs %s does not exist" % (container_src, interface_src), status=501,
+ mimetype="application/json")
+ if not self.api.manage.check_vnf_intf_pair(container_dst, interface_dst):
+ return Response(u"VNF %s or intfs %s does not exist" % (container_dst, interface_dst), status=501,
+ mimetype="application/json")
+
+ try:
+ cookie = self.api.manage.network_action_start(container_src, container_dst, vnf_src_interface=interface_src,
+ vnf_dst_interface=interface_dst, bidirectional=True,
+ layer2=True)
+ resp = {'cookie': cookie}
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+ return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+ def post(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+ """
+ A post request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+ will create a chain between two interfaces at the specified vnfs.
+ The POST data contains the path like this.
+ { "path": ["dc1.s1", "s1", "dc4.s1"]}
+ path specifies the destination vnf and interface and contains a list of switches
+ that the path traverses. The path may not contain single hop loops like:
+ [s1, s2, s1].
+ This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+ 501 if vnf / intfs do not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ if request.is_json:
+ path = request.json.get('path')
+ layer2 = request.json.get('layer2', True)
+ else:
+ path = None
+ layer2 = True
+
+ # search for real names
+ real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+ if type(real_names) is not tuple:
+ # something went wrong
+ return real_names
+
+ container_src, container_dst, interface_src, interface_dst = real_names
+
+ try:
+ cookie = self.api.manage.network_action_start(container_src, container_dst, vnf_src_interface=interface_src,
+ vnf_dst_interface=interface_dst, bidirectional=True,
+ path=path, layer2=layer2)
+ resp = {'cookie': cookie}
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+ return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+ def delete(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+ """
+ A DELETE request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+ will delete a previously created chain.
+
+ :param src_dc: Name of the source datacenter
+ :type src_dc: `str`
+ :param src_stack: Name of the source stack
+ :type src_stack: `str`
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_dc: Name of the destination datacenter
+ :type dst_dc: ``str``
+ :param dst_stack: Name of the destination stack
+ :type dst_stack: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500\
+ also returns the cookie as dict {'cookie': value}
+ 501 if one of the VNF / intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ # search for real names
+ real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+ if type(real_names) is not tuple:
+ # something went wrong, real_names is a Response object
+ return real_names
+
+ container_src, container_dst, interface_src, interface_dst = real_names
+
+ try:
+ cookie = self.api.manage.network_action_stop(container_src, container_dst, vnf_src_interface=interface_src,
+ vnf_dst_interface=interface_dst, bidirectional=True)
+ return Response(json.dumps(cookie), status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error deleting the chain.\n %s" % (__name__, e))
+ return Response(u"Error deleting the chain", status=500, mimetype="application/json")
+
+ # Tries to find real container and interface names according to heat template names
+ # Returns a tuple of 4 or a Response object
+ def _findNames(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+ # search for datacenters
+ if src_dc not in self.api.manage.net.dcs or dst_dc not in self.api.manage.net.dcs:
+ return Response(u"At least one DC does not exist", status=500, mimetype="application/json")
+ dc_src = self.api.manage.net.dcs[src_dc]
+ dc_dst = self.api.manage.net.dcs[dst_dc]
+ # search for related OpenStackAPIs
+ api_src = None
+ api_dst = None
+ from openstack_api_endpoint import OpenstackApiEndpoint
+ for api in OpenstackApiEndpoint.dc_apis:
+ if api.compute.dc == dc_src:
+ api_src = api
+ if api.compute.dc == dc_dst:
+ api_dst = api
+ if api_src is None or api_dst is None:
+ return Response(u"At least one OpenStackAPI does not exist", status=500, mimetype="application/json")
+ # search for stacks
+ stack_src = None
+ stack_dst = None
+ for stack in api_src.compute.stacks.values():
+ if stack.stack_name == src_stack:
+ stack_src = stack
+ for stack in api_dst.compute.stacks.values():
+ if stack.stack_name == dst_stack:
+ stack_dst = stack
+ if stack_src is None or stack_dst is None:
+ return Response(u"At least one Stack does not exist", status=500, mimetype="application/json")
+ # search for servers
+ server_src = None
+ server_dst = None
+ for server in stack_src.servers.values():
+ if server.template_name == src_vnf:
+ server_src = server
+ break
+ for server in stack_dst.servers.values():
+ if server.template_name == dst_vnf:
+ server_dst = server
+ break
+ if server_src is None or server_dst is None:
+ return Response(u"At least one VNF does not exist", status=500, mimetype="application/json")
+
+ container_src = server_src.name
+ container_dst = server_dst.name
+
+ # search for ports
+ port_src = None
+ port_dst = None
+ if src_intfs in server_src.port_names:
+ port_src = stack_src.ports[src_intfs]
+ if dst_intfs in server_dst.port_names:
+ port_dst = stack_dst.ports[dst_intfs]
+ if port_src is None or port_dst is None:
+ return Response(u"At least one Port does not exist", status=500, mimetype="application/json")
+
+ interface_src = port_src.intf_name
+ interface_dst = port_dst.intf_name
+
+ return container_src, container_dst, interface_src, interface_dst
+
+
+class BalanceHostDcStack(Resource):
+ """
+ Handles requests to "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>"
+ Sets up LoadBalancers for VNFs that are belonging to a certain stack.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def post(self, src_dc, src_stack, vnf_src_name, vnf_src_interface):
+ """
+ A POST request to "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>"
+ will set up a loadbalancer. The target VNFs and interfaces are in the post data.
+
+ :Example:
+ See :class:`heat.chain_api.BalanceHost.post`
+
+ :param src_dc: Name of the source VNF
+ :type src_dc: ``str``
+ :param src_stack: Name of the source VNF interface to chain on
+ :type src_stack: ``str``
+ * src_stack == "floating" sets up a new floating node, so only use this name if you know what you are doing.
+ :param vnf_src_name:
+ :type vnf_src_name: ``str``
+ :param vnf_src_interface:
+ :type vnf_src_interface: ``str``
+ :return: flask.Response 200 if set up correctly else 500
+ :rtype: :class:`flask.Response`
+
+ """
+ try:
+ req = request.json
+ if req is None or len(req) == 0 or "dst_vnf_interfaces" not in req:
+ return Response(u"You have to specify destination vnfs via the POST data.",
+ status=500, mimetype="application/json")
+
+ dst_vnfs = req.get('dst_vnf_interfaces')
+ container_src = None
+ interface_src = None
+
+ # check src vnf/port
+ if src_stack != "floating":
+ real_src = self._findName(src_dc, src_stack, vnf_src_name, vnf_src_interface)
+ if type(real_src) is not tuple:
+ # something went wrong, real_src is a Response object
+ return real_src
+
+ container_src, interface_src = real_src
+
+ real_dst_dict = {}
+ for dst_vnf in dst_vnfs:
+ dst_dc = dst_vnf.get('pop', None)
+ dst_stack = dst_vnf.get('stack', None)
+ dst_server = dst_vnf.get('server', None)
+ dst_port = dst_vnf.get('port', None)
+ if dst_dc is not None and dst_stack is not None and dst_server is not None and dst_port is not None:
+ real_dst = self._findName(dst_dc, dst_stack, dst_server, dst_port)
+ if type(real_dst) is not tuple:
+ # something went wrong, real_dst is a Response object
+ return real_dst
+ real_dst_dict[real_dst[0]] = real_dst[1]
+
+ input_object = {"dst_vnf_interfaces": real_dst_dict, "path": req.get("path", None)}
+
+ if src_stack != "floating":
+ self.api.manage.add_loadbalancer(container_src, interface_src, lb_data=input_object)
+ return Response(u"Loadbalancer set up at %s:%s" % (container_src, interface_src),
+ status=200, mimetype="application/json")
+ else:
+ cookie, floating_ip = self.api.manage.add_floating_lb(src_dc, lb_data=input_object)
+
+ return Response(json.dumps({"cookie": "%d" % cookie, "floating_ip": "%s" % floating_ip}),
+ status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the loadbalancer at %s %s %s:%s.\n %s" %
+ (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e))
+ return Response(u"%s: Error setting up the loadbalancer at %s %s %s:%s.\n %s" %
+ (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e), status=500,
+ mimetype="application/json")
+
+ def delete(self, src_dc, src_stack, vnf_src_name, vnf_src_interface):
+ """
+ Will delete a load balancer that sits behind a specified interface at a vnf for a specific stack
+
+ :param src_dc: Name of the source VNF
+ :type src_dc: ``str``
+ :param src_stack: Name of the source VNF interface to chain on
+ :type src_stack: ``str``
+ :param vnf_src_name:
+ :type vnf_src_name: ``str``
+ :param vnf_src_interface:
+ :type vnf_src_interface: ``str``
+ :return: flask.Response 200 if set up correctly else 500
+ :rtype: :class:`flask.Response`
+
+ """
+ try:
+ # check src vnf/port
+ if src_stack != "floating":
+ real_src = self._findName(src_dc, src_stack, vnf_src_name, vnf_src_interface)
+ if type(real_src) is not tuple:
+ # something went wrong, real_src is a Response object
+ return real_src
+
+ container_src, interface_src = real_src
+
+ self.api.manage.delete_loadbalancer(container_src, interface_src)
+ return Response(u"Loadbalancer deleted at %s:%s" % (vnf_src_name, vnf_src_interface),
+ status=200, mimetype="application/json")
+ else:
+ cookie = vnf_src_name
+ self.api.manage.delete_floating_lb(cookie)
+ return Response(u"Floating loadbalancer with cookie %s deleted" % (cookie),
+ status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error deleting the loadbalancer at %s %s %s%s.\n %s" %
+ (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e))
+ return Response(u"%s: Error deleting the loadbalancer at %s %s %s%s." %
+ (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface), status=500,
+ mimetype="application/json")
+
+ # Tries to find real container and port name according to heat template names
+ # Returns a string or a Response object
+ def _findName(self, dc, stack, vnf, port):
+ # search for datacenters
+ if dc not in self.api.manage.net.dcs:
+ return Response(u"DC does not exist", status=500, mimetype="application/json")
+ dc_real = self.api.manage.net.dcs[dc]
+ # search for related OpenStackAPIs
+ api_real = None
+ from openstack_api_endpoint import OpenstackApiEndpoint
+ for api in OpenstackApiEndpoint.dc_apis:
+ if api.compute.dc == dc_real:
+ api_real = api
+ if api_real is None:
+ return Response(u"OpenStackAPI does not exist", status=500, mimetype="application/json")
+ # search for stacks
+ stack_real = None
+ for stackObj in api_real.compute.stacks.values():
+ if stackObj.stack_name == stack:
+ stack_real = stackObj
+ if stack_real is None:
+ return Response(u"Stack does not exist", status=500, mimetype="application/json")
+ # search for servers
+ server_real = None
+ for server in stack_real.servers.values():
+ if server.template_name == vnf:
+ server_real = server
+ break
+ if server_real is None:
+ return Response(u"VNF does not exist", status=500, mimetype="application/json")
+
+ container_real = server_real.name
+
+ # search for ports
+ port_real = None
+ if port in server_real.port_names:
+ port_real = stack_real.ports[port]
+ if port_real is None:
+ return Response(u"At least one Port does not exist", status=500, mimetype="application/json")
+
+ interface_real = port_real.intf_name
+
+ return container_real, interface_real
+
+
+class BalanceHost(Resource):
+ """
+ Handles requests at "/v1/lb/<vnf_src_name>/<vnf_src_interface>"
+ to set up or delete Load Balancers.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def post(self, vnf_src_name, vnf_src_interface):
+ """
+ Will set up a Load balancer behind an interface at a specified vnf
+ We need both to avoid naming conflicts as interface names are not unique
+
+ :param vnf_src_name: Name of the source VNF
+ :type vnf_src_name: ``str``
+ :param vnf_src_interface: Name of the source VNF interface to chain on
+ :type vnf_src_interface: ``str``
+ :return: flask.Response 200 if set up correctly else 500
+ 501 if VNF or intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ try:
+ req = request.json
+ if req is None or len(req) == 0 or "dst_vnf_interfaces" not in req:
+ return Response(u"You have to specify destination vnfs via the POST data.",
+ status=500, mimetype="application/json")
+
+ if vnf_src_name != "floating":
+ # check if VNF exist
+ if not self.api.manage.check_vnf_intf_pair(vnf_src_name, vnf_src_interface):
+ return Response(u"VNF %s or intfs %s does not exist" % (vnf_src_name, vnf_src_interface),
+ status=501,
+ mimetype="application/json")
+ self.api.manage.add_loadbalancer(vnf_src_name, vnf_src_interface, lb_data=req)
+
+ return Response(u"Loadbalancer set up at %s:%s" % (vnf_src_name, vnf_src_interface),
+ status=200, mimetype="application/json")
+ else:
+ cookie, floating_ip = self.api.manage.add_floating_lb(vnf_src_interface, lb_data=req)
+
+ return Response(json.dumps({"cookie": "%d" % cookie, "floating_ip": "%s" % floating_ip}),
+ status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the loadbalancer at %s:%s.\n %s" %
+ (__name__, vnf_src_name, vnf_src_interface, e))
+ return Response(u"%s: Error setting up the loadbalancer at %s:%s.\n %s" %
+ (__name__, vnf_src_name, vnf_src_interface, e), status=500, mimetype="application/json")
+
+ def delete(self, vnf_src_name, vnf_src_interface):
+ """
+ Will delete a load balancer that sits behind a specified interface at a vnf
+
+ :param vnf_src_name: Name of the source VNF
+ :type vnf_src_name: ``str``
+ :param vnf_src_interface: Name of the source VNF interface to chain on
+ :type vnf_src_interface: ``str``
+ :return: flask.Response 200 if set up correctly else 500
+ 501 if VNF or intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ # check if VNF exist
+ if not self.api.manage.check_vnf_intf_pair(vnf_src_name, vnf_src_interface):
+ return Response(u"VNF %s or intfs %s does not exist" % (vnf_src_name, vnf_src_interface), status=501,
+ mimetype="application/json")
+ try:
+ logging.debug("Deleting loadbalancer at %s: interface: %s" % (vnf_src_name, vnf_src_interface))
+ net = self.api.manage.net
+
+ if vnf_src_name != "floating":
+ # check if VNF exists
+ if vnf_src_name not in net:
+ return Response(u"Source VNF or interface can not be found." % vnf_src_name,
+ status=404, mimetype="application/json")
+
+ self.api.manage.delete_loadbalancer(vnf_src_name, vnf_src_interface)
+
+ return Response(u"Loadbalancer deleted at %s:%s" % (vnf_src_name, vnf_src_interface),
+ status=200, mimetype="application/json")
+ else:
+ cookie = vnf_src_name
+ self.api.manage.delete_floating_lb(cookie)
+ return Response(u"Floating loadbalancer with cookie %s removed" % (cookie),
+ status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error deleting the loadbalancer at %s%s.\n %s" %
+ (__name__, vnf_src_name, vnf_src_interface, e))
+ return Response(u"%s: Error deleting the loadbalancer at %s%s." %
+ (__name__, vnf_src_name, vnf_src_interface), status=500, mimetype="application/json")
+
+
+class QueryTopology(Resource):
+ """
+ Handles requests at "/v1/topo/"
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ Answers GET requests for the current network topology at "/v1/topo".
+ This will only return switches and datacenters and ignore currently deployed VNFs.
+
+ :return: 200 if successful with the network graph as json dict, else 500
+
+ """
+ try:
+ logging.debug("Querying topology")
+ graph = self.api.manage.net.DCNetwork_graph
+ net = self.api.manage.net
+ # root node is nodes
+ topology = {"nodes": list()}
+
+ for n in graph:
+ # remove root node as well as the floating switch fs1
+ if n != "root" and n != "fs1":
+ # we only want to return switches!
+ if not isinstance(net[n], OVSSwitch):
+ continue
+ node = dict()
+
+ # get real datacenter label
+ for dc in self.api.manage.net.dcs.values():
+ if str(dc.switch) == str(n):
+ node["name"] = str(n)
+ node["type"] = "Datacenter"
+ node["label"] = str(dc.label)
+ break
+
+ # node is not a datacenter. It has to be a switch
+ if node.get("type", "") != "Datacenter":
+ node["name"] = str(n)
+ node["type"] = "Switch"
+
+ node["links"] = list()
+ # add links to the topology
+ for graph_node, data in graph[n].items():
+ # only add links to the topology that connect switches
+ if isinstance(net[graph_node], OVSSwitch):
+ # we allow multiple edges between switches, so add them all
+ # with their unique keys
+ link = copy.copy(data)
+ for edge in link:
+ # do not add any links to the floating switch to the topology!
+ if graph_node == "fs1":
+ continue
+ # the translator wants everything as a string!
+ for key, value in link[edge].items():
+ link[edge][key] = str(value)
+ # name of the destination
+ link[edge]["name"] = graph_node
+ node["links"].append(link)
+
+ topology["nodes"].append(node)
+
+ return Response(json.dumps(topology),
+ status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error querying topology.\n %s" %
+ (__name__, e))
+ return Response(u"%s: Error querying topology.\n %s" %
+ (__name__, e), status=500, mimetype="application/json")