Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added REST endpoint for packaging + fixes #98

Merged
merged 14 commits into from
Aug 21, 2019
Merged
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,15 @@ This will start the tool in service mode running in the terminal forever until s

##### Locally-build images

The simplest option is using Docker Compose.

```bash
docker-compose up
```

Alternatively, you can also build and run the Docker container manually.
The commands here don't attach to the volume, i.e., projects are not stored persistently and lost after restart.

```bash
pipeline/build/build.sh
docker run --rm -d -p 5098:5098 --name tng-sdk-project registry.sonata-nfv.eu:5000/tng-sdk-project
Expand Down
42 changes: 42 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Copyright (c) 2015 SONATA-NFV, 5GTANGO, UBIWHERE, Paderborn University
# ALL RIGHTS RESERVED.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
# Neither the name of the SONATA-NFV, 5GTANGO, UBIWHERE, Paderborn University
# nor the names of its contributors may be used to endorse or promote
# products derived from this software without specific prior written
# permission.
#
# This work has been performed in the framework of the SONATA project,
# funded by the European Commission under Grant number 671517 through
# the Horizon 2020 and 5G-PPP programmes. The authors would like to
# acknowledge the contributions of their colleagues of the SONATA
# partner consortium (www.sonata-nfv.eu).
#
# This work has also been performed in the framework of the 5GTANGO project,
# funded by the European Commission under Grant number 761493 through
# the Horizon 2020 and 5G-PPP programmes. The authors would like to
# acknowledge the contributions of their colleagues of the SONATA
# partner consortium (www.5gtango.eu).


version: '3.7'

services:
tng-sdk-project:
build: .
image: registry.sonata-nfv.eu:5000/tng-sdk-project
container_name: tng-sdk-project
ports:
- 5098:5098
2 changes: 1 addition & 1 deletion docs/rest_api.json

Large diffs are not rendered by default.

67 changes: 0 additions & 67 deletions example-project/nstd-example.yml

This file was deleted.

4 changes: 0 additions & 4 deletions example-project/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,3 @@ files:
type: application/vnd.5gtango.slad
tags:
- eu.5gtango
- path: nstd-example.yml
type: application/vnd.5gtango.nstd
tags:
- eu.5gtango
3 changes: 1 addition & 2 deletions pipeline/publish/publish.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
set -e
docker push registry.sonata-nfv.eu:5000/tng-sdk-project

# publish to dev, which is then pushed to Docker Hub
echo 'Tagging and pushing :dev'
echo 'Tagging and pushing'
docker tag registry.sonata-nfv.eu:5000/tng-sdk-project:latest sonatanfv/tng-sdk-project:latest
docker push sonatanfv/tng-sdk-project:latest
2 changes: 2 additions & 0 deletions src/tngsdk/descriptorgen/descriptorgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ def generate(args=None):
else:
coloredlogs.install(level='INFO')

log.info("Generating descriptors with args {}".format(args))

# generate and save tango descriptors
if not args.osm:
descriptors = tango.generate_descriptors(args, log)
Expand Down
72 changes: 63 additions & 9 deletions src/tngsdk/rest.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
import shutil
import zipfile
from flask import Flask, Blueprint, send_from_directory
from flask_restplus import Resource, Api, Namespace, fields
from flask_restplus import Resource, Api, Namespace, fields, inputs
from werkzeug.middleware.proxy_fix import ProxyFix
from werkzeug.datastructures import FileStorage
from tngsdk import cli
Expand Down Expand Up @@ -87,20 +87,20 @@
project_parser.add_argument("image_names",
type=str,
required=False,
default="",
help="List of VNF image names (space-separated)")
project_parser.add_argument("image_types",
type=str,
required=False,
default="",
help="List of VNF image types (space-separated)")
project_parser.add_argument("only_tango",
type=bool,
# do not use "bool"!
# https://github.com/noirbizarre/flask-restplus/issues/199#issuecomment-276645303
type=inputs.boolean,
required=False,
default=False,
help="Generate only 5GTANGO descriptors")
project_parser.add_argument("only_osm",
type=bool,
type=inputs.boolean,
required=False,
default=False,
help="Generate only OSM descriptors")
Expand All @@ -119,6 +119,10 @@
filename_parser = api_v1.parser()
filename_parser.add_argument("filename", required=True, help="Filename of the file to remove")

package_parser = api_v1.parser()
package_parser.add_argument("skip_validation", required=False, type=inputs.boolean, default=False,
help="If true, skip validation when packaging. Else validate first.")

# models for marshaling return values from the API (also used for generating Swagger spec)
ping_get_model = api_v1.model("PingGet", {
"alive_since": fields.String(description="system uptime", required=True)
Expand Down Expand Up @@ -161,6 +165,12 @@
"error_msg": fields.String(description="error message")
})

package_post_model = api_v1.model("PackagePost", {
"project_uuid": fields.String(description="project UUID"),
"package_path": fields.String(description="Path of the created package"),
"error_msg": fields.String(description="error message")
})


def dump_swagger():
"""Dump Swagger specification in docs/rest_api.json"""
Expand Down Expand Up @@ -223,9 +233,10 @@ def post(self):
if v:
dgn_args.append('--osm')
elif k == 'image_names' or k == 'image_types':
dgn_args.append('--' + k)
# split multiple image names/types and append all of them
dgn_args.extend(v.split(' '))
if v is not None:
dgn_args.append('--' + k)
# split multiple image names/types and append all of them
dgn_args.extend(v.split(' '))
else:
# convert #vnfs from int to str (CLI only accepts string)
if k == 'vnfs':
Expand Down Expand Up @@ -362,7 +373,7 @@ def delete(self, project_uuid):
return {"project_uuid": project_uuid, "removed_file": filename, "error_msg": project.error_msg}


# TODO: do we even need this if we have the packager?
# not needed and implementation not completed; just left for possible future use
@api_v1.deprecated
@api_v1.route("/projects/<string:project_uuid>/download")
class ProjectDownload(Resource):
Expand All @@ -389,3 +400,46 @@ def get(self, project_uuid):

# TODO: return the zipped project; async?
return "not implemented", 501


@api_v1.route("/projects/<string:project_uuid>/package")
class ProjectPackage(Resource):
@api_v1.expect(package_parser)
@api_v1.marshal_with(package_post_model)
@api_v1.response(200, 'OK')
@api_v1.response(404, "Project not found")
@api_v1.response(500, "Packaging error")
@api_v1.response(503, "tng-sdk-package not installed")
def post(self, project_uuid):
"""Package (and validate) the specified project using tng-sdk-package (if installed)"""
args = package_parser.parse_args()
log.info("POST to /projects/{}/package with args: {}".format(project_uuid, args))

# try to load the project
project_path = os.path.join('projects', project_uuid)
if not os.path.isdir(project_path):
log.error("No project found with name/UUID {}".format(project_uuid))
return {'error_msg': "Project not found: {}".format(project_uuid)}, 404

# try to import the packager if it's installed
try:
import tngsdk.package
except BaseException as ex:
log.error("Cancel packaging: tng-sdk-package not installed")
log.debug(ex)
return {'error_msg': "Cancel packaging: tng-sdk-package not installed"}, 503

# call the packager
args = [
'--package', project_path,
'--output', project_path,
]
if args.skip_validation:
log.debug("Skipping validation")
args.append('--skip-validation')
r = tngsdk.package.run(args)
if r.error is not None:
return {'error_msg': "Package error: {}".format(r.error)}, 500
pkg_path = r.metadata.get("_storage_location")
log.debug("Packaged to {}".format(pkg_path))
return {'project_uuid': project_uuid, 'package_path': pkg_path, 'error_msg': None}
3 changes: 0 additions & 3 deletions tests/test_project_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,3 @@ def test_example_project_descriptors(self, workspace):

rpds = example_project.get_rpds()
assert rpds == ['rpd-example.yml']

nstds = example_project.get_nstds()
assert nstds == ['nstd-example.yml']