##// 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
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2016-2017 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 import collections
22 21 import logging
23 22
24 23 from pylons import tmpl_context as c
25 24 from pyramid.view import view_config
26 25 from pyramid.httpexceptions import HTTPFound
27 26
28 27 from rhodecode.translation import _
29 28
30 29 from rhodecode.admin.views.base import AdminSettingsView
31 30 from rhodecode.lib.auth import (
32 31 LoginRequired, HasPermissionAllDecorator, CSRFRequired)
33 32 from rhodecode.lib.utils2 import safe_int
34 33 from rhodecode.lib import system_info
35 34 from rhodecode.lib import user_sessions
36 35
37 36
38 37 from rhodecode.admin.navigation import navigation_list
39 38
40 39
41 40 log = logging.getLogger(__name__)
42 41
43 42
44 43 class AdminSessionSettingsView(AdminSettingsView):
45 44
46 45 @LoginRequired()
47 46 @HasPermissionAllDecorator('hg.admin')
48 47 @view_config(
49 48 route_name='admin_settings_sessions', request_method='GET',
50 49 renderer='rhodecode:templates/admin/settings/settings.mako')
51 50 def settings_sessions(self):
52 51 c.active = 'sessions'
53 52 c.navlist = navigation_list(self.request)
54 53
55 54 c.cleanup_older_days = 60
56 55 older_than_seconds = 60 * 60 * 24 * c.cleanup_older_days
57 56
58 57 config = system_info.rhodecode_config().get_value()['value']['config']
59 58 c.session_model = user_sessions.get_session_handler(
60 59 config.get('beaker.session.type', 'memory'))(config)
61 60
62 61 c.session_conf = c.session_model.config
63 62 c.session_count = c.session_model.get_count()
64 63 c.session_expired_count = c.session_model.get_expired_count(
65 64 older_than_seconds)
66 65
67 66 return {}
68 67
69 68 @LoginRequired()
70 69 @CSRFRequired()
71 70 @HasPermissionAllDecorator('hg.admin')
72 71 @view_config(
73 72 route_name='admin_settings_sessions_cleanup', request_method='POST')
74 73 def settings_sessions_cleanup(self):
75 74
76 75 expire_days = safe_int(self.request.params.get('expire_days'))
77 76
78 77 if expire_days is None:
79 78 expire_days = 60
80 79
81 80 older_than_seconds = 60 * 60 * 24 * expire_days
82 81
83 82 config = system_info.rhodecode_config().get_value()['value']['config']
84 83 session_model = user_sessions.get_session_handler(
85 84 config.get('beaker.session.type', 'memory'))(config)
86 85
87 86 try:
88 87 session_model.clean_sessions(
89 88 older_than_seconds=older_than_seconds)
90 89 self.request.session.flash(
91 90 _('Cleaned up old sessions'), queue='success')
92 91 except user_sessions.CleanupCommand as msg:
93 92 self.request.session.flash(msg.message, queue='warning')
94 93 except Exception as e:
95 94 log.exception('Failed session cleanup')
96 95 self.request.session.flash(
97 96 _('Failed to cleanup up old sessions'), queue='error')
98 97
99 98 redirect_to = self.request.resource_path(
100 99 self.context, route_name='admin_settings_sessions')
101 100 return HTTPFound(redirect_to)
@@ -1,125 +1,174
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2017-2017 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 import os
22 import time
21 23 import datetime
22 24 import dateutil
23 25 from rhodecode.model.db import DbSession, Session
24 26
25 27
26 28 class CleanupCommand(Exception):
27 29 pass
28 30
29 31
30 32 class BaseAuthSessions(object):
31 33 SESSION_TYPE = None
34 NOT_AVAILABLE = 'NOT AVAILABLE'
32 35
33 36 def __init__(self, config):
34 37 session_conf = {}
35 38 for k, v in config.items():
36 39 if k.startswith('beaker.session'):
37 40 session_conf[k] = v
38 41 self.config = session_conf
39 42
40 43 def get_count(self):
41 44 raise NotImplementedError
42 45
43 46 def get_expired_count(self, older_than_seconds=None):
44 47 raise NotImplementedError
45 48
46 49 def clean_sessions(self, older_than_seconds=None):
47 50 raise NotImplementedError
48 51
49 52 def _seconds_to_date(self, seconds):
50 53 return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(
51 54 seconds=seconds)
52 55
53 56
54 57 class DbAuthSessions(BaseAuthSessions):
55 58 SESSION_TYPE = 'ext:database'
56 59
57 60 def get_count(self):
58 61 return DbSession.query().count()
59 62
60 63 def get_expired_count(self, older_than_seconds=None):
61 64 expiry_date = self._seconds_to_date(older_than_seconds)
62 65 return DbSession.query().filter(DbSession.accessed < expiry_date).count()
63 66
64 67 def clean_sessions(self, older_than_seconds=None):
65 68 expiry_date = self._seconds_to_date(older_than_seconds)
66 69 DbSession.query().filter(DbSession.accessed < expiry_date).delete()
67 70 Session().commit()
68 71
69 72
70 73 class FileAuthSessions(BaseAuthSessions):
71 74 SESSION_TYPE = 'file sessions'
72 75
73 def get_count(self):
74 return 'NOT AVAILABLE'
75
76 def get_expired_count(self, older_than_seconds=None):
77 return self.get_count()
76 def _get_sessions_dir(self):
77 data_dir = self.config.get('beaker.session.data_dir')
78 return data_dir
78 79
79 def clean_sessions(self, older_than_seconds=None):
80 data_dir = self.config.get('beaker.session.data_dir')
81 raise CleanupCommand(
82 'Please execute this command: '
83 '`find . -mtime +60 -exec rm {{}} \;` inside {} directory'.format(
84 data_dir))
80 def _count_on_filesystem(self, path, older_than=0, callback=None):
81 value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
82 items_count = 0
83 used = 0
84 cur_time = time.time()
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 136 class MemcachedAuthSessions(BaseAuthSessions):
88 137 SESSION_TYPE = 'ext:memcached'
89 138
90 139 def get_count(self):
91 return 'NOT AVAILABLE'
140 return self.NOT_AVAILABLE
92 141
93 142 def get_expired_count(self, older_than_seconds=None):
94 return self.get_count()
143 return self.NOT_AVAILABLE
95 144
96 145 def clean_sessions(self, older_than_seconds=None):
97 146 raise CleanupCommand('Cleanup for this session type not yet available')
98 147
99 148
100 149 class MemoryAuthSessions(BaseAuthSessions):
101 150 SESSION_TYPE = 'memory'
102 151
103 152 def get_count(self):
104 return 'NOT AVAILABLE'
153 return self.NOT_AVAILABLE
105 154
106 155 def get_expired_count(self, older_than_seconds=None):
107 return self.get_count()
156 return self.NOT_AVAILABLE
108 157
109 158 def clean_sessions(self, older_than_seconds=None):
110 159 raise CleanupCommand('Cleanup for this session type not yet available')
111 160
112 161
113 162 def get_session_handler(session_type):
114 163 types = {
115 164 'file': FileAuthSessions,
116 165 'ext:memcached': MemcachedAuthSessions,
117 166 'ext:database': DbAuthSessions,
118 167 'memory': MemoryAuthSessions
119 168 }
120 169
121 170 try:
122 171 return types[session_type]
123 172 except KeyError:
124 173 raise ValueError(
125 174 'This type {} is not supported'.format(session_type))
General Comments 0
You need to be logged in to leave comments. Login now