Merge pull request #3482 from sfc-gh-tclinkenbeard/support-downgrades

Support and test downgrades
This commit is contained in:
Meng Xu 2020-07-28 17:00:54 -07:00 committed by GitHub
commit 2b8aa296bb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 383 additions and 122 deletions

View File

@ -261,7 +261,7 @@ namespace SummarizeTest
testFile = random.Choice(uniqueFiles);
string oldBinaryVersionLowerBound = "0.0.0";
string lastFolderName = Path.GetFileName(Path.GetDirectoryName(testFile));
if (lastFolderName.Contains("from_")) // Only perform upgrade tests from certain versions
if (lastFolderName.Contains("from_") || lastFolderName.Contains("to_")) // Only perform upgrade/downgrade tests from certain versions
{
oldBinaryVersionLowerBound = lastFolderName.Split('_').Last();
}
@ -295,14 +295,17 @@ namespace SummarizeTest
if (testDir.EndsWith("restarting"))
{
bool isDowngrade = Path.GetFileName(Path.GetDirectoryName(testFile)).Contains("to_");
string firstServerName = isDowngrade ? fdbserverName : oldServerName;
string secondServerName = isDowngrade ? oldServerName : fdbserverName;
int expectedUnseed = -1;
int unseed;
string uid = Guid.NewGuid().ToString();
bool useNewPlugin = oldServerName == fdbserverName || versionGreaterThanOrEqual(oldServerName.Split('-').Last(), "5.2.0");
result = RunTest(oldServerName, useNewPlugin ? tlsPluginFile : tlsPluginFile_5_1, summaryFileName, errorFileName, seed, buggify, testFile + "-1.txt", runDir, uid, expectedUnseed, out unseed, out retryableError, logOnRetryableError, useValgrind, false, true, oldServerName, traceToStdout);
bool useNewPlugin = (oldServerName == fdbserverName) || versionGreaterThanOrEqual(oldServerName.Split('-').Last(), "5.2.0");
result = RunTest(firstServerName, useNewPlugin ? tlsPluginFile : tlsPluginFile_5_1, summaryFileName, errorFileName, seed, buggify, testFile + "-1.txt", runDir, uid, expectedUnseed, out unseed, out retryableError, logOnRetryableError, useValgrind, false, true, oldServerName, traceToStdout);
if (result == 0)
{
result = RunTest(fdbserverName, tlsPluginFile, summaryFileName, errorFileName, seed+1, buggify, testFile + "-2.txt", runDir, uid, expectedUnseed, out unseed, out retryableError, logOnRetryableError, useValgrind, true, false, oldServerName, traceToStdout);
result = RunTest(secondServerName, tlsPluginFile, summaryFileName, errorFileName, seed+1, buggify, testFile + "-2.txt", runDir, uid, expectedUnseed, out unseed, out retryableError, logOnRetryableError, useValgrind, true, false, oldServerName, traceToStdout);
}
}
else

View File

