2021-09-21 06:40:53 +08:00
|
|
|
/*
|
|
|
|
* FileConfigureCommand.actor.cpp
|
|
|
|
*
|
|
|
|
* This source file is part of the FoundationDB open source project
|
|
|
|
*
|
2022-03-22 04:36:23 +08:00
|
|
|
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
2021-09-21 06:40:53 +08:00
|
|
|
*
|
|
|
|
* 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 "fdbcli/FlowLineNoise.h"
|
|
|
|
#include "fdbcli/fdbcli.actor.h"
|
|
|
|
|
|
|
|
#include "fdbclient/FDBOptions.g.h"
|
|
|
|
#include "fdbclient/IClientApi.h"
|
|
|
|
#include "fdbclient/ManagementAPI.actor.h"
|
|
|
|
#include "fdbclient/Schemas.h"
|
|
|
|
|
|
|
|
#include "flow/Arena.h"
|
|
|
|
#include "flow/FastRef.h"
|
|
|
|
#include "flow/ThreadHelper.actor.h"
|
|
|
|
#include "flow/actorcompiler.h" // This must be the last #include.
|
|
|
|
|
|
|
|
namespace fdb_cli {
|
|
|
|
|
|
|
|
ACTOR Future<bool> fileConfigureCommandActor(Reference<IDatabase> db,
|
|
|
|
std::string filePath,
|
|
|
|
bool isNewDatabase,
|
|
|
|
bool force) {
|
|
|
|
std::string contents(readFileBytes(filePath, 100000));
|
|
|
|
json_spirit::mValue config;
|
|
|
|
if (!json_spirit::read_string(contents, config)) {
|
|
|
|
fprintf(stderr, "ERROR: Invalid JSON\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (config.type() != json_spirit::obj_type) {
|
|
|
|
fprintf(stderr, "ERROR: Configuration file must contain a JSON object\n");
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
StatusObject configJSON = config.get_obj();
|
|
|
|
|
|
|
|
json_spirit::mValue schema;
|
|
|
|
if (!json_spirit::read_string(JSONSchemas::clusterConfigurationSchema.toString(), schema)) {
|
|
|
|
ASSERT(false);
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string errorStr;
|
|
|
|
if (!schemaMatch(schema.get_obj(), configJSON, errorStr)) {
|
|
|
|
printf("%s", errorStr.c_str());
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string configString;
|
|
|
|
if (isNewDatabase) {
|
|
|
|
configString = "new";
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const auto& [name, value] : configJSON) {
|
|
|
|
if (!configString.empty()) {
|
|
|
|
configString += " ";
|
|
|
|
}
|
|
|
|
if (value.type() == json_spirit::int_type) {
|
|
|
|
configString += name + ":=" + format("%d", value.get_int());
|
|
|
|
} else if (value.type() == json_spirit::str_type) {
|
|
|
|
configString += value.get_str();
|
|
|
|
} else if (value.type() == json_spirit::array_type) {
|
|
|
|
configString +=
|
|
|
|
name + "=" +
|
|
|
|
json_spirit::write_string(json_spirit::mValue(value.get_array()), json_spirit::Output_options::none);
|
|
|
|
} else {
|
|
|
|
printUsage(LiteralStringRef("fileconfigure"));
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ConfigurationResult result = wait(ManagementAPI::changeConfig(db, configString, force));
|
|
|
|
// Real errors get thrown from makeInterruptable and printed by the catch block in cli(), but
|
|
|
|
// there are various results specific to changeConfig() that we need to report:
|
|
|
|
bool ret = true;
|
|
|
|
switch (result) {
|
|
|
|
case ConfigurationResult::NO_OPTIONS_PROVIDED:
|
|
|
|
fprintf(stderr, "ERROR: No options provided\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::CONFLICTING_OPTIONS:
|
|
|
|
fprintf(stderr, "ERROR: Conflicting options\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::UNKNOWN_OPTION:
|
|
|
|
fprintf(stderr, "ERROR: Unknown option\n"); // This should not be possible because of schema match
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::INCOMPLETE_CONFIGURATION:
|
|
|
|
fprintf(stderr,
|
|
|
|
"ERROR: Must specify both a replication level and a storage engine when creating a new database\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::INVALID_CONFIGURATION:
|
|
|
|
fprintf(stderr, "ERROR: These changes would make the configuration invalid\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::DATABASE_ALREADY_CREATED:
|
|
|
|
fprintf(stderr, "ERROR: Database already exists! To change configuration, don't say `new'\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::DATABASE_CREATED:
|
|
|
|
printf("Database created\n");
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::DATABASE_UNAVAILABLE:
|
|
|
|
fprintf(stderr, "ERROR: The database is unavailable\n");
|
|
|
|
printf("Type `fileconfigure FORCE <FILENAME>' to configure without this check\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::STORAGE_IN_UNKNOWN_DCID:
|
|
|
|
fprintf(stderr, "ERROR: All storage servers must be in one of the known regions\n");
|
|
|
|
printf("Type `fileconfigure FORCE <FILENAME>' to configure without this check\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::REGION_NOT_FULLY_REPLICATED:
|
|
|
|
fprintf(stderr,
|
|
|
|
"ERROR: When usable_regions > 1, All regions with priority >= 0 must be fully replicated "
|
|
|
|
"before changing the configuration\n");
|
|
|
|
printf("Type `fileconfigure FORCE <FILENAME>' to configure without this check\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::MULTIPLE_ACTIVE_REGIONS:
|
|
|
|
fprintf(stderr, "ERROR: When changing usable_regions, only one region can have priority >= 0\n");
|
|
|
|
printf("Type `fileconfigure FORCE <FILENAME>' to configure without this check\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::REGIONS_CHANGED:
|
|
|
|
fprintf(stderr,
|
|
|
|
"ERROR: The region configuration cannot be changed while simultaneously changing usable_regions\n");
|
|
|
|
printf("Type `fileconfigure FORCE <FILENAME>' to configure without this check\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::NOT_ENOUGH_WORKERS:
|
|
|
|
fprintf(stderr, "ERROR: Not enough processes exist to support the specified configuration\n");
|
|
|
|
printf("Type `fileconfigure FORCE <FILENAME>' to configure without this check\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::REGION_REPLICATION_MISMATCH:
|
|
|
|
fprintf(stderr, "ERROR: `three_datacenter' replication is incompatible with region configuration\n");
|
|
|
|
printf("Type `fileconfigure FORCE <TOKEN...>' to configure without this check\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::DCID_MISSING:
|
|
|
|
fprintf(stderr, "ERROR: `No storage servers in one of the specified regions\n");
|
|
|
|
printf("Type `fileconfigure FORCE <TOKEN...>' to configure without this check\n");
|
|
|
|
ret = false;
|
|
|
|
break;
|
|
|
|
case ConfigurationResult::SUCCESS:
|
|
|
|
printf("Configuration changed\n");
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
ASSERT(false);
|
|
|
|
ret = false;
|
|
|
|
};
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
CommandFactory fileconfigureFactory(
|
|
|
|
"fileconfigure",
|
|
|
|
CommandHelp(
|
|
|
|
"fileconfigure [new] <FILENAME>",
|
|
|
|
"change the database configuration from a file",
|
|
|
|
"The `new' option, if present, initializes a new database with the given configuration rather than changing "
|
|
|
|
"the configuration of an existing one. Load a JSON document from the provided file, and change the database "
|
|
|
|
"configuration to match the contents of the JSON document. The format should be the same as the value of the "
|
|
|
|
"\"configuration\" entry in status JSON without \"excluded_servers\" or \"coordinators_count\"."));
|
|
|
|
|
|
|
|
} // namespace fdb_cli
|