blob: 69fb78acfd541444b000a2cbc588d555a806e14f [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 <string>
#include <vector>
#include <gtest/gtest.h>
#include <stout/check.hpp>
#include <stout/gtest.hpp>
#include <stout/stringify.hpp>
#include <stout/try.hpp>
#include <mesos/resources.hpp>
#include <mesos/resource_quantities.hpp>
#include <mesos/values.hpp>
using std::pair;
using std::string;
using std::vector;
namespace mesos {
namespace internal {
namespace tests {
static vector<pair<string, double>> toVector(
const ResourceQuantities& quantities)
{
vector<pair<string, double>> result;
foreach (auto&& quantity, quantities) {
result.push_back(std::make_pair(quantity.first, quantity.second.value()));
}
return result;
}
TEST(QuantitiesTest, FromStringValid)
{
// A single resource.
ResourceQuantities resourceQuantities =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:10"));
vector<pair<string, double>> expected = {{"cpus", 10}};
EXPECT_EQ(expected, toVector(resourceQuantities));
resourceQuantities =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:3.14"));
expected = {{"cpus", 3.14}};
EXPECT_EQ(expected, toVector(resourceQuantities));
// Whitespace is trimmed.
resourceQuantities =
CHECK_NOTERROR(ResourceQuantities::fromString(" cpus : 3.14 ; disk : 10 "));
expected = {{"cpus", 3.14}, {"disk", 10}};
EXPECT_EQ(expected, toVector(resourceQuantities));
// Zero value.
resourceQuantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:0"));
EXPECT_EQ(0u, resourceQuantities.size());
// Two resources.
resourceQuantities =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:10.5;ports:1"));
expected = {{"cpus", 10.5}, {"ports", 1}};
EXPECT_EQ(expected, toVector(resourceQuantities));
// Two resources with names out of alphabetical order.
resourceQuantities =
CHECK_NOTERROR(ResourceQuantities::fromString("ports:3;cpus:10.5"));
expected = {{"cpus", 10.5}, {"ports", 3}};
EXPECT_EQ(expected, toVector(resourceQuantities));
// Duplicate resource names.
resourceQuantities =
CHECK_NOTERROR(ResourceQuantities::fromString("ports:3;cpus:1;cpus:10.5"));
expected = {{"cpus", 11.5}, {"ports", 3}};
EXPECT_EQ(expected, toVector(resourceQuantities));
}
TEST(QuantitiesTest, FromStringInvalid)
{
// Invalid scalar.
Try<ResourceQuantities> resourceQuantities =
ResourceQuantities::fromString("cpus:a10");
EXPECT_ERROR(resourceQuantities);
resourceQuantities = ResourceQuantities::fromString("cpus:3.14c");
EXPECT_ERROR(resourceQuantities);
// Missing semicolon.
resourceQuantities = ResourceQuantities::fromString("ports:3,cpus:1");
EXPECT_ERROR(resourceQuantities);
// Negative value.
resourceQuantities = ResourceQuantities::fromString("ports:3,cpus:-1");
EXPECT_ERROR(resourceQuantities);
resourceQuantities = ResourceQuantities::fromString("cpus:nan");
EXPECT_ERROR(resourceQuantities);
resourceQuantities = ResourceQuantities::fromString("cpus:-nan");
EXPECT_ERROR(resourceQuantities);
resourceQuantities = ResourceQuantities::fromString("cpus:inf");
EXPECT_ERROR(resourceQuantities);
resourceQuantities = ResourceQuantities::fromString("cpus:-inf");
EXPECT_ERROR(resourceQuantities);
resourceQuantities = ResourceQuantities::fromString("cpus:infinity");
EXPECT_ERROR(resourceQuantities);
resourceQuantities = ResourceQuantities::fromString("cpus:-infinity");
EXPECT_ERROR(resourceQuantities);
}
TEST(QuantitiesTest, FromScalarResources)
{
// Empty resources.
ResourceQuantities quantities =
ResourceQuantities::fromScalarResources(Resources());
EXPECT_EQ(0u, quantities.size());
// Result entries are ordered alphabetically.
quantities = ResourceQuantities::fromScalarResources(
CHECK_NOTERROR(Resources::parse("cpus:1;mem:512;disk:800")));
vector<pair<string, double>> expected = {
{"cpus", 1}, {"disk", 800}, {"mem", 512}};
EXPECT_EQ(expected, toVector(quantities));
}
TEST(QuantitiesTest, FromResources)
{
// Empty resources.
ResourceQuantities quantities =
ResourceQuantities::fromResources(Resources());
EXPECT_EQ(0u, quantities.size());
// Result entries are ordered alphabetically.
quantities =
ResourceQuantities::fromResources(
CHECK_NOTERROR(Resources::parse(
"cpus:1;mem:512;ports:[5000-6000];zones:{a,b};disk:800")));
vector<pair<string, double>> expected = {
{"cpus", 1}, {"disk", 800}, {"mem", 512}, {"ports", 1001}, {"zones", 2}};
EXPECT_EQ(expected, toVector(quantities));
}
TEST(QuantitiesTest, Addition)
{
// Empty quantity:
// "cpus:10" + [ ] = "cpus:10"
ResourceQuantities quantities =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:10"));
quantities += ResourceQuantities();
vector<pair<string, double>> expected = {{"cpus", 10}};
EXPECT_EQ(expected, toVector(quantities));
// Same name entries:
// "cpus:10" + "cpus:0.1" = "cpus:10.1"
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:10"));
quantities += CHECK_NOTERROR(ResourceQuantities::fromString("cpus:0.1"));
expected = {{"cpus", 10.1}};
EXPECT_EQ(expected, toVector(quantities));
// Different name entries, insert at head:
// "cpus:10.1" + "alphas:1" = "alphas:1;cpus:10.1"
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:10.1"));
quantities += CHECK_NOTERROR(ResourceQuantities::fromString("alphas:1"));
expected = {{"alphas", 1}, {"cpus", 10.1}};
EXPECT_EQ(expected, toVector(quantities));
// Different name entries, insert at tail:
// "cpus:10.1" + "mem:1" = "cpus:10.1;mem:1"
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:10.1"));
quantities += CHECK_NOTERROR(ResourceQuantities::fromString("mem:1"));
expected = {{"cpus", 10.1}, {"mem", 1}};
EXPECT_EQ(expected, toVector(quantities));
// Mixed: "alphas:1;cpus:10.1" + "cpus:1;mem:100" =
// "alphas:1;cpus:11.1;mem:100"
quantities =
CHECK_NOTERROR(ResourceQuantities::fromString("alphas:1;cpus:10.1"));
quantities +=
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:100"));
expected = {{"alphas", 1}, {"cpus", 11.1}, {"mem", 100}};
EXPECT_EQ(expected, toVector(quantities));
}
TEST(QuantitiesTest, Subtraction)
{
// Empty subtrahend:
// [ ] - "cpus:1" = [ ]
ResourceQuantities quantities{};
quantities -= CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
EXPECT_EQ(0u, quantities.size());
// Empty minuend:
// "cpus:1" - [ ] = "cpus:1"
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
quantities -= ResourceQuantities();
vector<pair<string, double>> expected = {{"cpus", 1}};
EXPECT_EQ(expected, toVector(quantities));
// Same name entry, positive result is retained:
// "cpus:1" - "cpus:0.4" = "cpus:0.6"
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
quantities -= CHECK_NOTERROR(ResourceQuantities::fromString("cpus:0.4"));
expected = {{"cpus", 0.6}};
EXPECT_EQ(expected, toVector(quantities));
// Same name entry, zero is dropped:
// "cpus:1" - "cpus:1" = [ ]
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
quantities -= CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
EXPECT_EQ(0u, quantities.size());
// Same name entry, negative entry is dropped:
// "cpus:1" - "cpus:1.4" = [ ]
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
quantities -= CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1.4"));
EXPECT_EQ(0u, quantities.size());
// Different name entry:
// "cpus:1" - "mem:100" = "cpus:1"
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
quantities -= CHECK_NOTERROR(ResourceQuantities::fromString("mem:100"));
expected = {{"cpus", 1}};
EXPECT_EQ(expected, toVector(quantities));
// "cpus:1" - "alphas:1" = "cpus:1"
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
quantities -= CHECK_NOTERROR(ResourceQuantities::fromString("alphas:1"));
expected = {{"cpus", 1}};
EXPECT_EQ(expected, toVector(quantities));
// Mixed: "cpus:1;mem:100" - "alphas:1;cpus:0.5" = "cpus:0.5;mem:100"
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:100"));
quantities -=
CHECK_NOTERROR(ResourceQuantities::fromString("alphas:1;cpus:0.5"));
expected = {{"cpus", 0.5}, {"mem", 100}};
EXPECT_EQ(expected, toVector(quantities));
}
TEST(QuantitiesTest, Contains)
{
ResourceQuantities empty{};
EXPECT_TRUE(empty.contains(empty));
ResourceQuantities some =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
EXPECT_TRUE(some.contains(empty));
EXPECT_FALSE(empty.contains(some));
// Self contains.
some = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1"));
EXPECT_TRUE(some.contains(some));
// Superset and subset.
ResourceQuantities superset =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1;disk:1"));
ResourceQuantities subset =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1"));
EXPECT_TRUE(superset.contains(subset));
EXPECT_FALSE(subset.contains(superset));
// Intersected sets.
ResourceQuantities set1 =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1"));
ResourceQuantities set2 =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;disk:1"));
EXPECT_FALSE(set1.contains(set2));
EXPECT_FALSE(set2.contains(set1));
// Sets with no intersection.
set1 = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1"));
set2 = CHECK_NOTERROR(ResourceQuantities::fromString("gpu:1;disk:1"));
EXPECT_FALSE(set1.contains(set2));
EXPECT_FALSE(set2.contains(set1));
// Same name, different scalars.
superset = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:2;mem:2"));
subset = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:2;mem:1"));
EXPECT_TRUE(superset.contains(subset));
EXPECT_FALSE(subset.contains(superset));
}
TEST(QuantitiesTest, Stringify)
{
ResourceQuantities empty{};
EXPECT_EQ("{}", stringify(empty));
ResourceQuantities some =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1024"));
EXPECT_EQ("cpus:1; mem:1024", stringify(some));
}
TEST(QuantitiesTest, Sum)
{
ResourceQuantities empty{};
ResourceQuantities cpus1 =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1"));
ResourceQuantities cpus2 =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:2"));
ResourceQuantities memory =
CHECK_NOTERROR(ResourceQuantities::fromString("memory:1"));
ResourceQuantities cpuAndMemory =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;memory:1"));
hashmap<string, ResourceQuantities> quantitiesMap;
vector<pair<string, double>> expected;
EXPECT_EQ(expected, toVector(ResourceQuantities::sum(quantitiesMap)));
quantitiesMap["empty"] = empty;
EXPECT_EQ(expected, toVector(ResourceQuantities::sum(quantitiesMap)));
quantitiesMap["cpus1"] = cpus1;
expected = {{"cpus", 1}};
EXPECT_EQ(expected, toVector(ResourceQuantities::sum(quantitiesMap)));
quantitiesMap["cpus2"] = cpus2;
expected = {{"cpus", 3}};
EXPECT_EQ(expected, toVector(ResourceQuantities::sum(quantitiesMap)));
quantitiesMap["memory"] = memory;
expected = {{"cpus", 3}, {"memory", 1}};
EXPECT_EQ(expected, toVector(ResourceQuantities::sum(quantitiesMap)));
quantitiesMap["cpuAndMemory"] = cpuAndMemory;
expected = {{"cpus", 4}, {"memory", 2}};
EXPECT_EQ(expected, toVector(ResourceQuantities::sum(quantitiesMap)));
}
static vector<pair<string, double>> toVector(
const ResourceLimits& limits)
{
vector<pair<string, double>> result;
foreach (auto& limit, limits) {
result.push_back(std::make_pair(limit.first, limit.second.value()));
}
return result;
}
// These are similar to `QuantitiesTest.FromStringValid` except when zero
// values are involved.
TEST(LimitsTest, FromStringValid)
{
// A single resource.
ResourceLimits resourceLimits =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:10"));
vector<pair<string, double>> expected = {{"cpus", 10}};
EXPECT_EQ(expected, toVector(resourceLimits));
resourceLimits =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:3.14"));
expected = {{"cpus", 3.14}};
EXPECT_EQ(expected, toVector(resourceLimits));
// Whitespace is trimmed.
resourceLimits =
CHECK_NOTERROR(ResourceLimits::fromString(" cpus : 3.14 ; disk : 10 "));
expected = {{"cpus", 3.14}, {"disk", 10}};
EXPECT_EQ(expected, toVector(resourceLimits));
// Zero value is preserved.
resourceLimits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:0"));
expected = {{"cpus", 0}};
EXPECT_EQ(expected, toVector(resourceLimits));
// Two resources.
resourceLimits =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:10.5;ports:1"));
expected = {{"cpus", 10.5}, {"ports", 1}};
EXPECT_EQ(expected, toVector(resourceLimits));
// Two resources with names out of alphabetical order.
resourceLimits =
CHECK_NOTERROR(ResourceLimits::fromString("ports:3;cpus:10.5"));
expected = {{"cpus", 10.5}, {"ports", 3}};
EXPECT_EQ(expected, toVector(resourceLimits));
}
// These are identical to `QuantitiesTest.FromStringInvalid`.
TEST(LimitsTest, FromStringInvalid)
{
// Invalid scalar.
Try<ResourceLimits> resourceLimits =
ResourceLimits::fromString("cpus:a10");
EXPECT_ERROR(resourceLimits);
resourceLimits = ResourceLimits::fromString("cpus:3.14c");
EXPECT_ERROR(resourceLimits);
// Missing semicolon.
resourceLimits = ResourceLimits::fromString("ports:3,cpus:1");
EXPECT_ERROR(resourceLimits);
// Negative value.
resourceLimits = ResourceLimits::fromString("ports:3,cpus:-1");
EXPECT_ERROR(resourceLimits);
resourceLimits = ResourceLimits::fromString("cpus:nan");
EXPECT_ERROR(resourceLimits);
resourceLimits = ResourceLimits::fromString("cpus:-nan");
EXPECT_ERROR(resourceLimits);
resourceLimits = ResourceLimits::fromString("cpus:inf");
EXPECT_ERROR(resourceLimits);
resourceLimits = ResourceLimits::fromString("cpus:-inf");
EXPECT_ERROR(resourceLimits);
resourceLimits = ResourceLimits::fromString("cpus:infinity");
EXPECT_ERROR(resourceLimits);
resourceLimits = ResourceLimits::fromString("cpus:-infinity");
EXPECT_ERROR(resourceLimits);
// Duplicate entries.
resourceLimits = ResourceLimits::fromString("cpus:1;cpus:2");
EXPECT_ERROR(resourceLimits);
}
TEST(LimitsTest, Contains)
{
ResourceLimits infinite{};
EXPECT_TRUE(infinite.contains(infinite));
ResourceLimits finite = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1"));
infinite = ResourceLimits();
EXPECT_TRUE(infinite.contains(finite));
EXPECT_FALSE(finite.contains(infinite));
finite = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1"));
EXPECT_TRUE(finite.contains(finite));
ResourceLimits moreLimits =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1;disk:1"));
ResourceLimits lessLimits =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1"));
EXPECT_TRUE(lessLimits.contains(moreLimits));
EXPECT_FALSE(moreLimits.contains(lessLimits));
// Intersected sets.
ResourceLimits limits1 =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1"));
ResourceLimits limits2 =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;disk:1"));
EXPECT_FALSE(limits1.contains(limits2));
EXPECT_FALSE(limits2.contains(limits1));
// Sets with no intersection.
limits1 = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1"));
limits2 = CHECK_NOTERROR(ResourceLimits::fromString("gpu:1;disk:1"));
EXPECT_FALSE(limits1.contains(limits2));
EXPECT_FALSE(limits2.contains(limits1));
// Same name, different scalars.
ResourceLimits higherLimits =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:2;mem:2"));
ResourceLimits lowerLimits =
CHECK_NOTERROR(ResourceLimits::fromString("cpus:2;mem:1"));
EXPECT_TRUE(higherLimits.contains(lowerLimits));
EXPECT_FALSE(lowerLimits.contains(higherLimits));
}
TEST(LimitsTest, ContainsQuantities)
{
ResourceLimits noLimit{};
ResourceQuantities emptyQuantity{};
EXPECT_TRUE(noLimit.contains(emptyQuantity));
ResourceLimits limits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1"));
EXPECT_TRUE(limits.contains(emptyQuantity));
limits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1"));
ResourceQuantities quantities =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1"));
EXPECT_TRUE(limits.contains(quantities));
// Superset and subset.
limits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1;disk:1"));
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1"));
EXPECT_TRUE(limits.contains(quantities));
limits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1"));
quantities =
CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;mem:1;disk:1"));
EXPECT_TRUE(limits.contains(quantities));
// Intersected sets.
limits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1"));
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:1;disk:1"));
EXPECT_TRUE(limits.contains(quantities));
// Sets with no intersection.
limits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:1;mem:1"));
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("gpu:1;disk:1"));
EXPECT_TRUE(limits.contains(quantities));
// Same name, different scalars.
limits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:2;mem:2"));
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:2;mem:1"));
EXPECT_TRUE(limits.contains(quantities));
limits = CHECK_NOTERROR(ResourceLimits::fromString("cpus:2;mem:1"));
quantities = CHECK_NOTERROR(ResourceQuantities::fromString("cpus:2;mem:2"));
EXPECT_FALSE(limits.contains(quantities));
}
TEST(LimitsTest, SubtractQuantities)
{
auto limits = [](const string& resourceLimitsString) {
return CHECK_NOTERROR(ResourceLimits::fromString(resourceLimitsString));
};
auto subtract = [](const string& resourceLimitsString,
const string& resourceQuantitiesString) {
ResourceLimits limits =
CHECK_NOTERROR(ResourceLimits::fromString(resourceLimitsString));
ResourceQuantities quantities =
CHECK_NOTERROR(ResourceQuantities::fromString(resourceQuantitiesString));
return limits - quantities;
};
EXPECT_EQ(limits(""), subtract("", ""));
EXPECT_EQ(limits(""), subtract("", "cpus:10"));
EXPECT_EQ(limits("cpus:1"), subtract("cpus:1", ""));
EXPECT_EQ(limits("cpus:0"), subtract("cpus:1", "cpus:1"));
EXPECT_EQ(limits("cpus:0"), subtract("cpus:1", "cpus:2"));
EXPECT_EQ(limits("cpus:0;mem:10"), subtract("cpus:1;mem:10", "cpus:1"));
EXPECT_EQ(
limits("cpus:0;mem:10"), subtract("cpus:1;mem:10", "cpus:1;disk:10"));
EXPECT_EQ(
limits("cpus:0;mem:5"),
subtract("cpus:1;mem:10", "cpus:1;mem:5;disk:10"));
}
} // namespace tests {
} // namespace internal {
} // namespace mesos {