# son-emu
This is the repository of [SONATA's](http://sonata-nfv.eu) emulation platform.
-This emulation platform was created to support network service developers to locally prototype and test complete network service chains in realistic end-to-end multi-PoP scenarios. It allows the direct execution of real network functions, packaged as Docker containers, in emulated network topologies running locally on the network service developer's machine.
+This emulation platform was created to support network service developers to locally prototype and test complete network service chains in realistic end-to-end multi-PoP scenarios. It allows the execution of real network functions, packaged as Docker containers, in emulated network topologies running locally on the network service developer's machine.
### Cite this work
If you use son-emu for your research and/or other publications, please cite the following paper to reference our work:
-* Manuel Peuster, Holger Karl, and Steven van Rossem. "**MeDICINE: Rapid Prototyping of Production-Ready Network Services in Multi-PoP Environments.**" to appear in IEEE Conference on Network Function Virtualization and Software Defined Network (NFV-SDN), 2016.
- * Pre-print online: http://arxiv.org/abs/1606.05995
+* M. Peuster, H. Karl and S. van Rossem, **"MeDICINE: Rapid prototyping of production-ready network services in multi-PoP environments,"** 2016 IEEE Conference on Network Function Virtualization and Software Defined Networks (NFV-SDN), Palo Alto, CA, USA, 2016, pp. 148-153.
+doi: 10.1109/NFV-SDN.2016.7919490
+ * Link: http://ieeexplore.ieee.org/document/7919490/
+ * Pre-print: http://arxiv.org/abs/1606.05995
A short demo that showcases son-emu together with its dummy gatekeeper is available [here](https://www.youtube.com/watch?v=ZANz97pV9ao).
* `ansible` Install scripts
* `misc` Example packages and VNFs
* `src`
- * `emuvim` Emulator components
- * `api` API endpoint implementations
- * `rest` REST API for son-emu-cli
- * `sonata` Dummy gatekeeper API
- * `cli` Command line client to control the emulator
- * `dcemulator` Emulator core
- * `resourcemodel` Resource limitation models
- * `examples` Example topology scripts
- * `test` Test scripts
+ * `emuvim` Emulator components
+ * `api` API endpoint implementations
+ * `rest` REST API for son-emu-cli
+ * `sonata` Dummy gatekeeper API
+ * `openstack` OpenStack-like APIs for MANO integration
+ * `cli` Command line client to control the emulator
+ * `dashboard` A web-based dashboard to display the emulator's state
+ * `dcemulator` Emulator core
+ * `resourcemodel` Resource limitation models
+ * `examples` Example topology scripts
+ * `test` Test scripts
* `utils` Helper scripts for SONATA's CI/CD setup
4. Follow/answer related [issues](https://github.com/sonata-nfv/son-emu/issues) (see Feedback-Chanel, below).
## Installation
-There are two ways to install and use son-emu. The simple one is to use Vagrant to create a VirtualBox-based VM on you machine that contains the pre-installed and configured emulator. The more complicated installation requires a freshly installed Ubuntu 14.04 LTS and is done by a ansible playbook.
+There are two ways to install and use son-emu. The simple one is to use Vagrant to create a VirtualBox-based VM on your machine that contains the pre-installed and configured emulator. The more complicated installation requires a freshly installed Ubuntu 16.04 LTS and is done by a ansible playbook.
### Vagrant Installation
### Ansible Installation
-* Requires: Ubuntu 14.04 LTS
+* Requires: Ubuntu 16.04 LTS
* `sudo apt-get install ansible git aptitude`
* `sudo vim /etc/ansible/hosts`
* Add: `localhost ansible_connection=local`
## Usage
### Examples
-#### Manual Usage Example:
This simple example shows how to start the emulator with a simple topology (terminal 1) and how to start (terminal 2) some empty VNF containers in the emulated datacenters (PoPs) by using the son-emu-cli.
* `containernet> vnf1 ifconfig`
* `containernet> vnf1 ping -c 2 vnf2`
-#### Dummy Gatekeeper Example:
-
-This example shows how to deploy a SONATA example package in the emulator using the dummy gatekeeper.
-
-* First terminal (start the emulation platform):
- * `sudo python src/emuvim/examples/sonata_y1_demo_topology_1.py`
-* Second terminal (deploy the example package):
- * Upload: `curl -i -X POST -F package=@sonata-demo-docker.son http://127.0.0.1:5000/packages`
- * Instantiate: `curl -X POST http://127.0.0.1:5000/instantiations -d "{}"`
- * Verify that service runs: `son-emu-cli compute list`
-
-Note: The [son-push](https://github.com/mpeuster/son-cli) tool can be used instead of CURL.
-
### Further Documentation
* [Full CLI command documentation](https://github.com/sonata-nfv/son-emu/wiki/CLI-Command-Overview)
* [Requirements for Docker containers executed by the emulator](https://github.com/sonata-nfv/son-emu/wiki/Container-Requirements)
+* [REST API](https://github.com/sonata-nfv/son-emu/wiki/REST-API-command-overview)
## License
* Hadi Razzaghi Kouchaksaraei (https://github.com/hadik3r)
* Wouter Tavernier (https://github.com/wtaverni)
* Geoffroy Chollon (https://github.com/cgeoffroy)
+* Eduard Maas (https://github.com/edmaas)
#### Feedback-Chanel
'requests',
'prometheus_client',
'urllib3',
- 'ipaddress'
+ 'ipaddress',
+ 'simplejson'
],
zip_safe=False,
entry_points={
+++ /dev/null
-This is a placeholder. This folder will contain a OpenStack/HEAT like interface to the emulator.
\ No newline at end of file
--- /dev/null
+import json
+import logging
+import copy
+
+from mininet.node import OVSSwitch
+
+from flask import Flask
+from flask import Response, request
+from flask_restful import Api, Resource
+from mininet.link import Link
+import uuid
+
+
+class ChainApi(Resource):
+ """
+ The chain API is a component that is not used in OpenStack.
+ It is a custom built REST API that can be used to create network chains and loadbalancers.
+ """
+
+ def __init__(self, inc_ip, inc_port, manage):
+ # setup Flask
+ self.app = Flask(__name__)
+ self.api = Api(self.app)
+ self.ip = inc_ip
+ self.port = inc_port
+ self.manage = manage
+ self.playbook_file = '/tmp/son-emu-requests.log'
+ self.api.add_resource(ChainVersionsList, "/",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(ChainList, "/v1/chain/list",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(ChainVnfInterfaces, "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(ChainVnfDcStackInterfaces,
+ "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(BalanceHostList, "/v1/lb/list",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(BalanceHost, "/v1/lb/<vnf_src_name>/<vnf_src_interface>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(BalanceHostDcStack, "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(QueryTopology, "/v1/topo",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(Shutdown, "/shutdown")
+
+ @self.app.after_request
+ def add_access_control_header(response):
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ def _start_flask(self):
+ logging.info("Starting %s endpoint @ http://%s:%d" % ("ChainDummyApi", self.ip, self.port))
+ if self.app is not None:
+ self.app.before_request(self.dump_playbook)
+ self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+ def dump_playbook(self):
+ with self.manage.lock:
+ with open(self.playbook_file, 'a') as logfile:
+ if len(request.data) > 0:
+ data = "# CHAIN API\n"
+ data += "curl -X {type} -H \"Content-type: application/json\" -d '{data}' {url}".format(type=request.method,
+ data=request.data,
+ url=request.url)
+ logfile.write(data + "\n")
+
+
+class Shutdown(Resource):
+ def get(self):
+ logging.debug(("%s is beeing shut down") % (__name__))
+ func = request.environ.get('werkzeug.server.shutdown')
+ if func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ func()
+
+
+class ChainVersionsList(Resource):
+ '''
+ Entrypoint to find versions of the chain api.
+ '''
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ '''
+ :return: flask.Response containing the openstack like description of the chain api
+ '''
+ # at least let it look like an open stack function
+ try:
+ resp = """
+ {
+ "versions": [
+ {
+ "id": "v1",
+ "links": [
+ {
+ "href": "http://%s:%d/v1/",
+ "rel": "self"
+ }
+ ],
+ "status": "CURRENT",
+ "version": "1",
+ "min_version": "1",
+ "updated": "2013-07-23T11:33:21Z"
+ }
+ ]
+ }
+ """ % (self.api.ip, self.api.port)
+
+ return Response(resp, status=200, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not show list of versions." % __name__)
+ return ex.message, 500
+
+
+class ChainList(Resource):
+ '''
+ Will retrieve all chains including their paths.
+ '''
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ '''
+ :return: flask.Response containing all live chains
+ '''
+ # at least let it look like an open stack function
+ try:
+ resp = {"chains": list()}
+
+ for chain in self.api.manage.full_chain_data.values():
+ resp["chains"].append(chain)
+
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not list all network chains." % __name__)
+ return ex.message, 500
+
+
+class BalanceHostList(Resource):
+ '''
+ Will retrieve all loadbalance rules including their paths.
+ '''
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ '''
+ :return: flask.Response containing all live loadbalancer rules
+ '''
+ # at least let it look like an open stack function
+ try:
+ resp = {"loadbalancers": list()}
+
+ for lb in self.api.manage.full_lb_data.values():
+ resp["loadbalancers"].append(lb)
+
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not list all live loadbalancers." % __name__)
+ return ex.message, 500
+
+
+class ChainVnfInterfaces(Resource):
+ """
+ Handles requests targeted at: "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+ Requests are for tearing down or setting up a chain between two vnfs
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def put(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+ """
+ A put request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+ will create a chain between two interfaces at the specified vnfs.
+
+ Note:
+ Does not allow a custom path. Uses ``.post``
+ Internally just makes a POST request with no POST data!
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+ 501 if one of the VNF / intfs does not exist
+ :rtype: :class:`flask.Response`
+ """
+ return self.post(src_vnf, src_intfs, dst_vnf, dst_intfs)
+
+ def post(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+ """
+ A post request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+ will create a chain between two interfaces at the specified vnfs.
+ The POST data contains the path like this.
+ { "path": ["dc1.s1", "s1", "dc4.s1"]}
+ path specifies the destination vnf and interface and contains a list of switches
+ that the path traverses. The path may not contain single hop loops like:
+ [s1, s2, s1].
+ This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+ 501 if one of the VNF / intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+
+ if request.is_json:
+ path = request.json.get('path')
+ layer2 = request.json.get('layer2', True)
+ else:
+ path = None
+ layer2 = True
+
+ # check if both VNFs exist
+ if not self.api.manage.check_vnf_intf_pair(src_vnf, src_intfs):
+ return Response(u"VNF %s or intfs %s does not exist" % (src_vnf, src_intfs), status=501,
+ mimetype="application/json")
+ if not self.api.manage.check_vnf_intf_pair(dst_vnf, dst_intfs):
+ return Response(u"VNF %s or intfs %s does not exist" % (dst_vnf, dst_intfs), status=501,
+ mimetype="application/json")
+ try:
+ cookie = self.api.manage.network_action_start(src_vnf, dst_vnf, vnf_src_interface=src_intfs,
+ vnf_dst_interface=dst_intfs, bidirectional=True,
+ path=path, layer2=layer2)
+ resp = {'cookie': cookie}
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+ return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+ def delete(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+ """
+ A DELETE request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+ will delete a previously created chain.
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500\
+ also returns the cookie as dict {'cookie': value}
+ 501 if one of the VNF / intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ # check if both VNFs exist
+ # check if both VNFs exist
+ if not self.api.manage.check_vnf_intf_pair(src_vnf, src_intfs):
+ return Response(u"VNF %s or intfs %s does not exist" % (src_vnf, src_intfs), status=501,
+ mimetype="application/json")
+ if not self.api.manage.check_vnf_intf_pair(dst_vnf, dst_intfs):
+ return Response(u"VNF %s or intfs %s does not exist" % (dst_vnf, dst_intfs), status=501,
+ mimetype="application/json")
+ try:
+ cookie = self.api.manage.network_action_stop(src_vnf, dst_vnf, vnf_src_interface=src_intfs,
+ vnf_dst_interface=dst_intfs, bidirectional=True)
+ return Response(json.dumps(cookie), status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error deleting the chain.\n %s" % (__name__, e))
+ return Response(u"Error deleting the chain", status=500, mimetype="application/json")
+
+
+class ChainVnfDcStackInterfaces(Resource):
+ '''
+ Handles requests targeted at: "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+ Handles tearing down or setting up a chain between two vnfs for stacks.
+ '''
+
+ def __init__(self, api):
+ self.api = api
+
+ def put(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+ """
+ A PUT request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+ will set up chain.
+
+ :Note: PUT Requests can not set up custom paths!
+
+ :param src_dc: Name of the source datacenter
+ :type src_dc: `str`
+ :param src_stack: Name of the source stack
+ :type src_stack: `str`
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_dc: Name of the destination datacenter
+ :type dst_dc: ``str``
+ :param dst_stack: Name of the destination stack
+ :type dst_stack: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500\
+ also returns the cookie as dict {'cookie': value}
+ 501 if VNF or intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ # search for real names
+ real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+ if type(real_names) is not tuple:
+ # something went wrong
+ return real_names
+
+ container_src, container_dst, interface_src, interface_dst = real_names
+
+ # check if both VNFs exist
+ if not self.api.manage.check_vnf_intf_pair(container_src, interface_src):
+ return Response(u"VNF %s or intfs %s does not exist" % (container_src, interface_src), status=501,
+ mimetype="application/json")
+ if not self.api.manage.check_vnf_intf_pair(container_dst, interface_dst):
+ return Response(u"VNF %s or intfs %s does not exist" % (container_dst, interface_dst), status=501,
+ mimetype="application/json")
+
+ try:
+ cookie = self.api.manage.network_action_start(container_src, container_dst, vnf_src_interface=interface_src,
+ vnf_dst_interface=interface_dst, bidirectional=True,
+ layer2=True)
+ resp = {'cookie': cookie}
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+ return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+ def post(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+ """
+ A post request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+ will create a chain between two interfaces at the specified vnfs.
+ The POST data contains the path like this.
+ { "path": ["dc1.s1", "s1", "dc4.s1"]}
+ path specifies the destination vnf and interface and contains a list of switches
+ that the path traverses. The path may not contain single hop loops like:
+ [s1, s2, s1].
+ This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+ 501 if vnf / intfs do not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ if request.is_json:
+ path = request.json.get('path')
+ layer2 = request.json.get('layer2', True)
+ else:
+ path = None
+ layer2 = True
+
+ # search for real names
+ real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+ if type(real_names) is not tuple:
+ # something went wrong
+ return real_names
+
+ container_src, container_dst, interface_src, interface_dst = real_names
+
+ try:
+ cookie = self.api.manage.network_action_start(container_src, container_dst, vnf_src_interface=interface_src,
+ vnf_dst_interface=interface_dst, bidirectional=True,
+ path=path, layer2=layer2)
+ resp = {'cookie': cookie}
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+ return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+ def delete(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+ """
+ A DELETE request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+ will delete a previously created chain.
+
+ :param src_dc: Name of the source datacenter
+ :type src_dc: `str`
+ :param src_stack: Name of the source stack
+ :type src_stack: `str`
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param src_intfs: Name of the source VNF interface to chain on
+ :type src_intfs: ``str``
+ :param dst_dc: Name of the destination datacenter
+ :type dst_dc: ``str``
+ :param dst_stack: Name of the destination stack
+ :type dst_stack: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param dst_intfs: Name of the destination VNF interface to chain on
+ :type dst_intfs: ``str``
+ :return: flask.Response 200 if set up correctly else 500\
+ also returns the cookie as dict {'cookie': value}
+ 501 if one of the VNF / intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ # search for real names
+ real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+ if type(real_names) is not tuple:
+ # something went wrong, real_names is a Response object
+ return real_names
+
+ container_src, container_dst, interface_src, interface_dst = real_names
+
+ try:
+ cookie = self.api.manage.network_action_stop(container_src, container_dst, vnf_src_interface=interface_src,
+ vnf_dst_interface=interface_dst, bidirectional=True)
+ return Response(json.dumps(cookie), status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error deleting the chain.\n %s" % (__name__, e))
+ return Response(u"Error deleting the chain", status=500, mimetype="application/json")
+
+ # Tries to find real container and interface names according to heat template names
+ # Returns a tuple of 4 or a Response object
+ def _findNames(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+ # search for datacenters
+ if src_dc not in self.api.manage.net.dcs or dst_dc not in self.api.manage.net.dcs:
+ return Response(u"At least one DC does not exist", status=500, mimetype="application/json")
+ dc_src = self.api.manage.net.dcs[src_dc]
+ dc_dst = self.api.manage.net.dcs[dst_dc]
+ # search for related OpenStackAPIs
+ api_src = None
+ api_dst = None
+ from openstack_api_endpoint import OpenstackApiEndpoint
+ for api in OpenstackApiEndpoint.dc_apis:
+ if api.compute.dc == dc_src:
+ api_src = api
+ if api.compute.dc == dc_dst:
+ api_dst = api
+ if api_src is None or api_dst is None:
+ return Response(u"At least one OpenStackAPI does not exist", status=500, mimetype="application/json")
+ # search for stacks
+ stack_src = None
+ stack_dst = None
+ for stack in api_src.compute.stacks.values():
+ if stack.stack_name == src_stack:
+ stack_src = stack
+ for stack in api_dst.compute.stacks.values():
+ if stack.stack_name == dst_stack:
+ stack_dst = stack
+ if stack_src is None or stack_dst is None:
+ return Response(u"At least one Stack does not exist", status=500, mimetype="application/json")
+ # search for servers
+ server_src = None
+ server_dst = None
+ for server in stack_src.servers.values():
+ if server.template_name == src_vnf:
+ server_src = server
+ break
+ for server in stack_dst.servers.values():
+ if server.template_name == dst_vnf:
+ server_dst = server
+ break
+ if server_src is None or server_dst is None:
+ return Response(u"At least one VNF does not exist", status=500, mimetype="application/json")
+
+ container_src = server_src.name
+ container_dst = server_dst.name
+
+ # search for ports
+ port_src = None
+ port_dst = None
+ if src_intfs in server_src.port_names:
+ port_src = stack_src.ports[src_intfs]
+ if dst_intfs in server_dst.port_names:
+ port_dst = stack_dst.ports[dst_intfs]
+ if port_src is None or port_dst is None:
+ return Response(u"At least one Port does not exist", status=500, mimetype="application/json")
+
+ interface_src = port_src.intf_name
+ interface_dst = port_dst.intf_name
+
+ return container_src, container_dst, interface_src, interface_dst
+
+
+class BalanceHostDcStack(Resource):
+ """
+ Handles requests to "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>"
+ Sets up LoadBalancers for VNFs that are belonging to a certain stack.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def post(self, src_dc, src_stack, vnf_src_name, vnf_src_interface):
+ """
+ A POST request to "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>"
+ will set up a loadbalancer. The target VNFs and interfaces are in the post data.
+
+ :Example:
+ See :class:`heat.chain_api.BalanceHost.post`
+
+ :param src_dc: Name of the source VNF
+ :type src_dc: ``str``
+ :param src_stack: Name of the source VNF interface to chain on
+ :type src_stack: ``str``
+ * src_stack == "floating" sets up a new floating node, so only use this name if you know what you are doing.
+ :param vnf_src_name:
+ :type vnf_src_name: ``str``
+ :param vnf_src_interface:
+ :type vnf_src_interface: ``str``
+ :return: flask.Response 200 if set up correctly else 500
+ :rtype: :class:`flask.Response`
+
+ """
+ try:
+ req = request.json
+ if req is None or len(req) == 0 or "dst_vnf_interfaces" not in req:
+ return Response(u"You have to specify destination vnfs via the POST data.",
+ status=500, mimetype="application/json")
+
+ dst_vnfs = req.get('dst_vnf_interfaces')
+ container_src = None
+ interface_src = None
+
+ # check src vnf/port
+ if src_stack != "floating":
+ real_src = self._findName(src_dc, src_stack, vnf_src_name, vnf_src_interface)
+ if type(real_src) is not tuple:
+ # something went wrong, real_src is a Response object
+ return real_src
+
+ container_src, interface_src = real_src
+
+ real_dst_dict = {}
+ for dst_vnf in dst_vnfs:
+ dst_dc = dst_vnf.get('pop', None)
+ dst_stack = dst_vnf.get('stack', None)
+ dst_server = dst_vnf.get('server', None)
+ dst_port = dst_vnf.get('port', None)
+ if dst_dc is not None and dst_stack is not None and dst_server is not None and dst_port is not None:
+ real_dst = self._findName(dst_dc, dst_stack, dst_server, dst_port)
+ if type(real_dst) is not tuple:
+ # something went wrong, real_dst is a Response object
+ return real_dst
+ real_dst_dict[real_dst[0]] = real_dst[1]
+
+ input_object = {"dst_vnf_interfaces": real_dst_dict, "path": req.get("path", None)}
+
+ if src_stack != "floating":
+ self.api.manage.add_loadbalancer(container_src, interface_src, lb_data=input_object)
+ return Response(u"Loadbalancer set up at %s:%s" % (container_src, interface_src),
+ status=200, mimetype="application/json")
+ else:
+ cookie, floating_ip = self.api.manage.add_floating_lb(src_dc, lb_data=input_object)
+
+ return Response(json.dumps({"cookie": "%d" % cookie, "floating_ip": "%s" % floating_ip}),
+ status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the loadbalancer at %s %s %s:%s.\n %s" %
+ (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e))
+ return Response(u"%s: Error setting up the loadbalancer at %s %s %s:%s.\n %s" %
+ (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e), status=500,
+ mimetype="application/json")
+
+ def delete(self, src_dc, src_stack, vnf_src_name, vnf_src_interface):
+ """
+ Will delete a load balancer that sits behind a specified interface at a vnf for a specific stack
+
+ :param src_dc: Name of the source VNF
+ :type src_dc: ``str``
+ :param src_stack: Name of the source VNF interface to chain on
+ :type src_stack: ``str``
+ :param vnf_src_name:
+ :type vnf_src_name: ``str``
+ :param vnf_src_interface:
+ :type vnf_src_interface: ``str``
+ :return: flask.Response 200 if set up correctly else 500
+ :rtype: :class:`flask.Response`
+
+ """
+ try:
+ # check src vnf/port
+ if src_stack != "floating":
+ real_src = self._findName(src_dc, src_stack, vnf_src_name, vnf_src_interface)
+ if type(real_src) is not tuple:
+ # something went wrong, real_src is a Response object
+ return real_src
+
+ container_src, interface_src = real_src
+
+ self.api.manage.delete_loadbalancer(container_src, interface_src)
+ return Response(u"Loadbalancer deleted at %s:%s" % (vnf_src_name, vnf_src_interface),
+ status=200, mimetype="application/json")
+ else:
+ cookie = vnf_src_name
+ self.api.manage.delete_floating_lb(cookie)
+ return Response(u"Floating loadbalancer with cookie %s deleted" % (cookie),
+ status=200, mimetype="application/json")
+
+ except Exception as e:
+ logging.exception(u"%s: Error deleting the loadbalancer at %s %s %s%s.\n %s" %
+ (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e))
+ return Response(u"%s: Error deleting the loadbalancer at %s %s %s%s." %
+ (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface), status=500,
+ mimetype="application/json")
+
+ # Tries to find real container and port name according to heat template names
+ # Returns a string or a Response object
+ def _findName(self, dc, stack, vnf, port):
+ # search for datacenters
+ if dc not in self.api.manage.net.dcs:
+ return Response(u"DC does not exist", status=500, mimetype="application/json")
+ dc_real = self.api.manage.net.dcs[dc]
+ # search for related OpenStackAPIs
+ api_real = None
+ from openstack_api_endpoint import OpenstackApiEndpoint
+ for api in OpenstackApiEndpoint.dc_apis:
+ if api.compute.dc == dc_real:
+ api_real = api
+ if api_real is None:
+ return Response(u"OpenStackAPI does not exist", status=500, mimetype="application/json")
+ # search for stacks
+ stack_real = None
+ for stackObj in api_real.compute.stacks.values():
+ if stackObj.stack_name == stack:
+ stack_real = stackObj
+ if stack_real is None:
+ return Response(u"Stack does not exist", status=500, mimetype="application/json")
+ # search for servers
+ server_real = None
+ for server in stack_real.servers.values():
+ if server.template_name == vnf:
+ server_real = server
+ break
+ if server_real is None:
+ return Response(u"VNF does not exist", status=500, mimetype="application/json")
+
+ container_real = server_real.name
+
+ # search for ports
+ port_real = None
+ if port in server_real.port_names:
+ port_real = stack_real.ports[port]
+ if port_real is None:
+ return Response(u"At least one Port does not exist", status=500, mimetype="application/json")
+
+ interface_real = port_real.intf_name
+
+ return container_real, interface_real
+
+
+class BalanceHost(Resource):
+ """
+ Handles requests at "/v1/lb/<vnf_src_name>/<vnf_src_interface>"
+ to set up or delete Load Balancers.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def post(self, vnf_src_name, vnf_src_interface):
+ """
+ Will set up a Load balancer behind an interface at a specified vnf
+ We need both to avoid naming conflicts as interface names are not unique
+
+ :param vnf_src_name: Name of the source VNF
+ :type vnf_src_name: ``str``
+ :param vnf_src_interface: Name of the source VNF interface to chain on
+ :type vnf_src_interface: ``str``
+ :return: flask.Response 200 if set up correctly else 500
+ 501 if VNF or intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ try:
+ req = request.json
+ if req is None or len(req) == 0 or "dst_vnf_interfaces" not in req:
+ return Response(u"You have to specify destination vnfs via the POST data.",
+ status=500, mimetype="application/json")
+
+ if vnf_src_name != "floating":
+ # check if VNF exist
+ if not self.api.manage.check_vnf_intf_pair(vnf_src_name, vnf_src_interface):
+ return Response(u"VNF %s or intfs %s does not exist" % (vnf_src_name, vnf_src_interface),
+ status=501,
+ mimetype="application/json")
+ self.api.manage.add_loadbalancer(vnf_src_name, vnf_src_interface, lb_data=req)
+
+ return Response(u"Loadbalancer set up at %s:%s" % (vnf_src_name, vnf_src_interface),
+ status=200, mimetype="application/json")
+ else:
+ cookie, floating_ip = self.api.manage.add_floating_lb(vnf_src_interface, lb_data=req)
+
+ return Response(json.dumps({"cookie": "%d" % cookie, "floating_ip": "%s" % floating_ip}),
+ status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error setting up the loadbalancer at %s:%s.\n %s" %
+ (__name__, vnf_src_name, vnf_src_interface, e))
+ return Response(u"%s: Error setting up the loadbalancer at %s:%s.\n %s" %
+ (__name__, vnf_src_name, vnf_src_interface, e), status=500, mimetype="application/json")
+
+ def delete(self, vnf_src_name, vnf_src_interface):
+ """
+ Will delete a load balancer that sits behind a specified interface at a vnf
+
+ :param vnf_src_name: Name of the source VNF
+ :type vnf_src_name: ``str``
+ :param vnf_src_interface: Name of the source VNF interface to chain on
+ :type vnf_src_interface: ``str``
+ :return: flask.Response 200 if set up correctly else 500
+ 501 if VNF or intfs does not exist
+ :rtype: :class:`flask.Response`
+
+ """
+ # check if VNF exist
+ if not self.api.manage.check_vnf_intf_pair(vnf_src_name, vnf_src_interface):
+ return Response(u"VNF %s or intfs %s does not exist" % (vnf_src_name, vnf_src_interface), status=501,
+ mimetype="application/json")
+ try:
+ logging.debug("Deleting loadbalancer at %s: interface: %s" % (vnf_src_name, vnf_src_interface))
+ net = self.api.manage.net
+
+ if vnf_src_name != "floating":
+ # check if VNF exists
+ if vnf_src_name not in net:
+ return Response(u"Source VNF or interface can not be found." % vnf_src_name,
+ status=404, mimetype="application/json")
+
+ self.api.manage.delete_loadbalancer(vnf_src_name, vnf_src_interface)
+
+ return Response(u"Loadbalancer deleted at %s:%s" % (vnf_src_name, vnf_src_interface),
+ status=200, mimetype="application/json")
+ else:
+ cookie = vnf_src_name
+ self.api.manage.delete_floating_lb(cookie)
+ return Response(u"Floating loadbalancer with cookie %s removed" % (cookie),
+ status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error deleting the loadbalancer at %s%s.\n %s" %
+ (__name__, vnf_src_name, vnf_src_interface, e))
+ return Response(u"%s: Error deleting the loadbalancer at %s%s." %
+ (__name__, vnf_src_name, vnf_src_interface), status=500, mimetype="application/json")
+
+
+class QueryTopology(Resource):
+ """
+ Handles requests at "/v1/topo/"
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ Answers GET requests for the current network topology at "/v1/topo".
+ This will only return switches and datacenters and ignore currently deployed VNFs.
+
+ :return: 200 if successful with the network graph as json dict, else 500
+
+ """
+ try:
+ logging.debug("Querying topology")
+ graph = self.api.manage.net.DCNetwork_graph
+ net = self.api.manage.net
+ # root node is nodes
+ topology = {"nodes": list()}
+
+ for n in graph:
+ # remove root node as well as the floating switch fs1
+ if n != "root" and n != "fs1":
+ # we only want to return switches!
+ if not isinstance(net[n], OVSSwitch):
+ continue
+ node = dict()
+
+ # get real datacenter label
+ for dc in self.api.manage.net.dcs.values():
+ if str(dc.switch) == str(n):
+ node["name"] = str(n)
+ node["type"] = "Datacenter"
+ node["label"] = str(dc.label)
+ break
+
+ # node is not a datacenter. It has to be a switch
+ if node.get("type", "") != "Datacenter":
+ node["name"] = str(n)
+ node["type"] = "Switch"
+
+ node["links"] = list()
+ # add links to the topology
+ for graph_node, data in graph[n].items():
+ # only add links to the topology that connect switches
+ if isinstance(net[graph_node], OVSSwitch):
+ # we allow multiple edges between switches, so add them all
+ # with their unique keys
+ link = copy.copy(data)
+ for edge in link:
+ # do not add any links to the floating switch to the topology!
+ if graph_node == "fs1":
+ continue
+ # the translator wants everything as a string!
+ for key, value in link[edge].items():
+ link[edge][key] = str(value)
+ # name of the destination
+ link[edge]["name"] = graph_node
+ node["links"].append(link)
+
+ topology["nodes"].append(node)
+
+ return Response(json.dumps(topology),
+ status=200, mimetype="application/json")
+ except Exception as e:
+ logging.exception(u"%s: Error querying topology.\n %s" %
+ (__name__, e))
+ return Response(u"%s: Error querying topology.\n %s" %
+ (__name__, e), status=500, mimetype="application/json")
--- /dev/null
+from mininet.link import Link
+from resources import *
+from docker import DockerClient
+import logging
+import threading
+import uuid
+import time
+import ip_handler as IP
+
+
+class HeatApiStackInvalidException(Exception):
+ """
+ Exception thrown when a submitted stack is invalid.
+ """
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class OpenstackCompute(object):
+ """
+ This class is a datacenter specific compute object that tracks all containers that are running in a datacenter,
+ as well as networks and configured ports.
+ It has some stack dependet logic and can check if a received stack is valid.
+
+ It also handles start and stop of containers.
+ """
+
+ def __init__(self):
+ self.dc = None
+ self.stacks = dict()
+ self.computeUnits = dict()
+ self.routers = dict()
+ self.flavors = dict()
+ self._images = dict()
+ self.nets = dict()
+ self.ports = dict()
+ self.compute_nets = dict()
+ self.dcli = DockerClient(base_url='unix://var/run/docker.sock')
+
+ @property
+ def images(self):
+ """
+ Updates the known images. Asks the docker daemon for a list of all known images and returns
+ the new dictionary.
+
+ :return: Returns the new image dictionary.
+ :rtype: ``dict``
+ """
+ for image in self.dcli.images.list():
+ if len(image.tags) > 0:
+ for t in image.tags:
+ t = t.replace(":latest", "") # only use short tag names for OSM compatibility
+ if t not in self._images:
+ self._images[t] = Image(t)
+ return self._images
+
+ def add_stack(self, stack):
+ """
+ Adds a new stack to the compute node.
+
+ :param stack: Stack dictionary.
+ :type stack: :class:`heat.resources.stack`
+ """
+ if not self.check_stack(stack):
+ self.clean_broken_stack(stack)
+ raise HeatApiStackInvalidException("Stack did not pass validity checks")
+ self.stacks[stack.id] = stack
+
+ def clean_broken_stack(self, stack):
+ for port in stack.ports.values():
+ if port.id in self.ports:
+ del self.ports[port.id]
+ for server in stack.servers.values():
+ if server.id in self.computeUnits:
+ del self.computeUnits[server.id]
+ for net in stack.nets.values():
+ if net.id in self.nets:
+ del self.nets[net.id]
+
+ def check_stack(self, stack):
+ """
+ Checks all dependencies of all servers, ports and routers and their most important parameters.
+
+ :param stack: A reference of the stack that should be checked.
+ :type stack: :class:`heat.resources.stack`
+ :return: * *True*: If the stack is completely fine.
+ * *False*: Else
+ :rtype: ``bool``
+ """
+ everything_ok = True
+ for server in stack.servers.values():
+ for port_name in server.port_names:
+ if port_name not in stack.ports:
+ logging.warning("Server %s of stack %s has a port named %s that is not known." %
+ (server.name, stack.stack_name, port_name))
+ everything_ok = False
+ if server.image is None:
+ logging.warning("Server %s holds no image." % (server.name))
+ everything_ok = False
+ if server.command is None:
+ logging.warning("Server %s holds no command." % (server.name))
+ everything_ok = False
+ for port in stack.ports.values():
+ if port.net_name not in stack.nets:
+ logging.warning("Port %s of stack %s has a network named %s that is not known." %
+ (port.name, stack.stack_name, port.net_name))
+ everything_ok = False
+ if port.intf_name is None:
+ logging.warning("Port %s has no interface name." % (port.name))
+ everything_ok = False
+ if port.ip_address is None:
+ logging.warning("Port %s has no IP address." % (port.name))
+ everything_ok = False
+ for router in stack.routers.values():
+ for subnet_name in router.subnet_names:
+ found = False
+ for net in stack.nets.values():
+ if net.subnet_name == subnet_name:
+ found = True
+ break
+ if not found:
+ logging.warning("Router %s of stack %s has a network named %s that is not known." %
+ (router.name, stack.stack_name, subnet_name))
+ everything_ok = False
+ return everything_ok
+
+ def add_flavor(self, name, cpu, memory, memory_unit, storage, storage_unit):
+ """
+ Adds a flavor to the stack.
+
+ :param name: Specifies the name of the flavor.
+ :type name: ``str``
+ :param cpu:
+ :type cpu: ``str``
+ :param memory:
+ :type memory: ``str``
+ :param memory_unit:
+ :type memory_unit: ``str``
+ :param storage:
+ :type storage: ``str``
+ :param storage_unit:
+ :type storage_unit: ``str``
+ """
+ flavor = InstanceFlavor(name, cpu, memory, memory_unit, storage, storage_unit)
+ self.flavors[flavor.name] = flavor
+ return flavor
+
+ def deploy_stack(self, stackid):
+ """
+ Deploys the stack and starts the emulation.
+
+ :param stackid: An UUID str of the stack
+ :type stackid: ``str``
+ :return: * *False*: If the Datacenter is None
+ * *True*: Else
+ :rtype: ``bool``
+ """
+ if self.dc is None:
+ return False
+
+ stack = self.stacks[stackid]
+ self.update_compute_dicts(stack)
+
+ # Create the networks first
+ for server in stack.servers.values():
+ self._start_compute(server)
+ return True
+
+ def delete_stack(self, stack_id):
+ """
+ Delete a stack and all its components.
+
+ :param stack_id: An UUID str of the stack
+ :type stack_id: ``str``
+ :return: * *False*: If the Datacenter is None
+ * *True*: Else
+ :rtype: ``bool``
+ """
+ if self.dc is None:
+ return False
+
+ # Stop all servers and their links of this stack
+ for server in self.stacks[stack_id].servers.values():
+ self.stop_compute(server)
+ self.delete_server(server)
+ for net in self.stacks[stack_id].nets.values():
+ self.delete_network(net.id)
+ for port in self.stacks[stack_id].ports.values():
+ self.delete_port(port.id)
+
+ del self.stacks[stack_id]
+ return True
+
+ def update_stack(self, old_stack_id, new_stack):
+ """
+ Determines differences within the old and the new stack and deletes, create or changes only parts that
+ differ between the two stacks.
+
+ :param old_stack_id: The ID of the old stack.
+ :type old_stack_id: ``str``
+ :param new_stack: A reference of the new stack.
+ :type new_stack: :class:`heat.resources.stack`
+ :return: * *True*: if the old stack could be updated to the new stack without any error.
+ * *False*: else
+ :rtype: ``bool``
+ """
+ if old_stack_id not in self.stacks:
+ return False
+ old_stack = self.stacks[old_stack_id]
+
+ # Update Stack IDs
+ for server in old_stack.servers.values():
+ if server.name in new_stack.servers:
+ new_stack.servers[server.name].id = server.id
+ for net in old_stack.nets.values():
+ if net.name in new_stack.nets:
+ new_stack.nets[net.name].id = net.id
+ for subnet in new_stack.nets.values():
+ if subnet.subnet_name == net.subnet_name:
+ subnet.subnet_id = net.subnet_id
+ break
+ for port in old_stack.ports.values():
+ if port.name in new_stack.ports:
+ new_stack.ports[port.name].id = port.id
+ for router in old_stack.routers.values():
+ if router.name in new_stack.routers:
+ new_stack.routers[router.name].id = router.id
+
+ # Update the compute dicts to now contain the new_stack components
+ self.update_compute_dicts(new_stack)
+
+ self.update_ip_addresses(old_stack, new_stack)
+
+ # Update all interface names - after each port has the correct UUID!!
+ for port in new_stack.ports.values():
+ port.create_intf_name()
+
+ if not self.check_stack(new_stack):
+ return False
+
+ # Remove unnecessary networks
+ for net in old_stack.nets.values():
+ if not net.name in new_stack.nets:
+ self.delete_network(net.id)
+
+ # Remove all unnecessary servers
+ for server in old_stack.servers.values():
+ if server.name in new_stack.servers:
+ if not server.compare_attributes(new_stack.servers[server.name]):
+ self.stop_compute(server)
+ else:
+ # Delete unused and changed links
+ for port_name in server.port_names:
+ if port_name in old_stack.ports and port_name in new_stack.ports:
+ if not old_stack.ports.get(port_name) == new_stack.ports.get(port_name):
+ my_links = self.dc.net.links
+ for link in my_links:
+ if str(link.intf1) == old_stack.ports[port_name].intf_name and \
+ str(link.intf1.ip) == \
+ old_stack.ports[port_name].ip_address.split('/')[0]:
+ self._remove_link(server.name, link)
+
+ # Add changed link
+ self._add_link(server.name,
+ new_stack.ports[port_name].ip_address,
+ new_stack.ports[port_name].intf_name,
+ new_stack.ports[port_name].net_name)
+ break
+ else:
+ my_links = self.dc.net.links
+ for link in my_links:
+ if str(link.intf1) == old_stack.ports[port_name].intf_name and \
+ str(link.intf1.ip) == old_stack.ports[port_name].ip_address.split('/')[0]:
+ self._remove_link(server.name, link)
+ break
+
+ # Create new links
+ for port_name in new_stack.servers[server.name].port_names:
+ if port_name not in server.port_names:
+ self._add_link(server.name,
+ new_stack.ports[port_name].ip_address,
+ new_stack.ports[port_name].intf_name,
+ new_stack.ports[port_name].net_name)
+ else:
+ self.stop_compute(server)
+
+ # Start all new servers
+ for server in new_stack.servers.values():
+ if server.name not in self.dc.containers:
+ self._start_compute(server)
+ else:
+ server.emulator_compute = self.dc.containers.get(server.name)
+
+ del self.stacks[old_stack_id]
+ self.stacks[new_stack.id] = new_stack
+ return True
+
+ def update_ip_addresses(self, old_stack, new_stack):
+ """
+ Updates the subnet and the port IP addresses - which should always be in this order!
+
+ :param old_stack: The currently running stack
+ :type old_stack: :class:`heat.resources.stack`
+ :param new_stack: The new created stack
+ :type new_stack: :class:`heat.resources.stack`
+ """
+ self.update_subnet_cidr(old_stack, new_stack)
+ self.update_port_addresses(old_stack, new_stack)
+
+ def update_port_addresses(self, old_stack, new_stack):
+ """
+ Updates the port IP addresses. First resets all issued addresses. Then get all IP addresses from the old
+ stack and sets them to the same ports in the new stack. Finally all new or changed instances will get new
+ IP addresses.
+
+ :param old_stack: The currently running stack
+ :type old_stack: :class:`heat.resources.stack`
+ :param new_stack: The new created stack
+ :type new_stack: :class:`heat.resources.stack`
+ """
+ for net in new_stack.nets.values():
+ net.reset_issued_ip_addresses()
+
+ for old_port in old_stack.ports.values():
+ for port in new_stack.ports.values():
+ if port.compare_attributes(old_port):
+ for net in new_stack.nets.values():
+ if net.name == port.net_name:
+ if net.assign_ip_address(old_port.ip_address, port.name):
+ port.ip_address = old_port.ip_address
+ port.mac_address = old_port.mac_address
+ else:
+ port.ip_address = net.get_new_ip_address(port.name)
+
+ for port in new_stack.ports.values():
+ for net in new_stack.nets.values():
+ if port.net_name == net.name and not net.is_my_ip(port.ip_address, port.name):
+ port.ip_address = net.get_new_ip_address(port.name)
+
+ def update_subnet_cidr(self, old_stack, new_stack):
+ """
+ Updates the subnet IP addresses. If the new stack contains subnets from the old stack it will take those
+ IP addresses. Otherwise it will create new IP addresses for the subnet.
+
+ :param old_stack: The currently running stack
+ :type old_stack: :class:`heat.resources.stack`
+ :param new_stack: The new created stack
+ :type new_stack: :class:`heat.resources.stack`
+ """
+ for old_subnet in old_stack.nets.values():
+ IP.free_cidr(old_subnet.get_cidr(), old_subnet.subnet_id)
+
+ for subnet in new_stack.nets.values():
+ subnet.clear_cidr()
+ for old_subnet in old_stack.nets.values():
+ if subnet.subnet_name == old_subnet.subnet_name:
+ if IP.assign_cidr(old_subnet.get_cidr(), subnet.subnet_id):
+ subnet.set_cidr(old_subnet.get_cidr())
+
+ for subnet in new_stack.nets.values():
+ if IP.is_cidr_issued(subnet.get_cidr()):
+ continue
+
+ cird = IP.get_new_cidr(subnet.subnet_id)
+ subnet.set_cidr(cird)
+ return
+
+ def update_compute_dicts(self, stack):
+ """
+ Update and add all stack components tho the compute dictionaries.
+
+ :param stack: A stack reference, to get all required components.
+ :type stack: :class:`heat.resources.stack`
+ """
+ for server in stack.servers.values():
+ self.computeUnits[server.id] = server
+ if isinstance(server.flavor, dict):
+ self.add_flavor(server.flavor['flavorName'],
+ server.flavor['vcpu'],
+ server.flavor['ram'], 'MB',
+ server.flavor['storage'], 'GB')
+ server.flavor = server.flavor['flavorName']
+ for router in stack.routers.values():
+ self.routers[router.id] = router
+ for net in stack.nets.values():
+ self.nets[net.id] = net
+ for port in stack.ports.values():
+ self.ports[port.id] = port
+
+ def _start_compute(self, server):
+ """
+ Starts a new compute object (docker container) inside the emulator.
+ Should only be called by stack modifications and not directly.
+
+ :param server: Specifies the compute resource.
+ :type server: :class:`heat.resources.server`
+ """
+ logging.debug("Starting new compute resources %s" % server.name)
+ network = list()
+
+ for port_name in server.port_names:
+ network_dict = dict()
+ port = self.find_port_by_name_or_id(port_name)
+ if port is not None:
+ network_dict['id'] = port.intf_name
+ network_dict['ip'] = port.ip_address
+ network_dict[network_dict['id']] = self.find_network_by_name_or_id(port.net_name).name
+ 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)
+ server.emulator_compute = c
+
+ for intf in c.intfs.values():
+ for port_name in server.port_names:
+ port = self.find_port_by_name_or_id(port_name)
+ if port is not None:
+ if intf.name == port.intf_name:
+ # wait up to one second for the intf to come up
+ self.timeout_sleep(intf.isUp, 1)
+ if port.mac_address is not None:
+ intf.setMAC(port.mac_address)
+ else:
+ port.mac_address = intf.MAC()
+
+ # Start the real emulator command now as specified in the dockerfile
+ # ENV SON_EMU_CMD
+ config = c.dcinfo.get("Config", dict())
+ env = config.get("Env", list())
+ for env_var in env:
+ if "SON_EMU_CMD=" in env_var:
+ cmd = str(env_var.split("=")[1])
+ server.son_emu_command = cmd
+ # execute command in new thread to ensure that GK is not blocked by VNF
+ t = threading.Thread(target=c.cmdPrint, args=(cmd,))
+ t.daemon = True
+ t.start()
+
+ def stop_compute(self, server):
+ """
+ Determines which links should be removed before removing the server itself.
+
+ :param server: The server that should be removed
+ :type server: ``heat.resources.server``
+ """
+ logging.debug("Stopping container %s with full name %s" % (server.name, server.full_name))
+ link_names = list()
+ for port_name in server.port_names:
+ link_names.append(self.find_port_by_name_or_id(port_name).intf_name)
+ my_links = self.dc.net.links
+ for link in my_links:
+ if str(link.intf1) in link_names:
+ # Remove all self created links that connect the server to the main switch
+ self._remove_link(server.name, link)
+
+ # Stop the server and the remaining connection to the datacenter switch
+ self.dc.stopCompute(server.name)
+ # Only now delete all its ports and the server itself
+ for port_name in server.port_names:
+ self.delete_port(port_name)
+ self.delete_server(server)
+
+ def find_server_by_name_or_id(self, name_or_id):
+ """
+ Tries to find the server by ID and if this does not succeed then tries to find it via name.
+
+ :param name_or_id: UUID or name of the server.
+ :type name_or_id: ``str``
+ :return: Returns the server reference if it was found or None
+ :rtype: :class:`heat.resources.server`
+ """
+ if name_or_id in self.computeUnits:
+ return self.computeUnits[name_or_id]
+
+ for server in self.computeUnits.values():
+ if server.name == name_or_id or server.template_name == name_or_id or server.full_name == name_or_id:
+ return server
+ return None
+
+ def create_server(self, name, stack_operation=False):
+ """
+ Creates a server with the specified name. Raises an exception when a server with the given name already
+ exists!
+
+ :param name: Name of the new server.
+ :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 server.
+ :rtype: :class:`heat.resources.server`
+ """
+ if self.find_server_by_name_or_id(name) is not None and not stack_operation:
+ raise Exception("Server with name %s already exists." % name)
+ server = Server(name)
+ server.id = str(uuid.uuid4())
+ if not stack_operation:
+ self.computeUnits[server.id] = server
+ return server
+
+ def delete_server(self, server):
+ """
+ Deletes the given server from the stack dictionary and the computeUnits dictionary.
+
+ :param server: Reference of the server that should be deleted.
+ :type server: :class:`heat.resources.server`
+ :return: * *False*: If the server name is not in the correct format ('datacentername_stackname_servername') \
+ or when no stack with the correct stackname was found.
+ * *True*: Else
+ :rtype: ``bool``
+ """
+ if server is None:
+ return False
+ name_parts = server.name.split('_')
+ if len(name_parts) < 3:
+ return False
+
+ for stack in self.stacks.values():
+ if stack.stack_name == name_parts[1]:
+ stack.servers.pop(server.id, None)
+ if self.computeUnits.pop(server.id, None) is None:
+ return False
+ return True
+
+ def find_network_by_name_or_id(self, name_or_id):
+ """
+ Tries to find the network by ID and if this does not succeed then tries to find it via name.
+
+ :param name_or_id: UUID or name of the network.
+ :type name_or_id: ``str``
+ :return: Returns the network reference if it was found or None
+ :rtype: :class:`heat.resources.net`
+ """
+ if name_or_id in self.nets:
+ return self.nets[name_or_id]
+ for net in self.nets.values():
+ if net.name == name_or_id:
+ return net
+
+ return None
+
+ def create_network(self, name, stack_operation=False):
+ """
+ Creates a new network with the given name. Raises an exception when a network with the given name already
+ exists!
+
+ :param name: Name of the new network.
+ :type name: ``str``
+ :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
+ :type stack_operation: ``bool``
+ :return: :class:`heat.resources.net`
+ """
+ logging.debug("Creating network with name %s" % name)
+ if self.find_network_by_name_or_id(name) is not None and not stack_operation:
+ logging.warning("Creating network with name %s failed, as it already exists" % name)
+ raise Exception("Network with name %s already exists." % name)
+ network = Net(name)
+ network.id = str(uuid.uuid4())
+ if not stack_operation:
+ self.nets[network.id] = network
+ return network
+
+ def delete_network(self, name_or_id):
+ """
+ Deletes the given network.
+
+ :param name_or_id: Name or UUID of the network.
+ :type name_or_id: ``str``
+ """
+ net = self.find_network_by_name_or_id(name_or_id)
+ if net is None:
+ raise Exception("Network with name or id %s does not exists." % name_or_id)
+
+ for stack in self.stacks.values():
+ stack.nets.pop(net.name, None)
+
+ self.nets.pop(net.id, None)
+
+ def create_port(self, name, stack_operation=False):
+ """
+ Creates a new port with the given name. Raises an exception when a port with the given name already
+ exists!
+
+ :param name: Name of the new port.
+ :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.
+ :rtype: :class:`heat.resources.port`
+ """
+ port = self.find_port_by_name_or_id(name)
+ if port is not None and not stack_operation:
+ logging.warning("Creating port with name %s failed, as it already exists" % name)
+ raise Exception("Port with name %s already exists." % name)
+ logging.debug("Creating port with name %s" % name)
+ port = Port(name)
+ if not stack_operation:
+ self.ports[port.id] = port
+ port.create_intf_name()
+ return port
+
+ def find_port_by_name_or_id(self, name_or_id):
+ """
+ Tries to find the port by ID and if this does not succeed then tries to find it via name.
+
+ :param name_or_id: UUID or name of the network.
+ :type name_or_id: ``str``
+ :return: Returns the port reference if it was found or None
+ :rtype: :class:`heat.resources.port`
+ """
+ if name_or_id in self.ports:
+ return self.ports[name_or_id]
+ for port in self.ports.values():
+ if port.name == name_or_id or port.template_name == name_or_id:
+ return port
+
+ return None
+
+ def delete_port(self, name_or_id):
+ """
+ Deletes the given port. Raises an exception when the port was not found!
+
+ :param name_or_id: UUID or name of the port.
+ :type name_or_id: ``str``
+ """
+ port = self.find_port_by_name_or_id(name_or_id)
+ if port is None:
+ raise Exception("Port with name or id %s does not exists." % name_or_id)
+
+ my_links = self.dc.net.links
+ for link in my_links:
+ if str(link.intf1) == port.intf_name and \
+ str(link.intf1.ip) == port.ip_address.split('/')[0]:
+ self._remove_link(link.intf1.node.name, link)
+ break
+
+ self.ports.pop(port.id, None)
+ for stack in self.stacks.values():
+ stack.ports.pop(port.name, 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.
+
+ :param node_name: Name of the required node.
+ :type node_name: ``str``
+ :param ip_address: IP-Address of the node.
+ :type ip_address: ``str``
+ :param link_name: Link name.
+ :type link_name: ``str``
+ :param net_name: Network name.
+ :type net_name: ``str``
+ """
+ node = self.dc.net.get(node_name)
+ params = {'params1': {'ip': ip_address,
+ 'id': link_name,
+ link_name: net_name},
+ 'intfName1': link_name,
+ 'cls': Link}
+ link = self.dc.net.addLink(node, self.dc.switch, **params)
+ OpenstackCompute.timeout_sleep(link.intf1.isUp, 1)
+
+ def _remove_link(self, server_name, link):
+ """
+ Removes a link between server and datacenter switch.
+
+ :param server_name: Specifies the server where the link starts.
+ :type server_name: ``str``
+ :param link: A reference of the link which should be removed.
+ :type link: :class:`mininet.link`
+ """
+ self.dc.switch.detach(link.intf2)
+ del self.dc.switch.intfs[self.dc.switch.ports[link.intf2]]
+ del self.dc.switch.ports[link.intf2]
+ del self.dc.switch.nameToIntf[link.intf2.name]
+ self.dc.net.removeLink(link=link)
+ for intf_key in self.dc.net[server_name].intfs.keys():
+ if self.dc.net[server_name].intfs[intf_key].link == link:
+ self.dc.net[server_name].intfs[intf_key].delete()
+ del self.dc.net[server_name].intfs[intf_key]
+
+ @staticmethod
+ def timeout_sleep(function, max_sleep):
+ """
+ This function will execute a function all 0.1 seconds until it successfully returns.
+ Will return after `max_sleep` seconds if not successful.
+
+ :param function: The function to execute. Should return true if done.
+ :type function: ``function``
+ :param max_sleep: Max seconds to sleep. 1 equals 1 second.
+ :type max_sleep: ``float``
+ """
+ current_time = time.time()
+ stop_time = current_time + max_sleep
+ while not function() and current_time < stop_time:
+ current_time = time.time()
+ time.sleep(0.1)
--- /dev/null
+from docker import DockerClient, APIClient
+import time
+import re
+
+
+def docker_container_id(container_name):
+ """
+ Uses the container name to return the container ID.
+
+ :param container_name: The full name of the docker container.
+ :type container_name: ``str``
+ :return: Returns the container ID or None if the container is not running or could not be found.
+ :rtype: ``dict``
+ """
+ c = APIClient()
+ detail = c.inspect_container(container_name)
+ if bool(detail["State"]["Running"]):
+ return detail['Id']
+ return None
+
+
+def docker_abs_cpu(container_id):
+ """
+ Returns the used CPU time since container startup and the system time in nanoseconds and returns the number
+ of available CPU cores.
+
+ :param container_id: The full ID of the docker container.
+ :type container_id: ``str``
+ :return: Returns a dict with CPU_used in nanoseconds, the current system time in nanoseconds and the number of
+ CPU cores available.
+ :rtype: ``dict``
+ """
+ with open('/sys/fs/cgroup/cpuacct/docker/' + container_id + '/cpuacct.usage_percpu', 'r') as f:
+ line = f.readline()
+ sys_time = int(time.time() * 1000000000)
+ numbers = [int(x) for x in line.split()]
+ cpu_usage = 0
+ for number in numbers:
+ cpu_usage += number
+ return {'CPU_used': cpu_usage, 'CPU_used_systime': sys_time, 'CPU_cores': len(numbers)}
+
+
+def docker_mem_used(container_id):
+ """
+ Bytes of memory used from the docker container.
+
+ Note: If you have problems with this command you have to enable memory control group.
+ For this you have to add the following kernel parameters: `cgroup_enable=memory swapaccount=1`.
+ See: https://docs.docker.com/engine/admin/runmetrics/
+
+ :param container_id: The full ID of the docker container.
+ :type container_id: ``str``
+ :return: Returns the memory utilization in bytes.
+ :rtype: ``str``
+ """
+ with open('/sys/fs/cgroup/memory/docker/' + container_id + '/memory.usage_in_bytes', 'r') as f:
+ return int(f.readline())
+
+
+def docker_max_mem(container_id):
+ """
+ Bytes of memory the docker container could use.
+
+ :param container_id: The full ID of the docker container.
+ :type container_id: ``str``
+ :return: Returns the bytes of memory the docker container could use.
+ :rtype: ``str``
+ """
+ with open('/sys/fs/cgroup/memory/docker/' + container_id + '/memory.limit_in_bytes', 'r') as f:
+ mem_limit = int(f.readline())
+ with open('/proc/meminfo', 'r') as f:
+ line = f.readline().split()
+ sys_value = int(line[1])
+ unit = line[2]
+ if unit == 'kB':
+ sys_value *= 1024
+ if unit == 'MB':
+ sys_value *= 1024 * 1024
+
+ if sys_value < mem_limit:
+ return sys_value
+ else:
+ return mem_limit
+
+
+def docker_mem(container_id):
+ """
+ Calculates the current, maximal and percentage usage of the specified docker container.
+
+ :param container_id: The full ID of the docker container.
+ :type container_id: ``str``
+ :return: Returns a dictionary with the total memory usage, the maximal available memory and the percentage
+ memory usage.
+ :rtype: ``dict``
+ """
+ out_dict = dict()
+ out_dict['MEM_used'] = docker_mem_used(container_id)
+ out_dict['MEM_limit'] = docker_max_mem(container_id)
+ out_dict['MEM_%'] = float(out_dict['MEM_used']) / float(out_dict['MEM_limit'])
+ return out_dict
+
+
+def docker_abs_net_io(container_id):
+ """
+ Network traffic of all network interfaces within the controller.
+
+ :param container_id: The full ID of the docker container.
+ :type container_id: ``str``
+ :return: Returns the absolute network I/O till container startup, in bytes. The return dict also contains the
+ system time.
+ :rtype: ``dict``
+ """
+ c = APIClient()
+ command = c.exec_create(container_id, 'ifconfig')
+ ifconfig = c.exec_start(command['Id'])
+ sys_time = int(time.time() * 1000000000)
+
+ in_bytes = 0
+ m = re.findall('RX bytes:(\d+)', str(ifconfig))
+ if m:
+ for number in m:
+ in_bytes += int(number)
+ else:
+ in_bytes = None
+
+ out_bytes = 0
+ m = re.findall('TX bytes:(\d+)', str(ifconfig))
+ if m:
+ for number in m:
+ out_bytes += int(number)
+ else:
+ out_bytes = None
+
+ return {'NET_in': in_bytes, 'NET_out': out_bytes, 'NET_systime': sys_time}
+
+
+def docker_block_rw(container_id):
+ """
+ Determines the disk read and write access from the controller since startup.
+
+ :param container_id: The full ID of the docker container.
+ :type container_id: ``str``
+ :return: Returns a dictionary with the total disc I/O since container startup, in bytes.
+ :rtype: ``dict``
+ """
+ with open('/sys/fs/cgroup/blkio/docker/' + container_id + '/blkio.throttle.io_service_bytes', 'r') as f:
+ read = f.readline().split()
+ write = f.readline().split()
+ rw_dict = dict()
+ rw_dict['BLOCK_systime'] = int(time.time() * 1000000000)
+ if len(read) < 3:
+ rw_dict['BLOCK_read'] = 0
+ else:
+ rw_dict['BLOCK_read'] = read[2]
+ if len(write) < 3:
+ rw_dict['BLOCK_write'] = 0
+ else:
+ rw_dict['BLOCK_write'] = write[2]
+ return rw_dict
+
+
+def docker_PIDS(container_id):
+ """
+ Determines the number of processes within the docker container.
+
+ :param container_id: The full ID of the docker container.
+ :type container_id: ``str``
+ :return: Returns the number of PIDS within a dictionary.
+ :rtype: ``dict``
+ """
+ with open('/sys/fs/cgroup/cpuacct/docker/' + container_id + '/tasks', 'r') as f:
+ return {'PIDS': len(f.read().split('\n')) - 1}
+
+
+def monitoring_over_time(container_id):
+ """
+ Calculates the cpu workload and the network traffic per second.
+
+ :param container_id: The full docker container ID
+ :type container_id: ``str``
+ :return: A dictionary with disk read and write per second, network traffic per second (in and out),
+ the cpu workload and the number of cpu cores available.
+ :rtype: ``dict``
+ """
+ first_cpu_usage = docker_abs_cpu(container_id)
+ first = docker_abs_net_io(container_id)
+ first_disk_io = docker_block_rw(container_id)
+ time.sleep(1)
+ second_cpu_usage = docker_abs_cpu(container_id)
+ second = docker_abs_net_io(container_id)
+ second_disk_io = docker_block_rw(container_id)
+
+ # Disk access
+ time_div = (int(second_disk_io['BLOCK_systime']) - int(first_disk_io['BLOCK_systime']))
+ read_div = int(second_disk_io['BLOCK_read']) - int(first_disk_io['BLOCK_read'])
+ write_div = int(second_disk_io['BLOCK_write']) - int(first_disk_io['BLOCK_write'])
+ out_dict = {'BLOCK_read/s': int(read_div * 1000000000 / float(time_div) + 0.5),
+ 'BLOCK_write/s': int(write_div * 1000000000 / float(time_div) + 0.5)}
+
+ # Network traffic
+ time_div = (int(second['NET_systime']) - int(first['NET_systime']))
+ in_div = int(second['NET_in']) - int(first['NET_in'])
+ out_div = int(second['NET_out']) - int(first['NET_out'])
+ out_dict.update({'NET_in/s': int(in_div * 1000000000 / float(time_div) + 0.5),
+ 'NET_out/s': int(out_div * 1000000000 / float(time_div) + 0.5)})
+
+ # CPU utilization
+ time_div = (int(second_cpu_usage['CPU_used_systime']) - int(first_cpu_usage['CPU_used_systime']))
+ usage_div = int(second_cpu_usage['CPU_used']) - int(first_cpu_usage['CPU_used'])
+ out_dict.update({'CPU_%': usage_div / float(time_div), 'CPU_cores': first_cpu_usage['CPU_cores']})
+ return out_dict
--- /dev/null
+from __future__ import print_function # TODO remove when print is no longer needed for debugging
+from resources import *
+from datetime import datetime
+import re
+import sys
+import uuid
+import logging
+import ip_handler as IP
+
+
+class HeatParser:
+ """
+ The HeatParser will parse a heat dictionary and create a stack and its components, to instantiate it within son-emu.
+ """
+
+ def __init__(self, compute):
+ self.description = None
+ self.parameter_groups = None
+ self.parameters = None
+ self.resources = None
+ self.outputs = None
+ self.compute = compute
+ self.bufferResource = list()
+
+ def parse_input(self, input_dict, stack, dc_label, stack_update=False):
+ """
+ It will parse the input dictionary into the corresponding classes, which are then stored within the stack.
+
+ :param input_dict: Dictionary with the template version and resources.
+ :type input_dict: ``dict``
+ :param stack: Reference of the stack that should finally contain all created classes.
+ :type stack: :class:`heat.resources.stack`
+ :param dc_label: String that contains the label of the used data center.
+ :type dc_label: ``str``
+ :param stack_update: Specifies if a new stack will be created or a older one will be updated
+ :type stack_update: ``bool``
+ :return: * *True*: If the template version is supported and all resources could be created.
+ * *False*: Else
+ :rtype: ``bool``
+ """
+ if not self.check_template_version(str(input_dict['heat_template_version'])):
+ print('Unsupported template version: ' + input_dict['heat_template_version'], file=sys.stderr)
+ return False
+
+ self.description = input_dict.get('description', None)
+ self.parameter_groups = input_dict.get('parameter_groups', None)
+ self.parameters = input_dict.get('parameters', None)
+ self.resources = input_dict.get('resources', None)
+ self.outputs = input_dict.get('outputs', None)
+ # clear bufferResources
+ self.bufferResource = list()
+
+ for resource in self.resources.values():
+ self.handle_resource(resource, stack, dc_label, stack_update=stack_update)
+
+ # This loop tries to create all classes which had unresolved dependencies.
+ unresolved_resources_last_round = len(self.bufferResource) + 1
+ while len(self.bufferResource) > 0 and unresolved_resources_last_round > len(self.bufferResource):
+ unresolved_resources_last_round = len(self.bufferResource)
+ number_of_items = len(self.bufferResource)
+ while number_of_items > 0:
+ self.handle_resource(self.bufferResource.pop(0), stack, dc_label, stack_update=stack_update)
+ number_of_items -= 1
+
+ if len(self.bufferResource) > 0:
+ print(str(len(self.bufferResource)) +
+ ' classes could not be created, because the dependencies could not be found.')
+ return False
+ return True
+
+ def handle_resource(self, resource, stack, dc_label, stack_update=False):
+ """
+ This function will take a resource (from a heat template) and determines which type it is and creates
+ the corresponding class, with its required parameters, for further calculations (like deploying the stack).
+ If it is not possible to create the class, because of unresolved dependencies, it will buffer the resource
+ within the 'self.bufferResource' list.
+
+ :param resource: Dict which contains all important informations about the type and parameters.
+ :type resource: ``dict``
+ :param stack: Reference of the stack that should finally contain the created class.
+ :type stack: :class:`heat.resources.stack`
+ :param dc_label: String that contains the label of the used data center
+ :type dc_label: ``str``
+ :param stack_update: Specifies if a new stack will be created or a older one will be updated
+ :type stack_update: ``bool``
+ :return: void
+ :rtype: ``None``
+ """
+ if "OS::Neutron::Net" in resource['type']:
+ try:
+ net_name = resource['properties']['name']
+ if net_name not in stack.nets:
+ stack.nets[net_name] = self.compute.create_network(net_name, True)
+
+ except Exception as e:
+ logging.warning('Could not create Net: ' + e.message)
+ return
+
+ if 'OS::Neutron::Subnet' in resource['type'] and "Net" not in resource['type']:
+ try:
+ net_name = resource['properties']['network']['get_resource']
+ if net_name not in stack.nets:
+ net = self.compute.create_network(net_name, stack_update)
+ stack.nets[net_name] = net
+ else:
+ net = stack.nets[net_name]
+
+ net.subnet_name = resource['properties']['name']
+ if 'gateway_ip' in resource['properties']:
+ net.gateway_ip = resource['properties']['gateway_ip']
+ net.subnet_id = resource['properties'].get('id', str(uuid.uuid4()))
+ net.subnet_creation_time = str(datetime.now())
+ if not stack_update:
+ net.set_cidr(IP.get_new_cidr(net.subnet_id))
+ except Exception as e:
+ logging.warning('Could not create Subnet: ' + e.message)
+ return
+
+ if 'OS::Neutron::Port' in resource['type']:
+ try:
+ port_name = resource['properties']['name']
+ if port_name not in stack.ports:
+ port = self.compute.create_port(port_name, stack_update)
+ stack.ports[port_name] = port
+ else:
+ port = stack.ports[port_name]
+
+ if resource['properties']['network']['get_resource'] in stack.nets:
+ net = stack.nets[resource['properties']['network']['get_resource']]
+ if net.subnet_id is not None:
+ port.net_name = net.name
+ port.ip_address = net.get_new_ip_address(port.name)
+ return
+ except Exception as e:
+ logging.warning('Could not create Port: ' + e.message)
+ self.bufferResource.append(resource)
+ return
+
+ if 'OS::Nova::Server' in resource['type']:
+ try:
+ compute_name = str(dc_label) + '_' + str(stack.stack_name) + '_' + str(resource['properties']['name'])
+ shortened_name = str(dc_label) + '_' + str(stack.stack_name) + '_' + \
+ self.shorten_server_name(str(resource['properties']['name']), stack)
+ nw_list = resource['properties']['networks']
+
+ if shortened_name not in stack.servers:
+ server = self.compute.create_server(shortened_name, stack_update)
+ stack.servers[shortened_name] = server
+ else:
+ server = stack.servers[shortened_name]
+
+ server.full_name = compute_name
+ server.template_name = str(resource['properties']['name'])
+ server.command = resource['properties'].get('command', '/bin/sh')
+ server.image = resource['properties']['image']
+ server.flavor = resource['properties']['flavor']
+
+ for port in nw_list:
+ port_name = port['port']['get_resource']
+ # just create a port
+ # we don't know which network it belongs to yet, but the resource will appear later in a valid
+ # template
+ if port_name not in stack.ports:
+ stack.ports[port_name] = self.compute.create_port(port_name, stack_update)
+ server.port_names.append(port_name)
+ return
+ except Exception as e:
+ logging.warning('Could not create Server: ' + e.message)
+ return
+
+ if 'OS::Neutron::RouterInterface' in resource['type']:
+ try:
+ router_name = None
+ subnet_name = resource['properties']['subnet']['get_resource']
+
+ if 'get_resource' in resource['properties']['router']:
+ router_name = resource['properties']['router']['get_resource']
+ else:
+ router_name = resource['properties']['router']
+
+ if router_name not in stack.routers:
+ stack.routers[router_name] = Router(router_name)
+
+ for tmp_net in stack.nets.values():
+ if tmp_net.subnet_name == subnet_name:
+ stack.routers[router_name].add_subnet(subnet_name)
+ return
+ except Exception as e:
+ logging.warning('Could not create RouterInterface: ' + e.__repr__())
+ self.bufferResource.append(resource)
+ return
+
+ if 'OS::Neutron::FloatingIP' in resource['type']:
+ try:
+ port_name = resource['properties']['port_id']['get_resource']
+ floating_network_id = resource['properties']['floating_network_id']
+ if port_name not in stack.ports:
+ stack.ports[port_name] = self.compute.create_port(port_name, stack_update)
+
+ stack.ports[port_name].floating_ip = floating_network_id
+ except Exception as e:
+ logging.warning('Could not create FloatingIP: ' + e.message)
+ return
+
+ if 'OS::Neutron::Router' in resource['type']:
+ try:
+ name = resource['properties']['name']
+ if name not in stack.routers:
+ stack.routers[name] = Router(name)
+ except Exception as e:
+ print('Could not create Router: ' + e.message)
+ return
+
+ logging.warning('Could not determine resource type!')
+ return
+
+ def shorten_server_name(self, server_name, stack):
+ """
+ Shortens the server name to a maximum of 12 characters plus the iterator string, if the original name was
+ used before.
+
+ :param server_name: The original server name.
+ :type server_name: ``str``
+ :param stack: A reference to the used stack.
+ :type stack: :class:`heat.resources.stack`
+ :return: A string with max. 12 characters plus iterator string.
+ :rtype: ``str``
+ """
+ server_name = self.shorten_name(server_name, 12)
+ iterator = 0
+ while server_name in stack.servers:
+ server_name = server_name[0:12] + str(iterator)
+ iterator += 1
+ return server_name
+
+ def shorten_name(self, name, max_size):
+ """
+ Shortens the name to max_size characters and replaces all '-' with '_'.
+
+ :param name: The original string.
+ :type name: ``str``
+ :param max_size: The number of allowed characters.
+ :type max_size: ``int``
+ :return: String with at most max_size characters and without '-'.
+ :rtype: ``str``
+ """
+ shortened_name = name.split(':', 1)[0]
+ shortened_name = shortened_name.replace("-", "_")
+ shortened_name = shortened_name[0:max_size]
+ return shortened_name
+
+ def check_template_version(self, version_string):
+ """
+ Checks if a version string is equal or later than 30-04-2015
+
+ :param version_string: String with the version.
+ :type version_string: ``str``
+ :return: * *True*: if the version is equal or later 30-04-2015.
+ * *False*: else
+ :rtype: ``bool``
+ """
+ r = re.compile('\d{4}-\d{2}-\d{2}')
+ if not r.match(version_string):
+ return False
+
+ year, month, day = map(int, version_string.split('-', 2))
+ if year < 2015:
+ return False
+ if year == 2015:
+ if month < 04:
+ return False
+ if month == 04 and day < 30:
+ return False
+ return True
--- /dev/null
+from resources.net import Net
+import threading
+
+lock = threading.Lock()
+
+__issued_ips = dict()
+__default_subnet_size = 256
+__default_subnet_bitmask = 24
+__first_ip = Net.ip_2_int('10.0.0.0')
+__last_ip = Net.ip_2_int('10.255.255.255')
+__current_ip = __first_ip
+
+
+def get_new_cidr(uuid):
+ """
+ Calculates a unused cidr for a subnet.
+
+ :param uuid: The UUID of the subnet - Thus it can store which subnet gets which CIDR
+ :type uuid: ``str``
+ :return: Returns None if all available CIDR are used. Otherwise returns a valid CIDR.
+ :rtype: ``str``
+ """
+ global lock
+ lock.acquire()
+
+ global __current_ip
+ while __first_ip <= __current_ip < __last_ip and __current_ip in __issued_ips:
+ __current_ip += __default_subnet_size
+
+ if __current_ip >= __last_ip or __current_ip < __first_ip or __current_ip in __issued_ips:
+ return None
+
+ __issued_ips[__current_ip] = uuid
+ lock.release()
+
+ return Net.int_2_ip(__current_ip) + '/' + str(__default_subnet_bitmask)
+
+
+def free_cidr(cidr, uuid):
+ """
+ Frees a issued CIDR thus it can be reused.
+
+ :param cidr: The currently used CIDR.
+ :type cidr: ``str``
+ :param uuid: The UUID of the Subnet, which uses this CIDR.
+ :type uuid: ``str``
+ :return: Returns False if the CIDR is None or the UUID did not correspond tho the used CIDR. Else it returns True.
+ :rtype: ``bool``
+ """
+ if cidr is None:
+ return False
+
+ global __current_ip
+ int_ip = Net.cidr_2_int(cidr)
+
+ global lock
+ lock.acquire()
+
+ if int_ip in __issued_ips and __issued_ips[int_ip] == uuid:
+ del __issued_ips[int_ip]
+ if int_ip < __current_ip:
+ __current_ip = int_ip
+ lock.release()
+ return True
+ lock.release()
+ return False
+
+
+def is_cidr_issued(cidr):
+ """
+ Returns True if the CIDR is used.
+
+ :param cidr: The requested CIDR.
+ :type cidr: ``str``
+ :return: Returns True if the CIDR is used, else False.
+ :rtype: ``bool``
+ """
+ if cidr is None:
+ return False
+
+ int_ip = Net.cidr_2_int(cidr)
+
+ if int_ip in __issued_ips:
+ return True
+ return False
+
+
+def is_my_cidr(cidr, uuid):
+ """
+ Checks if the UUID and the used CIDR are related.
+
+ :param cidr: The issued CIDR.
+ :type cidr: ``str``
+ :param uuid: The Subnet UUID.
+ :type uuid: ``str``
+ :return: Returns False if the CIDR is None or if the CIDR is not issued. Else returns True.
+ :rtype: ``bool``
+ """
+ if cidr is None:
+ return False
+
+ int_ip = Net.cidr_2_int(cidr)
+
+ if not int_ip in __issued_ips:
+ return False
+
+ if __issued_ips[int_ip] == uuid:
+ return True
+ return False
+
+
+def assign_cidr(cidr, uuid):
+ """
+ Allows a subnet to request a specific CIDR.
+
+ :param cidr: The requested CIDR.
+ :type cidr: ``str``
+ :param uuid: The Subnet UUID.
+ :type uuid: ``str``
+ :return: Returns False if the CIDR is None or if the CIDR is already issued. Returns True if the CIDR could be
+ assigned to the UUID.
+ """
+ if cidr is None:
+ return False
+
+ int_ip = Net.cidr_2_int(cidr)
+
+ if int_ip in __issued_ips:
+ return False
+
+ global lock
+ lock.acquire()
+ __issued_ips[int_ip] = uuid
+ lock.release()
+ return True
--- /dev/null
+"""Openstack manage component of PG Sandman.
+
+.. module:: manage
+ :synopsis: Module containing the OpenstackManage class.
+.. moduleauthor: PG Sandman
+
+"""
+
+import logging
+import threading
+import uuid
+import networkx as nx
+import chain_api
+import json
+import random
+from emuvim.api.openstack.resources import Net, Port
+from mininet.node import OVSSwitch, RemoteController, Node
+
+
+class OpenstackManage(object):
+ """
+ OpenstackManage is a singleton and management component for the emulator.
+ It is the brain of the Openstack component and manages everything that is not datacenter specific like
+ network chains or load balancers.
+ """
+ __instance = None
+
+ def __new__(cls):
+ if OpenstackManage.__instance is None:
+ OpenstackManage.__instance = object.__new__(cls)
+ return OpenstackManage.__instance
+
+ def __init__(self, ip="0.0.0.0", port=4000):
+ # we are a singleton, only initialize once!
+ self.lock = threading.Lock()
+ with self.lock:
+ if hasattr(self, "init"):
+ return
+ self.init = True
+
+ self.endpoints = dict()
+ self.cookies = set()
+ self.cookies.add(0)
+ self.ip = ip
+ self.port = port
+ self._net = None
+ # to keep track which src_vnf(input port on the switch) handles a load balancer
+ self.lb_flow_cookies = dict()
+ self.chain_flow_cookies = dict()
+
+ # for the visualization also store the complete chain data incl. paths
+ self.full_chain_data = dict()
+ self.full_lb_data = dict()
+
+ # flow groups could be handled for each switch separately, but this global group counter should be easier to
+ # debug and to maintain
+ self.flow_groups = dict()
+
+ # we want one global chain api. this should not be datacenter dependent!
+ self.chain = chain_api.ChainApi(ip, port, self)
+ self.thread = threading.Thread(target=self.chain._start_flask, args=())
+ self.thread.daemon = True
+ self.thread.name = self.chain.__class__
+ self.thread.start()
+
+ # floating ip network setup
+ self.floating_switch = None
+ self.floating_network = None
+ self.floating_netmask = "192.168.100.0/24"
+ self.floating_nodes = dict()
+ self.floating_cookies = dict()
+ self.floating_intf = None
+ self.floating_links = dict()
+
+ @property
+ def net(self):
+ return self._net
+
+ @net.setter
+ def net(self, value):
+ if self._net is None:
+ self._net = value
+ self.init_floating_network()
+ self._net = value
+
+ def init_floating_network(self):
+ """
+ Initialize the floating network component for the emulator.
+ Will not do anything if already initialized.
+ """
+ if self.net is not None and self.floating_switch is None:
+ # create a floating network
+ fn = self.floating_network = Net("default")
+ fn.id = str(uuid.uuid4())
+ fn.set_cidr(self.floating_netmask)
+
+ # create a subnet
+ fn.subnet_id = str(uuid.uuid4())
+ fn.subnet_name = fn.name + "-sub"
+
+ # create a port for the host
+ port = Port("root-port")
+ #port.id = str(uuid.uuid4())
+ port.net_name = fn.name
+
+ # get next free ip
+ root_ip = fn.get_new_ip_address(port.name)
+ port.ip_address = root_ip
+ # floating ip network setup
+ # wierd way of getting a datacenter object
+ first_dc = self.net.dcs.values()[0]
+ # set a dpid for the switch. for this we have to get the id of the next possible dc
+ self.floating_switch = self.net.addSwitch("fs1", dpid=hex(first_dc._get_next_dc_dpid())[2:])
+ # this is the interface appearing on the physical host
+ self.floating_root = Node('root', inNamespace=False)
+ self.net.hosts.append(self.floating_root)
+ self.net.nameToNode['root'] = self.floating_root
+ self.floating_intf = self.net.addLink(self.floating_root, self.floating_switch).intf1
+ self.floating_root.setIP(root_ip, intf=self.floating_intf)
+ self.floating_nodes[(self.floating_root.name, root_ip)] = self.floating_root
+
+
+ def stop_floating_network(self):
+ self._net = None
+ self.floating_switch = None
+
+ def add_endpoint(self, ep):
+ """
+ Registers an openstack endpoint with manage
+
+ :param ep: Openstack API endpoint
+ :type ep: :class:`heat.openstack_api_endpoint`
+ """
+ key = "%s:%s" % (ep.ip, ep.port)
+ self.endpoints[key] = ep
+
+ def get_cookie(self):
+ """
+ Get an unused cookie.
+
+ :return: Cookie
+ :rtype: ``int``
+ """
+ cookie = int(max(self.cookies) + 1)
+ self.cookies.add(cookie)
+ return cookie
+
+ def get_flow_group(self, src_vnf_name, src_vnf_interface):
+ """
+ Gets free group that is not currently used by any other flow for the specified interface / VNF.
+
+ :param src_vnf_name: Source VNF name
+ :type src_vnf_name: ``str``
+ :param src_vnf_interface: Source VNF interface name
+ :type src_vnf_interface: ``str``
+ :return: Flow group identifier.
+ :rtype: ``int``
+ """
+ if (src_vnf_name, src_vnf_interface) not in self.flow_groups:
+ grp = int(len(self.flow_groups) + 1)
+ self.flow_groups[(src_vnf_name, src_vnf_interface)] = grp
+ else:
+ grp = self.flow_groups[(src_vnf_name, src_vnf_interface)]
+ return grp
+
+ def check_vnf_intf_pair(self, vnf_name, vnf_intf_name):
+ """
+ Checks if a VNF exists and has the given interface
+
+ :param vnf_name: Name of the VNF to be checked
+ :type vnf_name: ``str``
+ :param vnf_intf_name: Name of the interface that belongst to the VNF
+ :type vnf_intf_name: ``str``
+ :return: ``True`` if it is valid pair, else ``False``
+ :rtype: ``bool``
+ """
+
+ if vnf_name in self.net:
+ vnf = self.net.getNodeByName(vnf_name)
+ return vnf_intf_name in vnf.nameToIntf
+
+ def network_action_start(self, vnf_src_name, vnf_dst_name, **kwargs):
+ """
+ Starts a network chain for a source destination pair
+
+ :param vnf_src_name: Name of the source VNF
+ :type vnf_src_name: ``str``
+ :param vnf_dst_name: Name of the source VNF interface
+ :type vnf_dst_name: ``str``
+ :param \**kwargs: See below
+
+ :Keyword Arguments:
+ * *vnf_src_interface* (``str``): Name of source interface.
+ * *vnf_dst_interface* (``str``): Name of destination interface.
+ * *weight* (``int``): This value is fed into the shortest path computation if no path is specified.
+ * *match* (``str``): A custom match entry for the openflow flow rules. Only vlanid or port possible.
+ * *bidirectional* (``bool``): If set the chain will be set in both directions, else it will just set up \
+ from source to destination.
+ * *cookie* (``int``): Cookie value used by openflow. Used to identify the flows in the switches to be \
+ able to modify the correct flows.
+ * *no_route* (``bool``): If set a layer 3 route to the target interface will not be set up.
+ :return: The cookie chosen for the flow.
+ :rtype: ``int``
+ """
+ try:
+ vnf_src_interface = kwargs.get('vnf_src_interface')
+ vnf_dst_interface = kwargs.get('vnf_dst_interface')
+ layer2 = kwargs.get('layer2', True)
+ match = kwargs.get('match')
+ flow = (vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
+ if flow in self.chain_flow_cookies:
+ raise Exception("There is already a chain at the specified src/dst pair!")
+ # set up a layer 2 chain, this allows multiple chains for the same interface
+ src_node = self.net.getNodeByName(vnf_src_name)
+ dst_node = self.net.getNodeByName(vnf_dst_name)
+ dst_intf = dst_node.intf(vnf_dst_interface)
+ if layer2:
+ switch, inport = self._get_connected_switch_data(vnf_src_name, vnf_src_interface)
+ self.setup_arp_reply_at(switch, inport, dst_intf.IP(), dst_intf.MAC())
+ if isinstance(match, str):
+ match += ",dl_dst=%s" % dst_intf.MAC()
+ else:
+ match = "dl_dst=%s" % dst_intf.MAC()
+
+ cookie = kwargs.get('cookie', self.get_cookie())
+ self.cookies.add(cookie)
+ c = self.net.setChain(
+ vnf_src_name, vnf_dst_name,
+ vnf_src_interface=vnf_src_interface,
+ vnf_dst_interface=vnf_dst_interface,
+ cmd='add-flow',
+ weight=kwargs.get('weight'),
+ match=match,
+ bidirectional=False,
+ cookie=cookie,
+ path=kwargs.get('path'))
+
+ # to keep this logic seperate of the core son-emu do the housekeeping here
+ data = dict()
+ data["src_vnf"] = vnf_src_name
+ data["src_intf"] = vnf_src_interface
+ data["dst_vnf"] = vnf_dst_name
+ data["dst_intf"] = vnf_dst_interface
+ data["cookie"] = cookie
+ data["layer2"] = layer2
+ if kwargs.get('path') is not None:
+ data["path"] = kwargs.get('path')
+ else:
+ data["path"] = self._get_path(vnf_src_name, vnf_dst_name, vnf_src_interface,
+ vnf_dst_interface)[0]
+
+ # add route to dst ip to this interface
+ # this might block on containers that are still setting up, so start a new thread
+ if not kwargs.get('no_route'):
+ # son_emu does not like concurrent commands for a container so we need to lock this if multiple chains
+ # on the same interface are created
+ src_node.setHostRoute(dst_node.intf(vnf_dst_interface).IP(), vnf_src_interface)
+
+ try:
+ son_emu_data = json.loads(self.get_son_emu_chain_data(vnf_src_name))
+ except:
+ son_emu_data = dict()
+ if "son_emu_data" not in son_emu_data:
+ son_emu_data["son_emu_data"] = dict()
+ if "interfaces" not in son_emu_data["son_emu_data"]:
+ son_emu_data["son_emu_data"]["interfaces"] = dict()
+ if vnf_src_interface not in son_emu_data["son_emu_data"]["interfaces"]:
+ son_emu_data["son_emu_data"]["interfaces"][vnf_src_interface] = list()
+ son_emu_data["son_emu_data"]["interfaces"][vnf_src_interface].append(dst_intf.IP())
+
+ self.set_son_emu_chain_data(vnf_src_name, son_emu_data)
+
+ if kwargs.get('bidirectional', False):
+ # call the reverse direction
+ path = kwargs.get('path')
+ if path is not None:
+ path = list(reversed(path))
+ self.network_action_start(vnf_dst_name, vnf_src_name, vnf_src_interface=vnf_dst_interface,
+ vnf_dst_interface=vnf_src_interface, bidirectional=False,
+ layer2=kwargs.get('layer2', False), path=path,
+ no_route=kwargs.get('no_route'))
+
+ self.full_chain_data[flow] = data
+ self.chain_flow_cookies[flow] = cookie
+ return cookie
+ except Exception as ex:
+ logging.exception("RPC error.")
+ raise Exception(ex.message)
+
+ def network_action_stop(self, vnf_src_name, vnf_dst_name, **kwargs):
+ """
+ Starts a network chain for a source destination pair
+
+ :param vnf_src_name: Name of the source VNF
+ :type vnf_src_name: ``str``
+ :param vnf_dst_name: Name of the source VNF interface
+ :type vnf_dst_name: ``str``
+ :param \**kwargs: See below
+
+ :Keyword Arguments:
+ * *vnf_src_interface* (``str``): Name of source interface.
+ * *vnf_dst_interface* (``str``): Name of destination interface.
+ * *bidirectional* (``bool``): If set the chain will be torn down in both directions, else it will just\
+ be torn down from source to destination.
+ * *cookie* (``int``): Cookie value used by openflow. Used to identify the flows in the switches to be \
+ able to modify the correct flows.
+ """
+ try:
+ if 'cookie' in kwargs:
+ return self.delete_flow_by_cookie(kwargs.get('cookie'))
+
+ if kwargs.get('bidirectional', False):
+ self.delete_chain_by_intf(vnf_dst_name, kwargs.get('vnf_dst_interface'),
+ vnf_src_name, kwargs.get('vnf_src_interface'))
+
+ return self.delete_chain_by_intf(vnf_src_name, kwargs.get('vnf_src_interface'),
+ vnf_dst_name, kwargs.get('vnf_dst_interface'))
+ except Exception as ex:
+ logging.exception("RPC error.")
+ return ex.message
+
+ def set_son_emu_chain_data(self, vnf_name, data):
+ """
+ Set son-emu chain data for this node.
+
+ :param vnf_name: The name of the vnf where the data is stored.
+ :type vnf_name: ``str``
+ :param data: Raw data to store on the node.
+ :type data: ``str``
+ """
+ self.net.getNodeByName(vnf_name).cmd("echo \'%s\' > /tmp/son_emu_data.json" % json.dumps(data))
+ ip_list = []
+ for intf in data['son_emu_data']['interfaces'].values():
+ ip_list.extend(intf)
+
+ self.net.getNodeByName(vnf_name).cmd("echo \'%s\' > /tmp/son_emu_data" % "\n".join(ip_list))
+
+ def get_son_emu_chain_data(self, vnf_name):
+ """
+ Get the current son-emu chain data set for this node.
+
+ :param vnf_name: The name of the vnf where the data is stored.
+ :type vnf_name: ``str``
+ :return: raw data stored on the node
+ :rtype: ``str``
+ """
+ return self.net.getNodeByName(vnf_name).cmd("cat /tmp/son_emu_data.json")
+
+ def _get_connected_switch_data(self, vnf_name, vnf_interface):
+ """
+ Get the switch an interface is connected to
+ :param vnf_name: Name of the VNF
+ :type vnf_name: ``str``
+ :param vnf_interface: Name of the VNF interface
+ :type vnf_interface: ``str``
+ :return: List containing the switch, and the inport number
+ :rtype: [``str``, ``int``]
+ """
+ src_sw = None
+ src_sw_inport_nr = None
+ for connected_sw in self.net.DCNetwork_graph.neighbors(vnf_name):
+ link_dict = self.net.DCNetwork_graph[vnf_name][connected_sw]
+ for link in link_dict:
+ if (link_dict[link]['src_port_id'] == vnf_interface or
+ link_dict[link][
+ 'src_port_name'] == vnf_interface):
+ # found the right link and connected switch
+ src_sw = connected_sw
+ src_sw_inport_nr = link_dict[link]['dst_port_nr']
+ break
+
+ return src_sw, src_sw_inport_nr
+
+ def _get_path(self, src_vnf, dst_vnf, src_vnf_intf, dst_vnf_intf):
+ """
+ Own implementation of the get_path function from DCNetwork, because we just want the path and not set up
+ flows on the way.
+
+ :param src_vnf: Name of the source VNF
+ :type src_vnf: ``str``
+ :param dst_vnf: Name of the destination VNF
+ :type dst_vnf: ``str``
+ :param src_vnf_intf: Name of the source VNF interface
+ :type src_vnf_intf: ``str``
+ :param dst_vnf_intf: Name of the destination VNF interface
+ :type dst_vnf_intf: ``str``
+ :return: path, src_sw, dst_sw
+ :rtype: ``list``, ``str``, ``str``
+ """
+ # modified version of the _chainAddFlow from emuvim.dcemulator.net._chainAddFlow
+ src_sw = None
+ dst_sw = None
+ logging.debug("Find shortest path from vnf %s to %s",
+ src_vnf, dst_vnf)
+
+ for connected_sw in self.net.DCNetwork_graph.neighbors(src_vnf):
+ link_dict = self.net.DCNetwork_graph[src_vnf][connected_sw]
+ for link in link_dict:
+ if (link_dict[link]['src_port_id'] == src_vnf_intf or
+ link_dict[link][
+ 'src_port_name'] == src_vnf_intf):
+ # found the right link and connected switch
+ src_sw = connected_sw
+ break
+
+ for connected_sw in self.net.DCNetwork_graph.neighbors(dst_vnf):
+ link_dict = self.net.DCNetwork_graph[connected_sw][dst_vnf]
+ for link in link_dict:
+ if link_dict[link]['dst_port_id'] == dst_vnf_intf or \
+ link_dict[link][
+ 'dst_port_name'] == dst_vnf_intf:
+ # found the right link and connected
+ dst_sw = connected_sw
+ break
+ logging.debug("From switch %s to %s " % (src_sw, dst_sw))
+
+ # get shortest path
+ try:
+ # returns the first found shortest path
+ # if all shortest paths are wanted, use: all_shortest_paths
+ path = nx.shortest_path(self.net.DCNetwork_graph, src_sw, dst_sw)
+ except:
+ logging.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
+ src_vnf, dst_vnf, src_sw, dst_sw))
+ logging.debug("Graph nodes: %r" % self.net.DCNetwork_graph.nodes())
+ logging.debug("Graph edges: %r" % self.net.DCNetwork_graph.edges())
+ for e, v in self.net.DCNetwork_graph.edges():
+ logging.debug("%r" % self.net.DCNetwork_graph[e][v])
+ return "No path could be found between {0} and {1}".format(src_vnf, dst_vnf)
+
+ logging.info("Shortest path between {0} and {1}: {2}".format(src_vnf, dst_vnf, path))
+ return path, src_sw, dst_sw
+
+ def add_loadbalancer(self, src_vnf_name, src_vnf_interface, lb_data):
+ """
+ This function will set up a loadbalancer at the given interface.
+
+ :param src_vnf_name: Name of the source VNF
+ :type src_vnf_name: ``str``
+ :param src_vnf_interface: Name of the destination VNF
+ :type src_vnf_interface: ``str``
+ :param lb_data: A dictionary containing the destination data as well as custom path settings
+ :type lb_data: ``dict``
+
+ :Example:
+ lbdata = {"dst_vnf_interfaces": {"dc2_man_web0": "port-man-2",
+ "dc3_man_web0": "port-man-4","dc4_man_web0": "port-man-6"}, "path": {"dc2_man_web0": {"port-man-2": [ "dc1.s1",\
+ "s1", "dc2.s1"]}}}
+ """
+ net = self.net
+ src_sw_inport_nr = 0
+ src_sw = None
+ dest_intfs_mapping = lb_data.get('dst_vnf_interfaces', dict())
+ # a custom path can be specified as a list of switches
+ custom_paths = lb_data.get('path', dict())
+ dest_vnf_outport_nrs = list()
+
+ logging.debug("Call to add_loadbalancer at %s intfs:%s" % (src_vnf_name, src_vnf_interface))
+
+ if not self.check_vnf_intf_pair(src_vnf_name, src_vnf_interface):
+ raise Exception(u"Source VNF %s or intfs %s does not exist" % (src_vnf_name, src_vnf_interface))
+
+ # find the switch belonging to the source interface, as well as the inport nr
+ for connected_sw in net.DCNetwork_graph.neighbors(src_vnf_name):
+ link_dict = net.DCNetwork_graph[src_vnf_name][connected_sw]
+ for link in link_dict:
+ if link_dict[link]['src_port_name'] == src_vnf_interface:
+ src_sw = connected_sw
+ src_sw_inport_nr = link_dict[link]['dst_port_nr']
+ break
+
+ if src_sw is None or src_sw_inport_nr == 0:
+ raise Exception(u"Source VNF or interface can not be found.")
+
+ # get all target interface outport numbers
+ for vnf_name in dest_intfs_mapping:
+ if vnf_name not in net.DCNetwork_graph:
+ raise Exception(u"Target VNF %s is not known." % vnf_name)
+ for connected_sw in net.DCNetwork_graph.neighbors(vnf_name):
+ link_dict = net.DCNetwork_graph[vnf_name][connected_sw]
+ for link in link_dict:
+ if link_dict[link]['src_port_name'] == dest_intfs_mapping[vnf_name]:
+ dest_vnf_outport_nrs.append(int(link_dict[link]['dst_port_nr']))
+ # get first switch
+ if (src_vnf_name, src_vnf_interface) not in self.lb_flow_cookies:
+ self.lb_flow_cookies[(src_vnf_name, src_vnf_interface)] = list()
+
+ src_intf = None
+ src_ip = None
+ src_mac = None
+ for intf in net[src_vnf_name].intfs.values():
+ if intf.name == src_vnf_interface:
+ src_mac = intf.mac
+ src_ip = intf.ip
+ src_intf = intf
+
+ # set up paths for each destination vnf individually
+ index = 0
+ cookie = self.get_cookie()
+ main_cmd = "add-flow -OOpenFlow13"
+ self.lb_flow_cookies[(src_vnf_name, src_vnf_interface)].append(cookie)
+
+ # bookkeeping
+ data = dict()
+ data["src_vnf"] = src_vnf_name
+ data["src_intf"] = src_vnf_interface
+ data["paths"] = list()
+ data["cookie"] = cookie
+
+ # lb mac for src -> target connections
+ lb_mac = "31:33:70:%02x:%02x:%02x" % (random.randint(0, 255),random.randint(0, 255),random.randint(0, 255))
+
+ # calculate lb ip as src_intf.ip +1
+ octets = src_ip.split('.')
+ octets[3] = str(int(octets[3]) + 1)
+ plus_one = '.'.join(octets)
+
+ # set up arp reply as well as add the route to the interface
+ self.setup_arp_reply_at(src_sw, src_sw_inport_nr, plus_one, lb_mac, cookie=cookie)
+ net.getNodeByName(src_vnf_name).setHostRoute(plus_one, src_vnf_interface)
+
+ for dst_vnf_name, dst_vnf_interface in dest_intfs_mapping.items():
+ path, src_sw, dst_sw = self._get_path(src_vnf_name, dst_vnf_name,
+ src_vnf_interface, dst_vnf_interface)
+
+ # use custom path if one is supplied
+ # json does not support hashing on tuples so we use nested dicts
+ if custom_paths is not None and dst_vnf_name in custom_paths:
+ if dst_vnf_interface in custom_paths[dst_vnf_name]:
+ path = custom_paths[dst_vnf_name][dst_vnf_interface]
+ logging.debug("Taking custom path from %s to %s: %s" % (src_vnf_name, dst_vnf_name, path))
+
+ if not self.check_vnf_intf_pair(dst_vnf_name, dst_vnf_interface):
+ self.delete_loadbalancer(src_vnf_name, src_vnf_interface)
+ raise Exception(u"VNF %s or intfs %s does not exist" % (dst_vnf_name, dst_vnf_interface))
+ if isinstance(path, dict):
+ self.delete_loadbalancer(src_vnf_name, src_vnf_interface)
+ raise Exception(u"Can not find a valid path. Are you specifying the right interfaces?.")
+
+ target_mac = "fa:17:00:03:13:37"
+ target_ip = "0.0.0.0"
+ for intf in net[dst_vnf_name].intfs.values():
+ if intf.name == dst_vnf_interface:
+ target_mac = str(intf.mac)
+ target_ip = str(intf.ip)
+ dst_sw_outport_nr = dest_vnf_outport_nrs[index]
+ current_hop = src_sw
+ switch_inport_nr = src_sw_inport_nr
+
+ #self.setup_arp_reply_at(src_sw, src_sw_inport_nr, target_ip, target_mac, cookie=cookie)
+ net.getNodeByName(dst_vnf_name).setHostRoute(src_ip, dst_vnf_interface)
+
+ # choose free vlan if path contains more than 1 switch
+ if len(path) > 1:
+ vlan = net.vlans.pop()
+ if vlan == 0:
+ vlan = net.vlans.pop()
+ else:
+ vlan = None
+
+ single_flow_data = dict()
+ single_flow_data["dst_vnf"] = dst_vnf_name
+ single_flow_data["dst_intf"] = dst_vnf_interface
+ single_flow_data["path"] = path
+ single_flow_data["vlan"] = vlan
+ single_flow_data["cookie"] = cookie
+
+ data["paths"].append(single_flow_data)
+
+ # src to target
+ for i in range(0, len(path)):
+ if i < len(path) - 1:
+ next_hop = path[i + 1]
+ else:
+ # last switch reached
+ next_hop = dst_vnf_name
+ next_node = net.getNodeByName(next_hop)
+ if next_hop == dst_vnf_name:
+ switch_outport_nr = dst_sw_outport_nr
+ logging.info("end node reached: {0}".format(dst_vnf_name))
+ elif not isinstance(next_node, OVSSwitch):
+ logging.info("Next node: {0} is not a switch".format(next_hop))
+ return "Next node: {0} is not a switch".format(next_hop)
+ else:
+ # take first link between switches by default
+ index_edge_out = 0
+ switch_outport_nr = net.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
+
+ cmd = 'priority=1,in_port=%s,cookie=%s' % (switch_inport_nr, cookie)
+ cmd_back = 'priority=1,in_port=%s,cookie=%s' % (switch_outport_nr, cookie)
+ # if a vlan is picked, the connection is routed through multiple switches
+ if vlan is not None:
+ if path.index(current_hop) == 0: # first node
+ # flow #index set up
+ cmd = 'in_port=%s' % src_sw_inport_nr
+ cmd += ',cookie=%s' % cookie
+ cmd += ',table=%s' % cookie
+ cmd += ',ip'
+ cmd += ',reg1=%s' % index
+ cmd += ',actions='
+ # set vlan id
+ cmd += ',push_vlan:0x8100'
+ masked_vlan = vlan | 0x1000
+ cmd += ',set_field:%s->vlan_vid' % masked_vlan
+ cmd += ',set_field:%s->eth_dst' % target_mac
+ cmd += ',set_field:%s->ip_dst' % target_ip
+ cmd += ',output:%s' % switch_outport_nr
+
+ # last switch for reverse route
+ # remove any vlan tags
+ cmd_back += ',dl_vlan=%s' % vlan
+ cmd_back += ',actions=pop_vlan,output:%s' % switch_inport_nr
+ elif next_hop == dst_vnf_name: # last switch
+ # remove any vlan tags
+ cmd += ',dl_vlan=%s' % vlan
+ cmd += ',actions=pop_vlan,output:%s' % switch_outport_nr
+ # set up arp replys at the port so the dst nodes know the src
+ self.setup_arp_reply_at(current_hop, switch_outport_nr, src_ip, src_mac, cookie=cookie)
+
+ # reverse route
+ cmd_back = 'in_port=%s' % switch_outport_nr
+ cmd_back += ',cookie=%s' % cookie
+ cmd_back += ',ip'
+ cmd_back += ',actions='
+ cmd_back += 'push_vlan:0x8100'
+ masked_vlan = vlan | 0x1000
+ cmd_back += ',set_field:%s->vlan_vid' % masked_vlan
+ cmd_back += ',set_field:%s->eth_src' % lb_mac
+ cmd_back += ',set_field:%s->ip_src' % plus_one
+ cmd_back += ',output:%s' % switch_inport_nr
+ else: # middle nodes
+ # if we have a circle in the path we need to specify this, as openflow will ignore the packet
+ # if we just output it on the same port as it came in
+ if switch_inport_nr == switch_outport_nr:
+ cmd += ',dl_vlan=%s,actions=IN_PORT' % (vlan)
+ cmd_back += ',dl_vlan=%s,actions=IN_PORT' % (vlan)
+ else:
+ cmd += ',dl_vlan=%s,actions=output:%s' % (vlan, switch_outport_nr)
+ cmd_back += ',dl_vlan=%s,actions=output:%s' % (vlan, switch_inport_nr)
+ # output the packet at the correct outport
+ else:
+ cmd = 'in_port=%s' % src_sw_inport_nr
+ cmd += ',cookie=%s' % cookie
+ cmd += ',table=%s' % cookie
+ cmd += ',ip'
+ cmd += ',reg1=%s' % index
+ cmd += ',actions='
+ cmd += ',set_field:%s->eth_dst' % target_mac
+ cmd += ',set_field:%s->ip_dst' % target_ip
+ cmd += ',output:%s' % switch_outport_nr
+
+ # reverse route
+ cmd_back = 'in_port=%s' % switch_outport_nr
+ cmd_back += ',cookie=%s' % cookie
+ cmd_back += ',ip'
+ cmd_back += ',actions='
+ cmd_back += ',set_field:%s->eth_src' % lb_mac
+ cmd_back += ',set_field:%s->ip_src' % plus_one
+ cmd_back += ',output:%s' % src_sw_inport_nr
+
+ self.setup_arp_reply_at(current_hop, switch_outport_nr, src_ip, src_mac, cookie=cookie)
+
+ # excecute the command on the target switch
+ logging.debug(cmd)
+ cmd = "\"%s\"" % cmd
+ cmd_back = "\"%s\"" % cmd_back
+ net[current_hop].dpctl(main_cmd, cmd)
+ net[current_hop].dpctl(main_cmd, cmd_back)
+
+ # set next hop for the next iteration step
+ if isinstance(next_node, OVSSwitch):
+ switch_inport_nr = net.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
+ current_hop = next_hop
+
+ # advance to next destination
+ index += 1
+
+ # set up the actual load balancing rule as a multipath on the very first switch
+ cmd = '"in_port=%s' % src_sw_inport_nr
+ cmd += ',cookie=%s' % (cookie)
+ cmd += ',ip'
+ cmd += ',actions='
+ # push 0x01 into the first register
+ cmd += 'load:0x1->NXM_NX_REG0[]'
+ # load balance modulo n over all dest interfaces
+ # TODO: in newer openvswitch implementations this should be changed to symmetric_l3l4+udp
+ # to balance any kind of traffic
+ cmd += ',multipath(symmetric_l4,1024,modulo_n,%s,0,NXM_NX_REG1[0..12])' % len(dest_intfs_mapping)
+ # reuse the cookie as table entry as it will be unique
+ cmd += ',resubmit(, %s)"' % cookie
+
+ # actually add the flow
+ logging.debug("Switch: %s, CMD: %s" % (src_sw, cmd))
+ net[src_sw].dpctl(main_cmd, cmd)
+
+ # finally add all flow data to the internal data storage
+ self.full_lb_data[(src_vnf_name, src_vnf_interface)] = data
+
+ def add_floating_lb(self, datacenter, lb_data):
+ """
+ This function will set up a loadbalancer at the given datacenter.
+ This function returns the floating ip assigned to the loadbalancer as multiple ones are possible.
+
+ :param datacenter: The datacenter entrypoint
+ :type datacenter: ``str``
+ :param lb_data: A dictionary containing the destination data as well as custom path settings
+ :type lb_data: ``dict``
+
+ :Example:
+ lbdata = {"dst_vnf_interfaces": {"dc2_man_web0": "port-man-2",
+ "dc3_man_web0": "port-man-4","dc4_man_web0": "port-man-6"}, "path": {"dc2_man_web0": {"port-man-2": [ "dc1.s1",\
+ "s1", "dc2.s1"]}}}
+ """
+ net = self.net
+ src_sw_inport_nr = 1
+ src_sw = self.floating_switch.name
+ dest_intfs_mapping = lb_data.get('dst_vnf_interfaces', dict())
+ # a custom path can be specified as a list of switches
+ custom_paths = lb_data.get('path', dict())
+ dest_vnf_outport_nrs = list()
+
+ if datacenter not in self.net.dcs:
+ raise Exception(u"Source datacenter can not be found.")
+
+ # get all target interface outport numbers
+ for vnf_name in dest_intfs_mapping:
+ if vnf_name not in net.DCNetwork_graph:
+ raise Exception(u"Target VNF %s is not known." % vnf_name)
+ for connected_sw in net.DCNetwork_graph.neighbors(vnf_name):
+ link_dict = net.DCNetwork_graph[vnf_name][connected_sw]
+ for link in link_dict:
+ if link_dict[link]['src_port_name'] == dest_intfs_mapping[vnf_name]:
+ dest_vnf_outport_nrs.append(int(link_dict[link]['dst_port_nr']))
+
+ if len(dest_vnf_outport_nrs) == 0:
+ raise Exception("There are no paths specified for the loadbalancer")
+ src_ip = self.floating_intf.IP()
+ src_mac = self.floating_intf.MAC()
+
+ # set up paths for each destination vnf individually
+ index = 0
+ cookie = self.get_cookie()
+ main_cmd = "add-flow -OOpenFlow13"
+ floating_ip = self.floating_network.get_new_ip_address("floating-ip").split("/")[0]
+
+ for dst_vnf_name, dst_vnf_interface in dest_intfs_mapping.items():
+ path = None
+ # use custom path if one is supplied
+ # json does not support hashing on tuples so we use nested dicts
+ if custom_paths is not None and dst_vnf_name in custom_paths:
+ if dst_vnf_interface in custom_paths[dst_vnf_name]:
+ path = custom_paths[dst_vnf_name][dst_vnf_interface]
+ logging.debug("Taking custom path to %s: %s" % (dst_vnf_name, path))
+ else:
+ if datacenter not in self.floating_links:
+ self.floating_links[datacenter] = \
+ net.addLink(self.floating_switch, datacenter)
+ path = \
+ self._get_path(self.floating_root.name, dst_vnf_name, self.floating_intf.name, dst_vnf_interface)[0]
+
+ if isinstance(path, dict):
+ self.delete_flow_by_cookie(cookie)
+ raise Exception(u"Can not find a valid path. Are you specifying the right interfaces?.")
+
+ intf = net[dst_vnf_name].nameToIntf[dst_vnf_interface]
+ target_mac = str(intf.MAC())
+ target_ip = str(intf.IP())
+ dst_sw_outport_nr = dest_vnf_outport_nrs[index]
+ current_hop = src_sw
+ switch_inport_nr = src_sw_inport_nr
+ vlan = net.vlans.pop()
+
+ # iterate all switches on the path
+ for i in range(0, len(path)):
+ if i < len(path) - 1:
+ next_hop = path[i + 1]
+ else:
+ # last switch reached
+ next_hop = dst_vnf_name
+ next_node = net.getNodeByName(next_hop)
+
+ # sanity checks
+ if next_hop == dst_vnf_name:
+ switch_outport_nr = dst_sw_outport_nr
+ logging.info("end node reached: {0}".format(dst_vnf_name))
+ elif not isinstance(next_node, OVSSwitch):
+ logging.info("Next node: {0} is not a switch".format(next_hop))
+ return "Next node: {0} is not a switch".format(next_hop)
+ else:
+ # take first link between switches by default
+ index_edge_out = 0
+ switch_outport_nr = net.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
+
+ # default filters, just overwritten on the first node and last node
+ cmd = 'priority=1,in_port=%s,cookie=%s' % (switch_inport_nr, cookie)
+ cmd_back = 'priority=1,in_port=%s,cookie=%s' % (switch_outport_nr, cookie)
+ if i == 0: # first node
+ cmd = 'in_port=%s' % src_sw_inport_nr
+ cmd += ',cookie=%s' % cookie
+ cmd += ',table=%s' % cookie
+ cmd += ',ip'
+ cmd += ',ip_dst=%s' % floating_ip
+ cmd += ',reg1=%s' % index
+ cmd += ',actions='
+ # set vlan id
+ cmd += ',push_vlan:0x8100'
+ masked_vlan = vlan | 0x1000
+ cmd += ',set_field:%s->vlan_vid' % masked_vlan
+ cmd += ',set_field:%s->eth_dst' % target_mac
+ cmd += ',set_field:%s->ip_dst' % target_ip
+ cmd += ',output:%s' % switch_outport_nr
+
+ # last switch for reverse route
+ # remove any vlan tags
+ cmd_back += ',dl_vlan=%s' % vlan
+ cmd_back += ',actions=pop_vlan,output:%s' % switch_inport_nr
+ self.setup_arp_reply_at(current_hop, src_sw_inport_nr, floating_ip, target_mac, cookie=cookie)
+ elif next_hop == dst_vnf_name: # last switch
+ # remove any vlan tags
+ cmd += ',dl_vlan=%s' % vlan
+ cmd += ',actions=pop_vlan,output:%s' % switch_outport_nr
+ # set up arp replys at the port so the dst nodes know the src
+ self.setup_arp_reply_at(current_hop, switch_outport_nr, src_ip, src_mac, cookie=cookie)
+
+ # reverse route
+ cmd_back = 'in_port=%s' % switch_outport_nr
+ cmd_back += ',cookie=%s' % cookie
+ cmd_back += ',ip'
+ cmd_back += ',actions='
+ cmd_back += 'push_vlan:0x8100'
+ masked_vlan = vlan | 0x1000
+ cmd_back += ',set_field:%s->vlan_vid' % masked_vlan
+ cmd_back += ',set_field:%s->eth_src' % src_mac
+ cmd_back += ',set_field:%s->ip_src' % floating_ip
+ cmd_back += ',output:%s' % switch_inport_nr
+ net.getNodeByName(dst_vnf_name).setHostRoute(src_ip, dst_vnf_interface)
+ else: # middle node
+ # if we have a circle in the path we need to specify this, as openflow will ignore the packet
+ # if we just output it on the same port as it came in
+ if switch_inport_nr == switch_outport_nr:
+ cmd += ',dl_vlan=%s,actions=IN_PORT' % (vlan)
+ cmd_back += ',dl_vlan=%s,actions=IN_PORT' % (vlan)
+ else:
+ cmd += ',dl_vlan=%s,actions=output:%s' % (vlan, switch_outport_nr)
+ cmd_back += ',dl_vlan=%s,actions=output:%s' % (vlan, switch_inport_nr)
+
+ # excecute the command on the target switch
+ logging.debug(cmd)
+ cmd = "\"%s\"" % cmd
+ cmd_back = "\"%s\"" % cmd_back
+ net[current_hop].dpctl(main_cmd, cmd)
+ net[current_hop].dpctl(main_cmd, cmd_back)
+
+ # set next hop for the next iteration step
+ if isinstance(next_node, OVSSwitch):
+ switch_inport_nr = net.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
+ current_hop = next_hop
+
+ # advance to next destination
+ index += 1
+
+ # set up the actual load balancing rule as a multipath on the very first switch
+ cmd = '"in_port=%s' % src_sw_inport_nr
+ cmd += ',cookie=%s' % (cookie)
+ cmd += ',ip'
+ cmd += ',actions='
+ # push 0x01 into the first register
+ cmd += 'load:0x1->NXM_NX_REG0[]'
+ # load balance modulo n over all dest interfaces
+ # TODO: in newer openvswitch implementations this should be changed to symmetric_l3l4+udp
+ # to balance any kind of traffic
+ cmd += ',multipath(symmetric_l4,1024,modulo_n,%s,0,NXM_NX_REG1[0..12])' % len(dest_intfs_mapping)
+ # reuse the cookie as table entry as it will be unique
+ cmd += ',resubmit(, %s)"' % cookie
+
+ # actually add the flow
+ logging.debug("Switch: %s, CMD: %s" % (src_sw, cmd))
+ net[src_sw].dpctl(main_cmd, cmd)
+
+ self.floating_cookies[cookie] = floating_ip
+
+ return cookie, floating_ip
+
+ def setup_arp_reply_at(self, switch, port_nr, target_ip, target_mac, cookie=None):
+ """
+ Sets up a custom ARP reply at a switch.
+ An ARP request coming in on the `port_nr` for `target_ip` will be answered with target IP/MAC.
+
+ :param switch: The switch belonging to the interface
+ :type switch: ``str``
+ :param port_nr: The port number at the switch that is connected to the interface
+ :type port_nr: ``int``
+ :param target_ip: The IP for which to set up the ARP reply
+ :type target_ip: ``str``
+ :param target_mac: The MAC address of the target interface
+ :type target_mac: ``str``
+ :param cookie: cookie to identify the ARP request, if None a new one will be picked
+ :type cookie: ``int`` or ``None``
+ :return: cookie
+ :rtype: ``int``
+ """
+ if cookie is None:
+ cookie = self.get_cookie()
+ main_cmd = "add-flow -OOpenFlow13"
+
+ # first set up ARP requests for the source node, so it will always 'find' a partner
+ cmd = '"in_port=%s' % port_nr
+ cmd += ',cookie=%s' % cookie
+ cmd += ',arp'
+ # only answer for target ip arp requests
+ cmd += ',arp_tpa=%s' % target_ip
+ cmd += ',actions='
+ # set message type to ARP reply
+ cmd += 'load:0x2->NXM_OF_ARP_OP[]'
+ # set src ip as dst ip
+ cmd += ',move:NXM_OF_ETH_SRC[]->NXM_OF_ETH_DST[]'
+ # set src mac
+ cmd += ',set_field:%s->eth_src' % target_mac
+ # set src as target
+ cmd += ',move:NXM_NX_ARP_SHA[]->NXM_NX_ARP_THA[], move:NXM_OF_ARP_SPA[]->NXM_OF_ARP_TPA[]'
+ # set target mac as hex
+ cmd += ',load:0x%s->NXM_NX_ARP_SHA[]' % "".join(target_mac.split(':'))
+ # set target ip as hex
+ octets = target_ip.split('.')
+ dst_ip_hex = '{:02X}{:02X}{:02X}{:02X}'.format(*map(int, octets))
+ cmd += ',load:0x%s->NXM_OF_ARP_SPA[]' % dst_ip_hex
+ # output to incoming port remember the closing "
+ cmd += ',IN_PORT"'
+ self.net[switch].dpctl(main_cmd, cmd)
+ logging.debug(
+ "Set up ARP reply at %s port %s." % (switch, port_nr))
+
+ def delete_flow_by_cookie(self, cookie):
+ """
+ Removes a flow identified by the cookie
+
+ :param cookie: The cookie for the specified flow
+ :type cookie: ``int``
+ :return: True if successful, else false
+ :rtype: ``bool``
+ """
+ if not cookie:
+ return False
+ logging.debug("Deleting flow by cookie %d" % (cookie))
+ flows = list()
+ # we have to call delete-group for each switch
+ for node in self.net.switches:
+ flow = dict()
+ flow["dpid"] = int(node.dpid, 16)
+ flow["cookie"] = cookie
+ flow['cookie_mask'] = int('0xffffffffffffffff', 16)
+
+ flows.append(flow)
+ for flow in flows:
+ logging.debug("Deleting flowentry with cookie %d" % (
+ flow["cookie"]))
+ if self.net.controller == RemoteController:
+ self.net.ryu_REST('stats/flowentry/delete', data=flow)
+
+ self.cookies.remove(cookie)
+ return True
+
+ def delete_chain_by_intf(self, src_vnf_name, src_vnf_intf, dst_vnf_name, dst_vnf_intf):
+ """
+ Removes a flow identified by the vnf_name/vnf_intf pairs
+
+ :param src_vnf_name: The vnf name for the specified flow
+ :type src_vnf_name: ``str``
+ :param src_vnf_intf: The interface name for the specified flow
+ :type src_vnf_intf: ``str``
+ :param dst_vnf_name: The vnf name for the specified flow
+ :type dst_vnf_name: ``str``
+ :param dst_vnf_intf: The interface name for the specified flow
+ :type dst_vnf_intf: ``str``
+ :return: True if successful, else false
+ :rtype: ``bool``
+ """
+ logging.debug("Deleting flow for vnf/intf pair %s %s" % (src_vnf_name, src_vnf_intf))
+ if not self.check_vnf_intf_pair(src_vnf_name, src_vnf_intf):
+ return False
+ if not self.check_vnf_intf_pair(dst_vnf_name, dst_vnf_intf):
+ return False
+ target_flow = (src_vnf_name, src_vnf_intf, dst_vnf_name, dst_vnf_intf)
+ if not target_flow in self.chain_flow_cookies:
+ return False
+
+ success = self.delete_flow_by_cookie(self.chain_flow_cookies[target_flow])
+
+ if success:
+ del self.chain_flow_cookies[target_flow]
+ del self.full_chain_data[target_flow]
+ return True
+ return False
+
+ def delete_loadbalancer(self, vnf_src_name, vnf_src_interface):
+ '''
+ Removes a loadbalancer that is configured for the node and interface
+
+ :param src_vnf_name: Name of the source VNF
+ :param src_vnf_interface: Name of the destination VNF
+ '''
+ flows = list()
+ # we have to call delete-group for each switch
+ delete_group = list()
+ group_id = self.get_flow_group(vnf_src_name, vnf_src_interface)
+ for node in self.net.switches:
+ for cookie in self.lb_flow_cookies[(vnf_src_name, vnf_src_interface)]:
+ flow = dict()
+ flow["dpid"] = int(node.dpid, 16)
+ flow["cookie"] = cookie
+ flow['cookie_mask'] = int('0xffffffffffffffff', 16)
+
+ flows.append(flow)
+ group_del = dict()
+ group_del["dpid"] = int(node.dpid, 16)
+ group_del["group_id"] = group_id
+ delete_group.append(group_del)
+
+ for flow in flows:
+ logging.debug("Deleting flowentry with cookie %d belonging to lb at %s:%s" % (
+ flow["cookie"], vnf_src_name, vnf_src_interface))
+ if self.net.controller == RemoteController:
+ self.net.ryu_REST('stats/flowentry/delete', data=flow)
+
+ logging.debug("Deleting group with id %s" % group_id)
+ for switch_del_group in delete_group:
+ if self.net.controller == RemoteController:
+ self.net.ryu_REST("stats/groupentry/delete", data=switch_del_group)
+
+ # unmap groupid from the interface
+ target_pair = (vnf_src_name, vnf_src_interface)
+ if target_pair in self.flow_groups:
+ del self.flow_groups[target_pair]
+ if target_pair in self.full_lb_data:
+ del self.full_lb_data[target_pair]
+
+ def delete_floating_lb(self, cookie):
+ """
+ Delete a floating loadbalancer.
+ Floating loadbalancers are different from normal ones as there are multiple ones on the same interface.
+ :param cookie: The cookie of the loadbalancer
+ :type cookie: ``int``
+ """
+ cookie = int(cookie)
+ if cookie not in self.floating_cookies:
+ raise Exception("Can not delete floating loadbalancer as the flowcookie is not known")
+
+ self.delete_flow_by_cookie(cookie)
+ floating_ip = self.floating_cookies[cookie]
+ self.floating_network.withdraw_ip_address(floating_ip)
+
+ def set_arp_entry(self, vnf_name, vnf_interface, ip, mac):
+ """
+ Sets an arp entry on the specified VNF. This is done on the node directly and not by open vswitch!
+ :param vnf_name: Name of the VNF
+ :type vnf_name: ``str``
+ :param vnf_interface: Name of the interface
+ :type vnf_interface: ``str``
+ :param ip: IP to reply to
+ :type ip: ``str``
+ :param mac: Answer with this MAC
+ :type mac: ``str``
+ """
+ node = self.net.getNodeByName(vnf_name)
+ node.cmd("arp -i %s -s %s %s" % (vnf_interface, ip, mac))
--- /dev/null
+from manage import OpenstackManage
+from openstack_dummies import *
+import logging
+import threading
+import compute
+import requests
+import socket
+import time
+
+
+class OpenstackApiEndpoint():
+ """
+ Base class for an OpenStack datacenter.
+ It holds information about all connected endpoints.
+ """
+ dc_apis = []
+
+ def __init__(self, listenip, port):
+ self.ip = listenip
+ self.port = port
+ self.compute = compute.OpenstackCompute()
+ self.openstack_endpoints = dict()
+ self.openstack_endpoints['keystone'] = KeystoneDummyApi(self.ip, self.port)
+ self.openstack_endpoints['neutron'] = NeutronDummyApi(self.ip, self.port + 4696, self.compute)
+ self.openstack_endpoints['nova'] = NovaDummyApi(self.ip, self.port + 3774, self.compute)
+ self.openstack_endpoints['heat'] = HeatDummyApi(self.ip, self.port + 3004, self.compute)
+ self.openstack_endpoints['glance'] = GlanceDummyApi(self.ip, self.port + 4242, self.compute)
+
+ self.rest_threads = list()
+ self.manage = OpenstackManage()
+ self.manage.add_endpoint(self)
+ OpenstackApiEndpoint.dc_apis.append(self)
+
+ def connect_datacenter(self, dc):
+ """
+ Connect a datacenter to this endpoint.
+ An endpoint can only be connected to a single datacenter.
+
+ :param dc: Datacenter object
+ :type dc: :class:`dc`
+ """
+ self.compute.dc = dc
+ for ep in self.openstack_endpoints.values():
+ ep.manage = self.manage
+ logging.info \
+ ("Connected DC(%s) to API endpoint %s(%s:%d)" % (dc.label, self.__class__.__name__, self.ip, self.port))
+
+ def connect_dc_network(self, dc_network):
+ """
+ Connect the datacenter network to the endpoint.
+
+ :param dc_network: Datacenter network reference
+ :type dc_network: :class:`.net`
+ """
+ self.manage.net = dc_network
+ self.compute.nets[self.manage.floating_network.id] = self.manage.floating_network
+ logging.info("Connected DCNetwork to API endpoint %s(%s:%d)" % (
+ self.__class__.__name__, self.ip, self.port))
+
+ def start(self, wait_for_port=False):
+ """
+ Start all connected OpenStack endpoints that are connected to this API endpoint.
+ """
+ for component in self.openstack_endpoints.values():
+ component.compute = self.compute
+ component.manage = self.manage
+ thread = threading.Thread(target=component._start_flask, args=())
+ thread.daemon = True
+ thread.name = component.__class__
+ thread.start()
+ if wait_for_port:
+ self._wait_for_port(component.ip, component.port)
+
+
+ def stop(self):
+ """
+ Stop all connected OpenStack endpoints that are connected to this API endpoint.
+ """
+ for component in self.openstack_endpoints.values():
+ url = "http://" + component.ip + ":" + str(component.port) + "/shutdown"
+ try:
+ requests.get(url)
+ except:
+ # seems to be stopped
+ pass
+
+ def _wait_for_port(self, ip, port):
+ for i in range(0, 10):
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(1) # 1 Second Timeout
+ r = s.connect_ex((ip, port))
+ if r == 0:
+ break # port is open proceed
+ else:
+ logging.warning("Waiting for {}:{} ... ({}/10)".format(ip, port, i + 1))
+ time.sleep(1)
--- /dev/null
+from glance_dummy_api import GlanceDummyApi
+from heat_dummy_api import HeatDummyApi
+from keystone_dummy_api import KeystoneDummyApi
+from neutron_dummy_api import NeutronDummyApi
+from nova_dummy_api import NovaDummyApi
+
--- /dev/null
+from flask import Flask, request
+from flask_restful import Api, Resource
+import logging
+
+
+class BaseOpenstackDummy(Resource):
+ """
+ This class is the base class for all openstack entrypoints of son-emu.
+ """
+
+ def __init__(self, listenip, port):
+ self.ip = listenip
+ self.port = port
+ self.compute = None
+ self.manage = None
+ self.playbook_file = '/tmp/son-emu-requests.log'
+ with open(self.playbook_file, 'w'):
+ pass
+
+ # setup Flask
+ self.app = Flask(__name__)
+ self.api = Api(self.app)
+
+ def _start_flask(self):
+ logging.info("Starting %s endpoint @ http://%s:%d" % (__name__, self.ip, self.port))
+ if self.app is not None:
+ self.app.before_request(self.dump_playbook)
+ self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+ def dump_playbook(self):
+ with self.manage.lock:
+ with open(self.playbook_file, 'a') as logfile:
+ if len(request.data) > 0:
+ data = "# %s API\n" % str(self.__class__).split('.')[-1].rstrip('\'>')
+ data += "curl -X {type} -H \"Content-type: application/json\" -d '{data}' {url}".format(type=request.method,
+ data=request.data,
+ url=request.url)
+ logfile.write(data + "\n")
--- /dev/null
+from flask_restful import Resource
+from flask import Response, request
+from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy
+import logging
+import json
+
+
+class GlanceDummyApi(BaseOpenstackDummy):
+ def __init__(self, in_ip, in_port, compute):
+ super(GlanceDummyApi, self).__init__(in_ip, in_port)
+ self.compute = compute
+ self.api.add_resource(Shutdown,
+ "/shutdown")
+ self.api.add_resource(GlanceListApiVersions,
+ "/versions")
+ self.api.add_resource(GlanceSchema,
+ "/v2/schemas/image",
+ "/v2/schemas/metadefs/namespace",
+ "/v2/schemas/metadefs/resource_type")
+ self.api.add_resource(GlanceListImagesApi,
+ "/v1/images",
+ "/v1/images/detail",
+ "/v2/images",
+ "/v2/images/detail",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(GlanceImageByIdApi,
+ "/v1/images/<id>",
+ "/v2/images/<id>",
+ resource_class_kwargs={'api': self})
+
+ def _start_flask(self):
+ logging.info("Starting %s endpoint @ http://%s:%d" % ("GlanceDummyApi", self.ip, self.port))
+ if self.app is not None:
+ self.app.before_request(self.dump_playbook)
+ self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+
+class Shutdown(Resource):
+ def get(self):
+ logging.debug(("%s is beeing shut down") % (__name__))
+ func = request.environ.get('werkzeug.server.shutdown')
+ if func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ func()
+
+
+class GlanceListApiVersions(Resource):
+ def get(self):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ resp = dict()
+ resp['versions'] = dict()
+ versions = [{
+ "status": "CURRENT",
+ "id": "v2",
+ "links": [
+ {
+ "href": request.url_root + '/v2',
+ "rel": "self"
+ }
+ ]
+ }]
+ resp['versions'] = versions
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+
+
+class GlanceSchema(Resource):
+ def get(self):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ resp = dict()
+ resp['name'] = 'someImageName'
+ resp['properties'] = dict()
+ # just an ugly hack to allow the openstack client to work
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+
+
+class GlanceListImagesApi(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ resp = dict()
+ resp['next'] = None
+ resp['first'] = "/v2/images"
+ resp['schema'] = "/v2/schemas/images"
+ resp['images'] = list()
+ limit = 18
+ c = 0
+ for image in self.api.compute.images.values():
+ f = dict()
+ f['id'] = image.id
+ f['name'] = str(image.name).replace(":latest", "")
+ f['checksum'] = "2dad48f09e2a447a9bf852bcd93548c1"
+ f['container_format'] = "docker"
+ f['disk_format'] = "raw"
+ f['size'] = 1
+ f['created_at'] = "2016-03-15T15:09:07.000000"
+ f['deleted'] = False
+ f['deleted_at'] = None
+ f['is_public'] = True
+ f['min_disk'] = 1
+ f['min_ram'] = 128
+ f['owner'] = "3dad48f09e2a447a9bf852bcd93548c1"
+ f['properties'] = {}
+ f['protected'] = False
+ f['status'] = "active"
+ f['updated_at'] = "2016-03-15T15:09:07.000000"
+ f['virtual_size'] = 1
+ f['marker'] = None
+ resp['images'].append(f)
+ 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
+ resp['images'] = None
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the list of images." % __name__)
+ return ex.message, 500
+
+ def post(self):
+ """
+ This one is a real fake! It does not really create anything and the mentioned image
+ should already be registered with Docker. However, this function returns a reply that looks
+ like the image was just created to make orchestrators, like OSM, happy.
+ """
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ # lets see what we should create
+ img_name = request.headers.get("X-Image-Meta-Name")
+ img_size = request.headers.get("X-Image-Meta-Size")
+ img_disk_format = request.headers.get("X-Image-Meta-Disk-Format")
+ img_is_public = request.headers.get("X-Image-Meta-Is-Public")
+ img_container_format = request.headers.get("X-Image-Meta-Container-Format")
+ # try to find ID of already existing image (matched by name)
+ img_id=None
+ for image in self.api.compute.images.values():
+ if img_name in image.name:
+ img_id = image.id
+ logging.debug("Image name: %s" % img_name)
+ logging.debug("Image id: %s" % img_id)
+ # build a response body that looks like a real one
+ resp = dict()
+ f = dict()
+ f['id'] = img_id
+ f['name'] = img_name
+ f['checksum'] = "2dad48f09e2a447a9bf852bcd93548c1"
+ f['container_format'] = img_container_format
+ f['disk_format'] = img_disk_format
+ f['size'] = img_size
+ f['created_at'] = "2016-03-15T15:09:07.000000"
+ f['deleted'] = False
+ f['deleted_at'] = None
+ f['is_public'] = img_is_public
+ f['min_disk'] = 1
+ f['min_ram'] = 128
+ f['owner'] = "3dad48f09e2a447a9bf852bcd93548c1"
+ f['properties'] = {}
+ f['protected'] = False
+ f['status'] = "active"
+ f['updated_at'] = "2016-03-15T15:09:07.000000"
+ f['virtual_size'] = 1
+ resp['image'] = f
+ # build actual response with headers and everything
+ r = Response(json.dumps(resp), status=201, mimetype="application/json")
+ r.headers.add("Location", "http://%s:%d/v1/images/%s" % (self.api.ip,
+ self.api.port,
+ img_id))
+ return r
+
+
+class GlanceImageByIdApi(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ logging.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)
+
+ def put(self, id):
+ logging.debug("API CALL: %s " % str(self.__class__.__name__))
+ logging.warning("Endpoint not implemented")
+ return None
+
+
--- /dev/null
+from flask import request, Response
+from flask_restful import Resource
+from emuvim.api.openstack.resources import Stack
+from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy
+from datetime import datetime
+from emuvim.api.openstack.heat_parser import HeatParser
+import logging
+import json
+
+
+class HeatDummyApi(BaseOpenstackDummy):
+ def __init__(self, in_ip, in_port, compute):
+ super(HeatDummyApi, self).__init__(in_ip, in_port)
+ self.compute = compute
+
+ self.api.add_resource(Shutdown, "/shutdown")
+ self.api.add_resource(HeatListAPIVersions, "/",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(HeatCreateStack, "/v1/<tenant_id>/stacks",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(HeatShowStack, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
+ "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(HeatUpdateStack, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
+ "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(HeatDeleteStack, "/v1/<tenant_id>/stacks/<stack_name_or_id>",
+ "/v1/<tenant_id>/stacks/<stack_name_or_id>/<stack_id>",
+ resource_class_kwargs={'api': self})
+
+ @self.app.after_request
+ def add_access_control_header(response):
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+
+ def _start_flask(self):
+ logging.info("Starting %s endpoint @ http://%s:%d" % (__name__, self.ip, self.port))
+ if self.app is not None:
+ self.app.before_request(self.dump_playbook)
+ self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+
+class Shutdown(Resource):
+ """
+ A get request to /shutdown will shut down this endpoint.
+ """
+
+ def get(self):
+ logging.debug(("%s is beeing shut down") % (__name__))
+ func = request.environ.get('werkzeug.server.shutdown')
+ if func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ func()
+
+
+class HeatListAPIVersions(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ resp = dict()
+
+ resp['versions'] = dict()
+ resp['versions'] = [{
+ "status": "CURRENT",
+ "id": "v1.0",
+ "links": [
+ {
+ "href": "http://%s:%d/v2.0" % (self.api.ip, self.api.port),
+ "rel": "self"
+ }
+ ]
+ }]
+
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+
+class HeatCreateStack(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def post(self, tenant_id):
+ """
+ Create and deploy a new stack.
+
+ :param tenant_id:
+ :return: 409, if the stack name was already used.
+ 400, if the heat template could not be parsed properly.
+ 500, if any exception occurred while creation.
+ 201, if everything worked out.
+ """
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+
+ try:
+ stack_dict = json.loads(request.data)
+ for stack in self.api.compute.stacks.values():
+ if stack.stack_name == stack_dict['stack_name']:
+ return [], 409
+ stack = Stack()
+ stack.stack_name = stack_dict['stack_name']
+ reader = HeatParser(self.api.compute)
+
+ if isinstance(stack_dict['template'], str) or isinstance(stack_dict['template'], unicode):
+ stack_dict['template'] = json.loads(stack_dict['template'])
+ if not reader.parse_input(stack_dict['template'], stack, self.api.compute.dc.label):
+ self.api.compute.clean_broken_stack(stack)
+ return 'Could not create stack.', 400
+
+ stack.creation_time = str(datetime.now())
+ stack.status = "CREATE_COMPLETE"
+
+ return_dict = {"stack": {"id": stack.id,
+ "links": [
+ {
+ "href": "http://%s:%s/v1/%s/stacks/%s"
+ % (self.api.ip, self.api.port, tenant_id, stack.id),
+ "rel": "self"
+ }]}}
+
+ self.api.compute.add_stack(stack)
+ self.api.compute.deploy_stack(stack.id)
+ return Response(json.dumps(return_dict), status=201, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception("Heat: Create Stack exception.")
+ return ex.message, 500
+
+ def get(self, tenant_id):
+ """
+ Calculates information about the requested stack.
+
+ :param tenant_id:
+ :return: Returns a json response which contains information like the stack id, name, status, creation time.
+ 500, if any exception occurred.
+ 200, if everything worked out.
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ return_stacks = dict()
+ return_stacks['stacks'] = list()
+ for stack in self.api.compute.stacks.values():
+ return_stacks['stacks'].append(
+ {"creation_time": stack.creation_time,
+ "description": "desc of " + stack.id,
+ "id": stack.id,
+ "links": [],
+ "stack_name": stack.stack_name,
+ "stack_status": stack.status,
+ "stack_status_reason": "Stack CREATE completed successfully",
+ "updated_time": stack.update_time,
+ "tags": ""
+ })
+
+ return Response(json.dumps(return_stacks), status=200, mimetype="application/json")
+ except Exception as ex:
+ logging.exception("Heat: List Stack exception.")
+ return ex.message, 500
+
+
+class HeatShowStack(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, tenant_id, stack_name_or_id, stack_id=None):
+ """
+ Calculates detailed information about the requested stack.
+
+ :param tenant_id:
+ :param stack_name_or_id:
+ :param stack_id:
+ :return: Returns a json response which contains information like the stack id, name, status, creation time.
+ 500, if any exception occurred.
+ 200, if everything worked out.
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ stack = None
+ if stack_name_or_id in self.api.compute.stacks:
+ stack = self.api.compute.stacks[stack_name_or_id]
+ else:
+ for tmp_stack in self.api.compute.stacks.values():
+ if tmp_stack.stack_name == stack_name_or_id:
+ stack = tmp_stack
+ if stack is None:
+ return 'Could not resolve Stack - ID', 404
+
+ return_stack = {
+ "stack": {
+ "capabilities": [],
+ "creation_time": stack.creation_time,
+ "description": "desc of " + stack.stack_name,
+ "disable_rollback": True,
+ "id": stack.id,
+ "links": [
+ {
+ "href": "http://%s:%s/v1/%s/stacks/%s"
+ % (self.api.ip, self.api.port, tenant_id, stack.id),
+ "rel": "self"
+ }
+ ],
+ "notification_topics": [],
+ "outputs": [],
+ "parameters": {
+ "OS::project_id": "3ab5b02f-a01f-4f95-afa1-e254afc4a435", # add real project id
+ "OS::stack_id": stack.id,
+ "OS::stack_name": stack.stack_name
+ },
+ "stack_name": stack.stack_name,
+ "stack_owner": "The owner of the stack.", # add stack owner
+ "stack_status": stack.status,
+ "stack_status_reason": "The reason for the current status of the stack.", # add status reason
+ "template_description": "The description of the stack template.",
+ "stack_user_project_id": "The project UUID of the stack user.",
+ "timeout_mins": "",
+ "updated_time": "",
+ "parent": "",
+ "tags": ""
+ }
+ }
+
+ return Response(json.dumps(return_stack), status=200, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception("Heat: Show stack exception.")
+ return ex.message, 500
+
+
+class HeatUpdateStack(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def put(self, tenant_id, stack_name_or_id, stack_id=None):
+ """
+ Updates an existing stack with a new heat template.
+
+ :param tenant_id:
+ :param stack_name_or_id: Specifies the stack, which should be updated.
+ :param stack_id:
+ :return: 404, if the requested stack could not be found.
+ 400, if the stack creation (because of errors in the heat template) or the stack update failed.
+ 500, if any exception occurred while updating.
+ 202, if everything worked out.
+ """
+ logging.debug("API CALL: %s PUT" % str(self.__class__.__name__))
+ try:
+ old_stack = None
+ if stack_name_or_id in self.api.compute.stacks:
+ old_stack = self.api.compute.stacks[stack_name_or_id]
+ else:
+ for tmp_stack in self.api.compute.stacks.values():
+ if tmp_stack.stack_name == stack_name_or_id:
+ old_stack = tmp_stack
+ if old_stack is None:
+ return 'Could not resolve Stack - ID', 404
+
+ stack_dict = json.loads(request.data)
+
+ stack = Stack()
+ stack.stack_name = old_stack.stack_name
+ stack.id = old_stack.id
+ stack.creation_time = old_stack.creation_time
+ stack.update_time = str(datetime.now())
+ stack.status = "UPDATE_COMPLETE"
+
+ reader = HeatParser(self.api.compute)
+ if isinstance(stack_dict['template'], str) or isinstance(stack_dict['template'], unicode):
+ stack_dict['template'] = json.loads(stack_dict['template'])
+ if not reader.parse_input(stack_dict['template'], stack, self.api.compute.dc.label, stack_update=True):
+ return 'Could not create stack.', 400
+
+ if not self.api.compute.update_stack(old_stack.id, stack):
+ return 'Could not update stack.', 400
+
+ return Response(status=202, mimetype="application/json")
+
+ except Exception as ex:
+ logging.exception("Heat: Update Stack exception")
+ return ex.message, 500
+
+
+class HeatDeleteStack(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def delete(self, tenant_id, stack_name_or_id, stack_id=None):
+ """
+ Deletes an existing stack.
+
+ :param tenant_id:
+ :param stack_name_or_id: Specifies the stack, which should be deleted.
+ :param stack_id:
+ :return: 500, if any exception occurred while deletion.
+ 204, if everything worked out.
+ """
+ logging.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
+ try:
+ if stack_name_or_id in self.api.compute.stacks:
+ self.api.compute.delete_stack(stack_name_or_id)
+ return Response('Deleted Stack: ' + stack_name_or_id, 204)
+
+ for stack in self.api.compute.stacks.values():
+ if stack.stack_name == stack_name_or_id:
+ self.api.compute.delete_stack(stack.id)
+ return Response('Deleted Stack: ' + stack_name_or_id, 204)
+
+ except Exception as ex:
+ logging.exception("Heat: Delete Stack exception")
+ return ex.message, 500
--- /dev/null
+from flask_restful import Resource
+from flask import request, Response
+from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy
+import logging
+import json
+
+
+class KeystoneDummyApi(BaseOpenstackDummy):
+ def __init__(self, in_ip, in_port):
+ super(KeystoneDummyApi, self).__init__(in_ip, in_port)
+
+ self.api.add_resource(KeystoneListVersions, "/", resource_class_kwargs={'api': self})
+ self.api.add_resource(Shutdown, "/shutdown")
+ self.api.add_resource(KeystoneShowAPIv2, "/v2.0", resource_class_kwargs={'api': self})
+ self.api.add_resource(KeystoneGetToken, "/v2.0/tokens", resource_class_kwargs={'api': self})
+
+ def _start_flask(self):
+ logging.info("Starting %s endpoint @ http://%s:%d" % (__name__, self.ip, self.port))
+ if self.app is not None:
+ self.app.before_request(self.dump_playbook)
+ self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+
+class Shutdown(Resource):
+ """
+ A get request to /shutdown will shut down this endpoint.
+ """
+
+ def get(self):
+ logging.debug(("%s is beeing shut down") % (__name__))
+ func = request.environ.get('werkzeug.server.shutdown')
+ if func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ func()
+
+
+class KeystoneListVersions(Resource):
+ """
+ List all known keystone versions.
+ Hardcoded for our version!
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ List API versions.
+
+ :return: Returns the api versions.
+ :rtype: :class:`flask.response` containing a static json encoded dict.
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ resp = dict()
+ resp['versions'] = dict()
+
+ version = [{
+ "id": "v2.0",
+ "links": [
+ {
+ "href": "http://%s:%d/v2.0" % (self.api.ip, self.api.port),
+ "rel": "self"
+ }
+ ],
+ "media-types": [
+ {
+ "base": "application/json",
+ "type": "application/vnd.openstack.identity-v2.0+json"
+ }
+ ],
+ "status": "stable",
+ "updated": "2014-04-17T00:00:00Z"
+ }]
+ resp['versions']['values'] = version
+
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+
+
+class KeystoneShowAPIv2(Resource):
+ """
+ Entrypoint for all openstack clients.
+ This returns all current entrypoints running on son-emu.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ List API entrypoints.
+
+ :return: Returns an openstack style response for all entrypoints.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ neutron_port = self.api.port + 4696
+ heat_port = self.api.port + 3004
+
+ resp = dict()
+ resp['version'] = {
+ "status": "stable",
+ "media-types": [
+ {
+ "base": "application/json",
+ "type": "application/vnd.openstack.identity-v2.0+json"
+ }
+ ],
+ "id": "v2.0",
+ "links": [
+ {
+ "href": "http://%s:%d/v2.0" % (self.api.ip, self.api.port),
+ "rel": "self"
+ },
+ {
+ "href": "http://%s:%d/v2.0/tokens" % (self.api.ip, self.api.port),
+ "rel": "self"
+ },
+ {
+ "href": "http://%s:%d/v2.0/networks" % (self.api.ip, neutron_port),
+ "rel": "self"
+ },
+ {
+ "href": "http://%s:%d/v2.0/subnets" % (self.api.ip, neutron_port),
+ "rel": "self"
+ },
+ {
+ "href": "http://%s:%d/v2.0/ports" % (self.api.ip, neutron_port),
+ "rel": "self"
+ },
+ {
+ "href": "http://%s:%d/v1/<tenant_id>/stacks" % (self.api.ip, heat_port),
+ "rel": "self"
+ }
+ ]
+ }
+
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+
+
+class KeystoneGetToken(Resource):
+ """
+ Returns a static keystone token.
+ We don't do any validation so we don't care.
+ """
+
+ def __init__(self, api):
+ self.api = api
+
+ def post(self):
+ """
+ List API entrypoints.
+
+ This is hardcoded. For a working "authentication" use these ENVVARS:
+
+ * OS_AUTH_URL=http://<ip>:<port>/v2.0
+ * OS_IDENTITY_API_VERSION=2.0
+ * OS_TENANT_ID=fc394f2ab2df4114bde39905f800dc57
+ * OS_REGION_NAME=RegionOne
+ * OS_USERNAME=bla
+ * OS_PASSWORD=bla
+
+ :return: Returns an openstack style response for all entrypoints.
+ :rtype: :class:`flask.response`
+ """
+
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ try:
+ ret = dict()
+ req = json.loads(request.data)
+ ret['access'] = dict()
+ ret['access']['token'] = dict()
+ token = ret['access']['token']
+
+ token['issued_at'] = "2014-01-30T15:30:58.819Z"
+ token['expires'] = "2999-01-30T15:30:58.819Z"
+ token['id'] = req['auth'].get('token', {'id': 'fc394f2ab2df4114bde39905f800dc57'}).get('id')
+ token['tenant'] = dict()
+ token['tenant']['description'] = None
+ token['tenant']['enabled'] = True
+ token['tenant']['id'] = req['auth'].get('tenantId', 'fc394f2ab2df4114bde39905f800dc57')
+ token['tenant']['name'] = "tenantName"
+
+ ret['access']['user'] = dict()
+ user = ret['access']['user']
+ user['username'] = req.get('username', "username")
+ user['name'] = "tenantName"
+ user['roles_links'] = list()
+ user['id'] = token['tenant'].get('id', "fc394f2ab2df4114bde39905f800dc57")
+ user['roles'] = [{'name': 'Member'}]
+
+ ret['access']['region_name'] = "RegionOne"
+
+ ret['access']['serviceCatalog'] = [{
+ "endpoints": [
+ {
+ "adminURL": "http://%s:%s/v2.1/%s" % (self.api.ip, self.api.port + 3774, user['id']),
+ "region": "RegionOne",
+ "internalURL": "http://%s:%s/v2.1/%s" % (self.api.ip, self.api.port + 3774, user['id']),
+ "id": "2dad48f09e2a447a9bf852bcd93548ef",
+ "publicURL": "http://%s:%s/v2.1/%s" % (self.api.ip, self.api.port + 3774, user['id'])
+ }
+ ],
+ "endpoints_links": [],
+ "type": "compute",
+ "name": "nova"
+ },
+ {
+ "endpoints": [
+ {
+ "adminURL": "http://%s:%s/v2.0" % (self.api.ip, self.api.port),
+ "region": "RegionOne",
+ "internalURL": "http://%s:%s/v2.0" % (self.api.ip, self.api.port),
+ "id": "2dad48f09e2a447a9bf852bcd93543fc",
+ "publicURL": "http://%s:%s/v2" % (self.api.ip, self.api.port)
+ }
+ ],
+ "endpoints_links": [],
+ "type": "identity",
+ "name": "keystone"
+ },
+ {
+ "endpoints": [
+ {
+ "adminURL": "http://%s:%s" % (self.api.ip, self.api.port + 4696),
+ "region": "RegionOne",
+ "internalURL": "http://%s:%s" % (self.api.ip, self.api.port + 4696),
+ "id": "2dad48f09e2a447a9bf852bcd93548cf",
+ "publicURL": "http://%s:%s" % (self.api.ip, self.api.port + 4696)
+ }
+ ],
+ "endpoints_links": [],
+ "type": "network",
+ "name": "neutron"
+ },
+ {
+ "endpoints": [
+ {
+ "adminURL": "http://%s:%s" % (self.api.ip, self.api.port + 4242),
+ "region": "RegionOne",
+ "internalURL": "http://%s:%s" % (self.api.ip, self.api.port + 4242),
+ "id": "2dad48f09e2a447a9bf852bcd93548cf",
+ "publicURL": "http://%s:%s" % (self.api.ip, self.api.port + 4242)
+ }
+ ],
+ "endpoints_links": [],
+ "type": "image",
+ "name": "glance"
+ },
+ {
+ "endpoints": [
+ {
+ "adminURL": "http://%s:%s/v1/%s" % (self.api.ip, self.api.port + 3004, user['id']),
+ "region": "RegionOne",
+ "internalURL": "http://%s:%s/v1/%s" % (self.api.ip, self.api.port + 3004, user['id']),
+ "id": "2dad48f09e2a447a9bf852bcd93548bf",
+ "publicURL": "http://%s:%s/v1/%s" % (self.api.ip, self.api.port + 3004, user['id'])
+ }
+ ],
+ "endpoints_links": [],
+ "type": "orchestration",
+ "name": "heat"
+ }
+ ]
+
+ ret['access']["metadata"] = {
+ "is_admin": 0,
+ "roles": [
+ "7598ac3c634d4c3da4b9126a5f67ca2b"
+ ]
+ },
+ ret['access']['trust'] = {
+ "id": "394998fa61f14736b1f0c1f322882949",
+ "trustee_user_id": "269348fdd9374b8885da1418e0730af1",
+ "trustor_user_id": "3ec3164f750146be97f21559ee4d9c51",
+ "impersonation": False
+ }
+ return Response(json.dumps(ret), status=200, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Keystone: Get token failed.")
+ return ex.message, 500
--- /dev/null
+from flask_restful import Resource
+from flask import request, Response
+from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy
+from datetime import datetime
+import logging
+import json
+import uuid
+import copy
+
+
+class NeutronDummyApi(BaseOpenstackDummy):
+ def __init__(self, ip, port, compute):
+ super(NeutronDummyApi, self).__init__(ip, port)
+ self.compute = compute
+
+ self.api.add_resource(NeutronListAPIVersions, "/")
+ self.api.add_resource(Shutdown, "/shutdown")
+ self.api.add_resource(NeutronShowAPIv2Details, "/v2.0")
+ self.api.add_resource(NeutronListNetworks, "/v2.0/networks.json", "/v2.0/networks",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronShowNetwork, "/v2.0/networks/<network_id>.json", "/v2.0/networks/<network_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronCreateNetwork, "/v2.0/networks.json", "/v2.0/networks",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronUpdateNetwork, "/v2.0/networks/<network_id>.json", "/v2.0/networks/<network_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronDeleteNetwork, "/v2.0/networks/<network_id>.json", "/v2.0/networks/<network_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronListSubnets, "/v2.0/subnets.json", "/v2.0/subnets",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronShowSubnet, "/v2.0/subnets/<subnet_id>.json", "/v2.0/subnets/<subnet_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronCreateSubnet, "/v2.0/subnets.json", "/v2.0/subnets",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronUpdateSubnet, "/v2.0/subnets/<subnet_id>.json", "/v2.0/subnets/<subnet_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronDeleteSubnet, "/v2.0/subnets/<subnet_id>.json", "/v2.0/subnets/<subnet_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronListPorts, "/v2.0/ports.json", "/v2.0/ports",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronShowPort, "/v2.0/ports/<port_id>.json", "/v2.0/ports/<port_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronCreatePort, "/v2.0/ports.json", "/v2.0/ports",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronUpdatePort, "/v2.0/ports/<port_id>.json", "/v2.0/ports/<port_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronDeletePort, "/v2.0/ports/<port_id>.json", "/v2.0/ports/<port_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NeutronAddFloatingIp, "/v2.0/floatingips.json", "/v2.0/floatingips",
+ resource_class_kwargs={'api': self})
+
+ def _start_flask(self):
+ logging.info("Starting %s endpoint @ http://%s:%d" % (__name__, self.ip, self.port))
+ if self.app is not None:
+ self.app.before_request(self.dump_playbook)
+ self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+
+class Shutdown(Resource):
+ def get(self):
+ logging.debug(("%s is beeing shut down") % (__name__))
+ func = request.environ.get('werkzeug.server.shutdown')
+ if func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ func()
+
+
+class NeutronListAPIVersions(Resource):
+ def get(self):
+ """
+ Lists API versions.
+
+ :return: Returns a json with API versions.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: Neutron - List API Versions")
+ resp = dict()
+ resp['versions'] = dict()
+
+ versions = [{
+ "status": "CURRENT",
+ "id": "v2.0",
+ "links": [
+ {
+ "href": request.url_root + '/v2.0',
+ "rel": "self"
+ }
+ ]
+ }]
+ resp['versions'] = versions
+
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+
+
+class NeutronShowAPIv2Details(Resource):
+ def get(self):
+ """
+ Returns API details.
+
+ :return: Returns a json with API details.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ resp = dict()
+
+ resp['resources'] = dict()
+ resp['resources'] = [{
+ "links": [
+ {
+ "href": request.url_root + 'v2.0/subnets',
+ "rel": "self"
+ }
+ ],
+ "name": "subnet",
+ "collection": "subnets"
+ },
+ {
+ "links": [
+ {
+ "href": request.url_root + 'v2.0/networks',
+ "rel": "self"
+ }
+ ],
+ "name": "network",
+ "collection": "networks"
+ },
+ {
+ "links": [
+ {
+ "href": request.url_root + 'v2.0/ports',
+ "rel": "self"
+ }
+ ],
+ "name": "ports",
+ "collection": "ports"
+ }
+ ]
+
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+
+
+class NeutronListNetworks(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ Lists all networks, used in son-emu. If a 'name' or one or more 'id's are specified, it will only list the
+ network with the name, or the networks specified via id.
+
+ :return: Returns a json response, starting with 'networks' as root node.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ if request.args.get('name'):
+ tmp_network = NeutronShowNetwork(self.api)
+ return tmp_network.get_network(request.args.get('name'), True)
+ id_list = request.args.getlist('id')
+ if len(id_list) == 1:
+ tmp_network = NeutronShowNetwork(self.api)
+ return tmp_network.get_network(request.args.get('id'), True)
+
+ network_list = list()
+ network_dict = dict()
+
+ if len(id_list) == 0:
+ for net in self.api.compute.nets.values():
+ tmp_network_dict = net.create_network_dict()
+ if tmp_network_dict not in network_list:
+ network_list.append(tmp_network_dict)
+ else:
+ for net in self.api.compute.nets.values():
+ if net.id in id_list:
+ tmp_network_dict = net.create_network_dict()
+ if tmp_network_dict not in network_list:
+ network_list.append(tmp_network_dict)
+
+ network_dict["networks"] = network_list
+
+ return Response(json.dumps(network_dict), status=200, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Neutron: List networks exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronShowNetwork(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, network_id):
+ """
+ Returns the network, specified via 'network_id'.
+
+ :param network_id: The unique ID string of the network.
+ :type network_id: ``str``
+ :return: Returns a json response, starting with 'network' as root node and one network description.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ return self.get_network(network_id, False)
+
+ def get_network(self, network_name_or_id, as_list):
+ """
+ Returns one network description of the network, specified via 'network_name_or_id'.
+
+ :param network_name_or_id: The indicator string, which specifies the requested network.
+ :type network_name_or_id: ``str``
+ :param as_list: Determines if the network description should start with the root node 'network' or 'networks'.
+ :type as_list: ``bool``
+ :return: Returns a json response, with one network description.
+ :rtype: :class:`flask.response`
+ """
+ try:
+ net = self.api.compute.find_network_by_name_or_id(network_name_or_id)
+ if net is None:
+ return Response(u'Network not found.\n', status=404, mimetype='application/json')
+
+ tmp_network_dict = net.create_network_dict()
+ tmp_dict = dict()
+ if as_list:
+ tmp_dict["networks"] = [tmp_network_dict]
+ else:
+ tmp_dict["network"] = tmp_network_dict
+
+ return Response(json.dumps(tmp_dict), status=200, mimetype='application/json')
+
+
+ except Exception as ex:
+ logging.exception("Neutron: Show network exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronCreateNetwork(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def post(self):
+ """
+ Creates a network with the name, specified within the request under ['network']['name'].
+
+ :return: * 400, if the network already exists.
+ * 500, if any exception occurred while creation.
+ * 201, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ try:
+ network_dict = json.loads(request.data)
+ name = network_dict['network']['name']
+ net = self.api.compute.find_network_by_name_or_id(name)
+ if net is not None:
+ return Response('Network already exists.\n', status=400, mimetype='application/json')
+
+ net = self.api.compute.create_network(name)
+ return Response(json.dumps({"network": net.create_network_dict()}), status=201, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron: Create network excepiton.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronUpdateNetwork(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def put(self, network_id): # TODO currently only the name will be changed
+ """
+ Updates the existing network with the given parameters.
+
+ :param network_id: The indicator string, which specifies the requested network.
+ :type network_id: ``str``
+ :return: * 404, if the network could not be found.
+ * 500, if any exception occurred while updating the network.
+ * 200, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s PUT" % str(self.__class__.__name__))
+ try:
+ if network_id in self.api.compute.nets:
+ net = self.api.compute.nets[network_id]
+ network_dict = json.loads(request.data)
+ old_net = copy.copy(net)
+
+ if "status" in network_dict["network"]:
+ net.status = network_dict["network"]["status"]
+ if "subnets" in network_dict["network"]:
+ pass # tmp_network_dict["subnets"] = None
+ if "name" in network_dict["network"] and net.name != network_dict["network"]["name"]:
+ net.name = network_dict["network"]["name"]
+ if "admin_state_up" in network_dict["network"]:
+ pass # tmp_network_dict["admin_state_up"] = True
+ if "tenant_id" in network_dict["network"]:
+ pass # tmp_network_dict["tenant_id"] = "c1210485b2424d48804aad5d39c61b8f"
+ if "shared" in network_dict["network"]:
+ pass # tmp_network_dict["shared"] = False
+
+ return Response(json.dumps(network_dict), status=200, mimetype='application/json')
+
+ return Response('Network not found.\n', status=404, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Neutron: Show networks exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronDeleteNetwork(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def delete(self, network_id):
+ """
+ Deletes the specified network and all its subnets.
+
+ :param network_id: The indicator string, which specifies the requested network.
+ :type network_id: ``str``
+ :return: * 404, if the network or the subnet could not be removed.
+ * 500, if any exception occurred while deletion.
+ * 204, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
+ try:
+ if network_id not in self.api.compute.nets:
+ return Response('Could not find network. (' + network_id + ')\n',
+ status=404, mimetype='application/json')
+
+ net = self.api.compute.nets[network_id]
+ delete_subnet = NeutronDeleteSubnet(self.api)
+ resp = delete_subnet.delete(net.subnet_id)
+
+ if not '204' in resp.status and not '404' in resp.status:
+ return resp
+
+ self.api.compute.delete_network(network_id)
+
+ return Response('Network ' + str(network_id) + ' deleted.\n', status=204, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron: Delete network exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronListSubnets(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ Lists all subnets, used in son-emu. If a 'name' or one or more 'id's are specified, it will only list the
+ subnet with the name, or the subnets specified via id.
+
+ :return: Returns a json response, starting with 'subnets' as root node.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ if request.args.get('name'):
+ show_subnet = NeutronShowSubnet(self.api)
+ return show_subnet.get_subnet(request.args.get('name'), True)
+ id_list = request.args.getlist('id')
+ if len(id_list) == 1:
+ show_subnet = NeutronShowSubnet(self.api)
+ return show_subnet.get_subnet(id_list[0], True)
+
+ subnet_list = list()
+ subnet_dict = dict()
+
+ if len(id_list) == 0:
+ for net in self.api.compute.nets.values():
+ if net.subnet_id is not None:
+ tmp_subnet_dict = net.create_subnet_dict()
+ subnet_list.append(tmp_subnet_dict)
+ else:
+ for net in self.api.compute.nets.values():
+ if net.subnet_id in id_list:
+ tmp_subnet_dict = net.create_subnet_dict()
+ subnet_list.append(tmp_subnet_dict)
+
+ subnet_dict["subnets"] = subnet_list
+
+ return Response(json.dumps(subnet_dict), status=200, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Neutron: List subnets exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronShowSubnet(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, subnet_id):
+ """
+ Returns the subnet, specified via 'subnet_id'.
+
+ :param subnet_id: The unique ID string of the subnet.
+ :type subnet_id: ``str``
+ :return: Returns a json response, starting with 'subnet' as root node and one subnet description.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ return self.get_subnet(subnet_id, False)
+
+ def get_subnet(self, subnet_name_or_id, as_list):
+ """
+ Returns one subnet description of the subnet, specified via 'subnet_name_or_id'.
+
+ :param subnet_name_or_id: The indicator string, which specifies the requested subnet.
+ :type subnet_name_or_id: ``str``
+ :param as_list: Determines if the subnet description should start with the root node 'subnet' or 'subnets'.
+ :type as_list: ``bool``
+ :return: Returns a json response, with one subnet description.
+ :rtype: :class:`flask.response`
+ """
+ try:
+ for net in self.api.compute.nets.values():
+ if net.subnet_id == subnet_name_or_id or net.subnet_name == subnet_name_or_id:
+ tmp_subnet_dict = net.create_subnet_dict()
+ tmp_dict = dict()
+ if as_list:
+ tmp_dict["subnets"] = [tmp_subnet_dict]
+ else:
+ tmp_dict["subnet"] = tmp_subnet_dict
+ return Response(json.dumps(tmp_dict), status=200, mimetype='application/json')
+
+ return Response('Subnet not found. (' + subnet_name_or_id + ')\n', status=404, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Neutron: Show subnet exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronCreateSubnet(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def post(self):
+ """
+ Creates a subnet with the name, specified within the request under ['subnet']['name'].
+
+ :return: * 400, if the 'CIDR' format is wrong or it does not exist.
+ * 404, if the network was not found.
+ * 409, if the corresponding network already has one subnet.
+ * 500, if any exception occurred while creation and
+ * 201, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ try:
+ subnet_dict = json.loads(request.data)
+ net = self.api.compute.find_network_by_name_or_id(subnet_dict['subnet']['network_id'])
+
+ if net is None:
+ return Response('Could not find network.\n', status=404, mimetype='application/json')
+
+ net.subnet_name = subnet_dict["subnet"].get('name', str(net.name) + '-sub')
+ if net.subnet_id is not None:
+ return Response('Only one subnet per network is supported\n', status=409, mimetype='application/json')
+
+ if "id" in subnet_dict["subnet"]:
+ net.subnet_id = subnet_dict["subnet"]["id"]
+ else:
+ net.subnet_id = str(uuid.uuid4())
+ import emuvim.api.openstack.ip_handler as IP
+ net.set_cidr(IP.get_new_cidr(net.subnet_id))
+
+ if "tenant_id" in subnet_dict["subnet"]:
+ pass
+ if "allocation_pools" in subnet_dict["subnet"]:
+ pass
+ if "gateway_ip" in subnet_dict["subnet"]:
+ net.gateway_ip = subnet_dict["subnet"]["gateway_ip"]
+ if "ip_version" in subnet_dict["subnet"]:
+ pass
+ if "enable_dhcp" in subnet_dict["subnet"]:
+ pass
+
+ return Response(json.dumps({'subnet': net.create_subnet_dict()}), status=201, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Neutron: Create network excepiton.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronUpdateSubnet(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def put(self, subnet_id):
+ """
+ Updates the existing subnet with the given parameters.
+
+ :param subnet_id: The indicator string, which specifies the requested subnet.
+ :type subnet_id: ``str``
+ :return: * 404, if the network could not be found.
+ * 500, if any exception occurred while updating the network.
+ * 200, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s PUT" % str(self.__class__.__name__))
+ try:
+ for net in self.api.compute.nets.values():
+ if net.subnet_id == subnet_id:
+ subnet_dict = json.loads(request.data)
+
+ if "name" in subnet_dict["subnet"]:
+ net.subnet_name = subnet_dict["subnet"]["name"]
+ if "network_id" in subnet_dict["subnet"]:
+ net.id = subnet_dict["subnet"]["network_id"]
+ if "tenant_id" in subnet_dict["subnet"]:
+ pass
+ if "allocation_pools" in subnet_dict["subnet"]:
+ pass
+ if "gateway_ip" in subnet_dict["subnet"]:
+ net.gateway_ip = subnet_dict["subnet"]["gateway_ip"]
+ if "ip_version" in subnet_dict["subnet"]:
+ pass
+ if "cidr" in subnet_dict["subnet"]:
+ net.set_cidr(subnet_dict["subnet"]["cidr"])
+ if "id" in subnet_dict["subnet"]:
+ net.subnet_id = subnet_dict["subnet"]["id"]
+ if "enable_dhcp" in subnet_dict["subnet"]:
+ pass
+
+ net.subnet_update_time = str(datetime.now())
+ tmp_dict = {'subnet': net.create_subnet_dict()}
+ return Response(json.dumps(tmp_dict), status=200, mimetype='application/json')
+
+ return Response('Network not found.\n', status=404, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Neutron: Show networks exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronDeleteSubnet(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def delete(self, subnet_id):
+ """
+ Deletes the specified subnet.
+
+ :param subnet_id: The indicator string, which specifies the requested subnet.
+ :type subnet_id: ``str``
+ :return: * 404, if the subnet could not be removed.
+ * 500, if any exception occurred while deletion.
+ * 204, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
+ try:
+ for net in self.api.compute.nets.values():
+ if net.subnet_id == subnet_id:
+ for server in self.api.compute.computeUnits.values():
+ for port_name in server.port_names:
+ port = self.api.compute.find_port_by_name_or_id(port_name)
+ if port.net_name == net.name:
+ port.ip_address = None
+ self.api.compute.dc.net.removeLink(
+ link=None,
+ node1=self.api.compute.dc.containers[server.name],
+ node2=self.api.compute.dc.switch)
+ port.net_name = None
+
+ net.delete_subnet()
+
+ return Response('Subnet ' + str(subnet_id) + ' deleted.\n',
+ status=204, mimetype='application/json')
+
+ return Response('Could not find subnet.', status=404, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron: Delete subnet exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronListPorts(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ Lists all ports, used in son-emu. If a 'name' or one or more 'id's are specified, it will only list the
+ port with the name, or the ports specified via id.
+
+ :return: Returns a json response, starting with 'ports' as root node.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ if request.args.get('name'):
+ show_port = NeutronShowPort(self.api)
+ return show_port.get_port(request.args.get('name'), True)
+ id_list = request.args.getlist('id')
+ if len(id_list) == 1:
+ show_port = NeutronShowPort(self.api)
+ return show_port.get_port(request.args.get('id'), True)
+
+ port_list = list()
+ port_dict = dict()
+
+ if len(id_list) == 0:
+ for port in self.api.compute.ports.values():
+ tmp_port_dict = port.create_port_dict(self.api.compute)
+ port_list.append(tmp_port_dict)
+ else:
+ for port in self.api.compute.ports.values():
+ if port.id in id_list:
+ tmp_port_dict = port.create_port_dict(self.api.compute)
+ port_list.append(tmp_port_dict)
+
+ port_dict["ports"] = port_list
+
+ return Response(json.dumps(port_dict), status=200, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Neutron: List ports exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronShowPort(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, port_id):
+ """
+ Returns the port, specified via 'port_id'.
+
+ :param port_id: The unique ID string of the network.
+ :type port_id: ``str``
+ :return: Returns a json response, starting with 'port' as root node and one network description.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ return self.get_port(port_id, False)
+
+ def get_port(self, port_name_or_id, as_list):
+ """
+ Returns one network description of the port, specified via 'port_name_or_id'.
+
+ :param port_name_or_id: The indicator string, which specifies the requested port.
+ :type port_name_or_id: ``str``
+ :param as_list: Determines if the port description should start with the root node 'port' or 'ports'.
+ :type as_list: ``bool``
+ :return: Returns a json response, with one port description.
+ :rtype: :class:`flask.response`
+ """
+ try:
+ port = self.api.compute.find_port_by_name_or_id(port_name_or_id)
+ if port is None:
+ return Response('Port not found. (' + port_name_or_id + ')\n', status=404, mimetype='application/json')
+ tmp_port_dict = port.create_port_dict(self.api.compute)
+ tmp_dict = dict()
+ if as_list:
+ tmp_dict["ports"] = [tmp_port_dict]
+ else:
+ tmp_dict["port"] = tmp_port_dict
+ return Response(json.dumps(tmp_dict), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron: Show port exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronCreatePort(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def post(self):
+ """
+ Creates a port with the name, specified within the request under ['port']['name'].
+
+ :return: * 404, if the network could not be found.
+ * 500, if any exception occurred while creation and
+ * 201, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ try:
+ port_dict = json.loads(request.data)
+ net_id = port_dict['port']['network_id']
+
+ if net_id not in self.api.compute.nets:
+ return Response('Could not find network.\n', status=404, mimetype='application/json')
+
+ net = self.api.compute.nets[net_id]
+ if 'name' in port_dict['port']:
+ name = port_dict['port']['name']
+ else:
+ num_ports = len(self.api.compute.ports)
+ name = "port:cp%s:man:%s" % (num_ports, str(uuid.uuid4()))
+
+ if self.api.compute.find_port_by_name_or_id(name):
+ return Response("Port with name %s already exists.\n" % name, status=500, mimetype='application/json')
+
+ port = self.api.compute.create_port(name)
+
+ port.net_name = net.name
+ port.ip_address = net.get_new_ip_address(name)
+
+ if "admin_state_up" in port_dict["port"]:
+ pass
+ if "device_id" in port_dict["port"]:
+ pass
+ if "device_owner" in port_dict["port"]:
+ pass
+ if "fixed_ips" in port_dict["port"]:
+ pass
+ if "mac_address" in port_dict["port"]:
+ port.mac_address = port_dict["port"]["mac_address"]
+ if "status" in port_dict["port"]:
+ pass
+ if "tenant_id" in port_dict["port"]:
+ pass
+
+ # add the port to a stack if the specified network is a stack network
+ for stack in self.api.compute.stacks.values():
+ for net in stack.nets.values():
+ if net.id == net_id:
+ stack.ports[name] = port
+
+ return Response(json.dumps({'port': port.create_port_dict(self.api.compute)}), status=201,
+ mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron: Show port exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronUpdatePort(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def put(self, port_id):
+ """
+ Updates the existing port with the given parameters.
+
+ :param network_id: The indicator string, which specifies the requested port.
+ :type network_id: ``str``
+ :return: * 404, if the network could not be found.
+ * 500, if any exception occurred while updating the network.
+ * 200, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s PUT" % str(self.__class__.__name__))
+ try:
+ port_dict = json.loads(request.data)
+ port = self.api.compute.find_port_by_name_or_id(port_id)
+ if port is None:
+ return Response("Port with id %s does not exists.\n" % port_id, status=404, mimetype='application/json')
+ old_port = copy.copy(port)
+
+ stack = None
+ for s in self.api.compute.stacks.values():
+ for port in s.ports.values():
+ if port.id == port_id:
+ stack = s
+ if "admin_state_up" in port_dict["port"]:
+ pass
+ if "device_id" in port_dict["port"]:
+ pass
+ if "device_owner" in port_dict["port"]:
+ pass
+ if "fixed_ips" in port_dict["port"]:
+ pass
+ if "id" in port_dict["port"]:
+ port.id = port_dict["port"]["id"]
+ if "mac_address" in port_dict["port"]:
+ port.mac_address = port_dict["port"]["mac_address"]
+ if "name" in port_dict["port"] and port_dict["port"]["name"] != port.name:
+ port.set_name(port_dict["port"]["name"])
+ if stack is not None:
+ if port.net_name in stack.nets:
+ stack.nets[port.net_name].update_port_name_for_ip_address(port.ip_address, port.name)
+ stack.ports[port.name] = stack.ports[old_port.name]
+ del stack.ports[old_port.name]
+ if "network_id" in port_dict["port"]:
+ pass
+ if "status" in port_dict["port"]:
+ pass
+ if "tenant_id" in port_dict["port"]:
+ pass
+
+ return Response(json.dumps({'port': port.create_port_dict(self.api.compute)}), status=200,
+ mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron: Update port exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronDeletePort(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def delete(self, port_id):
+ """
+ Deletes the specified port.
+
+ :param port_id: The indicator string, which specifies the requested port.
+ :type port_id: ``str``
+ :return: * 404, if the port could not be found.
+ * 500, if any exception occurred while deletion.
+ * 204, if everything worked out.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
+ try:
+ port = self.api.compute.find_port_by_name_or_id(port_id)
+ if port is None:
+ return Response("Port with id %s does not exists.\n" % port_id, status=404)
+ stack = None
+ for s in self.api.compute.stacks.values():
+ for p in s.ports.values():
+ if p.id == port_id:
+ stack = s
+ if stack is not None:
+ if port.net_name in stack.nets:
+ stack.nets[port.net_name].withdraw_ip_address(port.ip_address)
+ for server in stack.servers.values():
+ try:
+ server.port_names.remove(port.name)
+ except ValueError:
+ pass
+
+ # delete the port
+ self.api.compute.delete_port(port.id)
+
+ return Response('Port ' + port_id + ' deleted.\n', status=204, mimetype='application/json')
+
+ except Exception as ex:
+ logging.exception("Neutron: Delete port exception.")
+ return Response(ex.message, status=500, mimetype='application/json')
+
+
+class NeutronAddFloatingIp(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ Added a quick and dirty fake for the OSM integration. Returns a list of
+ floating IPs. Has nothing to do with the setup inside the emulator.
+ But its enough to make the OSM driver happy.
+ @PG Sandman: Feel free to improve this and let it do something meaningful.
+ """
+ resp = dict()
+ resp["floatingips"] = list()
+ # create a list of floting IP definitions and return it
+ for i in range(100, 110):
+ ip=dict()
+ ip["router_id"] = "router_id"
+ ip["description"] = "hardcoded in api"
+ ip["created_at"] = "router_id"
+ ip["updated_at"] = "router_id"
+ ip["revision_number"] = 1
+ ip["tenant_id"] = "tenant_id"
+ ip["project_id"] = "project_id"
+ ip["floating_network_id"] = str(i)
+ ip["status"] = "ACTIVE"
+ ip["id"] = str(i)
+ ip["port_id"] = "port_id"
+ ip["floating_ip_address"] = "172.0.0.%d" % i
+ ip["fixed_ip_address"] = "10.0.0.%d" % i
+ resp["floatingips"].append(ip)
+ return Response(json.dumps(resp), status=200, mimetype='application/json')
+
+
+ def post(self):
+ """
+ Adds a floating IP to neutron.
+
+ :return: Returns a floating network description.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ try:
+ # Fiddle with floating_network !
+ req = json.loads(request.data)
+
+ network_id = req["floatingip"]["floating_network_id"]
+ net = self.api.compute.find_network_by_name_or_id(network_id)
+ if net != self.api.manage.floating_network:
+ return Response("You have to specify the existing floating network\n",
+ status=400, mimetype='application/json')
+
+ port_id = req["floatingip"].get("port_id", None)
+ port = self.api.compute.find_port_by_name_or_id(port_id)
+ if port is not None:
+ if port.net_name != self.api.manage.floating_network.name:
+ return Response("You have to specify a port in the floating network\n",
+ status=400, mimetype='application/json')
+
+ if port.floating_ip is not None:
+ return Response("We allow only one floating ip per port\n", status=400, mimetype='application/json')
+ else:
+ num_ports = len(self.api.compute.ports)
+ name = "port:cp%s:fl:%s" % (num_ports, str(uuid.uuid4()))
+ port = self.api.compute.create_port(name)
+ port.net_name = net.name
+ port.ip_address = net.get_new_ip_address(name)
+
+ port.floating_ip = port.ip_address
+
+ response = dict()
+ resp = response["floatingip"] = dict()
+
+ resp["floating_network_id"] = net.id
+ resp["status"] = "ACTIVE"
+ resp["id"] = net.id
+ resp["port_id"] = port.id
+ resp["floating_ip_address"] = port.floating_ip
+ resp["fixed_ip_address"] = port.floating_ip
+
+ return Response(json.dumps(response), status=200, mimetype='application/json')
+ except Exception as ex:
+ logging.exception("Neutron: Create FloatingIP exception %s.", ex)
+ return Response(ex.message, status=500, mimetype='application/json')
--- /dev/null
+from flask_restful import Resource
+from flask import Response, request
+from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy
+import logging
+import json
+import uuid
+from mininet.link import Link
+
+
+class NovaDummyApi(BaseOpenstackDummy):
+ def __init__(self, in_ip, in_port, compute):
+ super(NovaDummyApi, self).__init__(in_ip, in_port)
+ self.compute = compute
+
+ self.api.add_resource(NovaVersionsList, "/",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(Shutdown, "/shutdown")
+ self.api.add_resource(NovaVersionShow, "/v2.1/<id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListServersApi, "/v2.1/<id>/servers",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListServersAndPortsApi, "/v2.1/<id>/servers/andPorts",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListServersDetailed, "/v2.1/<id>/servers/detail",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaShowServerDetails, "/v2.1/<id>/servers/<serverid>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaInterfaceToServer, "/v2.1/<id>/servers/<serverid>/os-interface",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaShowAndDeleteInterfaceAtServer, "/v2.1/<id>/servers/<serverid>/os-interface/<port_id>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListFlavors, "/v2.1/<id>/flavors", "/v2/<id>/flavors",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListFlavorsDetails, "/v2.1/<id>/flavors/detail", "/v2/<id>/flavors/detail",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListFlavorById, "/v2.1/<id>/flavors/<flavorid>", "/v2/<id>/flavors/<flavorid>",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListImages, "/v2.1/<id>/images",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListImagesDetails, "/v2.1/<id>/images/detail",
+ resource_class_kwargs={'api': self})
+ self.api.add_resource(NovaListImageById, "/v2.1/<id>/images/<imageid>",
+ resource_class_kwargs={'api': self})
+
+ def _start_flask(self):
+ logging.info("Starting %s endpoint @ http://%s:%d" % ("NovaDummyApi", self.ip, self.port))
+ # add some flavors for good measure
+ self.compute.add_flavor('m1.tiny', 1, 512, "MB", 1, "GB")
+ self.compute.add_flavor('m1.nano', 1, 64, "MB", 0, "GB")
+ self.compute.add_flavor('m1.micro', 1, 128, "MB", 0, "GB")
+ self.compute.add_flavor('m1.small', 1, 1024, "MB", 2, "GB")
+ if self.app is not None:
+ self.app.before_request(self.dump_playbook)
+ self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+
+class Shutdown(Resource):
+ """
+ A get request to /shutdown will shut down this endpoint.
+ """
+
+ def get(self):
+ logging.debug(("%s is beeing shut doen") % (__name__))
+ func = request.environ.get('werkzeug.server.shutdown')
+ if func is None:
+ raise RuntimeError('Not running with the Werkzeug Server')
+ func()
+
+
+class NovaVersionsList(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self):
+ """
+ Lists API versions.
+
+ :return: Returns a json with API versions.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ resp = """
+ {
+ "versions": [
+ {
+ "id": "v2.1",
+ "links": [
+ {
+ "href": "http://%s:%d/v2.1/",
+ "rel": "self"
+ }
+ ],
+ "status": "CURRENT",
+ "version": "2.38",
+ "min_version": "2.1",
+ "updated": "2013-07-23T11:33:21Z"
+ }
+ ]
+ }
+ """ % (self.api.ip, self.api.port)
+
+ response = Response(resp, status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not show list of versions." % __name__)
+ return ex.message, 500
+
+
+class NovaVersionShow(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ """
+ Returns API details.
+
+ :param id:
+ :type id: ``str``
+ :return: Returns a json with API details.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ try:
+ resp = """
+ {
+ "version": {
+ "id": "v2.1",
+ "links": [
+ {
+ "href": "http://%s:%d/v2.1/",
+ "rel": "self"
+ },
+ {
+ "href": "http://docs.openstack.org/",
+ "rel": "describedby",
+ "type": "text/html"
+ }
+ ],
+ "media-types": [
+ {
+ "base": "application/json",
+ "type": "application/vnd.openstack.compute+json;version=2.1"
+ }
+ ],
+ "status": "CURRENT",
+ "version": "2.38",
+ "min_version": "2.1",
+ "updated": "2013-07-23T11:33:21Z"
+ }
+ }
+ """ % (self.api.ip, self.api.port)
+
+ response = Response(resp, status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not show list of versions." % __name__)
+ return ex.message, 500
+
+
+class NovaListServersApi(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ """
+ Creates a list with all running servers and their detailed information.
+
+ :param id: Used to create a individual link to quarry further information.
+ :type id: ``str``
+ :return: Returns a json response with a dictionary that contains the server information.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ try:
+ resp = dict()
+ resp['servers'] = list()
+ for server in self.api.compute.computeUnits.values():
+ s = server.create_server_dict(self.api.compute)
+ s['links'] = [{'href': "http://%s:%d/v2.1/%s/servers/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ server.id)}]
+
+ resp['servers'].append(s)
+
+ response = Response(json.dumps(resp), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the list of servers." % __name__)
+ return ex.message, 500
+
+ def post(self, id):
+ """
+ Creates a server instance.
+
+ :param id: tenant id, we ignore this most of the time
+ :type id: ``str``
+ :return: Returns a flask response, with detailed information about the just created server.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ try:
+ server_dict = json.loads(request.data)['server']
+ networks = server_dict.get('networks', None)
+ name = str(self.api.compute.dc.label) + "_man_" + server_dict["name"][0:12]
+
+ if self.api.compute.find_server_by_name_or_id(name) is not None:
+ return Response("Server with name %s already exists." % name, status=409)
+ # TODO: not finished!
+ resp = dict()
+
+ 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"]
+
+ for flavor in self.api.compute.flavors.values():
+ if flavor.id == server_dict.get('flavorRef', ''):
+ server.flavor = flavor.name
+ for image in self.api.compute.images.values():
+ if image.id in server_dict['imageRef']:
+ server.image = image.name
+
+ if networks is not None:
+ for net in networks:
+ port = self.api.compute.find_port_by_name_or_id(net.get('port', ""))
+ if port is not None:
+ server.port_names.append(port.name)
+ else:
+ return Response("Currently only networking by port is supported.", status=400)
+
+ self.api.compute._start_compute(server)
+
+ response = NovaShowServerDetails(self.api).get(id, server.id)
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not create the server." % __name__)
+ return ex.message, 500
+
+
+class NovaListServersAndPortsApi(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ """
+ Creates a list with all running servers and their detailed information. This function also presents all
+ port information of each server.
+
+ :param id: Used to create a individual link to quarry further information.
+ :type id: ``str``
+ :return: Returns a json response with a dictionary that contains the server information.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ try:
+ resp = dict()
+ resp['servers'] = list()
+ for server in self.api.compute.computeUnits.values():
+ s = server.create_server_dict(self.api.compute)
+ s['links'] = [{'href': "http://%s:%d/v2.1/%s/servers/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ server.id)}]
+
+ s['ports'] = list()
+ for port_name in server.port_names:
+ port = self.api.compute.find_port_by_name_or_id(port_name)
+ if port is None:
+ continue
+
+ tmp = port.create_port_dict(self.api.compute)
+ tmp['intf_name'] = port.intf_name
+ s['ports'].append(tmp)
+
+ resp['servers'].append(s)
+
+ response = Response(json.dumps(resp), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the list of servers." % __name__)
+ return ex.message, 500
+
+
+class NovaListServersDetailed(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ """
+ As List Servers, it lists all running servers and their details but furthermore it also states the
+ used flavor and the server image.
+
+ :param id: tenant id, used for the 'href' link.
+ :type id: ``str``
+ :return: Returns a flask response, with detailed information aboit the servers and their flavor and image.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+
+ try:
+ resp = {"servers": list()}
+ for server in self.api.compute.computeUnits.values():
+ s = server.create_server_dict(self.api.compute)
+ s['links'] = [{'href': "http://%s:%d/v2.1/%s/servers/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ server.id)}]
+ flavor = self.api.compute.flavors[server.flavor]
+ s['flavor'] = {
+ "id": flavor.id,
+ "links": [
+ {
+ "href": "http://%s:%d/v2.1/%s/flavors/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ flavor.id),
+ "rel": "bookmark"
+ }
+ ]
+ }
+ image = self.api.compute.images[server.image]
+ s['image'] = {
+ "id": image.id,
+ "links": [
+ {
+ "href": "http://%s:%d/v2.1/%s/images/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ image.id),
+ "rel": "bookmark"
+ }
+ ]
+ }
+
+ resp['servers'].append(s)
+
+ response = Response(json.dumps(resp), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the list of servers." % __name__)
+ return ex.message, 500
+
+
+class NovaListFlavors(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ """
+ Lists all available flavors.
+
+ :param id: tenant id, used for the 'href' link
+ :type id: ``str``
+ :return: Returns a flask response with a list of all flavors.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ resp = dict()
+ resp['flavors'] = list()
+ for flavor in self.api.compute.flavors.values():
+ f = flavor.__dict__.copy()
+ f['id'] = flavor.id
+ f['name'] = flavor.name
+ f['links'] = [{'href': "http://%s:%d/v2.1/%s/flavors/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ flavor.id)}]
+ resp['flavors'].append(f)
+
+ response = Response(json.dumps(resp), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the list of servers." % __name__)
+ return ex.message, 500
+
+ def post(self, id):
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ data = json.loads(request.data).get("flavor")
+ logging.warning("Create Flavor: %s" % str(data))
+ # add to internal dict
+ f = self.api.compute.add_flavor(
+ data.get("name"),
+ data.get("vcpus"),
+ data.get("ram"), "MB",
+ data.get("disk"), "GB")
+ # create response based on incoming data
+ data["id"] = f.id
+ data["links"] = [{'href': "http://%s:%d/v2.1/%s/flavors/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ f.id)}]
+ resp = {"flavor": data}
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+
+class NovaListFlavorsDetails(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ """
+ Lists all flavors with additional information like ram and disk space.
+
+ :param id: tenant id, used for the 'href' link
+ :type id: ``str``
+ :return: Returns a flask response with a list of all flavors with additional information.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ resp = dict()
+ resp['flavors'] = list()
+ for flavor in self.api.compute.flavors.values():
+ # use the class dict. it should work fine
+ # but use a copy so we don't modifiy the original
+ f = flavor.__dict__.copy()
+ # add additional expected stuff stay openstack compatible
+ f['links'] = [{'href': "http://%s:%d/v2.1/%s/flavors/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ flavor.id)}]
+ f['OS-FLV-DISABLED:disabled'] = False
+ f['OS-FLV-EXT-DATA:ephemeral'] = 0
+ f['os-flavor-access:is_public'] = True
+ f['ram'] = flavor.memory
+ f['vcpus'] = flavor.cpu
+ f['swap'] = 0
+ f['disk'] = flavor.storage
+ f['rxtx_factor'] = 1.0
+ resp['flavors'].append(f)
+
+ response = Response(json.dumps(resp), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the list of servers." % __name__)
+ return ex.message, 500
+
+ def post(self, id):
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ data = json.loads(request.data).get("flavor")
+ logging.warning("Create Flavor: %s" % str(data))
+ # add to internal dict
+ f = self.api.compute.add_flavor(
+ data.get("name"),
+ data.get("vcpus"),
+ data.get("ram"), "MB",
+ data.get("disk"), "GB")
+ # create response based on incoming data
+ data["id"] = f.id
+ data["links"] = [{'href': "http://%s:%d/v2.1/%s/flavors/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ f.id)}]
+ resp = {"flavor": data}
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+
+class NovaListFlavorById(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id, flavorid):
+ """
+ Returns details about one flavor.
+
+ :param id: tenant id, used for the 'href' link
+ :type id: ``str``
+ :param flavorid: Represents the flavor.
+ :type flavorid: ``str``
+ :return: Returns a flask response with detailed information about the flavor.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ resp = dict()
+ resp['flavor'] = dict()
+ flavor = self.api.compute.flavors.get(flavorid, None)
+ if flavor is None:
+ for f in self.api.compute.flavors.values():
+ if f.id == flavorid:
+ flavor = f
+ break
+ resp['flavor']['id'] = flavor.id
+ resp['flavor']['name'] = flavor.name
+ resp['flavor']['links'] = [{'href': "http://%s:%d/v2.1/%s/flavors/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ flavor.id)}]
+ response = Response(json.dumps(resp), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve flavor with id %s" % (__name__, flavorid))
+ return ex.message, 500
+
+
+class NovaListImages(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ """
+ Creates a list of all usable images.
+
+ :param id: tenant id, used for the 'href' link
+ :type id: ``str``
+ :return: Returns a flask response with a list of available images.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ resp = dict()
+ resp['images'] = list()
+ for image in self.api.compute.images.values():
+ f = dict()
+ f['id'] = image.id
+ f['name'] = str(image.name).replace(":latest", "")
+ f['links'] = [{'href': "http://%s:%d/v2.1/%s/images/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ image.id)}]
+ resp['images'].append(f)
+ response = Response(json.dumps(resp), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the list of images." % __name__)
+ return ex.message, 500
+
+
+class NovaListImagesDetails(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id):
+ """
+ As List Images but with additional metadata.
+
+ :param id: tenant id, used for the 'href' link
+ :type id: ``str``
+ :return: Returns a flask response with a list of images and their metadata.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ resp = dict()
+ resp['images'] = list()
+ for image in self.api.compute.images.values():
+ # use the class dict. it should work fine
+ # but use a copy so we don't modifiy the original
+ f = image.__dict__.copy()
+ # add additional expected stuff stay openstack compatible
+ f['name'] = str(image.name).replace(":latest", "")
+ f['links'] = [{'href': "http://%s:%d/v2.1/%s/images/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ image.id)}]
+ f['metadata'] = {
+ "architecture": "x86_64",
+ "auto_disk_config": "True",
+ "kernel_id": "nokernel",
+ "ramdisk_id": "nokernel"
+ }
+ resp['images'].append(f)
+
+ response = Response(json.dumps(resp), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the list of images." % __name__)
+ return ex.message, 500
+
+
+class NovaListImageById(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id, imageid):
+ """
+ Gets an image by id from the emulator with openstack nova compliant return values.
+
+ :param id: tenantid, we ignore this most of the time
+ :type id: ``str``
+ :param imageid: id of the image. If it is 1 the dummy CREATE-IMAGE is returned
+ :type imageid: ``str``
+ :return: Returns a flask response with the information about one image.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ resp = dict()
+ i = resp['image'] = dict()
+ for image in self.api.compute.images.values():
+ if image.id == imageid or image.name == imageid:
+ i['id'] = image.id
+ i['name'] = image.name
+
+ return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+ response = Response("Image with id or name %s does not exists." % imageid, 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__, imageid))
+ return ex.message, 500
+
+
+class NovaShowServerDetails(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def get(self, id, serverid):
+ """
+ Returns detailed information about the specified server.
+
+ :param id: tenant id, used for the 'href' link
+ :type id: ``str``
+ :param serverid: Specifies the requested server.
+ :type serverid: ``str``
+ :return: Returns a flask response with details about the server.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ server = self.api.compute.find_server_by_name_or_id(serverid)
+ if server is None:
+ return Response("Server with id or name %s does not exists." % serverid, status=404)
+ s = server.create_server_dict()
+ s['links'] = [{'href': "http://%s:%d/v2.1/%s/servers/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ server.id)}]
+
+ flavor = self.api.compute.flavors[server.flavor]
+ s['flavor'] = {
+ "id": flavor.id,
+ "links": [
+ {
+ "href": "http://%s:%d/v2.1/%s/flavors/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ flavor.id),
+ "rel": "bookmark"
+ }
+ ]
+ }
+ image = self.api.compute.images[server.image]
+ s['image'] = {
+ "id": image.id,
+ "links": [
+ {
+ "href": "http://%s:%d/v2.1/%s/images/%s" % (self.api.ip,
+ self.api.port,
+ id,
+ image.id),
+ "rel": "bookmark"
+ }
+ ]
+ }
+
+ response = Response(json.dumps({'server': s}), status=200, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not retrieve the server details." % __name__)
+ return ex.message, 500
+
+ def delete(self, id, serverid):
+ """
+ Delete a server instance.
+
+ :param id: tenant id, we ignore this most of the time
+ :type id: ``str``
+ :param serverid: The UUID of the server
+ :type serverid: ``str``
+ :return: Returns 200 if everything is fine.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s POST" % str(self.__class__.__name__))
+ try:
+ server = self.api.compute.find_server_by_name_or_id(serverid)
+ if server is None:
+ return Response('Could not find server.', status=404, mimetype="application/json")
+
+ self.api.compute.stop_compute(server)
+
+ response = Response('Server deleted.', status=204, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not create the server." % __name__)
+ return ex.message, 500
+
+
+class NovaInterfaceToServer(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def post(self, id, serverid):
+ """
+ Add an interface to the specified server.
+
+ :param id: tenant id, we ignore this most of the time
+ :type id: ``str``
+ :param serverid: Specifies the server.
+ :type serverid: ``str``
+ :return: Returns a flask response with information about the attached interface.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ server = self.api.compute.find_server_by_name_or_id(serverid)
+ if server is None:
+ return Response("Server with id or name %s does not exists." % serverid, status=404)
+
+ if server.emulator_compute is None:
+ logging.error("The targeted container does not exist.")
+ return Response("The targeted container of %s does not exist." % serverid, status=404)
+ data = json.loads(request.data).get("interfaceAttachment")
+ resp = dict()
+ port = data.get("port_id", None)
+ net = data.get("net_id", None)
+ dc = self.api.compute.dc
+ network_dict = dict()
+ network = None
+
+ if net is not None and port is not None:
+ port = self.api.compute.find_port_by_name_or_id(port)
+ network = self.api.compute.find_network_by_name_or_id(net)
+ network_dict['id'] = port.intf_name
+ network_dict['ip'] = port.ip_address
+ network_dict[network_dict['id']] = network.name
+ elif net is not None:
+ network = self.api.compute.find_network_by_name_or_id(net)
+ if network is None:
+ return Response("Network with id or name %s does not exists." % net, status=404)
+ port = self.api.compute.create_port("port:cp%s:fl:%s" %
+ (len(self.api.compute.ports), str(uuid.uuid4())))
+
+ port.net_name = network.name
+ port.ip_address = network.get_new_ip_address(port.name)
+ network_dict['id'] = port.intf_name
+ network_dict['ip'] = port.ip_address
+ network_dict[network_dict['id']] = network.name
+ elif port is not None:
+ port = self.api.compute.find_port_by_name_or_id(port)
+ network_dict['id'] = port.intf_name
+ network_dict['ip'] = port.ip_address
+ network = self.api.compute.find_network_by_name_or_id(port.net_name)
+ network_dict[network_dict['id']] = network.name
+ else:
+ raise Exception("You can only attach interfaces by port or network at the moment")
+
+ if network == self.api.manage.floating_network:
+ dc.net.addLink(server.emulator_compute, self.api.manage.floating_switch,
+ params1=network_dict, cls=Link, intfName1=port.intf_name)
+ else:
+ dc.net.addLink(server.emulator_compute, dc.switch,
+ params1=network_dict, cls=Link, intfName1=port.intf_name)
+ resp["port_state"] = "ACTIVE"
+ resp["port_id"] = port.id
+ resp["net_id"] = self.api.compute.find_network_by_name_or_id(port.net_name).id
+ resp["mac_addr"] = port.mac_address
+ resp["fixed_ips"] = list()
+ fixed_ips = dict()
+ fixed_ips["ip_address"] = port.ip_address
+ fixed_ips["subnet_id"] = network.subnet_name
+ resp["fixed_ips"].append(fixed_ips)
+ response = Response(json.dumps({"interfaceAttachment": resp}), status=202, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not add interface to the server." % __name__)
+ return ex.message, 500
+
+
+class NovaShowAndDeleteInterfaceAtServer(Resource):
+ def __init__(self, api):
+ self.api = api
+
+ def delete(self, id, serverid, port_id):
+ """
+ Deletes an existing interface.
+
+ :param id: tenant id, we ignore this most of the time
+ :type id: ``str``
+ :param serverid: Specifies the server, where the interface will be deleted.
+ :type serverid: ``str``
+ :param port_id: Specifies the port of the interface.
+ :type port_id: ``str``
+ :return: Returns a flask response with 202 if everything worked out. Otherwise it will return 404 and an
+ error message.
+ :rtype: :class:`flask.response`
+ """
+ logging.debug("API CALL: %s GET" % str(self.__class__.__name__))
+ try:
+ server = self.api.compute.find_server_by_name_or_id(serverid)
+ if server is None:
+ return Response("Server with id or name %s does not exists." % serverid, status=404)
+ port = self.api.compute.find_port_by_name_or_id(port_id)
+ if port is None:
+ return Response("Port with id or name %s does not exists." % port_id, status=404)
+
+ for link in self.api.compute.dc.net.links:
+ if str(link.intf1) == port.intf_name and \
+ str(link.intf1.ip) == port.ip_address.split('/')[0]:
+ self.api.compute.dc.net.removeLink(link)
+ break
+
+ response = Response("", status=202, mimetype="application/json")
+ response.headers['Access-Control-Allow-Origin'] = '*'
+ return response
+
+ except Exception as ex:
+ logging.exception(u"%s: Could not detach interface from the server." % __name__)
+ return ex.message, 500
--- /dev/null
+from instance_flavor import InstanceFlavor
+from model import Model
+from net import Net
+from port import Port
+from resource import Resource
+from router import Router
+from server import Server
+from stack import Stack
+from template import Template
+from image import Image
\ No newline at end of file
--- /dev/null
+import uuid
+from datetime import datetime
+
+
+class Image:
+ def __init__(self, name, id=None):
+ self.name = name
+ if id is None:
+ self.id = str(uuid.uuid4())
+ else:
+ self.id = id
+ self.created = str(datetime.now())
+
+ def __eq__(self, other):
+ if self.name == other.name:
+ return True
+ return False
--- /dev/null
+import uuid
+
+
+class InstanceFlavor:
+ def __init__(self, name, cpu=None, memory=None, memory_unit=None, storage=None, storage_unit=None):
+ self.id = str(uuid.uuid4())
+ self.name = name
+ self.cpu = cpu
+ self.memory = memory
+ self.memory_unit = memory_unit
+ self.storage = storage
+ self.storage_unit = storage_unit
--- /dev/null
+class LoadBalancer(object):
+ def __init__(self, name, id=None, flavor=None, image=None, command=None, nw_list=None):
+ self.name = name
+ self.id = id # not set
+ self.out_ports = dict()
+ self.in_ports = dict()
--- /dev/null
+class Model:
+ def __init__(self, resources=None):
+ if not resources:
+ resources = list()
+ self.resources = resources
--- /dev/null
+import re
+
+
+class Net:
+ def __init__(self, name):
+ self.name = name
+ self.id = None
+ self.subnet_name = None
+ self.subnet_id = None
+ self.subnet_creation_time = None
+ self.subnet_update_time = None
+ self.gateway_ip = None
+ self.segmentation_id = None # not set
+ self._cidr = None
+ self.start_end_dict = None
+ self._issued_ip_addresses = dict()
+
+ def get_short_id(self):
+ """
+ Returns a shortened UUID, with only the first 6 characters.
+
+ :return: First 6 characters of the UUID
+ :rtype: ``str``
+ """
+ return str(self.id)[:6]
+
+ def get_new_ip_address(self, port_name):
+ """
+ Calculates the next unused IP Address which belongs to the subnet.
+
+ :param port_name: Specifies the port.
+ :type port_name: ``str``
+ :return: Returns a unused IP Address or none if all are in use.
+ :rtype: ``str``
+ """
+ if self.start_end_dict is None:
+ return None
+
+ int_start_ip = Net.ip_2_int(self.start_end_dict['start']) + 2 # First address as network address not usable
+ # Second one is for gateways only
+ int_end_ip = Net.ip_2_int(self.start_end_dict['end']) - 1 # Last address for broadcasts
+ while int_start_ip in self._issued_ip_addresses and int_start_ip <= int_end_ip:
+ int_start_ip += 1
+
+ if int_start_ip > int_end_ip:
+ return None
+
+ self._issued_ip_addresses[int_start_ip] = port_name
+ return Net.int_2_ip(int_start_ip) + '/' + self._cidr.rsplit('/', 1)[1]
+
+ def assign_ip_address(self, cidr, port_name):
+ """
+ Assigns the IP address to the port if it is currently NOT used.
+
+ :param cidr: The cidr used by the port - e.g. 10.0.0.1/24
+ :type cidr: ``str``
+ :param port_name: The port name
+ :type port_name: ``str``
+ :return: * *False*: If the IP address is already issued or if it is not within this subnet mask.
+ * *True*: Else
+ """
+ int_ip = Net.cidr_2_int(cidr)
+ if int_ip in self._issued_ip_addresses:
+ return False
+
+ int_start_ip = Net.ip_2_int(self.start_end_dict['start']) + 1 # First address as network address not usable
+ int_end_ip = Net.ip_2_int(self.start_end_dict['end']) - 1 # Last address for broadcasts
+ if int_ip < int_start_ip or int_ip > int_end_ip:
+ return False
+
+ self._issued_ip_addresses[int_ip] = port_name
+ return True
+
+ def is_my_ip(self, cidr, port_name):
+ """
+ Checks if the IP is registered for this port name.
+
+ :param cidr: The cidr used by the port - e.g. 10.0.0.1/24
+ :type cidr: ``str``
+ :param port_name: The port name
+ :type port_name: ``str``
+ :return: Returns true if the IP address belongs to the port name. Else it returns false.
+ """
+ int_ip = Net.cidr_2_int(cidr)
+
+ if not int_ip in self._issued_ip_addresses:
+ return False
+
+ if self._issued_ip_addresses[int_ip] == port_name:
+ return True
+ return False
+
+ def withdraw_ip_address(self, ip_address):
+ """
+ Removes the IP address from the list of issued addresses, thus other ports can use it.
+
+ :param ip_address: The issued IP address.
+ :type ip_address: ``str``
+ """
+ if ip_address is None:
+ return
+
+ if "/" in ip_address:
+ address, suffix = ip_address.rsplit('/', 1)
+ else:
+ address = ip_address
+ int_ip_address = Net.ip_2_int(address)
+ if int_ip_address in self._issued_ip_addresses.keys():
+ del self._issued_ip_addresses[int_ip_address]
+
+ def reset_issued_ip_addresses(self):
+ """
+ Resets all issued IP addresses.
+ """
+ self._issued_ip_addresses = dict()
+
+ def update_port_name_for_ip_address(self, ip_address, port_name):
+ """
+ Updates the port name of the issued IP address.
+
+ :param ip_address: The already issued IP address.
+ :type ip_address: ``str``
+ :param port_name: The new port name
+ :type port_name: ``str``
+ """
+ address, suffix = ip_address.rsplit('/', 1)
+ int_ip_address = Net.ip_2_int(address)
+ self._issued_ip_addresses[int_ip_address] = port_name
+
+ def set_cidr(self, cidr):
+ """
+ Sets the CIDR for the subnet. It previously checks for the correct CIDR format.
+
+ :param cidr: The new CIDR for the subnet.
+ :type cidr: ``str``
+ :return: * *True*: When the new CIDR was set successfully.
+ * *False*: If the CIDR format was wrong.
+ :rtype: ``bool``
+ """
+ if cidr is None:
+ if self._cidr is not None:
+ import emuvim.api.openstack.ip_handler as IP
+ IP.free_cidr(self._cidr, self.subnet_id)
+ self._cidr = None
+ self.reset_issued_ip_addresses()
+ self.start_end_dict = dict()
+ return True
+ if not Net.check_cidr_format(cidr):
+ return False
+
+ self.reset_issued_ip_addresses()
+ self.start_end_dict = Net.calculate_start_and_end_dict(cidr)
+ self._cidr = cidr
+ return True
+
+ def get_cidr(self):
+ """
+ Gets the CIDR.
+
+ :return: The CIDR
+ :rtype: ``str``
+ """
+ return self._cidr
+
+ def clear_cidr(self):
+ self._cidr = None
+ self.start_end_dict = dict()
+ self.reset_issued_ip_addresses()
+
+ def delete_subnet(self):
+ self.subnet_id = None
+ self.subnet_name = None
+ self.subnet_creation_time = None
+ self.subnet_update_time = None
+ self.set_cidr(None)
+
+ @staticmethod
+ def calculate_start_and_end_dict(cidr):
+ """
+ Calculates the start and end IP address for the subnet.
+
+ :param cidr: The CIDR for the subnet.
+ :type cidr: ``str``
+ :return: Dict with start and end ip address
+ :rtype: ``dict``
+ """
+ address, suffix = cidr.rsplit('/', 1)
+ int_suffix = int(suffix)
+ int_address = Net.ip_2_int(address)
+ address_space = 2 ** 32 - 1
+
+ for x in range(0, 31 - int_suffix):
+ address_space = ~(~address_space | (1 << x))
+
+ start = int_address & address_space
+ end = start + (2 ** (32 - int_suffix) - 1)
+
+ return {'start': Net.int_2_ip(start), 'end': Net.int_2_ip(end)}
+
+ @staticmethod
+ def cidr_2_int(cidr):
+ if cidr is None:
+ return None
+ ip = cidr.rsplit('/', 1)[0]
+ return Net.ip_2_int(ip)
+
+ @staticmethod
+ def ip_2_int(ip):
+ """
+ Converts a IP address to int.
+
+ :param ip: IP address
+ :type ip: ``str``
+ :return: IP address as int.
+ :rtype: ``int``
+ """
+ o = map(int, ip.split('.'))
+ res = (16777216 * o[0]) + (65536 * o[1]) + (256 * o[2]) + o[3]
+ return res
+
+ @staticmethod
+ def int_2_ip(int_ip):
+ """
+ Converts a int IP address to string.
+
+ :param int_ip: Int IP address.
+ :type int_ip: ``int``
+ :return: IP address
+ :rtype: ``str``
+ """
+ o1 = int(int_ip / 16777216) % 256
+ o2 = int(int_ip / 65536) % 256
+ o3 = int(int_ip / 256) % 256
+ o4 = int(int_ip) % 256
+ return '%(o1)s.%(o2)s.%(o3)s.%(o4)s' % locals()
+
+ @staticmethod
+ def check_cidr_format(cidr):
+ """
+ Checks the CIDR format. An valid example is: 192.168.0.0/29
+
+ :param cidr: CIDR to be checked.
+ :type cidr: ``str``
+ :return: * *True*: If the Format is correct.
+ * *False*: If it is not correct.
+ :rtype: ``bool``
+ """
+ r = re.compile('\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/\d{2}')
+ if r.match(cidr):
+ return True
+ return False
+
+ def create_network_dict(self):
+ """
+ Creates the network description dictionary.
+
+ :return: Network description.
+ :rtype: ``dict``
+ """
+ network_dict = dict()
+ network_dict["status"] = "ACTIVE" # TODO do we support inactive networks?
+ if self.subnet_id == None:
+ network_dict["subnets"] = []
+ else:
+ network_dict["subnets"] = [self.subnet_id]
+ network_dict["name"] = self.name
+ network_dict["admin_state_up"] = True # TODO is it always true?
+ network_dict["tenant_id"] = "abcdefghijklmnopqrstuvwxyz123456" # TODO what should go in here
+ network_dict["id"] = self.id
+ network_dict["shared"] = False # TODO is it always false?
+ return network_dict
+
+ def create_subnet_dict(self):
+ """
+ Creates the subnet description dictionary.
+
+ :return: Subnet description.
+ :rtype: ``dict``
+ """
+ subnet_dict = dict()
+ subnet_dict["name"] = self.subnet_name
+ subnet_dict["network_id"] = self.id
+ subnet_dict["tenant_id"] = "abcdefghijklmnopqrstuvwxyz123456" # TODO what should go in here?
+ subnet_dict["created_at"] = self.subnet_creation_time
+ subnet_dict["dns_nameservers"] = []
+ subnet_dict["allocation_pools"] = [self.start_end_dict]
+ subnet_dict["host_routers"] = []
+ subnet_dict["gateway_ip"] = self.gateway_ip
+ subnet_dict["ip_version"] = "4"
+ subnet_dict["cidr"] = self.get_cidr()
+ subnet_dict["updated_at"] = self.subnet_update_time
+ subnet_dict["id"] = self.subnet_id
+ subnet_dict["enable_dhcp"] = False # TODO do we support DHCP?
+ return subnet_dict
+
+ def __eq__(self, other):
+ if self.name == other.name and self.subnet_name == other.subnet_name and \
+ self.gateway_ip == other.gateway_ip and \
+ self.segmentation_id == other.segmentation_id and \
+ self._cidr == other._cidr and \
+ self.start_end_dict == other.start_end_dict:
+ return True
+ return False
+
+ def __hash__(self):
+ return hash((self.name,
+ self.subnet_name,
+ self.gateway_ip,
+ self.segmentation_id,
+ self._cidr,
+ self.start_end_dict))
--- /dev/null
+import logging
+import threading
+import uuid
+
+lock = threading.Lock()
+intf_names = dict()
+
+
+class Port:
+ def __init__(self, name, ip_address=None, mac_address=None, floating_ip=None):
+ self.name = name
+ self.intf_name = None
+ self.id = str(uuid.uuid4())
+ self.template_name = name
+ """
+ ip_address is structured like 10.0.0.1/24
+ """
+ self.ip_address = ip_address
+ self.mac_address = mac_address
+ self.floating_ip = floating_ip
+ self.net_name = None
+
+ def set_name(self, name):
+ """
+ Sets the port name.
+
+ :param name: New port name.
+ :type name: ``str``
+ """
+ if self.name == name:
+ return
+
+ # Delete old interface name
+ global lock
+ lock.acquire()
+ if intf_names[self.intf_name][0] == self.id and intf_names[self.intf_name][1] is False:
+ del intf_names[self.intf_name]
+ lock.release()
+
+ self.name = name
+ # Create new interface name
+ self.create_intf_name()
+
+ def create_intf_name(self):
+ """
+ Creates the interface name, while using the first 4 letters of the port name, the specification, if it is an
+ 'in' / 'out' port or something else, and a counter value if the name is already used. The counter starts
+ for each name at 0 and can go up to 999. After creating the name each port will post its interface name
+ into the global dictionary and adding his full name. Thus each port can determine if his desired interface
+ name is already used and choose the next one.
+ """
+ split_name = self.name.split(':')
+ if len(split_name) >= 3:
+ if split_name[2] == 'input' or split_name[2] == 'in':
+ self.intf_name = split_name[0][:4] + '-' + \
+ 'in'
+ elif split_name[2] == 'output' or split_name[2] == 'out':
+ self.intf_name = split_name[0][:4] + '-' + \
+ 'out'
+ else:
+ self.intf_name = split_name[0][:4] + '-' + \
+ split_name[2][:4]
+ else:
+ self.intf_name = self.name[:9]
+
+ global lock
+ lock.acquire()
+ counter = 0
+ global intf_names
+ intf_len = len(self.intf_name)
+ self.intf_name = self.intf_name + '-' + str(counter)[:4]
+ while self.intf_name in intf_names and counter < 999 and not intf_names[self.intf_name][0] == self.id:
+ counter += 1
+ self.intf_name = self.intf_name[:intf_len] + '-' + str(counter)[:4]
+
+ if counter >= 1000:
+ logging.ERROR("Port %s could not create unique interface name (%s)", self.name, self.intf_name)
+ lock.release()
+ return
+
+ updated = False
+ if self.intf_name in intf_names and intf_names[self.intf_name][0] == self.id:
+ updated = True
+
+ intf_names[self.intf_name] = [self.id, updated]
+ lock.release()
+
+ def get_short_id(self):
+ """
+ Gets a shortened ID which only contains first 6 characters.
+
+ :return: The first 6 characters of the UUID.
+ :rtype: ``str``
+ """
+ return str(self.id)[:6]
+
+ def create_port_dict(self, compute):
+ """
+ Creates the port description dictionary.
+
+ :param compute: Requires the compute resource to determine the used network.
+ :type compute: :class:`heat.compute`
+ :return: Returns the description dictionary.
+ :rtype: ``dict``
+ """
+ port_dict = dict()
+ port_dict["admin_state_up"] = True # TODO is it always true?
+ port_dict["device_id"] = "257614cc-e178-4c92-9c61-3b28d40eca44" # TODO find real values
+ port_dict["device_owner"] = "" # TODO do we have such things?
+ net = compute.find_network_by_name_or_id(self.net_name)
+ port_dict["fixed_ips"] = [
+ {
+ "ip_address": self.ip_address.rsplit('/', 1)[0] if self.ip_address is not None else "",
+ "subnet_id": net.subnet_id if net is not None else ""
+ }
+ ]
+ port_dict["id"] = self.id
+ port_dict["mac_address"] = self.mac_address
+ port_dict["name"] = self.name
+ port_dict["network_id"] = net.id if net is not None else ""
+ port_dict["status"] = "ACTIVE" # TODO do we support inactive port?
+ port_dict["tenant_id"] = "abcdefghijklmnopqrstuvwxyz123456" # TODO find real tenant_id
+ return port_dict
+
+ def compare_attributes(self, other):
+ """
+ Does NOT compare ip_address because this function only exists to check if we can
+ update the IP address without any changes
+
+ :param other: The port to compare with
+ :type other: :class:`heat.resources.port`
+ :return: True if the attributes are the same, else False.
+ :rtype: ``bool``
+ """
+ if other is None:
+ return False
+
+ if self.name == other.name and self.floating_ip == other.floating_ip and \
+ self.net_name == other.net_name:
+ return True
+ return False
+
+ def __eq__(self, other):
+ if other is None:
+ return False
+
+ if self.name == other.name and self.ip_address == other.ip_address and \
+ self.mac_address == other.mac_address and \
+ self.floating_ip == other.floating_ip and \
+ self.net_name == other.net_name:
+ return True
+ return False
+
+ def __hash__(self):
+ return hash((self.name,
+ self.ip_address,
+ self.mac_address,
+ self.floating_ip,
+ self.net_name))
+
+ def __del__(self):
+ global lock
+ lock.acquire()
+ global intf_names
+ if self.intf_name in intf_names and intf_names[self.intf_name][0] == self.id:
+ if intf_names[self.intf_name][1] is False:
+ del intf_names[self.intf_name]
+ else:
+ intf_names[self.intf_name][1] = False
+ lock.release()
--- /dev/null
+class Resource:
+ def __init__(self, name, type=None, properties=None):
+ self.name = name
+ self.type = type
+ self.properties = properties
--- /dev/null
+import uuid
+
+
+class Router:
+ def __init__(self, name, id=None):
+ self.name = name
+ self.id = id if id is not None else str(uuid.uuid4())
+ self.subnet_names = list()
+
+ def add_subnet(self, subnet_name):
+ self.subnet_names.append(subnet_name)
+
+ def __eq__(self, other):
+ if self.name == other.name and len(self.subnet_names) == len(other.subnet_names) and \
+ set(self.subnet_names) == set(other.subnet_names):
+ return True
+ return False
--- /dev/null
+class Server(object):
+ def __init__(self, name, id=None, flavor=None, image=None, command=None, nw_list=None):
+ self.name = name
+ self.full_name = None
+ self.template_name = None
+ self.id = id
+ self.image = image
+ self.command = command
+ self.port_names = list()
+ self.flavor = flavor
+ self.son_emu_command = None
+ self.emulator_compute = None
+
+ def compare_attributes(self, other):
+ """
+ Compares only class attributes like name and flavor but not the list of ports with the other server.
+
+ :param other: The second server to compare with.
+ :type other: :class:`heat.resources.server`
+ :return: * *True*: If all attributes are alike.
+ * *False*: Else
+ :rtype: ``bool``
+ """
+ if self.name == other.name and self.full_name == other.full_name and \
+ self.flavor == other.flavor and \
+ self.image == other.image and \
+ self.command == other.command:
+ return True
+ return False
+
+ def __eq__(self, other):
+ if self.name == other.name and self.full_name == other.full_name and \
+ self.flavor == other.flavor and \
+ self.image == other.image and \
+ self.command == other.command and \
+ len(self.port_names) == len(other.port_names) and \
+ set(self.port_names) == set(other.port_names):
+ return True
+ return False
+
+ def create_server_dict(self, compute=None):
+ """
+ Creates the server description dictionary.
+
+ :param compute: The compute resource for further status information.
+ :type compute: :class:`heat.compute`
+ :return: Server description dictionary.
+ :rtype: ``dict``
+ """
+ server_dict = dict()
+ server_dict['name'] = self.name
+ server_dict['full_name'] = self.full_name
+ server_dict['id'] = self.id
+ server_dict['template_name'] = self.template_name
+ server_dict['flavor'] = self.flavor
+ server_dict['image'] = self.image
+ if self.son_emu_command is not None:
+ server_dict['command'] = self.son_emu_command
+ else:
+ server_dict['command'] = self.command
+
+ if compute is not None:
+ server_dict['status'] = 'ACTIVE'
+ server_dict['OS-EXT-STS:power_state'] = 1
+ server_dict["OS-EXT-STS:task_state"] = None
+ return server_dict
--- /dev/null
+import uuid
+
+
+class Stack:
+ def __init__(self, id=None):
+ self.servers = dict()
+ self.nets = dict()
+ self.ports = dict()
+ self.routers = dict()
+ self.stack_name = None
+ self.creation_time = None
+ self.update_time = None
+ self.status = None
+ if id is None:
+ self.id = str(uuid.uuid4())
+ else:
+ self.id = id
+
+ def add_server(self, server):
+ """
+ Adds one server to the server dictionary.
+
+ :param server: The server to add.
+ :type server: :class:`heat.resources.server`
+ """
+ self.servers[server.name] = server
+
+ def add_net(self, net):
+ """
+ Adds one network to the network dictionary.
+
+ :param net: Network to add.
+ :type net: :class:`heat.resources.net`
+ """
+ self.nets[net.name] = net
+
+ def add_port(self, port):
+ """
+ Adds one port to the port dictionary.
+
+ :param port: Port to add.
+ :type port: :class:`heat.resources.port`
+ """
+ self.ports[port.name] = port
+
+ def add_router(self, router):
+ """
+ Adds one router to the port dictionary.
+
+ :param router: Router to add.
+ :type router: :class:`heat.resources.router`
+ """
+ self.routers[router.name] = router
--- /dev/null
+class Template:
+ def __init__(self, resources=None):
+ self.version = '2015-04-30'
+ self.resources = resources
logging.info("Started API endpoint @ http://%s:%d" % (self.ip, self.port))
def _start_flask(self):
- #self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+ #self.app.run(self.ip, self.port, debug=False, use_reloader=False)
#this should be a more production-fit http-server
- http_server = WSGIServer((self.ip, self.port), self.app)
+ #self.app.logger.setLevel(logging.ERROR)
+ http_server = WSGIServer((self.ip, self.port),
+ self.app,
+ log=open("/dev/null", "w") # This disables HTTP request logs to not mess up the CLI when e.g. the auto-updated dashboard is used
+ )
http_server.serve_forever()
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
- <script src="js/main.js" type="text/javascript"></script>
+ <script src="js/main_upb.js" type="text/javascript"></script>
</head>
<div class="col-sm-4" id="logo"><a href="http://sonata-nfv.eu" target="_blank"><img src="img/SONATA_new.png" alt="SONATA Logo" width="252" height="70"></a></div>
</div>
-<nav class="navbar navbar-default">
- <div class="container-fluid">
- <!-- Brand and toggle get grouped for better mobile display -->
- <div class="navbar-header">
- <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
- <span class="sr-only">Toggle navigation</span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- <span class="icon-bar"></span>
- </button>
- </div>
-
- <!-- Collect the nav links, forms, and other content for toggling -->
- <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
- <div class="navbar-form navbar- navbar-btn btn-group" role="group" aria-label="category">
- <!--<button type="button" class="btn btn-default active" id="btn_tab_pop">Datacenter List</button>-->
- <!--<button type="button" class="btn btn-default" id="btn_tab_container">Container List</button>-->
- <!--<button type="button" class="btn btn-default" id="btn_tab_other">Other</button> -->
- </div>
- <form class="navbar-form navbar-right">
- <div class="input-group">
- <span class="input-group-addon">http://</span>
- <input type="text" class="form-control" placeholder="api-url.or.ip" value="localhost:5001" id="text_api_host" data-provide="typeahead" size="30">
- </div>
- <button type="button" class="btn btn-success" id="btn_connect">Connect <span class="glyphicon glyphicon-play-circle"></span></button>
- <button type="button" class="btn btn-danger disabled" id="btn_disconnect">Disconnect <span class="glyphicon glyphicon-off"></span></button>
- </form>
- </div><!-- /.navbar-collapse -->
- </div><!-- /.container-fluid -->
-</nav>
</div>
<div id="content">
</div>
-<footer class="footer text-center small">(c) 2017 by SONATA Consortium and Paderborn University and IMEC</footer>
+<footer class="footer text-center small">(c) 2017 by SONATA Consortium, Paderborn University and IMEC</footer>
</body>
-var API_HOST = "http://127.0.0.1:5001";
+var API_HOST = ""; // set to a remote url if dashboard is not served by REST API server
var ERROR_ALERT = false;
var TIMESTAMP = 0;
var CONNECTED = false;
var LATENESS_UPDATE_INTERVAL = 50;
-var DATA_UPDATE_INTERVAL = 1000 * 10;
+var DATA_UPDATE_INTERVAL = 1000 * 2;
var LAST_UPDATE_TIMESTAMP_CONTAINER = 0;
var LAST_UPDATE_TIMESTAMP_DATACENTER = 0;
function update_lateness_loop() {
lateness_datacenter= (Date.now() - LAST_UPDATE_TIMESTAMP_DATACENTER) / 1000;
- $("#lbl_lateness_datacenter").text("Lateness: " + Number(lateness_datacenter).toPrecision(3) + "s");
+ $("#lbl_lateness_datacenter").text("Lateness: " + Number(lateness_datacenter).toPrecision(2) + "s");
lateness_container= (Date.now() - LAST_UPDATE_TIMESTAMP_CONTAINER) / 1000;
- $("#lbl_lateness_container").text("Lateness: " + Number(lateness_container).toPrecision(3) + "s");
+ $("#lbl_lateness_container").text("Lateness: " + Number(lateness_container).toPrecision(2) + "s");
// loop while connected
if(CONNECTED)
setTimeout(update_lateness_loop, LATENESS_UPDATE_INTERVAL)
{
ERROR_ALERT = true;
// show message
- alert("ERROR!\nAPI request failed.\n\n Please check the backend connection.", function() {
+ alert("API request failed. Is the emulator running?", function() {
// callback
ERROR_ALERT = false;
});
}
+ CONNECTED = false;
}
}
-function fetch_d3graph()
-{
- // do HTTP request and trigger gui update on success
- var request_url = API_HOST + "/restapi/network/d3jsgraph";
- console.debug("fetching from: " + request_url);
- //$.getJSON(request_url, update_graph);
-}
-
function fetch_loop()
{
// only fetch if we are connected
{
console.info("connect()");
// get host address
- API_HOST = "http://" + $("#text_api_host").val();
+ //API_HOST = "http://" + $("#text_api_host").val();
console.debug("API address: " + API_HOST);
// reset data
LAST_UPDATE_TIMESTAMP_DATACENTER = Date.now();
update_lateness_loop();
// restart data fetch loop
fetch_loop();
- // gui updates
- $("#btn_disconnect").removeClass("disabled");
- $("#btn_connect").addClass("disabled");
-}
-
-function disconnect()
-{
- console.info("disconnect()");
- CONNECTED = false;
- // gui updates
- $("#btn_connect").removeClass("disabled");
- $("#btn_disconnect").addClass("disabled");
}
$(document).ready(function(){
console.info("document ready");
// setup global connection error handling
- /*
+
$.ajaxSetup({
"error": errorAjaxConnection
});
- // add listeners
- $("#btn_connect").click(connect);
- $("#btn_disconnect").click(disconnect);
- */
- setTimeout(fetch_datacenter, 1000);//fetch_datacenter();
- setTimeout(fetch_container, 2000);//fetch_container();
-
+ // connect
+ connect();
// additional refresh on window focus
$(window).focus(function () {
"""
Remove the link from the Containernet and the networkx graph
"""
+ if link is not None:
+ node1 = link.intf1.node
+ node2 = link.intf2.node
+ assert node1 is not None
+ assert node2 is not None
Containernet.removeLink(self, link=link, node1=node1, node2=node2)
- self.DCNetwork_graph.remove_edge(node2.name, node1.name)
+ # TODO we might decrease the loglevel to debug:
+ try:
+ self.DCNetwork_graph.remove_edge(node2.name, node1.name)
+ except:
+ LOG.warning("%s not found in DCNetwork_graph." % ((node2.name, node1.name)))
+ try:
+ self.DCNetwork_graph.remove_edge(node1.name, node2.name)
+ except:
+ LOG.warning("%s not found in DCNetwork_graph." % ((node1.name, node2.name)))
def addDocker( self, label, **params ):
"""
--- /dev/null
+import logging
+from mininet.log import setLogLevel
+from emuvim.dcemulator.net import DCNetwork
+from emuvim.api.openstack.openstack_api_endpoint import OpenstackApiEndpoint
+from emuvim.api.rest.rest_api_endpoint import RestApiEndpoint
+
+
+logging.basicConfig(level=logging.INFO)
+setLogLevel('info') # set Mininet loglevel
+logging.getLogger('werkzeug').setLevel(logging.DEBUG)
+
+
+class DemoTopology(DCNetwork):
+ """
+ This is a 2x2 PoP topology used for the emulator MANO integration demo.
+ """
+
+ def __init__(self):
+ """
+ Initialize multi PoP emulator network.
+ """
+ super(DemoTopology, self).__init__(
+ monitor=False,
+ enable_learning=True
+ )
+ # define members for later use
+ self.pop1 = None
+ self.pop2 = None
+ self.pop3 = None
+ self.pop4 = None
+ self.sw1 = None
+ self.sw2 = None
+
+ def setup(self):
+ self._create_switches()
+ self._create_pops()
+ self._create_links()
+ self._create_rest_api_endpoints()
+ self._create_openstack_api_endpoints()
+
+ def _create_switches(self):
+ self.sw1 = self.addSwitch("s1")
+ self.sw2 = self.addSwitch("s2")
+
+ def _create_pops(self):
+ # two PoPs for the SONATA SP
+ self.pop1 = self.addDatacenter("sonata-pop1")
+ self.pop2 = self.addDatacenter("sonata-pop2")
+ # two PoPs for the OSM SP
+ self.pop3 = self.addDatacenter("osm-pop1")
+ self.pop4 = self.addDatacenter("osm-pop2")
+
+ def _create_links(self):
+ # SONATA island
+ self.addLink(self.pop1, self.sw1, delay="10ms")
+ self.addLink(self.pop2, self.sw1, delay="10ms")
+ # OSM island
+ self.addLink(self.pop3, self.sw2, delay="10ms")
+ self.addLink(self.pop4, self.sw2, delay="10ms")
+
+ def _create_openstack_api_endpoints(self):
+ # create
+ api1 = OpenstackApiEndpoint("0.0.0.0", 6001)
+ api2 = OpenstackApiEndpoint("0.0.0.0", 6002)
+ api3 = OpenstackApiEndpoint("0.0.0.0", 6003)
+ api4 = OpenstackApiEndpoint("0.0.0.0", 6004)
+ # connect PoPs
+ api1.connect_datacenter(self.pop1)
+ api2.connect_datacenter(self.pop2)
+ api3.connect_datacenter(self.pop3)
+ api4.connect_datacenter(self.pop4)
+ # connect network
+ api1.connect_dc_network(self)
+ api2.connect_dc_network(self)
+ api3.connect_dc_network(self)
+ api4.connect_dc_network(self)
+ # start
+ api1.start()
+ api2.start()
+ api3.start()
+ api4.start()
+
+ def _create_rest_api_endpoints(self):
+ # create
+ apiR = RestApiEndpoint("0.0.0.0", 5001)
+ # connect PoPs
+ apiR.connectDatacenter(self.pop1)
+ apiR.connectDatacenter(self.pop2)
+ apiR.connectDatacenter(self.pop3)
+ apiR.connectDatacenter(self.pop4)
+ # connect network
+ apiR.connectDCNetwork(self)
+ # start
+ apiR.start()
+
+
+def main():
+ t = DemoTopology()
+ t.setup()
+ t.start()
+ t.CLI()
+ t.stop()
+
+
+if __name__ == '__main__':
+ main()
--- /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).
+"""
+
+"""
+Helper module that implements helpers for test implementations.
+"""
+
+import unittest
+import os
+import subprocess
+import docker
+import time
+from emuvim.dcemulator.net import DCNetwork
+from emuvim.api.openstack.openstack_api_endpoint import OpenstackApiEndpoint
+from mininet.clean import cleanup
+from mininet.node import Controller
+
+class ApiBaseOpenStack(unittest.TestCase):
+ """
+ Helper class to do basic test setups.
+ s1 -- s2 -- s3 -- ... -- sN
+ """
+
+ def __init__(self, *args, **kwargs):
+ self.net = None
+ self.api = []
+ self.s = [] # list of switches
+ self.h = [] # list of hosts
+ self.d = [] # list of docker containers
+ self.dc = [] # list of data centers
+ self.docker_cli = None
+ super(ApiBaseOpenStack, self).__init__(*args, **kwargs)
+
+ def createNet(
+ self,
+ nswitches=0, ndatacenter=0, nhosts=0, ndockers=0,
+ autolinkswitches=False, controller=Controller, **kwargs):
+ """
+ Creates a Mininet instance and automatically adds some
+ nodes to it.
+
+ Attention, we should always use Mininet's default controller
+ for our tests. Only use other controllers if you want to test
+ specific controller functionality.
+ """
+ self.net = DCNetwork(controller=controller, **kwargs)
+ for i in range(0, ndatacenter):
+ self.api.append(OpenstackApiEndpoint("0.0.0.0", 15000+i))
+
+ # add some switches
+ # start from s1 because ovs does not like to have dpid = 0
+ # and switch name-number is being used by mininet to set the dpid
+ for i in range(1, nswitches+1):
+ self.s.append(self.net.addSwitch('s%d' % i))
+ # if specified, chain all switches
+ if autolinkswitches:
+ for i in range(0, len(self.s) - 1):
+ self.net.addLink(self.s[i], self.s[i + 1])
+ self.net.addLink(self.s[2], self.s[0]) # link switches s1, s2 and s3
+
+ # add some data centers
+ for i in range(0, ndatacenter):
+ self.dc.append(
+ self.net.addDatacenter(
+ 'dc%d' % i,
+ metadata={"unittest_dc": i}))
+ self.net.addLink(self.dc[0].switch, self.s[0]) # link switches dc0.s1 with s1
+ # connect data centers to the endpoint
+ for i in range(0, ndatacenter):
+ self.api[i].connect_datacenter(self.dc[i])
+ self.api[i].connect_dc_network(self.net)
+ # add some hosts
+ for i in range(0, nhosts):
+ self.h.append(self.net.addHost('h%d' % i))
+ # add some dockers
+ for i in range(0, ndockers):
+ self.d.append(self.net.addDocker('d%d' % i, dimage="ubuntu:trusty"))
+
+ def startApi(self):
+ for i in self.api:
+ i.start(wait_for_port=True)
+
+ def stopApi(self):
+ for i in self.api:
+ i.manage.stop_floating_network()
+ i.stop()
+
+ def startNet(self):
+ self.net.start()
+
+ def stopNet(self):
+ self.net.stop()
+
+ def getDockerCli(self):
+ """
+ Helper to interact with local docker instance.
+ """
+ if self.docker_cli is None:
+ self.docker_cli = docker.Client(
+ base_url='unix://var/run/docker.sock')
+ return self.docker_cli
+
+ def getContainernetContainers(self):
+ """
+ List the containers managed by containernet
+ """
+ return self.getDockerCli().containers(filters={"label": "com.containernet"})
+
+ @staticmethod
+ def setUp():
+ pass
+
+
+ def tearDown(self):
+ time.sleep(2)
+ print('->>>>>>> tear everything down ->>>>>>>>>>>>>>>')
+ self.stopApi() # stop all flask threads
+ self.stopNet() # stop some mininet and containernet stuff
+ cleanup()
+ # make sure that all pending docker containers are killed
+ with open(os.devnull, 'w') as devnull: # kill a possibly running docker process that blocks the open ports
+ subprocess.call("kill $(netstat -npl | grep '15000' | grep -o -e'[0-9]\+/docker' | grep -o -e '[0-9]\+')",
+ stdout=devnull,
+ stderr=devnull,
+ shell=True)
+
+ with open(os.devnull, 'w') as devnull:
+ subprocess.call(
+ "sudo docker rm -f $(sudo docker ps --filter 'label=com.containernet' -a -q)",
+ stdout=devnull,
+ stderr=devnull,
+ shell=True)
+ time.sleep(2)
+
+
+
+
+
--- /dev/null
+---
+heat_template_version: '2015-04-30'
+resources:
+ firewall1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: firewall1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:firewall-2-tcpdump:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:firewall-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:iperf-2-firewall:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: iperf1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:input-2-iperf:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: iperf1:input-2-iperf:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:iperf-2-firewall:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: iperf:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:firewall-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Router
+ properties:
+ name: sonata-demo:firewall-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Router
+ properties:
+ name: sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:mgmt:internal:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router: 20790da5-2dc1-4c7e-b9c3-a8d590517563
+ subnet:
+ get_resource: sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: tcpdump1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:firewall-2-tcpdump:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:firewall-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: tcpdump1:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: tcpdump:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
--- /dev/null
+---
+heat_template_version: '2015-04-30'
+resources:
+ firewall1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: firewall1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:firewall-2-loadbalancer:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:firewall-2-loadbalancer:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall1:iperf-2-firewall:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ floating:tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::FloatingIP
+ properties:
+ floating_network_id: decd89e2-1681-427e-ac24-6e9f1abb1715
+ port_id:
+ get_resource: tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: iperf1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:input-2-iperf:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: iperf1:input-2-iperf:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf1:iperf-2-firewall:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: iperf:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: loadbalancer1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: loadbalancer1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: loadbalancer1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: loadbalancer1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: loadbalancer:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: loadbalancer1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: loadbalancer:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer1:firewall-2-loadbalancer:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:firewall-2-loadbalancer:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: loadbalancer:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer1:loadbalancer-2-tcpdump:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: loadbalancer:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: loadbalancer:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: loadbalancer:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: loadbalancer:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: loadbalancer:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ loadbalancer:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.1.0/29
+ gateway_ip: 192.1.0.1
+ name: loadbalancer:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: loadbalancer:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:firewall-2-loadbalancer:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Router
+ properties:
+ name: sonata-demo:firewall-2-loadbalancer:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Router
+ properties:
+ name: sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Router
+ properties:
+ name: sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:mgmt:internal:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router: 20790da5-2dc1-4c7e-b9c3-a8d590517563
+ subnet:
+ get_resource: sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: tcpdump1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:loadbalancer-2-tcpdump:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump1:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: tcpdump1:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump2:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: tcpdump2:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: tcpdump2:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: tcpdump2:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump2:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump2:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump2:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump2:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump2:loadbalancer-2-tcpdump:2:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump2:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: tcpdump2:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump2:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: tcpdump2:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump2:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump3:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Nova::Server
+ properties:
+ flavor:
+ flavorName: m1.small
+ vcpu: 2
+ ram: 2048
+ storage: 20
+ image: ubuntu:trusty
+ name: tcpdump3:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ networks:
+ - port:
+ get_resource: tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: tcpdump3:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ - port:
+ get_resource: tcpdump3:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump3:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump3:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump3:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Port
+ properties:
+ name: tcpdump3:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump3:loadbalancer-2-tcpdump:3:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::RouterInterface
+ properties:
+ router:
+ get_resource: sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ subnet:
+ get_resource: tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump3:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: tcpdump3:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump3:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: tcpdump3:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump3:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Net
+ properties:
+ name: tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ tcpdump:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest:
+ type: OS::Neutron::Subnet
+ properties:
+ cidr: 192.0.2.0/29
+ gateway_ip: 192.1.0.1
+ name: tcpdump:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ network:
+ get_resource: tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
--- /dev/null
+{
+ "auth": {
+ "tenantName": "demo",
+ "tenantId": "fc394f2ab2df4114bde39905f800dc57",
+ "token": {
+ "id": "cbc36478b0bd8e67e89469c7749d4127"
+ }
+ }
+}
\ No newline at end of file
--- /dev/null
+{
+ "template": {
+ "heat_template_version": "2015-04-30",
+ "resources": {
+ "firewall1": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "firewall1",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "firewall1:cp01:mgmt"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "firewall1:cp02:input"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "firewall1:cp03:output"
+ }
+ }
+ ]
+ }
+ },
+ "firewall1:cp01:mgmt": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "firewall1:cp01:mgmt",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net"
+ }
+ }
+ },
+ "firewall1:cp02:input": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "firewall1:cp02:input",
+ "network": {
+ "get_resource": "firewall:input:net"
+ }
+ }
+ },
+ "firewall1:cp03:output": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "firewall1:cp03:output",
+ "network": {
+ "get_resource": "firewall:output:net"
+ }
+ }
+ },
+ "firewall1:firewall-2-tcpdump:1": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:firewall-2-tcpdump"
+ },
+ "subnet": {
+ "get_resource": "firewall:output:subnet"
+ }
+ }
+ },
+ "firewall1:iperf-2-firewall:1": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:iperf-2-firewall"
+ },
+ "subnet": {
+ "get_resource": "firewall:input:subnet"
+ }
+ }
+ },
+ "firewall:input:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "firewall:input:net"
+ }
+ },
+ "firewall:input:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "firewall:input:subnet",
+ "network": {
+ "get_resource": "firewall:input:net"
+ }
+ }
+ },
+ "firewall:output:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "firewall:output:net"
+ }
+ },
+ "firewall:output:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "firewall:output:subnet",
+ "network": {
+ "get_resource": "firewall:output:net"
+ }
+ }
+ },
+ "floating:firewall1:cp01:mgmt": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "firewall1:cp01:mgmt"
+ }
+ }
+ },
+ "floating:iperf1:cp01:mgmt": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "iperf1:cp01:mgmt"
+ }
+ }
+ },
+ "floating:tcpdump1:cp01:mgmt": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "tcpdump1:cp01:mgmt"
+ }
+ }
+ },
+ "iperf1": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "iperf1",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "iperf1:cp01:mgmt"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "iperf1:cp02:input"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "iperf1:cp03:output"
+ }
+ }
+ ]
+ }
+ },
+ "iperf1:cp01:mgmt": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp01:mgmt",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net"
+ }
+ }
+ },
+ "iperf1:cp02:input": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp02:input",
+ "network": {
+ "get_resource": "iperf:input:net"
+ }
+ }
+ },
+ "iperf1:cp03:output": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp03:output",
+ "network": {
+ "get_resource": "iperf:output:net"
+ }
+ }
+ },
+ "iperf1:input-2-iperf:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf1:input-2-iperf:net"
+ }
+ },
+ "iperf1:input-2-iperf:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf1:input-2-iperf:subnet",
+ "network": {
+ "get_resource": "iperf1:input-2-iperf:net"
+ }
+ }
+ },
+ "iperf1:iperf-2-firewall:1": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:iperf-2-firewall"
+ },
+ "subnet": {
+ "get_resource": "iperf:output:subnet"
+ }
+ }
+ },
+ "iperf:input:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf:input:net"
+ }
+ },
+ "iperf:input:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf:input:subnet",
+ "network": {
+ "get_resource": "iperf:input:net"
+ }
+ }
+ },
+ "iperf:output:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf:output:net"
+ }
+ },
+ "iperf:output:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf:output:subnet",
+ "network": {
+ "get_resource": "iperf:output:net"
+ }
+ }
+ },
+ "sonata-demo:firewall-2-tcpdump": {
+ "type": "OS::Neutron::Router",
+ "properties": {
+ "name": "sonata-demo:firewall-2-tcpdump"
+ }
+ },
+ "sonata-demo:iperf-2-firewall": {
+ "type": "OS::Neutron::Router",
+ "properties": {
+ "name": "sonata-demo:iperf-2-firewall"
+ }
+ },
+ "sonata-demo:mgmt:internal": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": "20790da5-2dc1-4c7e-b9c3-a8d590517563",
+ "subnet": {
+ "get_resource": "sonata-demo:mgmt:subnet"
+ }
+ }
+ },
+ "sonata-demo:mgmt:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "sonata-demo:mgmt:net"
+ }
+ },
+ "sonata-demo:mgmt:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "sonata-demo:mgmt:subnet",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net"
+ }
+ }
+ },
+ "tcpdump1": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "tcpdump1",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp01:mgmt"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp02:input"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp03:output"
+ }
+ }
+ ]
+ }
+ },
+ "tcpdump1:cp01:mgmt": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp01:mgmt",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net"
+ }
+ }
+ },
+ "tcpdump1:cp02:input": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp02:input",
+ "network": {
+ "get_resource": "tcpdump:input:net"
+ }
+ }
+ },
+ "tcpdump1:cp03:output": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp03:output",
+ "network": {
+ "get_resource": "tcpdump:output:net"
+ }
+ }
+ },
+ "tcpdump1:firewall-2-tcpdump:1": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:firewall-2-tcpdump"
+ },
+ "subnet": {
+ "get_resource": "tcpdump:input:subnet"
+ }
+ }
+ },
+ "tcpdump1:tcpdump-2-output:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump1:tcpdump-2-output:net"
+ }
+ },
+ "tcpdump1:tcpdump-2-output:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump1:tcpdump-2-output:subnet",
+ "network": {
+ "get_resource": "tcpdump1:tcpdump-2-output:net"
+ }
+ }
+ },
+ "tcpdump:input:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump:input:net"
+ }
+ },
+ "tcpdump:input:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump:input:subnet",
+ "network": {
+ "get_resource": "tcpdump:input:net"
+ }
+ }
+ },
+ "tcpdump:output:net": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump:output:net"
+ }
+ },
+ "tcpdump:output:subnet": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump:output:subnet",
+ "network": {
+ "get_resource": "tcpdump:output:net"
+ }
+ }
+ }
+ }
+ },
+ "stack_name": "s1"
+
+}
\ No newline at end of file
--- /dev/null
+{
+ "template": {
+ "heat_template_version": "2015-04-30",
+ "resources": {
+ "firewall1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "firewall1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "firewall1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall1:firewall-2-tcpdump:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:firewall-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall1:iperf-2-firewall:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "iperf1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "iperf1:input-2-iperf:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf1:input-2-iperf:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:iperf-2-firewall:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "iperf:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "sonata-demo:firewall-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Router",
+ "properties": {
+ "name": "sonata-demo:firewall-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Router",
+ "properties": {
+ "name": "sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "sonata-demo:mgmt:internal:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": "20790da5-2dc1-4c7e-b9c3-a8d590517563",
+ "subnet": {
+ "get_resource": "sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "tcpdump1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:firewall-2-tcpdump:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:firewall-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "tcpdump1:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump1:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "tcpdump:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ }
+ }
+ },
+ "stack_name": "s1"
+
+}
\ No newline at end of file
--- /dev/null
+{
+ "template": {
+ "heat_template_version": "2015-04-30",
+ "resources": {
+ "firewall1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "firewall1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "firewall1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall1:firewall-2-loadbalancer:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:firewall-2-loadbalancer:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall1:iperf-2-firewall:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "firewall:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "firewall:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "firewall:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "firewall:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "firewall1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "floating:tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::FloatingIP",
+ "properties": {
+ "floating_network_id": "decd89e2-1681-427e-ac24-6e9f1abb1715",
+ "port_id": {
+ "get_resource": "tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "iperf1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "iperf1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "iperf1:input-2-iperf:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf1:input-2-iperf:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf1:input-2-iperf:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf1:iperf-2-firewall:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "iperf:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "iperf:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "iperf:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "loadbalancer1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "loadbalancer1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "loadbalancer1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "loadbalancer1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "loadbalancer1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "loadbalancer1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "loadbalancer1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "loadbalancer:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "loadbalancer1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "loadbalancer1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "loadbalancer:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "loadbalancer1:firewall-2-loadbalancer:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:firewall-2-loadbalancer:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "loadbalancer:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "loadbalancer1:loadbalancer-2-tcpdump:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "loadbalancer:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "loadbalancer:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "loadbalancer:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "loadbalancer:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "loadbalancer:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "loadbalancer:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "loadbalancer:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "loadbalancer:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "loadbalancer:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.1.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "loadbalancer:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "loadbalancer:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "sonata-demo:firewall-2-loadbalancer:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Router",
+ "properties": {
+ "name": "sonata-demo:firewall-2-loadbalancer:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Router",
+ "properties": {
+ "name": "sonata-demo:iperf-2-firewall:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Router",
+ "properties": {
+ "name": "sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "sonata-demo:mgmt:internal:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": "20790da5-2dc1-4c7e-b9c3-a8d590517563",
+ "subnet": {
+ "get_resource": "sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/28",
+ "gateway_ip": "192.1.0.1",
+ "name": "sonata-demo:mgmt:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "tcpdump1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump1:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:loadbalancer-2-tcpdump:1:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "tcpdump1:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump1:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump1:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump2:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "tcpdump2:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump2:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump2:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump2:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump2:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump2:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump2:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump2:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump2:loadbalancer-2-tcpdump:2:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump2:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump2:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "tcpdump2:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump2:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump2:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump3:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Nova::Server",
+ "properties": {
+ "flavor": {
+ "flavorName": "m1.small",
+ "vcpu": 2,
+ "ram": 2048,
+ "storage": 20
+ },
+ "image": "ubuntu:trusty",
+ "name": "tcpdump3:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "networks": [
+ {
+ "port": {
+ "get_resource": "tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump3:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ {
+ "port": {
+ "get_resource": "tcpdump3:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ ]
+ }
+ },
+ "tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump3:cp01:mgmt:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "sonata-demo:mgmt:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump3:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump3:cp02:input:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump3:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Port",
+ "properties": {
+ "name": "tcpdump3:cp03:output:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump3:loadbalancer-2-tcpdump:3:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::RouterInterface",
+ "properties": {
+ "router": {
+ "get_resource": "sonata-demo:loadbalancer-2-tcpdump:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ },
+ "subnet": {
+ "get_resource": "tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump3:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump3:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "tcpdump3:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump3:tcpdump-2-output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump3:tcpdump-2-output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump:input:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ },
+ "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Net",
+ "properties": {
+ "name": "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ },
+ "tcpdump:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest": {
+ "type": "OS::Neutron::Subnet",
+ "properties": {
+ "cidr": "192.0.2.0/29",
+ "gateway_ip": "192.1.0.1",
+ "name": "tcpdump:output:subnet:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest",
+ "network": {
+ "get_resource": "tcpdump:output:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest"
+ }
+ }
+ }
+ }
+ },
+ "stack_name": "s1"
+}
\ No newline at end of file
--- /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).
+"""
+
+"""
+Test suite to automatically test emulator REST API endpoints.
+"""
+
+import os
+import unittest
+import requests
+import simplejson as json
+import time
+
+from emuvim.test.api_base_openstack import ApiBaseOpenStack
+
+
+class testRestApi(ApiBaseOpenStack):
+ """
+ Tests to check the REST API endpoints of the emulator.
+ """
+
+ def setUp(self):
+ # create network
+ self.createNet(nswitches=3, ndatacenter=2, nhosts=2, ndockers=0, autolinkswitches=True)
+
+ # setup links
+ self.net.addLink(self.dc[0], self.h[0])
+ self.net.addLink(self.h[1], self.dc[1])
+ self.net.addLink(self.dc[0], self.dc[1])
+
+ # start api
+ self.startApi()
+
+ # start Mininet network
+ self.startNet()
+
+ @unittest.skip("temporarily disabled")
+ def testChainingDummy(self):
+ print('->>>>>>> test Chaining Class->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ print(" ")
+
+ headers = {'Content-type': 'application/json'}
+ test_heatapi_template_create_stack = open(os.path.join(os.path.dirname(__file__), "test_heatapi_template_chaining.json")).read()
+ url = "http://0.0.0.0:8004/v1/tenantabc123/stacks"
+ requests.post(url, data=json.dumps(json.loads(test_heatapi_template_create_stack)), headers=headers)
+
+
+ print('->>>>>>> test Chaining Versions ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/"
+ listapiversionstackresponse = requests.get(url, headers=headers)
+ self.assertEqual(listapiversionstackresponse.status_code, 200)
+ self.assertEqual(json.loads(listapiversionstackresponse.content)["versions"][0]["id"], "v1")
+ print(" ")
+
+ print('->>>>>>> test Chaining List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/list"
+ chainlistresponse = requests.get(url, headers=headers)
+ self.assertEqual(chainlistresponse.status_code, 200)
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"], [])
+ print(" ")
+
+ print('->>>>>>> test Loadbalancing List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/list"
+ lblistresponse = requests.get(url, headers=headers)
+ self.assertEqual(lblistresponse.status_code, 200)
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"], [])
+ print(" ")
+
+ testchain = "dc0_s1_firewall1/fire-out-0/dc0_s1_iperf1/iper-in-0"
+ print('->>>>>>> test Chain VNF Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" %(testchain)
+ chainvnfresponse = requests.put(url)
+ self.assertEqual(chainvnfresponse.status_code, 200)
+ self.assertGreaterEqual(json.loads(chainvnfresponse.content)["cookie"], 0)
+ print(" ")
+
+ print('->>>>>>> test Chaining List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/list"
+ chainlistresponse = requests.get(url, headers=headers)
+ self.assertEqual(chainlistresponse.status_code, 200)
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"][0]["dst_vnf"], "dc0_s1_firewall1")
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"][0]["dst_intf"], "fire-out-0")
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"][0]["src_vnf"], "dc0_s1_iperf1")
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"][0]["src_intf"], "iper-in-0")
+ print(" ")
+
+ print('->>>>>>> test Chain VNF Delete Chain ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ deletechainvnfresponse = requests.delete(url)
+ self.assertEqual(deletechainvnfresponse.status_code, 200)
+ self.assertEqual(deletechainvnfresponse.content, "true")
+ print(" ")
+
+ print('->>>>>>> test Chaining List If Empty Again ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/list"
+ chainlistresponse = requests.get(url, headers=headers)
+ self.assertEqual(chainlistresponse.status_code, 200)
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"], [])
+ print(" ")
+
+ testchain = "dc0_s1_firewall1/fire-out-0/dc0_s1_iperf1/iper-in-0"
+ print('->>>>>>> test Stack Chain VNF Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ stackchainvnfresponse = requests.post(url, data=json.dumps(json.loads('{"path":["dc1.s1", "s1","s2","s3","s1","dc1.s1"]}')), headers=headers)
+ self.assertEqual(stackchainvnfresponse.status_code, 200)
+ print (stackchainvnfresponse.content)
+ self.assertGreaterEqual(json.loads(stackchainvnfresponse.content)["cookie"], 0)
+ print(" ")
+
+
+ print('->>>>>>> test Stack Chaining List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/list"
+ chainlistresponse = requests.get(url, headers=headers)
+ self.assertEqual(chainlistresponse.status_code, 200)
+ print (chainlistresponse.content)
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"][0]["dst_vnf"], "dc0_s1_firewall1")
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"][0]["dst_intf"], "fire-out-0")
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"][0]["src_vnf"], "dc0_s1_iperf1")
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"][0]["src_intf"], "iper-in-0")
+ self.assertItemsEqual(json.loads(chainlistresponse.content)["chains"][0]["path"],['dc1.s1', 's1', 's2', 's3', 's1', 'dc1.s1'])
+ print(" ")
+
+ print('->>>>>>> test Chain VNF Delete Chain ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ deletechainvnfresponse = requests.delete(url)
+ self.assertEqual(deletechainvnfresponse.status_code, 200)
+ self.assertEqual(deletechainvnfresponse.content, "true")
+ print(" ")
+
+ print('->>>>>>> test Chaining List If Empty Again ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/list"
+ chainlistresponse = requests.get(url, headers=headers)
+ self.assertEqual(chainlistresponse.status_code, 200)
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"], [])
+ print(" ")
+
+
+ testchain = "dc0_s1_firewall1/non-existing-interface/dc0_s1_iperf1/iper-in-0"
+ print('->>>>>>> test Chain VNF Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ chainvnfresponse = requests.put(url)
+ self.assertEqual(chainvnfresponse.status_code, 501)
+ print(" ")
+
+ testchain = "dc0_s1_firewall1/fire-out-0/dc0_s1_iperf1/non-existing-interface"
+ print('->>>>>>> test Chain VNF Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ chainvnfresponse = requests.put(url)
+ self.assertEqual(chainvnfresponse.status_code, 501)
+ print(" ")
+
+ testchain = "dc0_s1_firewall1/non-existing-interface/dc0_s1_iperf1/iper-in-0"
+ print('->>>>>>> test Chain VNF Delete Chain ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ deletechainvnfresponse = requests.delete(url)
+ self.assertEqual(deletechainvnfresponse.status_code, 501)
+ print(" ")
+
+ testchain = "dc0_s1_firewall1/fire-out-0/dc0_s1_iperf1/non-existing-interface"
+ print('->>>>>>> test Chain VNF Delete Chain ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ deletechainvnfresponse = requests.delete(url)
+ self.assertEqual(deletechainvnfresponse.status_code, 501)
+ print(" ")
+
+ testchain = "non-existent-dc/s1/firewall1/firewall1:cp03:output/dc0/s1/iperf1/iperf1:cp02:input"
+ print('->>>>>>> test Chain VNF Non-Existing DC ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ chainvnfresponse = requests.put(url)
+ self.assertEqual(chainvnfresponse.status_code, 500)
+ print(" ")
+
+
+ testchain = "dc0/s1/firewall1/non-existent:cp03:output/dc0/s1/iperf1/iperf1:cp02:input"
+ print('->>>>>>> test Chain VNF Non-Existing Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ chainvnfresponse = requests.put(url)
+ self.assertEqual(chainvnfresponse.status_code, 500)
+ print(" ")
+
+ testchain = "dc0/s1/firewall1/firewall1:cp03:output/dc0/s1/iperf1/non-existent:cp02:input"
+ print('->>>>>>> test Chain VNF Non-Existing Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ chainvnfresponse = requests.put(url)
+ self.assertEqual(chainvnfresponse.status_code, 500)
+ print(" ")
+
+ testchain = "dc0/s1/firewall1/firewall1:cp03:output/dc0/s1/iperf1/iperf1:cp02:input"
+ print('->>>>>>> test Chain VNF Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ chainvnfresponse = requests.put(url)
+ print (chainvnfresponse.content)
+ self.assertEqual(chainvnfresponse.status_code, 200)
+ self.assertGreaterEqual(json.loads(chainvnfresponse.content)["cookie"], 0)
+ print(" ")
+
+ print('->>>>>>> test Chain VNF Delete Chain ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ deletechainvnfresponse = requests.delete(url)
+ self.assertEqual(deletechainvnfresponse.status_code, 200)
+ self.assertEqual(deletechainvnfresponse.content, "true")
+ print(" ")
+
+ print('->>>>>>> test Chaining List If Empty Again ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/list"
+ chainlistresponse = requests.get(url, headers=headers)
+ self.assertEqual(chainlistresponse.status_code, 200)
+ self.assertEqual(json.loads(chainlistresponse.content)["chains"], [])
+ print(" ")
+
+ testchain = "dc0/s1/firewall1/firewall1:cp03:output/dc0/s1/iperf1/iperf1:cp02:input"
+ print('->>>>>>> test Stack Chain VNF Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ stackchainvnfresponse = requests.post(url, data=json.dumps(
+ json.loads('{"path":["dc1.s1", "s1","s2","s3","s1","dc1.s1"]}')), headers=headers)
+ self.assertEqual(stackchainvnfresponse.status_code, 200)
+ print (stackchainvnfresponse.content)
+ self.assertGreaterEqual(json.loads(stackchainvnfresponse.content)["cookie"], 0)
+ print(" ")
+
+ testchain = "dc0/s1/firewall1/firewall1:cp03:output/dc0/s1/iperf1/iperf1:cp02:input"
+ print('->>>>>>> test Stack Chain VNF Interfaces ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/chain/%s" % (testchain)
+ stackchainvnfresponse = requests.delete(url, headers=headers)
+ self.assertEqual(stackchainvnfresponse.status_code, 200)
+ print(" ")
+
+
+ print('->>>>>>> test Loadbalancing ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/dc0/s1/firewall1/firewall1:cp03:output"
+ lblistresponse = requests.post(url, data=json.dumps(
+ {"dst_vnf_interfaces":[{"pop":"dc0","stack":"s1","server":"iperf1","port":"iperf1:cp02:input"}]})
+ , headers=headers)
+ print (lblistresponse.content)
+ self.assertEqual(lblistresponse.status_code, 200)
+ self.assertIn("dc0_s1_firewall1:fire-out-0", lblistresponse.content)
+ print(" ")
+
+ print('->>>>>>> test Loadbalancing List ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/list"
+ lblistresponse = requests.get(url, headers=headers)
+ self.assertEqual(lblistresponse.status_code, 200)
+ print (lblistresponse.content )
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["paths"][0]["dst_vnf"], "dc0_s1_iperf1")
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["paths"][0]["dst_intf"], "iper-in-0")
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["src_vnf"], "dc0_s1_firewall1")
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["src_intf"],"fire-out-0")
+ print(" ")
+
+ print('->>>>>>> test delete Loadbalancing ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/dc0/s1/firewall1/firewall1:cp03:output"
+ lbdeleteresponse = requests.delete(url, headers=headers)
+ print (lbdeleteresponse.content)
+ self.assertEqual(lbdeleteresponse.status_code, 200)
+ print(" ")
+
+ print('->>>>>>> testFloatingLoadbalancer ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/dc0/floating/bla/blubb"
+ lblistresponse = requests.post(url, data=json.dumps(
+ {"dst_vnf_interfaces":[{"pop":"dc0","stack":"s1","server":"iperf1","port":"iperf1:cp02:input"}]})
+ , headers=headers)
+ print (lblistresponse.content)
+ self.assertEqual(lblistresponse.status_code, 200)
+ resp = json.loads(lblistresponse.content)
+ self.assertIsNotNone(resp.get('cookie'))
+ self.assertIsNotNone(resp.get('floating_ip'))
+ cookie = resp.get('cookie')
+ print(" ")
+
+ print('->>>>>>> testDeleteFloatingLoadbalancer ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/dc0/floating/%s/blubb" % cookie
+ lblistresponse = requests.delete(url, headers=headers)
+ print (lblistresponse.content)
+ self.assertEqual(lblistresponse.status_code, 200)
+ print(" ")
+
+ print('->>>>>>> testLoadbalancingCustomPath ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/dc0_s1_firewall1/fire-out-0"
+ lblistresponse = requests.post(url, data=json.dumps(
+ {"dst_vnf_interfaces":{"dc0_s1_iperf1":"iper-in-0"},
+ "path": {"dc0_s1_iperf1": {"iper-in-0": ["dc1.s1", "s1","s2","s3","s1","dc1.s1"]}}}), headers=headers)
+ print (lblistresponse.content)
+ self.assertEqual(lblistresponse.status_code, 200)
+ self.assertIn("dc0_s1_firewall1:fire-out-0", lblistresponse.content)
+ print(" ")
+
+ print('->>>>>>> testLoadbalancingListCustomPath ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/list"
+ lblistresponse = requests.get(url, headers=headers)
+ self.assertEqual(lblistresponse.status_code, 200)
+ print (lblistresponse.content )
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["paths"][0]["dst_vnf"], "dc0_s1_iperf1")
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["paths"][0]["dst_intf"], "iper-in-0")
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["paths"][0]["path"],
+ ["dc1.s1", "s1","s2","s3","s1","dc1.s1"] )
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["src_vnf"], "dc0_s1_firewall1")
+ self.assertEqual(json.loads(lblistresponse.content)["loadbalancers"][0]["src_intf"],"fire-out-0")
+ print(" ")
+
+
+ print('->>>>>>> test Delete Loadbalancing ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/lb/dc0_s1_firewall1/fire-out-0"
+ lblistresponse = requests.delete(url, headers=headers)
+ self.assertEqual(lblistresponse.status_code, 200)
+ print(" ")
+
+ print('->>>>>>> test Query Topology ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:4000/v1/topo"
+ topolistresponse = requests.get(url, headers=headers)
+ print(topolistresponse.content)
+ self.assertEqual(topolistresponse.status_code, 200)
+ print(" ")
+
+
+ def testNovaDummy(self):
+ print('->>>>>>> test Nova Dummy Class->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ print(" ")
+
+ headers = {'Content-type': 'application/json'}
+ test_heatapi_template_create_stack = open(os.path.join(os.path.dirname(__file__), "test_heatapi_template_create_stack.json")).read()
+ url = "http://0.0.0.0:18004/v1/tenantabc123/stacks"
+ requests.post(url, data=json.dumps(json.loads(test_heatapi_template_create_stack)),
+ headers=headers)
+
+ print('->>>>>>> test Nova List Versions ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/"
+ listapiversionnovaresponse = requests.get(url, headers=headers)
+ self.assertEqual(listapiversionnovaresponse.status_code, 200)
+ self.assertEqual(json.loads(listapiversionnovaresponse.content)["versions"][0]["id"], "v2.1")
+ self.assertEqual(json.loads(listapiversionnovaresponse.content)["versions"][0]["status"], "CURRENT")
+ self.assertEqual(json.loads(listapiversionnovaresponse.content)["versions"][0]["version"], "2.38")
+ self.assertEqual(json.loads(listapiversionnovaresponse.content)["versions"][0]["min_version"], "2.1")
+ self.assertEqual(json.loads(listapiversionnovaresponse.content)["versions"][0]["updated"], "2013-07-23T11:33:21Z")
+ print(" ")
+
+ print('->>>>>>> test Nova Version Show ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla"
+ listapiversion21novaresponse = requests.get(url, headers=headers)
+ self.assertEqual(listapiversion21novaresponse.status_code, 200)
+ self.assertEqual(json.loads(listapiversion21novaresponse.content)["version"]["id"], "v2.1")
+ self.assertEqual(json.loads(listapiversion21novaresponse.content)["version"]["status"], "CURRENT")
+ self.assertEqual(json.loads(listapiversion21novaresponse.content)["version"]["version"], "2.38")
+ self.assertEqual(json.loads(listapiversion21novaresponse.content)["version"]["min_version"], "2.1")
+ self.assertEqual(json.loads(listapiversion21novaresponse.content)["version"]["updated"], "2013-07-23T11:33:21Z")
+ print(" ")
+
+ print('->>>>>>> test Nova Version List Server APIs ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers"
+ listserverapisnovaresponse = requests.get(url, headers=headers)
+ self.assertEqual(listserverapisnovaresponse.status_code, 200)
+ self.assertNotEqual(json.loads(listserverapisnovaresponse.content)["servers"][0]["name"], "")
+ print(" ")
+
+ print('->>>>>>> test Nova Delete Server APIs ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/%s" % (json.loads(listserverapisnovaresponse.content)["servers"][0]["id"])
+ deleteserverapisnovaresponse = requests.delete(url, headers=headers)
+ self.assertEqual(deleteserverapisnovaresponse.status_code, 204)
+ print(" ")
+
+ print('->>>>>>> test Nova Delete Non-Existing Server APIs ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/non-existing-ix"
+ deleteserverapisnovaresponse = requests.delete(url, headers=headers)
+ self.assertEqual(deleteserverapisnovaresponse.status_code, 404)
+ print(" ")
+
+
+ print('->>>>>>> testNovaVersionListServerAPIs_withPortInformation ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/andPorts"
+ listserverapisnovaresponse = requests.get(url, headers=headers)
+ self.assertEqual(listserverapisnovaresponse.status_code, 200)
+ self.assertNotEqual(json.loads(listserverapisnovaresponse.content)["servers"][0]["name"], "")
+ print(" ")
+
+ print('->>>>>>> test Nova List Flavors ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/flavors"
+ listflavorsresponse = requests.get(url, headers=headers)
+ self.assertEqual(listflavorsresponse.status_code, 200)
+ self.assertIn(json.loads(listflavorsresponse.content)["flavors"][0]["name"], ["m1.nano", "m1.tiny", "m1.micro", "m1.small"])
+ self.assertIn(json.loads(listflavorsresponse.content)["flavors"][1]["name"], ["m1.nano", "m1.tiny", "m1.micro", "m1.small"])
+ self.assertIn(json.loads(listflavorsresponse.content)["flavors"][2]["name"], ["m1.nano", "m1.tiny", "m1.micro", "m1.small"])
+ print(" ")
+
+ print('->>>>>>> testNovaAddFlavors ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/flavors"
+ addflavorsresponse = requests.post(url,
+ data='{"flavor":{"name": "testFlavor", "vcpus": "test_vcpus", "ram": 1024, "disk": 10}}',
+ headers=headers)
+ self.assertEqual(addflavorsresponse.status_code, 200)
+ self.assertIsNotNone(json.loads(addflavorsresponse.content)["flavor"]["id"])
+ self.assertIsNotNone(json.loads(addflavorsresponse.content)["flavor"]["links"][0]['href'])
+ print(" ")
+
+ print('->>>>>>> test Nova List Flavors Detail ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/flavors/detail"
+ listflavorsdetailresponse = requests.get(url, headers=headers)
+ self.assertEqual(listflavorsdetailresponse.status_code, 200)
+ self.assertIn(json.loads(listflavorsdetailresponse.content)["flavors"][0]["name"],["m1.nano", "m1.tiny", "m1.micro", "m1.small"])
+ self.assertIn(json.loads(listflavorsdetailresponse.content)["flavors"][1]["name"],["m1.nano", "m1.tiny", "m1.micro", "m1.small"])
+ self.assertIn(json.loads(listflavorsdetailresponse.content)["flavors"][2]["name"],["m1.nano", "m1.tiny", "m1.micro", "m1.small"])
+ print(" ")
+
+ print('->>>>>>> testNovaAddFlavors ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/flavors/detail"
+ addflavorsresponse = requests.post(url,
+ data='{"flavor":{"name": "testFlavor", "vcpus": "test_vcpus", "ram": 1024, "disk": 10}}',
+ headers=headers)
+ self.assertEqual(addflavorsresponse.status_code, 200)
+ self.assertIsNotNone(json.loads(addflavorsresponse.content)["flavor"]["id"])
+ self.assertIsNotNone(json.loads(addflavorsresponse.content)["flavor"]["links"][0]['href'])
+ print(" ")
+
+ print('->>>>>>> test Nova List Flavor By Id ->>>>>>>>>>>>>>>')
+
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/flavors/%s" % (json.loads(listflavorsdetailresponse.content)["flavors"][0]["name"])
+ listflavorsbyidresponse = requests.get(url, headers=headers)
+ self.assertEqual(listflavorsbyidresponse.status_code, 200)
+ self.assertEqual(json.loads(listflavorsbyidresponse.content)["flavor"]["id"], json.loads(listflavorsdetailresponse.content)["flavors"][0]["id"])
+ print(" ")
+
+ print('->>>>>>> test Nova List Images ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/images"
+ listimagesresponse = requests.get(url, headers=headers)
+ self.assertEqual(listimagesresponse.status_code, 200)
+ print(listimagesresponse.content)
+ # deactivated: highly depends on the environment in which the tests are executed. one cannot make such an assumption.
+ #self.assertIn(json.loads(listimagesresponse.content)["images"][0]["name"],["google/cadvisor:latest", "ubuntu:trusty", "prom/pushgateway:latest"])
+ #self.assertIn(json.loads(listimagesresponse.content)["images"][1]["name"],["google/cadvisor:latest", "ubuntu:trusty", "prom/pushgateway:latest"])
+ #self.assertIn(json.loads(listimagesresponse.content)["images"][2]["name"],["google/cadvisor:latest", "ubuntu:trusty", "prom/pushgateway:latest"])
+ print(" ")
+
+ print('->>>>>>> test Nova List Images Details ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/images/detail"
+ listimagesdetailsresponse = requests.get(url, headers=headers)
+ self.assertEqual(listimagesdetailsresponse.status_code, 200)
+ # deactivated: highly depends on the environment in which the tests are executed. one cannot make such an assumption.
+ #self.assertIn(json.loads(listimagesdetailsresponse.content)["images"][0]["name"],["google/cadvisor:latest", "ubuntu:trusty", "prom/pushgateway:latest"])
+ #self.assertIn(json.loads(listimagesdetailsresponse.content)["images"][1]["name"],["google/cadvisor:latest", "ubuntu:trusty", "prom/pushgateway:latest"])
+ #self.assertIn(json.loads(listimagesdetailsresponse.content)["images"][2]["name"],["google/cadvisor:latest", "ubuntu:trusty", "prom/pushgateway:latest"])
+ self.assertEqual(json.loads(listimagesdetailsresponse.content)["images"][0]["metadata"]["architecture"],"x86_64")
+ print(" ")
+
+ print('->>>>>>> test Nova List Image By Id ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/images/%s" % (json.loads(listimagesdetailsresponse.content)["images"][0]["id"])
+ listimagebyidresponse = requests.get(url, headers=headers)
+ self.assertEqual(listimagebyidresponse.status_code, 200)
+ self.assertEqual(json.loads(listimagebyidresponse.content)["image"]["id"],json.loads(listimagesdetailsresponse.content)["images"][0]["id"])
+ print(" ")
+
+ print('->>>>>>> test Nova List Image By Non-Existend Id ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/images/non_existing_id"
+ listimagebynonexistingidresponse = requests.get(url, headers=headers)
+ self.assertEqual(listimagebynonexistingidresponse.status_code, 404)
+ print(" ")
+
+ #find ubuntu id
+ for image in json.loads(listimagesresponse.content)["images"]:
+ if image["name"] == "ubuntu:trusty":
+ ubuntu_image_id = image["id"]
+
+
+
+ print('->>>>>>> test Nova Create Server Instance ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers"
+ data = '{"server": {"name": "X", "flavorRef": "%s", "imageRef":"%s"}}' % (json.loads(listflavorsresponse.content)["flavors"][0]["id"], ubuntu_image_id)
+ createserverinstance = requests.post(url, data=data, headers=headers)
+ self.assertEqual(createserverinstance.status_code, 200)
+ self.assertEqual(json.loads(createserverinstance.content)["server"]["image"]["id"], ubuntu_image_id)
+ print(" ")
+
+ print('->>>>>>> test Nova Create Server Instance With Already Existing Name ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers"
+ data = '{"server": {"name": "X", "flavorRef": "%s", "imageRef":"%s"}}' % (json.loads(listflavorsresponse.content)["flavors"][0]["id"], ubuntu_image_id)
+ createserverinstance = requests.post(url, data=data, headers=headers)
+ self.assertEqual(createserverinstance.status_code, 409)
+ print(" ")
+
+ print('->>>>>>> test Nova Version List Server APIs Detailed ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/detail"
+ listserverapisdetailedresponse = requests.get(url, headers=headers)
+ self.assertEqual(listserverapisdetailedresponse.status_code, 200)
+ self.assertEqual(json.loads(listserverapisdetailedresponse.content)["servers"][0]["status"], "ACTIVE")
+ print(" ")
+
+ print('->>>>>>> test Nova Show Server Details ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/%s" % (json.loads(listserverapisdetailedresponse.content)["servers"][0]["id"])
+ listserverdetailsresponse = requests.get(url, headers=headers)
+ self.assertEqual(listserverdetailsresponse.status_code, 200)
+ self.assertEqual(json.loads(listserverdetailsresponse.content)["server"]["flavor"]["links"][0]["rel"], "bookmark")
+ print(" ")
+
+ print('->>>>>>> test Nova Show Non-Existing Server Details ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/non_existing_server_id"
+ listnonexistingserverdetailsresponse = requests.get(url, headers=headers)
+ self.assertEqual(listnonexistingserverdetailsresponse.status_code, 404)
+ print(" ")
+
+
+
+ def testNeutronDummy(self):
+ print('->>>>>>> test Neutron Dummy Class->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ print(" ")
+
+ headers = {'Content-type': 'application/json'}
+ test_heatapi_template_create_stack = open(os.path.join(os.path.dirname(__file__), "test_heatapi_template_create_stack.json")).read()
+ url = "http://0.0.0.0:18004/v1/tenantabc123/stacks"
+ requests.post(url, data=json.dumps(json.loads(test_heatapi_template_create_stack)), headers=headers)
+ # test_heatapi_keystone_get_token = open("test_heatapi_keystone_get_token.json").read()
+
+ print('->>>>>>> test Neutron List Versions ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/"
+ listapiversionstackresponse = requests.get(url, headers=headers)
+ self.assertEqual(listapiversionstackresponse.status_code, 200)
+ self.assertEqual(json.loads(listapiversionstackresponse.content)["versions"][0]["id"], "v2.0")
+ print(" ")
+
+ print('->>>>>>> test Neutron Show API v2.0 ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0"
+ listapiversionv20response = requests.get(url, headers=headers)
+ self.assertEqual(listapiversionv20response.status_code, 200)
+ self.assertEqual(json.loads(listapiversionv20response.content)["resources"][0]["name"], "subnet")
+ self.assertEqual(json.loads(listapiversionv20response.content)["resources"][1]["name"], "network")
+ self.assertEqual(json.loads(listapiversionv20response.content)["resources"][2]["name"], "ports")
+ print(" ")
+
+ print('->>>>>>> test Neutron List Networks ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks"
+ listnetworksesponse1 = requests.get(url, headers=headers)
+ self.assertEqual(listnetworksesponse1.status_code, 200)
+ self.assertEqual(json.loads(listnetworksesponse1.content)["networks"][0]["status"], "ACTIVE")
+ listNetworksId = json.loads(listnetworksesponse1.content)["networks"][0]["id"]
+ listNetworksName = json.loads(listnetworksesponse1.content)["networks"][0]["name"]
+ listNetworksId2 = json.loads(listnetworksesponse1.content)["networks"][1]["id"]
+ print(" ")
+
+ print('->>>>>>> test Neutron List Non-Existing Networks ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks?name=non_existent_network_name"
+ listnetworksesponse2 = requests.get(url,headers=headers)
+ self.assertEqual(listnetworksesponse2.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Neutron List Networks By Name ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks?name=" + listNetworksName #tcpdump-vnf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ listnetworksesponse3 = requests.get(url, headers=headers)
+ self.assertEqual(listnetworksesponse3.status_code, 200)
+ self.assertEqual(json.loads(listnetworksesponse3.content)["networks"][0]["name"], listNetworksName)
+ print(" ")
+
+ print('->>>>>>> test Neutron List Networks By Id ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks?id=" + listNetworksId # tcpdump-vnf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ listnetworksesponse4 = requests.get(url, headers=headers)
+ self.assertEqual(listnetworksesponse4.status_code, 200)
+ self.assertEqual(json.loads(listnetworksesponse4.content)["networks"][0]["id"], listNetworksId)
+ print(" ")
+
+ print('->>>>>>> test Neutron List Networks By Multiple Ids ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks?id=" + listNetworksId + "&id="+ listNetworksId2 # tcpdump-vnf:input:net:9df6a98f-9e11-4cb7-b3c0-InAdUnitTest
+ listnetworksesponse5 = requests.get(url, headers=headers)
+ self.assertEqual(listnetworksesponse5.status_code, 200)
+ self.assertEqual(json.loads(listnetworksesponse5.content)["networks"][0]["id"], listNetworksId)
+ self.assertEqual(json.loads(listnetworksesponse5.content)["networks"][1]["id"], listNetworksId2)
+ print(" ")
+
+ print('->>>>>>> test Neutron Show Network ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks/"+listNetworksId
+ shownetworksesponse = requests.get(url, headers=headers)
+ self.assertEqual(shownetworksesponse.status_code, 200)
+ self.assertEqual(json.loads(shownetworksesponse.content)["network"]["status"], "ACTIVE")
+ print(" ")
+
+ print('->>>>>>> test Neutron Show Network Non-ExistendNetwork ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks/non_existent_network_id"
+ shownetworksesponse2 = requests.get(url, headers=headers)
+ self.assertEqual(shownetworksesponse2.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Neutron Create Network ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks"
+ createnetworkresponse = requests.post(url, data='{"network": {"name": "sample_network","admin_state_up": true}}', headers=headers)
+ self.assertEqual(createnetworkresponse.status_code, 201)
+ self.assertEqual(json.loads(createnetworkresponse.content)["network"]["status"], "ACTIVE")
+ print(" ")
+
+ print('->>>>>>> test Neutron Create Network With Existing Name ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks"
+ createnetworkresponsefailure = requests.post(url,data='{"network": {"name": "sample_network","admin_state_up": true}}',headers=headers)
+ self.assertEqual(createnetworkresponsefailure.status_code, 400)
+ print(" ")
+
+ print('->>>>>>> test Neutron Update Network ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks/%s" % (json.loads(createnetworkresponse.content)["network"]["id"])
+ updatenetworkresponse = requests.put(url, data='{"network": {"status": "ACTIVE", "admin_state_up":true, "tenant_id":"abcd123", "name": "sample_network_new_name", "shared":false}}' , headers=headers)
+ self.assertEqual(updatenetworkresponse.status_code, 200)
+ self.assertEqual(json.loads(updatenetworkresponse.content)["network"]["name"], "sample_network_new_name")
+ self.assertEqual(json.loads(updatenetworkresponse.content)["network"]["tenant_id"], "abcd123")
+ print(" ")
+
+ print('->>>>>>> test Neutron Update Non-Existing Network ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks/non-existing-name123"
+ updatenetworkresponse = requests.put(url, data='{"network": {"name": "sample_network_new_name"}}', headers=headers)
+ self.assertEqual(updatenetworkresponse.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Neutron List Subnets ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets"
+ listsubnetsresponse = requests.get(url, headers=headers)
+ listSubnetName = json.loads(listsubnetsresponse.content)["subnets"][0]["name"]
+ listSubnetId = json.loads(listsubnetsresponse.content)["subnets"][0]["id"]
+ listSubnetId2 = json.loads(listsubnetsresponse.content)["subnets"][1]["id"]
+ self.assertEqual(listsubnetsresponse.status_code, 200)
+ self.assertNotIn('None', listSubnetName)
+ print(" ")
+
+ print('->>>>>>> test Neutron List Subnets By Name ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets?name="+listSubnetName
+ listsubnetByNameresponse = requests.get(url, headers=headers)
+ self.assertEqual(listsubnetByNameresponse.status_code, 200)
+ self.assertNotIn('None', json.loads(listsubnetByNameresponse.content)["subnets"][0]["name"])
+ print(" ")
+
+ print('->>>>>>> test Neutron List Subnets By Id ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets?id=" + listSubnetId
+ listsubnetsbyidresponse = requests.get(url, headers=headers)
+ self.assertEqual(listsubnetsbyidresponse.status_code, 200)
+ self.assertNotIn("None", json.loads(listsubnetsbyidresponse.content)["subnets"][0]["name"])
+ print(" ")
+
+ print('->>>>>>> test Neutron List Subnets By Multiple Id ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets?id=" + listSubnetId +"&id="+listSubnetId2
+ listsubnetsbymultipleidsresponse = requests.get(url, headers=headers)
+ self.assertEqual(listsubnetsbymultipleidsresponse.status_code, 200)
+ self.assertNotIn("None", json.loads(listsubnetsbymultipleidsresponse.content)["subnets"][0]["name"])
+ print(" ")
+
+
+
+ print('->>>>>>> test Neutron Show Subnet->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets/%s" % (json.loads(listsubnetsresponse.content)["subnets"][0]["id"])
+ showsubnetsresponse = requests.get(url, headers=headers)
+ self.assertEqual(showsubnetsresponse.status_code, 200)
+ self.assertNotIn("None", json.loads(showsubnetsresponse.content)["subnet"]["name"])
+ print(" ")
+
+ print('->>>>>>> test Neutron Show Non-Existing Subnet->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets/non-existing-id123"
+ showsubnetsresponse = requests.get(url, headers=headers)
+ self.assertEqual(showsubnetsresponse.status_code, 404)
+ print(" ")
+
+
+ print('->>>>>>> test Neutron Create Subnet ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets"
+ createsubnetdata = '{"subnet": {"name": "new_subnet", "network_id": "%s","ip_version": 4,"cidr": "10.0.0.1/24"} }' % (json.loads(createnetworkresponse.content)["network"]["id"])
+ createsubnetresponse = requests.post(url, data=createsubnetdata, headers=headers)
+ self.assertEqual(createsubnetresponse.status_code, 201)
+ self.assertEqual(json.loads(createsubnetresponse.content)["subnet"]["name"], "new_subnet")
+ print(" ")
+
+ print('->>>>>>> test Neutron Create Second Subnet ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets"
+ createsubnetdata = '{"subnet": {"name": "new_subnet", "network_id": "%s","ip_version": 4,"cidr": "10.0.0.1/24"} }' % (json.loads(createnetworkresponse.content)["network"]["id"])
+ createsubnetfailureresponse = requests.post(url, data=createsubnetdata, headers=headers)
+ self.assertEqual(createsubnetfailureresponse.status_code, 409)
+ print(" ")
+
+ print('->>>>>>> test Neutron Update Subnet ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets/%s" % (json.loads(createsubnetresponse.content)["subnet"]["id"])
+ updatesubnetdata = '{"subnet": {"name": "new_subnet_new_name", "network_id":"some_id", "tenant_id":"new_tenant_id", "allocation_pools":"change_me", "gateway_ip":"192.168.1.120", "ip_version":4, "cidr":"10.0.0.1/24", "id":"some_new_id", "enable_dhcp":true} }'
+ updatesubnetresponse = requests.put(url, data=updatesubnetdata, headers=headers)
+ self.assertEqual(updatesubnetresponse.status_code, 200)
+ self.assertEqual(json.loads(updatesubnetresponse.content)["subnet"]["name"], "new_subnet_new_name")
+ print(" ")
+
+ print('->>>>>>> test Neutron Update Non-Existing Subnet ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/subnets/non-existing-subnet-12345"
+ updatenonexistingsubnetdata = '{"subnet": {"name": "new_subnet_new_name"} }'
+ updatenonexistingsubnetresponse = requests.put(url, data=updatenonexistingsubnetdata, headers=headers)
+ self.assertEqual(updatenonexistingsubnetresponse.status_code, 404)
+ print(" ")
+
+
+
+ print('->>>>>>> test Neutron List Ports ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports"
+ listportsesponse = requests.get(url, headers=headers)
+ self.assertEqual(listportsesponse.status_code, 200)
+ self.assertEqual(json.loads(listportsesponse.content)["ports"][0]["status"], "ACTIVE")
+ listPortsName = json.loads(listportsesponse.content)["ports"][0]["name"]
+ listPortsId1 = json.loads(listportsesponse.content)["ports"][0]["id"]
+ listPortsId2 = json.loads(listportsesponse.content)["ports"][1]["id"]
+ print(" ")
+
+ print('->>>>>>> test Neutron List Ports By Name ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports?name=" + listPortsName
+ listportsbynameesponse = requests.get(url, headers=headers)
+ self.assertEqual(listportsbynameesponse.status_code, 200)
+ self.assertEqual(json.loads(listportsbynameesponse.content)["ports"][0]["name"], listPortsName)
+ print(" ")
+
+ print('->>>>>>> test Neutron List Ports By Id ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports?id=" + listPortsId1
+ listportsbyidesponse = requests.get(url, headers=headers)
+ self.assertEqual(listportsbyidesponse.status_code, 200)
+ self.assertEqual(json.loads(listportsbyidesponse.content)["ports"][0]["id"], listPortsId1)
+ print(" ")
+
+ print('->>>>>>> test Neutron List Ports By Multiple Ids ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports?id=" + listPortsId1 +"&id="+listPortsId2
+ listportsbymultipleidsesponse = requests.get(url, headers=headers)
+ self.assertEqual(listportsbymultipleidsesponse.status_code, 200)
+ self.assertEqual(json.loads(listportsbymultipleidsesponse.content)["ports"][0]["id"], listPortsId1)
+ print(" ")
+
+ print('->>>>>>> test Neutron List Non-Existing Ports ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports?id=non-existing-port-id"
+ listportsbynonexistingidsesponse = requests.get(url, headers=headers)
+ self.assertEqual(listportsbynonexistingidsesponse.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Neutron Show Port ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports/%s" % (json.loads(listportsesponse.content)["ports"][0]["id"])
+ showportresponse = requests.get(url, headers=headers)
+ self.assertEqual(showportresponse.status_code, 200)
+ self.assertEqual(json.loads(showportresponse.content)["port"]["status"], "ACTIVE")
+ print(" ")
+
+ print('->>>>>>> test Neutron Show Non-Existing Port ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports/non-existing-portid123"
+ shownonexistingportresponse = requests.get(url, headers=headers)
+ self.assertEqual(shownonexistingportresponse.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Neutron Create Port In Non-Existing Network ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports"
+ createnonexistingportdata = '{"port": {"name": "new_port", "network_id": "non-existing-id"} }'
+ createnonexistingnetworkportresponse = requests.post(url, data=createnonexistingportdata, headers=headers)
+ self.assertEqual(createnonexistingnetworkportresponse.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Neutron Create Port ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports"
+ createportdata = '{"port": {"name": "new_port", "network_id": "%s", "admin_state_up":true, "device_id":"device_id123", "device_owner":"device_owner123", "fixed_ips":"change_me","id":"new_id1234", "mac_address":"12:34:56:78:90", "status":"change_me", "tenant_id":"tenant_id123"} }' % (json.loads(createnetworkresponse.content)["network"]["id"])
+ createportresponse = requests.post(url, data=createportdata, headers=headers)
+ self.assertEqual(createportresponse.status_code, 201)
+ print (createportresponse.content)
+ self.assertEqual(json.loads(createportresponse.content)["port"]["name"], "new_port")
+ print(" ")
+
+ print('->>>>>>> test Neutron Create Port With Existing Name ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports"
+ createportwithexistingnamedata = '{"port": {"name": "new_port", "network_id": "%s"} }' % (json.loads(createnetworkresponse.content)["network"]["id"])
+ createportwithexistingnameresponse = requests.post(url, data=createportwithexistingnamedata, headers=headers)
+ self.assertEqual(createportwithexistingnameresponse.status_code, 500)
+ print(" ")
+
+ print('->>>>>>> test Neutron Create Port Without Name ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports"
+ createportdatawithoutname = '{"port": {"network_id": "%s"} }' % (json.loads(createnetworkresponse.content)["network"]["id"])
+ createportwithoutnameresponse = requests.post(url, data=createportdatawithoutname, headers=headers)
+ self.assertEqual(createportwithoutnameresponse.status_code, 201)
+ self.assertIn("port:cp", json.loads(createportwithoutnameresponse.content)["port"]["name"])
+ print(" ")
+
+ print('->>>>>>> test Neutron Update Port ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ print(json.loads(createportresponse.content)["port"]["name"])
+ url = "http://0.0.0.0:19696/v2.0/ports/%s" % (json.loads(createportresponse.content)["port"]["name"])
+ updateportdata = '{"port": {"name": "new_port_new_name", "admin_state_up":true, "device_id":"device_id123", "device_owner":"device_owner123", "fixed_ips":"change_me","mac_address":"12:34:56:78:90", "status":"change_me", "tenant_id":"tenant_id123", "network_id":"network_id123"} }'
+ updateportresponse = requests.put(url, data=updateportdata, headers=headers)
+ self.assertEqual(updateportresponse.status_code, 200)
+ self.assertEqual(json.loads(updateportresponse.content)["port"]["name"], "new_port_new_name")
+ print(" ")
+
+ print('->>>>>>> test Neutron Update Non-Existing Port ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports/non-existing-port-ip"
+ updatenonexistingportdata = '{"port": {"name": "new_port_new_name"} }'
+ updatenonexistingportresponse = requests.put(url, data=updatenonexistingportdata, headers=headers)
+ self.assertEqual(updatenonexistingportresponse.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Neutron Delete Port ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ righturl = "http://0.0.0.0:19696/v2.0/ports/%s" % (json.loads(createportresponse.content)["port"]["id"])
+ deleterightportresponse = requests.delete(righturl, headers=headers)
+ self.assertEqual(deleterightportresponse.status_code, 204)
+ print(" ")
+
+
+ print('->>>>>>> test Neutron Delete Non-Existing Port ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ wrongurl = "http://0.0.0.0:19696/v2.0/ports/unknownid"
+ deletewrongportresponse = requests.delete(wrongurl, headers=headers)
+ self.assertEqual(deletewrongportresponse.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Neutron Delete Subnet ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ wrongurl = "http://0.0.0.0:19696/v2.0/subnets/unknownid"
+ righturl = "http://0.0.0.0:19696/v2.0/subnets/%s" % (json.loads(updatesubnetresponse.content)["subnet"]["id"])
+ deletewrongsubnetresponse = requests.delete(wrongurl, headers=headers)
+ deleterightsubnetresponse = requests.delete(righturl, headers=headers)
+ self.assertEqual(deletewrongsubnetresponse.status_code, 404)
+ self.assertEqual(deleterightsubnetresponse.status_code, 204)
+ print(" ")
+
+ print('->>>>>>> test Neutron Delete Network ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ righturl = "http://0.0.0.0:19696/v2.0/networks/%s" % (json.loads(createnetworkresponse.content)["network"]["id"])
+ deleterightnetworkresponse = requests.delete(righturl, headers=headers)
+ self.assertEqual(deleterightnetworkresponse.status_code, 204)
+ print(" ")
+
+ print('->>>>>>> test Neutron Delete Non-Existing Network ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ wrongurl = "http://0.0.0.0:19696/v2.0/networks/unknownid"
+ deletewrongnetworkresponse = requests.delete(wrongurl, headers=headers)
+ self.assertEqual(deletewrongnetworkresponse.status_code, 404)
+ print(" ")
+
+ def testKeystomeDummy(self):
+ print('->>>>>>> test Keystone Dummy Class->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ print(" ")
+
+ headers = {'Content-type': 'application/json'}
+ test_heatapi_keystone_get_token = open(os.path.join(os.path.dirname(__file__), "test_heatapi_keystone_get_token.json")).read()
+
+ print('->>>>>>> test Keystone List Versions ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:15000/"
+ listapiversionstackresponse = requests.get(url, headers=headers)
+ self.assertEqual(listapiversionstackresponse.status_code, 200)
+ self.assertEqual(json.loads(listapiversionstackresponse.content)["versions"]["values"][0]["id"], "v2.0")
+ print(" ")
+
+ print('->>>>>>> test Keystone Show ApiV2 ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:15000/v2.0"
+ showapiversionstackresponse = requests.get(url, headers=headers)
+ self.assertEqual(showapiversionstackresponse.status_code, 200)
+ self.assertEqual(json.loads(showapiversionstackresponse.content)["version"]["id"], "v2.0")
+ print(" ")
+
+ print('->>>>>>> test Keystone Get Token ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:15000/v2.0/tokens"
+ gettokenstackresponse = requests.post(url, data=json.dumps(json.loads(test_heatapi_keystone_get_token)), headers=headers)
+ self.assertEqual(gettokenstackresponse.status_code, 200)
+ self.assertEqual(json.loads(gettokenstackresponse.content)["access"]["user"]["name"], "tenantName")
+ print(" ")
+
+
+ def testHeatDummy(self):
+ print('->>>>>>> test Heat Dummy Class->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ print(" ")
+
+ headers = {'Content-type': 'application/json'}
+ test_heatapi_template_create_stack = open(os.path.join(os.path.dirname(__file__), "test_heatapi_template_create_stack.json")).read()
+ test_heatapi_template_update_stack = open(os.path.join(os.path.dirname(__file__), "test_heatapi_template_update_stack.json")).read()
+
+ print('->>>>>>> test Heat List API Versions Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/"
+ listapiversionstackresponse = requests.get(url, headers=headers)
+ self.assertEqual(listapiversionstackresponse.status_code, 200)
+ self.assertEqual(json.loads(listapiversionstackresponse.content)["versions"][0]["id"], "v1.0")
+ print(" ")
+
+ print('->>>>>>> test Create Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123/stacks"
+ createstackresponse = requests.post(url, data=json.dumps(json.loads(test_heatapi_template_create_stack)), headers=headers)
+ self.assertEqual(createstackresponse.status_code, 201)
+ self.assertNotEqual(json.loads(createstackresponse.content)["stack"]["id"], "")
+ print(" ")
+
+ print('->>>>>>> test Create Stack With Existing Name ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123/stacks"
+ createstackwithexistingnameresponse = requests.post(url, data='{"stack_name" : "s1"}', headers=headers)
+ self.assertEqual(createstackwithexistingnameresponse.status_code, 409)
+ print(" ")
+
+ print('->>>>>>> test Create Stack With Unsupported Version ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123/stacks"
+ createstackwitheunsupportedversionresponse = requests.post(url, data='{"stack_name" : "stackname123", "template" : {"heat_template_version": "2015-04-29"}}', headers=headers)
+ self.assertEqual(createstackwitheunsupportedversionresponse.status_code, 400)
+ print(" ")
+
+
+ print('->>>>>>> test List Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123/stacks"
+ liststackresponse = requests.get(url, headers=headers)
+ self.assertEqual(liststackresponse.status_code, 200)
+ self.assertEqual(json.loads(liststackresponse.content)["stacks"][0]["stack_status"], "CREATE_COMPLETE")
+ print(" ")
+
+
+ print('->>>>>>> test Show Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123showStack/stacks/%s"% json.loads(createstackresponse.content)['stack']['id']
+ liststackdetailsresponse = requests.get(url, headers=headers)
+ self.assertEqual(liststackdetailsresponse.status_code, 200)
+ self.assertEqual(json.loads(liststackdetailsresponse.content)["stack"]["stack_status"], "CREATE_COMPLETE")
+ print(" ")
+
+ print('->>>>>>> test Show Non-Exisitng Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123showStack/stacks/non_exisitng_id123"
+ listnonexistingstackdetailsresponse = requests.get(url, headers=headers)
+ self.assertEqual(listnonexistingstackdetailsresponse.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Update Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123updateStack/stacks/%s"% json.loads(createstackresponse.content)['stack']['id']
+ updatestackresponse = requests.put(url, data=json.dumps(json.loads(test_heatapi_template_update_stack)),
+ headers=headers)
+ self.assertEqual(updatestackresponse.status_code, 202)
+ liststackdetailsresponse = requests.get(url, headers=headers)
+ self.assertEqual(json.loads(liststackdetailsresponse.content)["stack"]["stack_status"], "UPDATE_COMPLETE")
+ print(" ")
+
+ print('->>>>>>> test Update Non-Existing Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123updateStack/stacks/non_existing_id_1234"
+ updatenonexistingstackresponse = requests.put(url, data={"non": "sense"}, headers=headers)
+ self.assertEqual(updatenonexistingstackresponse.status_code, 404)
+ print(" ")
+
+ print('->>>>>>> test Delete Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123showStack/stacks/%s" % \
+ json.loads(createstackresponse.content)['stack']['id']
+ deletestackdetailsresponse = requests.delete(url, headers=headers)
+ self.assertEqual(deletestackdetailsresponse.status_code, 204)
+ print(" ")
+
+
+ def test_CombinedTesting(self):
+ print('->>>>>>> test Combinded tests->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ print(" ")
+
+ headers = {'Content-type': 'application/json'}
+ test_heatapi_template_create_stack = open(os.path.join(os.path.dirname(__file__),
+ "test_heatapi_template_create_stack.json")).read()
+ test_heatapi_template_update_stack = open(os.path.join(os.path.dirname(__file__),
+ "test_heatapi_template_update_stack.json")).read()
+
+ print('->>>>>>> test Combined Create Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123/stacks"
+ createstackresponse = requests.post(url,
+ data=json.dumps(json.loads(test_heatapi_template_create_stack)),
+ headers=headers)
+ self.assertEqual(createstackresponse.status_code, 201)
+ self.assertNotEqual(json.loads(createstackresponse.content)["stack"]["id"], "")
+ print(" ")
+
+ print('->>>>>>> test Combined Neutron List Ports ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports"
+ listportsesponse = requests.get(url, headers=headers)
+ self.assertEqual(listportsesponse.status_code, 200)
+ self.assertEqual(len(json.loads(listportsesponse.content)["ports"]), 9)
+ for port in json.loads(listportsesponse.content)["ports"]:
+ self.assertEqual(len(str(port['fixed_ips'][0]['subnet_id'])), 36)
+ print(" ")
+
+ print('->>>>>>> test Combined Neutron List Networks ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks"
+ listnetworksesponse = requests.get(url, headers=headers)
+ self.assertEqual(listnetworksesponse.status_code, 200)
+ self.assertEqual(len(json.loads(listnetworksesponse.content)["networks"]), 10)
+ for net in json.loads(listnetworksesponse.content)["networks"]:
+ self.assertEqual(len(str(net['subnets'][0])), 36)
+ print(" ")
+
+ print('->>>>>>> test Combined Update Stack ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18004/v1/tenantabc123updateStack/stacks/%s"% \
+ json.loads(createstackresponse.content)['stack']['id']
+ updatestackresponse = requests.put(url,
+ data=json.dumps(json.loads(test_heatapi_template_update_stack)),
+ headers=headers)
+ self.assertEqual(updatestackresponse.status_code, 202)
+ liststackdetailsresponse = requests.get(url, headers=headers)
+ self.assertEqual(json.loads(liststackdetailsresponse.content)["stack"]["stack_status"], "UPDATE_COMPLETE")
+ print(" ")
+
+ print('->>>>>>> test Combined Neutron List Ports ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/ports"
+ listportsesponse = requests.get(url, headers=headers)
+ self.assertEqual(listportsesponse.status_code, 200)
+ self.assertEqual(len(json.loads(listportsesponse.content)["ports"]), 18)
+ for port in json.loads(listportsesponse.content)["ports"]:
+ self.assertEqual(len(str(port['fixed_ips'][0]['subnet_id'])), 36)
+ print(" ")
+
+ print('->>>>>>> test Combined Neutron List Networks ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/networks"
+ listnetworksesponse = requests.get(url, headers=headers)
+ self.assertEqual(listnetworksesponse.status_code, 200)
+ self.assertEqual(len(json.loads(listnetworksesponse.content)["networks"]), 14)
+ for net in json.loads(listnetworksesponse.content)["networks"]:
+ self.assertEqual(len(str(net['subnets'][0])), 36)
+ print(" ")
+
+
+ # workflow create floating ip and assign it to a server
+
+ print('->>>>>>> CombinedNeutronCreateFloatingIP ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:19696/v2.0/floatingips"
+ createflip = requests.post(url, headers=headers,
+ data='{"floatingip":{"floating_network_id":"default"}}')
+ self.assertEqual(createflip.status_code, 200)
+ self.assertIsNotNone(json.loads(createflip.content)["floatingip"].get("port_id"))
+ port_id = json.loads(createflip.content)["floatingip"].get("port_id")
+ print(" ")
+
+ print('->>>>>>> CombinedNovaGetServer ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/detail"
+ listserverapisdetailedresponse = requests.get(url, headers=headers)
+ self.assertEqual(listserverapisdetailedresponse.status_code, 200)
+ self.assertEqual(json.loads(listserverapisdetailedresponse.content)["servers"][0]["status"], "ACTIVE")
+ server_id = json.loads(listserverapisdetailedresponse.content)["servers"][0]["id"]
+ print(" ")
+
+ print('->>>>>>> CombinedNovaAssignInterface ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/%s/os-interface" % server_id
+ assign = requests.post(url, headers=headers,
+ data='{"interfaceAttachment":{"net_id": "default"}}')
+ self.assertEqual(assign.status_code, 202)
+ self.assertIsNotNone(json.loads(assign.content)["interfaceAttachment"].get("port_id"))
+ port_id = json.loads(assign.content)["interfaceAttachment"].get("port_id")
+ print(" ")
+
+ print('->>>>>>> CombinedNovaDeleteInterface ->>>>>>>>>>>>>>>')
+ print('->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>')
+ url = "http://0.0.0.0:18774/v2.1/id_bla/servers/%s/os-interface/%s" % (server_id, port_id)
+ getintfs = requests.delete(url, headers=headers)
+ self.assertEqual(getintfs.status_code, 202)
+ print(" ")
+
+
+if __name__ == '__main__':
+ unittest.main()
sdkg1.connectDatacenter(self.dc[1])
# run the dummy gatekeeper (in another thread, don't block)
sdkg1.start()
+ time.sleep(3)
# start Mininet network
self.startNet()
time.sleep(1)
sdkg1.connectDatacenter(self.dc[1])
# run the dummy gatekeeper (in another thread, don't block)
sdkg1.start()
+ time.sleep(3)
# start Mininet network
self.startNet()
time.sleep(1)
sdkg1.connectDatacenter(self.dc[1])
# run the dummy gatekeeper (in another thread, don't block)
sdkg1.start()
+ time.sleep(3)
# start Mininet network
self.startNet()
time.sleep(1)