##// END OF EJS Templates
user-sessions: add count and cleanup of file-based sessions.
marcink -
r1364:bf929781 default
parent child Browse files
Show More
@@ -1,101 +1,100 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2016-2017 RhodeCode GmbH
3 # Copyright (C) 2016-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import collections
22 import logging
21 import logging
23
22
24 from pylons import tmpl_context as c
23 from pylons import tmpl_context as c
25 from pyramid.view import view_config
24 from pyramid.view import view_config
26 from pyramid.httpexceptions import HTTPFound
25 from pyramid.httpexceptions import HTTPFound
27
26
28 from rhodecode.translation import _
27 from rhodecode.translation import _
29
28
30 from rhodecode.admin.views.base import AdminSettingsView
29 from rhodecode.admin.views.base import AdminSettingsView
31 from rhodecode.lib.auth import (
30 from rhodecode.lib.auth import (
32 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
31 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
33 from rhodecode.lib.utils2 import safe_int
32 from rhodecode.lib.utils2 import safe_int
34 from rhodecode.lib import system_info
33 from rhodecode.lib import system_info
35 from rhodecode.lib import user_sessions
34 from rhodecode.lib import user_sessions
36
35
37
36
38 from rhodecode.admin.navigation import navigation_list
37 from rhodecode.admin.navigation import navigation_list
39
38
40
39
41 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
42
41
43
42
44 class AdminSessionSettingsView(AdminSettingsView):
43 class AdminSessionSettingsView(AdminSettingsView):
45
44
46 @LoginRequired()
45 @LoginRequired()
47 @HasPermissionAllDecorator('hg.admin')
46 @HasPermissionAllDecorator('hg.admin')
48 @view_config(
47 @view_config(
49 route_name='admin_settings_sessions', request_method='GET',
48 route_name='admin_settings_sessions', request_method='GET',
50 renderer='rhodecode:templates/admin/settings/settings.mako')
49 renderer='rhodecode:templates/admin/settings/settings.mako')
51 def settings_sessions(self):
50 def settings_sessions(self):
52 c.active = 'sessions'
51 c.active = 'sessions'
53 c.navlist = navigation_list(self.request)
52 c.navlist = navigation_list(self.request)
54
53
55 c.cleanup_older_days = 60
54 c.cleanup_older_days = 60
56 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
55 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
57
56
58 config = system_info.rhodecode_config().get_value()['value']['config']
57 config = system_info.rhodecode_config().get_value()['value']['config']
59 c.session_model = user_sessions.get_session_handler(
58 c.session_model = user_sessions.get_session_handler(
60 config.get('beaker.session.type', 'memory'))(config)
59 config.get('beaker.session.type', 'memory'))(config)
61
60
62 c.session_conf = c.session_model.config
61 c.session_conf = c.session_model.config
63 c.session_count = c.session_model.get_count()
62 c.session_count = c.session_model.get_count()
64 c.session_expired_count = c.session_model.get_expired_count(
63 c.session_expired_count = c.session_model.get_expired_count(
65 older_than_seconds)
64 older_than_seconds)
66
65
67 return {}
66 return {}
68
67
69 @LoginRequired()
68 @LoginRequired()
70 @CSRFRequired()
69 @CSRFRequired()
71 @HasPermissionAllDecorator('hg.admin')
70 @HasPermissionAllDecorator('hg.admin')
72 @view_config(
71 @view_config(
73 route_name='admin_settings_sessions_cleanup', request_method='POST')
72 route_name='admin_settings_sessions_cleanup', request_method='POST')
74 def settings_sessions_cleanup(self):
73 def settings_sessions_cleanup(self):
75
74
76 expire_days = safe_int(self.request.params.get('expire_days'))
75 expire_days = safe_int(self.request.params.get('expire_days'))
77
76
78 if expire_days is None:
77 if expire_days is None:
79 expire_days = 60
78 expire_days = 60
80
79
81 older_than_seconds = 60 * 60 * 24 * expire_days
80 older_than_seconds = 60 * 60 * 24 * expire_days
82
81
83 config = system_info.rhodecode_config().get_value()['value']['config']
82 config = system_info.rhodecode_config().get_value()['value']['config']
84 session_model = user_sessions.get_session_handler(
83 session_model = user_sessions.get_session_handler(
85 config.get('beaker.session.type', 'memory'))(config)
84 config.get('beaker.session.type', 'memory'))(config)
86
85
87 try:
86 try:
88 session_model.clean_sessions(
87 session_model.clean_sessions(
89 older_than_seconds=older_than_seconds)
88 older_than_seconds=older_than_seconds)
90 self.request.session.flash(
89 self.request.session.flash(
91 _('Cleaned up old sessions'), queue='success')
90 _('Cleaned up old sessions'), queue='success')
92 except user_sessions.CleanupCommand as msg:
91 except user_sessions.CleanupCommand as msg:
93 self.request.session.flash(msg.message, queue='warning')
92 self.request.session.flash(msg.message, queue='warning')
94 except Exception as e:
93 except Exception as e:
95 log.exception('Failed session cleanup')
94 log.exception('Failed session cleanup')
96 self.request.session.flash(
95 self.request.session.flash(
97 _('Failed to cleanup up old sessions'), queue='error')
96 _('Failed to cleanup up old sessions'), queue='error')
98
97
99 redirect_to = self.request.resource_path(
98 redirect_to = self.request.resource_path(
100 self.context, route_name='admin_settings_sessions')
99 self.context, route_name='admin_settings_sessions')
101 return HTTPFound(redirect_to)
100 return HTTPFound(redirect_to)
@@ -1,125 +1,174 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2017-2017 RhodeCode GmbH
3 # Copyright (C) 2017-2017 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 import os
22 import time
21 import datetime
23 import datetime
22 import dateutil
24 import dateutil
23 from rhodecode.model.db import DbSession, Session
25 from rhodecode.model.db import DbSession, Session
24
26
25
27
26 class CleanupCommand(Exception):
28 class CleanupCommand(Exception):
27 pass
29 pass
28
30
29
31
30 class BaseAuthSessions(object):
32 class BaseAuthSessions(object):
31 SESSION_TYPE = None
33 SESSION_TYPE = None
34 NOT_AVAILABLE = 'NOT AVAILABLE'
32
35
33 def __init__(self, config):
36 def __init__(self, config):
34 session_conf = {}
37 session_conf = {}
35 for k, v in config.items():
38 for k, v in config.items():
36 if k.startswith('beaker.session'):
39 if k.startswith('beaker.session'):
37 session_conf[k] = v
40 session_conf[k] = v
38 self.config = session_conf
41 self.config = session_conf
39
42
40 def get_count(self):
43 def get_count(self):
41 raise NotImplementedError
44 raise NotImplementedError
42
45
43 def get_expired_count(self, older_than_seconds=None):
46 def get_expired_count(self, older_than_seconds=None):
44 raise NotImplementedError
47 raise NotImplementedError
45
48
46 def clean_sessions(self, older_than_seconds=None):
49 def clean_sessions(self, older_than_seconds=None):
47 raise NotImplementedError
50 raise NotImplementedError
48
51
49 def _seconds_to_date(self, seconds):
52 def _seconds_to_date(self, seconds):
50 return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(
53 return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(
51 seconds=seconds)
54 seconds=seconds)
52
55
53
56
54 class DbAuthSessions(BaseAuthSessions):
57 class DbAuthSessions(BaseAuthSessions):
55 SESSION_TYPE = 'ext:database'
58 SESSION_TYPE = 'ext:database'
56
59
57 def get_count(self):
60 def get_count(self):
58 return DbSession.query().count()
61 return DbSession.query().count()
59
62
60 def get_expired_count(self, older_than_seconds=None):
63 def get_expired_count(self, older_than_seconds=None):
61 expiry_date = self._seconds_to_date(older_than_seconds)
64 expiry_date = self._seconds_to_date(older_than_seconds)
62 return DbSession.query().filter(DbSession.accessed < expiry_date).count()
65 return DbSession.query().filter(DbSession.accessed < expiry_date).count()
63
66
64 def clean_sessions(self, older_than_seconds=None):
67 def clean_sessions(self, older_than_seconds=None):
65 expiry_date = self._seconds_to_date(older_than_seconds)
68 expiry_date = self._seconds_to_date(older_than_seconds)
66 DbSession.query().filter(DbSession.accessed < expiry_date).delete()
69 DbSession.query().filter(DbSession.accessed < expiry_date).delete()
67 Session().commit()
70 Session().commit()
68
71
69
72
70 class FileAuthSessions(BaseAuthSessions):
73 class FileAuthSessions(BaseAuthSessions):
71 SESSION_TYPE = 'file sessions'
74 SESSION_TYPE = 'file sessions'
72
75
73 def get_count(self):
76 def _get_sessions_dir(self):
74 return 'NOT AVAILABLE'
77 data_dir = self.config.get('beaker.session.data_dir')
75
78 return data_dir
76 def get_expired_count(self, older_than_seconds=None):
77 return self.get_count()
78
79
79 def clean_sessions(self, older_than_seconds=None):
80 def _count_on_filesystem(self, path, older_than=0, callback=None):
80 data_dir = self.config.get('beaker.session.data_dir')
81 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
81 raise CleanupCommand(
82 items_count = 0
82 'Please execute this command: '
83 used = 0
83 '`find . -mtime +60 -exec rm {{}} \;` inside {} directory'.format(
84 cur_time = time.time()
84 data_dir))
85 for root, dirs, files in os.walk(path):
86 for f in files:
87 final_path = os.path.join(root, f)
88 try:
89 mtime = os.stat(final_path).st_mtime
90 if (cur_time - mtime) > older_than:
91 items_count += 1
92 if callback:
93 callback_res = callback(final_path)
94 else:
95 used += os.path.getsize(final_path)
96 except OSError:
97 pass
98 value.update({
99 'percent': 100,
100 'used': used,
101 'total': used,
102 'items': items_count
103 })
104 return value
105
106 def get_count(self):
107 try:
108 sessions_dir = self._get_sessions_dir()
109 items_count = self._count_on_filesystem(sessions_dir)['items']
110 except Exception:
111 items_count = self.NOT_AVAILABLE
112 return items_count
113
114 def get_expired_count(self, older_than_seconds=0):
115 try:
116 sessions_dir = self._get_sessions_dir()
117 items_count = self._count_on_filesystem(
118 sessions_dir, older_than=older_than_seconds)['items']
119 except Exception:
120 items_count = self.NOT_AVAILABLE
121 return items_count
122
123 def clean_sessions(self, older_than_seconds=0):
124 # find . -mtime +60 -exec rm {} \;
125
126 sessions_dir = self._get_sessions_dir()
127
128 def remove_item(path):
129 os.remove(path)
130
131 return self._count_on_filesystem(
132 sessions_dir, older_than=older_than_seconds,
133 callback=remove_item)['items']
85
134
86
135
87 class MemcachedAuthSessions(BaseAuthSessions):
136 class MemcachedAuthSessions(BaseAuthSessions):
88 SESSION_TYPE = 'ext:memcached'
137 SESSION_TYPE = 'ext:memcached'
89
138
90 def get_count(self):
139 def get_count(self):
91 return 'NOT AVAILABLE'
140 return self.NOT_AVAILABLE
92
141
93 def get_expired_count(self, older_than_seconds=None):
142 def get_expired_count(self, older_than_seconds=None):
94 return self.get_count()
143 return self.NOT_AVAILABLE
95
144
96 def clean_sessions(self, older_than_seconds=None):
145 def clean_sessions(self, older_than_seconds=None):
97 raise CleanupCommand('Cleanup for this session type not yet available')
146 raise CleanupCommand('Cleanup for this session type not yet available')
98
147
99
148
100 class MemoryAuthSessions(BaseAuthSessions):
149 class MemoryAuthSessions(BaseAuthSessions):
101 SESSION_TYPE = 'memory'
150 SESSION_TYPE = 'memory'
102
151
103 def get_count(self):
152 def get_count(self):
104 return 'NOT AVAILABLE'
153 return self.NOT_AVAILABLE
105
154
106 def get_expired_count(self, older_than_seconds=None):
155 def get_expired_count(self, older_than_seconds=None):
107 return self.get_count()
156 return self.NOT_AVAILABLE
108
157
109 def clean_sessions(self, older_than_seconds=None):
158 def clean_sessions(self, older_than_seconds=None):
110 raise CleanupCommand('Cleanup for this session type not yet available')
159 raise CleanupCommand('Cleanup for this session type not yet available')
111
160
112
161
113 def get_session_handler(session_type):
162 def get_session_handler(session_type):
114 types = {
163 types = {
115 'file': FileAuthSessions,
164 'file': FileAuthSessions,
116 'ext:memcached': MemcachedAuthSessions,
165 'ext:memcached': MemcachedAuthSessions,
117 'ext:database': DbAuthSessions,
166 'ext:database': DbAuthSessions,
118 'memory': MemoryAuthSessions
167 'memory': MemoryAuthSessions
119 }
168 }
120
169
121 try:
170 try:
122 return types[session_type]
171 return types[session_type]
123 except KeyError:
172 except KeyError:
124 raise ValueError(
173 raise ValueError(
125 'This type {} is not supported'.format(session_type))
174 'This type {} is not supported'.format(session_type))
General Comments 0
You need to be logged in to leave comments. Login now