blob: 3bb82b9479e1275b86a874a9983bb180ab884f85 [file] [log] [blame]
#pragma once
#include "ibm/utils.hpp"
#include "logging.hpp"
#include <boost/container/flat_map.hpp>
#include <boost/endian/conversion.hpp>
#include <nlohmann/json.hpp>
#include <filesystem>
#include <fstream>
#include <variant>
namespace crow
{
namespace ibm_mc_lock
{
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, std::pair<uint32_t, LockRequest>>;
using RcGetLockList =
std::variant<std::string, std::vector<std::pair<uint32_t, LockRequests>>>;
using ListOfTransactionIds = std::vector<uint32_t>;
using RcAcquireLock = std::pair<bool, std::variant<Rc, std::pair<bool, int>>>;
using RcReleaseLockApi = std::pair<bool, std::variant<bool, RcRelaseLock>>;
using SessionFlags = std::pair<SType, SType>;
using ListOfSessionIds = std::vector<std::string>;
class Lock
{
uint32_t transactionId = 0;
boost::container::flat_map<uint32_t, LockRequests> lockTable;
protected:
/*
* This function implements the logic for validating an incoming
* lock request/requests.
*
* Returns : True (if Valid)
* Returns : False (if not a Valid lock request)
*/
virtual bool isValidLockRequest(const LockRequest& refLockRecord);
/*
* This function implements the logic of checking if the incoming
* multi-lock request is not having conflicting requirements.
*
* Returns : True (if conflicting)
* Returns : False (if not conflicting)
*/
virtual bool isConflictRequest(const LockRequests& refLockRequestStructure);
/*
* 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)
*/
virtual bool isConflictRecord(const LockRequest& refLockRecord1,
const LockRequest& refLockRecord2);
/*
* This function implements the logic of checking the conflicting
* locks from a incoming single/multi lock requests with the already
* existing lock request in the lock table.
*
*/
virtual Rc isConflictWithTable(const LockRequests& refLockRequestStructure);
/*
* This function implements the logic of checking the ownership of the
* lock from the releaselock request.
*
* Returns : True (if the requesting HMC & Session owns the lock(s))
* Returns : False (if the request HMC or Session does not own the lock(s))
*/
virtual RcRelaseLock isItMyLock(const ListOfTransactionIds& refRids,
const SessionFlags& ids);
/*
* This function validates the the list of transactionID's and returns false
* if the transaction ID is not valid & not present in the lock table
*/
virtual bool validateRids(const ListOfTransactionIds& refRids);
/*
* This function releases the locks that are already obtained by the
* requesting Management console.
*/
void releaseLock(const ListOfTransactionIds& refRids);
Lock()
{
transactionId = lockTable.empty() ? 0 : prev(lockTable.end())->first;
}
/*
* This function implements the algorithm for checking the respective
* bytes of the resource id based on the lock management algorithm.
*/
static bool checkByte(uint64_t resourceId1, uint64_t resourceId2,
uint32_t position);
/*
* 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.
*/
virtual uint32_t generateTransactionId();
public:
/*
* Explicitly deleted copy and move constructors
*/
Lock(const Lock&) = delete;
Lock(Lock&&) = delete;
Lock& operator=(const Lock&) = delete;
Lock& operator=(Lock&&) = delete;
/*
* This function implements the logic for acquiring a lock on a
* resource if the incoming request is legitimate without any
* conflicting requirements & without any conflicting requirement
* with the existing locks in the lock table.
*
*/
RcAcquireLock acquireLock(const LockRequests& lockRequestStructure);
/*
* This function implements the logic for releasing the lock that are
* owned by a management console session.
*
* The locks can be released by two ways
* - Using list of transaction ID's
* - Using a Session ID
*
* Client can choose either of the ways by using `Type` JSON key.
*
*/
RcReleaseLockApi releaseLock(const ListOfTransactionIds& p,
const SessionFlags& ids);
/*
* This function implements the logic for getting the list of locks obtained
* by a particular management console.
*/
RcGetLockList getLockList(const ListOfSessionIds& listSessionId);
/*
* This function is releases all the locks obtained by a particular
* session.
*/
void releaseLock(const std::string& sessionId);
static Lock& getInstance()
{
static Lock lockObject;
return lockObject;
}
virtual ~Lock() = default;
};
inline RcGetLockList Lock::getLockList(const ListOfSessionIds& listSessionId)
{
std::vector<std::pair<uint32_t, LockRequests>> lockList{};
if (!lockTable.empty())
{
for (const auto& i : listSessionId)
{
auto it = lockTable.begin();
while (it != lockTable.end())
{
// Check if session id of this entry matches with session id
// given
if (std::get<0>(it->second[0]) == i)
{
BMCWEB_LOG_DEBUG("Session id is found in the locktable");
// Push the whole lock record into a vector for returning
// the json
lockList.emplace_back(it->first, it->second);
}
// Go to next entry in map
it++;
}
}
}
// we may have found at least one entry with the given session id
// return the json list of lock records pertaining to the given
// session id, or send an empty list if lock table is empty
return {lockList};
}
inline RcReleaseLockApi Lock::releaseLock(const ListOfTransactionIds& p,
const SessionFlags& ids)
{
bool status = validateRids(p);
if (!status)
{
// Validation of rids failed
BMCWEB_LOG_ERROR("releaseLock: Contains invalid request id");
return std::make_pair(false, status);
}
// Validation passed, check if all the locks are owned by the
// requesting HMC
auto status2 = isItMyLock(p, ids);
if (!status2.first)
{
return std::make_pair(false, status2);
}
return std::make_pair(true, status2);
}
inline RcAcquireLock Lock::acquireLock(const LockRequests& lockRequestStructure)
{
// validate the lock request
for (const 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));
}
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);
}
inline void Lock::releaseLock(const std::string& sessionId)
{
if (!lockTable.empty())
{
auto it = lockTable.begin();
while (it != lockTable.end())
{
if (!it->second.empty())
{
// Check if session id of this entry matches with session id
// given
if (std::get<0>(it->second[0]) == sessionId)
{
BMCWEB_LOG_DEBUG("Remove the lock from the locktable "
"having sessionID={}",
sessionId);
BMCWEB_LOG_DEBUG("TransactionID ={}", it->first);
it = lockTable.erase(it);
}
else
{
it++;
}
}
}
}
}
inline RcRelaseLock Lock::isItMyLock(const ListOfTransactionIds& refRids,
const SessionFlags& ids)
{
for (const auto& id : refRids)
{
// Just need to compare the client id of the first lock records in the
// complete lock row(in the map), because the rest of the lock records
// would have the same client id
std::string expectedClientId = std::get<1>(lockTable[id][0]);
std::string expectedSessionId = std::get<0>(lockTable[id][0]);
if ((expectedClientId == ids.first) &&
(expectedSessionId == ids.second))
{
// It is owned by the currently request hmc
BMCWEB_LOG_DEBUG("Lock is owned by the current hmc");
// remove the lock
if (lockTable.erase(id) != 0U)
{
BMCWEB_LOG_DEBUG("Removing the locks with transaction ID : {}",
id);
}
else
{
BMCWEB_LOG_ERROR("Removing the locks from the lock table "
"failed, transaction ID: {}",
id);
}
}
else
{
BMCWEB_LOG_DEBUG("Lock is not owned by the current hmc");
return std::make_pair(false, std::make_pair(id, lockTable[id][0]));
}
}
return std::make_pair(true, std::make_pair(0, LockRequest()));
}
inline bool Lock::validateRids(const ListOfTransactionIds& refRids)
{
for (const auto& id : refRids)
{
auto search = lockTable.find(id);
if (search != lockTable.end())
{
BMCWEB_LOG_DEBUG("Valid Transaction id");
// continue for the next rid
}
else
{
BMCWEB_LOG_ERROR("validateRids: At least 1 inValid Request id: {}",
id);
return false;
}
}
return true;
}
inline bool Lock::isValidLockRequest(const LockRequest& refLockRecord)
{
// validate the locktype
if (!((std::get<2>(refLockRecord) == "Read" ||
(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 Segments Failed");
BMCWEB_LOG_DEBUG("Number of Segments provided : {}",
std::get<4>(refLockRecord).size());
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 (!((p.first == "LockSame" || (p.first == "LockAll") ||
(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 ((p.first == "LockSame" || (p.first == "LockAll")))
{
++lockFlag;
if (lockFlag >= 2)
{
return false;
}
}
}
return true;
}
inline Rc Lock::isConflictWithTable(const LockRequests& refLockRequestStructure)
{
if (lockTable.empty())
{
uint32_t thisTransactionId = generateTransactionId();
BMCWEB_LOG_DEBUG("{}", thisTransactionId);
// 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(thisTransactionId, refLockRequestStructure);
return std::make_pair(false, thisTransactionId);
}
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);
}
inline 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;
}
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 comparison, 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 comparison would be to check for the respective
// bytes in the resourceid based on the segment length.
inline bool Lock::checkByte(uint64_t resourceId1, uint64_t resourceId2,
uint32_t position)
{
if (position >= sizeof(resourceId1))
{
return false;
}
uint8_t pPosition = 0;
uint8_t qPosition = 0;
pPosition = 0xFF & (resourceId1 >> (8 * position));
qPosition = 0xFF & (resourceId2 >> (8 * position));
return pPosition == qPosition;
}
inline bool Lock::isConflictRecord(const LockRequest& refLockRecord1,
const LockRequest& refLockRecord2)
{
// No conflict if both are read locks
if (std::get<2>(refLockRecord1) == "Read" &&
std::get<2>(refLockRecord2) == "Read")
{
BMCWEB_LOG_DEBUG("Both are read locks, no conflict");
return false;
}
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 (p.first == "LockAll" ||
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 ((p.first == "LockSame" ||
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 j = 0; j < p.second; j++)
{
// if the segment data is different, then the locks is on a
// different resource so no conflict between the lock
// records.
// BMC is little endian, but the resourceID is formed by
// the Management Console in such a way that, the first byte
// from the MSB Position corresponds to the First Segment
// data. Therefore we need to convert the incoming
// resourceID into Big Endian before processing further.
if (!(checkByte(
boost::endian::endian_reverse(std::get<3>(refLockRecord1)),
boost::endian::endian_reverse(std::get<3>(refLockRecord2)),
j)))
{
return false;
}
}
++i;
}
return true;
}
inline uint32_t Lock::generateTransactionId()
{
++transactionId;
return transactionId;
}
} // namespace ibm_mc_lock
} // namespace crow