Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions score/kvs/docs/requirements/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,17 @@ Component Requirements

The component shall create a snapshot each time data is stored.

.. comp_req:: Snapshot Explicit Creation
:id: comp_req__kvs__snapshot_explicit_creation
:reqtype: Functional
:security: NO
:safety: ASIL_B
:satisfies: feat_req__persistency__snapshot_create
:status: valid
:belongs_to: comp__persistency_kvs

The component shall create a snapshot in the first available slot when the snapshot_create function is explicitly called.

.. comp_req:: Snapshot Maximum Number
:id: comp_req__kvs__snapshot_max_num
:reqtype: Functional
Expand Down
229 changes: 146 additions & 83 deletions src/cpp/src/kvs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -593,119 +593,141 @@ score::ResultBlank Kvs::flush()
result = score::MakeUnexpected(ErrorCode::JsonGeneratorError);
}
else
{
/* Rotate Snapshots */
auto rotate_result = snapshot_rotate();
if (!rotate_result)
{
result = rotate_result;
}
else
{
/* Write JSON Data */
std::string buf = std::move(buf_res.value());
result = write_json_data(buf);
}
{
/* Write JSON Data */
std::string buf = std::move(buf_res.value());
result = write_json_data(buf);

}
}

return result;
}

/* Retrieve the snapshot count*/
score::Result<size_t> Kvs::snapshot_count() const
score::Result<std::size_t> Kvs::snapshot_count() const
{
score::Result<size_t> result = score::MakeUnexpected(ErrorCode::UnmappedError);
size_t count = 0;
bool error = false;
for (size_t idx = 0; idx < KVS_MAX_SNAPSHOTS; ++idx)
for (size_t idx = 1; idx <= KVS_MAX_SNAPSHOTS; ++idx)
{
const score::filesystem::Path fname = filename_prefix.Native() + "_" + to_string(idx) + ".json";
const auto fname_exists_res = filesystem->standard->Exists(fname);
if (fname_exists_res)
if (!fname_exists_res)
{
if (false == fname_exists_res.value())
{
break;
}
/* Filesystem error: cannot determine whether the slot exists */
return score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
else
if (fname_exists_res.value())
{
error = true;
break;
count++;
}
count = idx + 1;
/* Slot is empty: do nothing and continue to the next slot */
}
if (error)
return count;
}

score::Result<std::size_t> Kvs::snapshot_create()
{
score::Result<std::size_t> result = score::MakeUnexpected(ErrorCode::UnmappedError);
std::unique_lock<std::mutex> lock(kvs_mutex, std::try_to_lock);
if (lock.owns_lock())
{
result = score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
auto snapshot_count_res = snapshot_count();
if (!snapshot_count_res)
{
result = score::MakeUnexpected(static_cast<ErrorCode>(*snapshot_count_res.error()));
}
else
{
if (snapshot_count_res.value() >= snapshot_max_count())
{
result = score::MakeUnexpected(ErrorCode::QuotaExceeded);
}
else
{
auto first_free_slot_res = first_free_slot();
if (!first_free_slot_res)
{
result = score::MakeUnexpected(static_cast<ErrorCode>(*first_free_slot_res.error()));
}
else
{
const size_t new_snapshot_id = first_free_slot_res.value();
const score::filesystem::Path src_json{filename_prefix.Native() + "_0.json"};
const score::filesystem::Path src_hash{filename_prefix.Native() + "_0.hash"};
const score::filesystem::Path dst_json{filename_prefix.Native() + "_" + to_string(new_snapshot_id) + ".json"};
const score::filesystem::Path dst_hash{filename_prefix.Native() + "_" + to_string(new_snapshot_id) + ".hash"};

const auto copy_json_res = filesystem->standard->CopyFile(src_json, dst_json);
if (!copy_json_res)
{
logger->LogError() << "Failed to copy snapshot JSON file to '" << dst_json << "'";
result = score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
else
{
const auto copy_hash_res = filesystem->standard->CopyFile(src_hash, dst_hash);
if (!copy_hash_res)
{
logger->LogError() << "Failed to copy snapshot hash file to '" << dst_hash << "'";
result = score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
else
{
result = new_snapshot_id;
}
}
}
}
}
}
else
{
result = count;
result = score::MakeUnexpected(ErrorCode::MutexLockFailed);
}

return result;
}

/* Retrieve the max snapshot count*/
size_t Kvs::snapshot_max_count() const
std::size_t Kvs::snapshot_max_count() const
{
return KVS_MAX_SNAPSHOTS;
}

/* Rotate Snapshots */
score::ResultBlank Kvs::snapshot_rotate()
/* Restore the key-value store from a snapshot*/
score::ResultBlank Kvs::snapshot_restore(const SnapshotId& snapshot_id)
{
score::ResultBlank result = score::MakeUnexpected(ErrorCode::UnmappedError);
std::unique_lock<std::mutex> lock(kvs_mutex, std::try_to_lock);
if (lock.owns_lock())
{
bool error = false;
for (size_t idx = KVS_MAX_SNAPSHOTS; idx > 0; --idx)
auto snapshot_count_res = snapshot_count();
if (!snapshot_count_res)
{
score::filesystem::Path hash_old = filename_prefix.Native() + "_" + to_string(idx - 1) + ".hash";
score::filesystem::Path hash_new = filename_prefix.Native() + "_" + to_string(idx) + ".hash";
score::filesystem::Path snap_old = filename_prefix.Native() + "_" + to_string(idx - 1) + ".json";
score::filesystem::Path snap_new = filename_prefix.Native() + "_" + to_string(idx) + ".json";

logger->LogInfo() << "rotating: " << snap_old << " -> " << snap_new;
/* Rename hash */
int32_t hash_rename = std::rename(hash_old.CStr(), hash_new.CStr());
if (0 != hash_rename)
result = score::MakeUnexpected(static_cast<ErrorCode>(*snapshot_count_res.error()));
}
else
{
if (snapshot_count_res.value() == 0 || snapshot_id.id >= snapshot_max_count())
{
if (errno != ENOENT)
{
error = true;
logger->LogError() << "error: could not rename hash file " << snap_old << ". Rename Errorcode "
<< errno;
result = score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
result = score::MakeUnexpected(ErrorCode::InvalidSnapshotId);
}
if (!error)
else
{
/* Rename snapshot */
int32_t snap_rename = std::rename(snap_old.CStr(), snap_new.CStr());
if (0 != snap_rename)
score::filesystem::Path restore_path = filename_prefix.Native() + "_" + to_string(snapshot_id.id + 1);
auto data_res = open_json(restore_path, OpenJsonNeedFile::Required);
if (!data_res)
{
if (errno != ENOENT)
{
error = true;
logger->LogError()
<< "error: could not rename snapshot file " << snap_old << ". Rename Errorcode " << errno;
result = score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
result = score::MakeUnexpected(static_cast<ErrorCode>(*data_res.error()));
}
else
{
kvs = std::move(data_res.value());
result = score::ResultBlank{};
}
}
if (error)
{
break;
}
}
if (!error)
{
result = score::ResultBlank{};
}
}
else
{
Expand All @@ -715,41 +737,61 @@ score::ResultBlank Kvs::snapshot_rotate()
return result;
}

/* Restore the key-value store from a snapshot*/
score::ResultBlank Kvs::snapshot_restore(const SnapshotId& snapshot_id)
/* Delete a snapshot*/
score::ResultBlank Kvs::snapshot_delete(const SnapshotId& snapshot_id)
{
score::ResultBlank result = score::MakeUnexpected(ErrorCode::UnmappedError);
std::unique_lock<std::mutex> lock(kvs_mutex, std::try_to_lock);
if (lock.owns_lock())
{
{
auto snapshot_count_res = snapshot_count();
if (!snapshot_count_res)
{
result = score::MakeUnexpected(static_cast<ErrorCode>(*snapshot_count_res.error()));
return score::MakeUnexpected(static_cast<ErrorCode>(*snapshot_count_res.error()));
}

const std::size_t count = snapshot_count_res.value();
if (count == 0 || snapshot_id.id >= snapshot_max_count())
{
result = score::MakeUnexpected(ErrorCode::InvalidSnapshotId);
}
else
{
/* Fail if the snapshot ID is the current KVS */
if (0 == snapshot_id.id)
/* Delete the target snapshot files (snapshot files are 1-indexed: _1.json, _2.json, ...) */
const score::filesystem::Path json_path{filename_prefix.Native() + "_" + to_string(snapshot_id.id + 1) + ".json"};
const score::filesystem::Path hash_path{filename_prefix.Native() + "_" + to_string(snapshot_id.id + 1) + ".hash"};

const auto json_exists_res = filesystem->standard->Exists(json_path);
if (!json_exists_res)
{
result = score::MakeUnexpected(ErrorCode::InvalidSnapshotId);
logger->LogError() << "Failed to check existence of snapshot JSON file '" << json_path << "'";
result = score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
else if (snapshot_count_res.value() < snapshot_id.id)
else if (!json_exists_res.value())
{
logger->LogError() << "Snapshot JSON file does not exist: '" << json_path << "'";
result = score::MakeUnexpected(ErrorCode::InvalidSnapshotId);
}
else
{
score::filesystem::Path restore_path = filename_prefix.Native() + "_" + to_string(snapshot_id.id);
auto data_res = open_json(restore_path, OpenJsonNeedFile::Required);
if (!data_res)
const auto remove_json_res = filesystem->standard->Remove(json_path);
if (!remove_json_res)
{
result = score::MakeUnexpected(static_cast<ErrorCode>(*data_res.error()));
logger->LogError() << "Failed to delete snapshot JSON file '" << json_path << "'";
result = score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
else
{
kvs = std::move(data_res.value());
result = score::ResultBlank{};
const auto remove_hash_res = filesystem->standard->Remove(hash_path);
if (!remove_hash_res)
{
logger->LogError() << "Failed to delete snapshot hash file '" << hash_path << "'";
result = score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
else
{
result = score::ResultBlank{};
}
}
}
}
Expand All @@ -762,6 +804,27 @@ score::ResultBlank Kvs::snapshot_restore(const SnapshotId& snapshot_id)
return result;
}

/* Returns the index (1-based) of the first snapshot slot that is free.
Checks _1, _2, _3 (up to KVS_MAX_SNAPSHOTS) and returns the first index
whose .json file does not exist. Returns OutOfStorageSpace if all slots are occupied. */
score::Result<std::size_t> Kvs::first_free_slot() const
{
for (size_t idx = 1U; idx <= KVS_MAX_SNAPSHOTS; ++idx)
{
const score::filesystem::Path fname = filename_prefix.Native() + "_" + to_string(idx) + ".json";
const auto exists_res = filesystem->standard->Exists(fname);
if (!exists_res)
{
return score::MakeUnexpected(ErrorCode::PhysicalStorageFailure);
}
if (!exists_res.value())
{
return idx; /* First free slot found */
}
}
return score::MakeUnexpected(ErrorCode::OutOfStorageSpace);
}

/* Get the filename for a snapshot*/
score::Result<score::filesystem::Path> Kvs::get_kvs_filename(const SnapshotId& snapshot_id) const
{
Expand Down
35 changes: 31 additions & 4 deletions src/cpp/src/kvs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -300,17 +300,44 @@ class Kvs final
* - On success: The total count of snapshots as a size_t value.
* - On failure: Returns an ErrorCode describing the error.
*/
score::Result<size_t> snapshot_count() const;
score::Result<std::size_t> snapshot_count() const;

/**
* @brief Retrieves the maximum number of snapshots that can be stored.
*
* This function returns the upper limit on the number of snapshots
* that the key-value store can maintain at any given time.
*
* @return The maximum count of snapshots as a size_t value.
* @return The maximum count of snapshots as a std::size_t value.
*/
size_t snapshot_max_count() const;
std::size_t snapshot_max_count() const;

/**
* @brief Creates a new snapshot of the current state of the key-value store.
*
* This function captures the current state of the key-value store and saves it
* as a snapshot. The snapshot can later be used to restore the key-value store
* to this exact state using the snapshot_restore function.
*
* @return score::ResultBlank<std::size_t>
* - On success: The identifier of the created snapshot as a std::size_t value.
* - On failure: An error code describing the reason for the failure.
*/
score::Result<std::size_t> snapshot_create();

/**
* @brief Deletes a specified snapshot from the key-value store.
*
* This function removes the snapshot identified by the given snapshot ID
* from the key-value store. Once deleted, the snapshot can no longer be
* used for restoration purposes. This operation is irreversible.
*
* @param snapshot_id The identifier of the snapshot to be deleted.
* @return score::ResultBlank
* - On success: An empty score::Result indicating the snapshot was deleted successfully.
* - On failure: An error code describing the reason for the failure.
*/
score::ResultBlank snapshot_delete(const SnapshotId& snapshot_id);

/**
* @brief Restores the state of the key-value store from a specified snapshot.
Expand Down Expand Up @@ -377,7 +404,7 @@ class Kvs final
std::unique_ptr<score::mw::log::Logger> logger;

/* Private Methods */
score::ResultBlank snapshot_rotate();
score::Result<std::size_t> first_free_slot() const;
score::Result<std::unordered_map<std::string, KvsValue>> parse_json_data(const std::string& data);
score::Result<std::unordered_map<std::string, KvsValue>> open_json(const score::filesystem::Path& prefix,
OpenJsonNeedFile need_file);
Expand Down
Loading
Loading