完善校验功能

This commit is contained in:
陈浩南 2024-05-04 11:57:13 +08:00
parent 606a36f254
commit 86e85f87e3
7 changed files with 94 additions and 13 deletions

View File

@ -15,6 +15,7 @@ find_package(zxorm REQUIRED)
find_package(nlohmann_json REQUIRED) find_package(nlohmann_json REQUIRED)
find_path(ZPP_BITS_INCLUDE_DIR zpp_bits.h REQUIRED) find_path(ZPP_BITS_INCLUDE_DIR zpp_bits.h REQUIRED)
find_package(range-v3 REQUIRED) find_package(range-v3 REQUIRED)
find_path(NAMEOF_INCLUDE_DIR nameof.hpp REQUIRED)
add_executable(hpcstat src/main.cpp src/env.cpp src/keys.cpp src/ssh.cpp src/sql.cpp src/lfs.cpp src/common.cpp) add_executable(hpcstat src/main.cpp src/env.cpp src/keys.cpp src/ssh.cpp src/sql.cpp src/lfs.cpp src/common.cpp)
target_compile_features(hpcstat PUBLIC cxx_std_23) target_compile_features(hpcstat PUBLIC cxx_std_23)

View File

@ -23,7 +23,7 @@
name = "hpcstat"; name = "hpcstat";
src = ./.; src = ./.;
buildInputs = with pkgs.pkgsStatic; buildInputs = with pkgs.pkgsStatic;
[ boost fmt localPackages.zxorm nlohmann_json localPackages.zpp-bits range-v3 ]; [ boost fmt localPackages.zxorm nlohmann_json localPackages.zpp-bits range-v3 localPackages.nameof ];
nativeBuildInputs = with pkgs; [ cmake pkg-config ]; nativeBuildInputs = with pkgs; [ cmake pkg-config ];
postInstall = "cp ${openssh}/bin/{ssh-add,ssh-keygen} $out/bin"; postInstall = "cp ${openssh}/bin/{ssh-add,ssh-keygen} $out/bin";
}; };
@ -35,7 +35,7 @@
{ {
nativeBuildInputs = with pkgs; [ pkg-config cmake clang-tools_18 ]; nativeBuildInputs = with pkgs; [ pkg-config cmake clang-tools_18 ];
buildInputs = (with pkgs.pkgsStatic; buildInputs = (with pkgs.pkgsStatic;
[ fmt boost localPackages.zxorm nlohmann_json localPackages.zpp-bits range-v3 ]); [ fmt boost localPackages.zxorm nlohmann_json localPackages.zpp-bits range-v3 localPackages.nameof ]);
# hardeningDisable = [ "all" ]; # hardeningDisable = [ "all" ];
# NIX_DEBUG = "1"; # NIX_DEBUG = "1";
CMAKE_EXPORT_COMPILE_COMMANDS = "1"; CMAKE_EXPORT_COMPILE_COMMANDS = "1";

View File

@ -12,6 +12,7 @@ namespace hpcstat::sql
std::optional<std::string> Subaccount, Ip; std::optional<std::string> Subaccount, Ip;
bool Interactive; bool Interactive;
using serialize = zpp::bits::members<8>; using serialize = zpp::bits::members<8>;
bool operator==(const LoginData& other) const = default;
}; };
using LoginTable = zxorm::Table using LoginTable = zxorm::Table
< <
@ -25,7 +26,13 @@ namespace hpcstat::sql
zxorm::Column<"ip", &LoginData::Ip>, zxorm::Column<"ip", &LoginData::Ip>,
zxorm::Column<"interactive", &LoginData::Interactive> zxorm::Column<"interactive", &LoginData::Interactive>
>; >;
struct LogoutData { unsigned Id = 0; long Time; std::string SessionId; }; struct LogoutData
{
unsigned Id = 0;
long Time;
std::string SessionId;
bool operator==(const LogoutData& other) const = default;
};
using LogoutTable = zxorm::Table using LogoutTable = zxorm::Table
< <
"logout", LogoutData, "logout", LogoutData,
@ -41,6 +48,7 @@ namespace hpcstat::sql
std::string Key, SessionId, SubmitDir, JobCommand, Signature = ""; std::string Key, SessionId, SubmitDir, JobCommand, Signature = "";
std::optional<std::string> Subaccount, Ip; std::optional<std::string> Subaccount, Ip;
using serialize = zpp::bits::members<10>; using serialize = zpp::bits::members<10>;
bool operator==(const SubmitJobData& other) const = default;
}; };
using SubmitJobTable = zxorm::Table using SubmitJobTable = zxorm::Table
< <
@ -61,9 +69,10 @@ namespace hpcstat::sql
unsigned Id = 0; unsigned Id = 0;
long Time; long Time;
unsigned JobId; unsigned JobId;
std::string JobResult, SubmitTime, JobDetail, Signature = ""; std::string JobResult, SubmitTime, JobDetail, Key, Signature = "";
double CpuTime; double CpuTime;
using serialize = zpp::bits::members<8>; using serialize = zpp::bits::members<9>;
bool operator==(const FinishJobData& other) const = default;
}; };
using FinishJobTable = zxorm::Table using FinishJobTable = zxorm::Table
< <
@ -74,6 +83,7 @@ namespace hpcstat::sql
zxorm::Column<"job_result", &FinishJobData::JobResult>, zxorm::Column<"job_result", &FinishJobData::JobResult>,
zxorm::Column<"submit_time", &FinishJobData::SubmitTime>, zxorm::Column<"submit_time", &FinishJobData::SubmitTime>,
zxorm::Column<"job_detail", &FinishJobData::JobDetail>, zxorm::Column<"job_detail", &FinishJobData::JobDetail>,
zxorm::Column<"key", &FinishJobData::Key>,
zxorm::Column<"signature", &FinishJobData::Signature>, zxorm::Column<"signature", &FinishJobData::Signature>,
zxorm::Column<"cpu_time", &FinishJobData::CpuTime> zxorm::Column<"cpu_time", &FinishJobData::CpuTime>
>; >;
@ -85,4 +95,8 @@ namespace hpcstat::sql
bool writedb(auto value); bool writedb(auto value);
// 查询 bjobs -a 的结果中,有哪些是已经被写入到数据库中的(按照任务 id 和提交时间计算),返回未被写入的任务 id // 查询 bjobs -a 的结果中,有哪些是已经被写入到数据库中的(按照任务 id 和提交时间计算),返回未被写入的任务 id
std::optional<std::set<unsigned>> finishjob_remove_existed(std::map<unsigned, std::string> jobid_submit_time); std::optional<std::set<unsigned>> finishjob_remove_existed(std::map<unsigned, std::string> jobid_submit_time);
// 检查数据库中已经有的数据是否被修改过,如果有修改过,返回 std::nullopt否则返回新增的数据用于校验签名
// 三个字符串分别是序列化后的数据,签名,指纹
std::optional<std::vector<std::tuple<std::string, std::string, std::string>>>
verify(std::string old_db, std::string new_db);
} }

