from mininet.link import Link
+
from resources import *
from docker import DockerClient
import logging
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')
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():
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.
--- /dev/null
+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)
"/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))
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
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
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__))
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')
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")
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:
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"
resp["floatingips"].append(ip)
return Response(json.dumps(resp), status=200, mimetype='application/json')
-
def post(self):
"""
Adds a floating IP to neutron.
--- /dev/null
+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')
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', ''):
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
--- /dev/null
+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
--- /dev/null
+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.")
--- /dev/null
+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
--- /dev/null
+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
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
pass
- cmd = kwargs.get('cmd')
+ cmd = kwargs.get('cmd', 'add-flow')
if cmd == 'add-flow' or cmd == 'del-flows':
ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
if kwargs.get('bidirectional'):
def start(self):
pass
- def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", **params):
+ def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", properties=dict(), **params):
"""
Create a new container as compute resource and connect it to this
data center.
:param command: command (string)
:param network: networks list({"ip": "10.0.0.254/8"}, {"ip": "11.0.0.254/24"})
:param flavor_name: name of the flavor for this compute container
+ :param properties: dictionary of properties (key-value) that will be passed as environment variables
:return:
"""
assert name is not None
params['cpu_period'] = self.net.cpu_period
params['cpu_quota'] = self.net.cpu_period * float(cpu_percentage)
+ env = properties
+ properties['VNF_NAME'] = name
# create the container
d = self.net.addDocker(
"%s" % (name),
dcmd=command,
datacenter=self,
flavor_name=flavor_name,
- environment = {'VNF_NAME':name},
+ environment = env,
**params
)
--- /dev/null
+"""
+Copyright (c) 2015 SONATA-NFV
+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.
+
+Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
+nor the names of its contributors may be used to endorse or promote
+products derived from this software without specific prior written
+permission.
+
+This work has been performed in the framework of the SONATA project,
+funded by the European Commission under Grant number 671517 through
+the Horizon 2020 and 5G-PPP programmes. The authors would like to
+acknowledge the contributions of their colleagues of the SONATA
+partner consortium (www.sonata-nfv.eu).
+"""
+import logging
+from mininet.log import setLogLevel
+from emuvim.dcemulator.net import DCNetwork
+
+from emuvim.api.openstack.openstack_api_endpoint import OpenstackApiEndpoint
+
+logging.basicConfig(level=logging.INFO)
+
+
+def create_topology():
+ net = DCNetwork(monitor=False, enable_learning=False)
+
+ dc1 = net.addDatacenter("dc1")
+
+ api1 = OpenstackApiEndpoint("0.0.0.0", 6001)
+ api1.connect_datacenter(dc1)
+ api1.start()
+ api1.connect_dc_network(net)
+
+ net.start()
+ net.CLI()
+ # when the user types exit in the CLI, we stop the emulator
+ net.stop()
+
+
+def main():
+ setLogLevel('info') # set Mininet loglevel
+ create_topology()
+
+
+if __name__ == '__main__':
+ main()
self.assertEqual(getintfs.status_code, 202)
print(" ")
+ def testNeutronSFC(self):
+ """
+ Tests the Neutron Service Function Chaining implementation. As Some functions build up on others, a
+ complete environment is created here:
+
+ Ports: p1, p2, p3, p4
+ Port Pairs: pp1(p1, p2), pp2(p3, p4)
+ Port Pair Groups: ppg1(pp1, pp2)
+ Flow Classifiers: fc1
+ Port Chain: pc1(ppg1, fc1)
+ """
+
+ headers = {'Content-type': 'application/json'}
+
+ print('->>>>>>> Create ports p1 - p4 ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ # Get network id
+ network_resp = requests.get("http://0.0.0.0:19696/v2.0/networks?name=default", headers=headers)
+ self.assertEqual(network_resp.status_code, 200)
+ network_id = json.loads(network_resp.content)["networks"][0]["id"]
+
+ url = "http://0.0.0.0:19696/v2.0/ports"
+ port_request = '{"port": {"name": "%s", "network_id": "%s"}}'
+ p1_resp = requests.post(url, data=port_request % ("p1", network_id), headers=headers)
+ self.assertEqual(p1_resp.status_code, 201)
+ p2_resp = requests.post(url, data=port_request % ("p2", network_id), headers=headers)
+ self.assertEqual(p2_resp.status_code, 201)
+ p3_resp = requests.post(url, data=port_request % ("p3", network_id), headers=headers)
+ self.assertEqual(p3_resp.status_code, 201)
+ p4_resp = requests.post(url, data=port_request % ("p4", network_id), headers=headers)
+ self.assertEqual(p4_resp.status_code, 201)
+
+ p1_id = json.loads(p1_resp.content)["port"]["id"]
+ p2_id = json.loads(p2_resp.content)["port"]["id"]
+ p3_id = json.loads(p3_resp.content)["port"]["id"]
+ p4_id = json.loads(p4_resp.content)["port"]["id"]
+
+ print('->>>>>>> test Neutron SFC Port Pair Create ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pairs"
+ pp1_resp = requests.post(url, data='{"port_pair": {"name": "pp1", "ingress": "%s", "egress": "%s"}}' % (p1_id, p2_id), headers=headers)
+ self.assertEqual(pp1_resp.status_code, 201)
+ pp2_resp = requests.post(url, data='{"port_pair": {"name": "pp2", "ingress": "%s", "egress": "%s"}}' % (p3_id, p4_id), headers=headers)
+ self.assertEqual(pp2_resp.status_code, 201)
+ pp3_resp = requests.post(url, data='{"port_pair": {"name": "pp3", "ingress": "%s", "egress": "%s"}}' % (p3_id, p4_id), headers=headers)
+ self.assertEqual(pp3_resp.status_code, 201)
+
+ pp1_id = json.loads(pp1_resp.content)["port_pair"]["id"]
+ pp2_id = json.loads(pp2_resp.content)["port_pair"]["id"]
+ pp3_id = json.loads(pp3_resp.content)["port_pair"]["id"]
+
+ print('->>>>>>> test Neutron SFC Port Pair Update ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pairs/%s" % pp3_id
+ pp3_update_resp = requests.put(url, data='{"port_pair": {"description": "port_pair_update"}}', headers=headers)
+ self.assertEqual(pp3_update_resp.status_code, 200)
+ self.assertEqual(json.loads(pp3_update_resp.content)["port_pair"]["description"], "port_pair_update")
+
+ print('->>>>>>> test Neutron SFC Port Pair Delete ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pairs/%s" % pp3_id
+ pp3_delete_resp = requests.delete(url, headers=headers)
+ self.assertEqual(pp3_delete_resp.status_code, 204)
+
+ print('->>>>>>> test Neutron SFC Port Pair List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pairs"
+ pp_list_resp = requests.get(url, headers=headers)
+ self.assertEqual(pp_list_resp.status_code, 200)
+ self.assertEqual(len(json.loads(pp_list_resp.content)["port_pairs"]), 2) # only pp1 and pp2 should be left
+
+ print('->>>>>>> test Neutron SFC Port Pair Show ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pairs/%s" % pp2_id
+ pp2_show_resp = requests.get(url, headers=headers)
+ self.assertEqual(pp2_show_resp.status_code, 200)
+ self.assertEqual(json.loads(pp2_show_resp.content)["port_pair"]["name"], "pp2")
+
+
+ print('->>>>>>> test Neutron SFC Port Pair Group Create ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pair_groups"
+ ppg1_resp = requests.post(url, data='{"port_pair_group": {"name": "ppg1", "port_pairs": ["%s"]}}' % (pp1_id), headers=headers)
+ self.assertEqual(ppg1_resp.status_code, 201)
+ ppg2_resp = requests.post(url, data='{"port_pair_group": {"name": "ppg2", "port_pairs": ["%s"]}}' % (pp2_id), headers=headers)
+ self.assertEqual(ppg2_resp.status_code, 201)
+ ppg3_resp = requests.post(url, data='{"port_pair_group": {"name": "ppg3", "port_pairs": ["%s"]}}' % (pp2_id), headers=headers)
+ self.assertEqual(ppg3_resp.status_code, 201)
+
+ ppg1_id = json.loads(ppg1_resp.content)["port_pair_group"]["id"]
+ ppg2_id = json.loads(ppg2_resp.content)["port_pair_group"]["id"]
+ ppg3_id = json.loads(ppg3_resp.content)["port_pair_group"]["id"]
+
+ print('->>>>>>> test Neutron SFC Port Pair Group Update ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pair_groups/%s" % ppg3_id
+ ppg3_update_resp = requests.put(url, data='{"port_pair_group": {"description": "port_pair_group_update"}}', headers=headers)
+ self.assertEqual(ppg3_update_resp.status_code, 200)
+ self.assertEqual(json.loads(ppg3_update_resp.content)["port_pair_group"]["description"], "port_pair_group_update")
+
+ print('->>>>>>> test Neutron SFC Port Pair Group Delete ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pair_groups/%s" % ppg3_id
+ ppg3_delete_resp = requests.delete(url, headers=headers)
+ self.assertEqual(ppg3_delete_resp.status_code, 204)
+
+ print('->>>>>>> test Neutron SFC Port Pair Group List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pair_groups"
+ ppg_list_resp = requests.get(url, headers=headers)
+ self.assertEqual(ppg_list_resp.status_code, 200)
+ self.assertEqual(len(json.loads(ppg_list_resp.content)["port_pair_groups"]), 2) # only ppg1 and ppg2 should be left
+
+ print('->>>>>>> test Neutron SFC Port Pair Group Show ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_pair_groups/%s" % ppg2_id
+ ppg2_show_resp = requests.get(url, headers=headers)
+ self.assertEqual(ppg2_show_resp.status_code, 200)
+ self.assertEqual(json.loads(ppg2_show_resp.content)["port_pair_group"]["name"], "ppg2")
+
+
+ print('->>>>>>> test Neutron SFC Flow Classifier Create ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/flow_classifiers"
+ fc1_resp = requests.post(url, data='{"flow_classifier": {"name": "fc1", "source_port_range_min": 22, "source_port_range_max": 4000}}', headers=headers)
+ self.assertEqual(fc1_resp.status_code, 201)
+ fc2_resp = requests.post(url, data='{"flow_classifier": {"name": "fc2", "source_port_range_min": 22, "source_port_range_max": 4000}}', headers=headers)
+ self.assertEqual(fc2_resp.status_code, 201)
+
+ fc1_id = json.loads(fc1_resp.content)["flow_classifier"]["id"]
+ fc2_id = json.loads(fc2_resp.content)["flow_classifier"]["id"]
+
+ print('->>>>>>> test Neutron SFC Flow Classifier Update ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/flow_classifiers/%s" % fc2_id
+ fc2_update_resp = requests.put(url, data='{"flow_classifier": {"description": "flow_classifier_update"}}', headers=headers)
+ self.assertEqual(fc2_update_resp.status_code, 200)
+ self.assertEqual(json.loads(fc2_update_resp.content)["flow_classifier"]["description"], "flow_classifier_update")
+
+ print('->>>>>>> test Neutron SFC Flow Classifier Delete ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/flow_classifiers/%s" % fc2_id
+ fc2_delete_resp = requests.delete(url, headers=headers)
+ self.assertEqual(fc2_delete_resp.status_code, 204)
+
+ print('->>>>>>> test Neutron SFC Flow Classifier List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/flow_classifiers"
+ fc_list_resp = requests.get(url, headers=headers)
+ self.assertEqual(fc_list_resp.status_code, 200)
+ self.assertEqual(len(json.loads(fc_list_resp.content)["flow_classifiers"]), 1) # only fc1
+
+ print('->>>>>>> test Neutron SFC Flow Classifier Show ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/flow_classifiers/%s" % fc1_id
+ fc1_show_resp = requests.get(url, headers=headers)
+ self.assertEqual(fc1_show_resp.status_code, 200)
+ self.assertEqual(json.loads(fc1_show_resp.content)["flow_classifier"]["name"], "fc1")
+
+
+ print('->>>>>>> test Neutron SFC Port Chain Create ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_chains"
+ pc1_resp = requests.post(url, data='{"port_chain": {"name": "pc1", "port_pair_groups": ["%s"], "flow_classifiers": ["%s"]}}' % (ppg1_id, fc1_id), headers=headers)
+ self.assertEqual(pc1_resp.status_code, 201)
+ pc2_resp = requests.post(url, data='{"port_chain": {"name": "pc2", "port_pair_groups": ["%s"], "flow_classifiers": ["%s"]}}' % (ppg1_id, fc1_id), headers=headers)
+ self.assertEqual(pc2_resp.status_code, 201)
+
+ pc1_id = json.loads(pc1_resp.content)["port_chain"]["id"]
+ pc2_id = json.loads(pc2_resp.content)["port_chain"]["id"]
+
+ print('->>>>>>> test Neutron SFC Port Chain Update ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_chains/%s" % pc2_id
+ pc2_update_resp = requests.put(url, data='{"port_chain": {"description": "port_chain_update"}}', headers=headers)
+ self.assertEqual(pc2_update_resp.status_code, 200)
+ self.assertEqual(json.loads(pc2_update_resp.content)["port_chain"]["description"], "port_chain_update")
+
+ print('->>>>>>> test Neutron SFC Port Chain Delete ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_chains/%s" % pc2_id
+ pc2_delete_resp = requests.delete(url, headers=headers)
+ self.assertEqual(pc2_delete_resp.status_code, 204)
+
+ print('->>>>>>> test Neutron SFC Port Chain List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_chains"
+ pc_list_resp = requests.get(url, headers=headers)
+ self.assertEqual(pc_list_resp.status_code, 200)
+ self.assertEqual(len(json.loads(pc_list_resp.content)["port_chains"]), 1) # only pc1
+
+ print('->>>>>>> test Neutron SFC Port Chain Show ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/sfc/port_chains/%s" % pc1_id
+ pc1_show_resp = requests.get(url, headers=headers)
+ self.assertEqual(pc1_show_resp.status_code, 200)
+ self.assertEqual(json.loads(pc1_show_resp.content)["port_chain"]["name"], "pc1")
if __name__ == '__main__':
unittest.main()