Commit 0eddfd6f authored by jan.koester's avatar jan.koester
Browse files

test

parent 08a05a25
Loading
Loading
Loading
Loading
+2 −1
Changes for CMakeLists.txt: 2 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -100,7 +100,8 @@ 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)
# iSCSI: initiator (a client of iscsid) and exported targets (sharedd);
# needs iscsid/api.h (iscsid-dev) and sharedd/api.h (sharedd-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
+185 −15
Changes for modules/iscsi/iscsi.cpp: 185 added lines, 15 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).
/* webctl module "iscsi": both sides of iSCSI, over sysbus
 * (sysbus::Client::callService) -- the initiator iscsid (disks of other
 * machines used here, include/iscsid/api.h) and the targets sharedd
 * exports with the kernel's LIO (volumes of this machine for others,
 * include/sharedd/api.h). Either works without the other daemon.
 *
 * Initiator (iscsid):
 *   info     -> {status{initiatorName, kernel, kernelError}, settings,
 *               targets[{name, address, port, autoLogin, auth, username,
 *               passwordSet, usernameIn, passwordInSet, state, error, sid,
@@ -17,6 +21,17 @@
 *   settings {initiatorName, initiatorAlias, loginTimeout,
 *             replacementTimeout, noopInterval, noopTimeout}
 *   reload
 *   info also has initiatorUnavailable (why iscsid can't be asked) instead
 *   of initiator/settings/targets
 *
 * Exported targets (sharedd):
 *   info adds export{state, error, settings{enabled, baseName, portals[]},
 *     targets[{name, iqn, comment, luns[], readOnly, open, initiators[],
 *     chapUser, chapPasswordSet, chapUserIn, chapPasswordInSet, sessions[],
 *     error}], devices[{device, kind, size, inUse}]}, or exportUnavailable
 *   exportCreate {target}, exportUpdate {name, target} (chapPassword /
 *     chapPasswordIn missing or null: kept), exportDelete {name}
 *   exportSettings {enabled, baseName, portals[]}
 */

#include <cstdlib>
@@ -24,6 +39,7 @@
#include <vector>

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

@@ -39,29 +55,175 @@ 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) {
// a call to iscsid or sharedd; on failure the error object to return
json_object *callDaemon(const char *service, 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);
    reply = bus.callService(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);
        return error(tr("%1 isn't running (Services: %1)", "%1 läuft nicht (Dienste: %1)", {service}), 503);
    int status = name == sysbus::error::AccessDenied ? 403 : name == sysbus::error::InvalidArgs ? 400 : 500;
    return error(reply.get("message", name), status);
}

json_object *callIscsid(const char *method, const Fields &args, sysbus::Message &reply) {
    return callDaemon(iscsid::Service, method, args, reply);
}

json_object *callSharedd(const char *method, const Fields &args, sysbus::Message &reply) {
    return callDaemon(sharedd::Service, method, args, reply);
}

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();
}

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

json_object *strings(const std::vector<std::string> &v) {
    json_object *a = json_object_new_array();
    for (auto &x : v) json_object_array_add(a, jstr(x));
    return a;
}

// the error's text into out[key], the error object freed
void unavailable(json_object *out, const char *key, json_object *e) {
    json_object_object_add(out, key, json_object_get(json_object_object_get(e, "error")));
    json_object_put(e);
}

// ---- exported targets (sharedd) ----

