Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions bin/showaccounting.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# --- BEGIN_HEADER ---
#
# showaccounting - Display storage accounting
# Copyright (C) 2003-2026 The MiG Project by the Science HPC Center at UCPH
#
# This file is part of MiG.
#
# MiG is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# MiG 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 General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

Check warning on line 23 in bin/showaccounting.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)

Check warning on line 23 in bin/showaccounting.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
#
# -- END_HEADER ---
#

"""Create accounting information for users"""

from __future__ import absolute_import, print_function

import datetime
import getopt
import re
import sys

from mig.lib.accounting import get_usage, human_readable_filesize
from mig.shared.conf import get_configuration_object
from mig.shared.defaults import gdp_distinguished_field


def usage(name='showaccounting.py'):
"""Usage help"""

print("""Create accounting information based on quota.
Usage:
%(name)s [ACCOUNTING_OPTIONS]
Where ACCOUNTING_OPTIONS may be one or more of:
-h Show this help
-v Verbose output
-c CONF_FILE Use CONF_FILE as server configuration
-f User filter Regex user (CERT_DN) filter
-m Minimum usage Only show accounts using more than
minimum usage (TB).
-t TIMESTAMP Use specific timestamp, latest if unset
""" % {'name': name})


def show_accounting(configuration,
timestamp,
user_filter,
minimum_usage,
verbose):
"""Print user accounting report"""
user_filter_re = None
if user_filter:
try:
user_filter_re = re.compile(user_filter)
except Exception as err:
print("ERROR: Failed to compile user_filter: %r error: %s"
% (user_filter, err))
return

usage = get_usage(configuration,
timestamp=timestamp,
verbose=verbose)

accounting = usage.get('accounting', {})
accounting_timestamp = usage.get('timestamp', 0)
accounting_datestr \
= datetime.datetime.fromtimestamp(accounting_timestamp) \
.strftime('%d/%m/%Y-%H:%M:%S')

# Sorted by total bytes and print usage for users

report_total_users = 0
report_shown_users = 0
report_total_bytes = 0
report_shown_bytes = 0
total_bytes_map = {}
for username, values in accounting.items():
# Do not show GDP project users
# projects are accounted for by the main user
if configuration.site_enable_gdp \
and username.find("/%s=" % gdp_distinguished_field) != -1:
continue
report_total_users += 1
total_bytes = values.get('total_bytes', 0)
report_total_bytes += total_bytes
if total_bytes < minimum_usage \
or user_filter_re and not user_filter_re.fullmatch(username):
continue
report_shown_users += 1
report_shown_bytes += total_bytes
total_bytes_map_userlist = total_bytes_map.get(total_bytes, [])
total_bytes_map_userlist.append(username)
total_bytes_map[total_bytes] = total_bytes_map_userlist
sorted_total_bytes = sorted(list(total_bytes_map), reverse=True)

print("\nAccounting (%d) %s for storage quota(s):"
% (accounting_timestamp, accounting_datestr))
for quota_fs, values in usage.get('quota', {}).items():
quota_mtime = values.get('mtime', 0)
quota_datestr = datetime.datetime.fromtimestamp(quota_mtime) \
.strftime('%d/%m/%Y-%H:%M:%S')
print(" - %s (%d) %s" % (quota_fs,
quota_mtime,
quota_datestr))

print("Found a total of %s users using %s storage"
% (report_total_users,
human_readable_filesize(report_total_bytes)))
print("Showing details for %s users using %s storage "
% (report_shown_users,
human_readable_filesize(report_shown_bytes)))
print("User filter: %r" % user_filter)
print("Minimum usage: %s" % human_readable_filesize(minimum_usage))
for total_bytes in sorted_total_bytes:
total_bytes_human = human_readable_filesize(total_bytes)
for username in total_bytes_map[total_bytes]:
report = accounting[username]
home_report = report.get('home_report', '')
freeze_report = report.get('freeze_report', '')
vgrid_report = report.get('vgrid_report', '')
ext_users_report = report.get('ext_users_report', '')
peers_report = report.get('peers_report', '')
print("\n%s:" % username)
print("Total usage: %s" % total_bytes_human)
if home_report:
print(home_report)
if freeze_report:
print(freeze_report)
if vgrid_report:
print(vgrid_report)
if ext_users_report:
print(ext_users_report)
if peers_report:
print(peers_report)


if '__main__' == __name__:
conf_path = None
user_filter = None
timestamp = 0
minimum_usage = 0
verbose = False
opt_args = 'hvc:f:m:t:'
try:
(opts, args) = getopt.getopt(sys.argv[1:], opt_args)
for (opt, val) in opts:
if opt == '-h':
usage()
sys.exit(0)
if opt == '-v':
verbose = True
elif opt == '-c':
conf_path = val
elif opt == '-f':
user_filter = val
elif opt == '-m':
minimum_usage = float(val)*(1024**4)

