Commit 2329b182 authored by jan.koester's avatar jan.koester
Browse files

test

parent 21e9dda0
Loading
Loading
Loading
Loading
+3 −0
Changes for CMakeLists.txt: 3 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -100,6 +100,9 @@ webctl_module(lvm modules/lvm/lvm.cpp modules/common/storaged.cpp)
target_link_libraries(webctl_mod_lvm PRIVATE sysbus::sysbus)
webctl_module(disks modules/disks/disks.cpp modules/common/storaged.cpp)
target_link_libraries(webctl_mod_disks PRIVATE sysbus::sysbus)
# iSCSI initiator: a client of iscsid; needs iscsid/api.h (iscsid-dev)
webctl_module(iscsi modules/iscsi/iscsi.cpp)
target_link_libraries(webctl_mod_iscsi PRIVATE sysbus::sysbus)
# mounts are syslite .mount units (sysbus CreateMount); mke2fs/mkfs.fat
# format; modules/common/blockdev.cpp: free devices, guarded probing
webctl_module(filesystems modules/filesystems/filesystems.cpp modules/common/blockdev.cpp)
+243 −0
Changes for modules/iscsi/iscsi.cpp: 243 added lines, 0 removed lines.
Original line number Diff line number Diff line
/* webctl module "iscsi": the iSCSI initiator iscsid, entirely over its
 * sysbus API (sysbus::Client::callService, include/iscsid/api.h).
 *
 *   info     -> {status{initiatorName, kernel, kernelError}, settings,
 *               targets[{name, address, port, autoLogin, auth, username,
 *               passwordSet, usernameIn, passwordInSet, state, error, sid,
 *               host, tpgt, alias, luns[{device, lun, size, vendor, model,
 *               inUse}]}]}
 *   discover {address, port, auth, username, password, usernameIn,
 *             passwordIn}   -> {targets[{name, portals[{address, port, tpgt}]}]}
 *   add      {target{name, address, port, autoLogin, auth, username,
 *             password, usernameIn, passwordIn}, login}
 *   update   {name, address, port, target{...}}   password fields: missing
 *             or null keep them
 *   delete / logout {name, address, port, force}
 *   login    {name, address, port}
 *   settings {initiatorName, initiatorAlias, loginTimeout,
 *             replacementTimeout, noopInterval, noopTimeout}
 *   reload
 */

#include <cstdlib>
#include <string>
#include <vector>

#include <iscsid/api.h>
#include <sysbus/client.h>
#include <webctl/module.h>

using webctl::error;
using webctl::jstr;
using webctl::tr;

