mirror of
https://github.com/nestriness/cdc-file-transfer.git
synced 2026-01-30 10:35:37 +02:00
Releasing the former Stadia file transfer tools
The tools allow efficient and fast synchronization of large directory trees from a Windows workstation to a Linux target machine. cdc_rsync* support efficient copy of files by using content-defined chunking (CDC) to identify chunks within files that can be reused. asset_stream_manager + cdc_fuse_fs support efficient streaming of a local directory to a remote virtual file system based on FUSE. It also employs CDC to identify and reuse unchanged data chunks.
This commit is contained in:
3
cdc_fuse_fs/.gitignore
vendored
Normal file
3
cdc_fuse_fs/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
GGP/*
|
||||
*.log
|
||||
*.user
|
||||
136
cdc_fuse_fs/BUILD
Normal file
136
cdc_fuse_fs/BUILD
Normal file
@@ -0,0 +1,136 @@
|
||||
package(default_visibility = ["//:__subpackages__"])
|
||||
|
||||
cc_binary(
|
||||
name = "cdc_fuse_fs",
|
||||
srcs = ["main.cc"],
|
||||
deps = [
|
||||
":cdc_fuse_fs_lib",
|
||||
":constants",
|
||||
"//absl_helper:jedec_size_flag",
|
||||
"//common:gamelet_component",
|
||||
"//common:log",
|
||||
"//data_store:data_provider",
|
||||
"//data_store:disk_data_store",
|
||||
"//data_store:grpc_reader",
|
||||
"@com_google_absl//absl/flags:parse",
|
||||
],
|
||||
)
|
||||
|
||||
# Dependencies for cdc_fuse_fs_lib, except for FUSE.
|
||||
cdc_fuse_fs_lib_shared_deps = [
|
||||
":asset",
|
||||
":asset_stream_client",
|
||||
":config_stream_client",
|
||||
"//common:log",
|
||||
"//common:path",
|
||||
"//common:platform",
|
||||
"//common:util",
|
||||
"//common:threadpool",
|
||||
"@com_github_jsoncpp//:jsoncpp",
|
||||
]
|
||||
|
||||
cc_library(
|
||||
name = "cdc_fuse_fs_lib",
|
||||
srcs = ["cdc_fuse_fs.cc"],
|
||||
hdrs = ["cdc_fuse_fs.h"],
|
||||
target_compatible_with = ["@platforms//os:linux"],
|
||||
deps = cdc_fuse_fs_lib_shared_deps + ["@com_github_fuse//:fuse_shared"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "cdc_fuse_fs_lib_mocked",
|
||||
srcs = ["cdc_fuse_fs.cc"],
|
||||
hdrs = ["cdc_fuse_fs.h"],
|
||||
copts = ["-DUSE_MOCK_LIBFUSE=1"],
|
||||
deps = cdc_fuse_fs_lib_shared_deps + [":mock_libfuse"],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "cdc_fuse_fs_test",
|
||||
srcs = ["cdc_fuse_fs_test.cc"],
|
||||
deps = [
|
||||
":cdc_fuse_fs_lib_mocked",
|
||||
"//common:status_test_macros",
|
||||
"//data_store",
|
||||
"//data_store:mem_data_store",
|
||||
"//manifest:fake_manifest_builder",
|
||||
"//manifest:manifest_builder",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "mock_libfuse",
|
||||
srcs = ["mock_libfuse.cc"],
|
||||
hdrs = ["mock_libfuse.h"],
|
||||
deps = ["//common:platform"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "constants",
|
||||
hdrs = ["constants.h"],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "asset_stream_client",
|
||||
srcs = ["asset_stream_client.cc"],
|
||||
hdrs = ["asset_stream_client.h"],
|
||||
deps = [
|
||||
"//common:log",
|
||||
"//common:status_macros",
|
||||
"//common:stopwatch",
|
||||
"//manifest:manifest_proto_defs",
|
||||
"//proto:asset_stream_service_grpc_proto",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "asset",
|
||||
srcs = ["asset.cc"],
|
||||
hdrs = ["asset.h"],
|
||||
deps = [
|
||||
"//common:buffer",
|
||||
"//common:status",
|
||||
"//data_store",
|
||||
"//manifest:content_id",
|
||||
"@com_google_absl//absl/status",
|
||||
"@com_google_absl//absl/status:statusor",
|
||||
"@com_google_absl//absl/strings",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
cc_test(
|
||||
name = "asset_test",
|
||||
srcs = ["asset_test.cc"],
|
||||
deps = [
|
||||
":asset",
|
||||
"//common:path",
|
||||
"//common:platform",
|
||||
"//common:status_test_macros",
|
||||
"//data_store:mem_data_store",
|
||||
"@com_google_googletest//:gtest",
|
||||
"@com_google_googletest//:gtest_main",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "config_stream_client",
|
||||
srcs = ["config_stream_client.cc"],
|
||||
hdrs = ["config_stream_client.h"],
|
||||
deps = [
|
||||
"//common:grpc_status",
|
||||
"//common:log",
|
||||
"//manifest:content_id",
|
||||
"//proto:asset_stream_service_grpc_proto",
|
||||
"@com_google_absl//absl/status",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all_test_sources",
|
||||
srcs = glob(["*_test.cc"]),
|
||||
)
|
||||
520
cdc_fuse_fs/asset.cc
Normal file
520
cdc_fuse_fs/asset.cc
Normal file
@@ -0,0 +1,520 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cdc_fuse_fs/asset.h"
|
||||
|
||||
#include "common/buffer.h"
|
||||
#include "common/status.h"
|
||||
#include "data_store/data_store_reader.h"
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
Asset::Asset() = default;
|
||||
|
||||
Asset::~Asset() = default;
|
||||
|
||||
void Asset::Initialize(ino_t parent_ino, DataStoreReader* data_store_reader,
|
||||
const AssetProto* proto) {
|
||||
parent_ino_ = parent_ino;
|
||||
|
||||
assert(!data_store_reader_ && data_store_reader);
|
||||
data_store_reader_ = data_store_reader;
|
||||
|
||||
assert(!proto_ && proto);
|
||||
proto_ = proto;
|
||||
|
||||
// Create a lookup for the direct assets, if any.
|
||||
// Lock the mutex for convenience, it's not strictly necessary here as no
|
||||
// other thread has access to this object.
|
||||
absl::WriterMutexLock lock(&mutex_);
|
||||
UpdateProtoLookup(proto_->dir_assets());
|
||||
}
|
||||
|
||||
absl::StatusOr<std::vector<const AssetProto*>> Asset::GetAllChildProtos() {
|
||||
mutex_.AssertNotHeld();
|
||||
assert(proto_);
|
||||
|
||||
if (proto_->type() != AssetProto::DIRECTORY) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Asset '%s' is not a directory asset", proto_->name()));
|
||||
}
|
||||
|
||||
// Fetch all indirect dir asset lists.
|
||||
for (;;) {
|
||||
bool list_was_fetched;
|
||||
ASSIGN_OR_RETURN(list_was_fetched, FetchNextDirAssetList(),
|
||||
"Failed to fetch directory assets");
|
||||
if (!list_was_fetched) break;
|
||||
}
|
||||
return GetLoadedChildProtos();
|
||||
}
|
||||
|
||||
std::vector<const AssetProto*> Asset::GetLoadedChildProtos() const {
|
||||
absl::ReaderMutexLock read_lock(&mutex_);
|
||||
|
||||
// Push all directory asset protos to a vector.
|
||||
std::vector<const AssetProto*> protos;
|
||||
protos.reserve(proto_lookup_.size());
|
||||
for (const std::pair<const absl::string_view, const AssetProto*>& kv :
|
||||
proto_lookup_) {
|
||||
protos.push_back(kv.second);
|
||||
}
|
||||
return protos;
|
||||
}
|
||||
|
||||
absl::StatusOr<const AssetProto*> Asset::Lookup(const char* name) {
|
||||
mutex_.AssertNotHeld();
|
||||
assert(proto_);
|
||||
if (proto_->type() != AssetProto::DIRECTORY) {
|
||||
return absl::InvalidArgumentError(
|
||||
absl::StrFormat("Asset '%s' is not a directory asset", proto_->name()));
|
||||
}
|
||||
|
||||
for (;;) {
|
||||
{
|
||||
absl::ReaderMutexLock read_lock(&mutex_);
|
||||
|
||||
// Check if we already have the asset.
|
||||
std::unordered_map<absl::string_view, const AssetProto*>::iterator it =
|
||||
proto_lookup_.find(name);
|
||||
if (it != proto_lookup_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch one more indirect asset list.
|
||||
bool list_was_fetched;
|
||||
ASSIGN_OR_RETURN(list_was_fetched, FetchNextDirAssetList(),
|
||||
"Failed to fetch directory assets");
|
||||
if (!list_was_fetched) {
|
||||
// All lists were fetched, but asset still wasn't found.
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
absl::StatusOr<uint64_t> Asset::Read(uint64_t offset, void* data,
|
||||
uint64_t size) {
|
||||
mutex_.AssertNotHeld();
|
||||
assert(proto_);
|
||||
if (proto_->type() != AssetProto::FILE)
|
||||
return absl::InvalidArgumentError("Not a file asset");
|
||||
|
||||
if (size == 0) return 0;
|
||||
|
||||
// Find a chunk list such that list offset <= offset < next list offset.
|
||||
int list_idx = FindChunkList(offset);
|
||||
const RepeatedChunkRefProto* chunk_refs;
|
||||
ASSIGN_OR_RETURN(chunk_refs, GetChunkRefList(list_idx),
|
||||
"Failed to fetch indirect chunk list %i", list_idx);
|
||||
uint64_t chunk_list_offset = ChunkListOffset(list_idx);
|
||||
if (!chunk_refs) return 0; // Out of bounds.
|
||||
|
||||
// Find a chunk such that chunk offset <= offset < next chunk offset.
|
||||
int chunk_idx = FindChunk(*chunk_refs, chunk_list_offset, offset);
|
||||
if (chunk_idx < 0 || chunk_idx >= chunk_refs->size()) {
|
||||
// Data is malformed, e.g. empty chunk list with non-zero file size.
|
||||
return MakeStatus(
|
||||
"Invalid chunk ref list %i. Found chunk index %i not in [0, %u).",
|
||||
list_idx, chunk_idx, chunk_refs->size());
|
||||
}
|
||||
|
||||
uint64_t data_bytes_left = size;
|
||||
uint64_t prefetch_bytes_left = data_store_reader_->PrefetchSize(size);
|
||||
// Collect the chunk IDs required to satisfy the read request.
|
||||
ChunkTransferList chunks;
|
||||
while (chunk_refs) {
|
||||
const ChunkRefProto& chunk_ref = chunk_refs->at(chunk_idx);
|
||||
|
||||
// Figure out how much data we have to read from the current chunk.
|
||||
uint64_t chunk_absolute_offset = chunk_list_offset + chunk_ref.offset();
|
||||
uint64_t chunk_offset =
|
||||
offset > chunk_absolute_offset ? offset - chunk_absolute_offset : 0;
|
||||
uint64_t chunk_size = ChunkSize(list_idx, chunk_idx, chunk_refs);
|
||||
assert(chunk_size >= chunk_offset);
|
||||
uint64_t bytes_to_read =
|
||||
std::min<uint64_t>(chunk_size - chunk_offset, data_bytes_left);
|
||||
uint64_t bytes_to_prefetch =
|
||||
std::min<uint64_t>(chunk_size - chunk_offset, prefetch_bytes_left);
|
||||
|
||||
// Enqueue a chunk transfer task.
|
||||
chunks.emplace_back(chunk_ref.chunk_id(), chunk_offset,
|
||||
bytes_to_read ? data : nullptr, bytes_to_read);
|
||||
data = static_cast<char*>(data) + bytes_to_read;
|
||||
data_bytes_left =
|
||||
data_bytes_left > bytes_to_read ? data_bytes_left - bytes_to_read : 0;
|
||||
prefetch_bytes_left -= bytes_to_prefetch;
|
||||
offset += bytes_to_prefetch;
|
||||
|
||||
// If we request enough data, we are done.
|
||||
if (!prefetch_bytes_left) break;
|
||||
|
||||
// Otherwise find next chunk.
|
||||
++chunk_idx;
|
||||
while (chunk_idx >= chunk_refs->size()) {
|
||||
// Go to next list.
|
||||
chunk_idx = 0;
|
||||
++list_idx;
|
||||
ASSIGN_OR_RETURN(chunk_refs, GetChunkRefList(list_idx),
|
||||
"Failed to fetch indirect chunk list %i", list_idx);
|
||||
chunk_list_offset = ChunkListOffset(list_idx);
|
||||
if (!chunk_refs) {
|
||||
// Out of bounds. If we're not at the file size now, it's an error.
|
||||
if (offset != proto_->file_size()) {
|
||||
return MakeStatus(
|
||||
"Read error at position %u. Expected to be at file size %u.",
|
||||
offset, proto_->file_size());
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (chunk_refs) {
|
||||
// We should be exactly at a chunk boundary now.
|
||||
uint64_t chunk_rel_offset = chunk_refs->at(chunk_idx).offset();
|
||||
if (offset != chunk_list_offset + chunk_rel_offset) {
|
||||
return MakeStatus("Unexpected chunk offset %u, expected %u + %u = %u",
|
||||
offset, chunk_list_offset, chunk_rel_offset,
|
||||
chunk_list_offset + chunk_rel_offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Read all data.
|
||||
absl::Status status = data_store_reader_->Get(&chunks);
|
||||
if (!status.ok() || !chunks.ReadDone()) {
|
||||
std::string msg = absl::StrFormat(
|
||||
"Failed to fetch chunk(s) [%s] for file '%s', offset %u, size %u",
|
||||
chunks.ToHexString(
|
||||
[](auto const& chunk) { return chunk.size && !chunk.done; }),
|
||||
proto_->name(), offset, size);
|
||||
return status.ok() ? absl::DataLossError(msg)
|
||||
: WrapStatus(status, "%s", msg);
|
||||
}
|
||||
return size - data_bytes_left;
|
||||
}
|
||||
|
||||
size_t Asset::GetNumFetchedFileChunkListsForTesting() {
|
||||
mutex_.AssertNotHeld();
|
||||
absl::ReaderMutexLock read_lock(&mutex_);
|
||||
|
||||
// In contrast to |dir_asset_lists_|, |file_chunk_lists_| might be fetched
|
||||
// out-of-order, e.g. if someone tried to read the end of the file.
|
||||
// Unfetched lists are nullptrs.
|
||||
int num_fetched = 0;
|
||||
for (const std::unique_ptr<ChunkListProto>& list : file_chunk_lists_) {
|
||||
if (list) {
|
||||
++num_fetched;
|
||||
}
|
||||
}
|
||||
return num_fetched;
|
||||
}
|
||||
|
||||
size_t Asset::GetNumFetchedDirAssetsListsForTesting() {
|
||||
mutex_.AssertNotHeld();
|
||||
absl::ReaderMutexLock read_lock(&mutex_);
|
||||
|
||||
return dir_asset_lists_.size();
|
||||
}
|
||||
|
||||
void Asset::UpdateProto(const AssetProto* proto) {
|
||||
absl::WriterMutexLock write_lock(&mutex_);
|
||||
proto_lookup_.clear();
|
||||
file_chunk_lists_.clear();
|
||||
dir_asset_lists_.clear();
|
||||
proto_ = proto;
|
||||
if (proto_) {
|
||||
UpdateProtoLookup(proto_->dir_assets());
|
||||
}
|
||||
}
|
||||
|
||||
bool Asset::IsConsistent(std::string* warning) const {
|
||||
assert(proto_ && warning);
|
||||
absl::ReaderMutexLock read_lock(&mutex_);
|
||||
switch (proto_->type()) {
|
||||
case AssetProto::FILE:
|
||||
if (!proto_lookup_.empty() || !proto_->dir_assets().empty() ||
|
||||
!proto_->dir_indirect_assets().empty()) {
|
||||
*warning = "File asset contains sub-assets";
|
||||
return false;
|
||||
}
|
||||
if (!proto_->symlink_target().empty()) {
|
||||
*warning = "File asset contains a symlink";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case AssetProto::DIRECTORY:
|
||||
if (!proto_->file_chunks().empty() || !file_chunk_lists_.empty() ||
|
||||
!proto_->file_indirect_chunks().empty()) {
|
||||
*warning = "Directory asset contains file chunks";
|
||||
return false;
|
||||
}
|
||||
if (!proto_->symlink_target().empty()) {
|
||||
*warning = "Directory asset contains a symlink";
|
||||
return false;
|
||||
}
|
||||
if (proto_->file_size() > 0) {
|
||||
*warning = "File size is defined for a directory asset";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case AssetProto::SYMLINK:
|
||||
if (!proto_lookup_.empty() || !proto_->dir_assets().empty() ||
|
||||
!proto_->dir_indirect_assets().empty()) {
|
||||
*warning = "Symlink asset contains sub-assets";
|
||||
return false;
|
||||
}
|
||||
if (!proto_->file_chunks().empty() || !file_chunk_lists_.empty() ||
|
||||
!proto_->file_indirect_chunks().empty()) {
|
||||
*warning = "Symlink asset contains file chunks";
|
||||
return false;
|
||||
}
|
||||
if (proto_->file_size() > 0) {
|
||||
*warning = "File size is defined for a symlink asset";
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
*warning = "Undefined asset type";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Directory assets should not have any file chunks.
|
||||
// Absolute file chunk offsets for all loaded direct and indirect chunks
|
||||
// should be monotonically increasing.
|
||||
if (proto_->type() == AssetProto::FILE) {
|
||||
// Check direct chunks.
|
||||
size_t total_offset = 0;
|
||||
for (int idx = 0; idx < proto_->file_chunks_size(); ++idx) {
|
||||
if (proto_->file_chunks(idx).offset() < total_offset) {
|
||||
*warning = absl::StrFormat(
|
||||
"Disordered direct chunks: idx=%u, total_offset=%u, "
|
||||
"chunk_offset=%u",
|
||||
idx, total_offset, proto_->file_chunks(idx).offset());
|
||||
return false;
|
||||
}
|
||||
total_offset = proto_->file_chunks(idx).offset();
|
||||
}
|
||||
|
||||
// Check indirect lists.
|
||||
size_t prev_list_offset = total_offset;
|
||||
for (int list_idx = 0; list_idx < proto_->file_indirect_chunks_size();
|
||||
++list_idx) {
|
||||
size_t list_offset = ChunkListOffset(list_idx);
|
||||
if (list_idx == 0 && proto_->file_chunks_size() == 0 &&
|
||||
list_offset != 0) {
|
||||
*warning = absl::StrFormat(
|
||||
"Disordered indirect chunk list: the list offset should be 0, as "
|
||||
"there are no direct file chunks: "
|
||||
"list_offset=%u, previous list_offset=%u",
|
||||
list_offset, prev_list_offset);
|
||||
return false;
|
||||
} else if (list_idx > 0 && (prev_list_offset >= list_offset ||
|
||||
total_offset >= list_offset)) {
|
||||
*warning = absl::StrFormat(
|
||||
"Disordered indirect chunk list: the list offset should increase: "
|
||||
"list_offset=%u, previous list_offset=%u, total_offset=%u",
|
||||
list_offset, prev_list_offset, total_offset);
|
||||
return false;
|
||||
}
|
||||
if (file_chunk_lists_.size() <= list_idx ||
|
||||
!file_chunk_lists_[list_idx]) {
|
||||
total_offset = list_offset;
|
||||
continue;
|
||||
}
|
||||
// If the list is fetched, check its chunks' order.
|
||||
for (int chunk_idx = 0;
|
||||
chunk_idx < file_chunk_lists_[list_idx]->chunks_size();
|
||||
++chunk_idx) {
|
||||
const ChunkRefProto& chunk =
|
||||
file_chunk_lists_[list_idx]->chunks(chunk_idx);
|
||||
if (chunk_idx == 0 && chunk.offset() != 0) {
|
||||
*warning = absl::StrFormat(
|
||||
"The offset of the first chunk in the list should be 0: "
|
||||
"list_idx=%u, list_offset=%u, chunk_offset=%u",
|
||||
list_idx, list_offset, chunk.offset());
|
||||
return false;
|
||||
}
|
||||
if (chunk.offset() + list_offset < total_offset) {
|
||||
*warning = absl::StrFormat(
|
||||
"Disordered indirect chunk list: list_idx=%u, list_offset=%u, "
|
||||
"offset=%u, chunk_offset=%u",
|
||||
list_idx, list_offset, total_offset, chunk.offset());
|
||||
return false;
|
||||
}
|
||||
total_offset = list_offset + chunk.offset();
|
||||
}
|
||||
}
|
||||
if (total_offset == 0 && proto_->file_size() == 0) {
|
||||
return true;
|
||||
}
|
||||
// The last absolute offset should be less than the file size.
|
||||
if (total_offset >= proto_->file_size()) {
|
||||
*warning = absl::StrFormat(
|
||||
"The last absolute file offset exceeds the file size: %u >= %u",
|
||||
total_offset, proto_->file_size());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
absl::StatusOr<bool> Asset::FetchNextDirAssetList() {
|
||||
mutex_.AssertNotHeld();
|
||||
assert(proto_);
|
||||
|
||||
{
|
||||
absl::ReaderMutexLock read_lock(&mutex_);
|
||||
|
||||
// Shortcut to prevent acquiring a write lock if everything has been loaded.
|
||||
if (dir_asset_lists_.size() >=
|
||||
static_cast<size_t>(proto_->dir_indirect_assets_size())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
absl::WriterMutexLock write_lock(&mutex_);
|
||||
|
||||
// Check again in case some other thread has run this in the meantime.
|
||||
if (dir_asset_lists_.size() >=
|
||||
static_cast<size_t>(proto_->dir_indirect_assets_size())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Read next indirect asset list.
|
||||
const ContentIdProto& id =
|
||||
proto_->dir_indirect_assets(static_cast<int>(dir_asset_lists_.size()));
|
||||
auto list = std::make_unique<AssetListProto>();
|
||||
RETURN_IF_ERROR(data_store_reader_->GetProto(id, list.get()),
|
||||
"Failed to fetch AssetList proto with id %s",
|
||||
ContentId::ToHexString(id));
|
||||
dir_asset_lists_.push_back(std::move(list));
|
||||
UpdateProtoLookup(dir_asset_lists_.back()->assets());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Asset::UpdateProtoLookup(const RepeatedAssetProto& list) {
|
||||
assert((mutex_.AssertHeld(), true));
|
||||
|
||||
for (const AssetProto& asset : list) {
|
||||
proto_lookup_[asset.name().c_str()] = &asset;
|
||||
}
|
||||
}
|
||||
|
||||
int Asset::FindChunkList(uint64_t offset) {
|
||||
assert(proto_);
|
||||
const RepeatedIndirectChunkListProto& lists = proto_->file_indirect_chunks();
|
||||
if (offset >= proto_->file_size()) {
|
||||
// |offset| is not inside the file.
|
||||
return proto_->file_indirect_chunks_size();
|
||||
}
|
||||
|
||||
// TODO: Optimize search by using average chunk size.
|
||||
auto it =
|
||||
std::upper_bound(lists.begin(), lists.end(), offset,
|
||||
[](uint64_t value, const IndirectChunkListProto& list) {
|
||||
return value < list.offset();
|
||||
});
|
||||
return it - lists.begin() - 1;
|
||||
}
|
||||
|
||||
int Asset::FindChunk(const RepeatedChunkRefProto& chunks,
|
||||
uint64_t chunk_list_offset, uint64_t chunk_offset) {
|
||||
assert(chunk_list_offset <= chunk_offset);
|
||||
uint64_t rel_offset = chunk_offset - chunk_list_offset;
|
||||
// TODO: Optimize search by using average chunk size.
|
||||
auto it = std::upper_bound(chunks.begin(), chunks.end(), rel_offset,
|
||||
[](uint64_t value, const ChunkRefProto& ch) {
|
||||
return value < ch.offset();
|
||||
});
|
||||
return it - chunks.begin() - 1;
|
||||
}
|
||||
|
||||
uint64_t Asset::ChunkListOffset(int list_idx) const {
|
||||
assert(list_idx >= -1 && proto_ &&
|
||||
list_idx <= proto_->file_indirect_chunks_size());
|
||||
|
||||
if (list_idx == -1) return 0;
|
||||
if (list_idx < proto_->file_indirect_chunks_size())
|
||||
return proto_->file_indirect_chunks(list_idx).offset();
|
||||
return proto_->file_size();
|
||||
}
|
||||
|
||||
uint64_t Asset::ChunkSize(int list_idx, int chunk_idx,
|
||||
const RepeatedChunkRefProto* chunk_refs) {
|
||||
assert(chunk_idx >= 0 && chunk_idx < chunk_refs->size());
|
||||
assert(list_idx >= -1 && proto_ &&
|
||||
list_idx <= proto_->file_indirect_chunks_size());
|
||||
|
||||
// If the next chunk is in the same chunk_refs list, just return offset diff.
|
||||
if (chunk_idx + 1 < chunk_refs->size()) {
|
||||
return chunk_refs->at(chunk_idx + 1).offset() -
|
||||
chunk_refs->at(chunk_idx).offset();
|
||||
}
|
||||
|
||||
// If the next chunk is on another list, use the next list's offset.
|
||||
// Note that this also works for the last list, where
|
||||
// GetChunkListOffset(list_idx + 1) returns the file size.
|
||||
uint64_t chunk_absolute_offset =
|
||||
chunk_refs->at(chunk_idx).offset() + ChunkListOffset(list_idx);
|
||||
return ChunkListOffset(list_idx + 1) - chunk_absolute_offset;
|
||||
}
|
||||
|
||||
absl::StatusOr<const RepeatedChunkRefProto*> Asset::GetChunkRefList(
|
||||
int list_idx) {
|
||||
mutex_.AssertNotHeld();
|
||||
assert(list_idx >= -1 && proto_ &&
|
||||
list_idx <= proto_->file_indirect_chunks_size());
|
||||
|
||||
if (list_idx == -1) {
|
||||
// Direct chunk list.
|
||||
return &proto_->file_chunks();
|
||||
}
|
||||
|
||||
if (list_idx == proto_->file_indirect_chunks_size()) {
|
||||
// Indicates EOF.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
{
|
||||
absl::ReaderMutexLock read_lock(&mutex_);
|
||||
|
||||
// Do a quick check first if the list is already loaded.
|
||||
// This only requires a read lock.
|
||||
if (static_cast<size_t>(list_idx) < file_chunk_lists_.size() &&
|
||||
file_chunk_lists_[list_idx]) {
|
||||
return &file_chunk_lists_[list_idx]->chunks();
|
||||
}
|
||||
}
|
||||
|
||||
absl::WriterMutexLock write_lock(&mutex_);
|
||||
|
||||
// Indirect chunk list. Check if it has to be fetched.
|
||||
if (file_chunk_lists_.size() < static_cast<size_t>(list_idx) + 1) {
|
||||
file_chunk_lists_.resize(list_idx + 1);
|
||||
}
|
||||
if (!file_chunk_lists_[list_idx]) {
|
||||
auto list = std::make_unique<ChunkListProto>();
|
||||
const ContentIdProto& list_id =
|
||||
proto_->file_indirect_chunks(list_idx).chunk_list_id();
|
||||
RETURN_IF_ERROR(data_store_reader_->GetProto(list_id, list.get()),
|
||||
"Failed to fetch ChunkListProto with id %s",
|
||||
ContentId::ToHexString(list_id));
|
||||
file_chunk_lists_[list_idx] = std::move(list);
|
||||
}
|
||||
return &file_chunk_lists_[list_idx]->chunks();
|
||||
}
|
||||
|
||||
} // namespace cdc_ft
|
||||
182
cdc_fuse_fs/asset.h
Normal file
182
cdc_fuse_fs/asset.h
Normal file
@@ -0,0 +1,182 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CDC_FUSE_FS_ASSET_H_
|
||||
#define CDC_FUSE_FS_ASSET_H_
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
#include "absl/base/thread_annotations.h"
|
||||
#include "absl/status/statusor.h"
|
||||
#include "absl/strings/string_view.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
#include "manifest/content_id.h"
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
class Buffer;
|
||||
class DataStoreReader;
|
||||
|
||||
// Wraps an asset proto for reading and adds additional functionality like name
|
||||
// lookup maps and lazy loading of directory assets and file chunks.
|
||||
// This class is accessed from multiple threads and has to be THREAD-SAFE.
|
||||
class Asset {
|
||||
public:
|
||||
// Inode key type (cmp. fuse_ino_t).
|
||||
using ino_t = uint64_t;
|
||||
|
||||
// Creates a new asset. Must call Initialize() before using it.
|
||||
Asset();
|
||||
~Asset();
|
||||
|
||||
// Make it non-copyable, non-assignable to prevent accidental misuse.
|
||||
Asset(const Asset& other) = delete;
|
||||
Asset& operator=(const Asset& other) = delete;
|
||||
|
||||
// Initialize the class. Must be called right after creation.
|
||||
// NOT thread-safe! (OK as usually no other threads have access at this time.)
|
||||
void Initialize(ino_t parent_ino, DataStoreReader* data_store_reader,
|
||||
const AssetProto* proto);
|
||||
|
||||
// Returns the parent inode id passed to Initialize().
|
||||
// Thread-safe.
|
||||
ino_t parent_ino() const { return parent_ino_; }
|
||||
|
||||
// Returns the asset proto passed to Initialize().
|
||||
// Thread-safe.
|
||||
const AssetProto* proto() const { return proto_; }
|
||||
|
||||
// Returns all child asset protos. Loads them if necessary.
|
||||
// Returns an error if loading an indirect asset list fails.
|
||||
// Returns an InvalidArugmentError if *this is not a directory asset.
|
||||
// |proto_| must be set.
|
||||
// Thread-safe.
|
||||
absl::StatusOr<std::vector<const AssetProto*>> GetAllChildProtos()
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns loaded children's protos. Thread-safe.
|
||||
std::vector<const AssetProto*> GetLoadedChildProtos() const;
|
||||
|
||||
// For directory assets, looks up a child asset by name. Loads indirect asset
|
||||
// lists if needed. Returns an error if loading asset lists fails.
|
||||
// Returns nullptr if the asset cannot be found.
|
||||
// Returns an InvalidArugmentError if *this is not a directory asset.
|
||||
// |proto_| must be set.
|
||||
// Thread-safe.
|
||||
absl::StatusOr<const AssetProto*> Lookup(const char* name)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// For file assets, reads |size| bytes from the file, starting from |offset|,
|
||||
// and puts the result into |data|. Returns the number of bytes read or 0 if
|
||||
// |offset| >= file size. Loads indirect chunk lists if needed.
|
||||
// Returns an error if loading chunk lists fails.
|
||||
// Returns an InvalidArugmentError if *this is not a file asset.
|
||||
// |proto_| must be set.
|
||||
// Thread-safe.
|
||||
absl::StatusOr<uint64_t> Read(uint64_t offset, void* data, uint64_t size);
|
||||
|
||||
size_t GetNumFetchedFileChunkListsForTesting() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
size_t GetNumFetchedDirAssetsListsForTesting() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Updates asset proto, all corresponding internal structures are cleaned up.
|
||||
// This is an expensive operation as the previously created internal
|
||||
// structures are removed. Thread-safe.
|
||||
void UpdateProto(const AssetProto* proto) ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Checks consistency of the asset, for example: directory assets should not
|
||||
// contain any file chunks. Any discovered inconsistencies are defined in
|
||||
// |warning|.
|
||||
bool IsConsistent(std::string* warning) const ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
private:
|
||||
// Loads the next indirect directory asset list.
|
||||
// Returns true if a list was fetched.
|
||||
// Returns false if all lists have already been fetched.
|
||||
// Returns an error if fetching an indirect asset list failed.
|
||||
// |proto_| must be set.
|
||||
absl::StatusOr<bool> FetchNextDirAssetList() ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Puts all assets from |list| into |proto_lookup_|.
|
||||
void UpdateProtoLookup(const RepeatedAssetProto& list)
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_);
|
||||
|
||||
// Returns the index of the indirect chunk list that |offset| falls into or -1
|
||||
// if |offset| is contained in the direct chunk list. Returns the number of
|
||||
// indirect lists in |proto_| if |offset| is larger or equal to the file size.
|
||||
// |proto_| must be set.
|
||||
int FindChunkList(uint64_t offset);
|
||||
|
||||
// Returns the index of the chunk that |chunk_offset| falls into. The offsets
|
||||
// in list |chunks| are interpreted relative to |chunk_list_offset|.
|
||||
int FindChunk(const RepeatedChunkRefProto& chunks, uint64_t chunk_list_offset,
|
||||
uint64_t chunk_offset);
|
||||
|
||||
// Gets the direct or an indirect chunk list. Fetches indirect chunk lists if
|
||||
// necessary. |list_idx| must be in [-1, number of indirect chunk lists].
|
||||
//
|
||||
// Returns the direct chunk list if |list_idx| is -1. Returns nullptr if
|
||||
// |list_idx| equals the number of indirect chunk lists. Returns the indirect
|
||||
// chunk list at index |list_idx| otherwise. Returns an error if fetching an
|
||||
// indirect chunk list fails.
|
||||
// |proto_| must be set.
|
||||
absl::StatusOr<const RepeatedChunkRefProto*> GetChunkRefList(int list_idx)
|
||||
ABSL_LOCKS_EXCLUDED(mutex_);
|
||||
|
||||
// Returns the absolute offset of the chunk list with index |list_idx|.
|
||||
// |list_idx| must be in [-1, number of indirect chunk lists]. -1 refers to
|
||||
// the direct chunk list, in which case 0 is returned. If |list_idx| equals
|
||||
// the number of indirect chunk lists, the file size is returned. Otherwise,
|
||||
// the corresponding indirect chunk list's offset is returned.
|
||||
// |proto_| must be set.
|
||||
uint64_t ChunkListOffset(int list_idx) const;
|
||||
|
||||
// Returns the chunk size of the chunk with index |chunk_idx| on the chunk
|
||||
// list with index |list_idx| and corresponding proto |chunk_refs|.
|
||||
// |list_idx| must be in [-1, number of indirect chunk lists - 1].
|
||||
// |chunk_idx| must be in [0, chunk_refs->size()].
|
||||
// |proto_| must be set.
|
||||
uint64_t ChunkSize(int list_idx, int chunk_idx,
|
||||
const RepeatedChunkRefProto* chunk_refs);
|
||||
|
||||
// Parent inode, for ".." in dir listings.
|
||||
ino_t parent_ino_ = 0;
|
||||
|
||||
// Interface for loading content (chunks, assets).
|
||||
DataStoreReader* data_store_reader_ = nullptr;
|
||||
|
||||
// Corresponding asset proto.
|
||||
const AssetProto* proto_ = nullptr;
|
||||
|
||||
// RW mutex for increased thread-safetiness.
|
||||
mutable absl::Mutex mutex_;
|
||||
|
||||
// Maps asset proto names to asset protos for all protos loaded so far.
|
||||
// The string views point directly into asset protos.
|
||||
std::unordered_map<absl::string_view, const AssetProto*> proto_lookup_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// Fetched |file_indirect_chunks| chunk lists.
|
||||
std::vector<std::unique_ptr<ChunkListProto>> file_chunk_lists_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
|
||||
// Fetched |dir_indirect_assets| fields so far.
|
||||
std::vector<std::unique_ptr<AssetListProto>> dir_asset_lists_
|
||||
ABSL_GUARDED_BY(mutex_);
|
||||
};
|
||||
|
||||
} // namespace cdc_ft
|
||||
|
||||
#endif // CDC_FUSE_FS_ASSET_H_
|
||||
112
cdc_fuse_fs/asset_stream_client.cc
Normal file
112
cdc_fuse_fs/asset_stream_client.cc
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cdc_fuse_fs/asset_stream_client.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "common/log.h"
|
||||
#include "common/stopwatch.h"
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
using GetContentRequest = proto::GetContentRequest;
|
||||
using GetContentResponse = proto::GetContentResponse;
|
||||
using SendCachedContentIdsRequest = proto::SendCachedContentIdsRequest;
|
||||
using SendCachedContentIdsResponse = proto::SendCachedContentIdsResponse;
|
||||
|
||||
AssetStreamClient::AssetStreamClient(std::shared_ptr<grpc::Channel> channel,
|
||||
bool enable_stats)
|
||||
: enable_stats_(enable_stats) {
|
||||
stub_ = AssetStreamService::NewStub(std::move(channel));
|
||||
}
|
||||
|
||||
AssetStreamClient::~AssetStreamClient() = default;
|
||||
|
||||
size_t TotalDataSize(const RepeatedStringProto& data) {
|
||||
size_t total_size = 0;
|
||||
for (const std::string& s : data) {
|
||||
total_size += s.size();
|
||||
}
|
||||
return total_size;
|
||||
}
|
||||
|
||||
absl::StatusOr<std::string> AssetStreamClient::GetContent(
|
||||
const ContentIdProto& id) {
|
||||
GetContentRequest request;
|
||||
*request.add_id() = id;
|
||||
if (enable_stats_)
|
||||
request.set_thread_id(thread_id_hash_(std::this_thread::get_id()));
|
||||
|
||||
grpc::ClientContext context;
|
||||
GetContentResponse response;
|
||||
|
||||
Stopwatch sw;
|
||||
grpc::Status status = stub_->GetContent(&context, request, &response);
|
||||
LOG_DEBUG("GRPC TIME %0.3f sec for %u chunks with %u bytes",
|
||||
sw.ElapsedSeconds(), response.data().size(),
|
||||
TotalDataSize(response.data()));
|
||||
|
||||
if (!status.ok()) {
|
||||
return absl::Status(static_cast<absl::StatusCode>(status.error_code()),
|
||||
status.error_message());
|
||||
}
|
||||
assert(response.data_size() == 1);
|
||||
return std::move(*response.mutable_data(0));
|
||||
}
|
||||
|
||||
absl::StatusOr<RepeatedStringProto> AssetStreamClient::GetContent(
|
||||
RepeatedContentIdProto chunk_ids) {
|
||||
if (chunk_ids.empty()) return RepeatedStringProto();
|
||||
|
||||
GetContentRequest request;
|
||||
*request.mutable_id() = std::move(chunk_ids);
|
||||
if (enable_stats_)
|
||||
request.set_thread_id(thread_id_hash_(std::this_thread::get_id()));
|
||||
|
||||
grpc::ClientContext context;
|
||||
GetContentResponse response;
|
||||
|
||||
Stopwatch sw;
|
||||
grpc::Status status = stub_->GetContent(&context, request, &response);
|
||||
|
||||
if (!status.ok()) {
|
||||
return absl::Status(static_cast<absl::StatusCode>(status.error_code()),
|
||||
status.error_message());
|
||||
}
|
||||
LOG_DEBUG("GRPC TIME %0.3f sec for %zu bytes", sw.ElapsedSeconds(),
|
||||
TotalDataSize(response.data()));
|
||||
|
||||
return std::move(*response.mutable_data());
|
||||
}
|
||||
|
||||
absl::Status AssetStreamClient::SendCachedContentIds(
|
||||
std::vector<ContentIdProto> content_ids) {
|
||||
SendCachedContentIdsRequest request;
|
||||
for (ContentIdProto& id : content_ids) *request.add_id() = std::move(id);
|
||||
|
||||
grpc::ClientContext context;
|
||||
SendCachedContentIdsResponse response;
|
||||
|
||||
grpc::Status status =
|
||||
stub_->SendCachedContentIds(&context, request, &response);
|
||||
if (!status.ok()) {
|
||||
return absl::Status(static_cast<absl::StatusCode>(status.error_code()),
|
||||
status.error_message());
|
||||
}
|
||||
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
} // namespace cdc_ft
|
||||
62
cdc_fuse_fs/asset_stream_client.h
Normal file
62
cdc_fuse_fs/asset_stream_client.h
Normal file
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CDC_FUSE_FS_ASSET_STREAM_CLIENT_H_
|
||||
#define CDC_FUSE_FS_ASSET_STREAM_CLIENT_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
|
||||
#include "absl/status/statusor.h"
|
||||
#include "grpcpp/channel.h"
|
||||
#include "manifest/manifest_proto_defs.h"
|
||||
#include "proto/asset_stream_service.grpc.pb.h"
|
||||
|
||||
namespace grpc_impl {
|
||||
class Channel;
|
||||
}
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
// gRpc client for streaming assets to a gamelets. The client runs inside the
|
||||
// CDC Fuse filesystem and requests chunks from the workstation.
|
||||
class AssetStreamClient {
|
||||
public:
|
||||
// |channel| is a grpc channel to use.
|
||||
// |enable_stats| determines whether additional statistics are sent.
|
||||
AssetStreamClient(std::shared_ptr<grpc::Channel> channel, bool enable_stats);
|
||||
~AssetStreamClient();
|
||||
|
||||
// Gets the content of the chunk with given |id|.
|
||||
absl::StatusOr<std::string> GetContent(const ContentIdProto& id);
|
||||
absl::StatusOr<RepeatedStringProto> GetContent(
|
||||
RepeatedContentIdProto chunk_ids);
|
||||
|
||||
// Sends the IDs of all cached chunks to the workstation for statistical
|
||||
// purposes.
|
||||
absl::Status SendCachedContentIds(std::vector<ContentIdProto> content_ids);
|
||||
|
||||
private:
|
||||
using AssetStreamService = proto::AssetStreamService;
|
||||
std::unique_ptr<AssetStreamService::Stub> stub_;
|
||||
bool enable_stats_;
|
||||
std::hash<std::thread::id> thread_id_hash_;
|
||||
};
|
||||
|
||||
} // namespace cdc_ft
|
||||
|
||||
#endif // CDC_FUSE_FS_ASSET_STREAM_CLIENT_H_
|
||||
820
cdc_fuse_fs/asset_test.cc
Normal file
820
cdc_fuse_fs/asset_test.cc
Normal file
@@ -0,0 +1,820 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cdc_fuse_fs/asset.h"
|
||||
|
||||
#include "absl/strings/match.h"
|
||||
#include "common/buffer.h"
|
||||
#include "common/path.h"
|
||||
#include "common/status_test_macros.h"
|
||||
#include "data_store/mem_data_store.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace cdc_ft {
|
||||
namespace {
|
||||
|
||||
class AssetTest : public ::testing::Test {
|
||||
public:
|
||||
AssetTest()
|
||||
: bad_id_(ContentId::FromDataString(std::string("does not exist"))) {
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n) {
|
||||
child_protos_[n].set_name("file" + std::to_string(n));
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
static constexpr Asset::ino_t kParentIno = 1;
|
||||
|
||||
// Adds chunks with the data given by |data_vec| to the store, and
|
||||
// adds references to the chunks to |list|. Updates |offset|.
|
||||
void AddChunks(std::vector<std::vector<char>> data_vec, uint64_t* offset,
|
||||
RepeatedChunkRefProto* list) {
|
||||
for (auto& data : data_vec) {
|
||||
ChunkRefProto* chunk_ref = list->Add();
|
||||
chunk_ref->set_offset(*offset);
|
||||
*offset += data.size();
|
||||
*chunk_ref->mutable_chunk_id() = store_.AddData(std::move(data));
|
||||
}
|
||||
}
|
||||
|
||||
// Adds chunks with the data given by |data_vec| to the store,
|
||||
// creates an indirect chunk list from those chunks and adds a reference to
|
||||
// that list to |list|.
|
||||
void AddIndirectChunks(std::vector<std::vector<char>> data_vec,
|
||||
uint64_t* offset,
|
||||
RepeatedIndirectChunkListProto* list) {
|
||||
uint64_t indirect_list_offset = *offset;
|
||||
*offset = 0;
|
||||
|
||||
ChunkListProto chunk_list;
|
||||
AddChunks(data_vec, offset, chunk_list.mutable_chunks());
|
||||
|
||||
IndirectChunkListProto* indirect_list = list->Add();
|
||||
indirect_list->set_offset(indirect_list_offset);
|
||||
*indirect_list->mutable_chunk_list_id() = store_.AddProto(chunk_list);
|
||||
*offset += indirect_list_offset;
|
||||
}
|
||||
|
||||
// Checks if the given list |protos| contains an asset having |name|.
|
||||
static bool ContainsAsset(const std::vector<const AssetProto*>& protos,
|
||||
const std::string& name) {
|
||||
return std::find_if(protos.begin(), protos.end(), [=](const AssetProto* p) {
|
||||
return p->name() == name;
|
||||
}) != protos.end();
|
||||
}
|
||||
|
||||
MemDataStore store_;
|
||||
AssetProto proto_;
|
||||
Asset asset_;
|
||||
|
||||
static constexpr size_t kNumChildProtos = 4;
|
||||
AssetProto child_protos_[kNumChildProtos];
|
||||
|
||||
const ContentIdProto bad_id_;
|
||||
std::string asset_check_;
|
||||
};
|
||||
|
||||
TEST_F(AssetTest, BasicGetters) {
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_EQ(asset_.parent_ino(), kParentIno);
|
||||
EXPECT_EQ(asset_.proto(), &proto_);
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ("Undefined asset type", asset_check_.c_str());
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, GetAllChildProtosDirectSucceeds) {
|
||||
// Put all children into the direct asset list.
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n)
|
||||
*proto_.add_dir_assets() = child_protos_[n];
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
absl::StatusOr<std::vector<const AssetProto*>> protos =
|
||||
asset_.GetAllChildProtos();
|
||||
|
||||
ASSERT_OK(protos);
|
||||
ASSERT_EQ(protos->size(), kNumChildProtos);
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n) {
|
||||
EXPECT_TRUE(ContainsAsset(protos.value(), child_protos_[n].name()))
|
||||
<< "Could not find asset " << child_protos_[n].name();
|
||||
}
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, GetAllChildProtosIndirectSucceeds) {
|
||||
// Put child0 into the direct asset list and children 1-N into indirect lists.
|
||||
*proto_.add_dir_assets() = child_protos_[0];
|
||||
for (size_t n = 1; n < kNumChildProtos; ++n) {
|
||||
AssetListProto list;
|
||||
*list.add_assets() = child_protos_[n];
|
||||
*proto_.add_dir_indirect_assets() = store_.AddProto(list);
|
||||
}
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
absl::StatusOr<std::vector<const AssetProto*>> protos =
|
||||
asset_.GetAllChildProtos();
|
||||
|
||||
EXPECT_EQ(asset_.GetNumFetchedDirAssetsListsForTesting(),
|
||||
kNumChildProtos - 1);
|
||||
ASSERT_OK(protos);
|
||||
ASSERT_EQ(protos->size(), kNumChildProtos);
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n) {
|
||||
EXPECT_TRUE(ContainsAsset(protos.value(), child_protos_[n].name()))
|
||||
<< "Could not find asset " << child_protos_[n].name();
|
||||
}
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, GetAllChildProtosWithBadListIdFails) {
|
||||
*proto_.add_dir_indirect_assets() = bad_id_;
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
absl::StatusOr<std::vector<const AssetProto*>> protos =
|
||||
asset_.GetAllChildProtos();
|
||||
|
||||
ASSERT_NOT_OK(protos);
|
||||
EXPECT_TRUE(absl::StrContains(protos.status().message(),
|
||||
"Failed to fetch directory assets"));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, GetAllChildProtosWithWrongTypeFails) {
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
absl::StatusOr<std::vector<const AssetProto*>> protos =
|
||||
asset_.GetAllChildProtos();
|
||||
|
||||
ASSERT_NOT_OK(protos);
|
||||
EXPECT_TRUE(absl::IsInvalidArgument(protos.status()));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, GetLoadedChildProtosSucceedsForEmpty) {
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
EXPECT_TRUE(asset_.GetLoadedChildProtos().empty());
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ("Undefined asset type", asset_check_.c_str());
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, GetLoadedChildProtosSucceedsForNonEmpty) {
|
||||
// Put child0 into the direct asset list and children 1-N into indirect lists.
|
||||
*proto_.add_dir_assets() = child_protos_[0];
|
||||
for (size_t n = 1; n < kNumChildProtos; ++n) {
|
||||
AssetListProto list;
|
||||
*list.add_assets() = child_protos_[n];
|
||||
*proto_.add_dir_indirect_assets() = store_.AddProto(list);
|
||||
}
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
// The direct list is always loaded.
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
std::vector<const AssetProto*> protos = asset_.GetLoadedChildProtos();
|
||||
ASSERT_EQ(protos.size(), 1);
|
||||
EXPECT_EQ(protos[0]->name(), child_protos_[0].name());
|
||||
|
||||
// A lookup for the first child triggers loading of the first indirect list.
|
||||
EXPECT_OK(asset_.Lookup(child_protos_[1].name().c_str()));
|
||||
protos = asset_.GetLoadedChildProtos();
|
||||
ASSERT_EQ(protos.size(), 2);
|
||||
EXPECT_TRUE(ContainsAsset(protos, child_protos_[0].name()));
|
||||
EXPECT_TRUE(ContainsAsset(protos, child_protos_[1].name()));
|
||||
|
||||
// GetAllChildProtos() triggers loading of all indirect lists.
|
||||
EXPECT_OK(asset_.GetAllChildProtos());
|
||||
protos = asset_.GetLoadedChildProtos();
|
||||
ASSERT_EQ(protos.size(), 4u);
|
||||
for (size_t n = 0; n < protos.size(); ++n) {
|
||||
EXPECT_TRUE(ContainsAsset(protos, child_protos_[n].name()))
|
||||
<< "Could not find asset " << child_protos_[n].name();
|
||||
}
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, LookupSucceeds) {
|
||||
// Put child0 into the direct asset list and children 1-N into indirect lists.
|
||||
*proto_.add_dir_assets() = child_protos_[0];
|
||||
for (size_t n = 1; n < kNumChildProtos; ++n) {
|
||||
AssetListProto list;
|
||||
*list.add_assets() = child_protos_[n];
|
||||
*proto_.add_dir_indirect_assets() = store_.AddProto(list);
|
||||
}
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
// Indirect asset lists should be fetched in a lazy fashion.
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
absl::StatusOr<const AssetProto*> file0 = asset_.Lookup("file0");
|
||||
EXPECT_EQ(asset_.GetNumFetchedDirAssetsListsForTesting(), 0);
|
||||
absl::StatusOr<const AssetProto*> file1 = asset_.Lookup("file1");
|
||||
EXPECT_EQ(asset_.GetNumFetchedDirAssetsListsForTesting(), 1);
|
||||
absl::StatusOr<const AssetProto*> file3 = asset_.Lookup("file3");
|
||||
EXPECT_EQ(asset_.GetNumFetchedDirAssetsListsForTesting(), 3);
|
||||
|
||||
ASSERT_OK(file0);
|
||||
ASSERT_OK(file1);
|
||||
ASSERT_OK(file3);
|
||||
|
||||
ASSERT_NE(*file0, nullptr);
|
||||
ASSERT_NE(*file1, nullptr);
|
||||
ASSERT_NE(*file3, nullptr);
|
||||
|
||||
EXPECT_EQ((*file0)->name(), child_protos_[0].name());
|
||||
EXPECT_EQ((*file1)->name(), child_protos_[1].name());
|
||||
EXPECT_EQ((*file3)->name(), child_protos_[3].name());
|
||||
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, LookupNotFoundSucceeds) {
|
||||
// Put child0 into the direct asset list and children 1-N into indirect lists.
|
||||
*proto_.add_dir_assets() = child_protos_[0];
|
||||
for (size_t n = 1; n < kNumChildProtos; ++n) {
|
||||
AssetListProto list;
|
||||
*list.add_assets() = child_protos_[n];
|
||||
*proto_.add_dir_indirect_assets() = store_.AddProto(list);
|
||||
}
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
absl::StatusOr<const AssetProto*> proto = asset_.Lookup("non_existing");
|
||||
|
||||
EXPECT_EQ(asset_.GetNumFetchedDirAssetsListsForTesting(),
|
||||
kNumChildProtos - 1);
|
||||
ASSERT_OK(proto);
|
||||
ASSERT_EQ(*proto, nullptr);
|
||||
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, LookupWithWrongTypeFails) {
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
absl::StatusOr<const AssetProto*> proto = asset_.Lookup("foo");
|
||||
|
||||
ASSERT_NOT_OK(proto);
|
||||
EXPECT_TRUE(absl::IsInvalidArgument(proto.status()));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, LookupWithBadListIdFails) {
|
||||
*proto_.add_dir_assets() = child_protos_[0];
|
||||
*proto_.add_dir_indirect_assets() = bad_id_;
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
// This should succeed since 'file0' on the direct assets list.
|
||||
ASSERT_OK(asset_.Lookup("file0"));
|
||||
|
||||
// This should fail since it should trigger loading the bad id.
|
||||
absl::StatusOr<const AssetProto*> proto = asset_.Lookup("file1");
|
||||
ASSERT_NOT_OK(proto);
|
||||
EXPECT_TRUE(absl::StrContains(proto.status().message(),
|
||||
"Failed to fetch directory assets"));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadDirectSucceeds) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{1, 2}, {3, 4}}, &offset, proto_.mutable_file_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(4);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_EQ(*bytes_read, 4);
|
||||
EXPECT_EQ(data, std::vector<char>({1, 2, 3, 4}));
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadIndirectSucceeds) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{1, 2}}, &offset, proto_.mutable_file_chunks());
|
||||
AddIndirectChunks({{3}, {4, 5, 6}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
AddIndirectChunks({{7, 8, 9}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(9);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_EQ(*bytes_read, 9);
|
||||
EXPECT_EQ(data, std::vector<char>({1, 2, 3, 4, 5, 6, 7, 8, 9}));
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadIndirectOnlySucceeds) {
|
||||
uint64_t offset = 0;
|
||||
AddIndirectChunks({{1, 2}}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(2);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_EQ(*bytes_read, 2);
|
||||
EXPECT_EQ(data, std::vector<char>({1, 2}));
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ("", asset_check_.c_str());
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadWithWrongType) {
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(1);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_NOT_OK(bytes_read);
|
||||
EXPECT_TRUE(absl::IsInvalidArgument(bytes_read.status()));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadIndirectWithBadListIdFails) {
|
||||
IndirectChunkListProto* indirect_list = proto_.add_file_indirect_chunks();
|
||||
indirect_list->set_offset(0);
|
||||
*indirect_list->mutable_chunk_list_id() = bad_id_;
|
||||
proto_.set_file_size(1);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(1);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_NOT_OK(bytes_read);
|
||||
EXPECT_TRUE(absl::StrContains(bytes_read.status().message(),
|
||||
"Failed to fetch indirect chunk list 0"));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadFetchesIndirectListsLazily) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{0, 1, 2}}, &offset, proto_.mutable_file_chunks());
|
||||
AddIndirectChunks({{3}}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
AddIndirectChunks({{4, 5, 6}, {7}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
AddIndirectChunks({{8, 9}}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
EXPECT_EQ(asset_.GetNumFetchedFileChunkListsForTesting(), 0);
|
||||
|
||||
// Read direct chunks. Should not trigger indirect reads.
|
||||
std::vector<char> data(10);
|
||||
absl::StatusOr<uint64_t> bytes_read = asset_.Read(0, data.data(), 3);
|
||||
EXPECT_EQ(asset_.GetNumFetchedFileChunkListsForTesting(), 0);
|
||||
|
||||
// Read an indirect chunk near the end ({ {8, 9} }).
|
||||
bytes_read = asset_.Read(8, data.data(), 1);
|
||||
EXPECT_EQ(asset_.GetNumFetchedFileChunkListsForTesting(), 1);
|
||||
|
||||
// Read an indirect chunk in the beginning ({ {3} }).
|
||||
bytes_read = asset_.Read(3, data.data(), 1);
|
||||
EXPECT_EQ(asset_.GetNumFetchedFileChunkListsForTesting(), 2);
|
||||
|
||||
// Read an indirect chunk in the middle ({ {4, 5, 6}, {7} }).
|
||||
bytes_read = asset_.Read(4, data.data(), 4);
|
||||
EXPECT_EQ(asset_.GetNumFetchedFileChunkListsForTesting(), 3);
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadEmptySucceeds) {
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
std::vector<char> data(4);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_EQ(*bytes_read, 0);
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadEmptyDirectChunkSucceeds) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{}, {1}}, &offset, proto_.mutable_file_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(4);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_EQ(*bytes_read, 1);
|
||||
data.resize(1);
|
||||
EXPECT_EQ(data, std::vector<char>({1}));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadEmptyIndirectChunkListFails) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{1}}, &offset, proto_.mutable_file_chunks());
|
||||
AddIndirectChunks({}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
AddIndirectChunks({{}}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
AddIndirectChunks({{2}}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(4);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_EQ(*bytes_read, 2);
|
||||
data.resize(2);
|
||||
EXPECT_EQ(data, std::vector<char>({1, 2}));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadWithBadFileSizeFails) {
|
||||
// Construct a case where the second chunk is empty, but file size indicates
|
||||
// that it should be 1 byte long. Reading that byte should fail.
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{1}, {}}, &offset, proto_.mutable_file_chunks());
|
||||
proto_.set_file_size(offset + 1);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(1);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(1, data.data(), data.size());
|
||||
|
||||
ASSERT_NOT_OK(bytes_read);
|
||||
EXPECT_TRUE(
|
||||
absl::StrContains(bytes_read.status().message(),
|
||||
"requested offset 0 is larger or equal than size 0"));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadWithBadChunkIdSizeFails) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{1}}, &offset, proto_.mutable_file_chunks());
|
||||
*proto_.mutable_file_chunks(0)->mutable_chunk_id() = bad_id_;
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(1);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_NOT_OK(bytes_read);
|
||||
EXPECT_TRUE(absl::StrContains(bytes_read.status().message(),
|
||||
"Failed to fetch chunk(s)"));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadWithBadOffsetFails) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{1, 2, 3}, {4, 5, 6}}, &offset, proto_.mutable_file_chunks());
|
||||
proto_.mutable_file_chunks(1)->set_offset(4); // Instead of 3.
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(6);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_NOT_OK(bytes_read);
|
||||
EXPECT_TRUE(absl::StrContains(bytes_read.status().message(),
|
||||
"requested size 4 at offset 0"));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadEmptyWithBadFileSize) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({}, &offset, proto_.mutable_file_chunks());
|
||||
proto_.set_file_size(1);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(1);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_NOT_OK(bytes_read);
|
||||
EXPECT_TRUE(absl::StrContains(bytes_read.status().message(),
|
||||
"Invalid chunk ref list"));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, ReadWithOffsetAndSizeSucceeds) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{0, 1}, {}, {2}}, &offset, proto_.mutable_file_chunks());
|
||||
AddIndirectChunks({{3}, {4, 5, 6}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
AddIndirectChunks({}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
AddIndirectChunks({{7, 8, 9}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
// Test all kinds of different permutations of offsets and sizes.
|
||||
std::vector<char> expected_data;
|
||||
for (offset = 0; offset < 12; ++offset) {
|
||||
for (uint64_t size = 0; size < 12; ++size) {
|
||||
expected_data.clear();
|
||||
for (uint64_t n = offset; n < std::min<uint64_t>(offset + size, 10);
|
||||
++n) {
|
||||
expected_data.push_back(static_cast<char>(n));
|
||||
}
|
||||
|
||||
std::vector<char> data(size);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(offset, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_EQ(*bytes_read, expected_data.size());
|
||||
data.resize(expected_data.size());
|
||||
EXPECT_EQ(data, expected_data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, UpdateProtoWithEmptyAssetSucceeds) {
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
// Put all children into the direct asset list.
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n)
|
||||
*proto_.add_dir_assets() = child_protos_[n];
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
absl::StatusOr<std::vector<const AssetProto*>> protos =
|
||||
asset_.GetAllChildProtos();
|
||||
ASSERT_OK(protos);
|
||||
ASSERT_EQ(protos->size(), kNumChildProtos);
|
||||
|
||||
AssetProto proto_updated;
|
||||
proto_updated.set_type(AssetProto::DIRECTORY);
|
||||
asset_.UpdateProto(&proto_updated);
|
||||
protos = asset_.GetAllChildProtos();
|
||||
ASSERT_OK(protos);
|
||||
ASSERT_TRUE(protos->empty());
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, UpdateProtoFromEmptyAssetSucceeds) {
|
||||
AssetProto empty_proto;
|
||||
empty_proto.set_type(AssetProto::DIRECTORY);
|
||||
asset_.Initialize(kParentIno, &store_, &empty_proto);
|
||||
absl::StatusOr<std::vector<const AssetProto*>> protos =
|
||||
asset_.GetAllChildProtos();
|
||||
ASSERT_OK(protos);
|
||||
ASSERT_TRUE(protos->empty());
|
||||
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
// Put all children into the direct asset list.
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n)
|
||||
*proto_.add_dir_assets() = child_protos_[n];
|
||||
asset_.UpdateProto(&proto_);
|
||||
|
||||
protos = asset_.GetAllChildProtos();
|
||||
ASSERT_OK(protos);
|
||||
ASSERT_EQ(protos->size(), kNumChildProtos);
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, AssetProtoComparison) {
|
||||
AssetProto a;
|
||||
AssetProto b;
|
||||
EXPECT_EQ(a, b);
|
||||
|
||||
a.set_type(AssetProto::DIRECTORY);
|
||||
b.set_type(AssetProto::FILE);
|
||||
EXPECT_NE(a, b);
|
||||
|
||||
b.set_type(AssetProto::DIRECTORY);
|
||||
EXPECT_EQ(a, b);
|
||||
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n)
|
||||
*a.add_dir_assets() = child_protos_[n];
|
||||
EXPECT_NE(a, b);
|
||||
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n)
|
||||
*b.add_dir_assets() = child_protos_[n];
|
||||
EXPECT_EQ(a, b);
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsFileWithDirAssets) {
|
||||
// Put all children into the direct asset list.
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n)
|
||||
*proto_.add_dir_assets() = child_protos_[n];
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(), "File asset contains sub-assets");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsFileWithSymlink) {
|
||||
proto_.set_symlink_target("symlink");
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(), "File asset contains a symlink");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsDirWithFileChunks) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{1, 2}}, &offset, proto_.mutable_file_chunks());
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(), "Directory asset contains file chunks");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsDirWithIndirectFileChunks) {
|
||||
uint64_t offset = 0;
|
||||
AddIndirectChunks({{3}, {4, 5, 6}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(), "Directory asset contains file chunks");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsDirWithSymlink) {
|
||||
proto_.set_symlink_target("symlink");
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(), "Directory asset contains a symlink");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsDirWithFileSize) {
|
||||
proto_.set_file_size(2);
|
||||
proto_.set_type(AssetProto::DIRECTORY);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(),
|
||||
"File size is defined for a directory asset");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsSymlinkWithDirAssets) {
|
||||
// Put all children into the direct asset list.
|
||||
for (size_t n = 0; n < kNumChildProtos; ++n)
|
||||
*proto_.add_dir_assets() = child_protos_[n];
|
||||
proto_.set_type(AssetProto::SYMLINK);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(), "Symlink asset contains sub-assets");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsSymlinkWithIndirectFileChunks) {
|
||||
uint64_t offset = 0;
|
||||
AddIndirectChunks({{3}, {4, 5, 6}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(offset);
|
||||
proto_.set_type(AssetProto::SYMLINK);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(), "Symlink asset contains file chunks");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsSymlinkWithFileSize) {
|
||||
proto_.set_file_size(2);
|
||||
proto_.set_type(AssetProto::SYMLINK);
|
||||
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(),
|
||||
"File size is defined for a symlink asset");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsUndefinedAssetType) {
|
||||
proto_.set_type(AssetProto::UNKNOWN);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(), "Undefined asset type");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsFileChunkWrongOffsets) {
|
||||
uint64_t offset = 10;
|
||||
AddChunks({{1}}, &offset, proto_.mutable_file_chunks());
|
||||
offset = 5;
|
||||
AddChunks({{2}}, &offset, proto_.mutable_file_chunks());
|
||||
proto_.set_file_size(2);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(
|
||||
asset_check_.c_str(),
|
||||
"Disordered direct chunks: idx=1, total_offset=10, chunk_offset=5");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsWrongFileSize) {
|
||||
uint64_t offset = 0;
|
||||
AddChunks({{1}, {2}}, &offset, proto_.mutable_file_chunks());
|
||||
AddIndirectChunks({{3}, {4}, {5}, {6}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
AddIndirectChunks({{7}, {8}, {9}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(5);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
std::vector<char> data(9);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(asset_check_.c_str(),
|
||||
"The last absolute file offset exceeds the file size: 8 >= 5");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsNonZeroFirstIndirectListOffset) {
|
||||
uint64_t offset = 10;
|
||||
AddIndirectChunks({{1}}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(1);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(
|
||||
asset_check_.c_str(),
|
||||
"Disordered indirect chunk list: the list offset should be 0, as there "
|
||||
"are no direct file chunks: list_offset=10, previous list_offset=0");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentFailsNonIncreasingIndirectListOffset) {
|
||||
uint64_t offset = 0;
|
||||
AddIndirectChunks({{1}, {2}, {3}}, &offset,
|
||||
proto_.mutable_file_indirect_chunks());
|
||||
offset = 1;
|
||||
AddIndirectChunks({{3}}, &offset, proto_.mutable_file_indirect_chunks());
|
||||
proto_.set_file_size(3);
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
|
||||
// Read the first indirect list to fill the internal structure.
|
||||
std::vector<char> data(3);
|
||||
absl::StatusOr<uint64_t> bytes_read =
|
||||
asset_.Read(0, data.data(), data.size());
|
||||
|
||||
ASSERT_OK(bytes_read);
|
||||
EXPECT_EQ(*bytes_read, 3);
|
||||
|
||||
EXPECT_FALSE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_STREQ(
|
||||
asset_check_.c_str(),
|
||||
"Disordered indirect chunk list: the list offset should increase: "
|
||||
"list_offset=1, previous list_offset=0, total_offset=2");
|
||||
}
|
||||
|
||||
TEST_F(AssetTest, IsConsistentEmptyFileSucceeds) {
|
||||
proto_.set_type(AssetProto::FILE);
|
||||
asset_.Initialize(kParentIno, &store_, &proto_);
|
||||
proto_.set_file_size(0);
|
||||
|
||||
EXPECT_TRUE(asset_.IsConsistent(&asset_check_));
|
||||
EXPECT_TRUE(asset_check_.empty());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cdc_ft
|
||||
1553
cdc_fuse_fs/cdc_fuse_fs.cc
Normal file
1553
cdc_fuse_fs/cdc_fuse_fs.cc
Normal file
File diff suppressed because it is too large
Load Diff
86
cdc_fuse_fs/cdc_fuse_fs.h
Normal file
86
cdc_fuse_fs/cdc_fuse_fs.h
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CDC_FUSE_FS_CDC_FUSE_FS_H_
|
||||
#define CDC_FUSE_FS_CDC_FUSE_FS_H_
|
||||
|
||||
#ifndef R_OK
|
||||
#define R_OK 4
|
||||
#endif
|
||||
#ifndef W_OK
|
||||
#define W_OK 2
|
||||
#endif
|
||||
#ifndef X_OK
|
||||
#define X_OK 1
|
||||
#endif
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "cdc_fuse_fs/config_stream_client.h"
|
||||
#include "grpcpp/channel.h"
|
||||
#include "manifest/manifest_proto_defs.h"
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
class DataStoreReader;
|
||||
|
||||
// CdcFuse filesystem constants, exposed for testing.
|
||||
namespace internal {
|
||||
// Number of hardlinks is not important since the fs is read-only (I think).
|
||||
constexpr int kCdcFuseDefaultNLink = 1;
|
||||
|
||||
// Cloudcast user and group id.
|
||||
constexpr int kCdcFuseCloudcastUid = 1000;
|
||||
constexpr int kCdcFuseCloudcastGid = 1000;
|
||||
|
||||
// Root user and group id.
|
||||
constexpr int kCdcFuseRootUid = 0;
|
||||
constexpr int kCdcFuseRootGid = 0;
|
||||
|
||||
// Default timeout after which the kernel will assume inodes are stale.
|
||||
constexpr double kCdcFuseInodeTimeoutSec = 1.0;
|
||||
} // namespace internal
|
||||
|
||||
namespace cdc_fuse_fs {
|
||||
|
||||
// Initializes the CDC FUSE filesystem. Parses the command line, sets up a
|
||||
// channel and a session, and optionally forks the process. For valid arguments
|
||||
// see fuse_common.h.
|
||||
absl::Status Initialize(int argc, char** argv);
|
||||
|
||||
// Starts a client to read configuration updates over gRPC |channel|.
|
||||
// |instance| is the gamelet instance id.
|
||||
absl::Status StartConfigClient(std::string instance,
|
||||
std::shared_ptr<grpc::Channel> channel);
|
||||
|
||||
// Sets the |data_store_reader| to load data from, initializes FUSE with a
|
||||
// manifest for an empty directory, and starts the filesystem. The call does
|
||||
// not return until the filesystem finishes running.
|
||||
// |consistency_check| defines whether FUSE consistency should be inspected
|
||||
// after each manifest update.
|
||||
absl::Status Run(DataStoreReader* data_store_reader, bool consistency_check);
|
||||
|
||||
// Releases resources. Should be called when the filesystem finished running.
|
||||
void Shutdown();
|
||||
|
||||
// Sets |manifest_id| as a CDC FUSE root.
|
||||
absl::Status SetManifest(const ContentIdProto& manifest_id);
|
||||
|
||||
} // namespace cdc_fuse_fs
|
||||
} // namespace cdc_ft
|
||||
|
||||
#endif // CDC_FUSE_FS_CDC_FUSE_FS_H_
|
||||
61
cdc_fuse_fs/cdc_fuse_fs.vcxproj
Normal file
61
cdc_fuse_fs/cdc_fuse_fs.vcxproj
Normal file
@@ -0,0 +1,61 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|GGP">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>GGP</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|GGP">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>GGP</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{a537310c-0571-43d5-b7fe-c867f702294f}</ProjectGuid>
|
||||
<RootNamespace>cdc_fuse_fs</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|GGP'" Label="Configuration">
|
||||
<ConfigurationType>Makefile</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|GGP'" Label="Configuration">
|
||||
<ConfigurationType>Makefile</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|GGP'">
|
||||
<OutDir>$(SolutionDir)bazel-out\k8-dbg\bin\cdc_fuse_fs\</OutDir>
|
||||
<NMakePreprocessorDefinitions>$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
|
||||
<AdditionalOptions>/std:c++17</AdditionalOptions>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|GGP'">
|
||||
<OutDir>$(SolutionDir)bazel-out\k8-opt\bin\cdc_fuse_fs\</OutDir>
|
||||
<NMakePreprocessorDefinitions>$(NMakePreprocessorDefinitions)</NMakePreprocessorDefinitions>
|
||||
<AdditionalOptions>/std:c++17</AdditionalOptions>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
<Import Project="..\all_files.vcxitems" Label="Shared" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|GGP'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|GGP'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros">
|
||||
</PropertyGroup>
|
||||
<!-- Bazel setup -->
|
||||
<PropertyGroup>
|
||||
<BazelTargets>//cdc_fuse_fs</BazelTargets>
|
||||
<BazelOutputFile>cdc_fuse_fs</BazelOutputFile>
|
||||
<BazelIncludePaths>..\;..\third_party\absl;..\third_party\blake3\c;..\third_party\googletest\googletest\include;..\third_party\protobuf\src;..\third_party\grpc\include;$(AdditionalIncludeDirectories)</BazelIncludePaths>
|
||||
<BazelSourcePathPrefix>..\/</BazelSourcePathPrefix>
|
||||
</PropertyGroup>
|
||||
<Import Project="..\NMakeBazelProject.targets" />
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
2
cdc_fuse_fs/cdc_fuse_fs.vcxproj.filters
Normal file
2
cdc_fuse_fs/cdc_fuse_fs.vcxproj.filters
Normal file
@@ -0,0 +1,2 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003" />
|
||||
1146
cdc_fuse_fs/cdc_fuse_fs_test.cc
Normal file
1146
cdc_fuse_fs/cdc_fuse_fs_test.cc
Normal file
File diff suppressed because it is too large
Load Diff
122
cdc_fuse_fs/config_stream_client.cc
Normal file
122
cdc_fuse_fs/config_stream_client.cc
Normal file
@@ -0,0 +1,122 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cdc_fuse_fs/config_stream_client.h"
|
||||
|
||||
#include <thread>
|
||||
|
||||
#include "common/grpc_status.h"
|
||||
#include "common/log.h"
|
||||
#include "manifest/content_id.h"
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
using GetManifestIdRequest = proto::GetManifestIdRequest;
|
||||
using GetManifestIdResponse = proto::GetManifestIdResponse;
|
||||
using AckManifestIdReceivedRequest = proto::AckManifestIdReceivedRequest;
|
||||
using AckManifestIdReceivedResponse = proto::AckManifestIdReceivedResponse;
|
||||
using ConfigStreamService = proto::ConfigStreamService;
|
||||
|
||||
// Asynchronous gRPC streaming client for streaming configuration changes to
|
||||
// gamelets. The client runs inside the CDC FUSE and requests updated manifest
|
||||
// from the workstation.
|
||||
class ManifestIdReader {
|
||||
public:
|
||||
ManifestIdReader(ConfigStreamService::Stub* stub) : stub_(stub) {}
|
||||
|
||||
// Starts a GetManifestId() request and listens to the stream of manifest ids
|
||||
// sent from the workstation. Calls |callback| on every manifest id received.
|
||||
absl::Status StartListeningToManifestUpdates(
|
||||
std::function<absl::Status(const ContentIdProto&)> callback) {
|
||||
callback_ = callback;
|
||||
|
||||
GetManifestIdRequest request;
|
||||
assert(!reader_);
|
||||
reader_ = stub_->GetManifestId(&context_, request);
|
||||
if (!reader_)
|
||||
return absl::UnavailableError("Failed to create manifest id reader");
|
||||
|
||||
reader_thread_ =
|
||||
std::make_unique<std::thread>([this]() { ReadThreadMain(); });
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
// Thread that reads manifest ids from the GetManifestId() response stream.
|
||||
void ReadThreadMain() {
|
||||
GetManifestIdResponse response;
|
||||
LOG_INFO("Started manifest id reader thread")
|
||||
for (;;) {
|
||||
LOG_INFO("Waiting for manifest id update")
|
||||
if (!reader_->Read(&response)) break;
|
||||
|
||||
LOG_INFO("Received new manifest id '%s'",
|
||||
ContentId::ToHexString(response.id()));
|
||||
absl::Status status = callback_(response.id());
|
||||
if (!status.ok()) {
|
||||
LOG_ERROR("Failed to execute callback for manifest update '%s': '%s'",
|
||||
ContentId::ToHexString(response.id()), status.message());
|
||||
}
|
||||
}
|
||||
// This should happen if the server shuts down.
|
||||
LOG_INFO("Stopped manifest id reader thread")
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
if (!reader_thread_) return;
|
||||
|
||||
context_.TryCancel();
|
||||
if (reader_thread_->joinable()) reader_thread_->join();
|
||||
reader_thread_.reset();
|
||||
}
|
||||
|
||||
private:
|
||||
ConfigStreamService::Stub* stub_;
|
||||
grpc::ClientContext context_;
|
||||
std::unique_ptr<grpc::ClientReader<GetManifestIdResponse>> reader_;
|
||||
std::function<absl::Status(const ContentIdProto&)> callback_;
|
||||
std::unique_ptr<std::thread> reader_thread_;
|
||||
};
|
||||
|
||||
ConfigStreamClient::ConfigStreamClient(std::string instance,
|
||||
std::shared_ptr<grpc::Channel> channel)
|
||||
: instance_(std::move(instance)),
|
||||
stub_(ConfigStreamService::NewStub(std::move(channel))),
|
||||
read_client_(std::make_unique<ManifestIdReader>(stub_.get())) {}
|
||||
|
||||
ConfigStreamClient::~ConfigStreamClient() = default;
|
||||
|
||||
absl::Status ConfigStreamClient::StartListeningToManifestUpdates(
|
||||
std::function<absl::Status(const ContentIdProto&)> callback) {
|
||||
LOG_INFO("Starting to listen to manifest updates");
|
||||
return read_client_->StartListeningToManifestUpdates(callback);
|
||||
}
|
||||
|
||||
absl::Status ConfigStreamClient::SendManifestAck(ContentIdProto manifest_id) {
|
||||
AckManifestIdReceivedRequest request;
|
||||
request.set_gamelet_id(instance_);
|
||||
*request.mutable_manifest_id() = std::move(manifest_id);
|
||||
|
||||
grpc::ClientContext context_;
|
||||
AckManifestIdReceivedResponse response;
|
||||
RETURN_ABSL_IF_ERROR(
|
||||
stub_->AckManifestIdReceived(&context_, request, &response));
|
||||
return absl::OkStatus();
|
||||
}
|
||||
|
||||
void ConfigStreamClient::Shutdown() {
|
||||
LOG_INFO("Stopping to listen to manifest updates");
|
||||
read_client_->Shutdown();
|
||||
}
|
||||
|
||||
} // namespace cdc_ft
|
||||
67
cdc_fuse_fs/config_stream_client.h
Normal file
67
cdc_fuse_fs/config_stream_client.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CDC_FUSE_FS_CONFIG_STREAM_CLIENT_H_
|
||||
#define CDC_FUSE_FS_CONFIG_STREAM_CLIENT_H_
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "absl/status/status.h"
|
||||
#include "grpcpp/grpcpp.h"
|
||||
#include "manifest/manifest_proto_defs.h"
|
||||
#include "proto/asset_stream_service.grpc.pb.h"
|
||||
|
||||
namespace grpc_impl {
|
||||
class Channel;
|
||||
}
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
class ManifestIdReader;
|
||||
|
||||
class ConfigStreamClient {
|
||||
public:
|
||||
// |instance| is the id of the gamelet.
|
||||
// |channel| is a gRPC channel to use.
|
||||
ConfigStreamClient(std::string instance,
|
||||
std::shared_ptr<grpc::Channel> channel);
|
||||
~ConfigStreamClient();
|
||||
|
||||
// Sends a request to get a stream of manifest id updates. |callback| is
|
||||
// called from a background thread for every manifest id received.
|
||||
// Returns immediately without waiting for the first manifest id.
|
||||
absl::Status StartListeningToManifestUpdates(
|
||||
std::function<absl::Status(const ContentIdProto&)> callback);
|
||||
|
||||
// Sends a message to indicate that the |manifest_id| was received and FUSE
|
||||
// has been updated to use the new manifest.
|
||||
absl::Status SendManifestAck(ContentIdProto manifest_id);
|
||||
|
||||
// Stops listening for manifest updates.
|
||||
void Shutdown();
|
||||
|
||||
private:
|
||||
using ConfigStreamService = proto::ConfigStreamService;
|
||||
|
||||
const std::string instance_;
|
||||
const std::unique_ptr<ConfigStreamService::Stub> stub_;
|
||||
|
||||
std::unique_ptr<ManifestIdReader> read_client_;
|
||||
};
|
||||
|
||||
} // namespace cdc_ft
|
||||
|
||||
#endif // CDC_FUSE_FS_CONFIG_STREAM_CLIENT_H_
|
||||
33
cdc_fuse_fs/constants.h
Normal file
33
cdc_fuse_fs/constants.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CDC_FUSE_FS_CONSTANTS_H_
|
||||
#define CDC_FUSE_FS_CONSTANTS_H_
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
// FUSE prints this to stdout when the binary timestamp and file size match the
|
||||
// file on the workstation.
|
||||
static constexpr char kFuseUpToDate[] = "cdc_fuse_fs is up-to-date";
|
||||
|
||||
// FUSE prints this to stdout when the binary timestamp or file size does not
|
||||
// match the file on the workstation. It indicates that the binary has to be
|
||||
// redeployed.
|
||||
static constexpr char kFuseNotUpToDate[] = "cdc_fuse_fs is not up-to-date";
|
||||
|
||||
} // namespace cdc_ft
|
||||
|
||||
#endif // CDC_FUSE_FS_CONSTANTS_H_
|
||||
202
cdc_fuse_fs/main.cc
Normal file
202
cdc_fuse_fs/main.cc
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "absl/flags/flag.h"
|
||||
#include "absl/flags/parse.h"
|
||||
#include "absl_helper/jedec_size_flag.h"
|
||||
#include "cdc_fuse_fs/cdc_fuse_fs.h"
|
||||
#include "cdc_fuse_fs/constants.h"
|
||||
#include "common/gamelet_component.h"
|
||||
#include "common/log.h"
|
||||
#include "common/path.h"
|
||||
#include "data_store/data_provider.h"
|
||||
#include "data_store/disk_data_store.h"
|
||||
#include "data_store/grpc_reader.h"
|
||||
#include "grpcpp/channel.h"
|
||||
#include "grpcpp/create_channel.h"
|
||||
#include "grpcpp/support/channel_arguments.h"
|
||||
|
||||
namespace cdc_ft {
|
||||
namespace {
|
||||
|
||||
constexpr char kFuseFilename[] = "cdc_fuse_fs";
|
||||
constexpr char kLibFuseFilename[] = "libfuse.so";
|
||||
|
||||
absl::StatusOr<bool> IsUpToDate(const std::string& components_arg) {
|
||||
// Components are expected to reside in the same dir as the executable.
|
||||
std::string component_dir;
|
||||
RETURN_IF_ERROR(path::GetExeDir(&component_dir));
|
||||
|
||||
std::vector<GameletComponent> components =
|
||||
GameletComponent::FromCommandLineArgs(components_arg);
|
||||
std::vector<GameletComponent> our_components;
|
||||
absl::Status status =
|
||||
GameletComponent::Get({path::Join(component_dir, kFuseFilename),
|
||||
path::Join(component_dir, kLibFuseFilename)},
|
||||
&our_components);
|
||||
if (!status.ok() || components != our_components) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace cdc_ft
|
||||
|
||||
ABSL_FLAG(std::string, instance, "", "Gamelet instance id");
|
||||
ABSL_FLAG(
|
||||
std::string, components, "",
|
||||
"Whitespace-separated triples filename, size and timestamp of the "
|
||||
"workstation version of this binary and dependencies. Used for a fast "
|
||||
"up-to-date check.");
|
||||
ABSL_FLAG(uint16_t, port, 0, "Port to connect to on localhost");
|
||||
ABSL_FLAG(cdc_ft::JedecSize, prefetch_size, cdc_ft::JedecSize(512 << 10),
|
||||
"Additional data to request from the server when a FUSE read of "
|
||||
"maximum size is detected. This amount is added to the original "
|
||||
"request. Supports common unit suffixes K, M, G");
|
||||
ABSL_FLAG(std::string, cache_dir, "/var/cache/asset_streaming",
|
||||
"Cache directory to store data chunks.");
|
||||
ABSL_FLAG(int, cache_dir_levels, 2,
|
||||
"Fanout of sub-directories to create within the cache directory.");
|
||||
ABSL_FLAG(int, verbosity, 0, "Log verbosity");
|
||||
ABSL_FLAG(bool, stats, false, "Enable statistics");
|
||||
ABSL_FLAG(bool, check, false, "Execute consistency check");
|
||||
ABSL_FLAG(cdc_ft::JedecSize, cache_capacity,
|
||||
cdc_ft::JedecSize(cdc_ft::DiskDataStore::kDefaultCapacity),
|
||||
"Cache capacity. Supports common unit suffixes K, M, G.");
|
||||
ABSL_FLAG(uint32_t, cleanup_timeout, cdc_ft::DataProvider::kCleanupTimeoutSec,
|
||||
"Period in seconds at which instance cache cleanups are run");
|
||||
ABSL_FLAG(uint32_t, access_idle_timeout, cdc_ft::DataProvider::kAccessIdleSec,
|
||||
"Do not run instance cache cleanups for this many seconds after the "
|
||||
"last file access");
|
||||
|
||||
static_assert(static_cast<int>(absl::StatusCode::kOk) == 0, "kOk != 0");
|
||||
|
||||
// Usage: cdc_fuse_fs <ABSL_FLAGs> -- mount_dir [-d|-s|..]
|
||||
// Any args after -- are FUSE args, search third_party/fuse for FUSE_OPT_KEY or
|
||||
// FUSE_LIB_OPT (there doesn't seem to be a place where they're all described).
|
||||
int main(int argc, char* argv[]) {
|
||||
// Parse absl flags.
|
||||
std::vector<char*> mount_args = absl::ParseCommandLine(argc, argv);
|
||||
std::string instance = absl::GetFlag(FLAGS_instance);
|
||||
std::string components = absl::GetFlag(FLAGS_components);
|
||||
uint16_t port = absl::GetFlag(FLAGS_port);
|
||||
std::string cache_dir = absl::GetFlag(FLAGS_cache_dir);
|
||||
int cache_dir_levels = absl::GetFlag(FLAGS_cache_dir_levels);
|
||||
int verbosity = absl::GetFlag(FLAGS_verbosity);
|
||||
bool stats = absl::GetFlag(FLAGS_stats);
|
||||
bool consistency_check = absl::GetFlag(FLAGS_check);
|
||||
uint64_t cache_capacity = absl::GetFlag(FLAGS_cache_capacity).Size();
|
||||
unsigned int dp_cleanup_timeout = absl::GetFlag(FLAGS_cleanup_timeout);
|
||||
unsigned int dp_access_idle_timeout =
|
||||
absl::GetFlag(FLAGS_access_idle_timeout);
|
||||
|
||||
// Log to console. Logs are streamed back to the workstation through the SSH
|
||||
// session.
|
||||
cdc_ft::Log::Initialize(std::make_unique<cdc_ft::ConsoleLog>(
|
||||
cdc_ft::Log::VerbosityToLogLevel(verbosity)));
|
||||
|
||||
// Perform up-to-date check.
|
||||
absl::StatusOr<bool> is_up_to_date = cdc_ft::IsUpToDate(components);
|
||||
if (!is_up_to_date.ok()) {
|
||||
LOG_ERROR("Failed to check file system freshness: %s",
|
||||
is_up_to_date.status().ToString());
|
||||
return static_cast<int>(is_up_to_date.status().code());
|
||||
}
|
||||
if (!*is_up_to_date) {
|
||||
printf("%s\n", cdc_ft::kFuseNotUpToDate);
|
||||
return 0;
|
||||
}
|
||||
printf("%s\n", cdc_ft::kFuseUpToDate);
|
||||
fflush(stdout);
|
||||
|
||||
// Create fs. The rest of the flags are mount flags, so pass them along.
|
||||
absl::Status status = cdc_ft::cdc_fuse_fs::Initialize(
|
||||
static_cast<int>(mount_args.size()), mount_args.data());
|
||||
if (!status.ok()) {
|
||||
LOG_ERROR("Failed to initialize file system: %s", status.ToString());
|
||||
return static_cast<int>(status.code());
|
||||
}
|
||||
|
||||
// Create disk data store.
|
||||
absl::StatusOr<std::unique_ptr<cdc_ft::DiskDataStore>> store =
|
||||
cdc_ft::DiskDataStore::Create(cache_dir_levels, cache_dir, false);
|
||||
if (!store.ok()) {
|
||||
LOG_ERROR("Failed to initialize the chunk cache in directory '%s': %s",
|
||||
absl::GetFlag(FLAGS_cache_dir), store.status().ToString());
|
||||
return 1;
|
||||
}
|
||||
LOG_INFO("Setting cache capacity to '%u'", cache_capacity);
|
||||
store.value()->SetCapacity(cache_capacity);
|
||||
LOG_INFO("Caching chunks in '%s'", store.value()->RootDir());
|
||||
|
||||
// Start a gRpc client.
|
||||
std::string client_address = absl::StrFormat("localhost:%u", port);
|
||||
grpc::ChannelArguments channel_args;
|
||||
channel_args.SetMaxReceiveMessageSize(-1);
|
||||
std::shared_ptr<grpc::Channel> grpc_channel = grpc::CreateCustomChannel(
|
||||
client_address, grpc::InsecureChannelCredentials(), channel_args);
|
||||
std::vector<std::unique_ptr<cdc_ft::DataStoreReader>> readers;
|
||||
readers.emplace_back(
|
||||
std::make_unique<cdc_ft::GrpcReader>(grpc_channel, stats));
|
||||
cdc_ft::GrpcReader* grpc_reader =
|
||||
static_cast<cdc_ft::GrpcReader*>(readers[0].get());
|
||||
|
||||
// Send all cached content ids to the client if statistics are enabled.
|
||||
if (stats) {
|
||||
LOG_INFO("Sending all cached content ids");
|
||||
absl::StatusOr<std::vector<cdc_ft::ContentIdProto>> ids =
|
||||
store.value()->List();
|
||||
if (!ids.ok()) {
|
||||
LOG_ERROR("Failed to get all cached content ids: %s",
|
||||
ids.status().ToString());
|
||||
return 1;
|
||||
}
|
||||
status = grpc_reader->SendCachedContentIds(*ids);
|
||||
if (!status.ok()) {
|
||||
LOG_ERROR("Failed to send all cached content ids: %s", status.ToString());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Create data provider.
|
||||
size_t prefetch_size = absl::GetFlag(FLAGS_prefetch_size).Size();
|
||||
cdc_ft::DataProvider data_provider(std::move(*store), std::move(readers),
|
||||
prefetch_size, dp_cleanup_timeout,
|
||||
dp_access_idle_timeout);
|
||||
|
||||
if (!cdc_ft::cdc_fuse_fs::StartConfigClient(instance, grpc_channel).ok()) {
|
||||
LOG_ERROR("Could not start reading configuration updates'");
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Run FUSE.
|
||||
LOG_INFO("Running filesystem");
|
||||
status = cdc_ft::cdc_fuse_fs::Run(&data_provider, consistency_check);
|
||||
if (!status.ok()) {
|
||||
LOG_ERROR("Filesystem stopped with error: %s", status.ToString());
|
||||
}
|
||||
LOG_INFO("Filesystem ran successfully and shuts down");
|
||||
|
||||
data_provider.Shutdown();
|
||||
cdc_ft::cdc_fuse_fs::Shutdown();
|
||||
cdc_ft::Log::Shutdown();
|
||||
|
||||
static_assert(static_cast<int>(absl::StatusCode::kOk) == 0, "kOk != 0");
|
||||
return static_cast<int>(status.code());
|
||||
}
|
||||
111
cdc_fuse_fs/mock_libfuse.cc
Normal file
111
cdc_fuse_fs/mock_libfuse.cc
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2022 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "cdc_fuse_fs/mock_libfuse.h"
|
||||
|
||||
#include <cassert>
|
||||
#include <cstring>
|
||||
|
||||
namespace cdc_ft {
|
||||
namespace {
|
||||
MockLibFuse* g_fuse;
|
||||
}
|
||||
|
||||
MockLibFuse::MockLibFuse() {
|
||||
assert(!g_fuse);
|
||||
g_fuse = this;
|
||||
}
|
||||
|
||||
MockLibFuse::~MockLibFuse() {
|
||||
assert(g_fuse == this);
|
||||
g_fuse = nullptr;
|
||||
}
|
||||
|
||||
void MockLibFuse::SetUid(int uid) {
|
||||
assert(g_fuse == this);
|
||||
g_fuse->context.uid = uid;
|
||||
}
|
||||
|
||||
void MockLibFuse::SetGid(int gid) {
|
||||
assert(g_fuse == this);
|
||||
g_fuse->context.gid = gid;
|
||||
}
|
||||
|
||||
size_t fuse_add_direntry(fuse_req_t req, char* buf, size_t bufsize,
|
||||
const char* name, const struct stat* stbuf,
|
||||
off_t off) {
|
||||
assert(g_fuse);
|
||||
if (bufsize >= sizeof(MockLibFuse::DirEntry)) {
|
||||
assert(stbuf);
|
||||
auto* entry = reinterpret_cast<MockLibFuse::DirEntry*>(buf);
|
||||
strncpy(entry->name, name, sizeof(entry->name));
|
||||
entry->name[sizeof(entry->name) - 1] = 0;
|
||||
entry->ino = stbuf->st_ino;
|
||||
entry->mode = stbuf->st_mode;
|
||||
entry->off = off;
|
||||
}
|
||||
return sizeof(MockLibFuse::DirEntry);
|
||||
}
|
||||
|
||||
int fuse_reply_attr(fuse_req_t req, const struct stat* attr,
|
||||
double attr_timeout) {
|
||||
assert(g_fuse);
|
||||
assert(attr);
|
||||
g_fuse->attrs.emplace_back(*attr, attr_timeout);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_reply_buf(fuse_req_t req, const char* buf, size_t size) {
|
||||
assert(g_fuse);
|
||||
std::vector<char> data;
|
||||
if (buf && size > 0) {
|
||||
data.insert(data.end(), buf, buf + size);
|
||||
}
|
||||
g_fuse->buffers.push_back(std::move(data));
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param* e) {
|
||||
assert(g_fuse);
|
||||
assert(e);
|
||||
g_fuse->entries.push_back(*e);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_reply_err(fuse_req_t req, int err) {
|
||||
assert(g_fuse);
|
||||
g_fuse->errors.push_back(err);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info* fi) {
|
||||
assert(g_fuse);
|
||||
assert(fi);
|
||||
g_fuse->open_files.push_back(*fi);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void fuse_reply_none(fuse_req_t req) {
|
||||
assert(g_fuse);
|
||||
++g_fuse->none_counter;
|
||||
}
|
||||
|
||||
int fuse_reply_statfs(fuse_req_t req, const struct statvfs* stbuf) { return 0; }
|
||||
|
||||
struct fuse_context* fuse_get_context() {
|
||||
assert(g_fuse);
|
||||
return &g_fuse->context;
|
||||
}
|
||||
|
||||
} // namespace cdc_ft
|
||||
123
cdc_fuse_fs/mock_libfuse.h
Normal file
123
cdc_fuse_fs/mock_libfuse.h
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright 2022 Google LLC
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CDC_FUSE_FS_MOCK_LIBFUSE_H_
|
||||
#define CDC_FUSE_FS_MOCK_LIBFUSE_H_
|
||||
|
||||
#include <fcntl.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
namespace cdc_ft {
|
||||
|
||||
//
|
||||
// The interface below mimics the part of the FUSE low level interface we need.
|
||||
// See include/fuse_lowlevel.h for more information.
|
||||
//
|
||||
|
||||
// Definitions.
|
||||
using fuse_ino_t = uint64_t;
|
||||
using fuse_req_t = void*;
|
||||
using nlink_t = uint64_t;
|
||||
|
||||
constexpr fuse_ino_t FUSE_ROOT_ID = 1;
|
||||
#ifndef O_DIRECT
|
||||
constexpr uint32_t O_DIRECT = 040000;
|
||||
#endif
|
||||
|
||||
struct fuse_entry_param {
|
||||
fuse_ino_t ino;
|
||||
struct stat attr;
|
||||
double attr_timeout;
|
||||
double entry_timeout;
|
||||
};
|
||||
|
||||
struct fuse_file_info {
|
||||
int flags = O_RDONLY;
|
||||
unsigned int direct_io : 1;
|
||||
unsigned int keep_cache : 1;
|
||||
|
||||
fuse_file_info() : direct_io(0), keep_cache(0) {}
|
||||
explicit fuse_file_info(int flags)
|
||||
: flags(flags), direct_io(0), keep_cache(0) {}
|
||||
};
|
||||
|
||||
struct fuse_forget_data {
|
||||
uint64_t ino;
|
||||
uint64_t nlookup;
|
||||
};
|
||||
|
||||
struct fuse_context {
|
||||
int uid;
|
||||
int gid;
|
||||
};
|
||||
|
||||
struct statvfs {
|
||||
uint32_t f_bsize;
|
||||
uint32_t f_namemax;
|
||||
};
|
||||
|
||||
// FUSE reply/action functions.
|
||||
size_t fuse_add_direntry(fuse_req_t req, char* buf, size_t bufsize,
|
||||
const char* name, const struct stat* stbuf, off_t off);
|
||||
int fuse_reply_attr(fuse_req_t req, const struct stat* attr,
|
||||
double attr_timeout);
|
||||
int fuse_reply_buf(fuse_req_t req, const char* buf, size_t size);
|
||||
int fuse_reply_entry(fuse_req_t req, const struct fuse_entry_param* e);
|
||||
int fuse_reply_err(fuse_req_t req, int err);
|
||||
int fuse_reply_open(fuse_req_t req, const struct fuse_file_info* fi);
|
||||
void fuse_reply_none(fuse_req_t req);
|
||||
int fuse_reply_statfs(fuse_req_t req, const struct statvfs* stbuf);
|
||||
struct fuse_context* fuse_get_context();
|
||||
|
||||
// FUSE mocking class. Basically just a recorder for the fuse_* callbacks above.
|
||||
struct MockLibFuse {
|
||||
public:
|
||||
MockLibFuse();
|
||||
~MockLibFuse();
|
||||
|
||||
struct Attr {
|
||||
struct stat value;
|
||||
double timeout;
|
||||
Attr(struct stat value, double timeout)
|
||||
: value(std::move(value)), timeout(timeout) {}
|
||||
};
|
||||
void SetUid(int uid);
|
||||
void SetGid(int gid);
|
||||
|
||||
// Struct stored in the buffer |buf| by fuse_add_direntry().
|
||||
// Uses a maximum name size for simplicity.
|
||||
struct DirEntry {
|
||||
fuse_ino_t ino;
|
||||
uint32_t mode;
|
||||
char name[32];
|
||||
off_t off;
|
||||
};
|
||||
|
||||
std::vector<fuse_entry_param> entries;
|
||||
std::vector<Attr> attrs;
|
||||
std::vector<int> errors;
|
||||
std::vector<fuse_file_info> open_files;
|
||||
std::vector<std::vector<char>> buffers;
|
||||
unsigned int none_counter = 0;
|
||||
fuse_context context;
|
||||
};
|
||||
|
||||
} // namespace cdc_ft
|
||||
|
||||
#endif // CDC_FUSE_FS_MOCK_LIBFUSE_H_
|
||||
Reference in New Issue
Block a user