Adding the generic solver code
[optf/osdf.git] / osdf / apps / baseapp.py
1 # -------------------------------------------------------------------------
2 #   Copyright (c) 2015-2017 AT&T Intellectual Property
3 #
4 #   Licensed under the Apache License, Version 2.0 (the "License");
5 #   you may not use this file except in compliance with the License.
6 #   You may obtain a copy of the License at
7 #
8 #       http://www.apache.org/licenses/LICENSE-2.0
9 #
10 #   Unless required by applicable law or agreed to in writing, software
11 #   distributed under the License is distributed on an "AS IS" BASIS,
12 #   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 #   See the License for the specific language governing permissions and
14 #   limitations under the License.
15 #
16 # -------------------------------------------------------------------------
17 #
18
19 """
20 OSDF Manager Main Flask Application
21 """
22
23 import json
24 import ssl
25 import sys
26 import time
27 import traceback
28 from optparse import OptionParser
29
30 import osdf.adapters.aaf.sms as sms
31 import osdf.operation.responses
32 import pydevd
33 from flask import Flask, request, Response, g
34 from osdf.config.base import osdf_config
35 from osdf.logging.osdf_logging import error_log, debug_log
36 from osdf.operation.error_handling import request_exception_to_json_body, internal_error_message
37 from osdf.operation.exceptions import BusinessException
38 from osdf.utils.mdc_utils import clear_mdc, mdc_from_json, default_mdc, get_request_id
39 from requests import RequestException
40 from schematics.exceptions import DataError
41
42 ERROR_TEMPLATE = osdf.ERROR_TEMPLATE
43
44 app = Flask(__name__)
45
46 BAD_CLIENT_REQUEST_MESSAGE = 'Client sent an invalid request'
47
48
49 @app.errorhandler(BusinessException)
50 def handle_business_exception(e):
51     """An exception explicitly raised due to some business rule"""
52     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
53     err_msg = ERROR_TEMPLATE.render(description=str(e))
54     response = Response(err_msg, content_type='application/json; charset=utf-8')
55     response.status_code = 400
56     return response
57
58
59 @app.errorhandler(RequestException)
60 def handle_request_exception(e):
61     """Returns a detailed synchronous message to the calling client
62     when osdf fails due to a remote call to another system"""
63     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
64     err_msg = request_exception_to_json_body(e)
65     response = Response(err_msg, content_type='application/json; charset=utf-8')
66     response.status_code = 400
67     return response
68
69
70 @app.errorhandler(DataError)
71 def handle_data_error(e):
72     """Returns a detailed message to the calling client when the initial synchronous message is invalid"""
73     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
74
75     body_dictionary = {
76         "serviceException": {
77             "text": BAD_CLIENT_REQUEST_MESSAGE,
78             "exceptionMessage": str(e.errors),
79             "errorType": "InvalidClientRequest"
80         }
81     }
82
83     body_as_json = json.dumps(body_dictionary)
84     response = Response(body_as_json, content_type='application/json; charset=utf-8')
85     response.status_code = 400
86     return response
87
88
89 @app.before_request
90 def log_request():
91     g.request_start = time.process_time()
92     if request.data:
93         if request.get_json():
94             request_json = request.get_json()
95             g.request_id = get_request_id(request_json)
96             mdc_from_json(request_json)
97         else:
98             g.request_id = "N/A"
99             default_mdc()
100     else:
101         g.request_id = "N/A"
102         default_mdc()
103
104
105 @app.after_request
106 def log_response(response):
107     clear_mdc()
108     return response
109
110
111 @app.errorhandler(500)
112 def internal_failure(error):
113     """Returned when unexpected coding errors occur during initial synchronous processing"""
114     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
115     response = Response(internal_error_message, content_type='application/json; charset=utf-8')
116     response.status_code = 500
117     return response
118
119
120 def get_options(argv):
121     program_version_string = '%%prog %s' % "v1.0"
122     program_longdesc = ""
123     program_license = ""
124
125     parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
126     parser.add_option("-l", "--local", dest="local", help="run locally", action="store_true", default=False)
127     parser.add_option("-t", "--devtest", dest="devtest", help="run in dev/test environment", action="store_true",
128                       default=False)
129     parser.add_option("-d", "--debughost", dest="debughost", help="IP Address of host running debug server", default='')
130     parser.add_option("-p", "--debugport", dest="debugport", help="Port number of debug server", type=int, default=5678)
131     opts, args = parser.parse_args(argv)
132
133     if opts.debughost:
134         debug_log.debug('pydevd.settrace({}, port={})'.format(opts.debughost, opts.debugport))
135         pydevd.settrace(opts.debughost, port=opts.debugport)
136     return opts
137
138
139 def build_ssl_context():
140     ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
141     ssl_context.set_ciphers('ECDHE-RSA-AES128-SHA256:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH')
142     ssl_context.load_cert_chain(sys_conf['ssl_context'][0], sys_conf['ssl_context'][1])
143     return ssl_context
144
145
146 def run_app():
147     global sys_conf
148     sys_conf = osdf_config['core']['osdf_system']
149     ports = sys_conf['osdf_ports']
150     internal_port, external_port = ports['internal'], ports['external']
151     local_host = sys_conf['osdf_ip_default']
152     common_app_opts = dict(host=local_host, threaded=True, use_reloader=False)
153     ssl_opts = sys_conf.get('ssl_context')
154     if ssl_opts:
155         common_app_opts.update({'ssl_context': build_ssl_context()})
156     opts = get_options(sys.argv)
157     # Load secrets from SMS
158     sms.load_secrets()
159     if not opts.local and not opts.devtest:  # normal deployment
160         app.run(port=internal_port, debug=False, **common_app_opts)
161     else:
162         port = internal_port if opts.local else external_port
163         app.run(port=port, debug=True, **common_app_opts)
164
165
166 if __name__ == "__main__":
167     run_app()