fix csit, cleanup dockerfile for osdf
[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 pydevd
31 from flask import Flask, request, Response, g
32 from requests import RequestException
33 from schematics.exceptions import DataError
34
35 import osdf.adapters.aaf.sms as sms
36 import osdf.operation.responses
37 from osdf.config.base import osdf_config
38 from osdf.logging.osdf_logging import error_log, debug_log
39 from osdf.operation.error_handling import request_exception_to_json_body, internal_error_message
40 from osdf.operation.exceptions import BusinessException
41 from osdf.utils.mdc_utils import clear_mdc, mdc_from_json, default_mdc, get_request_id
42
43 ERROR_TEMPLATE = osdf.ERROR_TEMPLATE
44
45 app = Flask(__name__)
46
47 BAD_CLIENT_REQUEST_MESSAGE = 'Client sent an invalid request'
48
49
50 @app.errorhandler(BusinessException)
51 def handle_business_exception(e):
52     """An exception explicitly raised due to some business rule"""
53     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
54     err_msg = ERROR_TEMPLATE.render(description=str(e))
55     response = Response(err_msg, content_type='application/json; charset=utf-8')
56     response.status_code = 400
57     return response
58
59
60 @app.errorhandler(RequestException)
61 def handle_request_exception(e):
62     """Returns a detailed synchronous message to the calling client
63     when osdf fails due to a remote call to another system"""
64     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
65     err_msg = request_exception_to_json_body(e)
66     response = Response(err_msg, content_type='application/json; charset=utf-8')
67     response.status_code = 400
68     return response
69
70
71 @app.errorhandler(DataError)
72 def handle_data_error(e):
73     """Returns a detailed message to the calling client when the initial synchronous message is invalid"""
74     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
75
76     body_dictionary = {
77         "serviceException": {
78             "text": BAD_CLIENT_REQUEST_MESSAGE,
79             "exceptionMessage": str(e.errors),
80             "errorType": "InvalidClientRequest"
81         }
82     }
83
84     body_as_json = json.dumps(body_dictionary)
85     response = Response(body_as_json, content_type='application/json; charset=utf-8')
86     response.status_code = 400
87     return response
88
89
90 @app.before_request
91 def log_request():
92     g.request_start = time.process_time()
93     if request.data:
94         if request.get_json():
95             request_json = request.get_json()
96             g.request_id = get_request_id(request_json)
97             mdc_from_json(request_json)
98         else:
99             g.request_id = "N/A"
100             default_mdc()
101     else:
102         g.request_id = "N/A"
103         default_mdc()
104
105
106 @app.after_request
107 def log_response(response):
108     clear_mdc()
109     return response
110
111
112 @app.errorhandler(500)
113 def internal_failure(error):
114     """Returned when unexpected coding errors occur during initial synchronous processing"""
115     error_log.error("Synchronous error for request id {} {}".format(g.request_id, traceback.format_exc()))
116     response = Response(internal_error_message, content_type='application/json; charset=utf-8')
117     response.status_code = 500
118     return response
119
120
121 def get_options(argv):
122     program_version_string = '%%prog %s' % "v1.0"
123     program_longdesc = ""
124     program_license = ""
125
126     parser = OptionParser(version=program_version_string, epilog=program_longdesc, description=program_license)
127     parser.add_option("-l", "--local", dest="local", help="run locally", action="store_true", default=False)
128     parser.add_option("-t", "--devtest", dest="devtest", help="run in dev/test environment", action="store_true",
129                       default=False)
130     parser.add_option("-d", "--debughost", dest="debughost", help="IP Address of host running debug server", default='')
131     parser.add_option("-p", "--debugport", dest="debugport", help="Port number of debug server", type=int, default=5678)
132     opts, args = parser.parse_args(argv)
133
134     if opts.debughost:
135         debug_log.debug('pydevd.settrace({}, port={})'.format(opts.debughost, opts.debugport))
136         pydevd.settrace(opts.debughost, port=opts.debugport)
137     return opts
138
139
140 def build_ssl_context():
141     ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLSv1_2)
142     ssl_context.set_ciphers('ECDHE-RSA-AES128-SHA256:EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH')
143     ssl_context.load_cert_chain(sys_conf['ssl_context'][0], sys_conf['ssl_context'][1])
144     return ssl_context
145
146
147 def run_app():
148     global sys_conf
149     sys_conf = osdf_config['core']['osdf_system']
150     ports = sys_conf['osdf_ports']
151     internal_port, external_port = ports['internal'], ports['external']
152     local_host = sys_conf['osdf_ip_default']
153     common_app_opts = dict(host=local_host, threaded=True, use_reloader=False)
154     ssl_opts = sys_conf.get('ssl_context')
155     if ssl_opts:
156         common_app_opts.update({'ssl_context': build_ssl_context()})
157     opts = get_options(sys.argv)
158     # Load secrets from SMS
159     sms.load_secrets()
160     if not opts.local and not opts.devtest:  # normal deployment
161         app.run(port=internal_port, debug=False, **common_app_opts)
162     else:
163         port = internal_port if opts.local else external_port
164         app.run(port=port, debug=True, **common_app_opts)
165
166
167 if __name__ == "__main__":
168     run_app()