Page MenuHomec4science

hpc_syncusers
No OneTemporary

File Metadata

Created
Sun, Sep 1, 14:24

hpc_syncusers

#!/usr/bin/perl -w
# hpc_syncusers - synchronise slurm user and account data with group memberships and YAML account definitions.
# - create/delete/modify slurm associations and scratch data
#
# Author : EPFL-SCITAS
# Date : March 2016
use strict;
use FindBin;
use lib "$FindBin::RealBin/../lib/blib";
use HPCUsers;
use Slurm;
use Scratch;
use Cmd;
use ParseAccounts;
use Cwd 'abs_path';
# -----------------------------------------------------------------------------
# Command line options
#
my %opt;
my $account_def_file = "accounts.yaml";
my $cluster = "";
sub print_usage() {
print STDERR << "EOF";
Synchronize SCITAS cluster users with their group membership and account definitions:
- set slurm associations accordingly
- create scratch directory
- read account definitions in file $account_def_file
- log commands in $Log::logfile
By default, does a dry run by only printing the commands required to bring users in sync.
usage: $0 [-f] [-g] [-h]
OPTIONS
-g : go for real by applying the required modifications, and log output.
-f : also free ressources of users having no more access to the cluster, i.e. delete slurm accounts and scratch data
-h : this (help) message
example: $0 -g
EOF
exit;
}
# array_diff(A,B) : return an array with all of the elements in A not being in B
sub array_diff(\@\@)
{
my %a = map{$_ => 1} @{$_[1]};
return grep(!exists($a{$_}), @{$_[0]});
}
# return an array with no dupplicate
sub array_unique(@) {
return keys %{{map {$_ => 1} @_}};
}
sub init()
{
use Getopt::Std;
my $opt_string = 'fhg';
getopts("$opt_string", \%opt) or print_usage();
# pathname of logfile
$cluster = Slurm::get_cluster_name();
my $logdir = "$FindBin::RealBin/../var/";
if ($cluster ne "" && -e "$logdir") {
$Log::logfile = abs_path($logdir . $cluster . "_" . $Log::logfile);
} else {
$Log::logfile = "/var/log/" . $Log::logfile;
}
# pathname of account definition file
my $pathname = abs_path("$FindBin::RealBin/../etc/" . $account_def_file);
if ( defined($pathname) && -e "$pathname" ) {
$account_def_file = $pathname;
} else {
$account_def_file = abs_path("$FindBin::RealBin/" . $account_def_file);
}
print_usage() if $opt{h};
$Cmd::dry_run = 1;
if ($opt{g}) {
$Cmd::dry_run = 0;
}
print_usage() if (scalar @ARGV > 1);
}
sub check_modify_user_associations($)
{
my ($username) = @_;
my @tmp = ();
my @existing_accounts = array_unique(sort(Slurm::user_accounts($username)));
foreach my $groupname (HPCUsers::member_of($username)) {
push(@tmp, ParseAccounts::group_to_slurm_accounts($groupname));
}
my @required_accounts = array_unique(sort(@tmp));
my @to_add = array_diff(@required_accounts, @existing_accounts);
my @to_remove = array_diff(@existing_accounts, @required_accounts);
if (scalar(@to_add) > 0 || scalar(@to_remove) > 0) {
Log::log_message(sprintf("modify user %s (%s)", $username, HPCUsers::user_to_string($username)));
Log::append_msg($Log::log_str) if ! $Cmd::dry_run;
print $Log::log_str, "\n";
# add new associations
foreach my $account (@to_add) {
Log::output_last_cmd_result() if Slurm::create_user_assoc($username,$account);
}
# remove obsolete associations
foreach my $account (@to_remove) {
Log::output_last_cmd_result() if Slurm::delete_user_assoc($username,$account);
}
# reset defaut account if necessary
if (! grep {$_ eq Slurm::default_account($username)} @required_accounts) {
# set default account to first account of @required_accounts
Log::output_last_cmd_result() if Slurm::set_default_account($username,$required_accounts[0]);
}
}
}
# User is new, create the slurm association(s) and private scratch directory
sub new_user($)
{
my ($username) = @_;
Log::log_message(sprintf("new user %s (%s)", $username, HPCUsers::user_to_string($username)));
Log::append_msg($Log::log_str) if !$Cmd::dry_run;
print $Log::log_str, "\n";
$Log::log_str = "";
# get the user accounts from the unix groups the user belongs to
my @groups = HPCUsers::member_of($username);
# print HPCUsers::groups_to_string($username), "\n";
my @accounts = ();
foreach my $groupname (@groups) {
push(@accounts,ParseAccounts::group_to_slurm_accounts($groupname));
}
# print $username, ":", Log::array_to_string(@accounts), "\n";
# Create slurm association(s)
foreach my $accountname (@accounts) {
Log::output_last_cmd_result() if Slurm::create_user_assoc($username,$accountname);
}
# Create scratch dir
Log::output_last_cmd_result() if Scratch::create_user_scratch_dir($username,HPCUsers::gid($username));
}
# User has no more access to SCITAS clusters:
# - delete the user from slurm dbd, along with all of its association(s)
# - delete its private scratch directory
sub free_user($)
{
my ($username) = @_;
return if (($username eq "query" || $username eq "root"));
Log::log_message(sprintf("free user %s (%s)", $username, HPCUsers::user_to_string($username)));
Log::append_msg($Log::log_str) if ! $Cmd::dry_run;
print $Log::log_str, "\n";
# delete slurm data
Log::output_last_cmd_result() if Slurm::delete_user($username);
# delete, or move scratch directory
Log::output_last_cmd_result() if Scratch::delete_user_scratch_dir($username);
}
sub scan_users()
{
foreach my $username (HPCUsers::all_users()) {
next if ($username eq "scitasbuild");
if (! HPCUsers::is_in_hpc_group($username) || ! HPCUsers::is_accredited($username)) {
# if user has no more access to the cluster, we can remove it's account and data
free_user($username) if ($opt{f} && Scratch::user_scratch_dir_exists($username));
} elsif (! Scratch::user_scratch_dir_exists($username)) {
# we assume that's a new user, because his private scratch directory doesn't exist
new_user($username);
} else {
check_modify_user_associations($username);
}
}
}
# Scan and update slurm accounts with definitions found in account YAML file
sub scan_accounts()
{
my $modified;
my $nb_iterations = 0;
rescan:
return if ++$nb_iterations > 25; # we never know, to avoid infinite loop
$modified = 0;
foreach my $account (ParseAccounts::all_accounts_tree()) {
my $parent = ParseAccounts::account_parent($account);
my $share = ParseAccounts::account_share($account);
if (! Slurm::account_exists($account)) {
# print "NOT EXISTS ", $account, " parent=", $parent, " share=", $share, "\n";
Log::output_last_cmd_result() if (Slurm::create_account($account,$parent,$share));
}
# check and set acount parent
if (Slurm::set_account_parent($account,$parent)) {
Log::output_last_cmd_result();
# if slurm account depedence tree is modified, we need to rescan accounts from scratch
goto rescan if ($Cmd::result{status} eq "reinitialized");
}
# check and set fairshare value
Log::output_last_cmd_result() if (Slurm::set_account_share($account,$share));
# check and set maxwall value
if (Slurm::set_account_maxwall($account,ParseAccounts::account_maxwall($account))) {
Log::output_last_cmd_result();
$modified = 1 if $Cmd::result{status} eq "reinitialized";
}
# check and set maxnodes value
if (Slurm::set_account_maxnodes($account,ParseAccounts::account_maxnodes($account))) {
Log::output_last_cmd_result();
$modified = 1 if $Cmd::result{status} eq "reinitialized";
}
# if slurm data has been modified enough, it's safer to rescan all accounts from scratch
goto rescan if ($modified);
}
}
# -----------------------------------------------------------------------------
init();
HPCUsers::init();
# search all groups to be able to associate users to accounts
HPCUsers::search_scoldap_for_groups();
# For debugging
# HPCUsers::print_users();
# Slurm::print_accounts();
# parse YAML account definition file
ParseAccounts::init($cluster,$account_def_file);
#ParseAccounts::print();
# first scan, modify the accounts
scan_accounts();
# then add, delete user associations
scan_users();

Event Timeline