Implement Acquire Lock Function in Lock Service
- This commit implements the rest API by which any external
client(Ex: Hardware Management Console) can request for a
single/multiple locks as per the design specification mentioned
in `docs/designs/management-console/hmc-lock-management.md`
Tested By:
1. curl -k -H "X-Auth-Token:$bmc_tokens" -XPOST -H "Content-type: application/json" -d '{
"Request" :[
{
"LockType":"Read",
"SegmentFlags":
[
{"LockFlag":"LockSame","SegmentLength":3},
{"LockFlag":"DontLock","SegmentLength":4}
],
"ResourceID": 256
}
]
}' https://<ip>/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock
2.curl -k -H "X-Auth-Token:$bmc_tokens" -XPOST -H "Content-type: application/json" -d '{
"Request" :[
{
"LockType":"Read",
"SegmentFlags":
[
{"LockFlag":"LockAll","SegmentLength":2},
{"LockFlag":"DontLock","SegmentLength":1}
],
"ResourceID": 234
},
{
"LockType" : "Read",
"SegmentFlags":
[
{"LockFlag":"DontLock","SegmentLength":2},
{"LockFlag":"DontLock","SegmentLength":1}
],
"ResourceID": 234
}
]}' https://<ip>/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock
Signed-off-by: manojkiraneda <manojkiran.eda@gmail.com>
Change-Id: Ia173878702afe7c00160b7935d6a03099b7df622
diff --git a/include/ibm/locks.hpp b/include/ibm/locks.hpp
new file mode 100644
index 0000000..60c55bf
--- /dev/null
+++ b/include/ibm/locks.hpp
@@ -0,0 +1,422 @@
+#pragma once
+
+#include <app.h>
+
+#include <boost/algorithm/string.hpp>
+#include <boost/container/flat_map.hpp>
+#include <filesystem>
+#include <nlohmann/json.hpp>
+
+namespace crow
+{
+namespace ibm_mc_lock
+{
+
+namespace fs = std::filesystem;
+using SType = std::string;
+
+/*----------------------------------------
+|Segment flags : LockFlag | SegmentLength|
+------------------------------------------*/
+
+using SegmentFlags = std::vector<std::pair<SType, uint32_t>>;
+
+// Lockrequest = session-id | hmc-id | locktype | resourceid | segmentinfo
+using LockRequest = std::tuple<SType, SType, SType, uint64_t, SegmentFlags>;
+
+using LockRequests = std::vector<LockRequest>;
+using Rc =
+ std::pair<bool, std::variant<uint32_t, std::pair<uint32_t, LockRequest>>>;
+using RcRelaseLock = std::pair<bool, LockRequest>;
+using RcGetLocklist = std::pair<
+ bool,
+ std::variant<std::string, std::vector<std::pair<uint32_t, LockRequests>>>>;
+
+using RcAcquireLock = std::pair<bool, std::variant<Rc, std::pair<bool, int>>>;
+
+class Lock
+{
+ uint32_t transactionId;
+ boost::container::flat_map<uint32_t, LockRequests> lockTable;
+
+ /*
+ * This function implements the logic for validating an incomming
+ * lock request/requests.
+ *
+ * Returns : True (if Valid)
+ * Returns : False (if not a Valid lock request)
+ */
+
+ bool isValidLockRequest(const LockRequest);
+
+ /*
+ * This function implements the logic of checking if the incomming
+ * multi-lock request is not having conflicting requirements.
+ *
+ * Returns : True (if conflicting)
+ * Returns : False (if not conflicting)
+ */
+
+ bool isConflictRequest(const LockRequests);
+ /*
+ * Implements the core algorithm to find the conflicting
+ * lock requests.
+ *
+ * This functions takes two lock requests and check if both
+ * are conflicting to each other.
+ *
+ * Returns : True (if conflicting)
+ * Returns : False (if not conflicting)
+ */
+ bool isConflictRecord(const LockRequest, const LockRequest);
+
+ /*
+ * This function implements the logic of checking the conflicting
+ * locks from a incomming single/multi lock requests with the already
+ * existing lock request in the lock table.
+ *
+ */
+
+ Rc isConflictWithTable(const LockRequests);
+
+ /*
+ * This function implements the algorithm for checking the respective
+ * bytes of the resource id based on the lock management algorithm.
+ */
+
+ bool checkByte(uint64_t, uint64_t, uint32_t);
+
+ /*
+ * This functions implements a counter that generates a unique 32 bit
+ * number for every successful transaction. This number will be used by
+ * the Management Console for debug.
+ */
+ uint32_t generateTransactionId();
+
+ public:
+ /*
+ * This function implements the logic for acquiring a lock on a
+ * resource if the incomming request is legitimate without any
+ * conflicting requirements & without any conflicting requirement
+ * with the exsiting locks in the lock table.
+ *
+ */
+
+ RcAcquireLock acquireLock(const LockRequests);
+
+ public:
+ Lock()
+ {
+ transactionId = 0;
+ }
+
+} lockObject;
+
+RcAcquireLock Lock::acquireLock(const LockRequests lockRequestStructure)
+{
+
+ // validate the lock request
+
+ for (auto &lockRecord : lockRequestStructure)
+ {
+ bool status = isValidLockRequest(lockRecord);
+ if (!status)
+ {
+ BMCWEB_LOG_DEBUG << "Not a Valid record";
+ BMCWEB_LOG_DEBUG << "Bad json in request";
+ return std::make_pair(true, std::make_pair(status, 0));
+ }
+ }
+ // check for conflict record
+
+ const LockRequests &multiRequest = lockRequestStructure;
+ bool status = isConflictRequest(multiRequest);
+
+ if (status)
+ {
+ BMCWEB_LOG_DEBUG << "There is a conflict within itself";
+ return std::make_pair(true, std::make_pair(status, 1));
+ }
+ else
+ {
+ BMCWEB_LOG_DEBUG << "The request is not conflicting within itself";
+
+ // Need to check for conflict with the locktable entries.
+
+ auto conflict = isConflictWithTable(multiRequest);
+
+ BMCWEB_LOG_DEBUG << "Done with checking conflict with the locktable";
+ return std::make_pair(false, conflict);
+ }
+
+ return std::make_pair(true, std::make_pair(true, 1));
+}
+
+bool Lock::isValidLockRequest(const LockRequest refLockRecord)
+{
+
+ // validate the locktype
+
+ if (!((boost::equals(std::get<2>(refLockRecord), "Read") ||
+ (boost::equals(std::get<2>(refLockRecord), "Write")))))
+ {
+ BMCWEB_LOG_DEBUG << "Validation of LockType Failed";
+ BMCWEB_LOG_DEBUG << "Locktype : " << std::get<2>(refLockRecord);
+ return false;
+ }
+
+ BMCWEB_LOG_DEBUG << static_cast<int>(std::get<4>(refLockRecord).size());
+
+ // validate the number of segments
+ // Allowed No of segments are between 2 and 6
+ if ((static_cast<int>(std::get<4>(refLockRecord).size()) > 6) ||
+ (static_cast<int>(std::get<4>(refLockRecord).size()) < 2))
+ {
+ BMCWEB_LOG_DEBUG << "Validation of Number of Segements Failed";
+ BMCWEB_LOG_DEBUG << "Number of Segments provied : "
+ << sizeof(std::get<4>(refLockRecord));
+ return false;
+ }
+
+ int lockFlag = 0;
+ // validate the lockflags & segment length
+
+ for (const auto &p : std::get<4>(refLockRecord))
+ {
+
+ // validate the lock flags
+ // Allowed lockflags are locksame,lockall & dontlock
+
+ if (!((boost::equals(p.first, "LockSame") ||
+ (boost::equals(p.first, "LockAll")) ||
+ (boost::equals(p.first, "DontLock")))))
+ {
+ BMCWEB_LOG_DEBUG << "Validation of lock flags failed";
+ BMCWEB_LOG_DEBUG << p.first;
+ return false;
+ }
+
+ // validate the segment length
+ // Allowed values of segment length are between 1 and 4
+
+ if (p.second < 1 || p.second > 4)
+ {
+ BMCWEB_LOG_DEBUG << "Validation of Segment Length Failed";
+ BMCWEB_LOG_DEBUG << p.second;
+ return false;
+ }
+
+ if ((boost::equals(p.first, "LockSame") ||
+ (boost::equals(p.first, "LockAll"))))
+ {
+ ++lockFlag;
+ if (lockFlag >= 2)
+ {
+ return false;
+ }
+ }
+ }
+
+ // validate the segment length
+ return true;
+}
+
+Rc Lock::isConflictWithTable(const LockRequests refLockRequestStructure)
+{
+
+ uint32_t transactionId;
+
+ if (lockTable.empty())
+ {
+ transactionId = generateTransactionId();
+ BMCWEB_LOG_DEBUG << transactionId;
+ // Lock table is empty, so we are safe to add the lockrecords
+ // as there will be no conflict
+ BMCWEB_LOG_DEBUG << "Lock table is empty, so adding the lockrecords";
+ lockTable.emplace(std::pair<uint32_t, LockRequests>(
+ transactionId, refLockRequestStructure));
+
+ return std::make_pair(false, transactionId);
+ }
+
+ else
+ {
+ BMCWEB_LOG_DEBUG
+ << "Lock table is not empty, check for conflict with lock table";
+ // Lock table is not empty, compare the lockrequest entries with
+ // the entries in the lock table
+
+ for (const auto &lockRecord1 : refLockRequestStructure)
+ {
+ for (const auto &map : lockTable)
+ {
+ for (const auto &lockRecord2 : map.second)
+ {
+ bool status = isConflictRecord(lockRecord1, lockRecord2);
+ if (status)
+ {
+ return std::make_pair(
+ true, std::make_pair(map.first, lockRecord2));
+ }
+ }
+ }
+ }
+
+ // Reached here, so no conflict with the locktable, so we are safe to
+ // add the request records into the lock table
+
+ // Lock table is empty, so we are safe to add the lockrecords
+ // as there will be no conflict
+ BMCWEB_LOG_DEBUG << " Adding elements into lock table";
+ transactionId = generateTransactionId();
+ lockTable.emplace(
+ std::make_pair(transactionId, refLockRequestStructure));
+ }
+ return std::make_pair(false, transactionId);
+}
+
+bool Lock::isConflictRequest(const LockRequests refLockRequestStructure)
+{
+ // check for all the locks coming in as a part of single request
+ // return conflict if any two lock requests are conflicting
+
+ if (refLockRequestStructure.size() == 1)
+ {
+ BMCWEB_LOG_DEBUG << "Only single lock request, so there is no conflict";
+ // This means , we have only one lock request in the current
+ // request , so no conflict within the request
+ return false;
+ }
+
+ else
+ {
+ BMCWEB_LOG_DEBUG
+ << "There are multiple lock requests coming in a single request";
+
+ // There are multiple requests a part of one request
+
+ for (uint32_t i = 0; i < refLockRequestStructure.size(); i++)
+ {
+ for (uint32_t j = i + 1; j < refLockRequestStructure.size(); j++)
+ {
+ const LockRequest &p = refLockRequestStructure[i];
+ const LockRequest &q = refLockRequestStructure[j];
+ bool status = isConflictRecord(p, q);
+
+ if (status)
+ {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+// This function converts the provided uint64_t resource id's from the two
+// lock requests subjected for comparision, and this function also compares
+// the content by bytes mentioned by a uint32_t number.
+
+// If all the elements in the lock requests which are subjected for comparison
+// are same, then the last comparision would be to check for the respective
+// bytes in the resourceid based on the segment length.
+
+bool Lock::checkByte(uint64_t resourceId1, uint64_t resourceId2,
+ uint32_t position)
+{
+ uint8_t *p = reinterpret_cast<uint8_t *>(&resourceId1);
+ uint8_t *q = reinterpret_cast<uint8_t *>(&resourceId2);
+
+ BMCWEB_LOG_DEBUG << "Comparing bytes " << std::to_string(p[position]) << ","
+ << std::to_string(q[position]);
+ if (p[position] != q[position])
+ {
+ return false;
+ }
+
+ else
+ {
+ return true;
+ }
+ return true;
+}
+
+bool Lock::isConflictRecord(const LockRequest refLockRecord1,
+ const LockRequest refLockRecord2)
+{
+ // No conflict if both are read locks
+
+ if (boost::equals(std::get<2>(refLockRecord1), "Read") &&
+ boost::equals(std::get<2>(refLockRecord2), "Read"))
+ {
+ BMCWEB_LOG_DEBUG << "Both are read locks, no conflict";
+ return false;
+ }
+
+ else
+ {
+ uint32_t i = 0;
+ for (const auto &p : std::get<4>(refLockRecord1))
+ {
+
+ // return conflict when any of them is try to lock all resources
+ // under the current resource level.
+ if (boost::equals(p.first, "LockAll") ||
+ boost::equals(std::get<4>(refLockRecord2)[i].first, "LockAll"))
+ {
+ BMCWEB_LOG_DEBUG
+ << "Either of the Comparing locks are trying to lock all "
+ "resources under the current resource level";
+ return true;
+ }
+
+ // determine if there is a lock-all-with-same-segment-size.
+ // If the current segment sizes are the same,then we should fail.
+
+ if ((boost::equals(p.first, "LockSame") ||
+ boost::equals(std::get<4>(refLockRecord2)[i].first,
+ "LockSame")) &&
+ (p.second == std::get<4>(refLockRecord2)[i].second))
+ {
+ return true;
+ }
+
+ // if segment lengths are not the same, it means two different locks
+ // So no conflict
+ if (p.second != std::get<4>(refLockRecord2)[i].second)
+ {
+ BMCWEB_LOG_DEBUG << "Segment lengths are not same";
+ BMCWEB_LOG_DEBUG << "Segment 1 length : " << p.second;
+ BMCWEB_LOG_DEBUG << "Segment 2 length : "
+ << std::get<4>(refLockRecord2)[i].second;
+ return false;
+ }
+
+ // compare segment data
+
+ for (uint32_t i = 0; i < p.second; i++)
+ {
+ // if the segment data is different , then the locks is on a
+ // different resource So no conflict between the lock records
+ if (!(checkByte(std::get<3>(refLockRecord1),
+ std::get<3>(refLockRecord2), i)))
+ {
+ return false;
+ }
+ }
+
+ ++i;
+ }
+ }
+
+ return false;
+}
+
+uint32_t Lock::generateTransactionId()
+{
+ ++transactionId;
+ return transactionId;
+}
+
+} // namespace ibm_mc_lock
+} // namespace crow
diff --git a/include/ibm/management_console_rest.hpp b/include/ibm/management_console_rest.hpp
index 225861d..a4dcf4a 100644
--- a/include/ibm/management_console_rest.hpp
+++ b/include/ibm/management_console_rest.hpp
@@ -7,10 +7,21 @@
#include <boost/container/flat_set.hpp>
#include <filesystem>
#include <fstream>
+#include <ibm/locks.hpp>
+#include <nlohmann/json.hpp>
#include <regex>
#include <sdbusplus/message/types.hpp>
+#include <utils/json_utils.hpp>
#define MAX_SAVE_AREA_FILESIZE 200000
+using SType = std::string;
+using SegmentFlags = std::vector<std::pair<std::string, uint32_t>>;
+using LockRequest = std::tuple<SType, SType, SType, uint64_t, SegmentFlags>;
+using LockRequests = std::vector<LockRequest>;
+using Rc = std::pair<bool, std::variant<uint32_t, LockRequest>>;
+using RcGetLockList = std::pair<
+ bool,
+ std::variant<std::string, std::vector<std::pair<uint32_t, LockRequests>>>>;
namespace crow
{
@@ -259,6 +270,142 @@
}
}
+void handleAcquireLockAPI(const crow::Request &req, crow::Response &res,
+ std::vector<nlohmann::json> body)
+{
+ LockRequests lockRequestStructure;
+ for (auto &element : body)
+ {
+ std::string lockType;
+ uint64_t resourceId;
+
+ SegmentFlags segInfo;
+ std::vector<nlohmann::json> segmentFlags;
+
+ if (!redfish::json_util::readJson(element, res, "LockType", lockType,
+ "ResourceID", resourceId,
+ "SegmentFlags", segmentFlags))
+ {
+ BMCWEB_LOG_DEBUG << "Not a Valid JSON";
+ res.result(boost::beast::http::status::bad_request);
+ res.end();
+ return;
+ }
+ BMCWEB_LOG_DEBUG << lockType;
+ BMCWEB_LOG_DEBUG << resourceId;
+
+ BMCWEB_LOG_DEBUG << "Segment Flags are present";
+
+ for (auto &e : segmentFlags)
+ {
+ std::string lockFlags;
+ uint32_t segmentLength;
+
+ if (!redfish::json_util::readJson(e, res, "LockFlag", lockFlags,
+ "SegmentLength", segmentLength))
+ {
+ res.result(boost::beast::http::status::bad_request);
+ res.end();
+ return;
+ }
+
+ BMCWEB_LOG_DEBUG << "Lockflag : " << lockFlags;
+ BMCWEB_LOG_DEBUG << "SegmentLength : " << segmentLength;
+
+ segInfo.push_back(std::make_pair(lockFlags, segmentLength));
+ }
+ lockRequestStructure.push_back(make_tuple(
+ req.session->uniqueId, "hmc-id", lockType, resourceId, segInfo));
+ }
+
+ // print lock request into journal
+
+ for (uint32_t i = 0; i < lockRequestStructure.size(); i++)
+ {
+ BMCWEB_LOG_DEBUG << std::get<0>(lockRequestStructure[i]);
+ BMCWEB_LOG_DEBUG << std::get<1>(lockRequestStructure[i]);
+ BMCWEB_LOG_DEBUG << std::get<2>(lockRequestStructure[i]);
+ BMCWEB_LOG_DEBUG << std::get<3>(lockRequestStructure[i]);
+
+ for (const auto &p : std::get<4>(lockRequestStructure[i]))
+ {
+ BMCWEB_LOG_DEBUG << p.first << ", " << p.second;
+ }
+ }
+
+ const LockRequests &t = lockRequestStructure;
+
+ auto varAcquireLock = crow::ibm_mc_lock::lockObject.acquireLock(t);
+
+ if (varAcquireLock.first)
+ {
+ // Either validity failure of there is a conflict with itself
+
+ auto validityStatus =
+ std::get<std::pair<bool, int>>(varAcquireLock.second);
+
+ if ((!validityStatus.first) && (validityStatus.second == 0))
+ {
+ BMCWEB_LOG_DEBUG << "Not a Valid record";
+ BMCWEB_LOG_DEBUG << "Bad json in request";
+ res.result(boost::beast::http::status::bad_request);
+ res.end();
+ return;
+ }
+ if (validityStatus.first && (validityStatus.second == 1))
+ {
+ BMCWEB_LOG_DEBUG << "There is a conflict within itself";
+ res.result(boost::beast::http::status::bad_request);
+ res.end();
+ return;
+ }
+ }
+ else
+ {
+ auto conflictStatus =
+ std::get<crow::ibm_mc_lock::Rc>(varAcquireLock.second);
+ if (!conflictStatus.first)
+ {
+ BMCWEB_LOG_DEBUG << "There is no conflict with the locktable";
+ res.result(boost::beast::http::status::ok);
+
+ auto var = std::get<uint32_t>(conflictStatus.second);
+ nlohmann::json returnJson;
+ returnJson["id"] = var;
+ res.jsonValue["TransactionID"] = var;
+ res.end();
+ return;
+ }
+ else
+ {
+ BMCWEB_LOG_DEBUG << "There is a conflict with the lock table";
+ res.result(boost::beast::http::status::conflict);
+ auto var = std::get<std::pair<uint32_t, LockRequest>>(
+ conflictStatus.second);
+ nlohmann::json returnJson, segments;
+ nlohmann::json myarray = nlohmann::json::array();
+ returnJson["TransactionID"] = var.first;
+ returnJson["SessionID"] = std::get<0>(var.second);
+ returnJson["HMCID"] = std::get<1>(var.second);
+ returnJson["LockType"] = std::get<2>(var.second);
+ returnJson["ResourceID"] = std::get<3>(var.second);
+
+ for (uint32_t i = 0; i < std::get<4>(var.second).size(); i++)
+ {
+ segments["LockFlag"] = std::get<4>(var.second)[i].first;
+ segments["SegmentLength"] = std::get<4>(var.second)[i].second;
+ myarray.push_back(segments);
+ }
+
+ returnJson["SegmentFlags"] = myarray;
+
+ res.jsonValue["Record"] = returnJson;
+ res.end();
+ return;
+ }
+ }
+}
+
template <typename... Middlewares> void requestRoutes(Crow<Middlewares...> &app)
{
@@ -306,6 +453,22 @@
[](const crow::Request &req, crow::Response &res) {
getLockServiceData(res);
});
+
+ BMCWEB_ROUTE(app, "/ibm/v1/HMC/LockService/Actions/LockService.AcquireLock")
+ .requires({"ConfigureComponents", "ConfigureManager"})
+ .methods("POST"_method)(
+ [](const crow::Request &req, crow::Response &res) {
+ std::vector<nlohmann::json> body;
+
+ if (!redfish::json_util::readJson(req, res, "Request", body))
+ {
+ BMCWEB_LOG_DEBUG << "Not a Valid JSON";
+ res.result(boost::beast::http::status::bad_request);
+ res.end();
+ return;
+ }
+ handleAcquireLockAPI(req, res, body);
+ });
}
} // namespace ibm_mc