Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Version 0.8.0: add sqlite / mount uenv repo images #43

Merged
merged 47 commits into from
May 24, 2024
Merged
Changes from 1 commit
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
882f836
add sqlitecpp as subproject
simonpintarelli Apr 18, 2024
299885d
update
simonpintarelli Apr 26, 2024
a2a23bf
move files
simonpintarelli Apr 26, 2024
2daa9d5
use sqlite3 in meson
simonpintarelli Apr 26, 2024
fe13d34
update
simonpintarelli Apr 28, 2024
a6b1d4b
update
simonpintarelli Apr 28, 2024
1232b87
add sqlite3, sqlite3-devel to docker image
simonpintarelli Apr 28, 2024
ca1771b
make uenv OCI repo optional
simonpintarelli Apr 28, 2024
6022960
update
simonpintarelli Apr 28, 2024
bdd6d0d
remove subprojects directory
simonpintarelli Apr 28, 2024
978b590
cleanup
simonpintarelli Apr 28, 2024
78a4a82
format
simonpintarelli Apr 28, 2024
2a3a64d
fix query
simonpintarelli Apr 28, 2024
679d920
version = 1.0.0
simonpintarelli Apr 28, 2024
2747bd3
pass uarch via slurm flag
simonpintarelli Apr 28, 2024
f92cf19
format
simonpintarelli Apr 28, 2024
ac7bff4
add SQLiteStatement::bind
simonpintarelli Apr 30, 2024
34d0cdc
don't assemble sql query by hand
simonpintarelli Apr 30, 2024
81eed96
add bash bats test for sqlite
simonpintarelli Apr 30, 2024
4f1abdd
fix mount by id/sha
simonpintarelli Apr 30, 2024
c8d60bd
add bats tests for sqlite
simonpintarelli Apr 30, 2024
487de5f
bats: attempt to mount ambiguous image
simonpintarelli Apr 30, 2024
fd145bd
fix path
simonpintarelli Apr 30, 2024
de0b918
cleanup
simonpintarelli Apr 30, 2024
0673849
cleanup files
simonpintarelli Apr 30, 2024
aa5a7c4
cleanup
simonpintarelli Apr 30, 2024
b080a40
run tests in directory
simonpintarelli May 2, 2024
7f5e9e7
change regex for name/version:tag
simonpintarelli May 10, 2024
10f78bd
add sqlite to local Dockerfile
simonpintarelli May 10, 2024
d6ce76a
regex parsing
bcumming May 16, 2024
8580f47
cleanup parseargs code
bcumming May 16, 2024
8032a61
constify and localised vars in getenv code
bcumming May 16, 2024
ccbf4a9
return an error string instead of an exception
bcumming May 16, 2024
e6fb5c5
refactor mount code to not depend on slurm
bcumming May 16, 2024
e8faeb2
finish refactoring of database lookup into slurm-free backend; fix so…
bcumming May 17, 2024
3a82d93
split with drop_empty argument
simonpintarelli May 17, 2024
c678ae3
unused variable
simonpintarelli May 17, 2024
5463a53
fix constructor
simonpintarelli May 17, 2024
cf19dc5
format
simonpintarelli May 17, 2024
27777ec
fix error message generation in mount (still probably wrong, the docs…
bcumming May 17, 2024
248efdb
all code without slurm/spank symbols moved to lib path
bcumming May 17, 2024
14f08f5
add catch2 for testing; add string unit tests
bcumming May 18, 2024
7d5ad4f
add uenv name parsing unit test
bcumming May 18, 2024
45d4e16
merge origin
bcumming May 18, 2024
5272e45
remove extra `;`
simonpintarelli May 22, 2024
c324caf
delete TODO.md
simonpintarelli May 24, 2024
e9d920b
v0.8.0
simonpintarelli May 24, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
refactor mount code to not depend on slurm
bcumming committed May 16, 2024
commit e6fb5c5f42ad9398d47382a842df812facbe9433
3 changes: 2 additions & 1 deletion meson.build
Original file line number Diff line number Diff line change
@@ -20,7 +20,8 @@ shared_module('slurm-uenv-mount',
'src/mount.cpp',
'src/parse_args.cpp',
'src/sqlite/sqlite.cpp',
'src/util/helper.cpp'],
'src/util/strings.cpp',
'src/util/filesystem.cpp'],
dependencies: [libmount_dep, sqlite3_dep],
cpp_args: ['-Wall', '-Wpedantic', '-Wextra'],
install: true)
117 changes: 41 additions & 76 deletions src/mount.cpp
Original file line number Diff line number Diff line change
@@ -1,125 +1,90 @@
#include "mount.hpp"
#include "parse_args.hpp"
#include "util/expected.hpp"
#include "util/helper.hpp"
#include <cstdlib>
#include <string>

