##// END OF EJS Templates
ops: added healthcheck view
super-admin -
r4736:d501e9ec stable
parent child Browse files
Show More
@@ -1,56 +1,64 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 from rhodecode.apps._base import ADMIN_PREFIX
22 22
23 23
24 24 def admin_routes(config):
25 25 from rhodecode.apps.ops.views import OpsView
26 26
27 27 config.add_route(
28 28 name='ops_ping',
29 29 pattern='/ping')
30 30 config.add_view(
31 31 OpsView,
32 32 attr='ops_ping',
33 33 route_name='ops_ping', request_method='GET',
34 34 renderer='json_ext')
35 35
36 36 config.add_route(
37 37 name='ops_error_test',
38 38 pattern='/error')
39 39 config.add_view(
40 40 OpsView,
41 41 attr='ops_error_test',
42 42 route_name='ops_error_test', request_method='GET',
43 43 renderer='json_ext')
44 44
45 45 config.add_route(
46 46 name='ops_redirect_test',
47 47 pattern='/redirect')
48 48 config.add_view(
49 49 OpsView,
50 50 attr='ops_redirect_test',
51 51 route_name='ops_redirect_test', request_method='GET',
52 52 renderer='json_ext')
53 53
54 config.add_route(
55 name='ops_healthcheck',
56 pattern='/status')
57 config.add_view(
58 OpsView,
59 attr='ops_healthcheck',
60 route_name='ops_healthcheck', request_method='GET',
61 renderer='json_ext')
54 62
55 63 def includeme(config):
56 64 config.include(admin_routes, route_prefix=ADMIN_PREFIX + '/ops')
@@ -1,74 +1,94 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import time
22 22 import logging
23 23
24 24
25 25 from pyramid.httpexceptions import HTTPFound
26 26
27 27 from rhodecode.apps._base import BaseAppView
28 28 from rhodecode.lib import helpers as h
29 29
30 30 log = logging.getLogger(__name__)
31 31
32 32
33 33 class OpsView(BaseAppView):
34 34
35 35 def load_default_context(self):
36 36 c = self._get_local_tmpl_context()
37 37 c.user = c.auth_user.get_instance()
38 38
39 39 return c
40 40
41 41 def ops_ping(self):
42 42 data = {
43 43 'instance': self.request.registry.settings.get('instance_id'),
44 44 }
45 45 if getattr(self.request, 'user'):
46 46 caller_name = 'anonymous'
47 47 if self.request.user.user_id:
48 48 caller_name = self.request.user.username
49 49
50 50 data.update({
51 51 'caller_ip': self.request.user.ip_addr,
52 52 'caller_name': caller_name,
53 53 })
54 54 return {'ok': data}
55 55
56 56 def ops_error_test(self):
57 57 """
58 58 Test exception handling and emails on errors
59 59 """
60 60
61 61 class TestException(Exception):
62 62 pass
63 63 # add timeout so we add some sort of rate limiter
64 64 time.sleep(2)
65 65 msg = ('RhodeCode Enterprise test exception. '
66 66 'Client:{}. Generation time: {}.'.format(self.request.user, time.time()))
67 67 raise TestException(msg)
68 68
69 69 def ops_redirect_test(self):
70 70 """
71 71 Test redirect handling
72 72 """
73 73 redirect_to = self.request.GET.get('to') or h.route_path('home')
74 74 raise HTTPFound(redirect_to)
75
76 def ops_healthcheck(self):
77 from rhodecode.lib.system_info import load_system_info
78
79 vcsserver_info = load_system_info('vcs_server')
80 if vcsserver_info:
81 vcsserver_info = vcsserver_info['human_value']
82
83 db_info = load_system_info('database_info')
84 if db_info:
85 db_info = db_info['human_value']
86
87 health_spec = {
88 'caller_ip': self.request.user.ip_addr,
89 'vcsserver': vcsserver_info,
90 'db': db_info,
91 }
92
93 return {'healthcheck': health_spec}
94
@@ -1,283 +1,284 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-2020 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 import gzip
22 22 import shutil
23 23 import logging
24 24 import tempfile
25 25 import urlparse
26 26
27 27 from webob.exc import HTTPNotFound
28 28
29 29 import rhodecode
30 30 from rhodecode.lib.middleware.appenlight import wrap_in_appenlight_if_enabled
31 31 from rhodecode.lib.middleware.simplegit import SimpleGit, GIT_PROTO_PAT
32 32 from rhodecode.lib.middleware.simplehg import SimpleHg
33 33 from rhodecode.lib.middleware.simplesvn import SimpleSvn
34 34 from rhodecode.model.settings import VcsSettingsModel
35 35
36 36 log = logging.getLogger(__name__)
37 37
38 38 VCS_TYPE_KEY = '_rc_vcs_type'
39 39 VCS_TYPE_SKIP = '_rc_vcs_skip'
40 40
41 41
42 42 def is_git(environ):
43 43 """
44 44 Returns True if requests should be handled by GIT wsgi middleware
45 45 """
46 46 is_git_path = GIT_PROTO_PAT.match(environ['PATH_INFO'])
47 47 log.debug(
48 48 'request path: `%s` detected as GIT PROTOCOL %s', environ['PATH_INFO'],
49 49 is_git_path is not None)
50 50
51 51 return is_git_path
52 52
53 53
54 54 def is_hg(environ):
55 55 """
56 56 Returns True if requests target is mercurial server - header
57 57 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
58 58 """
59 59 is_hg_path = False
60 60
61 61 http_accept = environ.get('HTTP_ACCEPT')
62 62
63 63 if http_accept and http_accept.startswith('application/mercurial'):
64 64 query = urlparse.parse_qs(environ['QUERY_STRING'])
65 65 if 'cmd' in query:
66 66 is_hg_path = True
67 67
68 68 log.debug(
69 69 'request path: `%s` detected as HG PROTOCOL %s', environ['PATH_INFO'],
70 70 is_hg_path)
71 71
72 72 return is_hg_path
73 73
74 74
75 75 def is_svn(environ):
76 76 """
77 77 Returns True if requests target is Subversion server
78 78 """
79 79
80 80 http_dav = environ.get('HTTP_DAV', '')
81 81 magic_path_segment = rhodecode.CONFIG.get(
82 82 'rhodecode_subversion_magic_path', '/!svn')
83 83 is_svn_path = (
84 84 'subversion' in http_dav or
85 85 magic_path_segment in environ['PATH_INFO']
86 86 or environ['REQUEST_METHOD'] in ['PROPFIND', 'PROPPATCH']
87 87 )
88 88 log.debug(
89 89 'request path: `%s` detected as SVN PROTOCOL %s', environ['PATH_INFO'],
90 90 is_svn_path)
91 91
92 92 return is_svn_path
93 93
94 94
95 95 class GunzipMiddleware(object):
96 96 """
97 97 WSGI middleware that unzips gzip-encoded requests before
98 98 passing on to the underlying application.
99 99 """
100 100
101 101 def __init__(self, application):
102 102 self.app = application
103 103
104 104 def __call__(self, environ, start_response):
105 105 accepts_encoding_header = environ.get('HTTP_CONTENT_ENCODING', b'')
106 106
107 107 if b'gzip' in accepts_encoding_header:
108 108 log.debug('gzip detected, now running gunzip wrapper')
109 109 wsgi_input = environ['wsgi.input']
110 110
111 111 if not hasattr(environ['wsgi.input'], 'seek'):
112 112 # The gzip implementation in the standard library of Python 2.x
113 113 # requires the '.seek()' and '.tell()' methods to be available
114 114 # on the input stream. Read the data into a temporary file to
115 115 # work around this limitation.
116 116
117 117 wsgi_input = tempfile.SpooledTemporaryFile(64 * 1024 * 1024)
118 118 shutil.copyfileobj(environ['wsgi.input'], wsgi_input)
119 119 wsgi_input.seek(0)
120 120
121 121 environ['wsgi.input'] = gzip.GzipFile(fileobj=wsgi_input, mode='r')
122 122 # since we "Ungzipped" the content we say now it's no longer gzip
123 123 # content encoding
124 124 del environ['HTTP_CONTENT_ENCODING']
125 125
126 126 # content length has changes ? or i'm not sure
127 127 if 'CONTENT_LENGTH' in environ:
128 128 del environ['CONTENT_LENGTH']
129 129 else:
130 130 log.debug('content not gzipped, gzipMiddleware passing '
131 131 'request further')
132 132 return self.app(environ, start_response)
133 133
134 134
135 135 def is_vcs_call(environ):
136 136 if VCS_TYPE_KEY in environ:
137 137 raw_type = environ[VCS_TYPE_KEY]
138 138 return raw_type and raw_type != VCS_TYPE_SKIP
139 139 return False
140 140
141 141
142 142 def get_path_elem(route_path):
143 143 if not route_path:
144 144 return None
145 145
146 146 cleaned_route_path = route_path.lstrip('/')
147 147 if cleaned_route_path:
148 148 cleaned_route_path_elems = cleaned_route_path.split('/')
149 149 if cleaned_route_path_elems:
150 150 return cleaned_route_path_elems[0]
151 151 return None
152 152
153 153
154 154 def detect_vcs_request(environ, backends):
155 155 checks = {
156 156 'hg': (is_hg, SimpleHg),
157 157 'git': (is_git, SimpleGit),
158 158 'svn': (is_svn, SimpleSvn),
159 159 }
160 160 handler = None
161 161 # List of path views first chunk we don't do any checks
162 162 white_list = [
163 163 # e.g /_file_store/download
164 164 '_file_store',
165 165
166 166 # static files no detection
167 167 '_static',
168 168
169 # skip ops ping
169 # skip ops ping, status
170 170 '_admin/ops/ping',
171 '_admin/ops/status',
171 172
172 173 # full channelstream connect should be VCS skipped
173 174 '_admin/channelstream/connect',
174 175 ]
175 176
176 177 path_info = environ['PATH_INFO']
177 178
178 179 path_elem = get_path_elem(path_info)
179 180
180 181 if path_elem in white_list:
181 182 log.debug('path `%s` in whitelist, skipping...', path_info)
182 183 return handler
183 184
184 185 path_url = path_info.lstrip('/')
185 186 if path_url in white_list:
186 187 log.debug('full url path `%s` in whitelist, skipping...', path_url)
187 188 return handler
188 189
189 190 if VCS_TYPE_KEY in environ:
190 191 raw_type = environ[VCS_TYPE_KEY]
191 192 if raw_type == VCS_TYPE_SKIP:
192 193 log.debug('got `skip` marker for vcs detection, skipping...')
193 194 return handler
194 195
195 196 _check, handler = checks.get(raw_type) or [None, None]
196 197 if handler:
197 198 log.debug('got handler:%s from environ', handler)
198 199
199 200 if not handler:
200 201 log.debug('request start: checking if request for `%s` is of VCS type in order: %s', path_elem, backends)
201 202 for vcs_type in backends:
202 203 vcs_check, _handler = checks[vcs_type]
203 204 if vcs_check(environ):
204 205 log.debug('vcs handler found %s', _handler)
205 206 handler = _handler
206 207 break
207 208
208 209 return handler
209 210
210 211
211 212 class VCSMiddleware(object):
212 213
213 214 def __init__(self, app, registry, config, appenlight_client):
214 215 self.application = app
215 216 self.registry = registry
216 217 self.config = config
217 218 self.appenlight_client = appenlight_client
218 219 self.use_gzip = True
219 220 # order in which we check the middlewares, based on vcs.backends config
220 221 self.check_middlewares = config['vcs.backends']
221 222
222 223 def vcs_config(self, repo_name=None):
223 224 """
224 225 returns serialized VcsSettings
225 226 """
226 227 try:
227 228 return VcsSettingsModel(
228 229 repo=repo_name).get_ui_settings_as_config_obj()
229 230 except Exception:
230 231 pass
231 232
232 233 def wrap_in_gzip_if_enabled(self, app, config):
233 234 if self.use_gzip:
234 235 app = GunzipMiddleware(app)
235 236 return app
236 237
237 238 def _get_handler_app(self, environ):
238 239 app = None
239 240 log.debug('VCSMiddleware: detecting vcs type.')
240 241 handler = detect_vcs_request(environ, self.check_middlewares)
241 242 if handler:
242 243 app = handler(self.config, self.registry)
243 244
244 245 return app
245 246
246 247 def __call__(self, environ, start_response):
247 248 # check if we handle one of interesting protocols, optionally extract
248 249 # specific vcsSettings and allow changes of how things are wrapped
249 250 vcs_handler = self._get_handler_app(environ)
250 251 if vcs_handler:
251 252 # translate the _REPO_ID into real repo NAME for usage
252 253 # in middleware
253 254 environ['PATH_INFO'] = vcs_handler._get_by_id(environ['PATH_INFO'])
254 255
255 256 # Set acl, url and vcs repo names.
256 257 vcs_handler.set_repo_names(environ)
257 258
258 259 # register repo config back to the handler
259 260 vcs_conf = self.vcs_config(vcs_handler.acl_repo_name)
260 261 # maybe damaged/non existent settings. We still want to
261 262 # pass that point to validate on is_valid_and_existing_repo
262 263 # and return proper HTTP Code back to client
263 264 if vcs_conf:
264 265 vcs_handler.repo_vcs_config = vcs_conf
265 266
266 267 # check for type, presence in database and on filesystem
267 268 if not vcs_handler.is_valid_and_existing_repo(
268 269 vcs_handler.acl_repo_name,
269 270 vcs_handler.base_path,
270 271 vcs_handler.SCM):
271 272 return HTTPNotFound()(environ, start_response)
272 273
273 274 environ['REPO_NAME'] = vcs_handler.url_repo_name
274 275
275 276 # Wrap handler in middlewares if they are enabled.
276 277 vcs_handler = self.wrap_in_gzip_if_enabled(
277 278 vcs_handler, self.config)
278 279 vcs_handler, _ = wrap_in_appenlight_if_enabled(
279 280 vcs_handler, self.config, self.appenlight_client)
280 281
281 282 return vcs_handler(environ, start_response)
282 283
283 284 return self.application(environ, start_response)
General Comments 0
You need to be logged in to leave comments. Login now