##// END OF EJS Templates
user sessions: get ability to count memcached sessions
marcink -
r2141:7a5a9bba default
parent child Browse files
Show More
@@ -1,181 +1,223 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
21 import os
22 import re
22 import time
23 import time
23 import datetime
24 import datetime
24 import dateutil
25 import dateutil
26
25 from rhodecode.model.db import DbSession, Session
27 from rhodecode.model.db import DbSession, Session
26
28
27
29
28 class CleanupCommand(Exception):
30 class CleanupCommand(Exception):
29 pass
31 pass
30
32
31
33
32 class BaseAuthSessions(object):
34 class BaseAuthSessions(object):
33 SESSION_TYPE = None
35 SESSION_TYPE = None
34 NOT_AVAILABLE = 'NOT AVAILABLE'
36 NOT_AVAILABLE = 'NOT AVAILABLE'
35
37
36 def __init__(self, config):
38 def __init__(self, config):
37 session_conf = {}
39 session_conf = {}
38 for k, v in config.items():
40 for k, v in config.items():
39 if k.startswith('beaker.session'):
41 if k.startswith('beaker.session'):
40 session_conf[k] = v
42 session_conf[k] = v
41 self.config = session_conf
43 self.config = session_conf
42
44
43 def get_count(self):
45 def get_count(self):
44 raise NotImplementedError
46 raise NotImplementedError
45
47
46 def get_expired_count(self, older_than_seconds=None):
48 def get_expired_count(self, older_than_seconds=None):
47 raise NotImplementedError
49 raise NotImplementedError
48
50
49 def clean_sessions(self, older_than_seconds=None):
51 def clean_sessions(self, older_than_seconds=None):
50 raise NotImplementedError
52 raise NotImplementedError
51
53
52 def _seconds_to_date(self, seconds):
54 def _seconds_to_date(self, seconds):
53 return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(
55 return datetime.datetime.utcnow() - dateutil.relativedelta.relativedelta(
54 seconds=seconds)
56 seconds=seconds)
55
57
56
58
57 class DbAuthSessions(BaseAuthSessions):
59 class DbAuthSessions(BaseAuthSessions):
58 SESSION_TYPE = 'ext:database'
60 SESSION_TYPE = 'ext:database'
59
61
60 def get_count(self):
62 def get_count(self):
61 return DbSession.query().count()
63 return DbSession.query().count()
62
64
63 def get_expired_count(self, older_than_seconds=None):
65 def get_expired_count(self, older_than_seconds=None):
64 expiry_date = self._seconds_to_date(older_than_seconds)
66 expiry_date = self._seconds_to_date(older_than_seconds)
65 return DbSession.query().filter(DbSession.accessed < expiry_date).count()
67 return DbSession.query().filter(DbSession.accessed < expiry_date).count()
66
68
67 def clean_sessions(self, older_than_seconds=None):
69 def clean_sessions(self, older_than_seconds=None):
68 expiry_date = self._seconds_to_date(older_than_seconds)
70 expiry_date = self._seconds_to_date(older_than_seconds)
69 to_remove = DbSession.query().filter(DbSession.accessed < expiry_date).count()
71 to_remove = DbSession.query().filter(DbSession.accessed < expiry_date).count()
70 DbSession.query().filter(DbSession.accessed < expiry_date).delete()
72 DbSession.query().filter(DbSession.accessed < expiry_date).delete()
71 Session().commit()
73 Session().commit()
72 return to_remove
74 return to_remove
73
75
74
76
75 class FileAuthSessions(BaseAuthSessions):
77 class FileAuthSessions(BaseAuthSessions):
76 SESSION_TYPE = 'file sessions'
78 SESSION_TYPE = 'file sessions'
77
79
78 def _get_sessions_dir(self):
80 def _get_sessions_dir(self):
79 data_dir = self.config.get('beaker.session.data_dir')
81 data_dir = self.config.get('beaker.session.data_dir')
80 return data_dir
82 return data_dir
81
83
82 def _count_on_filesystem(self, path, older_than=0, callback=None):
84 def _count_on_filesystem(self, path, older_than=0, callback=None):
83 value = dict(percent=0, used=0, total=0, items=0, callbacks=0,
85 value = dict(percent=0, used=0, total=0, items=0, callbacks=0,
84 path=path, text='')
86 path=path, text='')
85 items_count = 0
87 items_count = 0
86 used = 0
88 used = 0
87 callbacks = 0
89 callbacks = 0
88 cur_time = time.time()
90 cur_time = time.time()
89 for root, dirs, files in os.walk(path):
91 for root, dirs, files in os.walk(path):
90 for f in files:
92 for f in files:
91 final_path = os.path.join(root, f)
93 final_path = os.path.join(root, f)
92 try:
94 try:
93 mtime = os.stat(final_path).st_mtime
95 mtime = os.stat(final_path).st_mtime
94 if (cur_time - mtime) > older_than:
96 if (cur_time - mtime) > older_than:
95 items_count += 1
97 items_count += 1
96 if callback:
98 if callback:
97 callback_res = callback(final_path)
99 callback_res = callback(final_path)
98 callbacks += 1
100 callbacks += 1
99 else:
101 else:
100 used += os.path.getsize(final_path)
102 used += os.path.getsize(final_path)
101 except OSError:
103 except OSError:
102 pass
104 pass
103 value.update({
105 value.update({
104 'percent': 100,
106 'percent': 100,
105 'used': used,
107 'used': used,
106 'total': used,
108 'total': used,
107 'items': items_count,
109 'items': items_count,
108 'callbacks': callbacks
110 'callbacks': callbacks
109 })
111 })
110 return value
112 return value
111
113
112 def get_count(self):
114 def get_count(self):
113 try:
115 try:
114 sessions_dir = self._get_sessions_dir()
116 sessions_dir = self._get_sessions_dir()
115 items_count = self._count_on_filesystem(sessions_dir)['items']
117 items_count = self._count_on_filesystem(sessions_dir)['items']
116 except Exception:
118 except Exception:
117 items_count = self.NOT_AVAILABLE
119 items_count = self.NOT_AVAILABLE
118 return items_count
120 return items_count
119
121
120 def get_expired_count(self, older_than_seconds=0):
122 def get_expired_count(self, older_than_seconds=0):
121 try:
123 try:
122 sessions_dir = self._get_sessions_dir()
124 sessions_dir = self._get_sessions_dir()
123 items_count = self._count_on_filesystem(
125 items_count = self._count_on_filesystem(
124 sessions_dir, older_than=older_than_seconds)['items']
126 sessions_dir, older_than=older_than_seconds)['items']
125 except Exception:
127 except Exception:
126 items_count = self.NOT_AVAILABLE
128 items_count = self.NOT_AVAILABLE
127 return items_count
129 return items_count
128
130
129 def clean_sessions(self, older_than_seconds=0):
131 def clean_sessions(self, older_than_seconds=0):
130 # find . -mtime +60 -exec rm {} \;
132 # find . -mtime +60 -exec rm {} \;
131
133
132 sessions_dir = self._get_sessions_dir()
134 sessions_dir = self._get_sessions_dir()
133
135
134 def remove_item(path):
136 def remove_item(path):
135 os.remove(path)
137 os.remove(path)
136
138
137 stats = self._count_on_filesystem(
139 stats = self._count_on_filesystem(
138 sessions_dir, older_than=older_than_seconds,
140 sessions_dir, older_than=older_than_seconds,
139 callback=remove_item)
141 callback=remove_item)
140 return stats['callbacks']
142 return stats['callbacks']
141
143
142
144
145
143 class MemcachedAuthSessions(BaseAuthSessions):
146 class MemcachedAuthSessions(BaseAuthSessions):
144 SESSION_TYPE = 'ext:memcached'
147 SESSION_TYPE = 'ext:memcached'
148 _key_regex = re.compile(r'ITEM (.*_session) \[(.*); (.*)\]')
149
150 def _get_client(self):
151 import memcache
152 client = memcache.Client([self.config.get('beaker.session.url')])
153 return client
154
155 def _get_telnet_client(self, host, port):
156 import telnetlib
157 client = telnetlib.Telnet(host, port, None)
158 return client
159
160 def _run_telnet_cmd(self, client, cmd):
161 client.write("%s\n" % cmd)
162 return client.read_until('END')
163
164 def key_details(self, client, slab_ids, limit=100):
165 """ Return a list of tuples containing keys and details """
166 cmd = 'stats cachedump %s %s'
167 for slab_id in slab_ids:
168 for key in self._key_regex.finditer(
169 self._run_telnet_cmd(client, cmd % (slab_id, limit))):
170 yield key
145
171
146 def get_count(self):
172 def get_count(self):
147 return self.NOT_AVAILABLE
173 client = self._get_client()
174 count = self.NOT_AVAILABLE
175 try:
176 slabs = []
177 for server, slabs_data in client.get_slabs():
178 slabs.extend(slabs_data.keys())
179
180 host, port = client.servers[0].address
181 telnet_client = self._get_telnet_client(host, port)
182 keys = self.key_details(telnet_client, slabs)
183 count = 0
184 for _k in keys:
185 count += 1
186 except Exception:
187 return count
188
189 return count
148
190
149 def get_expired_count(self, older_than_seconds=None):
191 def get_expired_count(self, older_than_seconds=None):
150 return self.NOT_AVAILABLE
192 return self.NOT_AVAILABLE
151
193
152 def clean_sessions(self, older_than_seconds=None):
194 def clean_sessions(self, older_than_seconds=None):
153 raise CleanupCommand('Cleanup for this session type not yet available')
195 raise CleanupCommand('Cleanup for this session type not yet available')
154
196
155
197
156 class MemoryAuthSessions(BaseAuthSessions):
198 class MemoryAuthSessions(BaseAuthSessions):
157 SESSION_TYPE = 'memory'
199 SESSION_TYPE = 'memory'
158
200
159 def get_count(self):
201 def get_count(self):
160 return self.NOT_AVAILABLE
202 return self.NOT_AVAILABLE
161
203
162 def get_expired_count(self, older_than_seconds=None):
204 def get_expired_count(self, older_than_seconds=None):
163 return self.NOT_AVAILABLE
205 return self.NOT_AVAILABLE
164
206
165 def clean_sessions(self, older_than_seconds=None):
207 def clean_sessions(self, older_than_seconds=None):
166 raise CleanupCommand('Cleanup for this session type not yet available')
208 raise CleanupCommand('Cleanup for this session type not yet available')
167
209
168
210
169 def get_session_handler(session_type):
211 def get_session_handler(session_type):
170 types = {
212 types = {
171 'file': FileAuthSessions,
213 'file': FileAuthSessions,
172 'ext:memcached': MemcachedAuthSessions,
214 'ext:memcached': MemcachedAuthSessions,
173 'ext:database': DbAuthSessions,
215 'ext:database': DbAuthSessions,
174 'memory': MemoryAuthSessions
216 'memory': MemoryAuthSessions
175 }
217 }
176
218
177 try:
219 try:
178 return types[session_type]
220 return types[session_type]
179 except KeyError:
221 except KeyError:
180 raise ValueError(
222 raise ValueError(
181 'This type {} is not supported'.format(session_type))
223 'This type {} is not supported'.format(session_type))
General Comments 0
You need to be logged in to leave comments. Login now