View File

@ -17,7 +17,7 @@ namespace hpcstat::lfs
else else
{ {
std::set<std::string> valid_args = { "J", "q", "n", "R", "o" }; std::set<std::string> valid_args = { "J", "q", "n", "R", "o" };
for (auto it = args.begin(); it != args.end(); it++) for (auto it = args.begin(); it != args.end(); ++it)
{ {
if (it->length() > 0 && (*it)[0] == '-') if (it->length() > 0 && (*it)[0] == '-')
{ {
@ -29,7 +29,7 @@ namespace hpcstat::lfs
"please submit issue on [github](https://github.com/CHN-beta/hpcstat) or contact chn@chn.moe.\n"; "please submit issue on [github](https://github.com/CHN-beta/hpcstat) or contact chn@chn.moe.\n";
return std::nullopt; return std::nullopt;
} }
else if (it + 1 != args.end() && ((it + 1)->length() == 0 || (*(it + 1))[0] != '-')) it++; else if (it + 1 != args.end() && ((it + 1)->length() == 0 || (*(it + 1))[0] != '-')) ++it;
} }
else break; else break;
} }

View File

@ -1,3 +1,4 @@
# include <thread>
# include <hpcstat/sql.hpp> # include <hpcstat/sql.hpp>
# include <hpcstat/ssh.hpp> # include <hpcstat/ssh.hpp>
# include <hpcstat/env.hpp> # include <hpcstat/env.hpp>
@ -10,6 +11,7 @@
int main(int argc, const char** argv) int main(int argc, const char** argv)
{ {
using namespace hpcstat; using namespace hpcstat;
using namespace std::literals;
std::vector<std::string> args(argv, argv + argc); std::vector<std::string> args(argv, argv + argc);
if (args.size() == 1) { std::cout << "Usage: hpcstat initdb|login|logout|submitjob|finishjob\n"; return 1; } if (args.size() == 1) { std::cout << "Usage: hpcstat initdb|login|logout|submitjob|finishjob\n"; return 1; }
@ -20,6 +22,7 @@ int main(int argc, const char** argv)
else if (args[1] == "login") else if (args[1] == "login")
{ {
if (env::interactive()) std::cout << "Communicating with the agent..." << std::flush; if (env::interactive()) std::cout << "Communicating with the agent..." << std::flush;
std::this_thread::sleep_for(1s); // might silly but it tells everyone that we are doing something
if (auto fp = ssh::fingerprint(); !fp) return 1; if (auto fp = ssh::fingerprint(); !fp) return 1;
else if (auto session = env::env("XDG_SESSION_ID", true); !session) else if (auto session = env::env("XDG_SESSION_ID", true); !session)
return 1; return 1;
@ -34,7 +37,8 @@ int main(int argc, const char** argv)
if (!signature) return 1; if (!signature) return 1;
data.Signature = *signature; data.Signature = *signature;
sql::writedb(data); sql::writedb(data);
if (env::interactive()) std::cout << fmt::format("\33[2K\rLogged in as {}.\n", Keys[*fp].Username); if (env::interactive())
std::cout << fmt::format("\33[2K\rLogged in as {} (fingerprint: SHA256:{}).\n", Keys[*fp].Username, *fp);
} }
} }
else if (args[1] == "logout") else if (args[1] == "logout")
@ -94,7 +98,7 @@ int main(int argc, const char** argv)
sql::FinishJobData data sql::FinishJobData data
{ {
.Time = now(), .JobId = jobid, .JobResult = std::get<1>(all_jobs->at(jobid)), .Time = now(), .JobId = jobid, .JobResult = std::get<1>(all_jobs->at(jobid)),
.SubmitTime = std::get<0>(all_jobs->at(jobid)), .JobDetail = *detail, .SubmitTime = std::get<0>(all_jobs->at(jobid)), .JobDetail = *detail, .Key = *fp,
.CpuTime = std::get<2>(all_jobs->at(jobid)), .CpuTime = std::get<2>(all_jobs->at(jobid)),
}; };
if if
@ -107,7 +111,14 @@ int main(int argc, const char** argv)
} }
} }
} }
else if (args[1] == "verify")
{
if (args.size() < 4) { std::cerr << "Usage: hpcstat verify <old.db> <new.db>\n"; return 1; }
if (auto db_verify_result = sql::verify(args[2], args[3]); !db_verify_result) return 1;
else for (auto& data : *db_verify_result)
if (!std::apply(ssh::verify, data))
{ std::cerr << fmt::format("Failed to verify data: {}\n", std::get<0>(data)); return 1; }
}
else { std::cerr << "Unknown command.\n"; return 1; } else { std::cerr << "Unknown command.\n"; return 1; }
return 0; return 0;
} }

