完成登陆登出

This commit is contained in:
陈浩南 2024-05-01 10:45:25 +08:00
parent 5e02dfec1a
commit 2d9c3cab76
4 changed files with 86 additions and 81 deletions

View File

@ -1,3 +1,3 @@
CompileFlags: CompileFlags:
Add: [ -Wall, -Wextra, -std=c++23 ] Add: [ -Wall, -Wextra, -std=c++23 ]
Compiler: gcc Compiler: g++

View File

@ -3,10 +3,6 @@ project(hpcstat VERSION 0.0.0 LANGUAGES CXX)
enable_testing() enable_testing()
include(GNUInstallDirs) include(GNUInstallDirs)
set_property(GLOBAL PROPERTY CXX_STANDARD 23)
set_property(GLOBAL PROPERTY CXX_STANDARD_REQUIRED ON)
set_property(GLOBAL PROPERTY CXX_EXTENSIONS OFF)
if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES)
message("Setting build type to 'Release' as none was specified.") message("Setting build type to 'Release' as none was specified.")
set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE)
@ -18,6 +14,7 @@ find_package(Boost REQUIRED COMPONENTS headers filesystem)
find_package(zxorm REQUIRED) find_package(zxorm REQUIRED)
add_executable(hpcstat src/main.cpp) add_executable(hpcstat src/main.cpp)
target_compile_features(hpcstat PUBLIC cxx_std_23)
target_link_libraries(hpcstat PRIVATE fmt::fmt Boost::headers Boost::filesystem zxorm::zxorm) target_link_libraries(hpcstat PRIVATE fmt::fmt Boost::headers Boost::filesystem zxorm::zxorm)
install(TARGETS hpcstat RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(TARGETS hpcstat RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})

View File

@ -22,7 +22,7 @@
{ {
name = "hpcstat"; name = "hpcstat";
src = ./.; src = ./.;
buildInputs = with pkgs.pkgsStatic; [ boost fmt sqlitecpp ]; buildInputs = with pkgs.pkgsStatic; [ boost fmt localPackages.zxorm ];
nativeBuildInputs = with pkgs; [ cmake pkg-config ]; nativeBuildInputs = with pkgs; [ cmake pkg-config ];
postInstall = "cp ${openssh}/bin/ssh-add $out/bin"; postInstall = "cp ${openssh}/bin/ssh-add $out/bin";
}; };

View File