namespace {

using Fields = std::vector<std::pair<std::string, std::string>>;

const char *yesNo(bool b) { return b ? "yes" : "no"; }

long long num(const std::string &v) { return std::strtoll(v.c_str(), nullptr, 10); }

// an iscsid call; on failure the error object to return
json_object *callIscsid(const char *method, const Fields &args, sysbus::Message &reply) {
    sysbus::Client bus;
    if (!bus.connect())
        return error(tr("sysbus isn't reachable: %1", "sysbus ist nicht erreichbar: %1", {bus.lastError()}), 503);
    Fields all = args;
    all.emplace_back("lang", webctl::language());
    // discovery may wait for the login timeout of an unreachable portal
    reply = bus.callService(iscsid::Service, method, all, 120000);
    if (reply.type != sysbus::MessageType::Error) return nullptr;
    const std::string &name = reply.member;
    if (name == sysbus::error::ServiceUnknown)
        return error(tr("iscsid isn't running (Services: iscsid)", "iscsid läuft nicht (Dienste: iscsid)"), 503);
    int status = name == sysbus::error::AccessDenied ? 403 : name == sysbus::error::InvalidArgs ? 400 : 500;
    return error(reply.get("message", name), status);
}

json_object *simple(const char *method, const Fields &f) {
    sysbus::Message r;
    if (json_object *e = callIscsid(method, f, r)) return e;
    return webctl::ok();
}

// the target named by name/address/port in args
Fields key(json_object *a) {
    return {{"target", webctl::str(a, "name")},
            {"address", webctl::str(a, "address")},
            {"port", std::to_string(webctl::integer(a, "port", 3260))}};
}

json_object *targetsJson(const sysbus::Message &r) {
    json_object *targets = json_object_new_array();
    json_object *cur = nullptr, *luns = nullptr, *lun = nullptr;
    for (auto &f : r.fields) {
        const std::string &k = f.first, &v = f.second;
        if (k == "target") {
            cur = json_object_new_object();
            luns = json_object_new_array();
            lun = nullptr;
            json_object_object_add(cur, "name", jstr(v));
            json_object_object_add(cur, "luns", luns);
            json_object_array_add(targets, cur);
            continue;
        }
        if (!cur) continue;
        if (k == "device") {
            lun = json_object_new_object();
            json_object_object_add(lun, "device", jstr(v));
            json_object_array_add(luns, lun);
        } else if (lun && (k == "vendor" || k == "model")) {
            json_object_object_add(lun, k.c_str(), jstr(v));
        } else if (lun && (k == "lun" || k == "size")) {
            json_object_object_add(lun, k.c_str(), json_object_new_int64(num(v)));
        } else if (lun && k == "in_use") {
            json_object_object_add(lun, "inUse", json_object_new_boolean(v == "yes"));
        } else if (k == "address" || k == "auth" || k == "username" || k == "state" || k == "error" || k == "alias") {
            json_object_object_add(cur, k.c_str(), jstr(v));
        } else if (k == "username_in") {
            json_object_object_add(cur, "usernameIn", jstr(v));
        } else if (k == "port" || k == "sid" || k == "host" || k == "tpgt") {
            json_object_object_add(cur, k.c_str(), json_object_new_int64(num(v)));
        } else if (k == "auto") {
            json_object_object_add(cur, "autoLogin", json_object_new_boolean(v == "yes"));
        } else if (k == "password_set") {
            json_object_object_add(cur, "passwordSet", json_object_new_boolean(v == "yes"));
        } else if (k == "password_in_set") {
            json_object_object_add(cur, "passwordInSet", json_object_new_boolean(v == "yes"));
        }
    }
    return targets;
}

json_object *info() {
    json_object *out = json_object_new_object();
    sysbus::Message r;
    if (json_object *e = callIscsid(iscsid::method::Status, {}, r)) {
        json_object_object_add(out, "unavailable", json_object_get(json_object_object_get(e, "error")));
        json_object_put(e);
        return out;
    }
    json_object *st = json_object_new_object();
    json_object_object_add(st, "initiatorName", jstr(r.get("initiator_name")));
    json_object_object_add(st, "kernel", json_object_new_boolean(r.get("kernel") == "yes"));
    json_object_object_add(st, "kernelError", jstr(r.get("kernel_error")));
    json_object_object_add(out, "initiator", st);  // not "status": the frontend takes that for the HTTP status

    if (json_object *e = callIscsid(iscsid::method::GetSettings, {}, r)) return json_object_put(out), e;
    json_object *set = json_object_new_object();
    json_object_object_add(set, "initiatorName", jstr(r.get("initiator_name")));
    json_object_object_add(set, "initiatorAlias", jstr(r.get("initiator_alias")));
    for (auto &p : {std::make_pair("loginTimeout", "login_timeout"), std::make_pair("replacementTimeout", "replacement_timeout"),
                    std::make_pair("noopInterval", "noop_interval"), std::make_pair("noopTimeout", "noop_timeout")})
        json_object_object_add(set, p.first, json_object_new_int64(num(r.get(p.second))));
    json_object_object_add(out, "settings", set);

    if (json_object *e = callIscsid(iscsid::method::ListTargets, {}, r)) return json_object_put(out), e;
    json_object_object_add(out, "targets", targetsJson(r));
    return out;
}

json_object *discover(json_object *a) {
    Fields f = {{"address", webctl::str(a, "address")}, {"port", std::to_string(webctl::integer(a, "port", 3260))}};
    std::string auth = webctl::str(a, "auth", "none");
    f.emplace_back("auth", auth);
    if (auth == "chap") {
        for (auto &p : {std::make_pair("username", "username"), std::make_pair("password", "password"),
                        std::make_pair("usernameIn", "username_in"), std::make_pair("passwordIn", "password_in")})
            f.emplace_back(p.second, webctl::str(a, p.first));
    }
    sysbus::Message r;
    if (json_object *e = callIscsid(iscsid::method::Discover, f, r)) return e;
    json_object *targets = json_object_new_array();
    json_object *cur = nullptr, *portals = nullptr, *portal = nullptr;
    for (auto &kv : r.fields) {
        if (kv.first == "target") {
            cur = json_object_new_object();
            portals = json_object_new_array();
            json_object_object_add(cur, "name", jstr(kv.second));
            json_object_object_add(cur, "portals", portals);
            json_object_array_add(targets, cur);
        } else if (cur && kv.first == "portal_address") {
            portal = json_object_new_object();
            json_object_object_add(portal, "address", jstr(kv.second));
            json_object_array_add(portals, portal);
        } else if (portal && kv.first == "portal_port") {
            json_object_object_add(portal, "port", json_object_new_int64(num(kv.second)));
        } else if (portal && kv.first == "portal_tpgt") {
            json_object_object_add(portal, "tpgt", json_object_new_int64(num(kv.second)));
        }
    }
    json_object *out = json_object_new_object();
    json_object_object_add(out, "targets", targets);
    return out;
}

// a target from the UI as iscsid's fields; keepSecrets: missing/null
// passwords stay as they are
void targetFields(json_object *t, Fields &f, bool keepSecrets) {
    f.emplace_back("auto", yesNo(webctl::boolean(t, "autoLogin", true)));
    std::string auth = webctl::str(t, "auth", "none");
    f.emplace_back("auth", auth);
    f.emplace_back("username", auth == "chap" ? webctl::str(t, "username") : "");
    f.emplace_back("username_in", auth == "chap" ? webctl::str(t, "usernameIn") : "");
    for (auto &p : {std::make_pair("password", "password"), std::make_pair("passwordIn", "password_in")}) {
        json_object *v = nullptr;
        bool given = json_object_object_get_ex(t, p.first, &v) && v;
        if (auth != "chap") f.emplace_back(p.second, "");
        else if (given || !keepSecrets) f.emplace_back(p.second, webctl::str(t, p.first));
    }
}

json_object *call(const webctl_request *req) {
    webctl::LanguageScope lang(req->lang);
    std::string action = req->action;
    json_object *a = req->args;

    if (action == "info") return info();
    if (action == "discover") return discover(a);
    if (action == "reload") return simple(iscsid::method::Reload, {});
    if (action == "login") return simple(iscsid::method::Login, key(a));
    if (action == "logout" || action == "delete") {
        Fields f = key(a);
        f.emplace_back("force", yesNo(webctl::boolean(a, "force")));
        return simple(action == "logout" ? iscsid::method::Logout : iscsid::method::DeleteTarget, f);
    }
    if (action == "add" || action == "update") {
        json_object *t = nullptr;
        if (!json_object_object_get_ex(a, "target", &t) || !t || !json_object_is_type(t, json_type_object))
            return error(tr("no target given", "kein Target angegeben"));
        Fields f;
        if (action == "add") {
            f = key(t);
            targetFields(t, f, false);
            f.emplace_back("login", yesNo(webctl::boolean(a, "login")));
            return simple(iscsid::method::AddTarget, f);
        }
        f = key(a);
        f.emplace_back("new_target", webctl::str(t, "name"));
        f.emplace_back("new_address", webctl::str(t, "address"));
        f.emplace_back("new_port", std::to_string(webctl::integer(t, "port", 3260)));
        targetFields(t, f, true);
        return simple(iscsid::method::EditTarget, f);
    }
    if (action == "settings") {
        Fields f;
        if (webctl::has(a, "initiatorName")) f.emplace_back("initiator_name", webctl::str(a, "initiatorName"));
        if (webctl::has(a, "initiatorAlias")) f.emplace_back("initiator_alias", webctl::str(a, "initiatorAlias"));
        for (auto &p : {std::make_pair("loginTimeout", "login_timeout"), std::make_pair("replacementTimeout", "replacement_timeout"),
                        std::make_pair("noopInterval", "noop_interval"), std::make_pair("noopTimeout", "noop_timeout")})
            if (webctl::has(a, p.first)) {
                long v = webctl::integer(a, p.first, -1);
                if (v < 0) return error(tr("timeouts are seconds", "Timeouts sind Sekunden"));
                f.emplace_back(p.second, std::to_string(v));
            }
        return simple(iscsid::method::SetSettings, f);
    }
    return error(tr("unknown action %1", "unbekannte Aktion %1", {action}), 404);
}

} // namespace

WEBCTL_MODULE({WEBCTL_MODULE_ABI, "iscsi", "iSCSI", 26, nullptr, nullptr, call});

www/modules/iscsi.js

0 → 100644
+320 −0

File added.

Preview size limit exceeded, changes collapsed.