2 # Copyright 2017 Cisco Systems, Inc. All rights reserved.
4 # Licensed under the Apache License, Version 2.0 (the "License"); you may
5 # not use this file except in compliance with the License. You may obtain
6 # a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 # License for the specific language governing permissions and limitations
19 from threading import Thread
22 from flask import Flask
23 from flask import jsonify
24 from flask import request
26 from .summarizer import NFVBenchSummarizer
29 from .utils import RunLock
31 from .__init__ import __version__
34 STATUS_ERROR = 'ERROR'
35 STATUS_PENDING = 'PENDING'
36 STATUS_NOT_FOUND = 'NOT_FOUND'
38 def result_json(status, message, request_id=None):
41 'error_message': message
43 if request_id is not None:
44 body['request_id'] = request_id
50 return json.loads(json.dumps(data))
54 return uuid.uuid4().hex
59 run_queue = queue.Queue()
67 def enqueue(config, request_id):
69 config['request_id'] = request_id
70 Ctx.run_queue.put(config)
72 if len(Ctx.ids) >= Ctx.MAXLEN:
74 del Ctx.results[Ctx.ids.pop(0)]
77 Ctx.ids.append(request_id)
81 config = Ctx.run_queue.get()
82 Ctx.current_id = config['request_id']
92 res['request_id'] = Ctx.current_id
93 Ctx.results[Ctx.current_id] = res
97 def get_result(request_id=None):
100 res = Ctx.results[request_id]
103 # pylint: disable=unsubscriptable-object
104 if Ctx.result and request_id == Ctx.result['request_id']:
107 # pylint: enable=unsubscriptable-object
118 def get_current_request_id():
119 return Ctx.current_id
123 app = Flask(__name__)
124 busy_json = result_json(STATUS_ERROR, 'there is already an NFVbench request running')
125 not_busy_json = result_json(STATUS_ERROR, 'no pending NFVbench run')
126 not_found_msg = 'results not found'
127 pending_msg = 'NFVbench run still pending'
129 # --------- HTTP requests ------------
131 @app.route('/version', methods=['GET'])
135 @app.route('/start_run', methods=['POST'])
137 config = load_json(request.json)
141 return jsonify(busy_json)
142 request_id = get_uuid()
143 Ctx.enqueue(config, request_id)
144 return jsonify(result_json(STATUS_PENDING, pending_msg, request_id))
146 @app.route('/status', defaults={'request_id': None}, methods=['GET'])
147 @app.route('/status/<request_id>', methods=['GET'])
148 def _get_status(request_id):
150 if Ctx.is_busy() and request_id == Ctx.get_current_request_id():
151 # task with request_id still pending
152 return jsonify(result_json(STATUS_PENDING, pending_msg, request_id))
154 res = Ctx.get_result(request_id)
156 # found result for given request_id
158 # result for given request_id not found
159 return jsonify(result_json(STATUS_NOT_FOUND, not_found_msg, request_id))
161 # task still pending, return with request_id
162 return jsonify(result_json(STATUS_PENDING,
164 Ctx.get_current_request_id()))
166 res = Ctx.get_result()
169 return jsonify(not_busy_json)
173 class WebServer(object):
174 """This class takes care of the web server. Caller should simply create an instance
175 of this class and pass a runner object then invoke the run method
178 def __init__(self, runner, fluent_logger):
179 self.nfvbench_runner = runner
180 self.app = setup_flask()
181 self.fluent_logger = fluent_logger
183 def run(self, host, port):
185 # app.run will not return so we need to run it in a background thread so that
186 # the calling thread (main thread) can keep doing work
187 Thread(target=self.app.run, args=(host, port)).start()
189 # wait for run requests
190 # the runner must be executed from the main thread (Trex client library requirement)
193 # print 'main thread waiting for requests...'
194 config = Ctx.dequeue()
195 # print 'main thread processing request...'
198 # remove unfilled values as we do not want them to override default values with None
199 config = {k: v for k, v in list(config.items()) if v is not None}
201 if self.fluent_logger:
202 self.fluent_logger.start_new_run()
203 results = self.nfvbench_runner.run(config, config)
204 except Exception as exc:
205 results = result_json(STATUS_ERROR, str(exc))
206 LOG.exception('NFVbench runner exception:')
208 # this might overwrite a previously unfetched result
209 Ctx.set_result(results)
211 summary = NFVBenchSummarizer(results['result'], self.fluent_logger)
212 LOG.info(str(summary))
213 if 'json' in config and 'result' in results and results['status']:
214 self.nfvbench_runner.save(results['result'])
216 # in case of error, 'result' might be missing
217 if 'error_message' in results:
218 LOG.error(results['error_message'])
220 LOG.error('REST request completed without results or error message')
222 if self.fluent_logger:
223 self.fluent_logger.send_run_summary(True)