1
0
Fork 0
arangodb/arangod/VocBase/Methods/Version.cpp

176 lines
6.7 KiB
C++

////////////////////////////////////////////////////////////////////////////////
/// DISCLAIMER
///
/// Copyright 2017 ArangoDB GmbH, Cologne, Germany
///
/// 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.
///
/// Copyright holder is ArangoDB GmbH, Cologne, Germany
///
/// @author Simon Grätzer
////////////////////////////////////////////////////////////////////////////////
#include "Version.h"
#include "Basics/Common.h"
#include "Basics/FileUtils.h"
#include "Basics/VelocyPackHelper.h"
#include "Basics/files.h"
#include "Logger/Logger.h"
#include "Rest/Version.h"
#include "RestServer/DatabaseFeature.h"
#include "StorageEngine/EngineSelectorFeature.h"
#include "StorageEngine/StorageEngine.h"
#include <velocypack/Builder.h>
#include <velocypack/Iterator.h>
#include <velocypack/Parser.h>
#include <velocypack/Slice.h>
#include <velocypack/velocypack-aliases.h>
using namespace arangodb;
using namespace arangodb::methods;
uint64_t Version::parseVersion(char const* str, size_t len) {
uint64_t result = 0;
uint64_t tmp = 0;
for (size_t i = 0; i < len; i++) {
char c = str[i];
if ('0' <= c && c <= '9') {
tmp = tmp * 10 + static_cast<size_t>(c - '0');
} else if (c == '.') {
result = result * 100 + tmp;
tmp = 0;
} else {
// stop at first other character (e.g. "3.4.devel")
while (result > 0 && result < 100) {
// do we have 5 digits already? if we, then boost the version
// number accordingly. this can happen for version strings
// such as "3.4.devel" or "4.devel"
result *= 100;
}
break;
}
}
return result * 100 + tmp;
}
uint64_t Version::parseVersion(char const* str) {
return parseVersion(str, strlen(str));
}
/// @brief "(((major * 100) + minor) * 100) + patch"
uint64_t Version::current() {
return parseVersion(ARANGODB_VERSION);
}
VersionResult Version::check(TRI_vocbase_t* vocbase) {
StorageEngine* engine = EngineSelectorFeature::ENGINE;
TRI_ASSERT(engine != nullptr);
std::string versionFile = engine->versionFilename(vocbase->id());
if (!basics::FileUtils::exists(versionFile)) {
LOG_TOPIC("fde3f", DEBUG, Logger::STARTUP) << "VERSION file '" << versionFile << "' not found";
return VersionResult{VersionResult::NO_VERSION_FILE, 0, 0, {}};
}
std::string versionInfo = basics::FileUtils::slurp(versionFile);
LOG_TOPIC("3da0f", DEBUG, Logger::STARTUP)
<< "found VERSION file '" << versionFile << "', content: " << versionInfo;
if (versionInfo.empty()) {
LOG_TOPIC("dc4de", ERR, Logger::STARTUP) << "VERSION file '" << versionFile << "' is empty";
return VersionResult{VersionResult::CANNOT_READ_VERSION_FILE, 0, 0, {}};
}
uint64_t lastVersion = UINT64_MAX;
uint64_t serverVersion = Version::current();
std::map<std::string, bool> tasks;
try {
std::shared_ptr<VPackBuilder> parsed = velocypack::Parser::fromJson(versionInfo);
VPackSlice versionVals = parsed->slice();
if (!versionVals.isObject() || !versionVals.get("version").isNumber()) {
LOG_TOPIC("0c863", ERR, Logger::STARTUP) << "cannot parse VERSION file '" << versionFile
<< "' content: " << versionInfo;
return VersionResult{VersionResult::CANNOT_PARSE_VERSION_FILE, 0, 0, tasks};
}
lastVersion = versionVals.get("version").getUInt();
VPackSlice run = versionVals.get("tasks");
if (run.isNone() || !run.isObject()) {
LOG_TOPIC("2897d", ERR, Logger::STARTUP) << "invalid VERSION file '" << versionFile
<< "' content: " << versionInfo;
return VersionResult{VersionResult::CANNOT_PARSE_VERSION_FILE, 0, 0, tasks};
}
for (VPackObjectIterator::ObjectPair pair : VPackObjectIterator(run)) {
tasks.emplace(pair.key.copyString(), pair.value.getBool());
}
} catch (velocypack::Exception const& e) {
LOG_TOPIC("2d92a", ERR, Logger::STARTUP)
<< "cannot parse VERSION file '" << versionFile << "': " << e.what()
<< ". file content: " << versionInfo;
return VersionResult{VersionResult::CANNOT_PARSE_VERSION_FILE, 0, 0, tasks};
}
TRI_ASSERT(lastVersion != UINT32_MAX);
VersionResult res = {VersionResult::NO_VERSION_FILE, serverVersion, lastVersion, tasks};
if (lastVersion / 100 == serverVersion / 100) {
LOG_TOPIC("e9cc3", DEBUG, Logger::STARTUP) << "version match: last version " << lastVersion
<< ", current version " << serverVersion;
res.status = VersionResult::VERSION_MATCH;
} else if (lastVersion > serverVersion) { // downgrade??
LOG_TOPIC("73276", DEBUG, Logger::STARTUP) << "downgrade: last version " << lastVersion
<< ", current version " << serverVersion;
res.status = VersionResult::DOWNGRADE_NEEDED;
} else if (lastVersion < serverVersion) { // upgrade
LOG_TOPIC("0f77f", DEBUG, Logger::STARTUP) << "upgrade: last version " << lastVersion
<< ", current version " << serverVersion;
res.status = VersionResult::UPGRADE_NEEDED;
} else {
LOG_TOPIC("b0d3c", ERR, Logger::STARTUP) << "should not happen: last version " << lastVersion
<< ", current version " << serverVersion;
}
return res;
}
Result Version::write(TRI_vocbase_t* vocbase, std::map<std::string, bool> const& tasks, bool sync) {
StorageEngine* engine = EngineSelectorFeature::ENGINE;
TRI_ASSERT(engine != nullptr);
std::string versionFile = engine->versionFilename(vocbase->id());
if (versionFile.empty()) {
// cluster engine
return Result();
}
VPackOptions opts;
opts.buildUnindexedObjects = true;
VPackBuilder builder(&opts);
builder.openObject(true);
builder.add("version", VPackValue(Version::current()));
builder.add("tasks", VPackValue(VPackValueType::Object));
for (auto const& task : tasks) {
builder.add(task.first, VPackValue(task.second));
}
builder.close();
builder.close();
if (!basics::VelocyPackHelper::velocyPackToFile(versionFile, builder.slice(), sync)) {
LOG_TOPIC("33860", ERR, Logger::STARTUP) << "writing VERSION file '" << versionFile
<< "' failed: " << TRI_last_error();
return Result(TRI_errno(), TRI_last_error());
}
return Result();
}