json_object *exportTargetJson(const std::vector<std::pair<std::string, std::string>> &f) {
    json_object *o = json_object_new_object();
    std::vector<std::string> luns, initiators, sessions;
    for (auto &p : f) {
        const std::string &k = p.first, &v = p.second;
        if (k == "name" || k == "iqn" || k == "comment" || k == "error") json_object_object_add(o, k.c_str(), jstr(v));
        else if (k == "lun") luns.push_back(v);
        else if (k == "initiator") initiators.push_back(v);
        else if (k == "session") sessions.push_back(v);
        else if (k == "read_only") json_object_object_add(o, "readOnly", json_object_new_boolean(v == "yes"));
        else if (k == "open") json_object_object_add(o, "open", json_object_new_boolean(v == "yes"));
        else if (k == "chap_user") json_object_object_add(o, "chapUser", jstr(v));
        else if (k == "chap_user_in") json_object_object_add(o, "chapUserIn", jstr(v));
        else if (k == "chap_password_set") json_object_object_add(o, "chapPasswordSet", json_object_new_boolean(v == "yes"));
        else if (k == "chap_password_in_set") json_object_object_add(o, "chapPasswordInSet", json_object_new_boolean(v == "yes"));
    }
    json_object_object_add(o, "luns", strings(luns));
    json_object_object_add(o, "initiators", strings(initiators));
    json_object_object_add(o, "sessions", strings(sessions));
    return o;
}

// sharedd's iSCSI side into out["export"]; nullptr or the error
json_object *exportInfo(json_object *out) {
    sysbus::Message r;
    if (json_object *e = callSharedd(sharedd::method::Status, {}, r)) return e;
    json_object *ex = json_object_new_object();
    json_object_object_add(ex, "state", jstr(r.get("iscsi_state")));
    json_object_object_add(ex, "error", jstr(r.get("iscsi_error")));
    if (json_object *e = callSharedd(sharedd::method::GetSettings, {}, r)) return json_object_put(ex), e;
    json_object *set = json_object_new_object();
    json_object_object_add(set, "enabled", json_object_new_boolean(r.get("iscsi") == "yes"));
    json_object_object_add(set, "baseName", jstr(r.get("iscsi_base_name")));
    json_object_object_add(set, "portals", strings(r.getAll("iscsi_portal")));
    json_object_object_add(ex, "settings", set);

    if (json_object *e = callSharedd(sharedd::method::ListIscsiTargets, {}, r)) return json_object_put(ex), e;
    json_object *targets = json_object_new_array();
    std::vector<std::pair<std::string, std::string>> cur;
    auto flush = [&] {
        if (!cur.empty()) json_object_array_add(targets, exportTargetJson(cur));
        cur.clear();
    };
    for (auto &f : r.fields) {
        if (f.first == "name") flush();
        cur.push_back(f);
    }
    flush();
    json_object_object_add(ex, "targets", targets);

    if (json_object *e = callSharedd(sharedd::method::ListIscsiDevices, {}, r)) return json_object_put(ex), e;
    json_object *devices = json_object_new_array(), *dev = nullptr;
    for (auto &f : r.fields) {
        if (f.first == "device") {
            dev = json_object_new_object();
            json_object_array_add(devices, dev);
            json_object_object_add(dev, "device", jstr(f.second));
        } else if (dev && f.first == "size") {
            json_object_object_add(dev, "size", json_object_new_int64(num(f.second)));
        } else if (dev && (f.first == "kind")) {
            json_object_object_add(dev, "kind", jstr(f.second));
        } else if (dev && f.first == "in_use") {
            json_object_object_add(dev, "inUse", jstr(f.second));
        }
    }
    json_object_object_add(ex, "devices", devices);
    json_object_object_add(out, "export", ex);
    return nullptr;
}

// a target from the UI as sharedd's fields; keepSecrets: missing/null
// passwords stay as they are
std::string exportFields(json_object *t, Fields &out, bool keepSecrets) {
    if (!t || !json_object_is_type(t, json_type_object)) return tr("no target given", "kein Target angegeben");
    out.emplace_back("comment", webctl::str(t, "comment"));
    out.emplace_back("read_only", yesNo(webctl::boolean(t, "readOnly")));
    out.emplace_back("open", yesNo(webctl::boolean(t, "open")));
    for (auto &k : {std::make_pair("luns", "lun"), std::make_pair("initiators", "initiator")}) {
        json_object *list = nullptr;
        if (!json_object_object_get_ex(t, k.first, &list) || !list) continue;
        if (!json_object_is_type(list, json_type_array)) return tr("%1 must be a list", "%1 muss eine Liste sein", {k.first});
        for (size_t i = 0; i < json_object_array_length(list); ++i) {
            const char *v = json_object_get_string(json_object_array_get_idx(list, i));
            if (v && *v) out.emplace_back(k.second, v);
        }
    }
    out.emplace_back("chap_user", webctl::str(t, "chapUser"));
    out.emplace_back("chap_user_in", webctl::str(t, "chapUserIn"));
    for (auto &k : {std::make_pair("chapPassword", "chap_password"), std::make_pair("chapPasswordIn", "chap_password_in")}) {
        json_object *v = nullptr;
        bool given = json_object_object_get_ex(t, k.first, &v) && v;
        if (given || !keepSecrets) out.emplace_back(k.second, webctl::str(t, k.first));
    }
    return "";
}

