%
% Matlab script to create a Generic Cluster profile using Slurm
%
% This will enable users to launch jobs on the HPC cluster from Matlab
% running on their workstation
%
% Author: Tom Payerle (payerle@umd.edu) 2022-11-03
% (c) University of Maryland
% 
script_version = '0.1';
script_date = '2022-11-04';
fprintf('config_cluster_profile_local_workstation: Version %s (%s)\n', script_version, script_date);
fprintf('Script to create cluster profile to connect to UMD HPC cluster from your local workstation\n');
fprintf('(c) 2022 University of Maryland\n');


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%		System settings
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%--------- The version of Matlab we are running
myMatlabRelease = version('-release'); %This returns something like 2022b

%--------- Information about supported clusters

%-- % Cluster names *must* be all lowercase
%-- known_cluster_names = { "zaratan" };
%-- cluster_info_list = {
%-- 	% Zaratan
%-- 	{	% login node
%-- 		"login.zaratan.umd.edu",
%-- 		% matlab versions and paths to MATLAB root, remote Job storage (with %s for username)
%-- 		{ { "2022b", "/software/matlab/2022b/Linux" '/home/%s/scratch/MatlabParallelServer'},
%-- 		}
%-- 	}
%-- };
%-- cluster_info_by_cluster_name = containers.Map(known_cluster_names, cluster_info_list);

% cluster_info_list is cell array of cell arrays
% One outer cell array per cluster
% Per cluster cell array should have 4 elements
%	cluster name (all lowercase)
%	login node
%	version cell array: one element per available MATLAB version, with
%	   elements
%		version number (string)
%		full path to installation root for that version
%			(typically like /software/matlab/2022b/Linux)
%	default value for remote Job storage location.  This should 
%		contain one %s tag which will be replaced with username
%		of user
cluster_info_list = {
	% Zaratan cluster
	{ 	% Cluster name
		"zaratan", 
		% login node
	  	"login.zaratan.umd.edu", 
		% Matlab versions
		{
		    {	% MATLAB version
			'2022b',
			% PAth to installation root
			'/software/matlab/2022b/Linux',
		    },
		},
		% remoteJobStorageLocation
		'/home/%s/scratch/MatlabParallelServer',
	},
};
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%		Get information from the user
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%-------- Name of cluster
% Ask user
cluster_name = input('Which cluster do you wish to connect to: [zaratan] ', 's');
% Default
if isempty(cluster_name) == true
	cluster_name = 'zaratan';
end
% Convert to lowercase
cluster_name = lower(cluster_name);
% and a copy with first letter uppercase
cluster_Name = cluster_name;
cluster_Name(1) = upper(cluster_Name(1));

% Verify cluster is known to us
cluster_info = zeros(0);
known_cluster_names = [];
for k = 1:numel(cluster_info_list)
	tmp1 = cluster_info_list{k};
	tmp2 = tmp1{1};
	known_cluster_names = [ known_cluster_names, tmp2 ];
	if cluster_name == tmp2
		cluster_info = tmp1;
		break;
	end
end

if isempty(cluster_info)
	tmp = strjoin(known_cluster_names, ', ');
	error('Unknown cluster "%s"; expecting one of [%s]\n', cluster_name, tmp);
end
% Get info for this cluster
cluster_login_node = cluster_info{2};
cluster_matlab_versions = cluster_info{3};
cluster_jobstorage_string = cluster_info{4};
cluster_matlab_version = zeros(0);
cluster_matlab_path = zeros(0);
known_versions = [];
for k = 1:numel(cluster_matlab_versions)
	vrec = cluster_matlab_versions{k};
	ver = vrec{1};
	path = vrec{2};
	known_versions = [ known_versions, ver];
	if ver == myMatlabRelease
		cluster_matlab_version = ver;
		cluster_matlab_path = path;
	end
end
if isempty(cluster_matlab_version) == true
	tmp0 = strjoin(known_versions, ", ");
	tmp1 = [ 'You are running MATLAB version %s.',
		'That version is not known to this script (script version %s) ',
		'to be supported by cluster %s.  (This script believes the',
		'only versions supported by the cluster are [%]).',
		'',
		'Please contact hpcc-help@umd.edu and provide the MATLAB ',
		'and script versions reported above.  ',
		'',
		'If you think that this MATLAB version _is) supported by the ',
		'cluster and this script is just out of date, please still',
		'contact us, but you can answer yes to the question below ',
		'and the script will try to continue.  It can create a ',
		'profile for that version of MATLAB, but the profile will ',
		'not work unless the correct version of MATLAB is actually ',
		'installed on the cluster',
	];
	tmp2 = strjoin(tmp1);
	tmp3 = sprintf(tmp2, myMatlabRelease, script_version, tmp0);
	warn(tmp3);

	tmp4 = input('Continue anyway [y/N]: ', 's');
	if isempty(tmp4) || tmp4 ~= "y"
		error("Not continuing at user request\n");
	end

	% Guess at correct path
	cluster_matlab_path = sprintf('/software/matlab/%s/Linux', myMatlabRelease);
