Repository: mesos Updated Branches: refs/heads/master e39809bd6 -> 49f4b2aff
Fixed comparison logic for additive reconfiguration policy. The case where multiple resources have the same name was not handled correctly, and could result in false negatives. Review: https://reviews.apache.org/r/65074/ Project: http://git-wip-us.apache.org/repos/asf/mesos/repo Commit: http://git-wip-us.apache.org/repos/asf/mesos/commit/49f4b2af Tree: http://git-wip-us.apache.org/repos/asf/mesos/tree/49f4b2af Diff: http://git-wip-us.apache.org/repos/asf/mesos/diff/49f4b2af Branch: refs/heads/master Commit: 49f4b2aff4d9f0603947b04071203a55d008a61c Parents: e39809b Author: Benno Evers <bev...@mesosphere.com> Authored: Fri Jan 12 11:33:57 2018 -0800 Committer: Vinod Kone <vinodk...@gmail.com> Committed: Fri Jan 12 11:35:00 2018 -0800 ---------------------------------------------------------------------- include/mesos/resources.hpp | 5 ++ src/common/resources.cpp | 22 +++++++- src/slave/compatibility.cpp | 73 ++++++++++++------------- src/slave/compatibility.hpp | 4 +- src/tests/slave_compatibility_tests.cpp | 80 ++++++++++++++++++++++++++++ 5 files changed, 142 insertions(+), 42 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/mesos/blob/49f4b2af/include/mesos/resources.hpp ---------------------------------------------------------------------- diff --git a/include/mesos/resources.hpp b/include/mesos/resources.hpp index 69bf823..7afe0d8 100644 --- a/include/mesos/resources.hpp +++ b/include/mesos/resources.hpp @@ -492,6 +492,11 @@ public: // Error if the conversion cannot be applied. Try<Resources> apply(const ResourceConversion& conversion) const; + // Finds a resource object with the same metadata (i.e. AllocationInfo, + // ReservationInfo, etc.) as the given one, ignoring the actual value. + // If multiple matching resources exist, the first match is returned. + Option<Resource> match(const Resource& resource) const; + // Obtains the conversion from the given operation and applies the // conversion. This method serves a syntax sugar for applying a // resource conversion. http://git-wip-us.apache.org/repos/asf/mesos/blob/49f4b2af/src/common/resources.cpp ---------------------------------------------------------------------- diff --git a/src/common/resources.cpp b/src/common/resources.cpp index 69e8e34..262dcfb 100644 --- a/src/common/resources.cpp +++ b/src/common/resources.cpp @@ -260,7 +260,7 @@ bool operator!=(const Resource::DiskInfo& left, const Resource::DiskInfo& right) } -bool operator==(const Resource& left, const Resource& right) +static bool compareResourceMetadata(const Resource& left, const Resource& right) { if (left.name() != right.name() || left.type() != right.type()) { return false; @@ -315,6 +315,15 @@ bool operator==(const Resource& left, const Resource& right) return false; } + return true; +} + + +bool operator==(const Resource& left, const Resource& right) { + if (!compareResourceMetadata(left, right)) { + return false; + } + if (left.type() == Value::SCALAR) { return left.scalar() == right.scalar(); } else if (left.type() == Value::RANGES) { @@ -1852,6 +1861,17 @@ Option<Value::Ranges> Resources::ephemeral_ports() const } +Option<Resource> Resources::match(const Resource& resource) const +{ + foreach (const Resource_& resource_, resources) { + if (compareResourceMetadata(resource_.resource, resource)) { + return resource_.resource; + } + } + + return None(); +} + ///////////////////////////////////////////////// // Private member functions. ///////////////////////////////////////////////// http://git-wip-us.apache.org/repos/asf/mesos/blob/49f4b2af/src/slave/compatibility.cpp ---------------------------------------------------------------------- diff --git a/src/slave/compatibility.cpp b/src/slave/compatibility.cpp index 4ead4a5..5551d93 100644 --- a/src/slave/compatibility.cpp +++ b/src/slave/compatibility.cpp @@ -19,6 +19,8 @@ #include <stout/strings.hpp> #include <stout/unreachable.hpp> +#include <mesos/attributes.hpp> +#include <mesos/resources.hpp> #include <mesos/values.hpp> #include "mesos/type_utils.hpp" @@ -49,33 +51,6 @@ Try<Nothing> equal( } -// T is instantiated below as either `Resource` or `Attribute`. -template<typename T> -Try<T> getMatchingValue( - const T& previous, - const google::protobuf::RepeatedPtrField<T>& values) -{ - auto match = std::find_if( - values.begin(), - values.end(), - [&previous](const T& value) { - return previous.name() == value.name(); - }); - - if (match == values.end()) { - return Error("Couldn't find '" + previous.name() + "'"); - } - - if (match->type() != previous.type()) { - return Error( - "Type of '" + previous.name() + "' changed from " + - stringify(previous.type()) + " to " + stringify(match->type())); - } - - return *match; -} - - Try<Nothing> additive( const SlaveInfo& previous, const SlaveInfo& current) @@ -101,16 +76,20 @@ Try<Nothing> additive( stringify(current.domain())); } + // As a side effect, the Resources constructor also normalizes all addable + // resources, e.g. 'cpus:5;cpus:5' -> cpus:10 + Resources previousResources(previous.resources()); + Resources currentResources(current.resources()); + // TODO(bennoe): We should probably check `resources.size()` and switch to a // smarter algorithm for the matching when its bigger than, say, 20. - for (const Resource& resource : previous.resources()) { - Try<Resource> match = - getMatchingValue(resource, current.resources()); + for (const Resource& resource : previousResources) { + Option<Resource> match = currentResources.match(resource); - if (match.isError()) { + if (match.isNone()) { return Error( - "Configuration change not permitted under 'additive' policy: " + - match.error()); + "Configuration change not permitted under 'additive' policy. " + "Resource not found: " + stringify(resource)); } switch (resource.type()) { @@ -145,20 +124,34 @@ Try<Nothing> additive( continue; } case Value::TEXT: { - // Text resources are not supported. + // Text resources are not supported by Mesos. UNREACHABLE(); } } } + const google::protobuf::RepeatedPtrField<Attribute>& currentAttributes = + current.attributes(); + for (const Attribute& attribute : previous.attributes()) { - Try<Attribute> match = - getMatchingValue(attribute, current.attributes()); + auto match = std::find_if( + currentAttributes.begin(), + currentAttributes.end(), + [&attribute](const Attribute& value) { + return attribute.name() == value.name(); + }); + + if (match == currentAttributes.end()) { + return Error( + "Configuration change not permitted under 'additive' policy: " + "Couldn't find attribute " + stringify(attribute)); + } - if (match.isError()) { + if (match->type() != attribute.type()) { return Error( - "Configuration change not permitted under 'additive' policy: " + - match.error()); + "Configuration change not permitted under 'additive' policy: " + "Type of attribute '" + attribute.name() + "' changed from " + + stringify(attribute.type()) + " to " + stringify(match->type())); } switch (attribute.type()) { @@ -192,7 +185,7 @@ Try<Nothing> additive( continue; } case Value::SET: { - // Set attributes are not supported. + // Set attributes are not supported by Mesos. UNREACHABLE(); } } http://git-wip-us.apache.org/repos/asf/mesos/blob/49f4b2af/src/slave/compatibility.hpp ---------------------------------------------------------------------- diff --git a/src/slave/compatibility.hpp b/src/slave/compatibility.hpp index 78b421a..de21fd6 100644 --- a/src/slave/compatibility.hpp +++ b/src/slave/compatibility.hpp @@ -48,12 +48,14 @@ Try<Nothing> equal( // | For type SCALAR: The new value must be not smaller than the old. // | For type RANGE: The new value must include the old ranges. // | For type SET: The new value must be a superset of the old. -// | New resources are permitted. +// | New resources are permitted. In the presence of reservations, +// | these rules are applied per role. // attributes | All previous attributes must be present with the same type. // | For type SCALAR: The new value must be not smaller than the old. // | For type RANGE: The new value must include the old ranges. // | For type TEXT: The new value must exactly match the previous. // | New attributes are permitted. +// Try<Nothing> additive( const SlaveInfo& previous, const SlaveInfo& current); http://git-wip-us.apache.org/repos/asf/mesos/blob/49f4b2af/src/tests/slave_compatibility_tests.cpp ---------------------------------------------------------------------- diff --git a/src/tests/slave_compatibility_tests.cpp b/src/tests/slave_compatibility_tests.cpp index ab5ed29..12bfdc3 100644 --- a/src/tests/slave_compatibility_tests.cpp +++ b/src/tests/slave_compatibility_tests.cpp @@ -66,6 +66,11 @@ TEST_F(SlaveCompatibilityTest, Equal) *(changedResources.mutable_resources()) = Resources::parse("cpus:600").get(); ASSERT_ERROR(slave::compatibility::equal(original, changedResources)); + + *(changedResources.mutable_resources()) = + Resources::parse("cpus:250;cpus:250").get(); + + ASSERT_SOME(slave::compatibility::equal(original, changedResources)); } @@ -116,6 +121,11 @@ TEST_F(SlaveCompatibilityTest, Additive) ASSERT_ERROR(slave::compatibility::additive( changedScalarResource, originalScalarResource)); + // Going from 50 to 30+30 is still an increase + SlaveInfo changedScalarResource2 = createSlaveInfo("cpus:30;cpus:30", ""); + ASSERT_SOME(slave::compatibility::additive( + originalScalarResource, changedScalarResource2)); + // Range attributes can be extended but not shrinked. SlaveInfo originalRangeResource = createSlaveInfo("range:[100-200]", ""); SlaveInfo changedRangeResource = createSlaveInfo("range:[100-300]", ""); @@ -170,6 +180,76 @@ TEST_F(SlaveCompatibilityTest, Additive) changedRangeAttribute, originalRangeAttribute)); } + +TEST_F(SlaveCompatibilityTest, AdditiveWithReservations) +{ + SlaveInfo originalReservations = createSlaveInfo("foo(A):10;foo(B):20", ""); + + // Ok: Both roles have increased amounts of `foo`. + SlaveInfo increasedReservations = createSlaveInfo("foo(A):20;foo(B):30", ""); + ASSERT_SOME(slave::compatibility::additive( + originalReservations, increasedReservations)); + + // Not ok: The total increases, but the amount of `foo` reserved for role A + // decreased. + SlaveInfo modifiedReservations = createSlaveInfo("foo(A):5;foo(B):50", ""); + ASSERT_ERROR(slave::compatibility::additive( + originalReservations, modifiedReservations)); +} + + +TEST_F(SlaveCompatibilityTest, Disks) +{ + const char* diskAsJson = R"_( + [ + { + "name": "disk", + "type": "SCALAR", + "scalar": { + "value": 868000 + } + } + ] + )_"; + + const char* diskAndMountAsJson = R"_( + [ + { + "name": "disk", + "type": "SCALAR", + "scalar": { + "value": 868000 + } + }, + { + "name": "disk", + "type": "SCALAR", + "scalar": { + "value": 1830000 + }, + "disk": { + "source": { + "type": "MOUNT", + "mount": { + "root" : "/srv/mesos/volumes/a" + } + } + } + } + ] + )_"; + + + + SlaveInfo info1 = createSlaveInfo(diskAsJson, ""); + SlaveInfo info2 = createSlaveInfo(diskAndMountAsJson, ""); + + ASSERT_SOME(slave::compatibility::additive(info1, info2)); + + // MESOS-8410. + ASSERT_SOME(slave::compatibility::additive(info2, info2)); +} + } // namespace tests { } // namespace internal { } // namespace mesos {