@ -18,18 +18,27 @@ using namespace std::literals;
// ssh fingerprint -> username // ssh fingerprint -> username
const std::map<std::string, std::string> Username const std::map<std::string, std::string> Username
{ {
{ "LNoYfq/SM7l8sFAy325WpC+li+kZl3jwST7TmP72Tz8", "Haonan Chen" } { "LNoYfq/SM7l8sFAy325WpC+li+kZl3jwST7TmP72Tz8", "Haonan Chen" },
{ "VJT5wgkb2RcIeVNTA+/NKxokctbYnJ/KgH6IxrKqIGE", "Bin Gong" },
{ "umC3/RB1vS8TQBHsY3IzhOiyqVrOSw2fB3rIpDQSmf4", "Leilei Xiang" },
{ "fdq5k13N2DAzIK/2a1Mm4/ZVsDUgT623TSOXsVswxT8", "Junqi Yao" },
{ "8USxEYi8ePPpLhk5FYBo2udT7/NFmEe8c2+oQajGXzA", "Enming Zhang" },
{ "7bmG24muNsaAZkCy7mQ9Nf2HuNafmvUO+Hf1bId9zts", "Yaping Wu" },
{ "dtx0QxdgFrXn2SYxtIRz43jIAH6rLgJidSdTvuTuews", "Jing Li" },
{ "8crUO9u4JiVqw3COyjXfzZe87s6XZFhvi0LaY0Mv6bg", "Huahan Zhan" },
{ "QkmIYw7rmDEAP+LDWxm6L2/XLnAqTwRUB7B0pxYlOUs", "Na Gao" }
}; };
// program path, set at start of main, e.g. /gpfs01/.../bin/hpcstat // program path, set at start of main, e.g. /gpfs01/.../bin/hpcstat
std::filesystem::path Program; std::filesystem::path Program;
// run a program, wait until it exit, return its stdout and stderr if it return 0, otherwise nullopt // run a program, wait until it exit, return its stdout if it return 0, otherwise nullopt
std::optional<std::string> exec(boost::filesystem::path program, std::vector<std::string> args) std::optional<std::string> exec(boost::filesystem::path program, std::vector<std::string> args)
{ {
namespace bp = boost::process; namespace bp = boost::process;
bp::ipstream output; bp::ipstream output;
auto process = bp::child(program, bp::args(args), bp::std_out > output, bp::std_err > bp::null); auto process = bp::child
(program, bp::args(args), bp::std_out > output, bp::std_err > stderr, bp::std_in < bp::null);
process.wait(); process.wait();
if (process.exit_code() != 0) return std::nullopt; if (process.exit_code() != 0) return std::nullopt;
std::stringstream ss; std::stringstream ss;
@ -38,11 +47,12 @@ std::optional<std::string> exec(boost::filesystem::path program, std::vector<std
} }
// detect ssh fingerprint using ssh-add // detect ssh fingerprint using ssh-add
// always assume sha256 fingerprint
std::optional<std::vector<std::string>> fingerprints() std::optional<std::vector<std::string>> fingerprints()
{ {
auto output = auto output =
exec(Program.replace_filename("ssh-add"), { "-l" }); exec(Program.replace_filename("ssh-add"), { "-l" });
if (!output) return std::nullopt; if (!output) { std::cerr << "Failed to get ssh fingerprints\n"; return std::nullopt; }
auto fingerprint = output->substr(0, 47); auto fingerprint = output->substr(0, 47);
// search for all strings that match the fingerprint pattern: sha256:... // search for all strings that match the fingerprint pattern: sha256:...
std::regex pattern(R"r(\b(?:sha|SHA)256:([0-9A-Za-z+/=]{43})\b)r"); std::regex pattern(R"r(\b(?:sha|SHA)256:([0-9A-Za-z+/=]{43})\b)r");
@ -60,17 +70,16 @@ std::optional<std::pair<std::string, std::string>> authenticated()
if (!fps) return std::nullopt; if (!fps) return std::nullopt;
for (auto& fp : *fps) for (auto& fp : *fps)
if (Username.contains(fp)) return std::make_pair(fp, Username.at(fp)); if (Username.contains(fp)) return std::make_pair(fp, Username.at(fp));
std::cerr << fmt::format("No valid fingerprint found, available fingerprints: {}\n", *fps);
return std::nullopt; return std::nullopt;
} }
// initialize the database // initialize the database
// std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now().time_since_epoch()).count();
struct LoginData struct LoginData
{ {
unsigned Id = 0; unsigned Id = 0; long Time = 0;
long Time = 0; std::string Key, SessionId;
std::string Key; std::optional<std::string> Subaccount;
std::string SessionId;
bool Interactive; bool Interactive;
}; };
using LoginTable = zxorm::Table using LoginTable = zxorm::Table
@ -79,115 +88,114 @@ using LoginTable = zxorm::Table
zxorm::Column<"id", &LoginData::Id, zxorm::PrimaryKey<>>, zxorm::Column<"id", &LoginData::Id, zxorm::PrimaryKey<>>,
zxorm::Column<"time", &LoginData::Time>, zxorm::Column<"time", &LoginData::Time>,
zxorm::Column<"key", &LoginData::Key>, zxorm::Column<"key", &LoginData::Key>,
zxorm::Column<"session_id", &LoginData::SessionId> zxorm::Column<"session_id", &LoginData::SessionId>,
zxorm::Column<"sub_account", &LoginData::Subaccount>,
zxorm::Column<"interactive", &LoginData::Interactive>
>; >;
struct LogoutData struct LogoutData { unsigned Id = 0; long Time = 0; std::string SessionId; };
{
unsigned Id = 0;
long Time = 0;
std::string SessionId;
};
using LogoutTable = zxorm::Table using LogoutTable = zxorm::Table
< <
"logout", LogoutData, "logout", LogoutData,
zxorm::Column<"id", &LogoutData::Id, zxorm::PrimaryKey<>>, zxorm::Column<"id", &LogoutData::Id, zxorm::PrimaryKey<>>,
zxorm::Column<"time", &LogoutData::Time>, zxorm::Column<"time", &LogoutData::Time>, zxorm::Column<"sessionid", &LogoutData::SessionId>
zxorm::Column<"session_id", &LogoutData::SessionId> >;
struct SubmitJobData
{ unsigned Id = 0; long Time = 0; int JobId; std::string Key, SessionId, Subaccount, SubmitDir, JobCommand; };
using SubmitJobTable = zxorm::Table
<
"submitjob", SubmitJobData,
zxorm::Column<"id", &SubmitJobData::Id, zxorm::PrimaryKey<>>,
zxorm::Column<"time", &SubmitJobData::Time>,
zxorm::Column<"job_id", &SubmitJobData::JobId>,
zxorm::Column<"key", &SubmitJobData::Key>,
zxorm::Column<"session_id", &SubmitJobData::SessionId>,
zxorm::Column<"sub_account", &SubmitJobData::Subaccount>,
zxorm::Column<"submit_dir", &SubmitJobData::SubmitDir>,
zxorm::Column<"job_command", &SubmitJobData::JobCommand>
>;
struct FinishJobData { unsigned Id = 0; long Time = 0; int JobId; std::string JobResult; double CpuTime; };
using FinishJobTable = zxorm::Table
<
"finishjob", FinishJobData,
zxorm::Column<"id", &FinishJobData::Id, zxorm::PrimaryKey<>>,
zxorm::Column<"time", &FinishJobData::Time>,
zxorm::Column<"job_id", &FinishJobData::JobId>,
zxorm::Column<"job_result", &FinishJobData::JobResult>,
zxorm::Column<"cpu_time", &FinishJobData::CpuTime>
>;
struct QueryJobData { unsigned Id = 0; int JobId; };
using QueryJobTable = zxorm::Table
<
"queryjob", QueryJobData,
zxorm::Column<"id", &QueryJobData::Id, zxorm::PrimaryKey<>>, zxorm::Column<"job_id", &QueryJobData::JobId>
>; >;
// struct SubmitData
// {
// unsigned Id;
// long Time;
// std::string Key;
// std::string SessionId;
// unsigned JobId;
// std::string JobWorkdir;
// std::string JobCommand;
// };
// struct FinishData
// {
// unsigned Id;
// long Time;
// unsigned JobId;
// std::string JobResult;
// double CpuTime;
// };
void initdb() void initdb()
{ {
auto dbfile = Program.replace_filename("hpcstat.db").string(); auto dbfile = Program.replace_filename("hpcstat.db").string();
zxorm::Connection<LoginTable, LogoutTable> conn(dbfile.c_str()); zxorm::Connection<LoginTable, LogoutTable, SubmitJobTable, FinishJobTable, QueryJobTable>
conn(dbfile.c_str());
conn.create_tables(); conn.create_tables();
} }
void writedb(auto value) void writedb(auto value)
{ {
auto dbfile = Program.replace_filename("hpcstat.db").string(); auto dbfile = Program.replace_filename("hpcstat.db").string();
zxorm::Connection<LoginTable, LogoutTable> conn(dbfile.c_str()); zxorm::Connection<LoginTable, LogoutTable, SubmitJobTable, FinishJobTable, QueryJobTable>
conn(dbfile.c_str());
value.Time = std::chrono::duration_cast<std::chrono::seconds> value.Time = std::chrono::duration_cast<std::chrono::seconds>
(std::chrono::system_clock::now().time_since_epoch()).count(); (std::chrono::system_clock::now().time_since_epoch()).count();
conn.insert_record(value); conn.insert_record(value);
} }
bool interactive() bool interactive() { return isatty(fileno(stdin)); }
{
return isatty(fileno(stdin));
}
// get value of XDG_SESSION_ID // get value of XDG_SESSION_ID
std::optional<std::string> session_id() std::optional<std::string> session_id()
{ {
auto value = std::getenv("XDG_SESSION_ID"); if (auto value = std::getenv("XDG_SESSION_ID"); !value)
if (!value) return std::nullopt; else return value; { std::cerr << "Failed to get session id\n"; return std::nullopt; }
else return value;
} }
// get value of HPCSTAT_SUBACCOUNT
std::optional<std::string> subaccount()
{ if (auto value = std::getenv("HPCSTAT_SUBACCOUNT"); value) return value; else return std::nullopt; }
int main(int argc, const char** argv) int main(int argc, const char** argv)
{ {
std::vector<std::string> args(argv, argv + argc); std::vector<std::string> args(argv, argv + argc);
Program = args[0]; Program = args[0];
if (args.size() == 1) if (args.size() == 1) { std::cout << "Usage: hpcstat initdb|login|logout|submitjob|finishjob\n"; return 1; }
{
std::cout << "Usage: hpcstat initdb|login|logout|submitjob|finishjob\n";
return 1;
}
else if (args[1] == "initdb") else if (args[1] == "initdb")
initdb(); initdb();
else if (args[1] == "login") else if (args[1] == "login")
{ {
auto key = authenticated(); if (auto key = authenticated(); !key) return 1;
if (!key) else if (auto session = session_id(); !session) return 1;
{ else writedb(LoginData
auto fps = fingerprints(); {.Key = key->first, .SessionId = *session, .Subaccount = subaccount(), .Interactive = interactive()});
if (!fingerprints())
std::cerr << "Failed to get ssh fingerprints\n";
else
std::cerr << fmt::format("No valid fingerprint found, available fingerprints: {}\n", *fps);
return 1;
}
auto session = session_id();
if (!session)
{
std::cerr << "Failed to get session id\n";
return 1;
}
writedb(LoginData{.Key = key->first, .SessionId = *session, .Interactive = interactive()});
} }
else if (args[1] == "logout") else if (args[1] == "logout")
{ {
auto session = session_id(); if (auto session = session_id(); !session) return 1;
if (!session) else writedb(LogoutData{.SessionId = *session});
{
std::cerr << "Failed to get session id\n";
return 1;
}
writedb(LogoutData{.SessionId = *session});
} }
else if (args[1] == "submitjob") else if (args[1] == "submitjob")
{ {
// TODO if (args.size() < 4) { std::cerr << "Usage: hpcstat submitjob <jobid> <submitdir> <jobcommand>\n"; return 1; }
if (auto key = authenticated(); !key) return 1;
else if (auto session = session_id(); !session) return 1;
else writedb(SubmitJobData
{
.JobId = std::stoi(args[2]), .Key = key->first, .SessionId = *session,
.SubmitDir = std::filesystem::current_path().string(),
.JobCommand = [&]
{ std::stringstream ss; for (int i = 3; i < args.size(); i++) ss << args[i] << " "; return ss.str(); }()
});
} }
else if (args[1] == "finishjob") else if (args[1] == "finishjob")
{ {
// TODO
} }
else else
{ {