json_object *exportAction(const std::string &action, json_object *a) {
    if (action == "exportDelete") return simpleSharedd(sharedd::method::DeleteIscsiTarget, {{"name", webctl::str(a, "name")}});
    if (action == "exportSettings") {
        Fields f = {{"iscsi", yesNo(webctl::boolean(a, "enabled", true))}, {"iscsi_base_name", webctl::str(a, "baseName")}};
        json_object *portals = nullptr;
        if (json_object_object_get_ex(a, "portals", &portals) && portals && json_object_is_type(portals, json_type_array))
            for (size_t i = 0; i < json_object_array_length(portals); ++i) {
                const char *v = json_object_get_string(json_object_array_get_idx(portals, i));
                f.emplace_back("iscsi_portal", v ? v : "");
            }
        return simpleSharedd(sharedd::method::SetSettings, f);
    }
    json_object *t = nullptr;
    json_object_object_get_ex(a, "target", &t);
    bool update = action == "exportUpdate";
    Fields f = {{"name", update ? webctl::str(a, "name") : webctl::str(t, "name")}};
    std::string why = exportFields(t, f, update);
    if (!why.empty()) return error(why);
    if (update && webctl::str(t, "name") != f[0].second) f.emplace_back("new_name", webctl::str(t, "name"));
    return simpleSharedd(update ? sharedd::method::EditIscsiTarget : sharedd::method::CreateIscsiTarget, f);
}

// the target named by name/address/port in args
Fields key(json_object *a) {
    return {{"target", webctl::str(a, "name")},
@@ -111,21 +273,17 @@ json_object *targetsJson(const sysbus::Message &r) {
    return targets;
}

json_object *info() {
    json_object *out = json_object_new_object();
// iscsid's side into out; nullptr or the error
json_object *initiatorInfo(json_object *out) {
    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;
    }
    if (json_object *e = callIscsid(iscsid::method::Status, {}, r)) return e;
    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;
    if (json_object *e = callIscsid(iscsid::method::GetSettings, {}, r)) return 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")));
@@ -134,8 +292,19 @@ json_object *info() {
        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;
    if (json_object *e = callIscsid(iscsid::method::ListTargets, {}, r)) return e;
    json_object_object_add(out, "targets", targetsJson(r));
    return nullptr;
}

json_object *info() {
    json_object *out = json_object_new_object();
    if (json_object *e = initiatorInfo(out)) {
        // the half that answers is still shown
        for (const char *k : {"initiator", "settings", "targets"}) json_object_object_del(out, k);
        unavailable(out, "initiatorUnavailable", e);
    }
    if (json_object *e = exportInfo(out)) unavailable(out, "exportUnavailable", e);
    return out;
}

@@ -196,6 +365,7 @@ json_object *call(const webctl_request *req) {
    json_object *a = req->args;

    if (action == "info") return info();
    if (action.compare(0, 6, "export") == 0) return exportAction(action, a);
    if (action == "discover") return discover(a);
    if (action == "reload") return simple(iscsid::method::Reload, {});
    if (action == "login") return simple(iscsid::method::Login, key(a));
+183 −8

File changed.

Preview size limit exceeded, changes collapsed.