#include <err.h>
#include <fcntl.h>
#include <libmount/libmount.h>
#include <linux/loop.h>
#include <sched.h>
#include <slurm/slurm_errno.h>
#include <sstream>
#include <string.h>
#include <string>

#include <linux/loop.h>
#include <sys/mount.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

extern "C" {
#include <slurm/spank.h>
}

namespace impl {
#include <libmount/libmount.h>
#include <slurm/slurm_errno.h>

util::expected<std::string, std::string> get_realpath(const std::string &path) {
char *p = realpath(path.c_str(), nullptr);
if (p) {
std::string ret(p);
free(p);
return ret;
} else {
char *err = strerror(errno);
return util::unexpected(err);
}
}
#include "mount.hpp"
#include "parse_args.hpp"
#include "util/expected.hpp"
#include "util/filesystem.hpp"

bool is_valid_mountpoint(const std::string &mount_point) {
struct stat mnt_stat;
auto mnt_status = stat(mount_point.c_str(), &mnt_stat);
if (mnt_status) {
slurm_spank_log("Invalid mount point \"%s\"", mount_point.c_str());
return false;
}
if (!S_ISDIR(mnt_stat.st_mode)) {
slurm_spank_log("Invalid mount point \"%s\" is not a directory",
mount_point.c_str());
return false;
}
return true;
}
namespace impl {

int do_mount(spank_t spank, const std::vector<mount_entry> &mount_entries) {
util::expected<std::string, std::string>
do_mount(const std::vector<mount_entry> &mount_entries) {
if (mount_entries.size() == 0)
return ESPANK_SUCCESS;
return "nothing to mount";
if (unshare(CLONE_NEWNS) != 0) {
slurm_spank_log("Failed to unshare the mount namespace");
return -ESPANK_ERROR;
return util::unexpected("Failed to unshare the mount namespace");
}
// make all mounts in new namespace slave mounts, changes in the original
// namesapce won't propagate into current namespace
if (mount(NULL, "/", NULL, MS_SLAVE | MS_REC, NULL) != 0) {
slurm_spank_log("mount: unable to change `/` to MS_SLAVE | MS_REC");
return -ESPANK_ERROR;
return util::unexpected("mount: unable to change `/` to MS_SLAVE | MS_REC");
}

for (auto &entry : mount_entries) {
std::string mount_point = entry.mount_point;
std::string squashfs_file = entry.image_path;

if (!is_file(squashfs_file) || !is_valid_mountpoint(mount_point)) {
return -ESPANK_ERROR;
if (!util::is_file(squashfs_file)) {
return util::unexpected("the uenv squashfs file does not exist: " +
squashfs_file);
}
if (!util::is_valid_mountpoint(mount_point)) {
return util::unexpected("the mount point is not a valide path: " +
mount_point);
}

auto cxt = mnt_new_context();

if (mnt_context_disable_mtab(cxt, 1) != 0) {
slurm_spank_log("Failed to disable mtab");
return -ESPANK_ERROR;
return util::unexpected("Failed to disable mtab");
}

if (mnt_context_set_fstype(cxt, "squashfs") != 0) {
slurm_spank_log("Failed to set fstype to squashfs");
return -ESPANK_ERROR;
return util::unexpected("Failed to set fstype to squashfs");
}

if (mnt_context_append_options(cxt, "loop,nosuid,nodev,ro") != 0) {
slurm_spank_log("Failed to set mount options");
return -ESPANK_ERROR;
return util::unexpected("Failed to set mount options");
}

if (mnt_context_set_source(cxt, squashfs_file.c_str()) != 0) {
slurm_spank_log("Failed to set source");
return -ESPANK_ERROR;
return util::unexpected("Failed to set source");
}

if (mnt_context_set_target(cxt, mount_point.c_str()) != 0) {
slurm_spank_log("Failed to set target");
return -ESPANK_ERROR;
return util::unexpected("Failed to set target");
}

int rc = mnt_context_mount(cxt);
if (rc != 0) {
// https://ftp.ntu.edu.tw/pub/linux/utils/util-linux/v2.38/libmount-docs/libmount-Mount-context.html#mnt-context-mount
char buf[256];
rc = mnt_context_get_excode(cxt, rc, buf, sizeof(buf));
slurm_spank_log("%s:%s", mnt_context_get_target(cxt), buf);
return -ESPANK_ERROR;
// https://ftp.ntu.edu.tw/pub/linux/utils/util-linux/v2.38/libmount-docs/libmount-Mount-context.html#mnt-context-mount
const int rc = mnt_context_mount(cxt);
const bool success = rc == 0 && mnt_context_get_status(cxt) == 1;
if (!success) {
char code_buf[256];
const auto x =
mnt_context_get_excode(cxt, rc, code_buf, sizeof(code_buf));
const char *target_buf = mnt_context_get_target(cxt);
// careful: mnt_context_get_target can return NULL
std::string target = (target_buf == nullptr) ? target_buf : "?";

return util::unexpected(target + ": " + code_buf);
}
}

// export image, mountpoints to environment (for nested calls of sbatch)
std::stringstream ss;
for (auto &entry : mount_entries) {
auto abs_image = get_realpath(entry.image_path);
auto abs_mount = get_realpath(entry.mount_point);
ss << "file://" << *abs_image << ":" << *abs_mount << ",";
}
spank_setenv(spank, UENV_MOUNT_LIST, ss.str().c_str(), 1);

return ESPANK_SUCCESS;
return "succesfully mounted";
}

} // namespace impl
12 changes: 7 additions & 5 deletions src/mount.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
extern "C" {
#include <slurm/spank.h>
}
#include "parse_args.hpp"
#pragma once

#include <string>

#include "parse_args.hpp"
#include "util/expected.hpp"

#define UENV_MOUNT_LIST "UENV_MOUNT_LIST"

namespace impl {
@@ -12,6 +13,7 @@ namespace impl {
bool is_valid_mountpoint(const std::string &mount_point);

/// mount images and export env variable UENV_MOUNT_LIST
int do_mount(spank_t spank, const std::vector<mount_entry> &mount_entries);
util::expected<std::string, std::string>
do_mount(const std::vector<mount_entry> &mount_entries);

} // namespace impl
77 changes: 61 additions & 16 deletions src/parse_args.cpp
Original file line number Diff line number Diff line change
@@ -1,21 +1,31 @@
#include "parse_args.hpp"
#include "sqlite/sqlite.hpp"
#include "util/expected.hpp"
#include "util/helper.hpp"
#include <algorithm>
#include <optional>
#include <regex>
#include <set>
#include <stdexcept>

#include "config.hpp"
#include <slurm/spank.h>

#include "config.hpp"
#include "parse_args.hpp"
#include "sqlite/sqlite.hpp"
#include "util/expected.hpp"
#include "util/filesystem.hpp"
#include "util/strings.hpp"

// abs path
#define LINUX_ABS_FPATH "/[^\\0,:]+"
#define JFROG_IMAGE "[^\\0,:/]+"

namespace impl {

struct uenv_desc {
using entry_t = std::optional<std::string>;
entry_t name;
entry_t version;
entry_t tag;
entry_t sha;
};

const std::regex default_pattern("(" LINUX_ABS_FPATH ")"
"(:" LINUX_ABS_FPATH ")?",
std::regex::ECMAScript);
@@ -28,15 +38,50 @@ const std::regex repo_pattern("(" JFROG_IMAGE ")"
"(:" LINUX_ABS_FPATH ")?",
std::regex::ECMAScript);

std::vector<std::string> split(const std::string &s, char delim) {
std::vector<std::string> elems;
std::stringstream ss(s);
std::string item;
while (std::getline(ss, item, delim)) {
if (!item.empty())
elems.push_back(item);
// split a string on a character delimiter
//
// if drop_empty==false (default)
//
// "" -> [""]
// "," -> ["", ""]
// ",," -> ["", "", ""]
// ",a" -> ["", "a"]
// "a," -> ["a", ""]
// "a" -> ["a"]
// "a,b" -> ["a", "b"]
// "a,b,c" -> ["a", "b", "c"]
// "a,b,,c" -> ["a", "b", "", "c"]
//
// if drop_empty==true
//
// "" -> []
// "," -> []
// ",," -> []
// ",a" -> ["a"]
// "a," -> ["a"]
// "a" -> ["a"]
// "a,b" -> ["a", "b"]
// "a,b,c" -> ["a", "b", "c"]
// "a,b,,c" -> ["a", "b", "c"]
std::vector<std::string> split(const std::string &s, const char delim,
const bool drop_empty = false) {
std::vector<std::string> results;

auto pos = s.cbegin();
auto end = s.cend();
auto next = std::find(pos, end, delim);
while (next != end) {
if (!drop_empty || pos != next) {
results.emplace_back(pos, next);
}
pos = next + 1;
next = std::find(pos, end, delim);
}
return elems;
if (!drop_empty || pos != next) {
results.emplace_back(pos, next);
}

return results;
}

/*
@@ -97,7 +142,7 @@ find_repo_image(const uenv_desc &desc, const std::string &repo_path,
std::optional<std::string> uenv_arch) {
std::string dbpath = repo_path + "/index.db";
// check if dbpath exists.
if (!is_file(dbpath)) {
if (!util::is_file(dbpath)) {
return util::unexpected("Can't open uenv repo. " + dbpath +
" is not a file.");
}
@@ -176,7 +221,7 @@ find_repo_image(const uenv_desc &desc, const std::string &repo_path,
util::expected<std::vector<mount_entry>, std::string>
parse_arg(const std::string &arg, std::optional<std::string> uenv_repo_path,
std::optional<std::string> uenv_arch) {
std::vector<std::string> arguments = split(arg, ',');
std::vector<std::string> arguments = split(arg, ',', true);

if (arguments.empty()) {
return util::unexpected("No mountpoints given.");
12 changes: 3 additions & 9 deletions src/parse_args.hpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#pragma once
#include "util/expected.hpp"

#include <optional>
#include <string>
#include <vector>

#include "util/expected.hpp"

namespace impl {

enum class protocol { file, https, jfrog };
@@ -21,12 +23,4 @@ parse_arg(const std::string &arg,
std::optional<std::string> uenv_repo_path = std::nullopt,
std::optional<std::string> uenv_arch = std::nullopt);

struct uenv_desc {
using entry_t = std::optional<std::string>;
entry_t name;
entry_t version;
entry_t tag;
entry_t sha;
};

} // namespace impl
26 changes: 21 additions & 5 deletions src/plugin.cpp
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
#include <cstdlib>
#include <optional>
#include <stdexcept>
#include <string>
#include <unistd.h>
#include <vector>

#include "config.hpp"
#include "mount.hpp"
#include "parse_args.hpp"
#include "util/helper.hpp"
#include "util/filesystem.hpp"
#include "util/strings.hpp"

extern "C" {
#include <slurm/slurm_errno.h>
@@ -149,7 +149,23 @@ int slurm_spank_init(spank_t sp, int ac [[maybe_unused]],
/// check if image, mountpoint is valid
int init_post_opt_remote(spank_t sp,
const std::vector<mount_entry> &mount_entries) {
return do_mount(sp, mount_entries);
auto result = do_mount(mount_entries);
if (!result) {
slurm_spank_log("error mounting the requested uenv image: %s",
result.error().c_str());
return -ESPANK_ERROR;
}

// export image, mountpoints to environment (for nested calls of sbatch)
std::string env_var;
for (auto &entry : mount_entries) {
auto abs_image = *util::realpath(entry.image_path);
auto abs_mount = *util::realpath(entry.mount_point);
env_var += "file://" + abs_image + ":" + abs_mount + ",";
}
spank_setenv(sp, UENV_MOUNT_LIST, env_var.c_str(), 1);

return ESPANK_SUCCESS;
}

/// check if image/mountpoint are valid
@@ -158,11 +174,11 @@ int init_post_opt_local_allocator(
const std::vector<mount_entry> &mount_entries) {
bool invalid_path = false;
for (auto &entry : mount_entries) {
if (!is_file(entry.image_path)) {
if (!util::is_file(entry.image_path)) {
invalid_path = true;
slurm_error("Image does not exist: %s", entry.image_path.c_str());
}
if (!is_valid_mountpoint(entry.mount_point)) {
if (!util::is_valid_mountpoint(entry.mount_point)) {
invalid_path = true;
slurm_error("Mountpoint is invalid: %s", entry.mount_point.c_str());
}
Loading