diff --git a/dragonflow/cmd/eventlet/df_rest_service.py b/dragonflow/cmd/eventlet/df_rest_service.py new file mode 100644 index 000000000..2290aff6f --- /dev/null +++ b/dragonflow/cmd/eventlet/df_rest_service.py @@ -0,0 +1,126 @@ +# 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 argparse + +import bottle +from http import HTTPStatus # Only from Python 3.5 +from oslo_serialization import jsonutils + +import dragonflow.common.exceptions as df_exceptions +from dragonflow.common import utils +from dragonflow.db import api_nb +from dragonflow.db import model_framework as mf +from dragonflow.db.models import all # noqa + + +def nbapi_decorator(f): + # f(nbapi, ...) -> f(...) + def wrapper(*args, **kwargs): + # REVISIT(oanson) We might get away with using functools, but we + # need nbapi to be instantiated after argument parsing. + nbapi = api_nb.NbApi.get_instance() + return f(nbapi, *args, **kwargs) + return wrapper + + +def model_decorator(f): + def wrapper(*args, **kwargs): + name = kwargs['name'] + try: + model = mf.get_model(name) + except KeyError: + bottle.abort(HTTPStatus.NOT_FOUND.value, + "Model '%s' not found" % (name,)) + return f(model, *args, **kwargs) + return wrapper + + +@bottle.get('/') +@model_decorator +@nbapi_decorator +def get_all(nbapi, model, name): + instances = nbapi.get_all(model) + result = [i.to_struct() for i in instances] + bottle.response.content_type = 'application/json' + return jsonutils.dumps(result) + + +@bottle.post('/') +@model_decorator +@nbapi_decorator +def create(nbapi, model, name): + """POST is create! Create a new instance""" + json_data_dict = bottle.json + if not json_data_dict: + bottle.abort(HTTPStatus.PRECONDITION_FAILED.value, + "JSON content required") + instance = model(**json_data_dict) + nbapi.create(instance) + bottle.response.status = HTTPStatus.CREATED.value + + +@bottle.put('/') +@model_decorator +@nbapi_decorator +def update(nbapi, model, name): + """PUT is update! Update an existing instance""" + json_data_dict = bottle.json + if not json_data_dict: + bottle.abort(HTTPStatus.PRECONDITION_FAILED.value, + "JSON content required") + instance = model(**json_data_dict) + try: + nbapi.update(instance) + except df_exceptions.DBKeyNotFound: + bottle.abort(HTTPStatus.NOT_FOUND.value, + "Model instance '%s/%s' not found" % (name, instance.id)) + bottle.response.status = HTTPStatus.NO_CONTENT.value + + +@bottle.get('//') +@model_decorator +@nbapi_decorator +def get(nbapi, model, name, id_): + instance = nbapi.get(model(id=id_)) + if not instance: + bottle.abort(HTTPStatus.NOT_FOUND.value, + "Model instance '%s/%s' not found" % (name, id_)) + bottle.response.content_type = 'application/json' + return instance.to_json() + + +@bottle.delete('//') +@model_decorator +@nbapi_decorator +def delete(nbapi, model, name, id_): + instance = nbapi.get(model(id=id_)) + if not instance: + bottle.abort(HTTPStatus.NOT_FOUND.value, + "Model instance '%s/%s' not found" % (name, id_)) + nbapi.delete(instance) + bottle.response.status = HTTPStatus.NO_CONTENT.value + + +def main(): + parser = argparse.ArgumentParser(description='Dragonflow REST server') + parser.add_argument('--host', type=str, default='127.0.0.1', + help='Host to listen on (127.0.0.1)') + parser.add_argument('--port', type=int, default=8080, + help='Port to listen on (8080)') + parser.add_argument('--config', type=str, + default='/etc/dragonflow/dragonflow.ini', + help=('Dragonflow config file ' + '(/etc/dragonflow/dragonflow.ini)')) + args = parser.parse_args() + utils.config_init(None, [args.config]) + bottle.run(host=args.host, port=args.port) diff --git a/dragonflow/common/utils.py b/dragonflow/common/utils.py index 04f48fb83..05df794bb 100644 --- a/dragonflow/common/utils.py +++ b/dragonflow/common/utils.py @@ -53,10 +53,15 @@ def etcdir(*p): def config_parse(conf=None, args=None, **kwargs): + config_files = [etcdir('neutron.conf'), etcdir('dragonflow.ini')] + config_init(conf, config_files, args, **kwargs) + + +def config_init(conf, config_files, args=None, **kwargs): if args is None: args = [] - args += ['--config-file', etcdir('neutron.conf')] - args += ['--config-file', etcdir('dragonflow.ini')] + for conf_file in config_files: + args.extend(['--config-file', conf_file]) if conf is None: release = version.version_info.release_string() cfg.CONF(args=args, project='dragonflow', diff --git a/setup.cfg b/setup.cfg index 7bbb64aad..23451278c 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,7 @@ console_scripts = df-bgp-service = dragonflow.cmd.eventlet.df_bgp_service:main df-skydive-service = dragonflow.cmd.df_skydive_service:service_main dragonflow-status = dragonflow.cmd.status:main + df-rest-service = dragonflow.cmd.eventlet.df_rest_service:main dragonflow.pubsub_driver = zmq_pubsub_driver = dragonflow.db.pubsub_drivers.zmq_pubsub_driver:ZMQPubSubConnect zmq_bind_pubsub_driver = dragonflow.db.pubsub_drivers.zmq_pubsub_driver:ZMQPubSubBind