Implemented Openstack Neutron SFC API
Signed-off-by: Malte Splietker <malte.splietker@gmail.com>
diff --git a/src/emuvim/api/openstack/compute.py b/src/emuvim/api/openstack/compute.py
index fd9b1ac..6d4d864 100755
--- a/src/emuvim/api/openstack/compute.py
+++ b/src/emuvim/api/openstack/compute.py
@@ -1,4 +1,5 @@
from mininet.link import Link
+
from resources import *
from docker import DockerClient
import logging
@@ -41,6 +42,10 @@
self._images = dict()
self.nets = dict()
self.ports = dict()
+ self.port_pairs = dict()
+ self.port_pair_groups = dict()
+ self.flow_classifiers = dict()
+ self.port_chains = dict()
self.compute_nets = dict()
self.dcli = DockerClient(base_url='unix://var/run/docker.sock')
@@ -416,7 +421,8 @@
network.append(network_dict)
self.compute_nets[server.name] = network
c = self.dc.startCompute(server.name, image=server.image, command=server.command,
- network=network, flavor_name=server.flavor)
+ network=network, flavor_name=server.flavor,
+ properties=server.properties)
server.emulator_compute = c
for intf in c.intfs.values():
@@ -645,6 +651,214 @@
for stack in self.stacks.values():
stack.ports.pop(port.name, None)
+ def create_port_pair(self, name, stack_operation=False):
+ """
+ Creates a new port pair with the given name. Raises an exception when a port pair with the given name already
+ exists!
+
+ :param name: Name of the new port pair.
+ :type name: ``str``
+ :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
+ :type stack_operation: ``bool``
+ :return: Returns the created port pair.
+ :rtype: :class:`openstack.resources.port_pair`
+ """
+ port_pair = self.find_port_pair_by_name_or_id(name)
+ if port_pair is not None and not stack_operation:
+ logging.warning("Creating port pair with name %s failed, as it already exists" % name)
+ raise Exception("Port pair with name %s already exists." % name)
+ logging.debug("Creating port pair with name %s" % name)
+ port_pair = PortPair(name)
+ if not stack_operation:
+ self.port_pairs[port_pair.id] = port_pair
+ return port_pair
+
+ def find_port_pair_by_name_or_id(self, name_or_id):
+ """
+ Tries to find the port pair by ID and if this does not succeed then tries to find it via name.
+
+ :param name_or_id: UUID or name of the port pair.
+ :type name_or_id: ``str``
+ :return: Returns the port pair reference if it was found or None
+ :rtype: :class:`openstack.resources.port_pair`
+ """
+ if name_or_id in self.port_pairs:
+ return self.port_pairs[name_or_id]
+ for port_pair in self.port_pairs.values():
+ if port_pair.name == name_or_id:
+ return port_pair
+
+ return None
+
+ def delete_port_pair(self, name_or_id):
+ """
+ Deletes the given port pair. Raises an exception when the port pair was not found!
+
+ :param name_or_id: UUID or name of the port pair.
+ :type name_or_id: ``str``
+ """
+ port_pair = self.find_port_pair_by_name_or_id(name_or_id)
+ if port_pair is None:
+ raise Exception("Port pair with name or id %s does not exists." % name_or_id)
+
+ self.port_pairs.pop(port_pair.id, None)
+
+ def create_port_pair_group(self, name, stack_operation=False):
+ """
+ Creates a new port pair group with the given name. Raises an exception when a port pair group
+ with the given name already exists!
+
+ :param name: Name of the new port pair group.
+ :type name: ``str``
+ :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
+ :type stack_operation: ``bool``
+ :return: Returns the created port pair group .
+ :rtype: :class:`openstack.resources.port_pair_group`
+ """
+ port_pair_group = self.find_port_pair_group_by_name_or_id(name)
+ if port_pair_group is not None and not stack_operation:
+ logging.warning("Creating port pair group with name %s failed, as it already exists" % name)
+ raise Exception("Port pair group with name %s already exists." % name)
+ logging.debug("Creating port pair group with name %s" % name)
+ port_pair_group = PortPairGroup(name)
+ if not stack_operation:
+ self.port_pair_groups[port_pair_group.id] = port_pair_group
+ return port_pair_group
+
+ def find_port_pair_group_by_name_or_id(self, name_or_id):
+ """
+ Tries to find the port pair group by ID and if this does not succeed then tries to find it via name.
+
+ :param name_or_id: UUID or name of the port pair group.
+ :type name_or_id: ``str``
+ :return: Returns the port pair group reference if it was found or None
+ :rtype: :class:`openstack.resources.port_pair_group`
+ """
+ if name_or_id in self.port_pair_groups:
+ return self.port_pair_groups[name_or_id]
+ for port_pair_group in self.port_pair_groups.values():
+ if port_pair_group.name == name_or_id:
+ return port_pair_group
+
+ return None
+
+ def delete_port_pair_group(self, name_or_id):
+ """
+ Deletes the given port pair group. Raises an exception when the port pair group was not found!
+
+ :param name_or_id: UUID or name of the port pair group.
+ :type name_or_id: ``str``
+ """
+ port_pair_group = self.find_port_pair_group_by_name_or_id(name_or_id)
+ if port_pair_group is None:
+ raise Exception("Port pair with name or id %s does not exists." % name_or_id)
+
+ self.port_pair_groups.pop(port_pair_group.id, None)
+
+ def create_port_chain(self, name, stack_operation=False):
+ """
+ Creates a new port chain with the given name. Raises an exception when a port chain with the given name already
+ exists!
+
+ :param name: Name of the new port chain
+ :type name: ``str``
+ :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
+ :type stack_operation: ``bool``
+ :return: Returns the created port chain.
+ :rtype: :class:`openstack.resources.port_chain.PortChain`
+ """
+ port_chain = self.find_port_chain_by_name_or_id(name)
+ if port_chain is not None and not stack_operation:
+ logging.warning("Creating port chain with name %s failed, as it already exists" % name)
+ raise Exception("Port chain with name %s already exists." % name)
+ logging.debug("Creating port chain with name %s" % name)
+ port_chain = PortChain(name)
+ if not stack_operation:
+ self.port_chains[port_chain.id] = port_chain
+ return port_chain
+
+ def find_port_chain_by_name_or_id(self, name_or_id):
+ """
+ Tries to find the port chain by ID and if this does not succeed then tries to find it via name.
+
+ :param name_or_id: UUID or name of the port chain.
+ :type name_or_id: ``str``
+ :return: Returns the port chain reference if it was found or None
+ :rtype: :class:`openstack.resources.port_chain.PortChain`
+ """
+ if name_or_id in self.port_chains:
+ return self.port_chains[name_or_id]
+ for port_chain in self.port_chains.values():
+ if port_chain.name == name_or_id:
+ return port_chain
+ return None
+
+ def delete_port_chain(self, name_or_id):
+ """
+ Deletes the given port chain. Raises an exception when the port chain was not found!
+
+ :param name_or_id: UUID or name of the port chain.
+ :type name_or_id: ``str``
+ """
+ port_chain = self.find_port_chain_by_name_or_id(name_or_id)
+ port_chain.uninstall(self)
+ if port_chain is None:
+ raise Exception("Port chain with name or id %s does not exists." % name_or_id)
+
+ self.port_chains.pop(port_chain.id, None)
+
+ def create_flow_classifier(self, name, stack_operation=False):
+ """
+ Creates a new flow classifier with the given name. Raises an exception when a flow classifier with the given name already
+ exists!
+
+ :param name: Name of the new flow classifier.
+ :type name: ``str``
+ :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
+ :type stack_operation: ``bool``
+ :return: Returns the created flow classifier.
+ :rtype: :class:`openstack.resources.flow_classifier`
+ """
+ flow_classifier = self.find_flow_classifier_by_name_or_id(name)
+ if flow_classifier is not None and not stack_operation:
+ logging.warning("Creating flow classifier with name %s failed, as it already exists" % name)
+ raise Exception("Flow classifier with name %s already exists." % name)
+ logging.debug("Creating flow classifier with name %s" % name)
+ flow_classifier = FlowClassifier(name)
+ if not stack_operation:
+ self.flow_classifiers[flow_classifier.id] = flow_classifier
+ return flow_classifier
+
+ def find_flow_classifier_by_name_or_id(self, name_or_id):
+ """
+ Tries to find the flow classifier by ID and if this does not succeed then tries to find it via name.
+
+ :param name_or_id: UUID or name of the flow classifier.
+ :type name_or_id: ``str``
+ :return: Returns the flow classifier reference if it was found or None
+ :rtype: :class:`openstack.resources.flow_classifier`
+ """
+ if name_or_id in self.flow_classifiers:
+ return self.flow_classifiers[name_or_id]
+ for flow_classifier in self.flow_classifiers.values():
+ if flow_classifier.name == name_or_id:
+ return flow_classifier
+
+ return None
+
+ def delete_flow_classifier(self, name_or_id):
+ """
+ Deletes the given flow classifier. Raises an exception when the flow classifier was not found!
+
+ :param name_or_id: UUID or name of the flow classifier.
+ :type name_or_id: ``str``
+ """
+ flow_classifier = self.find_flow_classifier_by_name_or_id(name_or_id)
+ if flow_classifier is None:
+ raise Exception("Flow classifier with name or id %s does not exists." % name_or_id)
+
+ self.flow_classifiers.pop(flow_classifier.id, None)
+
def _add_link(self, node_name, ip_address, link_name, net_name):
"""
Adds a new link between datacenter switch and the node with the given name.
diff --git a/src/emuvim/api/openstack/openstack_dummies/NeutronSFC.md b/src/emuvim/api/openstack/openstack_dummies/NeutronSFC.md
new file mode 100644
index 0000000..4acd322
--- /dev/null
+++ b/src/emuvim/api/openstack/openstack_dummies/NeutronSFC.md
@@ -0,0 +1,92 @@
+A complete description of the Service Function Chaining API can be found under
+https://docs.openstack.org/developer/networking-sfc/api.html
+
+### Working Example
+This section describes a complete, working example of the SFC API with Sonata.
+The following diagram shows the structure of the service chain that will be created.
+
+```
++-------+ +------+ +------+ +-------+
+| Host1 | | VNF1 | | VNF2 | | Host2 |
++-------+ +------+ +------+ +-------+
+ |p1 p2| |p3 p4| |p5 p6|
+ | | | | | | PC1 = {{p1, p2}, {p3, p4}, {p5, p6}}
+ +------>----+ +---->----+ +---->-----+
+ | |
+ ^ v PC2 = {{p6, p1}}
+ +-------------------<------------------+
+```
+Two port chains, PC1 and PC2, are created. PC1 chains packets from Host1 over VNF1 and VNF2 to Host2.
+Both network functions, VNF1 and VNF2, simply forward all packets from ingress to egress. PC2 creates a
+direct chain from Host2 to Host1, such that replies can be routed send back.
+(Note: Port chains are unidirectional)
+
+#### Prerequisites
+The following python packages required in order for the Openstack CLI commands to work.
+```
+sudo pip install python-openstackclient networking-sfc
+```
+Also the docker images `ubuntu:trusty` and `sonatanfv/sonata-snort-ids-vnf` have to be locally available
+(otherwise Sonata cannot start the containers).
+
+#### Execution
+First, start the DCEmulator:
+```
+sudo python src/emuvim/examples/openstack_single_dc.py
+```
+It creates a single data center and connects it to an instance of the Openstack API listening on `http://0.0.0.0:6001`.
+
+Then execute the following script. It starts the containers and sets up the port chains as described above.
+Finally a ping from Host1 to Host2 is executed to check the connection established by the port chains.
+
+```
+export OS_USERNAME="admin"
+export OS_PASSWORD="nope"
+export OS_PROJECT_NAME="nope"
+export OS_AUTH_URL="http://0.0.0.0:6001"
+
+# 1. Create ports
+openstack port create --network default p1
+openstack port create --network default p2
+openstack port create --network default p3
+openstack port create --network default p4
+openstack port create --network default p5
+openstack port create --network default p6
+
+# 2. Start servers
+openstack server create --image ubuntu:trusty --flavor m1.tiny --port p1 Host1
+openstack server create --image sonatanfv/sonata-snort-ids-vnf --flavor m1.tiny \
+ --port p2 --port p3 --property IFIN="p2-0" --property IFOUT="p3-0" VNF1
+openstack server create --image sonatanfv/sonata-snort-ids-vnf --flavor m1.tiny \
+ --port p4 --port p5 --property IFIN="p4-0" --property IFOUT="p5-0" VNF2
+openstack server create --image ubuntu:trusty --flavor m1.tiny --port p6 Host2
+
+# 3. Create port pairs
+openstack sfc port pair create --ingress p1 --egress p2 PP1
+openstack sfc port pair create --ingress p3 --egress p4 PP2
+openstack sfc port pair create --ingress p5 --egress p6 PP3
+openstack sfc port pair create --ingress p6 --egress p1 PP4 # for direct ping reply Host2->Host1
+
+# 4. Create port groups
+openstack sfc port pair group create --port-pair PP1 --port-pair PP2 --port-pair PP3 PPG1
+openstack sfc port pair group create --port-pair PP4 PPG2
+
+# 5. Create port chain
+openstack sfc port chain create --port-pair-group PPG1 PC1
+openstack sfc port chain create --port-pair-group PPG2 PC2
+
+# 6. Test the port chain
+export HOST1_DOCKER=$(openstack server list | grep "Host1" | awk '{print "mn."$4}')
+export HOST2_IP=$(openstack port show p6 | grep fixed_ips \
+ | awk 'match($4, "\x27,") {print substr($4, 13, RSTART - 13)}')
+sudo docker exec -it ${HOST1_DOCKER} ping -c 5 ${HOST2_IP}
+```
+
+To verify that the port chains actually works, commenting out the creation of either port chain (step 5)
+will result in the ping packets not getting through.
+
+### Unimplemented Features
+While all functions of the API are implemented and can be called, some of the internal functionality
+remains unimplemented at the moment:
+* Updating/deleting port chains (the metadata is updated, but the implemented chain is not)
+* FlowClassifiers (can be called, metadata is handles but not actually implemented with the chain)
diff --git a/src/emuvim/api/openstack/openstack_dummies/glance_dummy_api.py b/src/emuvim/api/openstack/openstack_dummies/glance_dummy_api.py
index 7819fb9..d9ca091 100755
--- a/src/emuvim/api/openstack/openstack_dummies/glance_dummy_api.py
+++ b/src/emuvim/api/openstack/openstack_dummies/glance_dummy_api.py
@@ -30,6 +30,10 @@
"/v1/images/<id>",
"/v2/images/<id>",
resource_class_kwargs={'api': self})
+ self.api.add_resource(GlanceImageByDockerNameApi,
+ "/v1/images/<owner>/<container>",
+ "/v2/images/<owner>/<container>",
+ resource_class_kwargs={'api': self})
def _start_flask(self):
LOG.info("Starting %s endpoint @ http://%s:%d" % ("GlanceDummyApi", self.ip, self.port))
@@ -112,7 +116,7 @@
f['virtual_size'] = 1
f['marker'] = None
resp['images'].append(f)
- c+=1
+ c += 1
if c > limit: # ugly hack to stop buggy glance client to do infinite requests
break
if "marker" in request.args: # ugly hack to fix pageination of openstack client
@@ -145,7 +149,7 @@
img_is_public = True if "public" in body_data.get("visibility") else False
img_container_format = body_data.get("container_format")
# try to find ID of already existing image (matched by name)
- img_id=None
+ img_id = None
for image in self.api.compute.images.values():
if str(img_name) in image.name:
img_id = image.id
@@ -187,9 +191,22 @@
def get(self, id):
LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
- from emuvim.api.heat.openstack_dummies.nova_dummy_api import NovaListImages
- nova = NovaListImages(self.api)
- return nova.get(id)
+ try:
+ resp = dict()
+ for image in self.api.compute.images.values():
+ if image.id == id or image.name == id:
+ resp['id'] = image.id
+ resp['name'] = image.name
+
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ response = Response("Image with id or name %s does not exists." % id, status=404)
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ LOG.exception(u"%s: Could not retrieve image with id %s." % (__name__, id))
+ return Response(ex.message, status=500, mimetype='application/json')
def put(self, id):
LOG.debug("API CALL: %s " % str(self.__class__.__name__))
@@ -197,3 +214,25 @@
return None
+class GlanceImageByDockerNameApi(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, owner, container):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ name = "%s/%s" % (owner, container)
+ if name in self.api.compute.images:
+ image = self.api.compute.images[name]
+ resp = dict()
+ resp['id'] = image.id
+ resp['name'] = image.name
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ response = Response("Image with id or name %s does not exists." % id, status=404)
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve image with id %s." % (__name__, id))
+ return Response(ex.message, status=500, mimetype='application/json')
diff --git a/src/emuvim/api/openstack/openstack_dummies/neutron_dummy_api.py b/src/emuvim/api/openstack/openstack_dummies/neutron_dummy_api.py
index ca1ff3d..9835bcd 100755
--- a/src/emuvim/api/openstack/openstack_dummies/neutron_dummy_api.py
+++ b/src/emuvim/api/openstack/openstack_dummies/neutron_dummy_api.py
@@ -2,12 +2,12 @@
from flask import request, Response
from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy
from datetime import datetime
+import neutron_sfc_dummy_api as SFC
import logging
import json
import uuid
import copy
-
LOG = logging.getLogger("api.openstack.neutron")
@@ -52,6 +52,63 @@
self.api.add_resource(NeutronAddFloatingIp, "/v2.0/floatingips.json", "/v2.0/floatingips",
resource_class_kwargs={'api': self})
+ # Service Function Chaining (SFC) API
+ self.api.add_resource(SFC.PortPairsCreate, "/v2.0/sfc/port_pairs.json", "/v2.0/sfc/port_pairs",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortPairsUpdate, "/v2.0/sfc/port_pairs/<pair_id>.json",
+ "/v2.0/sfc/port_pairs/<pair_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortPairsDelete, "/v2.0/sfc/port_pairs/<pair_id>.json",
+ "/v2.0/sfc/port_pairs/<pair_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortPairsList, "/v2.0/sfc/port_pairs.json", "/v2.0/sfc/port_pairs",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortPairsShow, "/v2.0/sfc/port_pairs/<pair_id>.json",
+ "/v2.0/sfc/port_pairs/<pair_id>",
+ resource_class_kwargs={'api': self})
+
+ self.api.add_resource(SFC.PortPairGroupCreate, "/v2.0/sfc/port_pair_groups.json", "/v2.0/sfc/port_pair_groups",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortPairGroupUpdate, "/v2.0/sfc/port_pair_groups/<group_id>.json",
+ "/v2.0/sfc/port_pair_groups/<group_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortPairGroupDelete, "/v2.0/sfc/port_pair_groups/<group_id>.json",
+ "/v2.0/sfc/port_pair_groups/<group_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortPairGroupList, "/v2.0/sfc/port_pair_groups.json", "/v2.0/sfc/port_pair_groups",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortPairGroupShow, "/v2.0/sfc/port_pair_groups/<group_id>.json",
+ "/v2.0/sfc/port_pair_groups/<group_id>",
+ resource_class_kwargs={'api': self})
+
+ self.api.add_resource(SFC.FlowClassifierCreate, "/v2.0/sfc/flow_classifiers.json", "/v2.0/sfc/flow_classifiers",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.FlowClassifierUpdate, "/v2.0/sfc/flow_classifiers/<flow_classifier_id>.json",
+ "/v2.0/sfc/flow_classifiers/<flow_classifier_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.FlowClassifierDelete, "/v2.0/sfc/flow_classifiers/<flow_classifier_id>.json",
+ "/v2.0/sfc/flow_classifiers/<flow_classifier_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.FlowClassifierList, "/v2.0/sfc/flow_classifiers.json", "/v2.0/sfc/flow_classifiers",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.FlowClassifierShow, "/v2.0/sfc/flow_classifiers/<flow_classifier_id>.json",
+ "/v2.0/sfc/flow_classifiers/<flow_classifier_id>",
+ resource_class_kwargs={'api': self})
+
+ self.api.add_resource(SFC.PortChainCreate, "/v2.0/sfc/port_chains.json", "/v2.0/sfc/port_chains",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortChainUpdate, "/v2.0/sfc/port_chains/<chain_id>.json",
+ "/v2.0/sfc/port_chains/<chain_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortChainDelete, "/v2.0/sfc/port_chains/<chain_id>.json",
+ "/v2.0/sfc/port_chains/<chain_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortChainList, "/v2.0/sfc/port_chains.json", "/v2.0/sfc/port_chains",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(SFC.PortChainShow, "/v2.0/sfc/port_chains/<chain_id>.json",
+ "/v2.0/sfc/port_chains/<chain_id>",
+ resource_class_kwargs={'api': self})
+
def _start_flask(self):
LOG.info("Starting %s endpoint @ http://%s:%d" % (__name__, self.ip, self.port))
if self.app is not None:
@@ -848,7 +905,7 @@
resp["floatingips"] = list()
# create a list of floting IP definitions and return it
for i in range(100, 110):
- ip=dict()
+ ip = dict()
ip["router_id"] = "router_id"
ip["description"] = "hardcoded in api"
ip["created_at"] = "router_id"
@@ -865,7 +922,6 @@
resp["floatingips"].append(ip)
return Response(json.dumps(resp), status=200, mimetype='application/json')
-
def post(self):
"""
Adds a floating IP to neutron.
diff --git a/src/emuvim/api/openstack/openstack_dummies/neutron_sfc_dummy_api.py b/src/emuvim/api/openstack/openstack_dummies/neutron_sfc_dummy_api.py
new file mode 100644
index 0000000..93121c3
--- /dev/null
+++ b/src/emuvim/api/openstack/openstack_dummies/neutron_sfc_dummy_api.py
@@ -0,0 +1,412 @@
+from flask_restful import Resource
+from flask import request, Response
+import logging
+import json
+import uuid
+
+from emuvim.api.openstack.resources.port_chain import PortChain
+
+
+class SFC(Resource):
+ def __init__(self, api):
+ self.api = api
+
+
+###############################################################################
+# Port Pair
+###############################################################################
+
+class PortPairsCreate(SFC):
+ def post(self):
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+
+ try:
+ request_dict = json.loads(request.data).get("port_pair")
+ name = request_dict["name"]
+
+ ingress_port = self.api.compute.find_port_by_name_or_id(request_dict["ingress"])
+ egress_port = self.api.compute.find_port_by_name_or_id(request_dict["egress"])
+
+ port_pair = self.api.compute.create_port_pair(name)
+ port_pair.ingress = ingress_port
+ port_pair.egress = egress_port
+ if "description" in request_dict:
+ port_pair.description = request_dict["description"]
+ if "service_function_parameters" in request_dict:
+ port_pair.service_function_parameters = request_dict["service_function_parameters"]
+
+ resp = {
+ "port_pair": port_pair.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=201, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortPairsUpdate(SFC):
+ def put(self, pair_id):
+ logging.debug("API CALL: %s PUT" % str(self.__class__.__name__))
+
+ try:
+ request_dict = json.loads(request.data).get("port_pair")
+ port_pair = self.api.compute.find_port_pair_by_name_or_id(pair_id)
+ if "name" in request_dict:
+ port_pair.name = request_dict["name"]
+ if "description" in request_dict:
+ port_pair.description = request_dict["description"]
+
+ resp = {
+ "port_pair": port_pair.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortPairsDelete(SFC):
+ def delete(self, pair_id):
+ logging.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
+ try:
+ self.api.compute.delete_port_pair(pair_id)
+
+ return Response("Port pair %s deleted.\n" % pair_id, status=204, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortPairsList(SFC):
+ def get(self):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ port_pair_list = []
+ for port_pair in self.api.compute.port_pairs.values():
+ port_pair_list.append(port_pair.create_dict(self.api.compute))
+ resp = {"port_pairs": port_pair_list}
+
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortPairsShow(SFC):
+ def get(self, pair_id):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ try:
+ port_pair = self.api.compute.find_port_pair_by_name_or_id(pair_id)
+ resp = {
+ "port_pair": port_pair.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+###############################################################################
+# Port Pair Group
+###############################################################################
+
+class PortPairGroupCreate(SFC):
+ def post(self):
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+
+ try:
+ request_dict = json.loads(request.data).get("port_pair_group")
+
+ port_pair_group = self.api.compute.create_port_pair_group(request_dict["name"])
+ port_pair_group.port_pairs = request_dict["port_pairs"]
+ if "description" in request_dict:
+ port_pair_group.description = request_dict["description"]
+ if "port_pair_group_parameters" in request_dict:
+ port_pair_group.port_pair_group_parameters = request_dict["port_pair_group_parameters"]
+
+ resp = {
+ "port_pair_group": port_pair_group.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=201, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortPairGroupUpdate(SFC):
+ def put(self, group_id):
+ logging.debug("API CALL: %s PUT" % str(self.__class__.__name__))
+
+ try:
+ request_dict = json.loads(request.data).get("port_pair_group")
+ port_pair_group = self.api.compute.find_port_pair_group_by_name_or_id(group_id)
+ if "name" in request_dict:
+ port_pair_group.name = request_dict["name"]
+ if "description" in request_dict:
+ port_pair_group.description = request_dict["description"]
+ if "port_pairs" in request_dict:
+ port_pair_group.port_pairs = request_dict["port_pairs"]
+
+ resp = {
+ "port_pair_group": port_pair_group.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortPairGroupDelete(SFC):
+ def delete(self, group_id):
+ logging.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
+ try:
+ self.api.compute.delete_port_pair_group(group_id)
+
+ return Response("Port pair group %s deleted.\n" % group_id, status=204, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortPairGroupList(SFC):
+ def get(self):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ port_pair_group_list = []
+ for port_pair_group in self.api.compute.port_pair_groups.values():
+ port_pair_group_list.append(port_pair_group.create_dict(self.api.compute))
+ resp = {"port_pair_groups": port_pair_group_list}
+
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortPairGroupShow(SFC):
+ def get(self, group_id):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ try:
+ port_pair_group = self.api.compute.find_port_pair_group_by_name_or_id(group_id)
+ resp = {
+ "port_pair_group": port_pair_group.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+###############################################################################
+# Flow Classifier
+###############################################################################
+
+class FlowClassifierCreate(SFC):
+ def post(self):
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+
+ try:
+ request_dict = json.loads(request.data).get("flow_classifier")
+
+ flow_classifier = self.api.compute.create_flow_classifier(request_dict["name"])
+ if "description" in request_dict:
+ flow_classifier.description = request_dict["description"]
+ if "ethertype" in request_dict:
+ flow_classifier.ethertype = request_dict["ethertype"]
+ if "protocol" in request_dict:
+ flow_classifier.protocol = request_dict["protocol"]
+ if "source_port_range_min" in request_dict:
+ flow_classifier.source_port_range_min = request_dict["source_port_range_min"]
+ if "source_port_range_max" in request_dict:
+ flow_classifier.source_port_range_max = request_dict["source_port_range_max"]
+ if "destination_port_range_min" in request_dict:
+ flow_classifier.destination_port_range_min = request_dict["destination_port_range_min"]
+ if "destination_port_range_max" in request_dict:
+ flow_classifier.destination_port_range_max = request_dict["destination_port_range_max"]
+ if "source_ip_prefix" in request_dict:
+ flow_classifier.source_ip_prefix = request_dict["source_ip_prefix"]
+ if "destination_ip_prefix" in request_dict:
+ flow_classifier.destination_ip_prefix = request_dict["destination_ip_prefix"]
+ if "logical_source_port" in request_dict:
+ flow_classifier.logical_source_port = request_dict["logical_source_port"]
+ if "logical_destination_port" in request_dict:
+ flow_classifier.logical_destination_port = request_dict["logical_destination_port"]
+ if "l7_parameters" in request_dict:
+ flow_classifier.l7_parameters = request_dict["l7_parameters"]
+
+ resp = {
+ "flow_classifier": flow_classifier.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=201, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class FlowClassifierUpdate(SFC):
+ def put(self, flow_classifier_id):
+ logging.debug("API CALL: %s PUT" % str(self.__class__.__name__))
+
+ try:
+ request_dict = json.loads(request.data).get("flow_classifier")
+ flow_classifier = self.api.compute.find_flow_classifier_by_name_or_id(flow_classifier_id)
+ if "name" in request_dict:
+ flow_classifier.name = request_dict["name"]
+ if "description" in request_dict:
+ flow_classifier.description = request_dict["description"]
+
+ resp = {
+ "flow_classifier": flow_classifier.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class FlowClassifierDelete(SFC):
+ def delete(self, flow_classifier_id):
+ logging.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
+ try:
+ self.api.compute.delete_flow_classifier(flow_classifier_id)
+
+ return Response("Port pair group %s deleted.\n" % flow_classifier_id, status=204,
+ mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class FlowClassifierList(SFC):
+ def get(self):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ flow_classifier_list = []
+ for flow_classifier in self.api.compute.flow_classifiers.values():
+ flow_classifier_list.append(flow_classifier.create_dict(self.api.compute))
+ resp = {"flow_classifiers": flow_classifier_list}
+
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class FlowClassifierShow(SFC):
+ def get(self, flow_classifier_id):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ try:
+ flow_classifier = self.api.compute.find_flow_classifier_by_name_or_id(flow_classifier_id)
+ resp = {
+ "flow_classifier": flow_classifier.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+###############################################################################
+# Port Chain
+###############################################################################
+
+class PortChainCreate(SFC):
+ def post(self):
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+
+ try:
+ request_dict = json.loads(request.data).get("port_chain")
+
+ port_chain = self.api.compute.create_port_chain(request_dict["name"])
+ port_chain.port_pair_groups = request_dict["port_pair_groups"]
+ if "description" in request_dict:
+ port_chain.description = request_dict["description"]
+ if "flow_classifiers" in request_dict:
+ port_chain.flow_classifiers = request_dict["flow_classifiers"]
+ if "chain_parameters" in request_dict:
+ port_chain.chain_parameters = request_dict["chain_parameters"]
+
+ port_chain.install(self.api.compute)
+
+ resp = {
+ "port_chain": port_chain.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=201, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortChainUpdate(SFC):
+ def put(self, chain_id):
+ logging.debug("API CALL: %s PUT" % str(self.__class__.__name__))
+ request_dict = json.loads(request.data).get("port_chain")
+
+ port_chain = self.api.compute.find_port_chain_by_name_or_id(chain_id)
+ if "name" in request_dict:
+ port_chain.name = request_dict["name"]
+ if "description" in request_dict:
+ port_chain.description = request_dict["description"]
+ if "flow_classfiers" in request_dict:
+ # TODO: update chain implementation
+ port_chain.description = request_dict["flow_classifiers"]
+ if "no_flow_classfiers" in request_dict:
+ port_chain.description = []
+ if "port_pair_groups" in request_dict:
+ # TODO: update chain implementation
+ port_chain.port_pair_groups = request_dict["port_pair_groups"]
+
+ port_chain.update()
+ try:
+ resp = {
+ "port_chain": port_chain.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortChainDelete(SFC):
+ def delete(self, chain_id):
+ logging.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
+
+ self.api.compute.delete_port_chain(chain_id)
+ try:
+ return Response("Port chain %s deleted.\n" % chain_id, status=204, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortChainList(SFC):
+ def get(self):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ port_chain_list = []
+ for port_chain in self.api.compute.port_chains.values():
+ port_chain_list.append(port_chain.create_dict(self.api.compute))
+ resp = {"port_chains": port_chain_list}
+
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class PortChainShow(SFC):
+ def get(self, chain_id):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ try:
+ port_chain = self.api.compute.find_port_chain_by_name_or_id(chain_id)
+ resp = {
+ "port_chain": port_chain.create_dict(self.api.compute)
+ }
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron SFC: %s Exception." % str(self.__class__.__name__))
+ return Response(ex.message, status=500, mimetype='application/json')
diff --git a/src/emuvim/api/openstack/openstack_dummies/nova_dummy_api.py b/src/emuvim/api/openstack/openstack_dummies/nova_dummy_api.py
index f294617..1d5a9ac 100755
--- a/src/emuvim/api/openstack/openstack_dummies/nova_dummy_api.py
+++ b/src/emuvim/api/openstack/openstack_dummies/nova_dummy_api.py
@@ -226,6 +226,8 @@
server = self.api.compute.create_server(name)
server.full_name = str(self.api.compute.dc.label) + "_man_" + server_dict["name"]
server.template_name = server_dict["name"]
+ if "metadata" in server_dict:
+ server.properties = server_dict["metadata"]
for flavor in self.api.compute.flavors.values():
if flavor.id == server_dict.get('flavorRef', ''):
diff --git a/src/emuvim/api/openstack/resources/__init__.py b/src/emuvim/api/openstack/resources/__init__.py
index ffddf54..fca6c6c 100755
--- a/src/emuvim/api/openstack/resources/__init__.py
+++ b/src/emuvim/api/openstack/resources/__init__.py
@@ -2,6 +2,10 @@
from model import Model
from net import Net
from port import Port
+from port_pair import PortPair
+from port_pair_group import PortPairGroup
+from flow_classifier import FlowClassifier
+from port_chain import PortChain
from resource import Resource
from router import Router
from server import Server
diff --git a/src/emuvim/api/openstack/resources/flow_classifier.py b/src/emuvim/api/openstack/resources/flow_classifier.py
new file mode 100644
index 0000000..dff6638
--- /dev/null
+++ b/src/emuvim/api/openstack/resources/flow_classifier.py
@@ -0,0 +1,52 @@
+import uuid
+
+
+class FlowClassifier(object):
+ def __init__(self, name):
+ self.id = str(uuid.uuid4())
+ self.tenant_id = "abcdefghijklmnopqrstuvwxyz123456"
+ self.name = name
+ self.description = ""
+ self.ethertype = "IPv4"
+ self.protocol = None
+ self.source_port_range_min = 0
+ self.source_port_range_max = 0
+ self.destination_port_range_min = 0
+ self.destination_port_range_max = 0
+ self.source_ip_prefix = None
+ self.destination_ip_prefix = None
+ self.logical_source_port = ""
+ self.logical_destination_port = ""
+ self.l7_parameters = dict()
+
+ def create_dict(self, compute):
+ representation = {
+ "name": self.name,
+ "tenant_id": self.tenant_id,
+ "description": self.description,
+ "id": self.id,
+ }
+ if self.ethertype:
+ representation["ethertype"] = self.ethertype
+ if self.protocol:
+ representation["protocol"] = self.protocol
+ if self.source_port_range_min:
+ representation["source_port_range_min"] = self.source_port_range_min
+ if self.source_port_range_max:
+ representation["source_port_range_max"] = self.source_port_range_max
+ if self.destination_port_range_min:
+ representation["destination_port_range_min"] = self.destination_port_range_min
+ if self.destination_port_range_max:
+ representation["destination_port_range_max"] = self.destination_port_range_max
+ if self.source_ip_prefix:
+ representation["source_ip_prefix"] = self.source_ip_prefix
+ if self.destination_ip_prefix:
+ representation["destination_ip_prefix"] = self.destination_ip_prefix
+ if len(self.logical_source_port):
+ representation["logical_source_port"] = self.logical_source_port
+ if len(self.logical_destination_port):
+ representation["logical_destination_port"] = self.logical_destination_port
+ if len(self.l7_parameters.items()):
+ representation["l7_parameters"] = self.l7_parameters
+
+ return representation
diff --git a/src/emuvim/api/openstack/resources/port_chain.py b/src/emuvim/api/openstack/resources/port_chain.py
new file mode 100644
index 0000000..2e91159
--- /dev/null
+++ b/src/emuvim/api/openstack/resources/port_chain.py
@@ -0,0 +1,71 @@
+import random
+import uuid
+import logging
+
+
+class PortChain(object):
+ def __init__(self, name):
+ self.id = str(uuid.uuid4())
+ self.tenant_id = "abcdefghijklmnopqrstuvwxyz123456"
+ self.name = name
+ self.description = ""
+ self.port_pair_groups = list()
+ self.flow_classifiers = list()
+ self.chain_parameters = dict()
+
+ # Cookie for internal identification of installed flows (e.g. to delete them)
+ self.cookie = random.randint(1, 0xffffffff)
+
+ def create_dict(self, compute):
+ representation = {
+ "name": self.name,
+ "tenant_id": self.tenant_id,
+ "description": self.description,
+ "flow_classifiers": self.flow_classifiers,
+ "port_pair_groups": self.port_pair_groups,
+ "id": self.id
+ }
+ return representation
+
+ def install(self, compute):
+ for flow_classifier_id in self.flow_classifiers:
+ flow_classifier = compute.find_flow_classifier_by_name_or_id(flow_classifier_id)
+ if flow_classifier:
+ pass
+ # TODO: for every flow classifier create match and pass it to setChain
+
+ for group_id in self.port_pair_groups:
+ port_pair_group = compute.find_port_pair_group_by_name_or_id(group_id)
+ for port_pair_id in port_pair_group.port_pairs:
+ port_pair = compute.find_port_pair_by_name_or_id(port_pair_id)
+
+ server_ingress = None
+ server_egress = None
+ for server in compute.computeUnits.values():
+ if port_pair.ingress.name in server.port_names:
+ server_ingress = server
+ elif port_pair.egress.name in server.port_names:
+ server_egress = server
+
+ # TODO: Not sure, if this should throw an error
+ if not server_ingress:
+ logging.warn("Neutron SFC: ingress port %s not connected." % str(port_pair.ingress.name))
+ continue
+ if not server_egress:
+ logging.warn("Neutron SFC: egress port %s not connected." % str(port_pair.egress.name))
+ continue
+
+ compute.dc.net.setChain(
+ server_ingress.name, server_egress.name,
+ port_pair.ingress.intf_name, port_pair.egress.intf_name,
+ cmd="add-flow", cookie=self.cookie, priority=10, bidirectional=False,
+ monitor=False
+ )
+
+ def uninstall(self, compute):
+ # TODO: implement
+ logging.warn("Removing flows is currently not implemented.")
+
+ def update(self):
+ # TODO: implement
+ logging.warn("Updating flows is currently not implemented.")
diff --git a/src/emuvim/api/openstack/resources/port_pair.py b/src/emuvim/api/openstack/resources/port_pair.py
new file mode 100644
index 0000000..38b983e
--- /dev/null
+++ b/src/emuvim/api/openstack/resources/port_pair.py
@@ -0,0 +1,23 @@
+import uuid
+
+
+class PortPair(object):
+ def __init__(self, name):
+ self.id = str(uuid.uuid4())
+ self.tenant_id = "abcdefghijklmnopqrstuvwxyz123456"
+ self.name = name
+ self.description = ""
+ self.ingress = None
+ self.egress = None
+ self.service_function_parameters = dict()
+
+ def create_dict(self, compute):
+ representation = {
+ "name": self.name,
+ "tenant_id": self.tenant_id,
+ "description": self.description,
+ "ingress": self.ingress.id,
+ "egress": self.egress.id,
+ "id": self.id
+ }
+ return representation
diff --git a/src/emuvim/api/openstack/resources/port_pair_group.py b/src/emuvim/api/openstack/resources/port_pair_group.py
new file mode 100644
index 0000000..9514689
--- /dev/null
+++ b/src/emuvim/api/openstack/resources/port_pair_group.py
@@ -0,0 +1,22 @@
+import uuid
+
+
+class PortPairGroup(object):
+ def __init__(self, name):
+ self.id = str(uuid.uuid4())
+ self.tenant_id = "abcdefghijklmnopqrstuvwxyz123456"
+ self.name = name
+ self.description = ""
+ self.port_pairs = list()
+ self.port_pair_group_parameters = dict()
+
+ def create_dict(self, compute):
+ representation = {
+ "name": self.name,
+ "tenant_id": self.tenant_id,
+ "description": self.description,
+ "port_pairs": self.port_pairs,
+ "port_pair_group_parameters": self.port_pair_group_parameters,
+ "id": self.id
+ }
+ return representation
diff --git a/src/emuvim/api/openstack/resources/server.py b/src/emuvim/api/openstack/resources/server.py
index d20a2d4..654521a 100755
--- a/src/emuvim/api/openstack/resources/server.py
+++ b/src/emuvim/api/openstack/resources/server.py
@@ -7,6 +7,7 @@
self.image = image
self.command = command
self.port_names = list()
+ self.properties = dict()
self.flavor = flavor
self.son_emu_command = None
self.emulator_compute = None