Browse Source

BlobStorage version control system and UT,

Some more tests

Mechanism implemented, require some more tests
serg-belyakov 2 years ago
parent
commit
51674dac4b

+ 257 - 0
ydb/core/driver_lib/run/version.cpp

@@ -1,6 +1,263 @@
 #include <library/cpp/svnversion/svnversion.h>
 #include "version.h"
 
+
+NKikimrConfig::TCurrentCompatibilityInformation* CompatibilityInformation = nullptr;
+
+const NKikimrConfig::TCurrentCompatibilityInformation* GetCurrentCompatibilityInformation() {
+    static TSpinLock lock;
+    TGuard<TSpinLock> g(lock);
+
+    if (!CompatibilityInformation) {
+        CompatibilityInformation = new NKikimrConfig::TCurrentCompatibilityInformation();
+        // Look for protobuf message format in ydb/core/protos/config.proto
+        // To be changed in new release:
+        CompatibilityInformation->SetBuild("trunk");
+    }
+
+    return CompatibilityInformation;
+}
+
+
+
+// Last stable YDB release, which doesn't include version control change
+// When the compatibility information is not present in component's data,
+// we assume component's version to be this version
+NKikimrConfig::TStoredCompatibilityInformation* UnknownYdbRelease = nullptr;
+const NKikimrConfig::TStoredCompatibilityInformation* GetUnknownYdbRelease() {
+    static TSpinLock lock;
+    TGuard<TSpinLock> g(lock);
+
+    if (!UnknownYdbRelease) {
+        UnknownYdbRelease = new NKikimrConfig::TStoredCompatibilityInformation();
+        UnknownYdbRelease->SetBuild("ydb");
+
+        auto* version = UnknownYdbRelease->MutableYdbVersion();
+        version->SetYear(22);
+        version->SetMajor(5);
+        version->SetMinor(7);
+        version->SetHotfix(0);
+    }
+
+    return UnknownYdbRelease;
+}
+
+NKikimrConfig::TStoredCompatibilityInformation MakeStoredCompatibiltyInformation(
+        ui32 componentId, const NKikimrConfig::TCurrentCompatibilityInformation* current) {
+    Y_VERIFY(current);
+
+    NKikimrConfig::TStoredCompatibilityInformation stored;
+    stored.SetBuild(current->GetBuild());
+    if (current->HasYdbVersion()) {
+        stored.MutableYdbVersion()->CopyFrom(current->GetYdbVersion());
+    }
+
+    for (ui32 i = 0; i < current->StoresReadableBySize(); i++) {
+        auto rule = current->GetStoresReadableBy(i);
+        if (!rule.HasComponentId() || rule.GetComponentId() == componentId ||
+                rule.GetComponentId() == (ui32)NKikimrConfig::TCompatibilityRule::Any) {
+            auto *newRule = stored.AddReadableBy();
+            if (rule.HasBuild()) {
+                newRule->SetBuild(rule.GetBuild());
+            }
+            newRule->MutableUpperLimit()->CopyFrom(rule.GetUpperLimit());
+            newRule->MutableBottomLimit()->CopyFrom(rule.GetBottomLimit());
+            newRule->SetForbidden(rule.GetForbidden());
+        }
+    }
+
+    return stored;
+}
+
+NKikimrConfig::TStoredCompatibilityInformation MakeStoredCompatibiltyInformation(ui32 componentId) {
+    return MakeStoredCompatibiltyInformation(componentId, GetCurrentCompatibilityInformation());
+}
+
+////////////////////////////////////////////////////////////////////////////////////////
+// YDB versions are compared alphabetically, much like strings,
+// but instead of chars there are 4 componets: Year, Major, Minor and Hotfix
+// Each of the version's components can be absent
+// If one is, than all the following components are also considered to be 'absent'
+// Absent component is equal to any other, including other absent
+// 
+// Some examples:
+// 22.1.1.1 < 22.1.2.0
+// 22.2.1.0 > 22.1._._
+// 23.1._._ == 23.1.1.0
+//
+// Function returns -1 if left < right, 0 if left == right, 1 if left > right
+i32 CompareVersions(const NKikimrConfig::TYdbVersion& left, const NKikimrConfig::TYdbVersion& right) {
+    if (!left.HasYear() || !right.HasYear()) {
+        return 0;
+    }
+    if (left.GetYear() < right.GetYear()) {
+        return -1; 
+    } else if (left.GetYear() > right.GetYear()) {
+        return 1;
+    }
+
+    if (!left.HasMajor() || !right.HasMajor()) {
+        return 0;
+    }
+    if (left.GetMajor() < right.GetMajor()) {
+        return -1; 
+    } else if (left.GetMajor() > right.GetMajor()) {
+        return 1;
+    }
+
+    if (!left.HasMinor() || !right.HasMinor()) {
+        return 0;
+    }
+    if (left.GetMinor() < right.GetMinor()) {
+        return -1; 
+    } else if (left.GetMinor() > right.GetMinor()) {
+        return 1;
+    }
+
+    if (!left.HasHotfix() || !right.HasHotfix()) {
+        return 0;
+    }
+    if (left.GetHotfix() < right.GetHotfix()) {
+        return -1; 
+    } else if (left.GetHotfix() > right.GetHotfix()) {
+        return 1;
+    }
+
+    return 0;
+}
+
+// If StoredCompatibilityInformation is not present, we:
+// compare current to UnknownYdbRelease, if current version is stable, otherwise
+// we consider versions compatible
+bool CheckNonPresent(const NKikimrConfig::TCurrentCompatibilityInformation* current,
+        ui32 componentId, TString& errorReason) {
+    if (!current->HasYdbVersion()) {
+        return true;
+    }
+    const auto* lastUnsupported = GetUnknownYdbRelease();
+    Y_VERIFY(lastUnsupported);
+    TString errorReason1;
+    if (!CheckVersionCompatibility(current, lastUnsupported, componentId, errorReason1)) {
+        errorReason = "No stored YDB version found, last unsupported release is incompatible: " + errorReason1;
+        return false;
+    } else {
+        return true;
+    }
+}
+
+// By default two stable versions are considered compatible, if their Year is the same 
+// and Major differ for no more, than 1, regardless of their Build
+// Two unstable versions are compatible only if they have the same Build
+// Stable and non-stable versions are not compatible by default
+bool CheckDefaultRules(TString currentBuild, const NKikimrConfig::TYdbVersion* currentYdbVersion, 
+        TString storedBuild, const NKikimrConfig::TYdbVersion* storedYdbVersion) {
+    if (!currentYdbVersion && !storedYdbVersion) {
+        return currentBuild == storedBuild;
+    }
+    if (currentYdbVersion && storedYdbVersion) {
+        if (!currentYdbVersion->HasYear() || !storedYdbVersion->HasYear()) {
+            return true;
+        }
+        if (!currentYdbVersion->HasMajor() || !storedYdbVersion->HasMajor()) {
+            return true;
+        }
+        return currentYdbVersion->GetYear() == storedYdbVersion->GetYear() && 
+                std::abs((i32)currentYdbVersion->GetMajor() - (i32)storedYdbVersion->GetMajor()) <= 1;
+    }
+
+    return false;
+}
+
+bool CheckRule(TString build, const NKikimrConfig::TYdbVersion* version, 
+        const NKikimrConfig::TCompatibilityRule& rule) {
+    if (rule.HasBuild()) {
+        if (rule.GetBuild() != build) {
+            return false;
+        }
+        if (version == nullptr) {
+            return true;
+        }
+    } else {
+        if (version == nullptr) {
+            return false;
+        }
+    }
+    
+    return (!rule.HasBottomLimit() || CompareVersions(*version, rule.GetBottomLimit()) > -1) &&
+            (!rule.HasUpperLimit() || CompareVersions(*version, rule.GetUpperLimit()) < 1);
+}
+
+bool CheckVersionCompatibility(const NKikimrConfig::TCurrentCompatibilityInformation* current,
+        const NKikimrConfig::TStoredCompatibilityInformation* stored, ui32 componentId, TString& errorReason) {
+    if (stored == nullptr) {
+        // version record is not found
+        return CheckNonPresent(current, componentId, errorReason);
+    }
+
+    const auto currentBuild = current->GetBuild();
+    const auto storedBuild = stored->GetBuild();
+    const auto* currentYdbVersion = current->HasYdbVersion() ? &current->GetYdbVersion() : nullptr;
+    const auto* storedYdbVersion = stored->HasYdbVersion() ? &stored->GetYdbVersion() : nullptr;
+
+    bool permitted = false;
+    bool useDefault = true;
+
+    for (ui32 i = 0; i < current->CanLoadFromSize(); ++i) {
+        const auto rule = current->GetCanLoadFrom(i);
+        if (!rule.HasComponentId() || rule.GetComponentId() == componentId ||
+                rule.GetComponentId() == (ui32)NKikimrConfig::TCompatibilityRule::Any) {
+            useDefault = false;
+            if (CheckRule(storedBuild, storedYdbVersion, rule)) {
+                if (rule.HasForbidden() && rule.GetForbidden()) {
+                    errorReason = "Stored version is explicitly prohibited";
+                    return false;
+                } else {
+                    permitted = true;
+                }
+            }
+        }
+    }
+
+    for (ui32 i = 0; i < stored->ReadableBySize(); ++i) {
+        const auto rule = stored->GetReadableBy(i);
+        if (!rule.HasComponentId() || rule.GetComponentId() == componentId ||
+                rule.GetComponentId() == (ui32)NKikimrConfig::TCompatibilityRule::Any) {
+            if (CheckRule(currentBuild, currentYdbVersion, rule)) {
+                useDefault = false;
+                if (rule.HasForbidden() && rule.GetForbidden()) {
+                    errorReason = "Current version is explicitly prohibited";
+                    return false;
+                } else {
+                    permitted = true;
+                }
+            }
+        }
+    }
+
+
+    if (permitted) {
+        return true;
+    } else {
+        if (useDefault) {
+            if (CheckDefaultRules(currentBuild, currentYdbVersion, storedBuild, storedYdbVersion)) {
+                return true;
+            } else {
+                errorReason = "Versions are not compatible by default rules";
+                return false;
+            }
+        }
+        errorReason = "Versions are not compatible by given rule sets";
+        return false;
+    }
+}
+
+bool CheckVersionCompatibility(const NKikimrConfig::TStoredCompatibilityInformation* stored,
+        ui32 componentId, TString& errorReason) {
+    return CheckVersionCompatibility(GetCurrentCompatibilityInformation(), 
+            stored, componentId, errorReason);
+}
+
+// obsolete version control
 TMaybe<NActors::TInterconnectProxyCommon::TVersionInfo> VERSION = NActors::TInterconnectProxyCommon::TVersionInfo{
     // version of this binary
     "trunk",

+ 18 - 0
ydb/core/driver_lib/run/version.h

@@ -1,7 +1,25 @@
 #pragma once
 
 #include <library/cpp/actors/interconnect/interconnect_common.h>
+#include <ydb/core/protos/config.pb.h>
 
+const NKikimrConfig::TCurrentCompatibilityInformation* GetCurrentCompatibilityInformation();
+const NKikimrConfig::TStoredCompatibilityInformation* GetUnknownYdbRelease();
+
+NKikimrConfig::TStoredCompatibilityInformation MakeStoredCompatibiltyInformation(ui32 componentId,
+        const NKikimrConfig::TCurrentCompatibilityInformation* current);
+
+NKikimrConfig::TStoredCompatibilityInformation MakeStoredCompatibiltyInformation(ui32 componentId);
+
+bool CheckVersionCompatibility(const NKikimrConfig::TCurrentCompatibilityInformation* current,
+        const NKikimrConfig::TStoredCompatibilityInformation* stored,
+        ui32 componentId, TString& errorReason);
+
+bool CheckVersionCompatibility(const NKikimrConfig::TStoredCompatibilityInformation* stored,
+        ui32 componentId, TString& errorReason);
+
+// obsolete version control
+// TODO: remove in the next major release
 extern TMaybe<NActors::TInterconnectProxyCommon::TVersionInfo> VERSION;
 
 void CheckVersionTag();

+ 621 - 0
ydb/core/driver_lib/run/version_ut.cpp

@@ -9,3 +9,624 @@ Y_UNIT_TEST_SUITE(VersionParser) {
         UNIT_ASSERT_VALUES_EQUAL(GetBranchName("svn://arcadia/arc/branches/kikimr/arcadia"), "branches/kikimr");
     }
 }