Check failure on line 171 in bin/showaccounting.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Incompatible types in assignment (expression has type "float", variable has type "int") [assignment]

Check failure on line 171 in bin/showaccounting.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

Incompatible types in assignment (expression has type "float", variable has type "int") [assignment]
elif opt == '-t':
timestamp = int(val)
else:
print('Error: %s not supported!' % opt)
usage()
sys.exit(1)
except getopt.GetoptError as err:
print('Error: ', err.msg)
usage()
sys.exit(1)

configuration = get_configuration_object(config_file=conf_path,
skip_log=True,
disable_auth_log=True)

show_accounting(configuration,
timestamp,
user_filter,
minimum_usage,
verbose)

sys.exit(0)
6 changes: 6 additions & 0 deletions mig/install/MiGserver-template.conf
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ workflows_home = %(state_path)s/workflows_home/
workflows_db_home = %(state_path)s/workflows_db_home/
notify_home = %(state_path)s/notify_home/
quota_home = %(state_path)s/quota_home/
accounting_home = %(state_path)s/accounting_home/

# GDP data categories metadata and helpers json file
gdp_data_categories = %(gdp_home)s/__GDP_DATA_CATEGORIES__
Expand Down Expand Up @@ -554,6 +555,9 @@ update_interval = __QUOTA_UPDATE_INTERVAL__
user_limit = __QUOTA_USER_LIMIT__
vgrid_limit = __QUOTA_VGRID_LIMIT__

[ACCOUNTING]
update_interval = __ACCOUNTING_UPDATE_INTERVAL__

[SITE]
# Web site appearance
# Whether to use Python 3 for all Python invocations
Expand Down Expand Up @@ -677,6 +681,8 @@ enable_openid = __ENABLE_OPENID__
enable_sharelinks = __ENABLE_SHARELINKS__
# Enable storage quota
enable_quota = __ENABLE_QUOTA__
# Enable storage accounting
enable_accounting = __ENABLE_ACCOUNTING__
Comment thread
jonasbardino marked this conversation as resolved.
# Enable background data transfers daemon - requires lftp and rsync
enable_transfers = __ENABLE_TRANSFERS__
# Explicit background transfer source addresses for use in pub key restrictions
Expand Down
2 changes: 2 additions & 0 deletions mig/install/generateconfs.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
Expand All @@ -20,7 +20,7 @@
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

Check warning on line 23 in mig/install/generateconfs.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)

Check warning on line 23 in mig/install/generateconfs.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

line too long (81 > 80 characters)
#
# -- END_HEADER ---
#
Expand All @@ -47,7 +47,7 @@

# NOTE: moved mig imports into try/except to avoid autopep8 moving to top!
try:
from mig.shared.defaults import MIG_BASE, MIG_ENV

Check failure on line 50 in mig/install/generateconfs.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

unused import 'MIG_ENV' (90% confidence)

Check failure on line 50 in mig/install/generateconfs.py

View workflow job for this annotation

GitHub Actions / Style check python and annotate

