"""
Mixin class for databse client with parent account related
functions
"""
from datetime import datetime
from accre.database.model import *
from accre.database.util import limit_and_offset
[docs]class DBClientParentAccountMixin:
"""
Functionality related to slurm parent accounts
"""
[docs] def add_account(self, *,
name,
department,
division,
organization,
pis,
scheduler_account=True,
fairshare=1,
max_cpu=None,
max_mem=None,
max_runmins=None,
qos='normal',
active=True,
join_date=None
):
"""
Add a new administrative account to the database. The
dept/div/org triplet must be a valid combination, i.e. the division
must be a member of the organization.
:param str name: The name of the account
:param str department: Name of the department, must be a department
already in the database
:param str division: Name of the division, must be a division
already in the database
:param str organization: Name of the organization, must be an
organization already in the database
:param list(str) pis: A list of PI vunetids. Typically one PI.
Can be an empty list for an account with no PI.
:param bool scheduler_account: True if this account should exist
on the cluster job scheduler
:param int fairshare: Scheduler fairshare for the account
:param int max_cpu: Maximum running CPU cores for the account
:param str max_mem: Maximum running memory for the account, should
be of the form "100G" or "10800M"
:param int max_runmins: Maximum running scheduled cpu minutes for
the account
:param str qos: Scheduler QOS for the account
:param bool active: True for accounts currently using the cluster
or being billed for services
:param datetime.datetime join_date: Date that the PI joined
the cluster, defaults to the current date
"""
if join_date is None:
join_date = datetime.now()
with self.engine.begin() as conn:
ins = ACCOUNTS.insert().values(
name=name,
department=department,
division=division,
organization=organization,
scheduler_account=scheduler_account,
fairshare=fairshare,
max_cpu=max_cpu,
max_mem=max_mem,
max_runmins=max_runmins,
qos=qos,
active=active,
join_date=join_date
)
conn.execute(ins)
for pi in pis:
ins = ACCOUNTS_PIS.insert().values(
name=name,
pi=pi,
)
conn.execute(ins)
ins = ACCOUNT_LOGS.insert().values(
name=name,
date=join_date,
comment="Account added to database with PIs: {}".format(','.join(pis))
)
conn.execute(ins)
[docs] def add_account_log(self, name, comment, log_date=None):
"""
Add an entry to the given business account's log.
:param str name: name of the account
:param str comment: entry to add to the log
:param datetime.datetime log_date: Date to use for the log entry,
defaults to the current date.
"""
if log_date is None:
log_date = datetime.now()
with self.engine.connect() as conn:
ins = ACCOUNT_LOGS.insert().values(
name=name,
date=log_date,
comment=comment
)
conn.execute(ins)
[docs] def get_account_logs(self, name):
"""
Fetch user logs for a given business account sorted by newest first
:param str name: business account name
:return: list of all account log dates and comments for the account
:rtype: list(tuple(datetime.datetime, comment))
"""
with self.engine.connect() as conn:
s = select([ACCOUNT_LOGS.c.date, ACCOUNT_LOGS.c.comment]).where(
ACCOUNT_LOGS.c.name == name
)
result = list(conn.execute(s))
return sorted(result, reverse=True)
[docs] def list_accounts(self, active=False):
"""
List all accounts in the databse, or all active accounts.
:param bool active: List only active accounts if set to true
:returns: List of account ids
:rtype: list(str)
"""
with self.engine.connect() as conn:
if active:
s = select([ACCOUNTS.c.name, ACCOUNTS.c.active]).where(
ACCOUNTS.c.active == True
)
else:
s = select([ACCOUNTS.c.name])
return [r['name'] for r in conn.execute(s).fetchall()]
[docs] def all_accounts_info(self, limit=None, offset=None):
"""
Retrieve general information for all ACCRE adminstrative accounts
NOT including PIs associated with the account.
:param int limit: Maximum number of records to return, return all
if None
:param int offset: Offset from first record to begin list of records,
start at the first record if None
:returns: General account information for all accounts
:rtype: list(dict)
"""
with self.engine.connect() as conn:
s = select([ACCOUNTS])
s = limit_and_offset(s, limit=limit, offset=offset)
accounts = conn.execute(s).fetchall()
return [dict(a) for a in accounts]
[docs] def account_info(self, name):
"""
Retrieve general information for an ACCRE adminstrative account
including all PI VUNetIDs
:param str name: ACCRE account to be queried
:returns: General account information
:rtype: dict
"""
with self.engine.connect() as conn:
s = select([ACCOUNTS]).where(ACCOUNTS.c.name == name)
account = conn.execute(s).fetchone()
s = select([ACCOUNTS_PIS]).where(ACCOUNTS_PIS.c.name == name)
pis = [u['pi'] for u in conn.execute(s).fetchall()]
s = select([GROUPS]).where(name == GROUPS.c.account)
groups = [u['name'] for u in conn.execute(s).fetchall()]
result = dict(account)
result['pis'] = pis
result['groups'] = groups
return result
[docs] def modify_account(self, account, reason=None, log_date=None, **updates):
"""
Modify one or more fields of an existing ACCRE account and
log the change with an optional reason.
:param str account: Name of the account
:param str reason: Explanation of the change to be logged
:param dict updates: Columns to be updated with their values
:param datetime.datetime log_date: Date to log the user modification,
defaults to the current date
"""
if log_date is None:
log_date = datetime.now()
invalid_fields = [
c for c in updates if c not in ACCOUNTS.columns or c == 'name'
]
if invalid_fields:
raise ACCREValueError(
'Invalid account fields for update: {}'.format(invalid_fields)
)
with self.engine.begin() as conn:
up = ACCOUNTS.update().values(**updates).where(
ACCOUNTS.c.name == account
)
conn.execute(up)
comment = 'Updated account fields: {0}.'.format(
', '.join(updates.keys())
)
if reason:
comment = comment + ' Reason: {0}'.format(reason)
ins = ACCOUNT_LOGS.insert().values(
name=account,
date=log_date,
comment=comment
)
conn.execute(ins)
[docs] def add_account_pis(self, name, *pis, reason=None, log_date=None):
"""
Add specified pis to a business account
:param str name: business account to add pi(s) to
:param str pis: pis to add to the account
:param str reason: explanation of the change for logging
:param datetime.datetime log_date: Date to use for the log entry,
defaults to the current date.
"""
if log_date is None:
log_date = datetime.now()
with self.engine.begin() as conn:
for pi in pis:
ins = ACCOUNTS_PIS.insert().values(
name=name,
pi=pi
)
conn.execute(ins)
msg = "Pi(s) {0} added to account".format(', '.join(pis))
if reason:
msg += ", reason: {0}".format(reason)
ins = ACCOUNT_LOGS.insert().values(
name=name,
date=log_date,
comment=msg
)
conn.execute(ins)
[docs] def remove_account_pis(self, name, *pis, reason=None, log_date=None):
"""
Remove specified pis from a business account
:param str name: business account remove add pi(s) from
:param str pis: pis to remove from the account
:param str reason: explanation of the change for logging
:param datetime.datetime log_date: Date to use for the log entry,
defaults to the current date.
"""
if log_date is None:
log_date = datetime.now()
with self.engine.begin() as conn:
for pi in pis:
delete = ACCOUNTS_PIS.delete().where(
(ACCOUNTS_PIS.c.name == name) &
(ACCOUNTS_PIS.c.pi == pi)
)
conn.execute(delete)
msg = "Pi(s) {0} removed from account".format(', '.join(pis))
if reason:
msg += ", reason: {0}".format(reason)
ins = ACCOUNT_LOGS.insert().values(
name=name,
date=log_date,
comment=msg
)
conn.execute(ins)