+
+Y_UNIT_TEST_SUITE(YdbVersion) {
+    struct TYdbVersion {
+        std::optional<ui32> Year;
+        std::optional<ui32> Major;
+        std::optional<ui32> Minor;
+        std::optional<ui32> Hotfix;
+
+        NKikimrConfig::TYdbVersion ToPB() {
+            NKikimrConfig::TYdbVersion res;
+            if (Year) {
+                res.SetYear(*Year);
+            }
+            if (Major) {
+                res.SetMajor(*Major);
+            }
+            if (Minor) {
+                res.SetMinor(*Minor);
+            }
+            if (Hotfix) {
+                res.SetHotfix(*Hotfix);
+            }
+
+            return res;
+        }
+    };
+
+    struct TCompatibilityRule {
+        std::optional<std::string> Build;
+        std::optional<TYdbVersion> BottomLimit;
+        std::optional<TYdbVersion> UpperLimit;
+        std::optional<ui32> ComponentId;
+        std::optional<bool> Forbidden;
+
+        NKikimrConfig::TCompatibilityRule ToPB() {
+            NKikimrConfig::TCompatibilityRule res;
+            if (Build) {
+                res.SetBuild(Build->data());
+            }
+            if (BottomLimit) {
+                res.MutableBottomLimit()->CopyFrom(BottomLimit->ToPB());
+            }
+            if (UpperLimit) {
+                res.MutableUpperLimit()->CopyFrom(UpperLimit->ToPB());
+            }
+            if (ComponentId) {
+                res.SetComponentId(*ComponentId);
+            }
+            if (Forbidden) {
+                res.SetForbidden(*Forbidden);
+            }
+
+            return res;
+        }
+    };
+
+    struct TCurrentCompatibilityInformation {
+        std::string Build = "ydb";
+        std::optional<TYdbVersion> YdbVersion;
+        std::vector<TCompatibilityRule> CanLoadFrom;
+        std::vector<TCompatibilityRule> StoresReadableBy;
+
+        NKikimrConfig::TCurrentCompatibilityInformation ToPB() {
+            NKikimrConfig::TCurrentCompatibilityInformation res;
+            res.SetBuild(Build.data());
+            if (YdbVersion) {
+                res.MutableYdbVersion()->CopyFrom(YdbVersion->ToPB());
+            }
+
+            for (auto canLoadFrom : CanLoadFrom) {
+                res.AddCanLoadFrom()->CopyFrom(canLoadFrom.ToPB());
+            }
+            for (auto storesReadableBy : StoresReadableBy) {
+                res.AddStoresReadableBy()->CopyFrom(storesReadableBy.ToPB());
+            }
+
+            return res;
+        }
+    };
+
+    struct TStoredCompatibilityInformation {
+        std::string Build = "ydb";
+        std::optional<TYdbVersion> YdbVersion;
+        std::vector<TCompatibilityRule> ReadableBy;
+
+        NKikimrConfig::TStoredCompatibilityInformation ToPB() {
+            NKikimrConfig::TStoredCompatibilityInformation res;
+            res.SetBuild(Build.data());
+            if (YdbVersion) {
+                res.MutableYdbVersion()->CopyFrom(YdbVersion->ToPB());
+            }
+
+            for (auto readableBy : ReadableBy) {
+                res.AddReadableBy()->CopyFrom(readableBy.ToPB());
+            }
+
+            return res;
+        }
+    };
+
+    void Test(TCurrentCompatibilityInformation current, TCurrentCompatibilityInformation store, bool expected) {
+        TString errorReason;
+        auto currentPB = current.ToPB();
+        auto storePB = store.ToPB();
+        auto storedPB = MakeStoredCompatibiltyInformation((ui32)NKikimrConfig::TCompatibilityRule::Test1, &storePB);
+        UNIT_ASSERT_EQUAL_C(CheckVersionCompatibility(&currentPB, &storedPB, 
+            (ui32)NKikimrConfig::TCompatibilityRule::Test1, errorReason), expected, errorReason);
+    }
+
+    Y_UNIT_TEST(DefaultSameVersion) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(DefaultPrevMajor) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 10 }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(DefaultNextMajor) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 8, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 1, .Hotfix = 0 }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(DefaultHotfix) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 10 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(DefaultCompatible) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 1, .Hotfix = 10 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 10, .Hotfix = 0 }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(DefaultNextYear) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 2, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(DefaultPrevYear) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 2, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(DefaultNewMajor) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 0 }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(DefaultOldMajor) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(DefaultDifferentBuild) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 1, .Hotfix = 0 }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(DefaultDifferentBuildIncompatible) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 0 },
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 },
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(LimitOld) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 1, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3 },
+                            .Forbidden = true
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 2, .Hotfix = 1 }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(LimitNew) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 1, .Hotfix = 3 },
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 2, .Hotfix = 0 },
+                    .StoresReadableBy = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 1 },
+                            .Forbidden = true
+                        }
+                    }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(CurrentCanLoadFrom) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 }
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 1 }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(CurrentCanLoadFromAllOlder) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 2, .Major = 4, .Minor = 1, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .UpperLimit = TYdbVersion{ .Year = 2, .Major = 4, .Minor = 1, .Hotfix = 0 }
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 1 }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(CurrentCanLoadFromIncompatible) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 2 }, 
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 }
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 1 }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(CurrentStoresReadableBy) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 },
+                    .StoresReadableBy = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 1, .Hotfix = 0 }
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 1 }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(StoredReadableBy) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 2, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 },
+                    .StoresReadableBy = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 }
+                        }
+                    }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(StoredReadableByIncompatible) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 2, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 },
+                    .StoresReadableBy = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 }
+                        }
+                    }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(StoredWithRules) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 },
+                    .StoresReadableBy = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 }
+                        }
+                    }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(StoredWithRulesIncompatible) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 5, .Minor = 1, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 },
+                    .StoresReadableBy = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 }
+                        }
+                    }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(OldNbsStored) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 2, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 },
+                    .StoresReadableBy = {
+                        TCompatibilityRule{
+                            .Build = "nbs",
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 }
+                        }
+                    }
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(OldNbsIncompatibleStored) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 2, .Hotfix = 0 }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 },
+                    .StoresReadableBy = {
+                        TCompatibilityRule{
+                            .Build = "nbs",
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 3, .Hotfix = 1 }
+                        }
+                    }
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(NewNbsCurrent) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 2, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .Build = "ydb",
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 2, .Hotfix = 0 }
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "ydb",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 2 },
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(NewNbsIncompatibleCurrent) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 2, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .Build = "ydb",
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 2, .Hotfix = 0 }
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .Build = "nbs",
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 2 },
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(OneAcceptedVersion) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 3, .Minor = 2, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 2 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 2 }
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 2 },
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(ForbiddenMinor) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 2, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3 },
+                            .Forbidden = true
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 1, .Minor = 3, .Hotfix = 1 },
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(ExtraAndForbidden) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 2, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 2, .Hotfix = 0 },
+                        },
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 3 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 3 },
+                            .Forbidden = true
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 3, .Hotfix = 0 },
+                }, 
+                false
+        );
+    }
+    Y_UNIT_TEST(SomeRulesAndOtherForbidden) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 2, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 2, .Hotfix = 0 },
+                        },
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 4 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 4 },
+                            .Forbidden = true
+                        }
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 3, .Hotfix = 0 },
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(Component) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 2, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 2, .Hotfix = 0 },
+                            .ComponentId = (ui32)NKikimrConfig::TCompatibilityRule::Test1,
+                        },
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 3, .Hotfix = 0 },
+                }, 
+                true
+        );
+    }
+    Y_UNIT_TEST(OtherComponent) {
+        Test(
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 2, .Hotfix = 0 },
+                    .CanLoadFrom = {
+                        TCompatibilityRule{
+                            .BottomLimit = TYdbVersion{ .Year = 1, .Major = 1 },
+                            .UpperLimit = TYdbVersion{ .Year = 1, .Major = 4, .Minor = 2, .Hotfix = 0 },
+                            .ComponentId = (ui32)NKikimrConfig::TCompatibilityRule::Test2,
+                        },
+                    }
+                }, 
+                TCurrentCompatibilityInformation{
+                    .YdbVersion = TYdbVersion{ .Year = 1, .Major = 2, .Minor = 3, .Hotfix = 0 },
+                }, 
+                false
+        );
+    }
+
+    Y_UNIT_TEST(CompatibleWithSelf) {
+        auto* current = GetCurrentCompatibilityInformation();
+        auto stored = MakeStoredCompatibiltyInformation((ui32)NKikimrConfig::TCompatibilityRule::Test1);
+        TString errorReason;
+        UNIT_ASSERT_C(CheckVersionCompatibility(current, &stored, 
+                (ui32)NKikimrConfig::TCompatibilityRule::Test1, errorReason), errorReason);
+    }
+}