unused import 'MIG_ENV' (90% confidence)
from mig.shared.install import generate_confs
except ImportError:
print("ERROR: the migrid modules must be in PYTHONPATH")
Expand Down Expand Up @@ -255,6 +255,7 @@
'quota_update_interval',
'quota_user_limit',
'quota_vgrid_limit',
'accounting_update_interval',
'wwwserve_max_bytes',
]
bool_names = [
Expand All @@ -273,6 +274,7 @@
'enable_events',
'enable_sharelinks',
'enable_quota',
'enable_accounting',
'enable_transfers',
'enable_freeze',
'enable_sandboxes',
Expand Down
53 changes: 51 additions & 2 deletions mig/install/migrid-init.d-deb-template
Original file line number Diff line number Diff line change
Expand Up @@ -70,13 +70,14 @@ MIG_NOTIFY=${MIG_CODE}/server/grid_notify.py
MIG_IMNOTIFY=${MIG_CODE}/server/grid_imnotify.py
MIG_VMPROXY=${MIG_CODE}/server/grid_vmproxy.py
MIG_QUOTA=${MIG_CODE}/server/grid_quota.py
MIG_ACCOUNTING=${MIG_CODE}/server/grid_accounting.py
MIG_CHKUSERROOT=${MIG_CODE}/server/chkuserroot.py
MIG_CHKSIDROOT=${MIG_CODE}/server/chksidroot.py

show_usage() {
echo "Usage: migrid {start|stop|status|restart|reload}[daemon DAEMON]"
echo "where daemon is left out for all or given along with DAEMON as one of the following"
echo "(script|monitor|sshmux|events|cron|janitor|transfers|openid|sftp|sftpsubsys|webdavs|ftps|notify|imnotify|vmproxy|quota|all)"
echo "(script|monitor|sshmux|events|cron|janitor|transfers|openid|sftp|sftpsubsys|webdavs|ftps|notify|imnotify|vmproxy|quota|accounting|all)"
}

check_enabled() {
Expand Down Expand Up @@ -284,6 +285,18 @@ start_quota() {
log_end_msg 1 || true
fi
}
start_accounting() {
check_enabled "accounting" || return 0
DAEMON_PATH=${MIG_QUOTA}
SHORT_NAME=$(basename ${DAEMON_PATH})
PID_FILE="$PID_DIR/${SHORT_NAME}.pid"
log_daemon_msg "Starting MiG accounting daemon" ${SHORT_NAME} || true
if start-stop-daemon --start --quiet --oknodo --pidfile ${PID_FILE} --make-pidfile --user root --chuid root --background --name ${SHORT_NAME} --startas ${DAEMON_PATH} ; then
log_end_msg 0 || true
else
log_end_msg 1 || true
fi
}
start_sftpsubsys() {
check_enabled "sftp_subsys" || return 0
DAEMON_PATH=${MIG_SFTPSUBSYS}
Expand Down Expand Up @@ -313,6 +326,7 @@ start_all() {
start_imnotify
start_vmproxy
start_quota
start_accounting
return 0
}

Expand Down Expand Up @@ -558,6 +572,19 @@ stop_quota() {
log_end_msg 1 || true
fi
}
stop_accounting() {
check_enabled "accounting" || return 0
DAEMON_PATH=${MIG_QUOTA}
SHORT_NAME=$(basename ${DAEMON_PATH})
PID_FILE="$PID_DIR/${SHORT_NAME}.pid"
log_daemon_msg "Stopping MiG accounting" ${SHORT_NAME} || true
if start-stop-daemon --stop --quiet --oknodo --pidfile ${PID_FILE} ; then
rm -f ${PID_FILE}
log_end_msg 0 || true
else
log_end_msg 1 || true
fi
}
stop_sftpsubsys_workers() {
DAEMON_PATH=${MIG_SFTPSUBSYS_WORKER}
SHORT_NAME=$(basename ${DAEMON_PATH})
Expand Down Expand Up @@ -598,6 +625,7 @@ stop_all() {
stop_imnotify
stop_vmproxy
stop_quota
stop_accounting
return 0
}

Expand Down Expand Up @@ -782,6 +810,18 @@ reload_quota() {
log_end_msg 1 || true
fi
}
reload_accounting() {
check_enabled "accounting" || return 0
DAEMON_PATH=${MIG_QUOTA}
SHORT_NAME=$(basename ${DAEMON_PATH})
PID_FILE="$PID_DIR/${SHORT_NAME}.pid"
log_daemon_msg "Reloading MiG accounting" ${SHORT_NAME} || true
if start-stop-daemon --stop --signal HUP --quiet --oknodo --pidfile ${PID_FILE} ; then
log_end_msg 0 || true
else
log_end_msg 1 || true
fi
}
reload_sftpsubsys_workers() {
DAEMON_PATH=${MIG_SFTPSUBSYS_WORKER}
SHORT_NAME=$(basename ${DAEMON_PATH})
Expand Down Expand Up @@ -835,6 +875,7 @@ reload_all() {
reload_imnotify
reload_vmproxy
reload_quota
reload_accounting
# Apache helpers to verify proper chrooting
reload_chkuserroot
reload_chksidroot
Expand Down Expand Up @@ -946,6 +987,13 @@ status_quota() {
PID_FILE="$PID_DIR/${SHORT_NAME}.pid"
status_of_proc -p ${PID_FILE} ${DAEMON_PATH} ${SHORT_NAME}
}
status_accounting() {
check_enabled "accounting" || return 0
DAEMON_PATH=${MIG_QUOTA}
SHORT_NAME=$(basename ${DAEMON_PATH})
PID_FILE="$PID_DIR/${SHORT_NAME}.pid"
status_of_proc -p ${PID_FILE} ${DAEMON_PATH} ${SHORT_NAME}
}
status_sftpsubsys_workers() {
DAEMON_PATH=${MIG_SFTPSUBSYS_WORKER}
SHORT_NAME=$(basename ${DAEMON_PATH})
Expand Down Expand Up @@ -985,6 +1033,7 @@ status_all() {
status_imnotify
status_vmproxy
status_quota
status_accounting
return 0
}

Expand All @@ -996,7 +1045,7 @@ test -f ${MIG_SCRIPT} || exit 0

# Force valid target
case "$2" in
script|monitor|sshmux|events|cron|janitor|transfers|openid|sftp|sftpsubsys|webdavs|ftps|notify|imnotify|vmproxy|quota|all)
script|monitor|sshmux|events|cron|janitor|transfers|openid|sftp|sftpsubsys|webdavs|ftps|notify|imnotify|vmproxy|quota|accounting|all)
TARGET="$2"
;;
'')
Expand Down
Loading