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 byteify
30 from utils import RunLock
32 from __init__ import __version__
35 STATUS_ERROR = 'ERROR'
36 STATUS_PENDING = 'PENDING'
37 STATUS_NOT_FOUND = 'NOT_FOUND'
39 def result_json(status, message, request_id=None):
42 'error_message': message
44 if request_id is not None:
45 body['request_id'] = request_id
51 return json.loads(json.dumps(data), object_hook=byteify)
55 return uuid.uuid4().hex
60 run_queue = Queue.Queue()
68 def enqueue(config, request_id):
70 config['request_id'] = request_id
71 Ctx.run_queue.put(config)
73 if len(Ctx.ids) >= Ctx.MAXLEN:
75 del Ctx.results[Ctx.ids.pop(0)]
78 Ctx.ids.append(request_id)
82 config = Ctx.run_queue.get()
83 Ctx.current_id = config['request_id']
93 res['request_id'] = Ctx.current_id
94 Ctx.results[Ctx.current_id] = res
98 def get_result(request_id=None):
101 res = Ctx.results[request_id]
105 if Ctx.result and request_id == Ctx.result['request_id']:
120 def get_current_request_id():
121 return Ctx.current_id
125 app = Flask(__name__)
126 busy_json = result_json(STATUS_ERROR, 'there is already an NFVbench request running')
127 not_busy_json = result_json(STATUS_ERROR, 'no pending NFVbench run')
128 not_found_msg = 'results not found'
129 pending_msg = 'NFVbench run still pending'
131 # --------- HTTP requests ------------
133 @app.route('/version', methods=['GET'])
137 @app.route('/start_run', methods=['POST'])
139 config = load_json(request.json)
143 return jsonify(busy_json)
144 request_id = get_uuid()
145 Ctx.enqueue(config, request_id)
146 return jsonify(result_json(STATUS_PENDING, pending_msg, request_id))
148 @app.route('/status', defaults={'request_id': None}, methods=['GET'])
149 @app.route('/status/<request_id>', methods=['GET'])
150 def _get_status(request_id):
152 if Ctx.is_busy() and request_id == Ctx.get_current_request_id():
153 # task with request_id still pending
154 return jsonify(result_json(STATUS_PENDING, pending_msg, request_id))
156 res = Ctx.get_result(request_id)
158 # found result for given request_id
160 # result for given request_id not found
161 return jsonify(result_json(STATUS_NOT_FOUND, not_found_msg, request_id))
164 # task still pending, return with request_id
165 return jsonify(result_json(STATUS_PENDING,
167 Ctx.get_current_request_id()))
169 res = Ctx.get_result()
172 return jsonify(not_busy_json)
177 class WebServer(object):
178 """This class takes care of the web server. Caller should simply create an instance
179 of this class and pass a runner object then invoke the run method
182 def __init__(self, runner, fluent_logger):
183 self.nfvbench_runner = runner
184 self.app = setup_flask()
185 self.fluent_logger = fluent_logger
187 def run(self, host, port):
189 # app.run will not return so we need to run it in a background thread so that
190 # the calling thread (main thread) can keep doing work
191 Thread(target=self.app.run, args=(host, port)).start()
193 # wait for run requests
194 # the runner must be executed from the main thread (Trex client library requirement)
197 # print 'main thread waiting for requests...'
198 config = Ctx.dequeue()
199 # print 'main thread processing request...'
202 # remove unfilled values as we do not want them to override default values with None
203 config = {k: v for k, v in config.items() if v is not None}
205 if self.fluent_logger:
206 self.fluent_logger.start_new_run()
207 results = self.nfvbench_runner.run(config, config)
208 except Exception as exc:
209 results = result_json(STATUS_ERROR, str(exc))
210 LOG.exception('NFVbench runner exception:')
212 # this might overwrite a previously unfetched result
213 Ctx.set_result(results)
215 summary = NFVBenchSummarizer(results['result'], self.fluent_logger)
216 LOG.info(str(summary))
218 # in case of error, 'result' might be missing
219 if 'error_message' in results:
220 LOG.error(results['error_message'])
222 LOG.error('REST request completed without results or error message')
224 if self.fluent_logger:
225 self.fluent_logger.send_run_summary(True)