blob: 9b12c3bc983efcc898f982a7ad8b37a7e6c818b7 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 "kudu/fs/dir_util.h"
#include <cstdint>
#include <ostream>
#include <set>
#include <utility>
#include <gflags/gflags_declare.h>
#include <glog/logging.h>
#include "kudu/fs/fs.pb.h"
#include "kudu/gutil/map-util.h"
#include "kudu/gutil/port.h"
#include "kudu/gutil/strings/substitute.h"
#include "kudu/util/env.h"
#include "kudu/util/env_util.h"
#include "kudu/util/path_util.h"
#include "kudu/util/pb_util.h"
#include "kudu/util/scoped_cleanup.h"
DECLARE_bool(enable_data_block_fsync);
using kudu::pb_util::CreateMode;
using std::set;
using std::string;
using std::unique_ptr;
using strings::Substitute;
namespace kudu {
namespace fs {
namespace {
const char kHolePunchErrorMsg[] =
"Error during hole punch test. The log block manager requires a "
"filesystem with hole punching support such as ext4 or xfs. On el6, "
"kernel version 2.6.32-358 or newer is required. To run without hole "
"punching (at the cost of some efficiency and scalability), reconfigure "
"Kudu to use the file block manager. Refer to the Kudu documentation for "
"more details. WARNING: the file block manager is not suitable for "
"production use and should be used only for small-scale evaluation and "
"development on systems where hole-punching is not available. It's "
"impossible to switch between block managers after data is written to the "
"server. Raw error message follows";
Status CheckHolePunch(Env* env, const string& path) {
// Arbitrary constants.
static uint64_t kFileSize = 4096 * 4;
static uint64_t kHoleOffset = 4096;
static uint64_t kHoleSize = 8192;
static uint64_t kPunchedFileSize = kFileSize - kHoleSize;
// Open the test file.
string filename = JoinPathSegments(path, "hole_punch_test_file");
unique_ptr<RWFile> file;
RWFileOptions opts;
RETURN_NOT_OK(env->NewRWFile(opts, filename, &file));
// The file has been created; delete it on exit no matter what happens.
auto file_deleter = MakeScopedCleanup([&]() {
WARN_NOT_OK(env->DeleteFile(filename),
"Could not delete file " + filename);
});
// Preallocate it, making sure the file's size is what we'd expect.
uint64_t sz;
RETURN_NOT_OK(file->PreAllocate(0, kFileSize, RWFile::CHANGE_FILE_SIZE));
RETURN_NOT_OK(env->GetFileSizeOnDisk(filename, &sz));
if (sz != kFileSize) {
return Status::IOError(Substitute(
"Unexpected pre-punch file size for $0: expected $1 but got $2",
filename, kFileSize, sz));
}
// Punch the hole, testing the file's size again.
RETURN_NOT_OK(file->PunchHole(kHoleOffset, kHoleSize));
RETURN_NOT_OK(env->GetFileSizeOnDisk(filename, &sz));
if (sz != kPunchedFileSize) {
return Status::IOError(Substitute(
"Unexpected post-punch file size for $0: expected $1 but got $2",
filename, kPunchedFileSize, sz));
}
return Status::OK();
}
} // anonymous namespace
// Evaluates 'status_expr' and if it results in a disk-failure error, logs a
// message and marks the instance as unhealthy, returning with no error.
//
// Note: A disk failure may thwart attempts to read directory entries at the OS
// level, leading to NotFound errors when reading the instance files. As such,
// we treat missing instances the same way we treat those that yield more
// blatant disk failure POSIX codes.
//
// Note: if a non-disk-failure error is produced, the instance will remain
// healthy. These errors should be handled externally.
#define RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(status_expr, msg) do { \
const Status& _s = (status_expr); \
if (PREDICT_FALSE(!_s.ok())) { \
const Status _s_prepended = _s.CloneAndPrepend(msg); \
if (_s.IsNotFound() || _s.IsDiskFailure()) { \
health_status_ = _s_prepended; \
VLOG(1) << "Directory instance has status: " << _s_prepended.ToString(); \
return Status::OK(); \
} \
return _s_prepended; \
} \
} while (0)
DirInstanceMetadataFile::DirInstanceMetadataFile(Env* env,
string uuid,
string dir_type,
string filename)
: env_(env),
uuid_(std::move(uuid)),
dir_type_(std::move(dir_type)),
filename_(std::move(filename)) {}
DirInstanceMetadataFile::~DirInstanceMetadataFile() {
if (lock_) {
WARN_NOT_OK(Unlock(), Substitute("Failed to unlock file $0", filename_));
}
}
Status DirInstanceMetadataFile::Create(const set<string>& all_uuids,
bool* created_dir) {
DCHECK(!lock_);
DCHECK(ContainsKey(all_uuids, uuid_));
const string dir_name = dir();
bool created;
// Create the directory if needed.
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(
env_util::CreateDirIfMissing(env_, dir_name, &created),
Substitute("Could not create directory $0", dir_name));
auto cleanup_dir_on_failure = MakeScopedCleanup([&] {
if (created) {
WARN_NOT_OK(env_->DeleteDir(dir_name), "Could not remove newly-created directory");
}
});
// If we're initializing the log block manager, check that we support
// hole-punching.
if (dir_type_ == "log") {
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(CheckHolePunch(env_, dir_name),
kHolePunchErrorMsg);
}
// Create a temporary file with which to fetch the filesystem's block size.
//
// This is a safer bet than using the parent directory as some filesystems
// advertise different block sizes for directories than for files. On top of
// that, the value may inform intra-file layout decisions made by Kudu, so
// it's more correct to derive it from a file in any case.
string created_filename;
string tmp_template = JoinPathSegments(
dir_name, Substitute("getblocksize$0.XXXXXX", kTmpInfix));
unique_ptr<WritableFile> tmp_file;
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(
env_->NewTempWritableFile(WritableFileOptions(),
tmp_template,
&created_filename, &tmp_file),
"failed to create temp file while checking block size");
SCOPED_CLEANUP({
WARN_NOT_OK(env_->DeleteFile(created_filename),
"could not delete temporary file");
});
uint64_t block_size;
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(env_->GetBlockSize(created_filename, &block_size),
"failed to check block size");
// Set up the directory set.
DirInstanceMetadataPB new_instance;
DirSetPB* new_dir_set = new_instance.mutable_dir_set();
new_dir_set->set_uuid(uuid_);
new_dir_set->mutable_all_uuids()->Reserve(all_uuids.size());
for (const string& u : all_uuids) {
new_dir_set->add_all_uuids(u);
}
// And the rest of the metadata.
new_instance.set_dir_type(dir_type_);
new_instance.set_filesystem_block_size_bytes(block_size);
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(pb_util::WritePBContainerToPath(
env_, filename_, new_instance,
pb_util::NO_OVERWRITE,
FLAGS_enable_data_block_fsync ? pb_util::SYNC : pb_util::NO_SYNC),
"failed to write PB");
// Now that we're returning success, we don't need to clean anything up, and
// we can indicate to callers there is a new directory to clean up (if
// appropriate).
cleanup_dir_on_failure.cancel();
if (created_dir) {
*created_dir = created;
}
return Status::OK();
}
Status DirInstanceMetadataFile::LoadFromDisk() {
DCHECK(!lock_) <<
"Opening a metadata file that's already locked would release the lock";
unique_ptr<DirInstanceMetadataPB> pb(new DirInstanceMetadataPB());
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(pb_util::ReadPBContainerFromPath(env_, filename_, pb.get()),
Substitute("Failed to read metadata file from $0", filename_));
if (pb->dir_type() != dir_type_) {
return Status::IOError(Substitute(
"existing instance was written using the '$0' format; cannot restart "
"with a different format type '$1'",
pb->dir_type(), dir_type_));
}
uint64_t block_size;
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(env_->GetBlockSize(filename_, &block_size),
Substitute("Failed to load metadata file. Could not get block size of $0", filename_));
if (pb->filesystem_block_size_bytes() != block_size) {
return Status::IOError("Wrong filesystem block size", Substitute(
"Expected $0 but was $1", pb->filesystem_block_size_bytes(), block_size));
}
uuid_ = pb->dir_set().uuid();
metadata_ = std::move(pb);
return Status::OK();
}
Status DirInstanceMetadataFile::Lock() {
DCHECK(!lock_);
FileLock* lock;
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(env_->LockFile(filename_, &lock),
"Could not lock instance file. Make sure that "
"Kudu is not already running and you are not trying to run "
"Kudu with a different user than before");
lock_.reset(lock);
return Status::OK();
}
Status DirInstanceMetadataFile::Unlock() {
DCHECK(lock_);
RETURN_NOT_OK_FAIL_INSTANCE_PREPEND(env_->UnlockFile(lock_.release()),
Substitute("Could not unlock $0", filename_));
return Status::OK();
}
} // namespace fs
} // namespace kudu