diff options
-rw-r--r-- | ice-server/README.MD | 32 | ||||
-rw-r--r-- | ice-server/__init__.py | 0 | ||||
-rw-r--r-- | ice-server/heat_test/__init__.py | 0 | ||||
-rw-r--r-- | ice-server/heat_test/app.py | 65 | ||||
-rw-r--r-- | ice-server/heat_test/default_settings.py | 9 | ||||
-rw-r--r-- | ice-server/heat_test/heat_validator.py | 79 | ||||
-rw-r--r-- | ice-server/heat_test/swagger/ice_api.yaml | 85 | ||||
-rw-r--r-- | ice-server/heat_test/test/fixture/test.zip | bin | 0 -> 166 bytes | |||
-rw-r--r-- | ice-server/heat_test/test/test_api.py | 62 | ||||
-rw-r--r-- | ice-server/pom.xml | 38 | ||||
-rw-r--r-- | ice-server/requirements.txt | 3 | ||||
-rw-r--r-- | ice-server/setup.py | 73 | ||||
-rw-r--r-- | ice-server/tox.ini | 53 | ||||
-rw-r--r-- | pom.xml | 1 |
14 files changed, 500 insertions, 0 deletions
diff --git a/ice-server/README.MD b/ice-server/README.MD new file mode 100644 index 0000000..87a5341 --- /dev/null +++ b/ice-server/README.MD @@ -0,0 +1,32 @@ +Introduction: +============= +This document provides the required steps for running the +heat validation rest service in a development environment. +If you only want to run it, go to distribution directory. + +Installation steps: +==================== +Install following software: +- python 3.4 or latest + +Configuration steps: +==================== +you can override the defaults settings (declared in default_settings.py) +if needed by pointing the env variable ICE_SETTINGS to a file +with the appropriate values : + +``$ export ICE_SETTINGS='/path/to/my/production/settings.py'`` + +How to run? +=========== + - In command console, cd to directory where this service is installed + - use virtualenv if needed + - ``$ pip install -r requirements.txt`` + - check that everything is ok by running unit tests : ``$ tox`` + - ``$ python app.py`` + - Once service is started, from MSB service, verify that "ice-test" is reported from GET request on "/openoapi/microservices/v1/services" + +Debug/development Mode +========== + debug mode is activated by default + in debug mode, the swagger ui is available on http://127.0.0.1:5000/onapapi/ice/v1/ui
\ No newline at end of file diff --git a/ice-server/__init__.py b/ice-server/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ice-server/__init__.py diff --git a/ice-server/heat_test/__init__.py b/ice-server/heat_test/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ice-server/heat_test/__init__.py diff --git a/ice-server/heat_test/app.py b/ice-server/heat_test/app.py new file mode 100644 index 0000000..e982c68 --- /dev/null +++ b/ice-server/heat_test/app.py @@ -0,0 +1,65 @@ +#!flask/bin/python +import argparse +import logging +import requests + +import connexion +from connexion.resolver import RestyResolver + + +def parse_args(): + """ + parse argument parameters + """ + parser = argparse.ArgumentParser(description='start the heat validation rest server') + parser.add_argument("--debug", help="increase output verbosity") + parser.add_argument("-p", "--port", type=int, help="listen port (default 5000)", default=5000) + args = parser.parse_args() + if args.debug: + logging.info("debug mode") + debug = True + else: + debug = False + if args.port: + port = args.port + else: + port = 5000 + + +def create_app(): + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + app = connexion.App(__name__, specification_dir='swagger/') + + # load default config + app.app.config.from_object("default_settings") + app.app.config.from_envvar('ICE_SETTINGS', silent=True) + app.add_api('ice_api.yaml', swagger_ui=app.app.config['DEBUG'], resolver=RestyResolver('ice_validator')) + + return app + +def create_test_app(): + print("create_test_app") + logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + app = connexion.App(__name__, specification_dir='swagger/') + app.add_api('ice_api.yaml', swagger_ui=app.app.config['DEBUG'], resolver=RestyResolver('ice_validator')) + app.app.testing = True + return app + + +def start_app(app): + logging.info("######################################") + logging.info("starting server") + logging.info("######################################") + app.run(port=app.app.config['ICE_PORT'], debug=app.app.config['DEBUG']) + + # register service in MSB + try: + msb_url = app.app.config['MSB_URL'] + requests.get(msb_url) + logging.info("registration to %s done", msb_url) + except: + logging.info("registration to %s failed", msb_url) + + +if __name__ == '__main__': + start_app(create_app()) diff --git a/ice-server/heat_test/default_settings.py b/ice-server/heat_test/default_settings.py new file mode 100644 index 0000000..d32d039 --- /dev/null +++ b/ice-server/heat_test/default_settings.py @@ -0,0 +1,9 @@ +# ICE settings +DEBUG = True +ICE_PORT = 5000 + +# MSB settings +MSB_IP = "127.0.0.1" +MSB_PORT = "80" +MSB_URL = "http://"+MSB_IP+':'+MSB_PORT+"/api/microservices/v1/services" + diff --git a/ice-server/heat_test/heat_validator.py b/ice-server/heat_test/heat_validator.py new file mode 100644 index 0000000..0cc32a1 --- /dev/null +++ b/ice-server/heat_test/heat_validator.py @@ -0,0 +1,79 @@ +import logging +import os +import shutil +import sys +import tempfile +import zipfile +from io import StringIO + +import pytest +from flask import (request, jsonify, abort) + + +class HeatValidator(object): + """REST service for HEAT templates validation""" + + # Customize messages for pytest exit codes... + msg = {0: 'OK', + 1: 'Tests failed', + 2: 'Interrupted', + 3: 'Internal error', + 4: 'Usage error', + 5: 'No tests collected'} + + def ping(self): + return "pong" + + def validate(self): + """validate the heat template contained in the uploaded zipfile + by running the ice_validator scripts""" + logging.info("validate") + + # check if the post request is valid + if 'file' not in request.files: + logging.error("invalid request: no file found") + abort(422, {'status': 1, 'message': 'no file found'}) + + try: + # extract the uploaded archive + zip_file = request.files['file'] + tmp_dir = tempfile.mkdtemp() + zip_ref = zipfile.ZipFile(zip_file, 'r') + zip_ref.extractall(tmp_dir) + zip_ref.close() + debug = request.args.get('debug') + + # execute the validation scripts with pytest + if debug == 'true': + # Save the original stream output, the console basically + original_output = sys.stdout + # Assign StringIO so the output is not sent anymore to the console + sys.stdout = StringIO() + exit_code = pytest.main(['../../validation-scripts/ice_validator', + '--resultlog=' + tmp_dir + '/result.txt', + '--template-dir', tmp_dir]) + with open(tmp_dir + '/result.txt', 'r') as result_file: + result = result_file.read() + if debug == 'true': + output = sys.stdout.getvalue() + # close the stream and reset stdout to the original value (console) + sys.stdout.close() + sys.stdout = original_output + except zipfile.BadZipFile: + logging.exception("invalid file") + abort(422, {'status': 4, 'message': 'invalid file'}) + except: + logging.exception("server error on file") + abort(500, {'status': 3, 'message': 'server error'}) + finally: + if os.path.exists(tmp_dir): + shutil.rmtree(tmp_dir) + + result = {'status': exit_code, 'message': self.msg[exit_code], 'result': result} + if debug == 'true': + result['debug'] = output + + return jsonify(result), 200 if (exit_code == 0) else 422 + + +class_instance = HeatValidator() diff --git a/ice-server/heat_test/swagger/ice_api.yaml b/ice-server/heat_test/swagger/ice_api.yaml new file mode 100644 index 0000000..c12269f --- /dev/null +++ b/ice-server/heat_test/swagger/ice_api.yaml @@ -0,0 +1,85 @@ +swagger: "2.0" + +info: + title: "ICE validation" + description: "Heat template validation rest API" + contact: + name: ONAP + url: https://www.onap.org + email: onap-discuss@lists.onap.org + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: "1.0.0" + +basePath: /onapapi/ice/v1 + +paths: + /: + get: + summary: "list HEAT templates" + operationId: heat_validator.class_instance.ping + responses: + '200': + description: 'server is up and running' + schema: + type: string + post: + summary: "validate HEAT template" + operationId: heat_validator.class_instance.validate + consumes: [ + "multipart/form-data" + ] + produces: [ + "application/json" + ] + parameters: [ + { + "name": "file", + "in": "formData", + "description": "file to upload", + "required": true, + "type": "file" + }, + { + "name": "debug", + "in": "query", + "description": "debug mode", + "required": false, + "type": "boolean" + } + ] + responses: + "200": + description: 'validation success' + schema: { + "$ref": "#/definitions/Result" + } + "400": + description: 'validation error' + schema: { + "$ref": "#/definitions/Result" + } + "500": + description: 'validation error' + schema: { + "$ref": "#/definitions/Result" + } +definitions: + Result: + required: [ + "status", + "message" + ] + properties: { + "status": { + "type": "integer", + "format": "int64" + }, + "message": { + "type": "string" + }, + "debug": { + "type": "string" + } + }
\ No newline at end of file diff --git a/ice-server/heat_test/test/fixture/test.zip b/ice-server/heat_test/test/fixture/test.zip Binary files differnew file mode 100644 index 0000000..d1e6d99 --- /dev/null +++ b/ice-server/heat_test/test/fixture/test.zip diff --git a/ice-server/heat_test/test/test_api.py b/ice-server/heat_test/test/test_api.py new file mode 100644 index 0000000..259a43b --- /dev/null +++ b/ice-server/heat_test/test/test_api.py @@ -0,0 +1,62 @@ +import io +import json +import os +import sys + +import pytest + +#sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir)) + +# Make sure that the application source directory (this directory's parent) is +# on sys.path. +here = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) +sys.path.insert(0, here) +print(sys.path) + +import app as myapp + +ICE_URL = '/ice/' + +flask_app = myapp.create_test_app() + + +@pytest.fixture(scope='module') +def client(): + with flask_app.app.test_client() as c: + yield c + + +def test_404(client): + response = client.get('/dummy') + assert response.status_code == 404 + + +def test_ping(client): + response = client.get(ICE_URL, content_type='application/json') + assert response.status_code == 200 + + +def test_validate_nofile(client): + response = client.post(ICE_URL) + assert response.status_code == 400 + assert json_of_response(response) is not None + + +def test_validate_not_zip(client): + data = {'file': (io.BytesIO(b'my file contents'), 'hello world.txt')} + response = client.post(ICE_URL, data=data) + assert response.status_code == 422 + + +@pytest.mark.skip(reason="no way of currently testing this") +def test_validate_bad_zip(client): + zf = os.path.join(os.path.dirname(__file__), 'fixture/test.zip') + data = {'file': (zf, 'test.zip')} + response = client.post(ICE_URL, data=data) + print("##"+zf) + assert response.status_code == 200 + + +def json_of_response(response): + """Decode json from response""" + return json.loads(response.data.decode('utf8')) diff --git a/ice-server/pom.xml b/ice-server/pom.xml new file mode 100644 index 0000000..3e6d2a5 --- /dev/null +++ b/ice-server/pom.xml @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<!-- +Copyright (c) 2018 Orange. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); you may +not use this file except in compliance with the License. You may obtain +a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +License for the specific language governing permissions and limitations +under the License. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.onap.oparent</groupId> + <artifactId>oparent</artifactId> + <version>1.0.0-SNAPSHOT</version> + <relativePath>../../oparent</relativePath> + </parent> + + <groupId>org.onap.vnfsdk.ice</groupId> + <artifactId>vnf-sdk-ice-server</artifactId> + <packaging>pom</packaging> + + <name>ice rest API</name> + <description>rest API for HEAT template validation</description> + <properties> + <sonar.language>py</sonar.language> + <sonar.pluginName>Python</sonar.pluginName> + <sonar.inclusions>**/*.py</sonar.inclusions> + <sonar.skip>false</sonar.skip> + </properties> +</project> diff --git a/ice-server/requirements.txt b/ice-server/requirements.txt new file mode 100644 index 0000000..4ea1f75 --- /dev/null +++ b/ice-server/requirements.txt @@ -0,0 +1,3 @@ +Flask +connexion +pytest
\ No newline at end of file diff --git a/ice-server/setup.py b/ice-server/setup.py new file mode 100644 index 0000000..b4c5aea --- /dev/null +++ b/ice-server/setup.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python
+
+#
+# Copyright (c) 2018 Orange. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+import os
+from setuptools import setup, find_packages
+import sys
+
+if sys.version_info < (2, 7):
+ sys.exit('VNF SDK requires Python 2.7+')
+
+root_dir = os.path.dirname(__file__)
+install_requires = []
+extras_require = {}
+
+with open(os.path.join(root_dir, 'requirements.txt')) as requirements:
+ for requirement in requirements.readlines():
+ # get rid of comments or trailing comments
+ requirement = requirement.split('#')[0].strip()
+ if not requirement:
+ continue # skip empty and comment lines
+ # dependencies which use environment markers have to go in as
+ # conditional dependencies under "extra_require", see more at:
+ # https://wheel.readthedocs.io/en/latest/index.html#defining-conditional-dependencies
+ if ';' in requirement:
+ package, condition = requirement.split(';')
+ cond_name = ':{0}'.format(condition.strip())
+ extras_require.setdefault(cond_name, [])
+ extras_require[cond_name].append(package.strip())
+ else:
+ install_requires.append(requirement)
+
+setup(
+ name='vnfsdk-ice-server',
+ version='0.1',
+ description='VNF SDK Heat validation tool',
+ license='Apache License Version 2.0',
+ url='http://onap.org/',
+
+ packages=find_packages('.'),
+# packages=[
+# 'heat_test',
+# 'heat_test.ice_validator',
+# 'heat_test.ice_validator.tests',
+# 'heat_test.ice_validator.tests.utils'
+# ],
+
+package_dir={'heat_test': 'heat_test'},
+# package_dir={
+# 'heat_test': 'src/heat_test',
+# 'heat_test.ice_validator': 'src/heat_test/ice_validator',
+# 'heat_test.ice_validator.tests': 'src/heat_test/ice_validator/tests',
+# 'heat_test.ice_validator.tests.utils': 'src/heat_test/ice_validator/tests/utils'
+# },
+
+ include_package_data=True,
+ install_requires=install_requires,
+ extras_require=extras_require)
+
diff --git a/ice-server/tox.ini b/ice-server/tox.ini new file mode 100644 index 0000000..8da170d --- /dev/null +++ b/ice-server/tox.ini @@ -0,0 +1,53 @@ +# -*- coding: utf8 -*- +# ============LICENSE_START======================================================= +# org.onap.vvp/validation-scripts +# =================================================================== +# Copyright © 2017 AT&T Intellectual Property. All rights reserved. +# =================================================================== +# +# Unless otherwise specified, all software contained herein is licensed +# under the Apache License, Version 2.0 (the “License”); +# you may not use this software 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. +# +# +# +# Unless otherwise specified, all documentation contained herein is licensed +# under the Creative Commons License, Attribution 4.0 Intl. (the “License”); +# you may not use this documentation except in compliance with the License. +# You may obtain a copy of the License at +# +# https://creativecommons.org/licenses/by/4.0/ +# +# Unless required by applicable law or agreed to in writing, documentation +# 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. +# +# ============LICENSE_END============================================ +# +# ECOMP is a trademark and service mark of AT&T Intellectual Property. +# + +[tox] +skipsdist=True +envlist = py3 + +[testenv] +distribute = False +commands = + {envpython} --version + pytest +deps = -rrequirements.txt + +[testenv:py3] +basepython=python3.5 @@ -32,5 +32,6 @@ under the License. <modules> <module>validation-scripts</module> + <module>ice-server</module> </modules> </project> |