blob: f5a6da95fa74dca90231c668a3dfcb2a4ddcaed3 [file]
/*
* 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 "namespace.h"
#include "jsoncons/json.hpp"
// Error messages
constexpr const char* kErrNamespaceExists = "the namespace already exists";
constexpr const char* kErrTokenExists = "the token already exists";
constexpr const char* kErrNamespaceNotFound = "the namespace was not found";
constexpr const char* kErrRequiredPassEmpty = "forbidden to add namespace when requirepass was empty";
constexpr const char* kErrClusterModeEnabled = "forbidden to add namespace when cluster mode was enabled";
constexpr const char* kErrDeleteDefaultNamespace = "forbidden to delete the default namespace";
constexpr const char* kErrAddDefaultNamespace = "forbidden to add the default namespace";
constexpr const char* kErrInvalidToken = "the token is duplicated with requirepass or masterauth";
constexpr const char* kErrCantModifyNamespace =
"modify namespace requires the server is running with a configuration file or enabled namespace replication";
Status IsNamespaceLegal(const std::string& ns) {
if (ns.size() > UINT8_MAX) {
return {Status::NotOK, fmt::format("size exceed limit {}", UINT8_MAX)};
}
char last_char = ns.back();
if (last_char == std::numeric_limits<char>::max()) {
return {Status::NotOK, "namespace contain illegal letter"};
}
return Status::OK();
}
bool Namespace::IsAllowModify() const {
auto config = storage_->GetConfig();
return config->HasConfigFile() || config->repl_namespace_enabled;
}
Status Namespace::loadFromDB(std::map<std::string, std::string>* db_tokens) const {
std::string value;
engine::Context ctx(storage_);
auto cf = storage_->GetCFHandle(ColumnFamilyID::Propagate);
auto s = storage_->Get(ctx, ctx.GetReadOptions(), cf, kNamespaceDBKey, &value);
if (!s.ok()) {
if (s.IsNotFound()) return Status::OK();
return {Status::NotOK, s.ToString()};
}
jsoncons::json j = jsoncons::json::parse(value);
for (const auto& iter : j.object_range()) {
db_tokens->insert({iter.key(), iter.value().as_string()});
}
return Status::OK();
}
Status Namespace::LoadAndRewrite() {
auto config = storage_->GetConfig();
// Namespace is NOT allowed in the cluster mode, so we don't need to rewrite here.
if (config->cluster_enabled) return Status::OK();
std::map<std::string, std::string> db_tokens;
auto s = loadFromDB(&db_tokens);
if (!s.IsOK()) return s;
if (!db_tokens.empty() && config->redis_databases > 0) {
return {Status::NotOK, "cannot enable redis-databases when namespaces exist in db"};
}
if (!db_tokens.empty() && !config->repl_namespace_enabled) {
return {Status::NotOK, "cannot switch off repl_namespace_enabled when namespaces exist in db"};
}
std::unique_lock<std::shared_mutex> lock(tokens_mu_);
// Load from the configuration file first
tokens_ = config->load_tokens;
// Merge the tokens from the database if the token is not in the configuration file
for (const auto& iter : db_tokens) {
if (tokens_.find(iter.first) == tokens_.end()) {
tokens_[iter.first] = iter.second;
}
}
// The following rewrite is to remove namespace/token pairs from the configuration if the namespace replication
// is enabled. So we don't need to do that if no tokens are loaded or the namespace replication is disabled.
if (config->load_tokens.empty() || !config->repl_namespace_enabled) return Status::OK();
return Rewrite(tokens_);
}
StatusOr<std::string> Namespace::Get(const std::string& ns) {
std::shared_lock lock(tokens_mu_);
for (const auto& iter : tokens_) {
if (iter.second == ns) {
return iter.first;
}
}
return {Status::NotFound};
}
StatusOr<std::string> Namespace::GetByToken(const std::string& token) {
std::shared_lock lock(tokens_mu_);
auto iter = tokens_.find(token);
if (iter == tokens_.end()) {
return {Status::NotFound};
}
return iter->second;
}
Status Namespace::Set(const std::string& ns, const std::string& token) {
auto s = IsNamespaceLegal(ns);
if (!s.IsOK()) return s;
auto config = storage_->GetConfig();
if (config->requirepass.empty()) {
return {Status::NotOK, kErrRequiredPassEmpty};
}
if (config->cluster_enabled) {
return {Status::NotOK, kErrClusterModeEnabled};
}
if (!IsAllowModify()) {
return {Status::NotOK, kErrCantModifyNamespace};
}
if (ns == kDefaultNamespace) {
return {Status::NotOK, kErrAddDefaultNamespace};
}
if (token == config->requirepass || token == config->masterauth) {
return {Status::NotOK, kErrInvalidToken};
}
std::unique_lock lock(tokens_mu_);
if (auto iter = tokens_.find(token); iter != tokens_.end()) {
if (iter->second == ns) { // nothing changed
return Status::OK();
} else { // new token is occupied by another namespace
return {Status::NotOK, kErrTokenExists};
}
}
for (const auto& iter : tokens_) {
if (iter.second == ns) { // need to delete the old token first
tokens_.erase(iter.first);
break;
}
}
tokens_[token] = ns;
s = Rewrite(tokens_);
if (!s.IsOK()) {
tokens_.erase(token);
return s;
}
return Status::OK();
}
Status Namespace::Add(const std::string& ns, const std::string& token) {
{
std::shared_lock lock(tokens_mu_);
// duplicate namespace
for (const auto& iter : tokens_) {
if (iter.second == ns) {
if (iter.first == token) return Status::OK();
return {Status::NotOK, kErrNamespaceExists};
}
}
// duplicate token
if (tokens_.find(token) != tokens_.end()) {
return {Status::NotOK, kErrTokenExists};
}
}
// we don't need to lock the mutex here because the Set method will lock it
return Set(ns, token);
}
Status Namespace::Del(const std::string& ns) {
if (ns == kDefaultNamespace) {
return {Status::NotOK, kErrDeleteDefaultNamespace};
}
if (!IsAllowModify()) {
return {Status::NotOK, kErrCantModifyNamespace};
}
std::unique_lock lock(tokens_mu_);
for (const auto& iter : tokens_) {
if (iter.second == ns) {
tokens_.erase(iter.first);
auto s = Rewrite(tokens_);
if (!s.IsOK()) {
tokens_[iter.first] = iter.second;
return s;
}
return Status::OK();
}
}
return {Status::NotOK, kErrNamespaceNotFound};
}
Status Namespace::Rewrite(const std::map<std::string, std::string>& tokens) const {
auto config = storage_->GetConfig();
// Rewrite the configuration file only if it's running with the configuration file
if (config->HasConfigFile()) {
auto s = config->Rewrite(tokens);
if (!s.IsOK()) {
return s;
}
}
// Don't propagate write to DB if its role is slave to prevent from
// increasing the DB sequence number.
if (config->IsSlave()) {
return Status::OK();
}
// Don't need to write to db if repl_namespace_enabled is false
if (!config->repl_namespace_enabled) {
return Status::OK();
}
jsoncons::json json;
for (const auto& iter : tokens) {
json[iter.first] = iter.second;
}
engine::Context ctx(storage_);
return storage_->WriteToPropagateCF(ctx, kNamespaceDBKey, json.to_string());
}