--- /dev/null
+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
--- /dev/null
+#!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())
--- /dev/null
+# 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"
+
--- /dev/null
+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()
--- /dev/null
+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
--- /dev/null
+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'))
--- /dev/null
+<?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>
--- /dev/null
+Flask
+connexion
+pytest
\ No newline at end of file
--- /dev/null
+#!/usr/bin/env python\r
+\r
+#\r
+# Copyright (c) 2018 Orange. All rights reserved.\r
+#\r
+# Licensed under the Apache License, Version 2.0 (the "License"); you may\r
+# not use this file except in compliance with the License. You may obtain\r
+# a copy of the License at\r
+#\r
+# http://www.apache.org/licenses/LICENSE-2.0\r
+#\r
+# Unless required by applicable law or agreed to in writing, software\r
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT\r
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the\r
+# License for the specific language governing permissions and limitations\r
+# under the License.\r
+#\r
+\r
+import os\r
+from setuptools import setup, find_packages\r
+import sys\r
+\r
+if sys.version_info < (2, 7):\r
+ sys.exit('VNF SDK requires Python 2.7+')\r
+\r
+root_dir = os.path.dirname(__file__)\r
+install_requires = []\r
+extras_require = {}\r
+\r
+with open(os.path.join(root_dir, 'requirements.txt')) as requirements:\r
+ for requirement in requirements.readlines():\r
+ # get rid of comments or trailing comments\r
+ requirement = requirement.split('#')[0].strip()\r
+ if not requirement:\r
+ continue # skip empty and comment lines\r
+ # dependencies which use environment markers have to go in as\r
+ # conditional dependencies under "extra_require", see more at:\r
+ # https://wheel.readthedocs.io/en/latest/index.html#defining-conditional-dependencies\r
+ if ';' in requirement:\r
+ package, condition = requirement.split(';')\r
+ cond_name = ':{0}'.format(condition.strip())\r
+ extras_require.setdefault(cond_name, [])\r
+ extras_require[cond_name].append(package.strip())\r
+ else:\r
+ install_requires.append(requirement)\r
+\r
+setup(\r
+ name='vnfsdk-ice-server',\r
+ version='0.1',\r
+ description='VNF SDK Heat validation tool',\r
+ license='Apache License Version 2.0',\r
+ url='http://onap.org/',\r
+\r
+ packages=find_packages('.'),\r
+# packages=[\r
+# 'heat_test',\r
+# 'heat_test.ice_validator',\r
+# 'heat_test.ice_validator.tests',\r
+# 'heat_test.ice_validator.tests.utils'\r
+# ],\r
+\r
+package_dir={'heat_test': 'heat_test'},\r
+# package_dir={\r
+# 'heat_test': 'src/heat_test',\r
+# 'heat_test.ice_validator': 'src/heat_test/ice_validator',\r
+# 'heat_test.ice_validator.tests': 'src/heat_test/ice_validator/tests',\r
+# 'heat_test.ice_validator.tests.utils': 'src/heat_test/ice_validator/tests/utils'\r
+# },\r
+\r
+ include_package_data=True,\r
+ install_requires=install_requires,\r
+ extras_require=extras_require)\r
+\r
--- /dev/null
+# -*- 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
<modules>
<module>validation-scripts</module>
+ <module>ice-server</module>
</modules>
</project>