View File

@ -4,6 +4,8 @@
# include <hpcstat/env.hpp> # include <hpcstat/env.hpp>
# include <range/v3/range.hpp> # include <range/v3/range.hpp>
# include <range/v3/view.hpp> # include <range/v3/view.hpp>
# include <nameof.hpp>
# include <fmt/format.h>
namespace hpcstat::sql namespace hpcstat::sql
{ {
@ -17,9 +19,12 @@ namespace hpcstat::sql
template std::string serialize(LoginData); template std::string serialize(LoginData);
template std::string serialize(SubmitJobData); template std::string serialize(SubmitJobData);
template std::string serialize(FinishJobData); template std::string serialize(FinishJobData);
std::optional<zxorm::Connection<LoginTable, LogoutTable, SubmitJobTable, FinishJobTable>> connect() std::optional<zxorm::Connection<LoginTable, LogoutTable, SubmitJobTable, FinishJobTable>> connect
(std::optional<std::string> dbfile = std::nullopt)
{ {
if (auto datadir = env::env("HPCSTAT_DATADIR", true); !datadir) if (dbfile) return std::make_optional<zxorm::Connection<LoginTable, LogoutTable, SubmitJobTable, FinishJobTable>>
(dbfile->c_str());
else if (auto datadir = env::env("HPCSTAT_DATADIR", true); !datadir)
return std::nullopt; return std::nullopt;
else else
{ {
@ -52,4 +57,54 @@ namespace hpcstat::sql
return not_logged_job; return not_logged_job;
} }
} }
std::optional<std::vector<std::tuple<std::string, std::string, std::string>>>
verify(std::string old_db, std::string new_db)
{
auto old_conn = connect(old_db), new_conn = connect(new_db);
if (!old_conn || !new_conn) { std::cerr << "Failed to connect to database.\n"; return std::nullopt; }
else
{
auto check_one = [&]<typename T>()
-> std::optional<std::vector<std::tuple<std::string, std::string, std::string>>>
{
auto old_query = old_conn->select_query<T>().many().exec(),
new_query = new_conn->select_query<T>().many().exec();
auto old_data_it = old_query.begin(), new_data_it = new_query.begin();
for (; old_data_it != old_query.end() && new_data_it != new_query.end(); ++old_data_it, ++new_data_it)
if (*old_data_it != *new_data_it)
{
std::cerr << fmt::format
("Data mismatch: {} {} != {}.\n", nameof::nameof_type<T>(), (*old_data_it).Id, (*new_data_it).Id);
return std::nullopt;
}
if (old_data_it != old_query.end() && new_data_it == new_query.end())
{
std::cerr << fmt::format("Data mismatch in {}.\n", nameof::nameof_type<T>());
return std::nullopt;
}
else if constexpr (requires(T data) { data.Signature; })
{
std::vector<std::tuple<std::string, std::string, std::string>> diff;
for (; old_data_it != old_query.end(); ++old_data_it)
{
auto data = *old_data_it;
data.Signature = "";
data.Id = 0;
diff.push_back({ serialize(data), (*old_data_it).Signature, (*old_data_it).Key });
}
return diff;
}
else return std::vector<std::tuple<std::string, std::string, std::string>>{};
};
auto check_many = [&]<typename T, typename... Ts>(auto&& self)
-> std::optional<std::vector<std::tuple<std::string, std::string, std::string>>>
{
if (auto diff = check_one.operator()<T>(); !diff) return std::nullopt;
else if constexpr (sizeof...(Ts) == 0) return diff;
else if (auto diff2 = self.template operator()<Ts...>(self); !diff2) return std::nullopt;
else { diff->insert(diff->end(), diff2->begin(), diff2->end()); return diff; }
};
return check_many.operator()<LoginData, LogoutData, SubmitJobData, FinishJobData>(check_many);
}
}
} }

View File

@ -30,7 +30,7 @@ namespace hpcstat::ssh
for for
( (
auto i = std::sregex_iterator(output->begin(), output->end(), pattern); auto i = std::sregex_iterator(output->begin(), output->end(), pattern);
i != std::sregex_iterator(); i++ i != std::sregex_iterator(); ++i
) )
if (Keys.contains(i->str(1))) return i->str(1); if (Keys.contains(i->str(1))) return i->str(1);
std::cerr << fmt::format("No valid fingerprint found in:\n{}\n", *output); std::cerr << fmt::format("No valid fingerprint found in:\n{}\n", *output);