diff --git a/rhodecode/api/tests/test_cleanup_sessions.py b/rhodecode/api/tests/test_cleanup_sessions.py
new file mode 100644
--- /dev/null
+++ b/rhodecode/api/tests/test_cleanup_sessions.py
@@ -0,0 +1,44 @@
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2017-2017 RhodeCode GmbH
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License, version 3
+# (only), as published by the Free Software Foundation.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see .
+#
+# This program is dual-licensed. If you wish to learn more about the
+# RhodeCode Enterprise Edition, including its added features, Support services,
+# and proprietary license terms, please see https://rhodecode.com/licenses/
+
+import mock
+import pytest
+
+from rhodecode.lib.user_sessions import FileAuthSessions
+from rhodecode.api.tests.utils import (
+ build_data, api_call, assert_ok, assert_error, crash)
+
+
+@pytest.mark.usefixtures("testuser_api", "app")
+class TestCleanupSessions(object):
+ def test_api_cleanup_sessions(self):
+ id_, params = build_data(self.apikey, 'cleanup_sessions')
+ response = api_call(self.app, params)
+
+ expected = {'backend': 'file sessions', 'sessions_removed': 0}
+ assert_ok(id_, expected, given=response.body)
+
+ @mock.patch.object(FileAuthSessions, 'clean_sessions', crash)
+ def test_api_cleanup_error(self):
+ id_, params = build_data(self.apikey, 'cleanup_sessions', )
+ response = api_call(self.app, params)
+
+ expected = 'Error occurred during session cleanup'
+ assert_error(id_, expected, given=response.body)
diff --git a/rhodecode/api/views/server_api.py b/rhodecode/api/views/server_api.py
--- a/rhodecode/api/views/server_api.py
+++ b/rhodecode/api/views/server_api.py
@@ -26,6 +26,8 @@ from rhodecode.api import jsonrpc_method
from rhodecode.api.utils import (
Optional, OAttr, has_superadmin_permission, get_user_or_error)
from rhodecode.lib.utils import repo2db_mapper
+from rhodecode.lib import system_info
+from rhodecode.lib import user_sessions
from rhodecode.model.db import UserIpMap
from rhodecode.model.scm import ScmModel
@@ -176,3 +178,67 @@ def rescan_repos(request, apiuser, remov
'Error occurred during rescan repositories action'
)
+
+@jsonrpc_method()
+def cleanup_sessions(request, apiuser, older_then=Optional(60)):
+ """
+ Triggers a session cleanup action.
+
+ If the ``older_then`` option is set, only sessions that hasn't been
+ accessed in the given number of days will be removed.
+
+ This command can only be run using an |authtoken| with admin rights to
+ the specified repository.
+
+ This command takes the following options:
+
+ :param apiuser: This is filled automatically from the |authtoken|.
+ :type apiuser: AuthUser
+ :param older_then: Deletes session that hasn't been accessed
+ in given number of days.
+ :type older_then: Optional(int)
+
+ Example output:
+
+ .. code-block:: bash
+
+ id :
+ result: {
+ "backend": "",
+ "sessions_removed":
+ }
+ error : null
+
+ Example error output:
+
+ .. code-block:: bash
+
+ id :
+ result : null
+ error : {
+ 'Error occurred during session cleanup'
+ }
+
+ """
+ if not has_superadmin_permission(apiuser):
+ raise JSONRPCForbidden()
+
+ older_then = Optional.extract(older_then)
+ older_than_seconds = 60 * 60 * 24 * older_then
+
+ config = system_info.rhodecode_config().get_value()['value']['config']
+ session_model = user_sessions.get_session_handler(
+ config.get('beaker.session.type', 'memory'))(config)
+
+ backend = session_model.SESSION_TYPE
+ try:
+ cleaned = session_model.clean_sessions(
+ older_than_seconds=older_than_seconds)
+ return {'sessions_removed': cleaned, 'backend': backend}
+ except user_sessions.CleanupCommand as msg:
+ return {'cleanup_command': msg.message, 'backend': backend}
+ except Exception as e:
+ log.exception('Failed session cleanup')
+ raise JSONRPCError(
+ 'Error occurred during session cleanup'
+ )
diff --git a/rhodecode/lib/user_sessions.py b/rhodecode/lib/user_sessions.py
--- a/rhodecode/lib/user_sessions.py
+++ b/rhodecode/lib/user_sessions.py
@@ -66,8 +66,10 @@ class DbAuthSessions(BaseAuthSessions):
def clean_sessions(self, older_than_seconds=None):
expiry_date = self._seconds_to_date(older_than_seconds)
+ to_remove = DbSession.query().filter(DbSession.accessed < expiry_date).count()
DbSession.query().filter(DbSession.accessed < expiry_date).delete()
Session().commit()
+ return to_remove
class FileAuthSessions(BaseAuthSessions):
@@ -78,9 +80,11 @@ class FileAuthSessions(BaseAuthSessions)
return data_dir
def _count_on_filesystem(self, path, older_than=0, callback=None):
- value = dict(percent=0, used=0, total=0, items=0, path=path, text='')
+ value = dict(percent=0, used=0, total=0, items=0, callbacks=0,
+ path=path, text='')
items_count = 0
used = 0
+ callbacks = 0
cur_time = time.time()
for root, dirs, files in os.walk(path):
for f in files:
@@ -91,6 +95,7 @@ class FileAuthSessions(BaseAuthSessions)
items_count += 1
if callback:
callback_res = callback(final_path)
+ callbacks += 1
else:
used += os.path.getsize(final_path)
except OSError:
@@ -99,7 +104,8 @@ class FileAuthSessions(BaseAuthSessions)
'percent': 100,
'used': used,
'total': used,
- 'items': items_count
+ 'items': items_count,
+ 'callbacks': callbacks
})
return value
@@ -128,9 +134,10 @@ class FileAuthSessions(BaseAuthSessions)
def remove_item(path):
os.remove(path)
- return self._count_on_filesystem(
+ stats = self._count_on_filesystem(
sessions_dir, older_than=older_than_seconds,
- callback=remove_item)['items']
+ callback=remove_item)
+ return stats['callbacks']
class MemcachedAuthSessions(BaseAuthSessions):