diff --git a/src/bindings/python/fluxacct/accounting/Makefile.am b/src/bindings/python/fluxacct/accounting/Makefile.am index 7db2c2cd..3ee5eb9b 100644 --- a/src/bindings/python/fluxacct/accounting/Makefile.am +++ b/src/bindings/python/fluxacct/accounting/Makefile.am @@ -1,6 +1,7 @@ acctpy_PYTHON = \ __init__.py \ - accounting_cli_functions.py \ + user_subcommands.py \ + bank_subcommands.py \ job_archive_interface.py \ create_db.py diff --git a/src/bindings/python/fluxacct/accounting/accounting_cli_functions.py b/src/bindings/python/fluxacct/accounting/bank_subcommands.py old mode 100755 new mode 100644 similarity index 61% rename from src/bindings/python/fluxacct/accounting/accounting_cli_functions.py rename to src/bindings/python/fluxacct/accounting/bank_subcommands.py index 2c92eea8..c48d2a46 --- a/src/bindings/python/fluxacct/accounting/accounting_cli_functions.py +++ b/src/bindings/python/fluxacct/accounting/bank_subcommands.py @@ -10,10 +10,10 @@ # SPDX-License-Identifier: LGPL-3.0 ############################################################### import sqlite3 -import time - import pandas as pd +from fluxacct.accounting import user_subcommands as u + def add_bank(conn, bank, shares, parent_bank=""): # if the parent bank is not "", that means the bank @@ -99,7 +99,7 @@ def get_sub_banks(row): for assoc_row in cursor.execute( select_associations_stmt, (row["bank"],) ): - delete_user(conn, username=assoc_row[0], bank=assoc_row[1]) + u.delete_user(conn, username=assoc_row[0], bank=assoc_row[1]) # else, delete all of its sub banks and continue traversing else: for _, sub_bank_row in dataframe.iterrows(): @@ -139,106 +139,3 @@ def edit_bank(conn, bank, shares): conn.commit() except pd.io.sql.DatabaseError as e_database_error: print(e_database_error) - - -def view_user(conn, user): - try: - # get the information pertaining to a user in the Accounting DB - select_stmt = "SELECT * FROM association_table where username=?" - dataframe = pd.read_sql_query(select_stmt, conn, params=(user,)) - # if the length of dataframe is 0, that means - # the user specified was not found in the table - if len(dataframe.index) == 0: - print("User not found in association_table") - else: - print(dataframe) - except pd.io.sql.DatabaseError as e_database_error: - print(e_database_error) - - -def add_user(conn, username, bank, admin_level=1, shares=1): - - try: - # insert the user values into association_table - conn.execute( - """ - INSERT INTO association_table ( - creation_time, - mod_time, - deleted, - username, - admin_level, - bank, - shares - ) - VALUES (?, ?, ?, ?, ?, ?, ?) - """, - ( - int(time.time()), - int(time.time()), - 0, - username, - admin_level, - bank, - shares, - ), - ) - # commit changes - conn.commit() - # insert the user values into job_usage_factor_table - conn.execute( - """ - INSERT INTO job_usage_factor_table ( - username, - bank - ) - VALUES (?, ?) - """, - ( - username, - bank, - ), - ) - conn.commit() - # make sure entry is unique - except sqlite3.IntegrityError as integrity_error: - print(integrity_error) - - -def delete_user(conn, username, bank): - # delete user account from association_table - delete_stmt = "DELETE FROM association_table WHERE username=? AND bank=?" - cursor = conn.cursor() - cursor.execute( - delete_stmt, - ( - username, - bank, - ), - ) - # commit changes - conn.commit() - - -def edit_user(conn, username, field, new_value): - fields = [ - "username", - "admin_level", - "bank", - "shares", - ] - if field in fields: - the_field = field - - # edit value in accounting database - conn.execute( - "UPDATE association_table SET " + the_field + "=? WHERE username=?", - ( - new_value, - username, - ), - ) - # commit changes - conn.commit() - else: - raise ValueError("Field not found in association table") diff --git a/src/bindings/python/fluxacct/accounting/test/test_bank_subcommands.py b/src/bindings/python/fluxacct/accounting/test/test_bank_subcommands.py index 52bfca5e..e04e6005 100755 --- a/src/bindings/python/fluxacct/accounting/test/test_bank_subcommands.py +++ b/src/bindings/python/fluxacct/accounting/test/test_bank_subcommands.py @@ -14,7 +14,7 @@ import sqlite3 import pandas as pd -from fluxacct.accounting import accounting_cli_functions as aclif +from fluxacct.accounting import bank_subcommands as b from fluxacct.accounting import create_db as c @@ -30,7 +30,7 @@ def setUpClass(self): # let's add a top-level account using the add-bank # subcommand def test_01_add_bank_success(self): - aclif.add_bank(acct_conn, bank="root", shares=100) + b.add_bank(acct_conn, bank="root", shares=100) select_stmt = "SELECT * FROM bank_table WHERE bank='root'" dataframe = pd.read_sql_query(select_stmt, acct_conn) self.assertEqual(len(dataframe.index), 1) @@ -38,14 +38,14 @@ def test_01_add_bank_success(self): # let's make sure if we try to add it a second time, # it fails gracefully def test_02_add_dup_bank(self): - aclif.add_bank(acct_conn, bank="root", shares=100) + b.add_bank(acct_conn, bank="root", shares=100) self.assertRaises(sqlite3.IntegrityError) # trying to add a sub account with an invalid parent bank # name should result in a failure def test_03_add_with_invalid_parent_bank(self): with self.assertRaises(Exception) as context: - aclif.add_bank( + b.add_bank( acct_conn, bank="bad_subaccount", parent_bank="bad_parentaccount", @@ -57,36 +57,36 @@ def test_03_add_with_invalid_parent_bank(self): # now let's add a couple sub accounts whose parent is 'root' # and whose total shares equal root's allocation (100 shares) def test_04_add_subaccounts(self): - aclif.add_bank(acct_conn, bank="sub_account_1", parent_bank="root", shares=50) + b.add_bank(acct_conn, bank="sub_account_1", parent_bank="root", shares=50) select_stmt = "SELECT * FROM bank_table WHERE bank='sub_account_1'" dataframe = pd.read_sql_query(select_stmt, acct_conn) self.assertEqual(len(dataframe.index), 1) - aclif.add_bank(acct_conn, bank="sub_account_2", parent_bank="root", shares=50) + b.add_bank(acct_conn, bank="sub_account_2", parent_bank="root", shares=50) select_stmt = "SELECT * FROM bank_table WHERE bank='sub_account_2'" dataframe = pd.read_sql_query(select_stmt, acct_conn) self.assertEqual(len(dataframe.index), 1) # removing a bank currently in the bank_table def test_05_delete_bank_success(self): - aclif.delete_bank(acct_conn, bank="sub_account_1") + b.delete_bank(acct_conn, bank="sub_account_1") select_stmt = "SELECT * FROM bank_table WHERE bank='sub_account_1'" dataframe = pd.read_sql_query(select_stmt, acct_conn) self.assertEqual(len(dataframe.index), 0) # deleting a parent bank should remove all of its sub banks def test_06_delete_parent_bank(self): - aclif.delete_bank(acct_conn, bank="root") - aclif.delete_bank(acct_conn, bank="sub_account_2") - - aclif.add_bank(acct_conn, bank="A", shares=1) - aclif.add_bank(acct_conn, bank="B", parent_bank="A", shares=1) - aclif.add_bank(acct_conn, bank="D", parent_bank="B", shares=1) - aclif.add_bank(acct_conn, bank="E", parent_bank="B", shares=1) - aclif.add_bank(acct_conn, bank="C", parent_bank="A", shares=1) - aclif.add_bank(acct_conn, bank="F", parent_bank="C", shares=1) - aclif.add_bank(acct_conn, bank="G", parent_bank="C", shares=1) - - aclif.delete_bank(acct_conn, bank="A") + b.delete_bank(acct_conn, bank="root") + b.delete_bank(acct_conn, bank="sub_account_2") + + b.add_bank(acct_conn, bank="A", shares=1) + b.add_bank(acct_conn, bank="B", parent_bank="A", shares=1) + b.add_bank(acct_conn, bank="D", parent_bank="B", shares=1) + b.add_bank(acct_conn, bank="E", parent_bank="B", shares=1) + b.add_bank(acct_conn, bank="C", parent_bank="A", shares=1) + b.add_bank(acct_conn, bank="F", parent_bank="C", shares=1) + b.add_bank(acct_conn, bank="G", parent_bank="C", shares=1) + + b.delete_bank(acct_conn, bank="A") select_stmt = "SELECT * FROM bank_table" dataframe = pd.read_sql_query(select_stmt, acct_conn) @@ -94,8 +94,8 @@ def test_06_delete_parent_bank(self): # edit a bank value def test_07_edit_bank_value(self): - aclif.add_bank(acct_conn, bank="root", shares=100) - aclif.edit_bank(acct_conn, bank="root", shares=50) + b.add_bank(acct_conn, bank="root", shares=100) + b.edit_bank(acct_conn, bank="root", shares=50) cursor = acct_conn.cursor() cursor.execute("SELECT shares FROM bank_table where bank='root'") @@ -105,8 +105,8 @@ def test_07_edit_bank_value(self): # an exception def test_08_edit_bank_value_fail(self): with self.assertRaises(Exception) as context: - aclif.add_bank(acct_conn, bank="bad_bank", shares=10) - aclif.edit_bank(acct_conn, bank="bad_bank", shares=-1) + b.add_bank(acct_conn, bank="bad_bank", shares=10) + b.edit_bank(acct_conn, bank="bad_bank", shares=-1) self.assertTrue("New shares amount must be >= 0" in str(context.exception)) diff --git a/src/bindings/python/fluxacct/accounting/test/test_job_archive_interface.py b/src/bindings/python/fluxacct/accounting/test/test_job_archive_interface.py index 6e43546f..c7e12603 100755 --- a/src/bindings/python/fluxacct/accounting/test/test_job_archive_interface.py +++ b/src/bindings/python/fluxacct/accounting/test/test_job_archive_interface.py @@ -20,7 +20,8 @@ from fluxacct.accounting import job_archive_interface as jobs from fluxacct.accounting import create_db as c -from fluxacct.accounting import accounting_cli_functions as aclif +from fluxacct.accounting import user_subcommands as u +from fluxacct.accounting import bank_subcommands as b class TestAccountingCLI(unittest.TestCase): @@ -66,16 +67,16 @@ def setUpClass(self): acct_conn.commit() # add bank hierarchy - aclif.add_bank(acct_conn, bank="A", shares=1) - aclif.add_bank(acct_conn, bank="B", parent_bank="A", shares=1) - aclif.add_bank(acct_conn, bank="C", parent_bank="B", shares=1) - aclif.add_bank(acct_conn, bank="D", parent_bank="B", shares=1) + b.add_bank(acct_conn, bank="A", shares=1) + b.add_bank(acct_conn, bank="B", parent_bank="A", shares=1) + b.add_bank(acct_conn, bank="C", parent_bank="B", shares=1) + b.add_bank(acct_conn, bank="D", parent_bank="B", shares=1) # add users - aclif.add_user(acct_conn, username="1001", bank="C") - aclif.add_user(acct_conn, username="1002", bank="C") - aclif.add_user(acct_conn, username="1003", bank="D") - aclif.add_user(acct_conn, username="1004", bank="D") + u.add_user(acct_conn, username="1001", bank="C") + u.add_user(acct_conn, username="1002", bank="C") + u.add_user(acct_conn, username="1003", bank="D") + u.add_user(acct_conn, username="1004", bank="D") jobid = 100 interval = 0 # add to job timestamps to diversify job-archive records @@ -394,7 +395,7 @@ def test_18_update_end_half_life_period(self): # removing a user from the flux-accounting DB should NOT remove their job # usage history from the job_usage_factor_table def test_19_keep_job_usage_records_upon_delete(self): - aclif.delete_user(acct_conn, username="1001", bank="C") + u.delete_user(acct_conn, username="1001", bank="C") select_stmt = """ SELECT * FROM diff --git a/src/bindings/python/fluxacct/accounting/test/test_user_subcommands.py b/src/bindings/python/fluxacct/accounting/test/test_user_subcommands.py index bd2c9bed..25b4dd3f 100755 --- a/src/bindings/python/fluxacct/accounting/test/test_user_subcommands.py +++ b/src/bindings/python/fluxacct/accounting/test/test_user_subcommands.py @@ -13,7 +13,7 @@ import os import sqlite3 -from fluxacct.accounting import accounting_cli_functions as aclif +from fluxacct.accounting import user_subcommands as u from fluxacct.accounting import create_db as c @@ -29,7 +29,7 @@ def setUpClass(self): # add a valid user to association_table def test_01_add_valid_user(self): - aclif.add_user( + u.add_user( acct_conn, username="fluxuser", admin_level="1", @@ -47,14 +47,14 @@ def test_01_add_valid_user(self): # adding a user with the same primary key (user_name, account) should # return an IntegrityError def test_02_add_duplicate_primary_key(self): - aclif.add_user( + u.add_user( acct_conn, username="fluxuser", admin_level="1", bank="acct", shares="10", ) - aclif.add_user( + u.add_user( acct_conn, username="fluxuser", admin_level="1", @@ -67,14 +67,14 @@ def test_02_add_duplicate_primary_key(self): # adding a user with the same username BUT a different account should # succeed def test_03_add_duplicate_user(self): - aclif.add_user( + u.add_user( acct_conn, username="dup_user", admin_level="1", bank="acct", shares="10", ) - aclif.add_user( + u.add_user( acct_conn, username="dup_user", admin_level="1", @@ -91,7 +91,7 @@ def test_03_add_duplicate_user(self): # edit a value for a user in the association table def test_04_edit_user_value(self): - aclif.edit_user(acct_conn, "fluxuser", "shares", "10000") + u.edit_user(acct_conn, "fluxuser", "shares", "10000") cursor = acct_conn.cursor() cursor.execute("SELECT shares FROM association_table where username='fluxuser'") @@ -101,7 +101,7 @@ def test_04_edit_user_value(self): # exist should return a ValueError def test_05_edit_bad_field(self): with self.assertRaises(ValueError): - aclif.edit_user(acct_conn, "fluxuser", "foo", "bar") + u.edit_user(acct_conn, "fluxuser", "foo", "bar") # delete a user from the association table def test_06_delete_user(self): @@ -113,7 +113,7 @@ def test_06_delete_user(self): self.assertEqual(len(num_rows_before_delete), 1) - aclif.delete_user(acct_conn, username="fluxuser", bank="acct") + u.delete_user(acct_conn, username="fluxuser", bank="acct") cursor.execute( "SELECT * FROM association_table WHERE username='fluxuser' AND bank='acct'" diff --git a/src/bindings/python/fluxacct/accounting/user_subcommands.py b/src/bindings/python/fluxacct/accounting/user_subcommands.py new file mode 100755 index 00000000..362b5ed5 --- /dev/null +++ b/src/bindings/python/fluxacct/accounting/user_subcommands.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 + +############################################################### +# Copyright 2020 Lawrence Livermore National Security, LLC +# (c.f. AUTHORS, NOTICE.LLNS, COPYING) +# +# This file is part of the Flux resource manager framework. +# For details, see https://github.com/flux-framework. +# +# SPDX-License-Identifier: LGPL-3.0 +############################################################### +import sqlite3 +import time + +import pandas as pd + + +def view_user(conn, user): + try: + # get the information pertaining to a user in the Accounting DB + select_stmt = "SELECT * FROM association_table where username=?" + dataframe = pd.read_sql_query(select_stmt, conn, params=(user,)) + # if the length of dataframe is 0, that means + # the user specified was not found in the table + if len(dataframe.index) == 0: + print("User not found in association_table") + else: + print(dataframe) + except pd.io.sql.DatabaseError as e_database_error: + print(e_database_error) + + +def add_user(conn, username, bank, admin_level=1, shares=1): + + try: + # insert the user values into association_table + conn.execute( + """ + INSERT INTO association_table ( + creation_time, + mod_time, + deleted, + username, + admin_level, + bank, + shares + ) + VALUES (?, ?, ?, ?, ?, ?, ?) + """, + ( + int(time.time()), + int(time.time()), + 0, + username, + admin_level, + bank, + shares, + ), + ) + # commit changes + conn.commit() + # insert the user values into job_usage_factor_table + conn.execute( + """ + INSERT INTO job_usage_factor_table ( + username, + bank + ) + VALUES (?, ?) + """, + ( + username, + bank, + ), + ) + conn.commit() + # make sure entry is unique + except sqlite3.IntegrityError as integrity_error: + print(integrity_error) + + +def delete_user(conn, username, bank): + # delete user account from association_table + delete_stmt = "DELETE FROM association_table WHERE username=? AND bank=?" + cursor = conn.cursor() + cursor.execute( + delete_stmt, + ( + username, + bank, + ), + ) + # commit changes + conn.commit() + + +def edit_user(conn, username, field, new_value): + fields = [ + "username", + "admin_level", + "bank", + "shares", + "max_jobs", + "max_wall_pj", + ] + if field in fields: + the_field = field + + # edit value in accounting database + conn.execute( + "UPDATE association_table SET " + the_field + "=? WHERE username=?", + ( + new_value, + username, + ), + ) + # commit changes + conn.commit() + else: + raise ValueError("Field not found in association table") diff --git a/src/cmd/flux-account.py b/src/cmd/flux-account.py index 01e727d0..b4f7f493 100755 --- a/src/cmd/flux-account.py +++ b/src/cmd/flux-account.py @@ -13,7 +13,8 @@ import os import fluxacct.accounting -from fluxacct.accounting import accounting_cli_functions as aclif +from fluxacct.accounting import user_subcommands as u +from fluxacct.accounting import bank_subcommands as b from fluxacct.accounting import job_archive_interface as jobs from fluxacct.accounting import create_db as c @@ -288,9 +289,9 @@ def set_output_file(args): def select_accounting_function(args, conn, output_file, parser): if args.func == "view_user": - aclif.view_user(conn, args.username) + u.view_user(conn, args.username) elif args.func == "add_user": - aclif.add_user( + u.add_user( conn, args.username, args.bank, @@ -300,9 +301,9 @@ def select_accounting_function(args, conn, output_file, parser): args.max_wall_pj, ) elif args.func == "delete_user": - aclif.delete_user(conn, args.username, args.bank) + u.delete_user(conn, args.username, args.bank) elif args.func == "edit_user": - aclif.edit_user(conn, args.username, args.field, args.new_value) + u.edit_user(conn, args.username, args.field, args.new_value) elif args.func == "view_job_records": jobs.view_job_records( conn, @@ -313,13 +314,13 @@ def select_accounting_function(args, conn, output_file, parser): after_start_time=args.after_start_time, ) elif args.func == "add_bank": - aclif.add_bank(conn, args.bank, args.shares, args.parent_bank) + b.add_bank(conn, args.bank, args.shares, args.parent_bank) elif args.func == "view_bank": - aclif.view_bank(conn, args.bank) + b.view_bank(conn, args.bank) elif args.func == "delete_bank": - aclif.delete_bank(conn, args.bank) + b.delete_bank(conn, args.bank) elif args.func == "edit_bank": - aclif.edit_bank(conn, args.bank, args.shares) + b.edit_bank(conn, args.bank, args.shares) elif args.func == "update_usage": jobs_conn = establish_sqlite_connection(args.job_archive_db_path) jobs.update_job_usage(conn, jobs_conn, args.priority_decay_half_life)