blob: d162ae890a470673a30de6d06dd4977811f35e1a [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.
#pragma once
#include <stdint.h>
#include <functional>
#include <memory>
#include <mutex>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
#include "kudu/gutil/macros.h"
#include "kudu/gutil/ref_counted.h"
#include "kudu/util/locks.h"
#include "kudu/util/metrics.h"
#include "kudu/util/monotime.h"
#include "kudu/util/random.h"
#include "kudu/util/status.h"
namespace kudu {
class Env;
class ThreadPool;
// We pass around the results of canonicalization to indicate to the
// directory manager which, if any, failed to canonicalize.
//
// TODO(awong): move the canonicalization of directories into the
// directory manager so we can avoid this extra plumbing.
struct CanonicalizedRootAndStatus {
std::string path;
Status status;
};
typedef std::vector<CanonicalizedRootAndStatus> CanonicalizedRootsList;
namespace fs {
typedef std::unordered_map<int, std::string> UuidByUuidIndexMap;
typedef std::unordered_map<std::string, int> UuidIndexByUuidMap;
class DirInstanceMetadataFile;
// Defines the behavior when opening a directory manager that has an
// inconsistent or incomplete set of instance files.
enum UpdateInstanceBehavior {
// If the directories don't match the on-disk dir sets, update the on-disk
// data to match if not in read-only mode.
UPDATE_AND_IGNORE_FAILURES,
// Like UPDATE_AND_IGNORE_FAILURES, but will return an error if any of the updates to the
// on-disk files fail.
UPDATE_AND_ERROR_ON_FAILURE,
// If the directories don't match the on-disk dir sets, continue without
// updating the on-disk data.
DONT_UPDATE
};
struct DirMetrics {
scoped_refptr<AtomicGauge<uint64_t>> dirs_failed;
scoped_refptr<AtomicGauge<uint64_t>> dirs_full;
};
// Detected type of filesystem.
enum class FsType {
// ext2, ext3, or ext4.
EXT,
// SGI xfs.
XFS,
// None of the above.
OTHER
};
// Representation of a directory (e.g. a data directory).
class Dir {
public:
Dir(Env* env,
DirMetrics* metrics,
FsType fs_type,
std::string dir,
std::unique_ptr<DirInstanceMetadataFile> metadata_file,
std::unique_ptr<ThreadPool> pool);
virtual ~Dir();
// Shuts down this dir's thread pool, waiting for any closures submitted via
// ExecClosure() to finish first.
void Shutdown();
// Run a task on this dir's thread pool.
//
// Normally the task is performed asynchronously. However, if submission to
// the pool fails, it runs synchronously on the current thread.
void ExecClosure(const std::function<void()>& task);
// Waits for any outstanding closures submitted via ExecClosure() to finish.
void WaitOnClosures();
// Tests whether the directory is full by comparing the free space of its
// underlying filesystem with a predefined "reserved" space value.
//
// If 'mode' is EXPIRED_ONLY, performs the test only if the dir was last
// determined to be full some time ago. If 'mode' is ALWAYS, the test is
// performed regardless.
//
// Only returns a bad Status in the event of a real error; fullness is
// reflected via is_full().
enum class RefreshMode {
EXPIRED_ONLY,
ALWAYS,
};
Status RefreshAvailableSpace(RefreshMode mode);
FsType fs_type() const { return fs_type_; }
// Return the full path of this directory.
const std::string& dir() const { return dir_; }
const DirInstanceMetadataFile* instance() const {
return metadata_file_.get();
}
bool is_full() const {
std::lock_guard<simple_spinlock> l(lock_);
return is_full_;
}
int64_t available_bytes() {
std::lock_guard<simple_spinlock> l(lock_);
return available_bytes_;
}
// The amount of time to cache the amount of available space in this
// directory.
virtual int available_space_cache_secs() const = 0;
// The number of bytes to reserve in each directory for non-Kudu usage. A
// value of -1 means 1% of the disk space in a directory will be reserved.
virtual int reserved_bytes() const = 0;
private:
Env* env_;
DirMetrics* metrics_;
const FsType fs_type_;
const std::string dir_;
const std::unique_ptr<DirInstanceMetadataFile> metadata_file_;
const std::unique_ptr<ThreadPool> pool_;
bool is_shutdown_;
// Protects 'last_space_check_', 'is_full_' and available_bytes_.
mutable simple_spinlock lock_;
MonoTime last_space_check_;
bool is_full_;
// The available bytes of this dir, updated by RefreshAvailableSpace.
int64_t available_bytes_;
DISALLOW_COPY_AND_ASSIGN(Dir);
};
struct DirManagerOptions {
public:
// The type of directory this directory manager should support.
//
// Must not be empty.
std::string dir_type;
// The entity under which all metrics should be grouped. If null, metrics
// will not be produced.
//
// Defaults to null.
scoped_refptr<MetricEntity> metric_entity;
// Whether the directory manager should only allow reading.
//
// Defaults to false.
bool read_only;
// Whether to update the on-disk instances when opening directories if
// inconsistencies are detected.
//
// Defaults to UPDATE_AND_IGNORE_FAILURES.
UpdateInstanceBehavior update_instances;
protected:
explicit DirManagerOptions(const std::string& dir_type);
};
class DirManager {
public:
enum class LockMode {
MANDATORY,
OPTIONAL,
NONE,
};
// Returns the root names from the input 'root_list'.
static std::vector<std::string> GetRootNames(const CanonicalizedRootsList& root_list);
virtual ~DirManager();
// Shuts down all directories' thread pools.
void Shutdown();
// Waits on all directories' thread pools.
void WaitOnClosures();
// Returns a list of all dirs.
const std::vector<std::unique_ptr<Dir>>& dirs() const {
return dirs_;
}
// Adds 'uuid_idx' to the set of failed directories. This directory will no
// longer be used. Logs an error message prefixed with 'error_message'
// describing what directories are affected.
//
// Returns an error if all directories have failed.
Status MarkDirFailed(int uuid_idx, const std::string& error_message = "");
// Fails the directory specified by 'uuid' and logs a warning if all
// directories have failed.
void MarkDirFailedByUuid(const std::string& uuid);
// Returns whether or not the 'uuid_idx' refers to a failed directory.
bool IsDirFailed(int uuid_idx) const;
// Returns whether the given tablet exists in a failed directory.
bool IsTabletInFailedDir(const std::string& tablet_id) const;
std::set<int> GetFailedDirs() const {
shared_lock<rw_spinlock> group_lock(dir_group_lock_.get_lock());
return failed_dirs_;
}
// Return a list of the canonicalized root directory names.
std::vector<std::string> GetRoots() const;
// Return a list of the canonicalized directory names.
std::vector<std::string> GetDirs() const;
// Finds a directory by uuid index, returning null if it can't be found.
//
// More information on uuid indexes and their relation to directories
// can be found next to DirSetPB in fs.proto.
Dir* FindDirByUuidIndex(int uuid_idx) const;
// Finds a uuid index by directory, returning false if it can't be found.
bool FindUuidIndexByDir(Dir* dir, int* uuid_idx) const;
// Finds a uuid index by root path, returning false if it can't be found.
bool FindUuidIndexByRoot(const std::string& root, int* uuid_idx) const;
// Finds a uuid index by UUID, returning false if it can't be found.
bool FindUuidIndexByUuid(const std::string& uuid, int* uuid_idx) const;
// Finds a UUID by canonicalized root name, returning false if it can't be found.
bool FindUuidByRoot(const std::string& root, std::string* uuid) const;
// Finds the set of tablet IDs that are registered to use the directory with
// the given UUID index.
std::set<std::string> FindTabletsByDirUuidIdx(int uuid_idx) const;
// Create a new directory using the appropriate directory implementation.
virtual std::unique_ptr<Dir> CreateNewDir(Env* env,
DirMetrics* metrics,
FsType fs_type,
std::string dir,
std::unique_ptr<DirInstanceMetadataFile>,
std::unique_ptr<ThreadPool> pool) = 0;
protected:
// The name to be used by this directory manager for each sub-directory of
// each directory root.
virtual const char* dir_name() const = 0;
// The name to be used by this directory manager for each instance file
// corresponding to this directory manager.
virtual const char* instance_metadata_filename() const = 0;
// Whether to sync the directories when updating this manager's directories.
virtual bool sync_dirs() const = 0;
// Whether to lock the directories to prevent concurrent usage. Note:
// read-only concurrent usage is still allowed.
virtual bool lock_dirs() const = 0;
// The max number of directories to be managed.
virtual int max_dirs() const = 0;
DirManager(Env* env,
std::unique_ptr<DirMetrics> dir_metrics,
int num_threads_per_dir,
const DirManagerOptions& opts,
CanonicalizedRootsList canonicalized_data_roots);
// Initializes the data directories on disk. Returns an error if initialized
// directories already exist.
//
// Note: this doesn't initialize any in-memory state for the directory
// manager.
virtual Status Create();
// Opens existing instance files from disk and indexes the files found.
//
// Returns an error if the number of on-disk directories found exceeds the
// max allowed, if locks need to be acquired and cannot be, or if there are
// no healthy directories.
//
// If appropriate, this will create any missing directories and rewrite
// existing instance files to be consistent with each other.
virtual Status Open();
// Populates the maps to index the given directories.
virtual Status PopulateDirectoryMaps(const std::vector<std::unique_ptr<Dir>>& dirs);
// Helper function to add a directory to the internal maps. Assumes that the
// UUID, UUID index, and directory name have not already been inserted.
void InsertToMaps(const std::string& uuid, int idx, Dir* dir);
// Loads the instance files for each directory root.
//
// On success, 'instance_files' contains instance objects, including those
// that failed to load because they were missing or because of a disk
// error; they are still considered "loaded" and are labeled unhealthy
// internally. 'has_existing_instances' is set to true if any of the instance
// files are healthy.
//
// Returns an error if an instance file fails in an irreconcileable way (e.g.
// the file is locked).
Status LoadInstances(
std::vector<std::unique_ptr<DirInstanceMetadataFile>>* instance_files,
bool* has_existing_instances);
// Takes the set of instance files, does some basic verification on them,
// creates any that don't exist on disk, and updates any that have a
// different set of UUIDs stored than the expected set.
//
// Returns an error if there is a configuration error, e.g. if the existing
// instances believe there should be a different block size.
//
// If in UPDATE_AND_IGNORE_FAILURES mode, an error is not returned in the event of a disk
// error. Instead, it is up to the caller to reload the instance files and
// proceed if healthy enough.
//
// If in UPDATE_AND_ERROR_ON_FAILURE mode, a failure to update instances will
// surface as an error.
Status CreateNewDirectoriesAndUpdateInstances(
std::vector<std::unique_ptr<DirInstanceMetadataFile>> instances);
// Updates the on-disk instance files specified by 'instances_to_update'
// (presumably those whose 'all_uuids' field doesn't match 'new_all_uuids')
// using the contents of 'new_all_uuids', skipping any unhealthy instance
// files.
//
// If in UPDATE_AND_IGNORE_FAILURES mode, this is best effort. If any of the instance
// updates fail (e.g. due to a disk error) in this mode, this will log a
// warning about the failed updates and return OK.
//
// If in UPDATE_AND_ERROR_ON_FAILURE mode, any failure will immediately attempt
// to clean up any altered state and return with an error.
Status UpdateHealthyInstances(
const std::vector<std::unique_ptr<DirInstanceMetadataFile>>& instances_to_update,
const std::set<std::string>& new_all_uuids);
// The environment to be used for all directory operations.
Env* env_;
// The number of threads to allocate per directory threadpool.
const int num_threads_per_dir_;
// The options that the Dirmanager was created with.
const DirManagerOptions opts_;
// The canonicalized roots provided to the constructor, taken verbatim.
// Common roots in the collections have been deduplicated.
const CanonicalizedRootsList canonicalized_fs_roots_;
// Directories tracked by this manager.
std::vector<std::unique_ptr<Dir>> dirs_;
// Set of metrics relating to the health of the directories that this manager
// is tracking.
std::unique_ptr<DirMetrics> metrics_;
// Lock protecting access to the directory group maps and to failed_dirs_. A
// percpu_rwlock is used so threads attempting to read (e.g. to get the next
// directory for an operation) do not block each other, while threads
// attempting to write (e.g. to create a new tablet, thereby registering
// directories per tablet) block all threads.
mutable percpu_rwlock dir_group_lock_;
// RNG used to select directories.
mutable ThreadSafeRandom rng_;
typedef std::unordered_map<std::string, std::string> UuidByRootMap;
UuidByRootMap uuid_by_root_;
typedef std::unordered_map<int, Dir*> UuidIndexMap;
UuidIndexMap dir_by_uuid_idx_;
typedef std::unordered_map<Dir*, int> ReverseUuidIndexMap;
ReverseUuidIndexMap uuid_idx_by_dir_;
typedef std::unordered_map<int, std::set<std::string>> TabletsByUuidIndexMap;
TabletsByUuidIndexMap tablets_by_uuid_idx_map_;
UuidByUuidIndexMap uuid_by_idx_;
UuidIndexByUuidMap idx_by_uuid_;
typedef std::set<int> FailedDirSet;
FailedDirSet failed_dirs_;
DISALLOW_COPY_AND_ASSIGN(DirManager);
};
} // namespace fs
} // namespace kudu