Fix issues in Slice selection
[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 from optparse import OptionParser
25 import ssl
26 import sys
27 import time
28 import traceback
29
30 from flask import Flask
31 from flask import g
32 from flask import request
33 from flask import Response
34 from onaplogging.mdcContext import MDC
35 import pydevd
36 from requests import RequestException
37 from schematics.exceptions import DataError
38
39 import osdf.adapters.aaf.sms as sms
40 from osdf.config.base import osdf_config
41 from osdf.logging.osdf_logging import audit_log
42 from osdf.logging.osdf_logging import debug_log
43 from osdf.logging.osdf_logging import error_log
44 from osdf.operation.error_handling import internal_error_message
45 from osdf.operation.error_handling import request_exception_to_json_body
46 from osdf.operation.exceptions import BusinessException
47 import osdf.operation.responses
48 from osdf.utils.mdc_utils import clear_mdc
49 from osdf.utils.mdc_utils import get_request_id
50 from osdf.utils.mdc_utils import populate_default_mdc
51 from osdf.utils.mdc_utils import populate_mdc
52 from osdf.utils.mdc_utils import set_error_details
53
54 ERROR_TEMPLATE = osdf.ERROR_TEMPLATE
55
56 app = Flask(__name__)
57
58 BAD_CLIENT_REQUEST_MESSAGE = 'Client sent an invalid request'
59
60
61 @app.errorhandler(BusinessException)
62 def handle_business_exception(e):
63     """An exception explicitly raised due to some business rule
64
65     """
66     error_log.error("Synchronous error for request id {} {}"
67                     .format(g.request_id, traceback.format_exc()))
68     err_msg = ERROR_TEMPLATE.render(description=str(e))
69     response = Response(err_msg, content_type='application/json; charset=utf-8')
70     response.status_code = 400
71     return response
72
73
74 @app.errorhandler(RequestException)
75 def handle_request_exception(e):
76     """Returns a detailed synchronous message to the calling client when osdf fails due to a remote call to another system
77
78     """
79     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
80     err_msg = request_exception_to_json_body(e)
81     response = Response(err_msg, content_type='application/json; charset=utf-8')
82     response.status_code = 400
83     return response
84
85
86 @app.errorhandler(DataError)
87 def handle_data_error(e):
88     """Returns a detailed message to the calling client when the initial synchronous message is invalid
89
90     """
91     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
92
93     body_dictionary = {
94         "serviceException": {
95             "text": BAD_CLIENT_REQUEST_MESSAGE,
96             "exceptionMessage": str(e.errors),
97             "errorType": "InvalidClientRequest"
98         }
99     }
100
101     body_as_json = json.dumps(body_dictionary)
102     response = Response(body_as_json, content_type='application/json; charset=utf-8')
103     response.status_code = 400
104     return response
105
106
107 @app.before_request
108 def log_request():
109     clear_mdc()
110     if request.content_type and 'json' in request.content_type:
111         populate_mdc(request)
112         g.request_id = get_request_id(request.get_json())
113         log_message(json.dumps(request.get_json()), "INPROGRESS", 'ENTRY')
114     else:
115         populate_default_mdc(request)
116         log_message('', "INPROGRESS", 'ENTRY')
117
118
119 @app.after_request
120 def log_response(response):
121     log_response_data(response)
122     return response
123
124
125 def log_response_data(response):
126     status_value = ''
127     try:
128         status_value = map_status_value(response)
129         log_message(response.get_data(as_text=True), status_value, 'EXIT')
130     except Exception:
131         try:
132             set_default_audit_mdc(request, status_value, 'EXIT')
133             audit_log.info(response.get_data(as_text=True))
134         except Exception:
135             set_error_details(300, 'Internal Error')
136             error_log.error("Error logging the response data due to {}".format(traceback.format_exc()))
137
138
139 def set_default_audit_mdc(request, status_value, p_marker):
140     MDC.put('partnerName', 'internal')
141     MDC.put('serviceName', request.path)
142     MDC.put('statusCode', status_value)
143     MDC.put('requestID', 'internal')
144     MDC.put('timer', int((time.process_time() - g.request_start) * 1000))
145     MDC.put('customField1', p_marker)
146
147
148 def log_message(message, status_value, p_marker='INVOKE'):
149     MDC.put('statusCode', status_value)
150     MDC.put('customField1', p_marker)
151     MDC.put('timer', int((time.process_time() - g.request_start) * 1000))
152     audit_log.info(message)
153
154
155 def map_status_value(response):
156     if 200 <= response.status_code < 300:
157         status_value = "COMPLETE"
158     else:
159         status_value = "ERROR"
160     return status_value
161
162
163 @app.errorhandler(500)
164 def internal_failure(error):
165     """Returned when unexpected coding errors occur during initial synchronous processing
166
167     """
168     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
169     response = Response(internal_error_message, content_type='application/json; charset=utf-8')
170     response.status_code = 500
171     return response
172
173
174 def get_options(argv):
175     program_version_string = '%%prog %s' % "v1.0"
176     program_longdesc = ""
177     program_license = ""
178
179     parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
180     parser.add_option("-l", "--local", dest="local", help="run locally", action="store_true", default=False)
181     parser.add_option("-t", "--devtest", dest="devtest", help="run in dev/test environment", action="store_true",
182                       default=False)
183     parser.add_option("-d", "--debughost", dest="debughost",
184                       help="IP Address of host running debug server", default='')
185     parser.add_option("-p", "--debugport", dest="debugport",
186                       help="Port number of debug server", type=int, default=5678)
187     opts, args = parser.parse_args(argv)
188
189     if opts.debughost:
190         debug_log.debug('pydevd.settrace({}, port={})'.format(opts.debughost, opts.debugport))
191         pydevd.settrace(opts.debughost, port=opts.debugport)
192     return opts
193
194
195 def build_ssl_context():
196     ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
197     ssl_context.set_ciphers('ECDHE-RSA-AES128-SHA256:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH')
198     ssl_context.load_cert_chain(sys_conf['ssl_context'][0], sys_conf['ssl_context'][1])
199     return ssl_context
200
201
202 def run_app():
203     global sys_conf
204     sys_conf = osdf_config['core']['osdf_system']
205     ports = sys_conf['osdf_ports']
206     internal_port, external_port = ports['internal'], ports['external']
207     local_host = sys_conf['osdf_ip_default']
208     common_app_opts = dict(host=local_host, threaded=True, use_reloader=False)
209     ssl_opts = sys_conf.get('ssl_context')
210     if ssl_opts:
211         common_app_opts.update({'ssl_context': build_ssl_context()})
212     opts = get_options(sys.argv)
213     # Load secrets from SMS
214     sms.load_secrets()
215     if not opts.local and not opts.devtest:  # normal deployment
216         app.run(port=internal_port, debug=False, **common_app_opts)
217     else:
218         port = internal_port if opts.local else external_port
219         app.run(port=port, debug=True, **common_app_opts)
220
221
222 if __name__ == "__main__":
223     run_app()