diff --git a/.dockerignore b/.dockerignore index 4a80382..b495acc 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,2 +1,6 @@ ./adaptation_layer/.venv +./adaptation_layer/.idea +./adaptation_layer/**/.pytest_cache +./adaptation_layer/**/pytestdebug.log +./adaptation_layer/robotframework diff --git a/Dockerfile b/Dockerfile index 9926bf4..06cc407 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,11 @@ -FROM python:3.6 as base +FROM python:3.6-slim as base EXPOSE 5000 -RUN apt-get update && apt-get install -y python3-dev -RUN ["pip3", "install", "pipenv"] -ENV PIPENV_VENV_IN_PROJECT 1 +RUN apt-get update && apt-get install -y build-essential +RUN ["pip3", "install", "pipenv==2018.11.26"] WORKDIR /usr/src/app # copy only pipfiles to install dependencies -COPY ./adaptation_layer/Pipfile . -COPY ./adaptation_layer/Pipfile.lock . -RUN ["pipenv", "install"] +COPY ./adaptation_layer/Pipfile* ./ +RUN ["pipenv", "install", "--system", "--ignore-pipfile", "--deploy"] COPY ./adaptation_layer . # setup env variables to initialize database ARG DB_SEED_NFVO @@ -15,13 +13,14 @@ ENV DB_SEED_NFVO $DB_SEED_NFVO ARG DB_SEED_NFVO_CRED ENV DB_SEED_NFVO_CRED $DB_SEED_NFVO_CRED RUN ["rm", "-f", "data/mso-lo.db"] -RUN ["pipenv", "run", "flask", "db", "upgrade"] -RUN ["pipenv", "run", "python", "manage.py", "seed"] - -FROM base as test -COPY ./openapi ./openapi +RUN ["flask", "db", "upgrade"] +RUN ["python", "manage.py", "seed"] FROM base as prod COPY ./uWSGI/app.ini . -CMD ["pipenv", "run", "uwsgi", "--ini", "app.ini"] +CMD ["uwsgi", "--ini", "app.ini"] + +FROM base as test +RUN ["pipenv", "install", "--system", "--ignore-pipfile", "--deploy", "--dev"] +COPY ./openapi ./openapi diff --git a/README.md b/README.md index b996eb5..acb9c09 100644 --- a/README.md +++ b/README.md @@ -21,35 +21,65 @@ Software is distributed under [Apache License, Version 2.0](http://www.apache.or ## Install guide -Before deploying, database must be populated with NFVO data. -Copy the mock files: +Copy the mock files (needed for a correct build): -``` +```shell script cd adaptation-layer/seed cp nfvo_mock.json nfvo.json cp nfvo_credentials_mock.json nfvo_credentials.json ``` -Edit the files by inserting your NFVOs information. +### Environment with local database -We use Docker Compose for deployment (it will also take care of database initialization). -From the repository main directory, run: +Edit [docker-compose.yaml](docker-compose.yml) and disable site-inventory support. +```yaml +SITEINV: 'false' ``` + +Edit `adaptation-layer/seed/nfvo.json` and `adaptation-layer/seed/nfvo_credentials.json` (copied before) +and insert your NFVO data. + +Deploy with: + +```shell script +docker pull python:3.6-slim docker-compose build docker-compose up ``` -You can test the app with: +### Environment with site-inventory (production) + +If [site-inventory](https://github.com/5GEVE/site-inventory) is available in your environment, +edit [docker-compose.yaml](docker-compose.yml) and change the environment variables for the `flask` service: +```yaml +SITEINV: 'true' +SITEINV_HOST: '192.168.17.20' +SITEINV_PORT: '8087' +SITEINV_INTERVAL: '300' +``` + +Then, deploy with: + +```shell script +docker pull python:3.6-slim +docker-compose build +docker-compose up ``` + +### Simple test + +You can test the app with: + +```shell script curl --request GET --url http://127.0.0.1:80/nfvo ``` ### Uninstall To remove the containers and volumes, use: -``` +```shell script docker-compose down --remove-orphans --volumes ``` @@ -76,7 +106,7 @@ and record the existence of the feature branch. To add it to your git configuration so it is used by default (locally for this repo), use: -``` +```shell script git config --add merge.ff false ``` @@ -85,24 +115,41 @@ git config --add merge.ff false For dependencies we use [Pipenv](https://pipenv.readthedocs.io/en/latest/). To setup the environment use: -``` +**Remember to copy and edit the mock files as said in the [install guide](#install-guide)** + +```shell script git checkout development -cd adaptation_layer/adaptation_layer +cd adaptation_layer pipenv install --dev # Create database with mock data pipenv run flask db upgrade pipenv run python manage.py seed # Run the flask app -pipenv run python app.py +FLASK_ENV=development flask run ``` -Please, always use `pipenv` to add dependencies: +Some features like notifications need [celery](https://docs.celeryproject.org/en/stable/index.html) and +[redis](https://redislabs.com/). +Simply setup a docker container with redis and run a celery worker. +```shell script +docker run -p 6379:6379 --name some-redis -d redis +export REDIS_HOST=localhost +celery -A tasks worker -B --loglevel=info ``` + +--- + +Please, always use `pipenv` to add/remove dependencies: + +```shell script pipenv install + +pipenv uninstall ``` -After that, please commit `Pipfile` and `Pipfile.lock`. +If everything works with the new dependencies, run `pipenv lock` and commit +both `Pipfile` and `Pipfile.lock`. ### Add a new NFVO driver @@ -114,24 +161,17 @@ For example, let's create `adaptation_layer/driver/onap.py`: from .interface import Driver class ONAP(Driver): + pass ``` Now you can start implementing the methods contained in the Driver interface. Your IDE should suggest you to create stubs for methods to be overriden. -### Error handling - -When throwing exceptions, please use the ones defined in [error_handler.py](adaptation_layer/error_handler.py). -This allows the application to correctly create the corresponding response and -status code. - -### Update the driver manager - To enable a newly created driver, edit [manager.py](adaptation_layer/driver/manager.py). The `get_driver()` method is simply a switch that returns an instance of the proper driver. -### Tests +### Add unit tests for a NFVO driver In order to test our software against an NFVO NBI, we need to mock it. For this purpose, we use [Prism](https://stoplight.io/open-source/prism/). @@ -141,22 +181,40 @@ Example: `/nfvo/nfvo_osm1/ns?__code=200` To add unit tests for a driver, create a new python file in `adaptation_layer/tests`. Please refer to [test_osm.py](/adaptation_layer/tests/test_osm.py) for examples. +### Error handling + +When throwing exceptions, please use the ones defined in [error_handler.py](adaptation_layer/error_handler.py). +This allows the application to correctly create the corresponding response and +status code. + +## Tests + +### Unit Tests + Unit tests can be executed by using Docker Compose files. -*Note*: the `--build-arg` parameters are used to initialize the database with mock data. +The following unit tests are currently available: -Example: +- [docker-compose.test-nfvo.yml](docker-compose.test-nfvo.yml) Test NFVO information retrieve +- [docker-compose.test-osm.yml](docker-compose.test-osm.yml) Test interactions with a mocked OSM +- [docker-compose.test-onap.yml](docker-compose.test-onap.yml) Test interactions with a mocked ONAP -``` -docker-compose -f docker-compose.test-osm.yml -p test-osm build \ - --build-arg DB_SEED_NFVO="seed/nfvo_mock.json" \ - --build-arg DB_SEED_NFVO_CRED="seed/nfvo_credentials_mock.json" -docker-compose -f docker-compose.test-osm.yml -p test-osm up +Example: +```shell script +docker-compose --file docker-compose.test-osm.yml --project-name test-osm build +docker-compose --file docker-compose.test-osm.yml --project-name test-osm up --abort-on-container-exit --exit-code-from test-osm ``` +*Note*: the `--project-name` parameter is necessary to distinguish test executions. + The file will run two containers: 1. A Prism server mocking the OSM NBI 2. A python container to execute unit tests -Unit tests execution for a new driver can be added by copying and modifyng [docker-compose.test-osm.yml](docker-compose.test-osm.yml). +Unit tests execution for a new driver can be added by copying and modifying +[docker-compose.test-osm.yml](docker-compose.test-osm.yml). + +### Integration tests +Integration tests are run with [Robot Framework](https://robotframework.org/). +Please refer to the specific [README](./adaptation_layer/robotframework/README.md). diff --git a/adaptation_layer/Pipfile b/adaptation_layer/Pipfile index 557220f..71dd7ae 100644 --- a/adaptation_layer/Pipfile +++ b/adaptation_layer/Pipfile @@ -6,20 +6,26 @@ verify_ssl = true [dev-packages] flake8 = "*" autopep8 = "*" +robotframework = "*" +RESTinstance = "*" +robotframework-dependencylibrary = "*" +robotframework-jsonlibrary = "*" +robotframework-jsonschemalibrary = "*" +robotframework-mockserver = "*" +pytest = "*" +jsonschema = "*" +prance = "*" [packages] flask = "==1.0.2" -pycurl = "*" requests = "*" pyaml = "*" uwsgi = "*" flask-script = "*" flask-sqlalchemy = "*" flask-migrate = "*" -pytest = "*" -jsonschema = "*" -prance = "*" -openapi-spec-validator = "*" +celery = "==4.4.2" +redis = "==3.5.2" [requires] python_version = "3.6" diff --git a/adaptation_layer/Pipfile.lock b/adaptation_layer/Pipfile.lock index 281ea4b..ed21785 100644 --- a/adaptation_layer/Pipfile.lock +++ b/adaptation_layer/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "1fbffbf9c418cbe34967068bc6a75c0959ee58712e90afdbbbe94f1786702b7e" + "sha256": "c7e221ef480358111253f50f702fcc9b38b3bafcc34bc2996cd5ea6f52c66488" }, "pipfile-spec": 6, "requires": { @@ -18,23 +18,38 @@ "default": { "alembic": { "hashes": [ - "sha256:d412982920653db6e5a44bfd13b1d0db5685cbaaccaf226195749c706e1e862a" + "sha256:035ab00497217628bf5d0be82d664d8713ab13d37b630084da8e1f98facf4dbf" ], - "version": "==1.3.3" + "version": "==1.4.2" }, - "attrs": { + "amqp": { "hashes": [ - "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", - "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + "sha256:6e649ca13a7df3faacdc8bbb280aa9a6602d22fd9d545336077e573a1f4ff3b8", + "sha256:77f1aef9410698d20eaeac5b73a87817365f457a507d82edf292e12cbb83b08d" ], - "version": "==19.3.0" + "version": "==2.5.2" + }, + "billiard": { + "hashes": [ + "sha256:bff575450859a6e0fbc2f9877d9b715b0bbc07c3565bb7ed2280526a0cdf5ede", + "sha256:d91725ce6425f33a97dfa72fb6bfef0e47d4652acd98a032bd1a7fbf06d5fa6a" + ], + "version": "==3.6.3.0" + }, + "celery": { + "hashes": [ + "sha256:108a0bf9018a871620936c33a3ee9f6336a89f8ef0a0f567a9001f4aa361415f", + "sha256:5b4b37e276033fe47575107a2775469f0b721646a08c96ec2c61531e4fe45f2a" + ], + "index": "pypi", + "version": "==4.4.2" }, "certifi": { "hashes": [ - "sha256:017c25db2a153ce562900032d5bc68e9f191e44e9a0f762f373977de9df1fbb3", - "sha256:25b64c7da4cd7479594d035c08c2d809eb4aab3a26e5a990ea98cc450c320f1f" + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" ], - "version": "==2019.11.28" + "version": "==2020.4.5.1" }, "chardet": { "hashes": [ @@ -45,10 +60,10 @@ }, "click": { "hashes": [ - "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13", - "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7" + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" ], - "version": "==7.0" + "version": "==7.1.2" }, "flask": { "hashes": [ @@ -60,11 +75,11 @@ }, "flask-migrate": { "hashes": [ - "sha256:6fb038be63d4c60727d5dfa5f581a6189af5b4e2925bc378697b4f0a40cfb4e1", - "sha256:a96ff1875a49a40bd3e8ac04fce73fdb0870b9211e6168608cbafa4eb839d502" + "sha256:4dc4a5cce8cbbb06b8dc963fd86cf8136bd7d875aabe2d840302ea739b243732", + "sha256:a69d508c2e09d289f6e55a417b3b8c7bfe70e640f53d2d9deb0d056a384f37ee" ], "index": "pypi", - "version": "==2.5.2" + "version": "==2.5.3" }, "flask-script": { "hashes": [ @@ -75,26 +90,26 @@ }, "flask-sqlalchemy": { "hashes": [ - "sha256:0078d8663330dc05a74bc72b3b6ddc441b9a744e2f56fe60af1a5bfc81334327", - "sha256:6974785d913666587949f7c2946f7001e4fa2cb2d19f4e69ead02e4b8f50b33d" + "sha256:0b656fbf87c5f24109d859bafa791d29751fabbda2302b606881ae5485b557a5", + "sha256:fcfe6df52cd2ed8a63008ca36b86a51fa7a4b70cef1c39e5625f722fca32308e" ], "index": "pypi", - "version": "==2.4.1" + "version": "==2.4.3" }, "idna": { "hashes": [ - "sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407", - "sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c" + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" ], - "version": "==2.8" + "version": "==2.9" }, "importlib-metadata": { "hashes": [ - "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302", - "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b" + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" ], "markers": "python_version < '3.8'", - "version": "==1.5.0" + "version": "==1.6.0" }, "itsdangerous": { "hashes": [ @@ -105,24 +120,24 @@ }, "jinja2": { "hashes": [ - "sha256:93187ffbc7808079673ef52771baa950426fd664d3aad1d0fa3e95644360e250", - "sha256:b0eaf100007721b5c16c1fc1eecb87409464edc10469ddc9a22a27a99123be49" + "sha256:89aab215427ef59c34ad58735269eb58b1a5808103067f7bb9d5836c651b3bb0", + "sha256:f0a4641d3cf955324a89c04f3d94663aa4d638abe8f733ecd3582848e1c37035" ], - "version": "==2.11.1" + "version": "==2.11.2" }, - "jsonschema": { + "kombu": { "hashes": [ - "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", - "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" + "sha256:2d1cda774126a044d91a7ff5fa6d09edf99f46924ab332a810760fe6740e9b76", + "sha256:598e7e749d6ab54f646b74b2d2df67755dee13894f73ab02a2a9feb8870c7cb2" ], - "index": "pypi", - "version": "==3.2.0" + "version": "==4.6.8" }, "mako": { "hashes": [ - "sha256:2984a6733e1d472796ceef37ad48c26f4a984bb18119bb2dbc37a44d8f6e75a4" + "sha256:8195c8c1400ceb53496064314c6736719c6f25e7479cd24c77be3d9361cddc27", + "sha256:93729a258e4ff0747c876bd9e20df1b9758028946e976324ccd2d68245c7b6a9" ], - "version": "==1.1.1" + "version": "==1.1.3" }, "markupsafe": { "hashes": [ @@ -162,28 +177,355 @@ ], "version": "==1.1.1" }, - "more-itertools": { + "pyaml": { "hashes": [ - "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c", - "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507" + "sha256:29a5c2a68660a799103d6949167bd6c7953d031449d08802386372de1db6ad71", + "sha256:67081749a82b72c45e5f7f812ee3a14a03b3f5c25ff36ec3b290514f8c4c4b99" ], - "version": "==8.2.0" + "index": "pypi", + "version": "==20.4.0" }, - "openapi-spec-validator": { + "python-dateutil": { "hashes": [ - "sha256:0caacd9829e9e3051e830165367bf58d436d9487b29a09220fa7edb9f47ff81b", - "sha256:d4da8aef72bf5be40cf0df444abd20009a41baf9048a8e03750c07a934f1bdd8", - "sha256:e489c7a273284bc78277ac22791482e8058d323b4a265015e9fcddf6a8045bcd" + "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", + "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + ], + "version": "==2.8.1" + }, + "python-editor": { + "hashes": [ + "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", + "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", + "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8" + ], + "version": "==1.0.4" + }, + "pytz": { + "hashes": [ + "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", + "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" + ], + "version": "==2020.1" + }, + "pyyaml": { + "hashes": [ + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" + ], + "version": "==5.3.1" + }, + "redis": { + "hashes": [ + "sha256:2ef11f489003f151777c064c5dbc6653dfb9f3eade159bcadc524619fddc2242", + "sha256:6d65e84bc58091140081ee9d9c187aab0480097750fac44239307a3bdf0b1251" ], "index": "pypi", - "version": "==0.2.8" + "version": "==3.5.2" + }, + "requests": { + "hashes": [ + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" + ], + "index": "pypi", + "version": "==2.23.0" + }, + "six": { + "hashes": [ + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" + ], + "version": "==1.15.0" + }, + "sqlalchemy": { + "hashes": [ + "sha256:128bc917ed20d78143a45024455ff0aed7d3b96772eba13d5dbaf9cc57e5c41b", + "sha256:156a27548ba4e1fed944ff9fcdc150633e61d350d673ae7baaf6c25c04ac1f71", + "sha256:27e2efc8f77661c9af2681755974205e7462f1ae126f498f4fe12a8b24761d15", + "sha256:2a12f8be25b9ea3d1d5b165202181f2b7da4b3395289000284e5bb86154ce87c", + "sha256:31c043d5211aa0e0773821fcc318eb5cbe2ec916dfbc4c6eea0c5188971988eb", + "sha256:65eb3b03229f684af0cf0ad3bcc771970c1260a82a791a8d07bffb63d8c95bcc", + "sha256:6cd157ce74a911325e164441ff2d9b4e244659a25b3146310518d83202f15f7a", + "sha256:703c002277f0fbc3c04d0ae4989a174753a7554b2963c584ce2ec0cddcf2bc53", + "sha256:869bbb637de58ab0a912b7f20e9192132f9fbc47fc6b5111cd1e0f6cdf5cf9b0", + "sha256:8a0e0cd21da047ea10267c37caf12add400a92f0620c8bc09e4a6531a765d6d7", + "sha256:8d01e949a5d22e5c4800d59b50617c56125fc187fbeb8fa423e99858546de616", + "sha256:925b4fe5e7c03ed76912b75a9a41dfd682d59c0be43bce88d3b27f7f5ba028fb", + "sha256:9cb1819008f0225a7c066cac8bb0cf90847b2c4a6eb9ebb7431dbd00c56c06c5", + "sha256:a87d496884f40c94c85a647c385f4fd5887941d2609f71043e2b73f2436d9c65", + "sha256:a9030cd30caf848a13a192c5e45367e3c6f363726569a56e75dc1151ee26d859", + "sha256:a9e75e49a0f1583eee0ce93270232b8e7bb4b1edc89cc70b07600d525aef4f43", + "sha256:b50f45d0e82b4562f59f0e0ca511f65e412f2a97d790eea5f60e34e5f1aabc9a", + "sha256:b7878e59ec31f12d54b3797689402ee3b5cfcb5598f2ebf26491732758751908", + "sha256:ce1ddaadee913543ff0154021d31b134551f63428065168e756d90bdc4c686f5", + "sha256:ce2646e4c0807f3461be0653502bb48c6e91a5171d6e450367082c79e12868bf", + "sha256:ce6c3d18b2a8ce364013d47b9cad71db815df31d55918403f8db7d890c9d07ae", + "sha256:e4e2664232005bd306f878b0f167a31f944a07c4de0152c444f8c61bbe3cfb38", + "sha256:e8aa395482728de8bdcca9cc0faf3765ab483e81e01923aaa736b42f0294f570", + "sha256:eb4fcf7105bf071c71068c6eee47499ab8d4b8f5a11fc35147c934f0faa60f23", + "sha256:ed375a79f06cad285166e5be74745df1ed6845c5624aafadec4b7a29c25866ef", + "sha256:f35248f7e0d63b234a109dd72fbfb4b5cb6cb6840b221d0df0ecbf54ab087654", + "sha256:f502ef245c492b391e0e23e94cba030ab91722dcc56963c85bfd7f3441ea2bbe", + "sha256:fe01bac7226499aedf472c62fa3b85b2c619365f3f14dd222ffe4f3aa91e5f98" + ], + "version": "==1.3.17" + }, + "urllib3": { + "hashes": [ + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" + ], + "version": "==1.25.9" + }, + "uwsgi": { + "hashes": [ + "sha256:4972ac538800fb2d421027f49b4a1869b66048839507ccf0aa2fda792d99f583" + ], + "index": "pypi", + "version": "==2.0.18" + }, + "vine": { + "hashes": [ + "sha256:133ee6d7a9016f177ddeaf191c1f58421a1dcc6ee9a42c58b34bed40e1d2cd87", + "sha256:ea4947cc56d1fd6f2095c8d543ee25dad966f78692528e68b4fada11ba3f98af" + ], + "version": "==1.3.0" + }, + "werkzeug": { + "hashes": [ + "sha256:2de2a5db0baeae7b2d2664949077c2ac63fbd16d98da0ff71837f7d1dea3fd43", + "sha256:6c80b1e5ad3665290ea39320b91e1be1e0d5f60652b964a3070216de83d2e47c" + ], + "version": "==1.0.1" + }, + "zipp": { + "hashes": [ + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" + ], + "version": "==3.1.0" + } + }, + "develop": { + "appdirs": { + "hashes": [ + "sha256:7d5d0167b2b1ba821647616af46a749d1c653740dd0d2415100fe26e27afdf41", + "sha256:a841dacd6b99318a741b166adb07e19ee71a274450e68237b4650ca1055ab128" + ], + "version": "==1.4.4" + }, + "attrs": { + "hashes": [ + "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c", + "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72" + ], + "version": "==19.3.0" + }, + "autopep8": { + "hashes": [ + "sha256:152fd8fe47d02082be86e05001ec23d6f420086db56b17fc883f3f965fb34954" + ], + "index": "pypi", + "version": "==1.5.2" + }, + "certifi": { + "hashes": [ + "sha256:1d987a998c75633c40847cc966fcf5904906c920a7f17ef374f5aa4282abd304", + "sha256:51fcb31174be6e6664c5f69e3e1691a2d72a1a12e90f872cbdb1567eb47b6519" + ], + "version": "==2020.4.5.1" + }, + "chardet": { + "hashes": [ + "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae", + "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691" + ], + "version": "==3.0.4" + }, + "click": { + "hashes": [ + "sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a", + "sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc" + ], + "version": "==7.1.2" + }, + "coverage": { + "hashes": [ + "sha256:00f1d23f4336efc3b311ed0d807feb45098fc86dee1ca13b3d6768cdab187c8a", + "sha256:01333e1bd22c59713ba8a79f088b3955946e293114479bbfc2e37d522be03355", + "sha256:0cb4be7e784dcdc050fc58ef05b71aa8e89b7e6636b99967fadbdba694cf2b65", + "sha256:0e61d9803d5851849c24f78227939c701ced6704f337cad0a91e0972c51c1ee7", + "sha256:1601e480b9b99697a570cea7ef749e88123c04b92d84cedaa01e117436b4a0a9", + "sha256:2742c7515b9eb368718cd091bad1a1b44135cc72468c731302b3d641895b83d1", + "sha256:2d27a3f742c98e5c6b461ee6ef7287400a1956c11421eb574d843d9ec1f772f0", + "sha256:402e1744733df483b93abbf209283898e9f0d67470707e3c7516d84f48524f55", + "sha256:5c542d1e62eece33c306d66fe0a5c4f7f7b3c08fecc46ead86d7916684b36d6c", + "sha256:5f2294dbf7875b991c381e3d5af2bcc3494d836affa52b809c91697449d0eda6", + "sha256:6402bd2fdedabbdb63a316308142597534ea8e1895f4e7d8bf7476c5e8751fef", + "sha256:66460ab1599d3cf894bb6baee8c684788819b71a5dc1e8fa2ecc152e5d752019", + "sha256:782caea581a6e9ff75eccda79287daefd1d2631cc09d642b6ee2d6da21fc0a4e", + "sha256:79a3cfd6346ce6c13145731d39db47b7a7b859c0272f02cdb89a3bdcbae233a0", + "sha256:7a5bdad4edec57b5fb8dae7d3ee58622d626fd3a0be0dfceda162a7035885ecf", + "sha256:8fa0cbc7ecad630e5b0f4f35b0f6ad419246b02bc750de7ac66db92667996d24", + "sha256:a027ef0492ede1e03a8054e3c37b8def89a1e3c471482e9f046906ba4f2aafd2", + "sha256:a3f3654d5734a3ece152636aad89f58afc9213c6520062db3978239db122f03c", + "sha256:a82b92b04a23d3c8a581fc049228bafde988abacba397d57ce95fe95e0338ab4", + "sha256:acf3763ed01af8410fc36afea23707d4ea58ba7e86a8ee915dfb9ceff9ef69d0", + "sha256:adeb4c5b608574a3d647011af36f7586811a2c1197c861aedb548dd2453b41cd", + "sha256:b83835506dfc185a319031cf853fa4bb1b3974b1f913f5bb1a0f3d98bdcded04", + "sha256:bb28a7245de68bf29f6fb199545d072d1036a1917dca17a1e75bbb919e14ee8e", + "sha256:bf9cb9a9fd8891e7efd2d44deb24b86d647394b9705b744ff6f8261e6f29a730", + "sha256:c317eaf5ff46a34305b202e73404f55f7389ef834b8dbf4da09b9b9b37f76dd2", + "sha256:dbe8c6ae7534b5b024296464f387d57c13caa942f6d8e6e0346f27e509f0f768", + "sha256:de807ae933cfb7f0c7d9d981a053772452217df2bf38e7e6267c9cbf9545a796", + "sha256:dead2ddede4c7ba6cb3a721870f5141c97dc7d85a079edb4bd8d88c3ad5b20c7", + "sha256:dec5202bfe6f672d4511086e125db035a52b00f1648d6407cc8e526912c0353a", + "sha256:e1ea316102ea1e1770724db01998d1603ed921c54a86a2efcb03428d5417e489", + "sha256:f90bfc4ad18450c80b024036eaf91e4a246ae287701aaa88eaebebf150868052" + ], + "version": "==5.1" + }, + "decorator": { + "hashes": [ + "sha256:41fa54c2a0cc4ba648be4fd43cff00aedf5b9465c9bf18d64325bc225f08f760", + "sha256:e3a62f0520172440ca0dcc823749319382e377f37f140a0b99ef45fecb84bfe7" + ], + "version": "==4.4.2" + }, + "distlib": { + "hashes": [ + "sha256:2e166e231a26b36d6dfe35a48c4464346620f8645ed0ace01ee31822b288de21" + ], + "version": "==0.3.0" + }, + "docutils": { + "hashes": [ + "sha256:0c5b78adfbf7762415433f5515cd5c9e762339e23369dbe8000d84a4bf4ab3af", + "sha256:c2de3a60e9e7d07be26b7f2b00ca0309c207e06c100f9cc2a94931fc75a478fc" + ], + "version": "==0.16" + }, + "filelock": { + "hashes": [ + "sha256:18d82244ee114f543149c66a6e0c14e9c4f8a1044b5cdaadd0f82159d6a6ff59", + "sha256:929b7d63ec5b7d6b71b0fa5ac14e030b3f70b75747cef1b10da9b879fef15836" + ], + "version": "==3.0.12" + }, + "flake8": { + "hashes": [ + "sha256:c69ac1668e434d37a2d2880b3ca9aafd54b3a10a3ac1ab101d22f29e29cf8634", + "sha256:ccaa799ef9893cebe69fdfefed76865aeaefbb94cb8545617b2298786a4de9a5" + ], + "index": "pypi", + "version": "==3.8.2" + }, + "flex": { + "hashes": [ + "sha256:292ed6a37f1ac0a10ad8669f5ceb82e8ba3106c16c54090820927bac8b0b29eb" + ], + "version": "==6.14.1" + }, + "genson": { + "hashes": [ + "sha256:40b8faa17f53240f224d683d7a302b5f7b35eb7ab2aba1c223a58a38b8d09c42" + ], + "version": "==1.2.1" + }, + "idna": { + "hashes": [ + "sha256:7588d1c14ae4c77d74036e8c22ff447b26d0fde8f007354fd48a7814db15b7cb", + "sha256:a068a21ceac8a4d63dbfd964670474107f541babbd2250d61922f029858365fa" + ], + "version": "==2.9" + }, + "importlib-metadata": { + "hashes": [ + "sha256:2a688cbaa90e0cc587f1df48bdc97a6eadccdcd9c35fb3f976a09e3b5016d90f", + "sha256:34513a8a0c4962bc66d35b359558fd8a5e10cd472d37aec5f66858addef32c1e" + ], + "markers": "python_version < '3.8'", + "version": "==1.6.0" + }, + "importlib-resources": { + "hashes": [ + "sha256:6f87df66833e1942667108628ec48900e02a4ab4ad850e25fbf07cb17cf734ca", + "sha256:85dc0b9b325ff78c8bef2e4ff42616094e16b98ebd5e3b50fe7e2f0bbcdcde49" + ], + "markers": "python_version < '3.7'", + "version": "==1.5.0" + }, + "jsonpath-ng": { + "hashes": [ + "sha256:77b1f93f4444a50c924cb11cdc273546ff79f41830d485916fc6ddf4e0c1ce55", + "sha256:a4f014c28b5a1279c3a189fb1d2f37a98848f359f19fd296b54e38ec6fc841a9" + ], + "version": "==1.5.1" + }, + "jsonpath-rw": { + "hashes": [ + "sha256:05c471281c45ae113f6103d1268ec7a4831a2e96aa80de45edc89b11fac4fbec" + ], + "version": "==1.4.0" + }, + "jsonpath-rw-ext": { + "hashes": [ + "sha256:0947e018c4e6d46f9d04c56487793c702eb225fa252891aa4ed41a9ca26f3d84", + "sha256:a9e44e803b6d87d135b09d1e5af0db4d4cf97ba62711a80aa51c8c721980a994" + ], + "version": "==1.2.2" + }, + "jsonpointer": { + "hashes": [ + "sha256:c192ba86648e05fdae4f08a17ec25180a9aef5008d973407b581798a83975362", + "sha256:ff379fa021d1b81ab539f5ec467c7745beb1a5671463f9dcc2b2d458bd361c1e" + ], + "version": "==2.0" + }, + "jsonschema": { + "hashes": [ + "sha256:4e5b3cf8216f577bee9ce139cbe72eca3ea4f292ec60928ff24758ce626cd163", + "sha256:c8a85b28d377cc7737e46e2d9f2b4f44ee3c0e1deac6bf46ddefc7187d30797a" + ], + "index": "pypi", + "version": "==3.2.0" + }, + "mccabe": { + "hashes": [ + "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", + "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + ], + "version": "==0.6.1" + }, + "more-itertools": { + "hashes": [ + "sha256:558bb897a2232f5e4f8e2399089e35aecb746e1f9191b6584a151647e89267be", + "sha256:7818f596b1e87be009031c7653d01acc46ed422e6656b394b0f765ce66ed4982" + ], + "version": "==8.3.0" }, "packaging": { "hashes": [ - "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73", - "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334" + "sha256:4357f74f47b9c12db93624a82154e9b120fa8293699949152b22065d556079f8", + "sha256:998416ba6962ae7fbd6596850b80e17859a5753ba17c32284f67bfff33784181" + ], + "version": "==20.4" + }, + "pbr": { + "hashes": [ + "sha256:07f558fece33b05caf857474a366dfcc00562bca13dd8b47b2b3e22d9f9bf55c", + "sha256:579170e23f8e0c2f24b0de612f71f648eccb79fb1322c814ae6b3c07b5ba23e8" ], - "version": "==20.1" + "version": "==5.4.5" }, "pluggy": { "hashes": [ @@ -192,13 +534,20 @@ ], "version": "==0.13.1" }, + "ply": { + "hashes": [ + "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3", + "sha256:096f9b8350b65ebd2fd1346b12452efe5b9607f7482813ffca50c22722a807ce" + ], + "version": "==3.11" + }, "prance": { "hashes": [ - "sha256:2f6571f16692ce89bfc0eea6577b7ebff93fcfdeb7e002c0323c7b618795f0ec", - "sha256:3f6d6d76a3854d7c1191459e2a61c8ff3f2fb9cd7d1227986fe5ff41e30b3e84" + "sha256:1f88df76d8d642efac1243801d39d9348bbb7530311270adbd7305ec45e077ac", + "sha256:e1ab67ec984c94b94e32bf17a2d6f35960db8b1ee248efea9f00ebaa1fcea1af" ], "index": "pypi", - "version": "==0.18.1" + "version": "==0.18.2" }, "py": { "hashes": [ @@ -207,188 +556,198 @@ ], "version": "==1.8.1" }, - "pyaml": { + "pycodestyle": { "hashes": [ - "sha256:10b7406f4d29dceca1bc4540713363e07c18886ac9a44edd3d7720314bdcb0ff", - "sha256:b3f636b467864319d7ded1558f86bb305b8612a274f5d443a62dc5eceb1b7176" + "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367", + "sha256:c58a7d2815e0e8d7972bf1803331fb0152f867bd89adf8a01dfd55085434192e" ], - "index": "pypi", - "version": "==19.12.0" + "version": "==2.6.0" + }, + "pyflakes": { + "hashes": [ + "sha256:0d94e0e05a19e57a99444b6ddcf9a6eb2e5c68d3ca1e98e90707af8152c90a92", + "sha256:35b2d75ee967ea93b55750aa9edbbf72813e06a66ba54438df2cfac9e3c27fc8" + ], + "version": "==2.2.0" }, - "pycurl": { + "pygments": { "hashes": [ - "sha256:1957c867e2a341f5526c107c7bbc5014d6e75fdc2a14294fcb8a47663fbd2e15", - "sha256:50aee0469511a9708a1f1a50d510b5ec2013fc6f5e720c32bbcb3b9c7b0f45b1", - "sha256:667db26516e50ce4a853745906f3b149c24756d85061b9d966eb7ec43a8c48a4", - "sha256:7cc13d3421cbd31921d77e22d1f57c0e1a8d0fb461938a587689a93162ccef2f", - "sha256:a0c62dbc66b9b947832307d6cf7bdb5e4da906ce7b3efe6f74292e8f3dc5abe3", - "sha256:a6966e8d9ccda31c6d077c4f8673aaa88141cc73d50e110e93e627b816d17fd1", - "sha256:beadfa7f052626864d70eb33cec8f2aeece12dfb483c2424cc07b057f83b7d35", - "sha256:c5c379c8cc777dda397f86f0d0049480250ae84a82a9d99d668f461d368fb39c", - "sha256:ec7dd291545842295b7b56c12c90ffad2976cc7070c98d7b1517b7b6cd5994b3" + "sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44", + "sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324" ], - "index": "pypi", - "version": "==7.43.0.5" + "version": "==2.6.1" }, "pyparsing": { "hashes": [ - "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f", - "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec" + "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1", + "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" ], - "version": "==2.4.6" + "version": "==2.4.7" }, "pyrsistent": { "hashes": [ - "sha256:cdc7b5e3ed77bed61270a47d35434a30617b9becdf2478af76ad2c6ade307280" + "sha256:28669905fe725965daa16184933676547c5bb40a5153055a8dee2a4bd7933ad3" ], - "version": "==0.15.7" + "version": "==0.16.0" }, "pytest": { "hashes": [ - "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d", - "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6" + "sha256:95c710d0a72d91c13fae35dce195633c929c3792f54125919847fdcdf7caa0d3", + "sha256:eb2b5e935f6a019317e455b6da83dd8650ac9ffd2ee73a7b657a30873d67a698" ], "index": "pypi", - "version": "==5.3.5" + "version": "==5.4.2" }, - "python-dateutil": { + "pytz": { "hashes": [ - "sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c", - "sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a" + "sha256:a494d53b6d39c3c6e44c3bec237336e14305e4f29bbf800b599253057fbb79ed", + "sha256:c35965d010ce31b23eeb663ed3cc8c906275d6be1a34393a1d73a41febf4a048" ], - "version": "==2.8.1" + "version": "==2020.1" }, - "python-editor": { + "pyyaml": { "hashes": [ - "sha256:1bf6e860a8ad52a14c3ee1252d5dc25b2030618ed80c022598f00176adc8367d", - "sha256:51fda6bcc5ddbbb7063b2af7509e43bd84bfc32a4ff71349ec7847713882327b", - "sha256:5f98b069316ea1c2ed3f67e7f5df6c0d8f10b689964a4a811ff64f0106819ec8" + "sha256:06a0d7ba600ce0b2d2fe2e78453a470b5a6e000a985dd4a4e54e436cc36b0e97", + "sha256:240097ff019d7c70a4922b6869d8a86407758333f02203e0fc6ff79c5dcede76", + "sha256:4f4b913ca1a7319b33cfb1369e91e50354d6f07a135f3b901aca02aa95940bd2", + "sha256:69f00dca373f240f842b2931fb2c7e14ddbacd1397d57157a9b005a6a9942648", + "sha256:73f099454b799e05e5ab51423c7bcf361c58d3206fa7b0d555426b1f4d9a3eaf", + "sha256:74809a57b329d6cc0fdccee6318f44b9b8649961fa73144a98735b0aaf029f1f", + "sha256:7739fc0fa8205b3ee8808aea45e968bc90082c10aef6ea95e855e10abf4a37b2", + "sha256:95f71d2af0ff4227885f7a6605c37fd53d3a106fcab511b8860ecca9fcf400ee", + "sha256:b8eac752c5e14d3eca0e6dd9199cd627518cb5ec06add0de9d32baeee6fe645d", + "sha256:cc8955cfbfc7a115fa81d85284ee61147059a753344bc51098f3ccd69b0d7e0c", + "sha256:d13155f591e6fcc1ec3b30685d50bf0711574e2c0dfffd7644babf8b5102ca1a" ], - "version": "==1.0.4" + "version": "==5.3.1" }, - "pyyaml": { + "requests": { "hashes": [ - "sha256:059b2ee3194d718896c0ad077dd8c043e5e909d9180f387ce42012662a4946d6", - "sha256:1cf708e2ac57f3aabc87405f04b86354f66799c8e62c28c5fc5f88b5521b2dbf", - "sha256:24521fa2890642614558b492b473bee0ac1f8057a7263156b02e8b14c88ce6f5", - "sha256:4fee71aa5bc6ed9d5f116327c04273e25ae31a3020386916905767ec4fc5317e", - "sha256:70024e02197337533eef7b85b068212420f950319cc8c580261963aefc75f811", - "sha256:74782fbd4d4f87ff04159e986886931456a1894c61229be9eaf4de6f6e44b99e", - "sha256:940532b111b1952befd7db542c370887a8611660d2b9becff75d39355303d82d", - "sha256:cb1f2f5e426dc9f07a7681419fe39cee823bb74f723f36f70399123f439e9b20", - "sha256:dbbb2379c19ed6042e8f11f2a2c66d39cceb8aeace421bfc29d085d93eda3689", - "sha256:e3a057b7a64f1222b56e47bcff5e4b94c4f61faac04c7c4ecb1985e18caa3994", - "sha256:e9f45bd5b92c7974e59bcd2dcc8631a6b6cc380a904725fce7bc08872e691615" + "sha256:43999036bfa82904b6af1d99e4882b560e5e2c68e5c4b0aa03b655f3d7d73fee", + "sha256:b3f43d496c6daba4493e7c431722aeb7dbc6288f52a6e04e7b6023b0247817e6" ], - "version": "==5.3" + "index": "pypi", + "version": "==2.23.0" }, - "requests": { + "restinstance": { "hashes": [ - "sha256:11e007a8a2aa0323f5a921e9e6a2d7e4e67d9877e85773fba9ba6419025cbeb4", - "sha256:9cf5292fcd0f598c671cfc1e0d7d1a7f13bb8085e9a590f48c010551dc6c4b31" + "sha256:c241e44e51c7a793c7c5ca0476caac2b703cc78da8041ab05ded965ce49bc187", + "sha256:ee1d5a68573915e8baafe97ef20889c06795a0355b89508996fb0e1381a0d44c" ], "index": "pypi", - "version": "==2.22.0" + "version": "==1.0.2" }, - "semver": { + "rfc3987": { "hashes": [ - "sha256:aa1c6be3bf23e346e00c509a7ee87735a7e0fd6b404cf066037cfeab2c770320", - "sha256:ed1edeaa0c27f68feb74f09f715077fd07b728446dc2bb7fc470fc0f737873a0" + "sha256:10702b1e51e5658843460b189b185c0366d2cf4cff716f13111b0ea9fd2dce53", + "sha256:d3c4d257a560d544e9826b38bc81db676890c79ab9d7ac92b39c7a253d5ca733" ], - "version": "==2.9.0" + "version": "==1.3.8" }, - "six": { + "robotframework": { "hashes": [ - "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a", - "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c" + "sha256:34923415afd9491141630ad1d7146b638ee869f1c7f97c450c0da5ce79e1ac23", + "sha256:d693e6d06b17f48669e2a8c4cb6c1f0d56e5f1a74835d18b8ea2118da7bf2d79" ], - "version": "==1.14.0" + "index": "pypi", + "version": "==3.2.1" }, - "sqlalchemy": { + "robotframework-dependencylibrary": { "hashes": [ - "sha256:64a7b71846db6423807e96820993fa12a03b89127d278290ca25c0b11ed7b4fb" + "sha256:1c59dffaf1ed30be3ad5e8b4ad5696f276754cf476ffee4e782d7c436d02dc39", + "sha256:2fd9a203f740a74c5427787ea19a86e4c76cebd5f9b1459f5290172cc13c8ecd" ], - "version": "==1.3.13" + "index": "pypi", + "version": "==1.0.0.post1" }, - "urllib3": { + "robotframework-jsonlibrary": { "hashes": [ - "sha256:2f3db8b19923a873b3e5256dc9c2dedfa883e33d87c690d9c7913e1f40673cdc", - "sha256:87716c2d2a7121198ebcb7ce7cccf6ce5e9ba539041cfbaeecfb641dc0bf6acc" + "sha256:2582f6e1f7b04666cc2e73e00f1d50563ce7c018803ddead8dab5bfc79466ed8" ], - "version": "==1.25.8" + "index": "pypi", + "version": "==0.3.1" }, - "uwsgi": { + "robotframework-jsonschemalibrary": { "hashes": [ - "sha256:4972ac538800fb2d421027f49b4a1869b66048839507ccf0aa2fda792d99f583" + "sha256:e52a00c8d8155b9c99d130da8360a7a692b19d292af1c58f0bf433df81df833b" ], "index": "pypi", - "version": "==2.0.18" + "version": "==1.0" }, - "wcwidth": { + "robotframework-mockserver": { "hashes": [ - "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603", - "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8" + "sha256:99bc32fedb95c1fdd976bc4334177488803a2dcadde24e197b63db693e4016f5" ], - "version": "==0.1.8" + "index": "pypi", + "version": "==0.0.6" }, - "werkzeug": { + "semver": { "hashes": [ - "sha256:1e0dedc2acb1f46827daa2e399c1485c8fa17c0d8e70b6b875b4e7f54bf408d2", - "sha256:b353856d37dec59d6511359f97f6a4b2468442e454bd1c98298ddce53cac1f04" + "sha256:21eb9deafc627dfd122e294f96acd0deadf1b5b7758ab3bbdf3698155dca4705", + "sha256:b08a84f604ef579e474ce448672a05c8d50d1ee0b24cee9fb58a12b260e4d0dc" ], - "version": "==0.16.1" + "version": "==2.10.1" }, - "zipp": { + "six": { "hashes": [ - "sha256:ccc94ed0909b58ffe34430ea5451f07bc0c76467d7081619a454bf5c98b89e28", - "sha256:feae2f18633c32fc71f2de629bfb3bd3c9325cd4419642b1f1da42ee488d9b98" + "sha256:30639c035cdb23534cd4aa2dd52c3bf48f06e5f4a941509c8bafd8ce11080259", + "sha256:8b74bedcbbbaca38ff6d7491d76f2b06b3592611af620f8426e82dddb04a5ced" ], - "version": "==2.1.0" - } - }, - "develop": { - "autopep8": { + "version": "==1.15.0" + }, + "strict-rfc3339": { "hashes": [ - "sha256:0f592a0447acea0c2b0a9602be1e4e3d86db52badd2e3c84f0193bfd89fd3a43" + "sha256:5cad17bedfc3af57b399db0fed32771f18fc54bbd917e85546088607ac5e1277" ], - "index": "pypi", - "version": "==1.5" + "version": "==0.7" }, - "entrypoints": { + "tox": { "hashes": [ - "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19", - "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451" + "sha256:96efa09710a3daeeb845561ebbe1497641d9cef2ee0aea30db6969058b2bda2f", + "sha256:9ee7de958a43806402a38c0d2aa07fa8553f4d2c20a15b140e9f771c2afeade0" ], - "version": "==0.3" + "version": "==3.0.0" }, - "flake8": { + "tzlocal": { "hashes": [ - "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb", - "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca" + "sha256:643c97c5294aedc737780a49d9df30889321cbe1204eac2c2ec6134035a92e44", + "sha256:e2cb6c6b5b604af38597403e9852872d7f534962ae2954c7f35efcb1ccacf4a4" ], - "index": "pypi", - "version": "==3.7.9" + "version": "==2.1" }, - "mccabe": { + "urllib3": { "hashes": [ - "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42", - "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f" + "sha256:3018294ebefce6572a474f0604c2021e33b3fd8006ecd11d62107a5d2a963527", + "sha256:88206b0eb87e6d677d424843ac5209e3fb9d0190d0ee169599165ec25e9d9115" ], - "version": "==0.6.1" + "version": "==1.25.9" }, - "pycodestyle": { + "validate-email": { "hashes": [ - "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56", - "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c" + "sha256:784719dc5f780be319cdd185dc85dd93afebdb6ebb943811bc4c7c5f9c72aeaf" ], - "version": "==2.5.0" + "version": "==1.3" }, - "pyflakes": { + "virtualenv": { + "hashes": [ + "sha256:a116629d4e7f4d03433b8afa27f43deba09d48bc48f5ecefa4f015a178efb6cf", + "sha256:a730548b27366c5e6cbdf6f97406d861cccece2e22275e8e1a757aeff5e00c70" + ], + "version": "==20.0.21" + }, + "wcwidth": { + "hashes": [ + "sha256:cafe2186b3c009a04067022ce1dcd79cb38d8d65ee4f4791b8888d6599d1bbe1", + "sha256:ee73862862a156bf77ff92b09034fc4825dd3af9cf81bc5b360668d425f3c5f1" + ], + "version": "==0.1.9" + }, + "zipp": { "hashes": [ - "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0", - "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2" + "sha256:aa36550ff0c0b7ef7fa639055d797116ee891440eac1a56f378e2d3179e0320b", + "sha256:c599e4d75c98f6798c509911d08a22e6c021d074469042177c8c86fb92eefd96" ], - "version": "==2.1.1" + "version": "==3.1.0" } } } diff --git a/adaptation_layer/app.py b/adaptation_layer/app.py index 421199e..ef800fe 100644 --- a/adaptation_layer/app.py +++ b/adaptation_layer/app.py @@ -11,23 +11,43 @@ # 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. +import logging +import os -from flask import jsonify, abort, request, make_response +from flask import jsonify, abort, request, make_response, Flask +from flask_migrate import Migrate import config import driver.manager as manager -from error_handler import NfvoNotFound, NsNotFound, NsdNotFound +import siteinventory +import sqlite +import tasks +from error_handler import NfvoNotFound, NsNotFound, NsdNotFound, \ + init_errorhandler, NfvoCredentialsNotFound, SubscriptionNotFound from error_handler import Unauthorized, BadRequest, ServerError, NsOpNotFound -app = config.app +SITEINV = os.getenv('SITEINV', 'false').lower() + +logging.basicConfig(level=logging.INFO) +app = Flask(__name__) +app.config.from_object(config.Config) +init_errorhandler(app) + +if SITEINV == 'true': + app.logger.info('using siteinventory') + database = siteinventory + tasks.post_osm_vims.delay() +else: + app.logger.info('using sqlite') + sqlite.db.init_app(app) + migrate = Migrate(app, sqlite.db) + database = sqlite @app.route('/nfvo', methods=['GET']) def get_nfvo_list(): try: - nfvo_list = manager.get_nfvo_list( - args={'args': request.args.to_dict()}) - return make_response(jsonify(nfvo_list), 200) + return make_response(jsonify(database.get_nfvo_list()), 200) except Unauthorized as e: abort(401, description=e.description) except ServerError as e: @@ -37,8 +57,7 @@ def get_nfvo_list(): @app.route('/nfvo/', methods=['GET']) def get_nfvo(nfvo_id): try: - nfvo = manager.get_nfvo(nfvo_id, args={'args': request.args.to_dict()}) - return make_response(jsonify(nfvo), 200) + return make_response(jsonify(database.get_nfvo_by_id(nfvo_id)), 200) except Unauthorized as e: abort(401, description=e.description) except NfvoNotFound as e: @@ -50,14 +69,15 @@ def get_nfvo(nfvo_id): @app.route('/nfvo//ns_instances', methods=['POST']) def create_ns(nfvo_id): try: - ns, headers = manager.get_driver(nfvo_id).create_ns( + driver = manager.get_driver(nfvo_id, database) + ns, headers = driver.create_ns( args={'payload': request.json, 'args': request.args.to_dict()}) return make_response(jsonify(ns), 201, headers) except BadRequest as e: abort(400, description=e.description) except Unauthorized as e: abort(401, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except NsdNotFound as e: abort(404, description=e.description) @@ -68,14 +88,15 @@ def create_ns(nfvo_id): @app.route('/nfvo//ns_instances', methods=['GET']) def get_ns_list(nfvo_id): try: - ns_list, headers = manager.get_driver(nfvo_id).get_ns_list( + driver = manager.get_driver(nfvo_id, database) + ns_list, headers = driver.get_ns_list( args={'args': request.args.to_dict()}) return make_response(jsonify(ns_list), 200, headers) except BadRequest as e: abort(400, description=e.description) except Unauthorized as e: abort(401, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except ServerError as e: abort(500, description=e.description) @@ -84,14 +105,15 @@ def get_ns_list(nfvo_id): @app.route('/nfvo//ns_instances/', methods=['GET']) def get_ns(nfvo_id, ns_id): try: - ns, headers = manager.get_driver(nfvo_id).get_ns( + driver = manager.get_driver(nfvo_id, database) + ns, headers = driver.get_ns( ns_id, args={'args': request.args.to_dict()}) return make_response(jsonify(ns), 200, headers) except BadRequest as e: abort(400, description=e.description) except Unauthorized as e: abort(401, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except NsNotFound as e: abort(404, description=e.description) @@ -102,14 +124,15 @@ def get_ns(nfvo_id, ns_id): @app.route('/nfvo//ns_instances/', methods=['DELETE']) def delete_ns(nfvo_id, ns_id): try: - empty_body, headers = manager.get_driver(nfvo_id).delete_ns( + driver = manager.get_driver(nfvo_id, database) + empty_body, headers = driver.delete_ns( ns_id, args={'args': request.args.to_dict()}) return make_response('', 204, headers) except BadRequest as e: abort(400, description=e.description) except Unauthorized as e: abort(401, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except NsNotFound as e: abort(404, description=e.description) @@ -120,14 +143,16 @@ def delete_ns(nfvo_id, ns_id): @app.route('/nfvo//ns_instances//instantiate', methods=['POST']) def instantiate_ns(nfvo_id, ns_id): try: - empty_body, headers = manager.get_driver(nfvo_id). \ - instantiate_ns(ns_id, args={'payload': request.json, 'args': request.args.to_dict()}) + driver = manager.get_driver(nfvo_id, database) + empty_body, headers = driver.instantiate_ns( + ns_id, + args={'payload': request.json, 'args': request.args.to_dict()}) return make_response('', 202, headers) except BadRequest as e: abort(400, description=e.description) except Unauthorized as e: abort(401, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except NsNotFound as e: abort(404, description=e.description) @@ -138,14 +163,16 @@ def instantiate_ns(nfvo_id, ns_id): @app.route('/nfvo//ns_instances//terminate', methods=['POST']) def terminate_ns(nfvo_id, ns_id): try: - empty_body, headers = manager.get_driver(nfvo_id).terminate_ns( - ns_id, args={'payload': request.json, 'args': request.args.to_dict()}) + driver = manager.get_driver(nfvo_id, database) + empty_body, headers = driver.terminate_ns( + ns_id, + args={'payload': request.json, 'args': request.args.to_dict()}) return make_response('', 202, headers) except BadRequest as e: abort(400, description=e.description) except Unauthorized as e: abort(401, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except NsNotFound as e: abort(404, description=e.description) @@ -156,14 +183,16 @@ def terminate_ns(nfvo_id, ns_id): @app.route('/nfvo//ns_instances//scale', methods=['POST']) def scale_ns(nfvo_id, ns_id): try: - empty_body, headers = manager.get_driver(nfvo_id).scale_ns( - ns_id, args={'payload': request.json, 'args': request.args.to_dict()}) + driver = manager.get_driver(nfvo_id, database) + empty_body, headers = driver.scale_ns( + ns_id, + args={'payload': request.json, 'args': request.args.to_dict()}) return make_response('', 202, headers) except BadRequest as e: abort(400, description=e.description) except Unauthorized as e: abort(401, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except NsNotFound as e: abort(404, description=e.description) @@ -174,7 +203,8 @@ def scale_ns(nfvo_id, ns_id): @app.route('/nfvo//ns_lcm_op_occs', methods=['GET']) def get_op_list(nfvo_id): try: - op_list, headers = manager.get_driver(nfvo_id).get_op_list( + driver = manager.get_driver(nfvo_id, database) + op_list, headers = driver.get_op_list( args={'args': request.args.to_dict()}) return make_response(jsonify(op_list), 200, headers) except BadRequest as e: @@ -183,7 +213,7 @@ def get_op_list(nfvo_id): abort(401, description=e.description) except NsNotFound as e: abort(404, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except ServerError as e: abort(500, description=e.description) @@ -192,14 +222,15 @@ def get_op_list(nfvo_id): @app.route('/nfvo//ns_lcm_op_occs/', methods=['GET']) def get_op(nfvo_id, nsLcmOpId): try: - ns_op, headers = manager.get_driver(nfvo_id).get_op( + driver = manager.get_driver(nfvo_id, database) + ns_op, headers = driver.get_op( nsLcmOpId, args={'args': request.args.to_dict()}) return make_response(jsonify(ns_op), 200, headers) except BadRequest as e: abort(400, description=e.description) except Unauthorized as e: abort(401, description=e.description) - except NfvoNotFound as e: + except (NfvoNotFound, NfvoCredentialsNotFound) as e: abort(404, description=e.description) except NsOpNotFound as e: abort(404, description=e.description) @@ -207,5 +238,58 @@ def get_op(nfvo_id, nsLcmOpId): abort(500, description=e.description) +@app.route('/nfvo//subscriptions', methods=['GET']) +def get_subscription_list(nfvo_id): + try: + return make_response(jsonify(database.get_subscription_list(nfvo_id)), + 200) + except Unauthorized as e: + abort(401, description=e.description) + except ServerError as e: + abort(500, description=e.description) + + +@app.route('/nfvo//subscriptions', methods=['POST']) +def create_subscription(nfvo_id): + try: + return make_response( + jsonify(database.create_subscription(nfvo_id, request.json)), 201) + except BadRequest as e: + abort(400, description=e.description) + except ServerError as e: + abort(500, description=e.description) + + +@app.route('/nfvo//subscriptions/', methods=['GET']) +def get_subscription(nfvo_id, subscriptionId): + try: + return make_response( + jsonify(database.get_subscription(nfvo_id, subscriptionId)), 200) + except SubscriptionNotFound as e: + abort(404, description=e.description) + except ServerError as e: + abort(500, description=e.description) + + +@app.route('/nfvo//subscriptions/', methods=['DELETE']) +def delete_subscription(nfvo_id, subscriptionId): + try: + database.delete_subscription(subscriptionId) + return make_response('', 204) + except SubscriptionNotFound as e: + abort(404, description=e.description) + except ServerError as e: + abort(500, description=e.description) + + +@app.route('/nfvo//notifications', methods=['POST']) +def post_notification(nfvo_id): + required = ('nsInstanceId', 'operation', 'operationState') + if not all(k in request.json for k in required): + abort(400, 'One of {0} is missing'.format(str(required))) + tasks.forward_notification.delay(request.json) + return make_response('', 204) + + if __name__ == '__main__': app.run(debug=True, host='0.0.0.0', port=5000) diff --git a/adaptation_layer/config.py b/adaptation_layer/config.py index 04f1f6a..4ca1666 100644 --- a/adaptation_layer/config.py +++ b/adaptation_layer/config.py @@ -13,21 +13,11 @@ # limitations under the License. import os -from flask import Flask -from flask_migrate import Migrate -from flask_sqlalchemy import SQLAlchemy -from error_handler import init_errorhandler class Config(object): basedir = os.path.abspath(os.path.dirname(__file__)) SQLALCHEMY_DATABASE_URI = os.environ.get('DATABASE_URL') or \ - 'sqlite:///' + os.path.join(basedir, 'data/mso-lo.db') + 'sqlite:///' + os.path.join(basedir, + 'data/mso-lo.db') SQLALCHEMY_TRACK_MODIFICATIONS = False - - -app = Flask(__name__) -app.config.from_object(Config) -init_errorhandler(app) -db = SQLAlchemy(app) -migrate = Migrate(app, db) diff --git a/adaptation_layer/driver/manager.py b/adaptation_layer/driver/manager.py index a6ea05e..2baf642 100644 --- a/adaptation_layer/driver/manager.py +++ b/adaptation_layer/driver/manager.py @@ -12,39 +12,19 @@ # See the License for the specific language governing permissions and # limitations under the License. -from error_handler import NfvoNotFound -from models import NFVO, NFVO_CREDENTIALS from .interface import Driver from .onap import ONAP from .osm import OSM -def get_driver(nfvo_id) -> Driver: - nfvo = NFVO.query.filter_by(id=nfvo_id).first() - nfvo_cred = NFVO_CREDENTIALS.query.filter_by(nfvo_id=nfvo_id).first() - if nfvo is None or nfvo_cred is None: - raise NfvoNotFound(nfvo_id=nfvo_id) - nfvo = nfvo.serialize - nfvo_cred = nfvo_cred.serialize - type = nfvo['type'].casefold() - if type == 'osm': +def get_driver(nfvo_id: int, db) -> Driver: + nfvo = db.get_nfvo_by_id(nfvo_id) + nfvo_type = nfvo['type'].casefold() + nfvo_cred = db.get_nfvo_cred(nfvo_id) + if nfvo_type == 'osm': return OSM(nfvo_cred) - elif type == 'onap': + elif nfvo_type == 'onap': return ONAP(nfvo_cred) else: raise NotImplementedError( - 'Driver type: {} is not implemented'.format(type)) - - -def get_nfvo_list(args=None) -> list: - results = NFVO.query.all() - nfvo_list = [result.serialize for result in results] - - return nfvo_list - - -def get_nfvo(nfvo_id, args=None) -> dict: - nfvo = NFVO.query.filter_by(id=nfvo_id).first() - if nfvo is None: - raise NfvoNotFound(nfvo_id=nfvo_id) - return nfvo.serialize + 'Driver type: {} is not implemented'.format(nfvo_type)) diff --git a/adaptation_layer/driver/onap.py b/adaptation_layer/driver/onap.py index 17465b8..c375c01 100644 --- a/adaptation_layer/driver/onap.py +++ b/adaptation_layer/driver/onap.py @@ -197,7 +197,7 @@ def get_op_list(self, args: Dict = None) -> Tuple[BodyList, Headers]: return op_list, headers def get_op(self, nsLcmOpId, args: Dict = None) -> Tuple[Body, Headers]: - _url = '{0}/ns_lcm_op_occs/lcm_id/{1}'.format(self._base_path, nsLcmOpId) + _url = '{0}/ns_lcm_op_occs/{1}'.format(self._base_path, nsLcmOpId) _url = self._build_url_query(_url, args) try: lcm_op, resp_headers = self._exec_get(_url, headers=self._headers) @@ -217,9 +217,9 @@ def _build_headers(self, resp_headers): headers = {} if 'location' in resp_headers: re_res = re.findall( - r"/(ns_db_id|ns_lcm_op_occs)/([A-Za-z0-9\-]+)", resp_headers['location']) + r"/(instances|ns_lcm_op_occs)/([A-Za-z0-9\-]+)", resp_headers['location']) if len(re_res): - if re_res[0][0] == 'ns_db_id': + if re_res[0][0] == 'instances': headers['location'] = '/nfvo/{0}/ns_instances/{1}'.format( self._nfvoId, re_res[0][1]) elif re_res[0][0] == 'ns_lcm_op_occs': diff --git a/adaptation_layer/driver/osm.py b/adaptation_layer/driver/osm.py index 6feb592..53a79e4 100644 --- a/adaptation_layer/driver/osm.py +++ b/adaptation_layer/driver/osm.py @@ -16,6 +16,7 @@ import os import re from datetime import datetime +from functools import wraps from typing import Dict, Tuple, List from urllib.parse import urlencode @@ -30,8 +31,29 @@ from .interface import Driver, Headers, BodyList, Body urllib3.disable_warnings(InsecureRequestWarning) -TESTING = os.environ.get("TESTING", False) -PRISM_ALIAS = os.environ.get("PRISM_ALIAS", "prism-osm") +TESTING = os.getenv('TESTING', 'false').lower() +PRISM_ALIAS = os.getenv("PRISM_ALIAS", "prism-osm") + + +def _authenticate(func): + @wraps(func) + def wrapper(self, *args, **kwargs): + if TESTING == 'true': + pass + elif not self._token or datetime.utcfromtimestamp( + self._token["expires"]) < datetime.utcnow(): + auth_payload = {'username': self._user, + 'password': self._password, + 'project_id': self._project} + token_url = "{0}/{1}".format(self._base_path, + self._token_endpoint) + self._token, headers = self._exec_post(token_url, + json=auth_payload) + self._headers["Authorization"] = 'Bearer {}'.format( + self._token['id']) + return func(self, *args, **kwargs) + + return wrapper class OSM(Driver): @@ -47,14 +69,13 @@ def __init__(self, nfvo_cred): self._project = nfvo_cred["project"] self._headers = {"Content-Type": "application/json", "Accept": "application/json"} - if TESTING is False: - self._base_path = 'https://{0}:{1}/osm'.format( - self._host, self._so_port) - token, headers = self._authenticate() - self._headers['Authorization'] = 'Bearer {}'.format(token['id']) + self._token = None + if TESTING == 'true': + self._base_path = 'http://{0}:{1}/osm'.format(PRISM_ALIAS, + self._so_port) else: - self._base_path = 'http://{0}:{1}/osm'.format( - PRISM_ALIAS, self._so_port) + self._base_path = 'https://{0}:{1}/osm'.format(self._host, + self._so_port) def _exec_get(self, url=None, params=None, headers=None): try: @@ -67,7 +88,7 @@ def _exec_get(self, url=None, params=None, headers=None): return resp.json(), resp.headers elif 'application/yaml' in resp.headers['content-type']: return YAML.load(resp.text, Loader=YAML.SafeLoader), \ - resp.headers + resp.headers else: return resp.text, resp.headers elif resp.status_code == 204: @@ -98,7 +119,7 @@ def _exec_post(self, url=None, data=None, json=None, headers=None): return resp.json(), resp.headers elif 'application/yaml' in resp.headers['content-type']: return YAML.load(resp.text, Loader=YAML.SafeLoader), \ - resp.headers + resp.headers else: return resp.text, resp.headers elif resp.status_code == 204: @@ -129,7 +150,7 @@ def _exec_delete(self, url=None, params=None, headers=None): return resp.json(), resp.headers elif 'application/yaml' in resp.headers['content-type']: return YAML.load(resp.text, Loader=YAML.SafeLoader), \ - resp.headers + resp.headers else: return resp.text, resp.headers elif resp.status_code == 204: @@ -149,18 +170,13 @@ def _exec_delete(self, url=None, params=None, headers=None): error = resp.text raise ServerError(error) - def _authenticate(self): - auth_payload = {'username': self._user, - 'password': self._password, - 'project_id': self._project} - token_url = "{0}/{1}".format(self._base_path, self._token_endpoint) - return self._exec_post(token_url, json=auth_payload) - + @_authenticate def _get_vnf_list(self, args=None): _url = "{0}/nslcm/v1/vnf_instances".format(self._base_path) _url = self._build_url_query(_url, args) return self._exec_get(_url, headers=self._headers) + @_authenticate def _get_vnf(self, vnfId: str, args=None): _url = "{0}/nslcm/v1/vnf_instances/{1}".format(self._base_path, vnfId) _url = self._build_url_query(_url, args) @@ -169,11 +185,13 @@ def _get_vnf(self, vnfId: str, args=None): except ResourceNotFound: raise VnfNotFound(vnf_id=vnfId) - def _get_vim_list(self): + @_authenticate + def get_vim_list(self): _url = "{0}/admin/v1/vims".format(self._base_path) _url = self._build_url_query(_url, None) return self._exec_get(_url, headers=self._headers) + @_authenticate def _get_vnfpkg(self, vnfPkgId, args=None): _url = "{0}/vnfpkgm/v1/vnf_packages/{1}".format( self._base_path, vnfPkgId) @@ -183,6 +201,7 @@ def _get_vnfpkg(self, vnfPkgId, args=None): except ResourceNotFound: raise VnfPkgNotFound(vnfpkg_id=vnfPkgId) + @_authenticate def _get_nsdpkg(self, args=None): _url = "{0}/nsd/v1/ns_descriptors".format(self._base_path) _url = self._build_url_query(_url, args) @@ -195,6 +214,7 @@ def _get_nsdpkg(self, args=None): args["args"]["id"])) return nsdpkg_list[0], headers + @_authenticate def get_ns_list(self, args=None) -> Tuple[BodyList, Headers]: _url = "{0}/nslcm/v1/ns_instances".format(self._base_path) _url = self._build_url_query(_url, args) @@ -205,6 +225,7 @@ def get_ns_list(self, args=None) -> Tuple[BodyList, Headers]: headers = self._build_headers(osm_headers) return sol_ns_list, headers + @_authenticate def create_ns(self, args=None) -> Tuple[Body, Headers]: _url = "{0}/nslcm/v1/ns_instances".format(self._base_path) _url = self._build_url_query(_url, args) @@ -221,6 +242,7 @@ def create_ns(self, args=None) -> Tuple[Body, Headers]: sol_ns, headerz = self.get_ns(osm_ns["id"]) return sol_ns, headers + @_authenticate def get_ns(self, nsId: str, args=None, skip_sol=False) \ -> Tuple[Body, Headers]: _url = "{0}/nslcm/v1/ns_instances/{1}".format(self._base_path, nsId) @@ -235,6 +257,7 @@ def get_ns(self, nsId: str, args=None, skip_sol=False) \ sol_ns = self._ns_im_converter(osm_ns) return sol_ns, headers + @_authenticate def delete_ns(self, nsId: str, args: Dict = None) -> Tuple[None, Headers]: _url = "{0}/nslcm/v1/ns_instances/{1}".format(self._base_path, nsId) _url = self._build_url_query(_url, args) @@ -249,20 +272,39 @@ def delete_ns(self, nsId: str, args: Dict = None) -> Tuple[None, Headers]: headers = self._build_headers(osm_headers) return None, headers + @_authenticate def instantiate_ns(self, nsId: str, args=None) -> Tuple[None, Headers]: _url = "{0}/nslcm/v1/ns_instances/{1}/instantiate".format( self._base_path, nsId) _url = self._build_url_query(_url, args) + instantiate_payload = {} ns_res, ns_head = self.get_ns(nsId, skip_sol=True) - args['payload'] = ns_res['instantiate_params'] + instantiate_payload.update(ns_res['instantiate_params']) + args_payload = args['payload'] + + if args_payload and 'additionalParamsForNs' in args_payload: + instantiate_payload.update(args_payload['additionalParamsForNs']) + if 'vnf' in instantiate_payload: + mapping = {v: str(i+1) for i, + v in enumerate(ns_res['constituent-vnfr-ref'])} + for vnf in instantiate_payload['vnf']: + if vnf.get('vnfInstanceId'): + vnf['member-vnf-index'] = mapping[vnf.pop( + 'vnfInstanceId')] + if 'wim_account' not in instantiate_payload: + instantiate_payload['wimAccountId'] = False + try: empty_body, osm_headers = self._exec_post( - _url, json=args['payload'], headers=self._headers) - except ResourceNotFound: + _url, json=instantiate_payload, headers=self._headers) + except ResourceNotFound as e: + print(e) raise NsNotFound(ns_id=nsId) + headers = self._build_headers(osm_headers) return None, headers + @_authenticate def terminate_ns(self, nsId: str, args=None) -> Tuple[None, Headers]: _url = "{0}/nslcm/v1/ns_instances/{1}/terminate".format( self._base_path, nsId) @@ -275,6 +317,7 @@ def terminate_ns(self, nsId: str, args=None) -> Tuple[None, Headers]: headers = self._build_headers(osm_headers) return None, headers + @_authenticate def scale_ns(self, nsId: str, args=None) -> Tuple[None, Headers]: _url = "{0}/nslcm/v1/ns_instances/{1}/scale".format( self._base_path, nsId) @@ -287,22 +330,18 @@ def scale_ns(self, nsId: str, args=None) -> Tuple[None, Headers]: headers = self._build_headers(osm_headers) return None, headers + @_authenticate def get_op_list(self, args: Dict = None) -> Tuple[BodyList, Headers]: - nsId = args['args']['nsInstanceId'] \ - if args['args'] and 'nsInstanceId' in args['args'] else None _url = "{0}/nslcm/v1/ns_lcm_op_occs".format(self._base_path) _url = self._build_url_query(_url, args) - try: - osm_op_list, osm_headers = self._exec_get( - _url, headers=self._headers) - except ResourceNotFound: - raise NsNotFound(ns_id=nsId) + osm_op_list, osm_headers = self._exec_get(_url, headers=self._headers) sol_op_list = [] for op in osm_op_list: sol_op_list.append(self._op_im_converter(op)) headers = self._build_headers(osm_headers) return sol_op_list, headers + @_authenticate def get_op(self, nsLcmOpId, args: Dict = None) -> Tuple[Body, Headers]: _url = "{0}/nslcm/v1/ns_lcm_op_occs/{1}".format( self._base_path, nsLcmOpId) @@ -336,6 +375,7 @@ def _cpinfo_converter(self, osm_vnf: Dict) -> List[Dict]: (ip_address, mac_address) = (None, None) cp_info.append({ "id": cp, + "cpdId": cp, "cpProtocolInfo": [ { "layerProtocol": "IP_OVER_ETHERNET", @@ -354,7 +394,7 @@ def _cpinfo_converter(self, osm_vnf: Dict) -> List[Dict]: return cp_info def _select_vim(self): - osm_vims, osm_vim_h = self._get_vim_list() + osm_vims, osm_vim_h = self.get_vim_list() if osm_vims and len(osm_vims) > 0: return osm_vims[0]['_id'] else: @@ -384,7 +424,8 @@ def _ns_im_converter(self, osm_ns: Dict) -> Dict: "id": osm_vnf["id"], "vnfdId": osm_vnf["vnfd-ref"], "vnfProductName": "", - "vimId": osm_vnf["vim-account-id"], + "vimId": osm_vnf["vim-account-id"] if osm_vnf["vim-account-id"] + else '', # same as the NS "instantiationState": osm_ns['_admin']['nsState'], } diff --git a/adaptation_layer/error_handler.py b/adaptation_layer/error_handler.py index 3ae5f8a..132af3f 100644 --- a/adaptation_layer/error_handler.py +++ b/adaptation_layer/error_handler.py @@ -43,6 +43,12 @@ def __init__(self, nfvo_id): self.description = 'NFVO {0} not found.'.format(nfvo_id) +class NfvoCredentialsNotFound(Error): + def __init__(self, nfvo_id): + self.description = 'NFVO credentials not found for NFVO id {0}.' \ + .format(nfvo_id) + + class ResourceNotFound(Error): def __init__(self): self.description = 'Resource not found.' @@ -78,6 +84,11 @@ def __init__(self, vnfpkg_id=None): self.description = 'VNF package {0} not found.'.format(vnfpkg_id) +class SubscriptionNotFound(Error): + def __init__(self, sub_id=None): + self.description = 'Subscription {0} not found.'.format(sub_id) + + class Unauthorized(Error): def __init__(self): self.description = 'Unauthorized' diff --git a/adaptation_layer/manage.py b/adaptation_layer/manage.py index a761c77..8f04202 100644 --- a/adaptation_layer/manage.py +++ b/adaptation_layer/manage.py @@ -17,8 +17,8 @@ from flask_script import Manager -from config import app, db -from models import NFVO, NFVO_CREDENTIALS +from app import app +from sqlite import db, NFVO, NFVO_CREDENTIALS manager = Manager(app) basedir = os.path.abspath(os.path.dirname(__file__)) diff --git a/adaptation_layer/robotframework/.gitignore b/adaptation_layer/robotframework/.gitignore new file mode 100644 index 0000000..b29a90d --- /dev/null +++ b/adaptation_layer/robotframework/.gitignore @@ -0,0 +1,4 @@ +/output +log.html +report.html +output.xml diff --git a/adaptation_layer/robotframework/LICENSE b/adaptation_layer/robotframework/LICENSE new file mode 100644 index 0000000..c7a6ba0 --- /dev/null +++ b/adaptation_layer/robotframework/LICENSE @@ -0,0 +1,23 @@ +Copyright 2019 ETSI + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/adaptation_layer/robotframework/MSO-LO-LCM-Workflow.robot b/adaptation_layer/robotframework/MSO-LO-LCM-Workflow.robot new file mode 100644 index 0000000..18f5e6c --- /dev/null +++ b/adaptation_layer/robotframework/MSO-LO-LCM-Workflow.robot @@ -0,0 +1,169 @@ +*** Settings *** +Resource environment/variables.txt +Variables ${SCHEMAS_PATH} +Resource NSLCMOperationKeywords.robot +Resource NFVOOperationKeywords.robot +Library REST ${MSO-LO_BASE_API} +Library OperatingSystem +Library JSONLibrary +Library JSONSchemaLibrary schemas/ + + +*** Test Cases *** +POST NS Instance Creation + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.2 + ... Test title: POST NS Instance Creation + ... Test objective: The objective is to test the workflow for Creating a NS instance + ... Pre-conditions: none + ... Post-Conditions: The NS lifecycle management operation occurrence is in NOT_ISTANTIATED state + POST New nsInstance + Check HTTP Response Status Code Is 201 + Check HTTP Response Header Contains Location + Check HTTP Response Body Json Schema Is ${ns_schema} + Check NS Id + Check VNF Ids + Check resource not_instantiated + POST New Subscription Good + Check Sub Id + +GET NS Instance List + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.1 + ... Test title: GET NS Instance List + ... Test objective: The objective is to test the workflow for retriving the NS instance list + ... Pre-conditions: none + ... Post-Conditions: none + GET NsInstances + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${ns_list_schema} + +GET Information about an individual NS Instance + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.3 + ... Test title: GET Information about an individual NS Instance + ... Test objective: The objective is to test that GET method returns an individual NS instance + ... Pre-conditions: none + ... Post-Conditions: none + GET IndividualNSInstance + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${ns_schema} + +POST NS Instance Instantiate + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.4 + ... Test title: POST NS Instance Instantiate + ... Test objective: The objective is to test the workflow for Instantiate a NS instance + ... Pre-conditions: the resource is in NOT_INSTANTIATED state + ... Post-Conditions: status code 202 + Check resource existence + Check resource not_instantiated + POST Instantiate nsInstance with vnf in additionalParamsForNs + Check HTTP Response Status Code Is 202 + Check HTTP Response Header Contains Location + Check Operation Occurrence Id + +GET NS LCM OP Occurrence Instantiate PROCESSING + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.5 + ... Test title: GET NS LCM OP Occurrence Instantiate PROCESSING + ... Test objective: The objective is to test the workflow for retrive NS LCM OP Occurrence + ... Pre-conditions: none + ... Post-Conditions: status code 200 + GET Individual NS LCM OP Occurrence + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${ns_lcm_op_occ_schema} + Check resource lcmOperationType is INSTANTIATE + Check resource operationState is PROCESSING + +GET NS LCM OP Occurrence Instantiate COMPLETED + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.6 + ... Test title: GET NS LCM OP Occurrence Instantiate COMPLETED + ... Test objective: The objective is to test the workflow for retrive NS LCM OP Occurrence + ... Pre-conditions: none + ... Post-Conditions: status code 200 + Wait Until Keyword Succeeds ${MAX_WAIT} ${INTERVAL_WAIT} Run Keywords + ... GET Individual NS LCM OP Occurrence + ... AND Check resource operationState is COMPLETED + +GET NS LCM OP Occurrences + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.5 + ... Test title: GET LCM OP Occurrences + ... Test objective: The objective is to test the workflow for retrive NS LCM OP Occurrences + ... Pre-conditions: none + ... Post-Conditions: status code 200 + GET NS LCM OP Occurrences + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${ns_lcm_op_occ_list_schema} + +POST NS Instance Terminate + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3. + ... Test title: POST Terminate NS Instance + ... Test objective: The objective is to test the workflow for Terminate a NS instance + ... Pre-conditions: the resource is in INSTANTIATED state + ... Post-Conditions: the resource is in NOT_INSTANTIATED state + Check resource existence + Check resource instantiated + POST Terminate NSInstance + Check HTTP Response Status Code Is 202 + Check Operation Occurrence Id + +GET NS LCM OP Occurrence Terminate PROCESSING + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.5 + ... Test title: GET NS LCM OP Occurrence Terminate PROCESSING + ... Test objective: The objective is to test the workflow for retrive NS LCM OP Occurrence + ... Pre-conditions: none + ... Post-Conditions: status code 200 + GET Individual NS LCM OP Occurrence + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${ns_lcm_op_occ_schema} + Check resource lcmOperationType is TERMINATE + Check resource operationState is PROCESSING + +GET NS LCM OP Occurrence Terminate COMPLETED + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.5 + ... Test title: GET NS LCM OP Occurrence Terminate COMPLETED + ... Test objective: The objective is to test the workflow for retrive NS LCM OP Occurrence + ... Pre-conditions: none + ... Post-Conditions: status code 200 + Wait Until Keyword Succeeds ${MAX_WAIT} ${INTERVAL_WAIT} Run Keywords + ... GET Individual NS LCM OP Occurrence + ... AND Check resource operationState is COMPLETED + +POST NS Instance Delete + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.6 + ... Test title: POST NS Instance Delete + ... Test objective: The objective is to test the workflow for Deleting a NS instance + ... Pre-conditions: the resource is in NOT_INSTANTIATED state + ... Post-Conditions: status code 204 + Sleep 5s + Check resource not_instantiated + DELETE IndividualNSInstance + Check HTTP Response Status Code Is 204 + DELETE Individual Subscription Good + +POST NS Instance Creation Bad Request + [Tags] standalone + [Documentation] Test ID: mso-lo-test-3.2.1 + ... Test title: POST Instance Creation Bad Request + ... Test objective: The objective is to test the workflow for Creating a NS instance with a bad request + ... Pre-conditions: none + ... Post-Conditions: Status code 400 + POST New nsInstance with invalid request body + Check HTTP Response Status Code Is 400 + +GET Information about an inexistent individual NS Instance + [Tags] standalone + [Documentation] Test ID: mso-lo-test-3.3.1 + ... Test title: GET Information about an inexistent individual NS Instance + ... Test objective: The objective is to test that GET method returns an inexistent individual NS instance + ... Pre-conditions: none + ... Post-Conditions: Status code 404 + GET IndividualNSInstance inexistent + Check HTTP Response Status Code Is 404 diff --git a/adaptation_layer/robotframework/MSO-LO-NFVO-Workflow.robot b/adaptation_layer/robotframework/MSO-LO-NFVO-Workflow.robot new file mode 100644 index 0000000..2fbe0e8 --- /dev/null +++ b/adaptation_layer/robotframework/MSO-LO-NFVO-Workflow.robot @@ -0,0 +1,41 @@ +*** Settings *** +Resource environment/variables.txt +Variables ${SCHEMAS_PATH} +Resource NSLCMOperationKeywords.robot +Resource NFVOOperationKeywords.robot +Library REST ${MSO-LO_BASE_API} +Library OperatingSystem +Library JSONLibrary +Library JSONSchemaLibrary schemas/ + + +*** Test Cases *** + +GET NFVO List + [Documentation] Test ID: mso-lo-test- + ... Test title: GET NFVO List + ... Test objective: The objective is to test the workflow for retriving the NFVO list + ... Pre-conditions: none + ... Post-Conditions: none + GET NFVO List + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${nfvo_list_schema} + +GET Individual NFVO informations + [Documentation] Test ID: mso-lo-test- + ... Test title: GET Individual NFVO informations + ... Test objective: The objective is to test the workflow for retriving the NFVO informations + ... Pre-conditions: none + ... Post-Conditions: none + GET IndividualNFVO + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${nfvo_schema} + +GET Individual NFVO informations inexistent + [Documentation] Test ID: mso-lo-test- + ... Test title: GET Individual NFVO informations inexistent + ... Test objective: The objective is to test the workflow for retriving the NFVO informations + ... Pre-conditions: none + ... Post-Conditions: none + GET IndividualNFVO inexistent + Check HTTP Response Status Code Is 404 diff --git a/adaptation_layer/robotframework/MSO-LO-SUBS.robot b/adaptation_layer/robotframework/MSO-LO-SUBS.robot new file mode 100644 index 0000000..5633702 --- /dev/null +++ b/adaptation_layer/robotframework/MSO-LO-SUBS.robot @@ -0,0 +1,69 @@ +*** Settings *** +Resource environment/variables.txt +Variables ${SCHEMAS_PATH} +Resource NSLCMOperationKeywords.robot +Library REST ${MSO-LO_BASE_API} +Library OperatingSystem +Library JSONLibrary +Library JSONSchemaLibrary schemas/ + + +*** Test Cases *** + +GET Subscription List 200 + [Documentation] Test ID: mso-lo-test- + ... Test title: GET Subscription List + GET Subscriptions + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${subscription_list_schema} + +POST Subscription Creation 201 + [Documentation] Test ID: mso-lo-test- + ... Test title: POST Subscription Creation 201 + POST New Subscription Good + Check HTTP Response Status Code Is 201 + Check HTTP Response Body Json Schema Is ${subscription_schema} + Check Sub Id + +POST Subscription Creation 400 + [Documentation] Test ID: mso-lo-test- + ... Test title: POST Subscription Creation 400 + POST New Subscription Bad + Check HTTP Response Status Code Is 400 + +GET Individual Subscription information 200 + [Documentation] Test ID: mso-lo-test- + ... Test title: GET Individual Subscription information 200 + GET Individual Subscription Good + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${subscription_schema} + +GET Individual Subscription information 404 + [Documentation] Test ID: mso-lo-test- + ... Test title: GET Individual Subscription information 404 + GET Individual Subscription Not Found + Check HTTP Response Status Code Is 404 + +DELETE Individual Subscription information 204 + [Documentation] Test ID: mso-lo-test- + ... Test title: DELETE Individual Subscription information 200 + DELETE Individual Subscription Good + Check HTTP Response Status Code Is 204 + +DELETE Individual Subscription information 404 + [Documentation] Test ID: mso-lo-test- + ... Test title: DELETE Individual Subscription information 404 + DELETE Individual Subscription Not Found + Check HTTP Response Status Code Is 404 + +POST Notification Creation 204 + [Documentation] Test ID: mso-lo-test- + ... Test title: POST Notification Creation 204 + POST New Notification Good + Check HTTP Response Status Code Is 204 + +POST Notification Creation 400 + [Documentation] Test ID: mso-lo-test- + ... Test title: POST Subscription Creation 400 + POST New Notification Bad + Check HTTP Response Status Code Is 400 diff --git a/adaptation_layer/robotframework/NFVOOperationKeywords.robot b/adaptation_layer/robotframework/NFVOOperationKeywords.robot new file mode 100644 index 0000000..e0cd173 --- /dev/null +++ b/adaptation_layer/robotframework/NFVOOperationKeywords.robot @@ -0,0 +1,37 @@ +*** Settings *** +Resource environment/variables.txt +Library REST ${MSO-LO_BASE_API} +Library JSONLibrary +Library Process +Library JSONSchemaLibrary schemas/ +Library OperatingSystem + + +*** Keywords *** + +GET NFVO List + Log Retrive NFVO list + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET IndividualNFVO + Log Retrive NFVO + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET IndividualNFVO inexistent + Log Retrive NFVO + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoIdinexistent} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} diff --git a/adaptation_layer/robotframework/NSLCMOperationKeywords.robot b/adaptation_layer/robotframework/NSLCMOperationKeywords.robot new file mode 100644 index 0000000..ae9984b --- /dev/null +++ b/adaptation_layer/robotframework/NSLCMOperationKeywords.robot @@ -0,0 +1,387 @@ +*** Settings *** +Resource environment/variables.txt +Library REST ${MSO-LO_BASE_API} +Library JSONLibrary +Library Process +Library jsonschema +Library OperatingSystem + +*** Keywords *** + +Check NS Id + Set Global Variable ${nsInstanceId} ${response[0]['body']['id']} + Should Not Be Empty ${nsInstanceId} + +Check VNF Ids + ${vnfInstances}= set variable ${response[0]['body']['vnfInstance']} + ${vnfInstances}= evaluate [v['id'] for v in ${vnfInstances}] + Set Global Variable ${vnfInstanceIds} ${vnfInstances} + Log ${vnfInstances} + Should Not Be Empty ${vnfInstanceIds} + +Check Operation Occurrence Id + ${nsLcmOpOcc}= set variable ${response[0]['headers']['Location']} + # using a basic regex, it can be improved + ${nsLcmOpOcc}= evaluate re.search(r'(/nfvo/.*/ns_lcm_op_occs/(.*))', '''${nsLcmOpOcc}''').group(2) re + Set Global Variable ${nsLcmOpOccId} ${nsLcmOpOcc} + Should Not Be Empty ${nsLcmOpOccId} + Log ${nsLcmOpOccId} + +Check resource FAILED_TEMP + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_lcm_op_occs/${nsLcmOpOccId} + String response body operationState FAILED_TEMP + +Check resource operationState is not + [Arguments] ${state} + Should Be Not Equal As Strings ${response[0]['body']['operationState']} ${state} + Log State is ${state} + +Check resource operationState is + [Arguments] ${state} + Should Be Equal As Strings ${response[0]['body']['operationState']} ${state} + Log State is ${state} + +Check resource lcmOperationType is + [Arguments] ${type} + Should Be Equal As Strings ${response[0]['body']['lcmOperationType']} ${type} + Log Type is ${type} + +Check Operation Notification Status is + [Arguments] ${status} + Check Operation Notification NsLcmOperationOccurrenceNotification ${status} + +Check resource instantiated + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId} + String response body nsState INSTANTIATED + +Check resource not_instantiated + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId} + String response body nsState NOT_INSTANTIATED + +Check operation resource state is FAILED_TEMP + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId} + String response body nsState FAILED_TEMP +Check operation resource state is not FAILED_TEMP + Check operation resource state is FAILED_TEMP + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId} + String response body nsState not FAILED_TEMP + +Check resource is finally failed + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId} + String response body nsState FINALLY_FAILED + +Launch another LCM operation + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/scaleNsToLevelRequest.json + Post ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId}/scale_to_level ${body} + Integer response status 202 + +Check resource existence + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId} + Integer response status 200 + +Check HTTP Response Status Code Is + [Arguments] ${expected_status} + Log Validate Status code + Should Be Equal as Strings ${response[0]['status']} ${expected_status} + Log Status code validated + +Check HTTP Response Header Contains + [Arguments] ${HEADER_TOCHECK} + Should Contain ${response[0]['headers']} ${HEADER_TOCHECK} + Log Header is present + +Check HTTP Response Body Json Schema Is + [Arguments] ${input_schema} + validate instance=${response[0]['body']} schema=${input_schema} + Log ${response[0]['body']} + Log Json Schema Validation OK + +Check HTTP Response Header ContentType is + [Arguments] ${expected_contentType} + Log Validate content type + Should Be Equal as Strings ${response[0]['headers']['Content-Type']} ${expected_contentType} + Log Content Type validated + +POST New nsInstance + Log Create NS instance by POST to ${apiRoot}/${nfvoId}/ns_instances + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/CreateNsRequest.json + ${body}= Update Value To Json ${body} $.nsdId ${nsdId} + Post ${apiRoot}/${nfvoId}/ns_instances ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST New nsInstance with invalid request body + Log Create NS instance by POST to ${apiRoot}/${nfvoId}/ns_instances with bad request body + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Post ${apiRoot}/${nfvoId}/ns_instances + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET NsInstances + Log Query NS The GET method queries information about multiple NS instances. + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET IndividualNSInstance + Log Trying to get information about an individual NS instance + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET IndividualNSInstance inexistent + Log Trying to get information about an individual NS instance + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceIdinexistent} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +DELETE IndividualNSInstance + log Trying to delete an individual NS instance + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Delete ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Instantiate nsInstance with vnf in additionalParamsForNs + Log Trying to Instantiate a ns Instance + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/InstantiateNsRequest.json + ${vnf}= set variable [] + ${vnf}= evaluate [{"vnfInstanceId": vnfId,"vimAccountId": random.choice(${vimAccountIds})} for vnfId in ${vnfInstanceIds}] random + Log ${vnf} + ${body}= Update Value To Json ${body} $.additionalParamsForNs.vnf ${vnf} + Log ${body} + Post ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId}/instantiate ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Instantiate nsInstance + Log Trying to Instantiate a ns Instance + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/InstantiateNsRequest.json + Post ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId}/instantiate ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST scale nsInstance + Log Trying to Instantiate a scale NS Instance + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/ScaleNsRequest.json + Post ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId}/scale ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Update NSInstance + Log Trying to Instantiate a Update NS Instance + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/UpdateNsRequest.json + Post ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId}/update ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Heal NSInstance + Log Trying to Instantiate a Heal NS Instance + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/HealNsRequest.json + Post ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId}/heal ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Terminate NSInstance + Log Trying to Instantiate a Terminate NS Instance + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/TerminateNsRequest.json + Post ${apiRoot}/${nfvoId}/ns_instances/${nsInstanceId}/terminate ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET NS LCM OP Occurrences + Log Query status information about multiple NS lifecycle management operation occurrences. + Set Headers {"Accept":"${ACCEPT}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Log Execute Query and validate response + Get ${apiRoot}/${nfvoId}/ns_lcm_op_occs + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET Individual NS LCM OP Occurrence + Log Query status information about individual NS lifecycle management operation occurrence. + Set Headers {"Accept":"${ACCEPT}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Log Execute Query and validate response + Get ${apiRoot}/${nfvoId}/ns_lcm_op_occs/${nsLcmOpOccId} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Retry operation task + Log Retry a NS lifecycle operation if that operation has experienced a temporary failure + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Log Execute Query and validate response + Post ${apiRoot}/${nfvoId}/ns_lcm_op_occs/${nsLcmOpOccId}/retry + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Rollback operation task + Log Rollback a NS lifecycle operation task + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Log Execute Query and validate response + Post ${apiRoot}/${nfvoId}/ns_lcm_op_occs/${nsLcmOpOccId}/rollback + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Continue operation task + Log Continue a NS lifecycle operation task + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Log Execute Query and validate response + Post ${apiRoot}/${nfvoId}/ns_lcm_op_occs/${nsLcmOpOccId}/continue + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Fail operation task + Log Fail a NS lifecycle operation task + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Log Execute Query and validate response + Post ${apiRoot}/${nfvoId}/ns_lcm_op_occs/${nsLcmOpOccId}/fail + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST Cancel operation task + Log Cancel a NS lifecycle operation task + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Log Execute Query and validate response + Post ${apiRoot}/${nfvoId}/ns_lcm_op_occs/${nsLcmOpOccId}/cancel + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +Check Sub Id + Set Global Variable ${subscriptionId} ${response[0]['body']['id']} + +GET Subscriptions + Log Get the list of active subscriptions + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Log Execute Query and validate response + Get ${apiRoot}/${nfvoId}/subscriptions + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST New Subscription Good + Log Create subscription instance by POST to ${apiRoot}/${nfvoId}/subscriptions + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/LccnSubscriptionRequest.json + ${body}= Update Value To Json ${body} $.nsInstanceId ${nsInstanceId} + ${body}= Update Value To Json ${body} $.callbackUri ${notificationUri} + Post ${apiRoot}/${nfvoId}/subscriptions ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST New Subscription Bad + Log Create subscription instance by POST to ${apiRoot}/${nfvoId}/subscriptions + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Post ${apiRoot}/${nfvoId}/subscriptions {"bad": "request"} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET Individual Subscription Good + log Trying to get information about an individual subscription + Set Headers {"Accept":"${ACCEPT}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/subscriptions/${subscriptionId} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +GET Individual Subscription Not Found + log Trying to get information about an individual subscription + Set Headers {"Accept":"${ACCEPT}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Get ${apiRoot}/${nfvoId}/subscriptions/-1 + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +DELETE Individual Subscription Good + log Try to delete an individual subscription + Set Headers {"Accept":"${ACCEPT}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Delete ${apiRoot}/${nfvoId}/subscriptions/${subscriptionId} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +DELETE Individual Subscription Not Found + log Try to delete an individual subscription + Set Headers {"Accept":"${ACCEPT}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Delete ${apiRoot}/${nfvoId}/subscriptions/-1 + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST New Notification Good + Log Create notification instance by POST to ${apiRoot}/${nfvoId}/notifications + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + ${body}= Load JSON From File jsons/Notification.json + Post ${apiRoot}/${nfvoId}/notifications ${body} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} + +POST New Notification Bad + Log Create subscription instance by POST to ${apiRoot}/${nfvoId}/notifications + Set Headers {"Accept":"${ACCEPT}"} + Set Headers {"Content-Type": "${CONTENT_TYPE}"} + Run Keyword If ${AUTH_USAGE} == 1 Set Headers {"Authorization":"${AUTHORIZATION}"} + Post ${apiRoot}/${nfvoId}/notifications {"bad": "request"} + ${outputResponse}= Output response + Set Global Variable @{response} ${outputResponse} diff --git a/adaptation_layer/robotframework/README.md b/adaptation_layer/robotframework/README.md new file mode 100644 index 0000000..7009ce0 --- /dev/null +++ b/adaptation_layer/robotframework/README.md @@ -0,0 +1,36 @@ +## Tests with Robot Framework + +The `.robot` scripts in this folder perform integration tests of the `mso-lo` application +against a real instance of a NFVO. + +To execute the tests you need: + +- An instance of `mso-lo` up and running +- An instance of a real NFVO +- Correct communication between the two above (check your database) + +Check the file [variables.txt](./environment/variables.txt) and change the variables according +to your setup. The important variables to change are: + +- `${MSO-LO_HOST}` +- `${MSO-LO_PORT}` +- `${nfvoId}` +- `${nsdId}` (this is the NSD to be instantiated during the tests, pick something simple) + +Example of execution: + +```shell script +$ cd {project-dir}/adaptation_layer +# activate the virtual env +$ pipenv shell +$ cd ./robotframework +# export mso-lo openapi file path relative to this directory +$ export OPENAPI_PATH=../../openapi/MSO-LO-swagger-resolved.yaml +$ robot --outputdir output MSO-LO-LCM-Workflow.robot MSO-LO-NFVO-Workflow.robot +``` + +Script [MSO-LO-LCM-Workflow.robot](./MSO-LO-LCM-Workflow.robot) contains a workflow that +instantiates and terminates a Network Service on the configured NFVO. +Some test cases include features to wait for the NS to be deployed. Maximum waiting time is set to +2 minutes. You can increase it if it is not enough by changing `${MAXIMUM_WAIT}` variable in +[variables.txt](./environment/variables.txt). diff --git a/adaptation_layer/robotframework/environment/variables.txt b/adaptation_layer/robotframework/environment/variables.txt new file mode 100644 index 0000000..0b65446 --- /dev/null +++ b/adaptation_layer/robotframework/environment/variables.txt @@ -0,0 +1,47 @@ +*** Variables *** +${MSO-LO_HOST} localhost # Hostname of the MSO-LO +${MSO-LO_PORT} 80 # Listening port of the MSO-LO +${HTTP} http +${MSO-LO_BASE_API} ${HTTP}://${MSO-LO_HOST}:${MSO-LO_PORT} +${SCHEMAS_PATH} ../tests/response_schemas.py + +${AUTHORIZATION} Bearer QWxhZGRpbjpvcGVuIHNlc2FtZQ== +${CONTENT_TYPE} application/json +${CONTENT_TYPE_PATCH} application/merge-patch+json +${ACCEPT_JSON} application/json +${ACCEPT} application/json +${apiRoot} nfvo +${nfvoId} 1 +${nfvoIdinexistent} 80 +${AUTH_USAGE} 1 +${WRONG_AUTHORIZATION} Bearer XXXXXWRONGXXXXX +${MAX_WAIT} 2 min +${INTERVAL_WAIT} 2 s +${nsdId} pingpong +${nsInstanceId} xxxxxxxxx-e602-4afa-8e13-962fb5a7d81f +${nsInstanceIdinexistent} 6fc3539c-e602-4afa-8e13-962fb5a7d81xxxxxxx +${ConflictNsInstanceId} 007c111c-e602-4afa-8e13-962fb5a7d81d +${nsInstanceName} Test-nsInstance +${nsInstanceDescription} description ns +${vnfInstanceIds} [] +${vimAccountIds} ['e59a8dfd-dd21-42cc-98f7-a68c83cfbdbd', '77c531ef-ff08-42a3-aeb9-3b8eae19a32f'] +${notificationUri} http://192.168.17.20:8083/ +${SINGLE_FILE_VNFD} 1 # If VNFD is PLAIN TEXT +${ACCEPT_PLAIN} text/plain +${ACCEPT_ZIP} application/zip +${nsPkgId_processing} 007c111c-38a1-42c0-a666-7475ecb1567c +${ARTIFACT_TYPE} application/octet-stream +${ARTIFACT_ID} artifactId +${WRONG_ACCEPT} application/json +${nsLcmOpOccId} 6fc3539c-e602-4afa-8e13-962fb5a7d81d +${CancelMode} GRACEFUL +${NFVO_DUPLICATION} 0 +${sub_filter} filter +${sub_filter_invalid} filter_invalid +${fields} criteria,objectInstanceIds + +${response} {} + + +${NEG_FILTER} attribute_not_exist=some_value +${NEG_SELECTOR} fields=wrong_field diff --git a/adaptation_layer/robotframework/jsons/CreateNsRequest.json b/adaptation_layer/robotframework/jsons/CreateNsRequest.json new file mode 100644 index 0000000..b347230 --- /dev/null +++ b/adaptation_layer/robotframework/jsons/CreateNsRequest.json @@ -0,0 +1,5 @@ +{ + "nsdId": "", + "nsName": "robot_ns_test", + "nsDescription": "Ns robotframework test" +} diff --git a/adaptation_layer/robotframework/jsons/HealNsRequest.json b/adaptation_layer/robotframework/jsons/HealNsRequest.json new file mode 100644 index 0000000..2922dc4 --- /dev/null +++ b/adaptation_layer/robotframework/jsons/HealNsRequest.json @@ -0,0 +1,3 @@ +{ + "degreeHealing": "HEAL_RESTORE" +} \ No newline at end of file diff --git a/adaptation_layer/robotframework/jsons/InstantiateNsRequest.json b/adaptation_layer/robotframework/jsons/InstantiateNsRequest.json new file mode 100644 index 0000000..3bc00a5 --- /dev/null +++ b/adaptation_layer/robotframework/jsons/InstantiateNsRequest.json @@ -0,0 +1,6 @@ +{ + + "additionalParamsForNs": { + "vnf": [] + } +} diff --git a/adaptation_layer/robotframework/jsons/LccnSubscriptionRequest.json b/adaptation_layer/robotframework/jsons/LccnSubscriptionRequest.json new file mode 100644 index 0000000..02655c2 --- /dev/null +++ b/adaptation_layer/robotframework/jsons/LccnSubscriptionRequest.json @@ -0,0 +1,9 @@ +{ + "callbackUri": "http://localhost:9097/", + "nsInstanceId": "45f95003-4dd1-4e20-87cf-4373c9f4e946", + "notificationTypes": [ + "NsLcmOperationOccurrenceNotification", + "NsIdentifierDeletionNotification", + "NsIdentifierCreationNotification" + ] +} \ No newline at end of file diff --git a/adaptation_layer/robotframework/jsons/Notification.json b/adaptation_layer/robotframework/jsons/Notification.json new file mode 100644 index 0000000..9852695 --- /dev/null +++ b/adaptation_layer/robotframework/jsons/Notification.json @@ -0,0 +1,8 @@ +{ + "nsInstanceId": "45f95003-4dd1-4e20-87cf-4373c9f4e946", + "nsLcmOpOccId": "45f95003-4dd1-4e20-87cf-4373c9f4e948", + "operation": "INSTANTIATE", + "notificationType": "NsLcmOperationOccurrenceNotification", + "timestamp": "2020-05-27T08:57:01+0000", + "operationState": "PROCESSING" +} \ No newline at end of file diff --git a/adaptation_layer/robotframework/jsons/TerminateNsRequest.json b/adaptation_layer/robotframework/jsons/TerminateNsRequest.json new file mode 100644 index 0000000..a65d7d3 --- /dev/null +++ b/adaptation_layer/robotframework/jsons/TerminateNsRequest.json @@ -0,0 +1,3 @@ +{ + "terminationTime": "2021-04-12T23:20:50.52Z" +} \ No newline at end of file diff --git a/adaptation_layer/robotframework/tests.robot b/adaptation_layer/robotframework/tests.robot new file mode 100644 index 0000000..120b959 --- /dev/null +++ b/adaptation_layer/robotframework/tests.robot @@ -0,0 +1,103 @@ +*** Settings *** +Resource environment/variables.txt +Variables ${SCHEMAS_PATH} +Resource NSLCMOperationKeywords.robot +Resource NFVOOperationKeywords.robot +Library REST ${MSO-LO_BASE_API} +Library OperatingSystem +Library JSONLibrary +Library JSONSchemaLibrary schemas/ + +*** Test Cases *** + +*** Test Cases *** +POST NS Instance Creation + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.2 + ... Test title: POST NS Instance Creation + ... Test objective: The objective is to test the workflow for Creating a NS instance + ... Pre-conditions: none + ... Post-Conditions: The NS lifecycle management operation occurrence is in NOT_ISTANTIATED state + POST New nsInstance + Check HTTP Response Status Code Is 201 + Check HTTP Response Header Contains Location + Check HTTP Response Body Json Schema Is ${ns_schema} + Check NS Id + Check VNF Ids + Check resource not_instantiated + +GET NS Instance List + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.1 + ... Test title: GET NS Instance List + ... Test objective: The objective is to test the workflow for retriving the NS instance list + ... Pre-conditions: none + ... Post-Conditions: none + GET NsInstances + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${ns_list_schema} + +GET Information about an individual NS Instance + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.3 + ... Test title: GET Information about an individual NS Instance + ... Test objective: The objective is to test that GET method returns an individual NS instance + ... Pre-conditions: none + ... Post-Conditions: none + GET IndividualNSInstance + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${ns_schema} + +POST NS Instance Instantiate + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.4 + ... Test title: POST NS Instance Instantiate + ... Test objective: The objective is to test the workflow for Instantiate a NS instance + ... Pre-conditions: the resource is in NOT_INSTANTIATED state + ... Post-Conditions: status code 202 + Check resource existence + Check resource not_instantiated + POST Instantiate nsInstance with vnf in additionalParamsForNs + Check HTTP Response Status Code Is 202 + Check HTTP Response Header Contains Location + Check Operation Occurrence Id + + +POST NS Instance Terminate + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3. + ... Test title: POST Terminate NS Instance + ... Test objective: The objective is to test the workflow for Terminate a NS instance + ... Pre-conditions: the resource is in INSTANTIATED state + ... Post-Conditions: the resource is in NOT_INSTANTIATED state + Check resource existence + Check resource instantiated + POST Terminate NSInstance + Check HTTP Response Status Code Is 202 + Check Operation Occurrence Id + +GET NS LCM OP Occurrence Terminate PROCESSING + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.5 + ... Test title: GET NS LCM OP Occurrence Terminate PROCESSING + ... Test objective: The objective is to test the workflow for retrive NS LCM OP Occurrence + ... Pre-conditions: none + ... Post-Conditions: status code 200 + GET Individual NS LCM OP Occurrence + Check HTTP Response Status Code Is 200 + Check HTTP Response Body Json Schema Is ${ns_lcm_op_occ_schema} + Check resource lcmOperationType is TERMINATE + Check resource operationState is PROCESSING + + + +POST NS Instance Delete + [Tags] instantiate-terminate-workflow + [Documentation] Test ID: mso-lo-test-3.6 + ... Test title: POST NS Instance Delete + ... Test objective: The objective is to test the workflow for Deleting a NS instance + ... Pre-conditions: the resource is in NOT_INSTANTIATED state + ... Post-Conditions: status code 204 + Check resource not_instantiated + DELETE IndividualNSInstance + Check HTTP Response Status Code Is 204 diff --git a/adaptation_layer/siteinventory.py b/adaptation_layer/siteinventory.py new file mode 100644 index 0000000..a69a4fe --- /dev/null +++ b/adaptation_layer/siteinventory.py @@ -0,0 +1,223 @@ +# Copyright 2019 CNIT, Francesco Lombardo, Matteo Pergolesi +# +# 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. +import logging +import os +from functools import wraps +from typing import List, Dict + +from requests import get, ConnectionError, Timeout, \ + TooManyRedirects, URLRequired, HTTPError, post, put, delete + +from error_handler import ServerError, NfvoNotFound, NfvoCredentialsNotFound, \ + Unauthorized, BadRequest, SubscriptionNotFound + +logger = logging.getLogger('app.siteinventory') +SITEINV_HTTPS = os.getenv('SITEINV_HTTPS', 'false').lower() +SITEINV_HOST = os.getenv('SITEINV_HOST') +SITEINV_PORT = os.getenv('SITEINV_PORT') +SITEINV_INTERVAL = os.getenv('SITEINV_INTERVAL') + +prot = 'https' if SITEINV_HTTPS == 'true' else 'http' +host = SITEINV_HOST if SITEINV_HOST else 'localhost' +port = int(SITEINV_PORT) if SITEINV_PORT else 8087 +interval = int(SITEINV_INTERVAL) if SITEINV_INTERVAL else 300 +url = '{0}://{1}:{2}'.format(prot, host, port) + + +def _server_error(func): + @wraps(func) + def wrapper(*args, **kwargs): + try: + return func(*args, **kwargs) + except (ConnectionError, Timeout, TooManyRedirects, URLRequired) as e: + raise ServerError('problem contacting site inventory: ' + str(e)) + + return wrapper + + +@_server_error +def post_vim_safe(osm_vim: Dict, nfvo_self: str): + vim_found = get( + url + '/vimAccounts/search/findByVimAccountNfvoId', + params={'uuid': osm_vim['_id']}) + vim_found.raise_for_status() + if vim_found.json()['_embedded']['vimAccounts']: + logger.info('vim {} found in site-inventory, skip'.format( + osm_vim['_id'] + )) + else: + payload = { + 'vimAccountNfvoId': osm_vim['_id'], + 'name': osm_vim['name'], + 'type': osm_vim['vim_type'], + 'uri': osm_vim['vim_url'], + 'tenant': osm_vim['vim_tenant_name'], + } + new_vim = post(url + '/vimAccounts', json=payload) + new_vim.raise_for_status() + logger.info('created new vimAccount with id {0}'.format( + new_vim.json()['vimAccountNfvoId'])) + put(new_vim.json()['_links']['nfvOrchestrators']['href'], + data=nfvo_self, + headers={'Content-Type': 'text/uri-list'}).raise_for_status() + logger.info('associated vimAccount to {0}'.format(nfvo_self)) + + +@_server_error +def find_nfvos_by_type(nfvo_type: str): + response = get( + url + '/nfvOrchestrators/search/findByTypeIgnoreCase', + params={'type': nfvo_type}) + response.raise_for_status() + return response.json()['_embedded']['nfvOrchestrators'] + + +@_server_error +def _get_nfvo(nfvo_id) -> Dict: + try: + resp = get(url + '/nfvOrchestrators/' + nfvo_id) + resp.raise_for_status() + nfvo = resp.json() + except HTTPError as e: + if e.response.status_code == 404: + raise NfvoNotFound(nfvo_id) + elif e.response.status_code == 401: + raise Unauthorized() + else: + raise + return nfvo + + +@_server_error +def _convert_nfvo(nfvo: Dict) -> Dict: + site = get(nfvo['_links']['site']['href']).json()['name'] + conv = { + 'id': nfvo['id'], + 'name': nfvo['name'], + 'type': nfvo['type'], + 'site': site + } + if nfvo['uri'] is not None: + conv['uri'] = nfvo['uri'] + if nfvo['createdAt'] is not None: + conv['createdAt'] = nfvo['createdAt'] + if nfvo['updatedAt'] is not None: + conv['updatedAt'] = nfvo['updatedAt'] + return conv + + +def convert_cred(nfvo): + del nfvo['credentials']['id'] + nfvo['credentials']['nfvo_id'] = nfvo['id'] + nfvo['credentials']['user'] = nfvo['credentials'].pop('username') + return nfvo['credentials'] + + +def get_nfvo_by_id(nfvo_id: int) -> Dict: + nfvo = _get_nfvo(nfvo_id) + return _convert_nfvo(nfvo) + + +def get_nfvo_cred(nfvo_id: int) -> Dict: + nfvo = _get_nfvo(nfvo_id) + if nfvo['credentials'] is None: + raise NfvoCredentialsNotFound(nfvo_id) + else: + return convert_cred(nfvo) + + +@_server_error +def get_nfvo_list() -> List[Dict]: + try: + resp = get(url + '/nfvOrchestrators') + resp.raise_for_status() + except HTTPError as e: + if e.response.status_code == 401: + raise Unauthorized() + else: + raise + return [_convert_nfvo(nfvo) for nfvo in + resp.json()['_embedded']['nfvOrchestrators']] + + +@_server_error +def get_subscription_list(nfvo_id: int) -> Dict: + try: + resp = get('{0}/nfvOrchestrators/{1}/subscriptions'.format(url, + nfvo_id)) + resp.raise_for_status() + except HTTPError as e: + if e.response.status_code == 401: + raise Unauthorized() + else: + raise + return resp.json() + + +@_server_error +def create_subscription(nfvo_id: int, body: Dict): + try: + create = post('{0}/subscriptions'.format(url), json=body) + create.raise_for_status() + associate = put(create.json()['_links']['nfvOrchestrators']['href'], + data='{0}/nfvOrchestrators/{1}'.format(url, + nfvo_id), + headers={'Content-Type': 'text/uri-list'}) + associate.raise_for_status() + except HTTPError as e: + if e.response.status_code == 400: + raise BadRequest() + else: + raise + return create.json() + + +@_server_error +def get_subscription(nfvo_id: int, subscriptionId: int) -> Dict: + try: + resp = get( + '{0}/nfvOrchestrators/{1}/subscriptions/{2}'.format(url, + nfvo_id, + subscriptionId)) + resp.raise_for_status() + except HTTPError as e: + if e.response.status_code == 404: + raise SubscriptionNotFound(sub_id=subscriptionId) + else: + raise + return resp.json() + + +@_server_error +def delete_subscription(subscriptionId: int) -> None: + try: + resp = delete( + '{0}/subscriptions/{1}'.format(url, subscriptionId)) + resp.raise_for_status() + except HTTPError as e: + if e.response.status_code == 404: + raise SubscriptionNotFound(sub_id=subscriptionId) + else: + raise + + +@_server_error +def search_subs_by_ns_instance(ns_instance_id: str) -> List[Dict]: + try: + subs = get(url + '/subscriptions/search/findByNsInstanceId', + params={'nsInstanceId': ns_instance_id}) + subs.raise_for_status() + except HTTPError: + raise + return subs.json()['_embedded']['subscriptions'] if subs else [] diff --git a/adaptation_layer/models.py b/adaptation_layer/sqlite.py similarity index 63% rename from adaptation_layer/models.py rename to adaptation_layer/sqlite.py index 696afed..1ed27e2 100644 --- a/adaptation_layer/models.py +++ b/adaptation_layer/sqlite.py @@ -13,8 +13,13 @@ # limitations under the License. import datetime +from typing import List, Dict -from config import db +from flask_sqlalchemy import SQLAlchemy + +from error_handler import NfvoNotFound + +db = SQLAlchemy() class NFVO(db.Model): @@ -37,8 +42,8 @@ def serialize(self): 'type': self.type, 'site': self.site, 'uri': self.uri, - 'created_at': self.created_at, - 'updated_at': self.updated_at + 'createdAt': self.created_at, + 'updatedAt': self.updated_at } def __init__(self, **kwargs): @@ -66,3 +71,41 @@ def serialize(self): 'user': self.user, 'password': self.password } + + +def get_nfvo_by_id(nfvo_id: int) -> Dict: + nfvo = NFVO.query.filter_by(id=nfvo_id).first() + if nfvo is None: + raise NfvoNotFound(nfvo_id=nfvo_id) + return nfvo.serialize + + +def get_nfvo_list() -> List[Dict]: + return [nfvo.serialize for nfvo in NFVO.query.all()] + + +def get_nfvo_cred(nfvo_id: int) -> Dict: + nfvo_cred = NFVO_CREDENTIALS.query.filter_by(nfvo_id=nfvo_id).first() + if nfvo_cred is None: + raise NfvoNotFound(nfvo_id=nfvo_id) + return nfvo_cred.serialize + + +def get_subscription_list(nfvo_id: int) -> Dict: + raise NotImplementedError("The method is not implemented") + + +def create_subscription(nfvo_id: int, body: Dict) -> Dict: + raise NotImplementedError("The method is not implemented") + + +def get_subscription(nfvo_id: int, subscriptionId: int) -> Dict: + raise NotImplementedError("The method is not implemented") + + +def delete_subscription(subscriptionId: int) -> None: + raise NotImplementedError("The method is not implemented") + + +def search_subs_by_ns_instance(ns_instance_id: str) -> List[Dict]: + raise NotImplementedError("The method is not implemented") diff --git a/adaptation_layer/tasks.py b/adaptation_layer/tasks.py new file mode 100644 index 0000000..a49b343 --- /dev/null +++ b/adaptation_layer/tasks.py @@ -0,0 +1,139 @@ +# Copyright 2019 CNIT, Francesco Lombardo, Matteo Pergolesi +# +# 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. +import os +from typing import Dict +from urllib.error import HTTPError + +import redis +from celery import Celery +from celery.utils.log import get_task_logger +from requests import post, RequestException + +import siteinventory +from driver.osm import OSM +from error_handler import ServerError, Error + +redis_host = os.getenv('REDIS_HOST') if os.getenv('REDIS_HOST') else 'redis' +redis_port = int(os.getenv('REDIS_PORT')) if os.getenv('REDIS_PORT') else 6379 + +celery = Celery('tasks', + broker='redis://{0}:{1}/0'.format(redis_host, redis_port), + backend='redis://{0}:{1}/0'.format(redis_host, redis_port)) + +celery.conf.beat_schedule = { + 'add_post_osm_vims_periodic': { + 'task': 'tasks.post_osm_vims', + 'schedule': siteinventory.interval + }, + 'add_osm_notifications': { + 'task': 'tasks.osm_notifications', + 'schedule': 5.0 + } +} +celery.conf.timezone = 'UTC' +logger = get_task_logger(__name__) + +last_op_status = redis.Redis(host=redis_host, port=redis_port, db=1, + decode_responses=True) + + +@celery.task +def post_osm_vims(): + osm_list = [] + try: + osm_list = siteinventory.find_nfvos_by_type('osm') + except (ServerError, HTTPError)as e: + logger.warning('error with siteinventory. skip post_osm_vims') + logger.debug(str(e)) + for osm in osm_list: + osm_vims = [] + if osm['credentials']: + try: + driver = OSM(siteinventory.convert_cred(osm)) + osm_vims, headers = driver.get_vim_list() + except Error as e: + logger.warning( + 'error contacting osm {0}:{1}'.format( + osm['credentials']['host'], + osm['credentials']['port'], + )) + logger.debug(str(e)) + continue + for v in osm_vims: + siteinventory.post_vim_safe(v, osm['_links']['self']['href']) + + +@celery.task +def osm_notifications(): + osm_list = [] + try: + osm_list = siteinventory.find_nfvos_by_type('osm') + except (ServerError, HTTPError)as e: + logger.warning('error with siteinventory, skip osm_notifications') + logger.debug(str(e)) + for osm in osm_list: + ops = [] + if osm['credentials']: + try: + driver = OSM(siteinventory.convert_cred(osm)) + ops, headers = driver.get_op_list({'args': {}}) + except Error as e: + logger.warning( + 'error contacting osm {0}:{1}'.format( + osm['credentials']['host'], + osm['credentials']['port'], + )) + logger.debug(str(e)) + continue + for op in ops: + last_s = last_op_status.get(op['id']) + logger.debug('last_s from redis: {}'.format(last_s)) + if not last_s or last_s != op['operationState']: + logger.info('different op state, send notification') + logger.debug('{},{}'.format(last_s, op['operationState'])) + last_op_status.set(op['id'], op['operationState']) + notify_payload = { + "nsInstanceId": op['nsInstanceId'], + "nsLcmOpOccId": op['id'], + "operation": op['lcmOperationType'], + "notificationType": "NsLcmOperationOccurrenceNotification", + "timestamp": op['startTime'], + "operationState": op['operationState'] + } + logger.debug(notify_payload) + forward_notification.delay(notify_payload) + + +@celery.task +def forward_notification(notification: Dict): + subs = [] + try: + subs = siteinventory.search_subs_by_ns_instance( + notification['nsInstanceId']) + except (ServerError, HTTPError)as e: + logger.warning('error with siteinventory. skip post_osm_vims') + logger.debug(str(e)) + if not subs: + logger.warning('no subscriptions for nsInstanceId {0}'.format( + notification['nsInstanceId'])) + for s in subs: + try: + if notification['notificationType'] in s['notificationTypes']: + resp = post(s['callbackUri'], json=notification) + resp.raise_for_status() + logger.info('Notification sent to {0}'.format(s['callbackUri'])) + except RequestException as e: + logger.warning( + 'Cannot send notification to {}'.format(s['callbackUri'])) + logger.debug(str(e)) diff --git a/adaptation_layer/tests/onap-api.yaml b/adaptation_layer/tests/onap-api.yaml index 4fc85aa..1ac249e 100644 --- a/adaptation_layer/tests/onap-api.yaml +++ b/adaptation_layer/tests/onap-api.yaml @@ -8,10 +8,10 @@ servers: info: description: - ONAP's ns-server API - version with Celery and rabbitMQ - version: "1.4" - title: ONAP's NS-server API + ONAP ns-server API + version with Celery and rabbitMQ with vnf info #and exCpInfo + version: "2.0" + title: ONAP NS-server API contact: email: michal.grzesik@orange.com license: @@ -23,13 +23,24 @@ tags: description: Management operations of NS instances - name: 'NS lcm' description: NS LCM Operations - # - name: 'Dev API' - # description: Dev API + - name: 'IWL Catalogue API' + description: API to retrieve the list of service specification and .csar archives # paths onap nbi & ns-server paths: # get all ns instances info '/instances': + parameters: + - in: query + name: offset + schema: + type: integer + description: The number of items to skip before starting to collect the result set + - in: query + name: limit + schema: + type: integer + description: The numbers of items to return get: tags: - 'NS instances' @@ -109,7 +120,7 @@ paths: schema: type: string format: uri - example: /ns_db_id/4e462039-e57b-44e4-8431-bc586f31b4cb + example: /instances/4e462039-e57b-44e4-8431-bc586f31b4cb content: application/json: schema: @@ -268,6 +279,40 @@ paths: # $ref: '#/components/responses/UnexpectedError' # default: # $ref: '#/components/responses/UnexpectedError' +# get a info lcm op occs with given ID + '/ns_lcm_op_occs/{lcm_id}': + parameters: + - name: lcm_id + in: path + required: true + description: lcm ID + schema: + type : string + get: + tags: + - 'NS lcm' + summary: retrieve a lcm info with given ID + description: retrieve a lcm info with given ID + operationId: lcmNS + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ID_lcm_op_occs' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '405': + $ref: '#/components/responses/MethodNotAllowed' + '500': + $ref: '#/components/responses/InternalServerError' + # '5XX': + # $ref: '#/components/responses/UnexpectedError' + # default: + # $ref: '#/components/responses/UnexpectedError' # get a info lcm op occs for given ns_Id '/ns_lcm_op_occs/ns_id/{ns_db_id}': parameters: @@ -302,28 +347,55 @@ paths: # $ref: '#/components/responses/UnexpectedError' # default: # $ref: '#/components/responses/UnexpectedError' -# get a info lcm op occs with given ID - '/ns_lcm_op_occs/lcm_id/{lcm_id}': +# get a list of service specifications + '/service_specification': + get: + tags: + - 'IWL Catalogue API' + summary: retrieve a list of all onboarded ns + description: retrieve a list of all onboarded ns + operationId: serviceList + responses: + '200': + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListServiceSpec' + '400': + $ref: '#/components/responses/BadRequest' + '404': + $ref: '#/components/responses/NotFound' + '405': + $ref: '#/components/responses/MethodNotAllowed' + '500': + $ref: '#/components/responses/InternalServerError' + # '5XX': + # $ref: '#/components/responses/UnexpectedError' + # default: + # $ref: '#/components/responses/UnexpectedError' +# get a list of service specifications + '/service_specification/{service_uuid}': parameters: - - name: lcm_id + - name: service_uuid in: path required: true - description: lcm ID + description: service specification UUID / nsdId schema: type : string get: tags: - - 'NS lcm' - summary: retrieve a lcm info with given ID - description: retrieve a lcm info with given ID - operationId: lcmNS + - 'IWL Catalogue API' + summary: retrieve a service specification .csar archive + description: retrieve a service specification .csar archive + operationId: serviceSpecArchive responses: '200': - description: OK - content: - application/json: - schema: - $ref: '#/components/schemas/ID_lcm_op_occs' + description: .csar archive + # content: + # application/pdf: + # schema: + # $ref: '#/components/schemas/ListServiceSpec' '400': $ref: '#/components/responses/BadRequest' '404': @@ -407,12 +479,18 @@ components: application/json: schema: $ref: '#/components/schemas/ID_lcm_op_occs' - OnapServiceSpecification: - description: Onap Service Specification + # OnapServiceSpecification: + # description: Onap Service Specification + # content: + # application/json: + # schema: + # $ref: '#/components/schemas/OnapServiceSpecification' + ListServiceSpec: + description: Onap Service Specification list content: application/json: schema: - $ref: '#/components/schemas/OnapServiceSpecification' + $ref: '#/components/schemas/ListServiceSpec' # end of schemas in responses # END RESPONSES schemas: @@ -476,8 +554,8 @@ components: nsState: type: string enum: - - NOT_INSTANTIATED - INSTANTIATED + - NOT_INSTANTIATED vnfInstance: type: array items: @@ -510,8 +588,8 @@ components: instantiationState: type: string enum: - - NOT_INSTANTIATED - INSTANTIATED + - NOT_INSTANTIATED instantiatedVnfInfo: type: object properties: @@ -523,64 +601,94 @@ components: items: $ref: '#/components/schemas/VnfExtCpInfo' # - VnfExtCpInfo: - type: object - properties: - id: - type: string - format: uuid - example: 3fa85f64-5717-4562-b3fc-2c963f66afa6 - cpProtocolInfo: - type: array - items: - $ref: '#/components/schemas/CpProtocolInfo' + vnfInstanceCreated: + type: object + properties: + id: + type: string + format: uuid + example: null #3fa85f64-5717-4562-b3fc-2c963f66afa6 + vnfdId: + type: string + format: uuid + example: 8a585f64-5717-4562-b3fc-2c963f66afa6 + vnfProductName: + type: string + vimId: + type: string + format: uuid + example: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + instantiationState: + type: string + enum: + #- INSTANTIATED + - NOT_INSTANTIATED +# instantiatedVnfInfo: +# type: object # - CpProtocolInfo: + VnfExtCpInfo: +# nullable: true type: object - required: - - ipOverEthernet - - layerProtocol - properties: - layerProtocol: - type: string - enum: - - IP_OVER_ETHERNET - ipOverEthernet: - allOf: - - $ref: '#/components/schemas/IpOverEthernetAddressInfo' + # properties: + # id: + # type: string + # format: uuid + # example: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + # cpdId: + # type: string + # format: uuid + # example: 3fa85f64-5717-4562-b3fc-2c963f66afa6 + # cpProtocolInfo: + # type: array + # items: + # $ref: '#/components/schemas/CpProtocolInfo' # - IpOverEthernetAddressInfo: - type: object - required: - - ipAddresses - - macAddress - properties: - macAddress: - type: string - format: MAC - ipAddresses: - type: array - items: - $ref: '#/components/schemas/IpOverEthernetAddressInfo_ipAddresses' +# CpProtocolInfo: +# type: object +# required: +# - ipOverEthernet +# - layerProtocol +# properties: +# layerProtocol: +# type: string +# enum: +# - IP_OVER_ETHERNET +# ipOverEthernet: +# allOf: +# - $ref: '#/components/schemas/IpOverEthernetAddressInfo' +# # # +# IpOverEthernetAddressInfo: +# type: object +# required: +# - ipAddresses +# - macAddress +# properties: +# macAddress: +# type: string +# format: MAC +# ipAddresses: +# type: array +# items: +# $ref: '#/components/schemas/IpOverEthernetAddressInfo_ipAddresses' # - IpOverEthernetAddressInfo_ipAddresses: - type: object - required: - - type - properties: - type: - type: string - enum: - - IPV4 - - IPV6 - addresses: - type: array - items: - $ref: '#/components/schemas/IpAddress' + # IpOverEthernetAddressInfo_ipAddresses: + # type: object + # required: + # - type + # properties: + # type: + # type: string + # enum: + # - IPV4 + # - IPV6 + # addresses: + # type: array + # items: + # $ref: '#/components/schemas/IpAddress' # - IpAddress: - type: string - format: IP + # IpAddress: + # type: string + # format: IP # NsInstanceIdCreated: type: object @@ -604,7 +712,7 @@ components: vnfInstance: type: array items: - $ref: '#/components/schemas/vnfInstance' + $ref: '#/components/schemas/vnfInstanceCreated' required: - id - nsInstanceName @@ -646,9 +754,18 @@ components: type: string format: date-time # - OnapServiceSpecification: + ListServiceSpec: + type: array + items: + $ref: '#/components/schemas/serviceSpec' +# + serviceSpec: type: object properties: + id: + type: string + format: uuid + example: 1de0e9c3-b238-44da-b01b-b249a7784b03 name: type: string # END SCHEMAS @@ -670,4 +787,4 @@ components: # securitySchemes: # bearerAuth: # type: http -# scheme: bearer \ No newline at end of file +# scheme: bearer diff --git a/adaptation_layer/tests/pytest.ini b/adaptation_layer/tests/pytest.ini new file mode 100644 index 0000000..3dae6f7 --- /dev/null +++ b/adaptation_layer/tests/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +filterwarnings = + ignore:Function 'semver.format_version' is deprecated:DeprecationWarning diff --git a/adaptation_layer/tests/response_schemas.py b/adaptation_layer/tests/response_schemas.py index ccb2182..12b4856 100644 --- a/adaptation_layer/tests/response_schemas.py +++ b/adaptation_layer/tests/response_schemas.py @@ -11,30 +11,14 @@ # 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. -import fnmatch import os from prance import ResolvingParser +OPENAPI_PATH = os.environ.get("OPENAPI_PATH", + '../../openapi/MSO-LO-swagger-resolved.yaml') -def _get_file_path(directory: str) -> str: - try: - for file in os.listdir(directory): - if fnmatch.fnmatch(file, 'MSO-LO-?.?-swagger-resolved.yaml'): - return os.path.join(directory, file) - except FileNotFoundError: - pass - - -# Test in container -file_path = _get_file_path('./openapi/') -if file_path is None: - # Test in dev environment - file_path = _get_file_path('../../openapi/') -if file_path is None: - raise FileNotFoundError("Openapi directory not found.") - -openapi = ResolvingParser(file_path).specification +openapi = ResolvingParser(OPENAPI_PATH).specification nfvo_schema = openapi["definitions"]["NFVO"] nfvo_list_schema = { @@ -57,3 +41,7 @@ def _get_file_path(directory: str) -> str: "type": "array", "items": ns_lcm_op_occ_schema } + +subscription_schema = openapi["definitions"]["LccnSubscription"] +subscription_list_schema = openapi["definitions"][ + "CollectionModelLccnSubscription"] diff --git a/adaptation_layer/tests/test_nfvo.py b/adaptation_layer/tests/test_nfvo.py index f763989..a8c3d33 100644 --- a/adaptation_layer/tests/test_nfvo.py +++ b/adaptation_layer/tests/test_nfvo.py @@ -14,12 +14,13 @@ # See the License for the specific language governing permissions and # limitations under the License. -import json import unittest + from jsonschema import validate from jsonschema.exceptions import ValidationError, SchemaError -from .response_schemas import nfvo_schema, nfvo_list_schema + from app import app +from .response_schemas import nfvo_schema, nfvo_list_schema class NFVOTestCase(unittest.TestCase): @@ -35,7 +36,7 @@ def tearDown(self): # Check status codes 200, 401, headers and payload for get_nfvo_list() def test_get_nfvo_list_200(self): res = self.client().get('/nfvo') - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) try: validate(res.json, nfvo_list_schema) except (ValidationError, SchemaError) as e: @@ -44,12 +45,12 @@ def test_get_nfvo_list_200(self): @unittest.skip('skip 401 test as we do not have authorization yet') def test_get_nfvo_list_401(self): res = self.client().get('/nfvo?__code=401') - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 200, 401, 404, headers and payload for get_nfvo(nfvoId) def test_get_nfvo_200(self): res = self.client().get('/nfvo/1') - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) try: validate(res.json, nfvo_schema) except (ValidationError, SchemaError) as e: @@ -57,9 +58,9 @@ def test_get_nfvo_200(self): def test_get_nfvo_404(self): res = self.client().get('/nfvo/-1') - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) @unittest.skip('skip 401 test as we do not have authorization yet') def test_get_nfvo_401(self): res = self.client().get('/nfvo/nfvo_osm1?__code=401') - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) diff --git a/adaptation_layer/tests/test_onap.py b/adaptation_layer/tests/test_onap.py index 5f2ce1c..d957648 100644 --- a/adaptation_layer/tests/test_onap.py +++ b/adaptation_layer/tests/test_onap.py @@ -23,6 +23,7 @@ from .response_schemas import ns_lcm_op_occ_schema, \ ns_list_schema, ns_schema, ns_lcm_op_occ_list_schema + # AUTHORIZATION unsupported by ONAP driver # scale a ns instance unsupported by ONAP driver @@ -44,7 +45,7 @@ def test_get_ns_list_200(self): validate(res.json, ns_list_schema) except (ValidationError, SchemaError) as e: self.fail(msg=e.message) - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) # Check status codes 200, 404, headers and payload for get_ns() def test_get_ns_200(self): @@ -54,17 +55,17 @@ def test_get_ns_200(self): validate(res.json, ns_schema) except (ValidationError, SchemaError) as e: self.fail(msg=e.message) - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) def test_get_ns_404(self): res = self.client().get( '/nfvo/2/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=404') - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) # Check status codes 201, 404, headers and payload for create_ns() def test_create_ns_201(self): res = self.client().post('/nfvo/2/ns_instances?__code=201', json=mock_ns) - self.assertEqual(res.status_code, 201) + self.assertEqual(201, res.status_code) self.assertIn('Location', res.headers) validate_url = urlparse(res.headers["Location"]) @@ -77,12 +78,12 @@ def test_create_ns_201(self): def test_create_ns_400(self): res = self.client().post('/nfvo/2/ns_instances?__code=400', json=mock_ns) - self.assertEqual(res.status_code, 400) + self.assertEqual(400, res.status_code) # Check status codes 202, 400, 404, headers and payload for instantiate_ns() def test_instantiate_ns_202(self): res = self.client().post('/nfvo/2/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/instantiate?__code=202') - self.assertEqual(res.status_code, 202) + self.assertEqual(202, res.status_code) self.assertIn('Location', res.headers) validate_url = urlparse(res.headers["Location"]) @@ -90,17 +91,17 @@ def test_instantiate_ns_202(self): def test_instantiate_ns_400(self): res = self.client().post('/nfvo/2/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/instantiate?__code=400') - self.assertEqual(res.status_code, 400) + self.assertEqual(400, res.status_code) def test_instantiate_ns_404(self): res = self.client().post('/nfvo/2/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/instantiate?__code=404') - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) # Check status codes 202, 404, headers and payload for terminate_ns() def test_terminate_ns_202(self): res = self.client().post('/nfvo/2/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/terminate?__code=202', json=mock_ns_terminate) - self.assertEqual(res.status_code, 202) + self.assertEqual(202, res.status_code) self.assertIn('Location', res.headers) validate_url = urlparse(res.headers["Location"]) @@ -109,18 +110,18 @@ def test_terminate_ns_202(self): def test_terminate_ns_404(self): res = self.client().post('/nfvo/2/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/terminate?__code=404', json=mock_ns_terminate) - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) # Check status codes 204, 404, headers and payload for delete_ns() def test_delete_ns_204(self): res = self.client().delete( '/nfvo/2/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=204') - self.assertEqual(res.status_code, 204) + self.assertEqual(204, res.status_code) def test_delete_ns_404(self): res = self.client().delete( '/nfvo/2/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=404') - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) # Check status codes 200, 404, headers and payload for get_ns_lcm_op_occs_() def test_get_ns_lcm_op_occs_200(self): @@ -129,11 +130,11 @@ def test_get_ns_lcm_op_occs_200(self): validate(res.json, ns_lcm_op_occ_schema) except (ValidationError, SchemaError) as e: self.fail(msg=e.message) - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) def test_get_ns_lcm_op_occs_404(self): res = self.client().get('/nfvo/2/ns_lcm_op_occs/49ccb6a2-5bcd-4f35-a2cf-7728c54c48b7?__code=404') - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) # Check status codes 200, headers and payload for get_ns_lcm_op_occs_list() def test_get_ns_lcm_op_occs_list_200(self): @@ -142,11 +143,11 @@ def test_get_ns_lcm_op_occs_list_200(self): validate(res.json, ns_lcm_op_occ_list_schema) except (ValidationError, SchemaError) as e: self.fail(msg=e.message) - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) def test_get_ns_lcm_op_occs_list_200_filter(self): res = self.client().get('/nfvo/2/ns_lcm_op_occs?nsInstanceId=1de0e9c3-b238-44da-b01b-b249a7784b03&__code=200') - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) try: validate(res.json, ns_lcm_op_occ_list_schema) diff --git a/adaptation_layer/tests/test_osm.py b/adaptation_layer/tests/test_osm.py index 4d4b0c3..6ff9927 100644 --- a/adaptation_layer/tests/test_osm.py +++ b/adaptation_layer/tests/test_osm.py @@ -39,7 +39,7 @@ def tearDown(self): # Check status codes 201, 401, 404, headers and payload for create_ns() def test_create_ns_201(self): res = self.client().post('/nfvo/1/ns_instances?__code=201', json=mock_ns) - self.assertEqual(res.status_code, 201) + self.assertEqual(201, res.status_code) self.assertIn('Location', res.headers) validate_url = urlparse(res.headers["Location"]) @@ -52,16 +52,16 @@ def test_create_ns_201(self): def test_create_ns_400(self): res = self.client().post('/nfvo/1/ns_instances?__code=400', json=mock_ns) - self.assertEqual(res.status_code, 400) + self.assertEqual(400, res.status_code) def test_create_ns_401(self): res = self.client().post('/nfvo/1/ns_instances?__code=401', json=mock_ns) - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 200, 401, 404, headers and payload for get_ns_list() def test_get_ns_list_200(self): res = self.client().get('/nfvo/1/ns_instances?__code=200') - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) try: validate(res.json, ns_list_schema) @@ -70,12 +70,12 @@ def test_get_ns_list_200(self): def test_get_ns_list_401(self): res = self.client().get('/nfvo/1/ns_instances?__code=401') - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 200, 401, 404, headers and payload for get_ns() def test_get_ns_200(self): res = self.client().get('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=200') - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) try: validate(res.json, ns_schema) @@ -84,29 +84,29 @@ def test_get_ns_200(self): def test_get_ns_404(self): res = self.client().get('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=404') - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) def test_get_ns_401(self): res = self.client().get('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=401') - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 204, 401, 404, headers and payload for delete_ns() def test_delete_ns_204(self): res = self.client().delete('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=204') - self.assertEqual(res.status_code, 204) + self.assertEqual(204, res.status_code) def test_delete_ns_404(self): res = self.client().delete('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=404') - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) def test_delete_ns_401(self): res = self.client().delete('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7?__code=401') - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 202, 401, 404, headers and payload for instantiate_ns() def test_instantiate_ns_202(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/instantiate?__code=202') - self.assertEqual(res.status_code, 202) + self.assertEqual(202, res.status_code) self.assertIn('Location', res.headers) validate_url = urlparse(res.headers["Location"]) @@ -114,17 +114,17 @@ def test_instantiate_ns_202(self): def test_instantiate_ns_400(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/instantiate?__code=400') - self.assertEqual(res.status_code, 400) + self.assertEqual(400, res.status_code) def test_instantiate_ns_401(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/instantiate?__code=401') - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 202, 401, 404, headers and payload for terminate_ns() def test_terminate_ns_202(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/terminate?__code=202', json=mock_ns_terminate) - self.assertEqual(res.status_code, 202) + self.assertEqual(202, res.status_code) self.assertIn('Location', res.headers) validate_url = urlparse(res.headers["Location"]) @@ -133,18 +133,18 @@ def test_terminate_ns_202(self): def test_terminate_ns_404(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/terminate?__code=404', json=mock_ns_terminate) - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) def test_terminate_ns_401(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/terminate?__code=401', json=mock_ns_terminate) - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 202, 401, 404, headers and payload for scale_ns() def test_scale_ns_202(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/scale?__code=202', json=mock_ns_scale) - self.assertEqual(res.status_code, 202) + self.assertEqual(202, res.status_code) self.assertIn('Location', res.headers) validate_url = urlparse(res.headers["Location"]) @@ -153,17 +153,17 @@ def test_scale_ns_202(self): def test_scale_ns_404(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/scale?__code=404', json=mock_ns_scale) - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) def test_scale_ns_401(self): res = self.client().post('/nfvo/1/ns_instances/49ccb6a2-5bcd-4f35-a2cf-7728c54e48b7/scale?__code=401', json=mock_ns_scale) - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 200, 401, headers and payload for get_ns_lcm_op_occs_list() def test_get_ns_lcm_op_occs_list_200(self): res = self.client().get('/nfvo/1/ns_lcm_op_occs?__code=200') - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) try: validate(res.json, ns_lcm_op_occ_list_schema) @@ -172,7 +172,7 @@ def test_get_ns_lcm_op_occs_list_200(self): def test_get_ns_lcm_op_occs_list_200_filter(self): res = self.client().get('/nfvo/1/ns_lcm_op_occs?nsInstanceId=1de0e9c3-b238-44da-b01b-b249a7784b03&__code=200') - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) try: validate(res.json, ns_lcm_op_occ_list_schema) @@ -181,26 +181,26 @@ def test_get_ns_lcm_op_occs_list_200_filter(self): def test_get_ns_lcm_op_occs_list_401(self): res = self.client().get('/nfvo/1/ns_lcm_op_occs?__code=401') - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) # Check status codes 200, 401, 404, headers and payload for get_ns_lcm_op_occs_() def test_get_ns_lcm_op_occs_200(self): res = self.client().get('/nfvo/1/ns_lcm_op_occs/49ccb6a2-5bcd-4f35-a2cf-7728c54c48b7?__code=200') - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) try: validate(res.json, ns_lcm_op_occ_schema) except (ValidationError, SchemaError) as e: self.fail(msg=e.message) - self.assertEqual(res.status_code, 200) + self.assertEqual(200, res.status_code) def test_get_ns_lcm_op_occs_404(self): res = self.client().get('/nfvo/1/ns_lcm_op_occs/49ccb6a2-5bcd-4f35-a2cf-7728c54c48b7?__code=404') - self.assertEqual(res.status_code, 404) + self.assertEqual(404, res.status_code) def test_get_ns_lcm_op_occs_401(self): res = self.client().get('/nfvo/1/ns_lcm_op_occs/49ccb6a2-5bcd-4f35-a2cf-7728c54c48b7?__code=401') - self.assertEqual(res.status_code, 401) + self.assertEqual(401, res.status_code) if __name__ == '__main__': diff --git a/docker-compose.test-nfvo.yml b/docker-compose.test-nfvo.yml index fbc203f..42b256c 100644 --- a/docker-compose.test-nfvo.yml +++ b/docker-compose.test-nfvo.yml @@ -4,8 +4,12 @@ services: build: context: . target: test + args: + DB_SEED_NFVO: seed/nfvo_mock.json + DB_SEED_NFVO_CRED: seed/nfvo_credentials_mock.json container_name: test-nfvo environment: TESTING: "True" - command: ["pipenv", "run", "pytest", "-v", "tests/test_nfvo.py"] + OPENAPI_PATH: ./openapi/MSO-LO-swagger-resolved.yaml + command: ["pytest", "-v", "tests/test_nfvo.py"] diff --git a/docker-compose.test-onap.yml b/docker-compose.test-onap.yml index f6f51e7..c8f097b 100644 --- a/docker-compose.test-onap.yml +++ b/docker-compose.test-onap.yml @@ -17,13 +17,17 @@ services: build: context: . target: test + args: + DB_SEED_NFVO: seed/nfvo_mock.json + DB_SEED_NFVO_CRED: seed/nfvo_credentials_mock.json container_name: test-onap depends_on: - prism-onap environment: TESTING: "True" PRISM_ALIAS: prism-onap - command: ["./tests/wait-for-it.sh", "prism-onap:9999", "--", "pipenv", "run", "pytest", "-v", "tests/test_onap.py"] + OPENAPI_PATH: ./openapi/MSO-LO-swagger-resolved.yaml + command: ["./tests/wait-for-it.sh", "prism-onap:9999", "--", "pytest", "-v", "tests/test_onap.py"] networks: mso-lo-net: aliases: diff --git a/docker-compose.test-osm.yml b/docker-compose.test-osm.yml index 7649503..e317409 100644 --- a/docker-compose.test-osm.yml +++ b/docker-compose.test-osm.yml @@ -17,13 +17,17 @@ services: build: context: . target: test + args: + DB_SEED_NFVO: seed/nfvo_mock.json + DB_SEED_NFVO_CRED: seed/nfvo_credentials_mock.json container_name: test-osm depends_on: - prism-osm environment: TESTING: "True" PRISM_ALIAS: prism-osm - command: ["./tests/wait-for-it.sh", "prism-osm:9999", "--", "pipenv", "run", "pytest", "-v", "tests/test_osm.py"] + OPENAPI_PATH: ./openapi/MSO-LO-swagger-resolved.yaml + command: ["./tests/wait-for-it.sh", "prism-osm:9999", "--", "pytest", "-v", "tests/test_osm.py"] networks: mso-lo-net: aliases: diff --git a/docker-compose.yml b/docker-compose.yml index 9cd2e67..5e2421a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,14 +16,29 @@ services: context: . target: prod container_name: mso-lo + depends_on: + - redis + environment: + SITEINV: 'true' + SITEINV_HTTPS: 'false' + SITEINV_HOST: '192.168.17.20' + SITEINV_PORT: '8087' + SITEINV_INTERVAL: '300' + REDIS_HOST: 'redis' + REDIS_PORT: '6379' networks: mso-lo-net: aliases: - flask-app - ports: - - 5000:5000 volumes: - mso-lo-data:/usr/src/app/data + redis: + image: redis:latest + container_name: redis + networks: + mso-lo-net: + aliases: + - redis networks: mso-lo-net: volumes: diff --git a/openapi/MSO-LO-3.3-swagger-resolved.yaml b/openapi/MSO-LO-swagger-resolved.yaml similarity index 64% rename from openapi/MSO-LO-3.3-swagger-resolved.yaml rename to openapi/MSO-LO-swagger-resolved.yaml index bf848f0..5536491 100644 --- a/openapi/MSO-LO-3.3-swagger-resolved.yaml +++ b/openapi/MSO-LO-swagger-resolved.yaml @@ -1,48 +1,48 @@ --- -swagger: "2.0" +swagger: '2.0' info: description: 5G-EVE Adaptation Layer API spec. - version: "3.3" + version: '3.5' title: 5G-EVE Adaptation Layer API contact: {} host: example.com basePath: /api tags: -- name: Local NFVO - description: Local NFVO information -- name: NS instances - description: Network Service instance management -- name: NS Subscriptions - description: Subscriptions to notifications about NS instances status -- name: Notification send - description: Operations for sending notifications to subscribed clients + - name: Local NFVO + description: Local NFVO information + - name: NS instances + description: Network Service instance management + - name: NS Subscriptions + description: Subscriptions to notifications about NS instances status + - name: Notification send + description: Operations for sending notifications to subscribed clients schemes: [] consumes: -- application/json + - application/json produces: -- application/json + - application/json paths: /nfvo: get: tags: - - Local NFVO + - Local NFVO summary: Retrieve list of local NFVO. description: | By passing in the appropriate options, you can search for available Local Orchestrators operationId: NFVO.fetch produces: - - application/json + - application/json parameters: - - name: NFVO parameters - in: query - description: nfvo identifier - required: false - type: string - - name: Pagination parameters - in: query - description: The parameters are used to create pagination - required: false - type: string + - name: NFVO parameters + in: query + description: nfvo identifier + required: false + type: string + - name: Pagination parameters + in: query + description: The parameters are used to create pagination + required: false + type: string responses: "200": description: search results matching criteria @@ -59,19 +59,19 @@ paths: /nfvo/{nfvoId}: get: tags: - - Local NFVO + - Local NFVO summary: Read an individual NFVO. description: | By passing in the appropriate options, you can search for available Local Orchestrators operationId: NFVO.getById produces: - - application/json + - application/json parameters: - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string responses: "200": description: NFVO found @@ -94,24 +94,24 @@ paths: /nfvo/{nfvoId}/ns_instances: get: tags: - - NS instances + - NS instances summary: Retrieve list of NS instances. description: | This operation allows to retrieve the list of NS from a specific Nfvo operationId: Nfvo.NS.list produces: - - application/json + - application/json parameters: - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - name: Pagination parameters - in: query - description: The parameters are used to create pagination - required: false - type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - name: Pagination parameters + in: query + description: The parameters are used to create pagination + required: false + type: string responses: "200": description: search results matching criteria @@ -131,29 +131,29 @@ paths: deprecated: false post: tags: - - NS instances + - NS instances summary: Create a new NS instance ID. description: Creates and returns a Network Service identifier (nsId) in a Nfvo operationId: Nfvo.NS.onboard produces: - - application/json + - application/json parameters: - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - in: body - name: body - description: Create NS request body - required: false - schema: - $ref: '#/definitions/NfvoNsInstancesRequest' + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - in: body + name: body + description: Create NS request body + required: false + schema: + $ref: '#/definitions/NfvoNsInstancesRequest' responses: "201": description: | 201 Created - A NS Instance identifier has been created successfully. The response body shall contain a representation of the created NS instance, as defined in clause 6.5.2.8. The HTTP response shall include a "Location" HTTP header that contains the resource URI of the created NS instance. + A NS Instance identifier has been created successfully. The response body shall contain a representation of the created NS instance, as defined in clause 6.5.2.8. The HTTP response shall include a 'Location' HTTP header that contains the resource URI of the created NS instance. headers: Location: type: string @@ -172,24 +172,24 @@ paths: /nfvo/{nfvoId}/ns_instances/{nsInstanceId}: get: tags: - - NS instances + - NS instances summary: Read an individual NS instance resource. description: | This operation allows to retrieve an existing NS from a specific Nfvo. operationId: Nfvo.NS.fetch produces: - - application/json + - application/json parameters: - - name: nsInstanceId - in: path - description: Identifier of the network service - required: true - type: string - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string + - name: nsInstanceId + in: path + description: Identifier of the network service + required: true + type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string responses: "200": description: NS @@ -211,24 +211,24 @@ paths: deprecated: false delete: tags: - - NS instances + - NS instances summary: Delete an individual NS instance resource. description: | Delete an individual NS instance resource. operationId: Nfvo.NS.terminate produces: - - application/json + - application/json parameters: - - name: nsInstanceId - in: path - description: Identifier of the network service - required: true - type: string - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string + - name: nsInstanceId + in: path + description: Identifier of the network service + required: true + type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string responses: "204": description: | @@ -250,28 +250,34 @@ paths: /nfvo/{nfvoId}/ns_instances/{nsInstanceId}/instantiate: post: tags: - - NS instances + - NS instances summary: Instantiate a NS instance. description: | Instantiate a NS. The precondition is that the NS instance must have been created and must be in NOT_INSTANTIATED state. As a result of the - success of this operation, the NFVO creates a "NS Lifecycle Operation - Occurrence" resource for the request, and the NS instance state becomes + success of this operation, the NFVO creates a 'NS Lifecycle Operation + Occurrence' resource for the request, and the NS instance state becomes INSTANTIATED. operationId: Nfvo.NS.instantiate produces: - - application/json + - application/json parameters: - - name: nsInstanceId - in: path - description: Identifier of the network service - required: true - type: string - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string + - name: nsInstanceId + in: path + description: Identifier of the network service + required: true + type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - in: body + name: body + description: Instantiate NS request body + required: false + schema: + $ref: '#/definitions/NfvoNsInstantiateRequest' responses: "202": description: | @@ -296,34 +302,34 @@ paths: /nfvo/{nfvoId}/ns_instances/{nsInstanceId}/scale: post: tags: - - NS instances + - NS instances summary: Scale NS instance. description: | Scale a NS instance. The precondition is that the NS instance must have been created and must be in INSTANTIATED state. As a result of the - success of this operation, the NFVO creates a "NS Lifecycle Operation - Occurrence" resource for the request, and the NS instance state remains + success of this operation, the NFVO creates a 'NS Lifecycle Operation + Occurrence' resource for the request, and the NS instance state remains INSTANTIATED. operationId: Nfvo.NS.scale produces: - - application/json + - application/json parameters: - - name: nsInstanceId - in: path - description: Identifier of the network service - required: true - type: string - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - in: body - name: body - description: Scale NS request body - required: false - schema: - $ref: '#/definitions/NfvoNsInstancesScaleRequest' + - name: nsInstanceId + in: path + description: Identifier of the network service + required: true + type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - in: body + name: body + description: Scale NS request body + required: false + schema: + $ref: '#/definitions/NfvoNsInstancesScaleRequest' responses: "202": description: | @@ -345,30 +351,30 @@ paths: /nfvo/{nfvoId}/ns_instances/{nsInstanceId}/terminate: post: tags: - - NS instances + - NS instances summary: Terminate a NS instance. description: | Terminate NS task. The POST method terminates a NS instance. This method can only be used with a NS instance in the INSTANTIATED state. Terminating a NS instance does not delete the NS instance identifier, but rather transitions the NS into the NOT_INSTANTIATED state. operationId: terminateNSinstance produces: - - application/json + - application/json parameters: - - name: nsInstanceId - in: path - description: Identifier of the network service - required: true - type: string - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - in: body - name: body - description: "" - required: false - schema: - $ref: '#/definitions/TerminateNsRequest' + - name: nsInstanceId + in: path + description: Identifier of the network service + required: true + type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - in: body + name: body + description: "" + required: false + schema: + $ref: '#/definitions/TerminateNsRequest' responses: "202": description: | @@ -390,38 +396,38 @@ paths: /nfvo/{nfvoId}/ns_lcm_op_occs: get: tags: - - NS LCM Operations + - NS LCM Operations summary: Query multiple NS LCM operation occurrences. description: | Get Operation Status. The client can use this method to query status information about multiple NS lifecycle management operation occurrences. This method shall follow the provisions specified in the Tables 6.4.9.3.2-1 and 6.4.9.3.2-2 for URI query parameters, request and response data structures, and response codes. operationId: Nfvo.NS_LCM_OP.list produces: - - application/json + - application/json parameters: - - name: filter - in: query - description: | - Only 'nsInstanceId' is supported. - Attribute-based filtering expression according to clause 5.2 of ETSI GS NFV SOL 013. The NFVO shall support receiving this parameter as part of the URI query string. The OSS/BSS may supply this parameter. All attribute names that appear in the NsLcmOpOcc and in data types referenced from it shall be supported by the NFVO in the filter expression. - required: false - type: string - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - name: Accept - in: header - description: | - Content-Types that are acceptable for the response. Reference: IETF RFC 7231 - required: true - type: string + - name: filter + in: query + description: | + Only 'nsInstanceId' is supported. + Attribute-based filtering expression according to clause 5.2 of ETSI GS NFV SOL 013. The NFVO shall support receiving this parameter as part of the URI query string. The OSS/BSS may supply this parameter. All attribute names that appear in the NsLcmOpOcc and in data types referenced from it shall be supported by the NFVO in the filter expression. + required: false + type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - name: Accept + in: header + description: | + Content-Types that are acceptable for the response. Reference: IETF RFC 7231 + required: true + type: string responses: "200": description: | 200 OK Status information for zero or more NS lifecycle management operation occurrences has been queried successfully. The response body shall contain in an array the representations of zero or more NS instances, as defined in clause 6.5.2.3. - If the "filter" URI parameter or one of the "all_fields", "fields", "exclude_fields" or "exclude_default" URI parameters was supplied in the request and is supported, the data in the response body shall have been transformed according to the rules specified in clauses 5.2.2 and 5.3.2 of ETSI GS NFV SOL 013, respectively. + If the 'filter' URI parameter or one of the 'all_fields', 'fields', 'exclude_fields' or 'exclude_default' URI parameters was supplied in the request and is supported, the data in the response body shall have been transformed according to the rules specified in clauses 5.2.2 and 5.3.2 of ETSI GS NFV SOL 013, respectively. If the NFVO supports alternative 2 (paging) according to clause 5.4.2.1 of ETSI GS NFV SOL 013 for this resource, inclusion of the Link HTTP header in this response shall follow the provisions in clause 5.4.2.3 of ETSI GS NFV SOL 013. headers: Content-Type: @@ -443,31 +449,31 @@ paths: /nfvo/{nfvoId}/ns_lcm_op_occs/{nsLcmOpOccId}: get: tags: - - NS LCM Operations + - NS LCM Operations summary: Read an individual NS LCM operation occurrence resource. description: | - The client can use this method to retrieve status information about a NS lifecycle management operation occurrence by reading an individual "NS LCM operation occurrence" resource. This method shall follow the provisions specified in the Tables 6.4.10.3.2-1 and 6.4.10.3.2-2 for URI query parameters, request and response data structures, and response codes. + The client can use this method to retrieve status information about a NS lifecycle management operation occurrence by reading an individual 'NS LCM operation occurrence' resource. This method shall follow the provisions specified in the Tables 6.4.10.3.2-1 and 6.4.10.3.2-2 for URI query parameters, request and response data structures, and response codes. operationId: Nfvo.NS_LCM_OP.fetch produces: - - application/json + - application/json parameters: - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - name: nsLcmOpOccId - in: path - description: | - Identifier of a NS lifecycle management operation occurrence. - required: true - type: string - - name: Accept - in: header - description: | - Content-Types that are acceptable for the response. Reference: IETF RFC 7231 - required: true - type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - name: nsLcmOpOccId + in: path + description: | + Identifier of a NS lifecycle management operation occurrence. + required: true + type: string + - name: Accept + in: header + description: | + Content-Types that are acceptable for the response. Reference: IETF RFC 7231 + required: true + type: string responses: "200": description: | @@ -500,25 +506,20 @@ paths: /nfvo/{nfvoId}/subscriptions: get: tags: - - NS Subscriptions + - NS Subscriptions summary: Query multiple subscriptions. description: | Query Subscription Information. The GET method queries the list of active subscriptions of the functional block that invokes the method. It can be used e.g. for resynchronization after error situations. operationId: NfvoSubscriptionsByNfvoIdGet produces: - - application/json + - application/hal+json parameters: - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - name: filter - in: query - description: Attribute-based filtering expression according to clause 5.2 of ETSI GS NFV SOL 013. The NFVO shall support receiving this parameter as part of the URI query string. The OSS/BSS may supply this parameter. All attribute names that appear in the LccnSubscription and in data types referenced from it shall be supported by the NFVO in the filter expression. - required: false - type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string responses: "200": description: | @@ -528,96 +529,68 @@ paths: schema: type: array items: - $ref: '#/definitions/NSInstanceSubscription' - "400": - description: The request is malformed - schema: - $ref: '#/definitions/ErrorResponse' - "401": - description: Unauthorized, Invalid credentials (client or user) - schema: - $ref: '#/definitions/ErrorResponse' - "404": - description: The specified resource was not found - schema: - $ref: '#/definitions/ErrorResponse' + $ref: '#/definitions/CollectionModelLccnSubscription' deprecated: false post: tags: - - NS Subscriptions + - NS Subscriptions summary: Subscribe to NS lifecycle change notifications. description: | - The POST method creates a new subscription. If subscription already exists, it returns a "303 See Other" response code referencing the existing subscription resource. + The POST method creates a new subscription. If subscription already exists, it returns a '303 See Other' response code referencing the existing subscription resource. operationId: NfvoSubscriptionsByNfvoIdPost produces: - - application/json + - application/hal+json parameters: - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - in: body - name: body - description: "" - required: false - schema: - $ref: '#/definitions/NfvoSubscriptionsRequest' + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - in: body + name: body + description: "" + required: false + schema: + $ref: '#/definitions/LccnSubscription' responses: "201": description: 201 Created. The subscription has been created successfully. headers: {} schema: - $ref: '#/definitions/NSInstanceSubscription' + $ref: '#/definitions/LccnSubscription' "400": description: The request is malformed schema: $ref: '#/definitions/ErrorResponse' - "401": - description: Unauthorized, Invalid credentials (client or user) - schema: - $ref: '#/definitions/ErrorResponse' - "404": - description: The specified resource was not found - schema: - $ref: '#/definitions/ErrorResponse' deprecated: false /nfvo/{nfvoId}/subscriptions/{subscriptionId}: get: tags: - - NS Subscriptions + - NS Subscriptions summary: Read an individual subscription resource. description: | The GET method retrieves information about a subscription by reading an individual subscription resource. This method shall support the URI query parameters, request and response data structures, and response codes, as specified in the Tables 6.4.17.3.2-1 and 6.4.17.3.2-2 operationId: NfvoSubscriptionsByNfvoIdAndSubscriptionIdGet produces: - - application/json + - application/hal+json parameters: - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - name: subscriptionId - in: path - description: Identifier of this subscription. - required: true - type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - name: subscriptionId + in: path + description: Identifier of this subscription. + required: true + type: string responses: "200": description: | 200 OK. The operation has completed successfully. The response body shall contain a representation of the subscription resource. headers: {} schema: - $ref: '#/definitions/NSInstanceSubscription' - "400": - description: The request is malformed - schema: - $ref: '#/definitions/ErrorResponse' - "401": - description: Unauthorized, Invalid credentials (client or user) - schema: - $ref: '#/definitions/ErrorResponse' + $ref: '#/definitions/LccnSubscription' "404": description: The specified resource was not found schema: @@ -625,89 +598,57 @@ paths: deprecated: false delete: tags: - - NS Subscriptions + - NS Subscriptions summary: Terminate a subscription. description: | The DELETE method terminates an individual subscription. This method shall support the URI query parameters, request and response data structures, and response codes, as specified in the Tables 6.4.17.3.5-1 and 6.4.17.3.5-2. operationId: NfvoSubscriptionsByNfvoIdAndSubscriptionIdDelete produces: - - application/json + - application/hal+json parameters: - - name: nfvoId - in: path - description: Identifier of a Nfvo - required: true - type: string - - name: subscriptionId - in: path - description: Identifier of this subscription. - required: true - type: string + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - name: subscriptionId + in: path + description: Identifier of this subscription. + required: true + type: string responses: "204": description: | 204 NO CONTENT. The subscription resource has been deleted successfully. The response body shall be empty. headers: {} - "400": - description: The request is malformed - schema: - $ref: '#/definitions/ErrorResponse' - "401": - description: Unauthorized, Invalid credentials (client or user) - schema: - $ref: '#/definitions/ErrorResponse' - "404": - description: The specified resource was not found - schema: - $ref: '#/definitions/ErrorResponse' - deprecated: false - /URI_is_provided_by_the_client_when_creating_the_subscription-NsIdentifierCreationNotification: - get: - tags: - - Notification send - summary: Test the notification endpoint. - description: | - Query NS Instances. - The GET method queries information about multiple NS instances. This method shall support the URI query parameters, request and response data structures, and response codes, as specified in the Tables 6.4.2.3.2-1 and 6.4.2.3.2-2. - operationId: URIIsProvidedByTheClientWhenCreatingTheSubscriptionNsIdentifierCreationNotificationGet - produces: - - application/json - parameters: [] - responses: - "204": - description: | - 204 No Content - Shall be returned when the notification endpoint has been tested successfully. The response body shall be empty. - headers: {} - "400": - description: The request is malformed - schema: - $ref: '#/definitions/ErrorResponse' - "401": - description: Unauthorized, Invalid credentials (client or user) - schema: - $ref: '#/definitions/ErrorResponse' "404": description: The specified resource was not found schema: $ref: '#/definitions/ErrorResponse' deprecated: false + /nfvo/{nfvoId}/notifications: post: tags: - - Notification send - summary: Notify about NS creation + - Notification send + summary: Create new notification about a NS instance hosted at nfvoId description: | + The POST method creates a new notification. If a subscription for the NS instance exists, forward the notification to the subscriber. The POST method delivers a notification from the server to the client. This method shall support the URI query parameters, request and response data structures, and response codes, as specified in the Tables 6.4.18.3.1-1 and 6.4.18.3.1-2. - operationId: URIIsProvidedByTheClientWhenCreatingTheSubscriptionNsIdentifierCreationNotificationPost + operationId: NfvoNotificationPost produces: - - application/json + - application/json parameters: - - in: body - name: body - description: A notification about the creation of a NS identifier and the related NS instance resource. - required: true - schema: - $ref: '#/definitions/body' + - name: nfvoId + in: path + description: Identifier of a Nfvo + required: true + type: string + - in: body + name: body + description: "" + required: false + schema: + $ref: '#/definitions/Notification' responses: "204": description: | @@ -718,88 +659,15 @@ paths: description: The request is malformed schema: $ref: '#/definitions/ErrorResponse' - "401": - description: Unauthorized, Invalid credentials (client or user) - schema: - $ref: '#/definitions/ErrorResponse' - "404": - description: The specified resource was not found - schema: - $ref: '#/definitions/ErrorResponse' - deprecated: false - /URI_is_provided_by_the_client_when_creating_the_subscription-NsIdentifierDeletionNotification: - get: - tags: - - Notification send - summary: Test the notification endpoint.1 - description: | - Query NS Instances. - The GET method queries information about multiple NS instances. This method shall support the URI query parameters, request and response data structures, and response codes, as specified in the Tables 6.4.2.3.2-1 and 6.4.2.3.2-2. - operationId: URIIsProvidedByTheClientWhenCreatingTheSubscriptionNsIdentifierDeletionNotificationGet - produces: - - application/json - parameters: [] - responses: - "204": - description: | - 204 No Content - Shall be returned when the notification endpoint has been tested successfully. The response body shall be empty. - headers: {} - "400": - description: The request is malformed - schema: - $ref: '#/definitions/ErrorResponse' - "401": - description: Unauthorized, Invalid credentials (client or user) - schema: - $ref: '#/definitions/ErrorResponse' - "404": - description: The specified resource was not found - schema: - $ref: '#/definitions/ErrorResponse' - deprecated: false - post: - tags: - - Notification send - summary: Notify about NS lifecycle change - description: | - The POST method delivers a notification from the server to the client. This method shall support the URI query parameters, request and response data structures, and response codes, as specified in the Tables 6.4.18.3.1-1 and 6.4.18.3.1-2. - operationId: URIIsProvidedByTheClientWhenCreatingTheSubscriptionNsIdentifierDeletionNotificationPost - produces: - - application/json - parameters: - - in: body - name: body - description: A notification about the deletion of a NS identifier and the related NS instance resource. - required: true - schema: - $ref: '#/definitions/body_1' - responses: - "204": - description: | - 204 No Content Shall be returned when the notification has been delivered successfully. - headers: {} - "400": - description: The request is malformed - schema: - $ref: '#/definitions/ErrorResponse' - "401": - description: Unauthorized, Invalid credentials (client or user) - schema: - $ref: '#/definitions/ErrorResponse' - "404": - description: The specified resource was not found - schema: - $ref: '#/definitions/ErrorResponse' deprecated: false definitions: NFVO: type: object required: - - id - - name - - site - - type + - id + - name + - site + - type properties: id: type: integer @@ -826,16 +694,16 @@ definitions: NsInstance: type: object required: - - id - - nsInstanceDescription - - nsInstanceName - - nsState - - nsdId + - id + - nsInstanceDescription + - nsInstanceName + - nsState + - nsdId properties: id: description: Identifier of the NS instance. allOf: - - $ref: '#/definitions/Identifier' + - $ref: '#/definitions/Identifier' nsInstanceName: type: string description: Human readable name of the NS instance. @@ -857,21 +725,21 @@ definitions: VnfInstance: type: object required: - - id - - instantiationState - - vnfProductName - - vnfdId + - id + - instantiationState + - vnfProductName + - vnfdId properties: id: description: | Identifier of the VNF instance. allOf: - - $ref: '#/definitions/Identifier' + - $ref: '#/definitions/Identifier' vnfdId: description: | Identifier of the VNFD on which the VNF instance is based. allOf: - - $ref: '#/definitions/Identifier' + - $ref: '#/definitions/Identifier' vnfProductName: type: string description: | @@ -880,14 +748,14 @@ definitions: description: | Identifier of a VIM that manages resources for the VNF instance. allOf: - - $ref: '#/definitions/Identifier' + - $ref: '#/definitions/Identifier' instantiationState: type: string description: | The instantiation state of the VNF. enum: - - NOT_INSTANTIATED - - INSTANTIATED + - NOT_INSTANTIATED + - INSTANTIATED instantiatedVnfInfo: $ref: '#/definitions/VnfInstance_instantiatedVnfInfo' description: | @@ -896,20 +764,26 @@ definitions: type: string description: | The state of the VNF instance. - enum: - - STARTED - - STOPPED example: STARTED + enum: + - STARTED + - STOPPED VnfExtCpInfo: type: object required: - - id + - cpdId + - id properties: id: description: | Identifier of the external CP instance and the related information instance. allOf: - - $ref: '#/definitions/IdentifierInVnf' + - $ref: '#/definitions/IdentifierInVnf' + cpdId: + description: | + Identifier of the external CPD, VnfExtCpd, in the VNFD. + allOf: + - $ref: '#/definitions/IdentifierInVnfd' cpProtocolInfo: type: array description: | @@ -922,33 +796,33 @@ definitions: CpProtocolInfo: type: object required: - - ipOverEthernet - - layerProtocol + - ipOverEthernet + - layerProtocol properties: layerProtocol: type: string description: | The identifier of layer(s) and protocol(s) associated to the network address information. Permitted values: IP_OVER_ETHERNET See note. enum: - - IP_OVER_ETHERNET + - IP_OVER_ETHERNET ipOverEthernet: description: | - IP addresses over Ethernet to assign to the CP or SAP instance. Shall be present if layerProtocol is equal to " IP_OVER_ETHERNET", and shall be absent otherwise. + IP addresses over Ethernet to assign to the CP or SAP instance. Shall be present if layerProtocol is equal to ' IP_OVER_ETHERNET', and shall be absent otherwise. allOf: - - $ref: '#/definitions/IpOverEthernetAddressInfo' + - $ref: '#/definitions/IpOverEthernetAddressInfo' description: | This type describes the protocol layer(s) that a CP or SAP uses together with protocol-related information, like addresses. It shall comply with the provisions defined in Table 6.5.3.58-1. IpOverEthernetAddressInfo: type: object required: - - ipAddresses - - macAddress + - ipAddresses + - macAddress properties: macAddress: description: | Assigned MAC address. allOf: - - $ref: '#/definitions/MacAddress' + - $ref: '#/definitions/MacAddress' ipAddresses: type: array description: | @@ -972,6 +846,11 @@ definitions: format: uuid description: | An identifier that is unique for the respective type within a VNF instance, but may not be globally unique. + IdentifierInVnfd: + type: string + format: uuid + description: | + An identifier that is unique for the respective type within a VNF descriptor, but may not be globally unique. Identifier: type: string format: uuid @@ -986,13 +865,13 @@ definitions: * `INSTANTIATED`: The NS instance is instantiated. example: NOT_INSTANTIATED enum: - - NOT_INSTANTIATED - - INSTANTIATED + - NOT_INSTANTIATED + - INSTANTIATED InstantiateNsRequest: type: object required: - - nsFlavourId - - nsInstantiationLevelId + - nsFlavourId + - nsInstantiationLevelId properties: nsFlavourId: type: string @@ -1004,21 +883,21 @@ definitions: NsLcmOpOcc: type: object required: - - id - - lcmOperationType - - nsInstanceId - - operationState - - startTime - - stateEnteredTime + - id + - lcmOperationType + - nsInstanceId + - operationState + - startTime + - stateEnteredTime properties: id: description: | Identifier of this NS lifecycle operation occurrence. allOf: - - $ref: '#/definitions/Identifier' + - $ref: '#/definitions/Identifier' operationState: allOf: - - $ref: '#/definitions/NsLcmOperationStateType' + - $ref: '#/definitions/NsLcmOperationStateType' stateEnteredTime: type: string format: date-time @@ -1028,10 +907,10 @@ definitions: description: | Identifier of the NS instance to which the operation applies. allOf: - - $ref: '#/definitions/Identifier' + - $ref: '#/definitions/Identifier' lcmOperationType: allOf: - - $ref: '#/definitions/NsLcmOpType' + - $ref: '#/definitions/NsLcmOpType' startTime: type: string format: date-time @@ -1049,22 +928,22 @@ definitions: COMPLETED | The LCM operation has been completed successfully. FAILED | The LCM operation has failed and it cannot be retried or rolled back, as it is determined that such action won't succeed. enum: - - PROCESSING - - COMPLETED - - FAILED + - PROCESSING + - COMPLETED + - FAILED NsLcmOpType: type: string description: | The enumeration NsLcmOpType represents those lifecycle operations that trigger a NS lifecycle management operation occurrence notification. Value | Description ------|------------ - INSTANTIATE | Represents the "Instantiate NS" LCM operation. - SCALE | Represents the "Scale NS" LCM operation. - TERMINATE | Represents the "Terminate NS" LCM operation. + INSTANTIATE | Represents the 'Instantiate NS' LCM operation. + SCALE | Represents the 'Scale NS' LCM operation. + TERMINATE | Represents the 'Terminate NS' LCM operation. enum: - - INSTANTIATE - - SCALE - - TERMINATE + - INSTANTIATE + - SCALE + - TERMINATE ErrorResponse: type: object properties: @@ -1089,25 +968,9 @@ definitions: format: date-time description: |- Timestamp indicating the end time of the NS, i.e. the NS will be terminated - automatically at this timestamp. Cardinality "0" indicates the NS termination + automatically at this timestamp. Cardinality '0' indicates the NS termination takes place immediately. title: TerminateNsRequest - NSInstanceSubscription: - type: object - required: - - callbackUri - - id - properties: - id: - description: An identifier with the intention of being globally unique. - allOf: - - $ref: '#/definitions/Identifier' - filter: - $ref: '#/definitions/NSInstanceSubscription_filter' - callbackUri: - type: string - description: String formatted according to IETF RFC 3986. - title: NSInstanceSubscription nsInstanceSubscriptionFilter1: type: object properties: @@ -1141,13 +1004,13 @@ definitions: body: type: object required: - - nsInstanceId - - subscriptionId + - nsInstanceId + - subscriptionId properties: notificationType: type: string example: NsIdentifierCreationNotification - description: Discriminator for the different notification types. Shall be set to "NsIdentifierCreationNotification" for this notification type. + description: Discriminator for the different notification types. Shall be set to 'NsIdentifierCreationNotification' for this notification type. subscriptionId: type: string description: An identifier with the intention of being globally unique. @@ -1162,13 +1025,13 @@ definitions: body_1: type: object required: - - nsInstanceId - - subscriptionId + - nsInstanceId + - subscriptionId properties: notificationType: type: string example: NsIdentifierDeletionNotification - description: Discriminator for the different notification types. Shall be set to "NsIdentifierDeletionNotification" for this notification type. + description: Discriminator for the different notification types. Shall be set to 'NsIdentifierDeletionNotification' for this notification type. subscriptionId: type: string description: An identifier with the intention of being globally unique. @@ -1207,6 +1070,11 @@ definitions: nsDescription: type: string title: NfvoNsInstancesRequest + NfvoNsInstantiateRequest: + type: object + properties: + additionalParamsForNs: + $ref: '#/definitions/NfvoNsInstantiateRequest_additionalParamsForNs' NfvoNsInstancesScaleRequest: type: object properties: @@ -1219,26 +1087,73 @@ definitions: type: object properties: {} title: NfvoNsInstancesScaleRequest - NfvoSubscriptionsRequest: + LccnSubscription: type: object + required: + - callbackUri + - nsInstanceId properties: - filter: - $ref: '#/definitions/NSInstanceSubscription_filter' callbackUri: type: string - description: String formatted according to IETF RFC 3986. - title: NfvoSubscriptionsRequest + id: + type: integer + format: int64 + notificationTypes: + type: array + items: + type: string + enum: + - NsLcmOperationOccurrenceNotification + - NsIdentifierCreationNotification + - NsIdentifierDeletionNotification + nsInstanceId: + type: string + format: uuid + title: LccnSubscription + CollectionModelLccnSubscription: + type: object + required: + - _embedded + - _links + properties: + _embedded: + $ref: '#/definitions/EmbeddedCollectionLccnSubscription' + _links: + type: object + description: Link collection + additionalProperties: true + title: CollectionModel«LccnSubscription» + xml: + name: entities + attribute: false + wrapped: false + description: Resources of LccnSubscription + EmbeddedCollectionLccnSubscription: + type: object + required: + - subscriptions + properties: + subscriptions: + type: array + description: Resource collection + items: + $ref: '#/definitions/LccnSubscription' + title: EmbeddedCollection«LccnSubscription» + xml: + name: content + wrapped: true + description: Embedded collection of LccnSubscription ScaleType: type: string title: ScaleType example: SCALE_NS enum: - - SCALE_NS - - SCALE_VNF + - SCALE_NS + - SCALE_VNF VnfInstance_instantiatedVnfInfo: type: object required: - - vnfState + - vnfState properties: vnfState: $ref: '#/definitions/VnfOperationalStateType' @@ -1254,18 +1169,71 @@ definitions: IpOverEthernetAddressInfo_ipAddresses: type: object required: - - type + - type properties: type: type: string description: | The type of the IP addresses. Permitted values: IPV4, IPV6. enum: - - IPV4 - - IPV6 + - IPV4 + - IPV6 addresses: type: array description: | - Fixed addresses assigned (from the subnet defined by "subnetId" if provided). + Fixed addresses assigned (from the subnet defined by 'subnetId' if provided). + items: + $ref: '#/definitions/IpAddress' + NfvoNsInstantiateRequest_additionalParamsForNs_vnf: + type: object + properties: + vnfInstanceId: + type: string + vimAccountId: + type: string + NfvoNsInstantiateRequest_additionalParamsForNs_vld: + items: + type: object + properties: + name: + type: string + example: vldnet + vim-network-name: + type: string + example: netVIM1 + NfvoNsInstantiateRequest_additionalParamsForNs: + type: object + properties: + vld: + type: array items: - $ref: '#/definitions/IpAddress' \ No newline at end of file + $ref: '#/definitions/NfvoNsInstantiateRequest_additionalParamsForNs_vld' + vnf: + type: array + items: + $ref: '#/definitions/NfvoNsInstantiateRequest_additionalParamsForNs_vnf' + Notification: + type: object + required: + - nsInstanceId + - operation + - operationState + properties: + nsInstanceId: + type: string + example: string + nsLcmOpOccId: + type: string + example: string + operation: + type: string + example: INSTANTIATE + notificationType: + type: string + example: NsLcmOperationOccurrenceNotification + timestamp: + type: string + example: string + operationState: + type: string + example: PROCESSING diff --git a/uWSGI/app.ini b/uWSGI/app.ini index 7adcd64..e4715e4 100644 --- a/uWSGI/app.ini +++ b/uWSGI/app.ini @@ -20,3 +20,7 @@ vacuum = true die-on-term = true +smart-attach-daemon = /tmp/celery.pid celery -A tasks worker -B -s /tmp/celerybeat-schedule --loglevel=info --pidfile=/tmp/celery.pid + +; will very probably consume more memory, but will run in a more consistent and clean environment +;lazy-apps = true