summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMickael JEZEQUEL <mickael.jezequel@orange.com>2018-03-07 17:19:48 +0100
committerMickael JEZEQUEL <mickael.jezequel@orange.com>2018-03-07 17:21:45 +0100
commitb2727af1d234bae3e64cf31a0af3dfee2d1c91f8 (patch)
tree40556f1679e2b9521c21598a43d0f4a71e6c2915
parentd5d7097ce064a9711cd7415d100db2560ef7ff08 (diff)
implement rest API for ICE tests
Change-Id: I1ccb630858907161c4e8b13986fe963bb309b608 Issue-ID: VNFSDK-213 Signed-off-by: Mickael JEZEQUEL <mickael.jezequel@orange.com>
-rw-r--r--ice-server/README.MD32
-rw-r--r--ice-server/__init__.py0
-rw-r--r--ice-server/heat_test/__init__.py0
-rw-r--r--ice-server/heat_test/app.py65
-rw-r--r--ice-server/heat_test/default_settings.py9
-rw-r--r--ice-server/heat_test/heat_validator.py79
-rw-r--r--ice-server/heat_test/swagger/ice_api.yaml85
-rw-r--r--ice-server/heat_test/test/fixture/test.zipbin0 -> 166 bytes
-rw-r--r--ice-server/heat_test/test/test_api.py62
-rw-r--r--ice-server/pom.xml38
-rw-r--r--ice-server/requirements.txt3
-rw-r--r--ice-server/setup.py73
-rw-r--r--ice-server/tox.ini53
-rw-r--r--pom.xml1
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
new file mode 100644
index 0000000..d1e6d99
--- /dev/null
+++ b/ice-server/heat_test/test/fixture/test.zip
Binary files differ
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
diff --git a/pom.xml b/pom.xml
index 0632691..0a941ea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -32,5 +32,6 @@ under the License.
<modules>
<module>validation-scripts</module>
+ <module>ice-server</module>
</modules>
</project>