+ 45 - 0
ydb/core/protos/config.proto

@@ -1725,3 +1725,48 @@ message TAppConfig {
 
     optional TConfigVersion Version = 102;
 }
+
+message TYdbVersion {
+    optional uint32 Year = 1;
+    optional uint32 Major = 2;
+    optional uint32 Minor = 3;
+    optional uint32 Hotfix = 4;
+}
+
+message TCompatibilityRule {
+    enum EComponentId {
+        Any = 0;
+        Test1 = 1;
+        Test2 = 2;
+    }
+
+    optional string Build = 1;
+    optional TYdbVersion BottomLimit = 2;
+    optional TYdbVersion UpperLimit = 3;
+
+    // don't use enum, because stored data can have values from newer YDB versions, 
+    // which are not included in current version
+    optional uint32 ComponentId = 4;
+
+    // don't use Forbidden until it's absolutely necessary
+    optional bool Forbidden = 5 [default = false];
+}
+
+message TCurrentCompatibilityInformation {
+    required string Build = 1;
+
+    // if YdbVersion is empty, build is assumed to be non-stable
+    optional TYdbVersion YdbVersion = 2;
+
+    repeated TCompatibilityRule CanLoadFrom = 3;
+    repeated TCompatibilityRule StoresReadableBy = 4;
+}
+
+message TStoredCompatibilityInformation {
+    required string Build = 1;
+
+    // if YdbVersion is empty, build is assumed to be non-stable
+    optional TYdbVersion YdbVersion = 2;
+
+    repeated TCompatibilityRule ReadableBy = 3;
+}