@ -139,6 +139,7 @@ set(FDBSERVER_SRCS
workloads/DDMetricsExclude.actor.cpp
workloads/DiskDurability.actor.cpp
workloads/DiskDurabilityTest.actor.cpp
workloads/Downgrade.actor.cpp
workloads/DummyWorkload.actor.cpp
workloads/ExternalWorkload.actor.cpp
workloads/FastTriggeredWatches.actor.cpp

View File

@ -0,0 +1,170 @@
/*
* Downgrade.actor.cpp
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2020 Apple Inc. and the FoundationDB project authors
*
* 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 "fdbclient/NativeAPI.actor.h"
#include "fdbserver/TesterInterface.actor.h"
#include "fdbserver/workloads/workloads.actor.h"
#include "flow/serialize.h"
#include "flow/actorcompiler.h" // This must be the last #include.
struct DowngradeWorkload : TestWorkload {
static constexpr const char* NAME = "Downgrade";
Key oldKey, newKey;
int numObjects;
DowngradeWorkload(WorkloadContext const& wcx) : TestWorkload(wcx) {
oldKey = getOption(options, LiteralStringRef("oldKey"), LiteralStringRef("oldKey"));
newKey = getOption(options, LiteralStringRef("newKey"), LiteralStringRef("newKey"));
numObjects = getOption(options, LiteralStringRef("numOptions"), deterministicRandom()->randomInt(0,100));
}
struct _Struct {
static constexpr FileIdentifier file_identifier = 2340487;
int oldField = 0;
};
struct OldStruct : public _Struct {
void setFields() { oldField = 1; }
bool isSet() const { return oldField == 1; }
template <class Archive>
void serialize(Archive& ar) {
serializer(ar, oldField);
}
};
struct NewStruct : public _Struct {
int newField = 0;
bool isSet() const {
return oldField == 1 && newField == 2;
}
void setFields() {
oldField = 1;
newField = 2;
}
template <class Archive>
void serialize(Archive& ar) {
serializer(ar, oldField, newField);
}
};
ACTOR static Future<Void> writeOld(Database cx, int numObjects, Key key) {
BinaryWriter writer(IncludeVersion(currentProtocolVersion));
std::vector<OldStruct> data(numObjects);
for (auto& oldObject : data) {
oldObject.setFields();
}
writer << data;
state Value value = writer.toValue();
state Transaction tr(cx);
loop {
try {
tr.set(key, value);
wait(tr.commit());
return Void();
} catch (Error& e) {
wait(tr.onError(e));
}
}
}
ACTOR static Future<Void> writeNew(Database cx, int numObjects, Key key) {
ProtocolVersion protocolVersion = currentProtocolVersion;
protocolVersion.addObjectSerializerFlag();
ObjectWriter writer(IncludeVersion(protocolVersion));
std::vector<NewStruct> data(numObjects);
for (auto& newObject : data) {
newObject.setFields();
}
writer.serialize(data);
state Value value = writer.toStringRef();
state Transaction tr(cx);
loop {
try {
tr.set(key, value);
wait(tr.commit());
return Void();
} catch (Error& e) {
wait(tr.onError(e));
}
}
}
ACTOR static Future<Void> readData(Database cx, int numObjects, Key key) {
state Transaction tr(cx);
state Value value;
loop {
try {
Optional<Value> _value = wait(tr.get(key));
ASSERT(_value.present());
value = _value.get();
break;
} catch (Error& e) {
wait(tr.onError(e));
}
}
{
// use BinaryReader
BinaryReader reader(value, IncludeVersion());
std::vector<OldStruct> data;
reader >> data;
ASSERT(data.size() == numObjects);
for (const auto& oldObject : data) {
ASSERT(oldObject.isSet());
}
}
{
// use ArenaReader
ArenaReader reader(Arena(), value, IncludeVersion());
std::vector<OldStruct> data;
reader >> data;
ASSERT(data.size() == numObjects);
for (const auto& oldObject : data) {
ASSERT(oldObject.isSet());
}
}
return Void();
}
std::string description() override { return NAME; }
Future<Void> setup(Database const& cx) override {
return clientId ? Void() : (writeOld(cx, numObjects, oldKey) && writeNew(cx, numObjects, newKey));
}
Future<Void> start(Database const& cx) override {
return clientId ? Void() : (readData(cx, numObjects, oldKey) && readData(cx, numObjects, newKey));
}
Future<bool> check(Database const& cx) override {
// Failures are checked with assertions
return true;
}
void getMetrics(vector<PerfMetric>& m) override {}
};
WorkloadFactory<DowngradeWorkload> DowngradeWorkloadFactory(DowngradeWorkload::NAME);

View File

@ -68,6 +68,7 @@ struct SaveContext {
template <class ReaderImpl>
class _ObjectReader {
protected:
ProtocolVersion mProtocolVersion;
public:
@ -79,8 +80,19 @@ public:
const uint8_t* data = static_cast<ReaderImpl*>(this)->data();
LoadContext<ReaderImpl> context(static_cast<ReaderImpl*>(this));
if(read_file_identifier(data) != file_identifier) {
TraceEvent(SevError, "MismatchedFileIdentifier").detail("Expected", file_identifier).detail("Read", read_file_identifier(data));
ASSERT(false);
// Some file identifiers are changed in 7.0, so file identifier mismatches
// are expected during a downgrade from 7.0 to 6.3
bool expectMismatch = mProtocolVersion >= ProtocolVersion(0x0FDB00B070000000LL);
{
TraceEvent te(expectMismatch ? SevInfo : SevError, "MismatchedFileIdentifier");
if (expectMismatch) {
te.suppressFor(1.0);
}
te.detail("Expected", file_identifier).detail("Read", read_file_identifier(data));
}
if (!expectMismatch) {
ASSERT(false);
}
}
load_members(data, context, items...);
}

View File

@ -135,3 +135,6 @@ constexpr ProtocolVersion currentProtocolVersion(0x0FDB00B063010001LL);
// This assert is intended to help prevent incrementing the leftmost digits accidentally. It will probably need to
// change when we reach version 10.
static_assert(currentProtocolVersion.version() < 0x0FDB00B100000000LL, "Unexpected protocol version");
// Downgrades are only supported for one minor version
constexpr ProtocolVersion minInvalidProtocolVersion(0x0FDB00B071000000LL);

View File

@ -78,7 +78,7 @@ inline typename Archive::WRITER& operator << (Archive& ar, const Item& item ) {
template <class Archive, class Item>
inline typename Archive::READER& operator >> (Archive& ar, Item& item ) {
load(ar, item);
ar.deserialize(item);
return ar;
}
@ -286,10 +286,8 @@ struct _IncludeVersion {
TraceEvent(SevWarnAlways, "InvalidSerializationVersion").error(err).detailf("Version", "%llx", v.versionWithFlags());
throw err;
}
if (v > currentProtocolVersion) {
// For now, no forward compatibility whatsoever is supported. In the future, this check may be weakened for
// particular data structures (e.g. to support mismatches between client and server versions when the client
// must deserialize zookeeper and database structures)
if (v >= minInvalidProtocolVersion) {
// Downgrades are only supported for one minor version
auto err = incompatible_protocol_version();
TraceEvent(SevError, "FutureProtocolVersion").error(err).detailf("Version", "%llx", v.versionWithFlags());
throw err;
@ -520,51 +518,28 @@ private:
}
};
class ArenaReader {
template<class Impl>
class _Reader {
public:
static const int isDeserializing = 1;
static constexpr bool isSerializing = false;
typedef ArenaReader READER;
using READER = Impl;
const void* readBytes( int bytes ) {
const char* b = begin;
const char* e = b + bytes;
ASSERT( e <= end );
begin = e;
return b;
}
const void* peekBytes( int bytes ) {
ASSERT( begin + bytes <= end );
const void *peekBytes(int bytes) {
ASSERT(begin + bytes <= end);
return begin;
}
void serializeBytes(void* data, int bytes) {
memcpy(data, readBytes(bytes), bytes);
}
const uint8_t* arenaRead( int bytes ) {
return (const uint8_t*)readBytes(bytes);
}
StringRef arenaReadAll() const {
return StringRef(reinterpret_cast<const uint8_t*>(begin), end - begin);
void serializeBytes(void *data, int bytes) {
memcpy(data, static_cast<Impl*>(this)->readBytes(bytes), bytes);
}
template <class T>
void serializeBinaryItem( T& t ) {
t = *(T*)readBytes(sizeof(T));
t = *(T*)(static_cast<Impl*>(this)->readBytes(sizeof(T)));
}
template <class VersionOptions>
ArenaReader( Arena const& arena, const StringRef& input, VersionOptions vo ) : m_pool(arena), check(NULL) {
begin = (const char*)input.begin();
end = begin + input.size();
vo.read(*this);
}
Arena& arena() { return m_pool; }
Arena &arena() { return m_pool; }
ProtocolVersion protocolVersion() const { return m_protocolVersion; }
void setProtocolVersion(ProtocolVersion pv) { m_protocolVersion = pv; }
@ -575,96 +550,129 @@ public:
check = begin;
}
void rewind() {
ASSERT(check != NULL);
begin = check;
check = NULL;
}
private:
const char *begin, *end, *check;
Arena m_pool;
ProtocolVersion m_protocolVersion;
};
class BinaryReader {
public:
static const int isDeserializing = 1;
static constexpr bool isSerializing = false;
typedef BinaryReader READER;
const void* readBytes( int bytes );
const void* peekBytes( int bytes ) {
ASSERT( begin + bytes <= end );
return begin;
}
void serializeBytes(void* data, int bytes) {
memcpy(data, readBytes(bytes), bytes);
}
template <class T>
void serializeBinaryItem( T& t ) {
t = *(T*)readBytes(sizeof(T));
}
const uint8_t* arenaRead( int bytes ) {
// Reads and returns the next bytes.
// The returned pointer has the lifetime of this.arena()
// Could be implemented zero-copy if [begin,end) was in this.arena() already; for now is a copy
if (!bytes) return NULL;
uint8_t* dat = new (arena()) uint8_t[ bytes ];
serializeBytes( dat, bytes );
return dat;
}
template <class VersionOptions>
BinaryReader( const void* data, int length, VersionOptions vo ) {
begin = (const char*)data;
end = begin + length;
check = nullptr;
vo.read(*this);
}
template <class VersionOptions>
BinaryReader( const StringRef& s, VersionOptions vo ) { begin = (const char*)s.begin(); end = begin + s.size(); vo.read(*this); }
template <class VersionOptions>
BinaryReader( const std::string& v, VersionOptions vo ) { begin = v.c_str(); end = begin + v.size(); vo.read(*this); }
Arena& arena() { return m_pool; }
template <class T, class VersionOptions>
static T fromStringRef( StringRef sr, VersionOptions vo ) {
T t;
BinaryReader r(sr, vo);
r >> t;
return t;
}
ProtocolVersion protocolVersion() const { return m_protocolVersion; }
void setProtocolVersion(ProtocolVersion pv) { m_protocolVersion = pv; }
void assertEnd() { ASSERT( begin == end ); }
bool empty() const { return begin == end; }
void checkpoint() {
check = begin;
}
void rewind() {
ASSERT(check != nullptr);
begin = check;
check = nullptr;
}
protected:
_Reader(const char* begin, const char* end) : begin(begin), end(end) {}
_Reader(const char* begin, const char* end, const Arena& arena) : begin(begin), end(end), m_pool(arena) {}
private:
const char *begin, *end, *check;
const char *begin, *end;
const char* check = nullptr;
Arena m_pool;
ProtocolVersion m_protocolVersion;
};
class ArenaReader : public _Reader<ArenaReader> {
Optional<ArenaObjectReader> arenaObjectReader;
public:
const void* readBytes( int bytes ) {
const char* b = begin;
const char* e = b + bytes;
ASSERT( e <= end );
begin = e;
return b;
}
const uint8_t* arenaRead( int bytes ) {
return (const uint8_t*)readBytes(bytes);
}
StringRef arenaReadAll() const {
return StringRef(reinterpret_cast<const uint8_t*>(begin), end - begin);
}
template <class VersionOptions>
ArenaReader(Arena const& arena, const StringRef& input, VersionOptions vo)
: _Reader(reinterpret_cast<const char*>(input.begin()), reinterpret_cast<const char*>(input.end()), arena) {
vo.read(*this);
if (m_protocolVersion.hasObjectSerializerFlag()) {
arenaObjectReader = ArenaObjectReader(arena, input, vo);
}
}
template <class T>
void deserialize(T& t) {
if constexpr (HasFileIdentifier<T>::value) {
if (arenaObjectReader.present()) {
arenaObjectReader.get().deserialize(t);
} else {
load(*this, t);
}
} else {
load(*this, t);
}
}
};
class BinaryReader : public _Reader<BinaryReader> {
Optional<ObjectReader> objectReader;
public:
const void* readBytes( int bytes );
const uint8_t* arenaRead( int bytes ) {
// Reads and returns the next bytes.
// The returned pointer has the lifetime of this.arena()
// Could be implemented zero-copy if [begin,end) was in this.arena() already; for now is a copy
if (!bytes) return nullptr;
uint8_t* dat = new (arena()) uint8_t[ bytes ];
serializeBytes( dat, bytes );
return dat;
}
template <class T, class VersionOptions>
static T fromStringRef( StringRef sr, VersionOptions vo ) {
T t;
BinaryReader r(sr, vo);
r >> t;
return t;
}
void assertEnd() { ASSERT(begin == end); }
template <class VersionOptions>
BinaryReader(const void* data, int length, VersionOptions vo)
: _Reader(reinterpret_cast<const char*>(data), reinterpret_cast<const char*>(data) + length) {
readVersion(vo);
}
template <class VersionOptions>
BinaryReader(const StringRef& s, VersionOptions vo)
: _Reader(reinterpret_cast<const char*>(s.begin()), reinterpret_cast<const char*>(s.end())) {
readVersion(vo);
}
template <class VersionOptions>
BinaryReader(const std::string& s, VersionOptions vo) : _Reader(s.c_str(), s.c_str() + s.size()) {
readVersion(vo);
}
template<class T>
void deserialize(T &t) {
if constexpr (HasFileIdentifier<T>::value) {
if (objectReader.present()) {
objectReader.get().deserialize(t);
} else {
load(*this, t);
}
} else {
load(*this, t);
}
}
private:
template <class VersionOptions>
void readVersion(VersionOptions vo) {
vo.read(*this);
if (m_protocolVersion.hasObjectSerializerFlag()) {
objectReader = ObjectReader(reinterpret_cast<const uint8_t*>(begin), AssumeVersion(m_protocolVersion));
}
}
};
struct SendBuffer {
uint8_t const* data;
SendBuffer* next;

View File

@ -151,6 +151,7 @@ if(WITH_PYTHON)
add_fdb_test(TEST_FILES rare/ConflictRangeRYOWCheck.txt)
add_fdb_test(TEST_FILES rare/CycleRollbackClogged.txt)
add_fdb_test(TEST_FILES rare/CycleWithKills.txt)
add_fdb_test(TEST_FILES rare/Downgrade.txt)
add_fdb_test(TEST_FILES rare/FuzzTest.txt)
add_fdb_test(TEST_FILES rare/InventoryTestHeavyWrites.txt)
add_fdb_test(TEST_FILES rare/LargeApiCorrectness.txt)
@ -189,6 +190,9 @@ if(WITH_PYTHON)
add_fdb_test(
TEST_FILES restarting/from_5.2.0/ClientTransactionProfilingCorrectness-1.txt
restarting/from_5.2.0/ClientTransactionProfilingCorrectness-2.txt)
add_fdb_test(
TEST_FILES restarting/to_6.3.5/CycleTestRestart-1.txt
restarting/to_6.3.5/CycleTestRestart-2.txt)
add_fdb_test(TEST_FILES slow/ApiCorrectness.txt)
add_fdb_test(TEST_FILES slow/ApiCorrectnessAtomicRestore.txt)
add_fdb_test(TEST_FILES slow/ApiCorrectnessSwitchover.txt)

4
tests/rare/Downgrade.txt Normal file
View File

@ -0,0 +1,4 @@
testTitle=Downgrade
testName=Downgrade
oldKey=oldKey
newKey=newKey

View File

@ -0,0 +1,30 @@
testTitle=Clogged
clearAfterTest=false
testName=Cycle
transactionsPerSecond=500.0
nodeCount=2500
testDuration=10.0
expectedRate=0
testName=RandomClogging
testDuration=10.0
testName=Rollback
meanDelay=10.0
testDuration=10.0
testName=Attrition
machinesToKill=10
machinesToLeave=3
reboot=true
testDuration=10.0
testName=Attrition
machinesToKill=10
machinesToLeave=3
reboot=true
testDuration=10.0
testName=SaveAndKill
restartInfoLocation=simfdb/restartInfo.ini
testDuration=10.0

View File

@ -0,0 +1,26 @@
testTitle=Clogged
runSetup=false
testName=Cycle
transactionsPerSecond=2500.0
nodeCount=2500
testDuration=10.0
expectedRate=0
testName=RandomClogging
testDuration=10.0
testName=Rollback
meanDelay=10.0
testDuration=10.0
testName=Attrition
machinesToKill=10
machinesToLeave=3
reboot=true
testDuration=10.0
testName=Attrition
machinesToKill=10
machinesToLeave=3
reboot=true
testDuration=10.0