end
if isempty(cluster_matlab_path) == true
	error('MATLAB version %s is *not* supported by cluster %s\n', myMatlabRelease, cluster_name);
end

%-------- Name of user
% Ask user
tmp = sprintf('What is your username on the %s cluster?: ', cluster_Name);
username = input(tmp, 's');
if isempty(username) == true
	error('You must provide your username on the cluster');
end

%-------- Plugin scripts directory
% Try to guess a default value
% Home directory
homedir = char(java.lang.System.getProperty('user.dir'));
%plugin_root_paths = [ homedir, "Documents", "MATLAB", "SupportPackages"];
rver = sprintf('R%s', myMatlabRelease);
tmp1 = fullfile(homedir, "Documents", "MATLAB", "SupportPackages", rver);
tmp2 = sprintf('Please provide the directory you untarred the MATLAB Slurm plugins into: [%s] ', tmp1);
% Ask user
fprintf('The MATLAB Slurm plugin path given below should contain a "parallel" subdirectory with a "slurm" subdirectory.\n');
plugin_root_dir = input(tmp2, 's');
% Default it
if isempty(plugin_root_dir) == true
	plugin_root_dir = tmp1
end
% Get full plugin path for root
%plugin_paths = [ plugin_root_dir, rver, "parallel", "slurm", "nonshared"];
plugin_path = fullfile(plugin_root_dir, "parallel", "slurm", "nonshared");

% Verify that plugin_path exists as a directory
if exist(plugin_path) ~= 7
	error('MATLAB Slurm Plugin directory %s does not exist or is not a file: %d\n', plugin_path, exist(plugin_path));
end
required_plugin_files = [ 
	"communicatingJobWrapper.sh",
	"communicatingJobWrapperSmpd.sh",
	"independentJobWrapper.sh",
];
for k = 1:numel(required_plugin_files)
	tmp1 = required_plugin_files(k);
	tmp2 = fullfile(plugin_path, tmp1);
	if exist(tmp2) ~= 2
		error('MATLAB Slurm Plugin directory %s missing required file %s (%d)\n', plugin_path, tmp1, exist(tmp2));
	end
end


%-------- Get the RemoteJobStorageLocation
% Generate default value
tmp1 = sprintf( cluster_jobstorage_string, username);
tmp2 = sprintf('Where should MATLAB store its job files on the cluster?:[%s] ', tmp1);
% Ask user
remJobStorageLoc = input(tmp2, 's');
% Default if needed
if isempty(remJobStorageLoc) == true
	remJobStorageLoc = tmp1;
end

%-------- Get the name of profile to save as
tmp1 = sprintf('%s HPC', cluster_Name);
tmp2 = sprintf('What should this profile be named?:[%s] ', tmp1);
profile_name = input(tmp2, 's');
if isempty(profile_name) == true
	profile_name = tmp1;
end
% Make sure no existing profile with this name exits
existing_profiles = parallel.clusterProfiles;
for k = 1:numel(existing_profiles)
	existing_prof = existing_profiles{k};
	if strcmp(profile_name, existing_prof) == 1
		error('A profile named "%s" already exists, and we refuse to overwrite it', profile_name)
	end
end

% Not sure if we can set this
profile_desc = sprintf('Profile for use on your local workstation, sending jobs to %s HPC cluster', cluster_Name);

% Default this; user can up later if desired
numWorkers = 32;

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%		Build cluster profile
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

profile = parallel.cluster.Generic;
%profile.Description = profile_desc;

%profile.Profile = profile_name; %Cant set, read-only
profile.ClusterMatlabRoot = cluster_matlab_path;
profile.OperatingSystem = 'unix';
profile.HasSharedFilesystem = boolean(0);
profile.RequiresOnlineLicensing = boolean(0);
profile.PluginScriptsLocation = plugin_path;
profile.NumWorkers = numWorkers;
%profile.AutoAttachFiles = boolean(1)

profile.AdditionalProperties.AuthenticationMode = 'Multifactor';
profile.AdditionalProperties.ClusterHost = cluster_login_node;
profile.AdditionalProperties.RemoteJobStorageLocation = remJobStorageLoc;
profile.AdditionalProperties.UseIdentityFile = 0;
profile.AdditionalProperties.UseUniqueSubfolders = boolean(1);
profile.AdditionalProperties.Username = username;


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%		Confirmation
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

fprintf('We are going to create a profile with the following properties\n');
fprintf('Profile name: %s\n', profile_name);
%printf('Description: %s\n', profile_desc);
profile
profile.AdditionalProperties

tmp1 = input('Shall we proceed to create the profile?[y/N] ', 's');
if isempty(tmp1)
	tmp1 = 'N';
end
tmp1 = lower(tmp1);
if (strcmp(tmp1,'y') == 1) || (strcmp(tmp1, 'yes'))
	profile.saveAsProfile(profile_name);
else
	error("Not saving profile; user did not confirm");
end








