From ea567565fecc34bab7f2378f4a495b9963f68138 Mon Sep 17 00:00:00 2001 From: Mykola Kobets Date: Thu, 21 May 2026 10:29:12 +0300 Subject: [PATCH] cm: launcher: rank scheduling errors in balancer Signed-off-by: Mykola Kobets --- src/core/cm/launcher/balancer.cpp | 51 +++++++----- src/core/cm/launcher/balancer.hpp | 5 +- src/core/cm/launcher/rankederror.hpp | 111 +++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 23 deletions(-) create mode 100644 src/core/cm/launcher/rankederror.hpp diff --git a/src/core/cm/launcher/balancer.cpp b/src/core/cm/launcher/balancer.cpp index 40773913a..7b8e85c5d 100644 --- a/src/core/cm/launcher/balancer.cpp +++ b/src/core/cm/launcher/balancer.cpp @@ -100,52 +100,55 @@ Error Balancer::PerformNodeBalancing(Array>& instances) continue; } - Error scheduleErr = ErrorEnum::eNotFound; + RankedError scheduleErr; for (const auto& manifest : imageIndex->mManifests) { LOG_DBG() << "Try to schedule instance" << Log::Field("instance", id) << Log::Field("manifest", manifest.mDigest); - scheduleErr = ScheduleInstance(instance, manifest); - if (scheduleErr.IsNone()) { + auto currentError = ScheduleInstance(instance, manifest); + if (currentError.IsNone()) { LOG_DBG() << "Instance scheduled successfully" << Log::Field("nodeID", info.mNodeID); break; } + + scheduleErr = RankedError::SelectHigherRankedError(scheduleErr, currentError); } if (!scheduleErr.IsNone()) { - LOG_ERR() << "Can't schedule instance" << Log::Field(scheduleErr); + LOG_ERR() << "Can't schedule instance" << Log::Field(scheduleErr.mError); - mInstanceManager->ScheduleInstance(instance, scheduleErr); + mInstanceManager->ScheduleInstance(instance, scheduleErr.mError); } } return ErrorEnum::eNone; } -Error Balancer::ScheduleInstance(SharedPtr& instance, const oci::IndexContentDescriptor& imageDescriptor) +RankedError Balancer::ScheduleInstance( + SharedPtr& instance, const oci::IndexContentDescriptor& imageDescriptor) { auto nodes = MakeUnique>(&mAllocator); auto releaseConfigs = DeferRelease(reinterpret_cast(1), [&](int*) { instance->ResetConfigs(); }); if (auto err = instance->LoadConfigs(imageDescriptor); !err.IsNone()) { - return AOS_ERROR_WRAP(Error(err, "can't load instance configs")); + return {AOS_ERROR_WRAP(Error(err, "can't load instance configs")), RankedError::cHighErrorRank}; } // Select node runtimes if (auto err = mNodeManager->GetConnectedNodes(*nodes); !err.IsNone()) { - return AOS_ERROR_WRAP(Error(err, "get connected nodes failed")); + return {AOS_ERROR_WRAP(Error(err, "get connected nodes failed")), RankedError::cHighErrorRank}; } if (auto err = SelectNodes(*instance, *nodes); !err.IsNone()) { - return AOS_ERROR_WRAP(Error(err, "can't find node for instance")); + return {AOS_ERROR_WRAP(Error(err, "can't find node for instance")), RankedError::cHighErrorRank}; } auto [nodeRuntime, selectErr] = SelectRuntime(*instance, *nodes); if (!selectErr.IsNone()) { - return AOS_ERROR_WRAP(Error(selectErr, "can't find runtime for instance")); + return selectErr; } // Schedule instance @@ -153,10 +156,10 @@ Error Balancer::ScheduleInstance(SharedPtr& instance, const oci::Index const auto& runtime = nodeRuntime.mSecond; if (auto err = mInstanceManager->ScheduleInstance(instance, *node, runtime->mRuntimeID); !err.IsNone()) { - return AOS_ERROR_WRAP(Error(err, "can't schedule instance")); + return {AOS_ERROR_WRAP(Error(err, "can't schedule instance")), RankedError::cHighErrorRank}; } - return ErrorEnum::eNone; + return {ErrorEnum::eNone, RankedError::cNoErrorRank}; } Error Balancer::SelectNodes(Instance& instance, Array& nodes) @@ -194,42 +197,48 @@ void Balancer::FilterNodesByResources(Instance& instance, Array& nodes) nodes.RemoveIf([&instance](const Node* node) { return !instance.AreNodeResourcesOk(node->GetInfo().mResources); }); } -RetWithError> Balancer::SelectRuntime(Instance& instance, Array& nodes) +RetWithRankedError> Balancer::SelectRuntime(Instance& instance, Array& nodes) { auto nodeRuntimes = MakeUnique(&mAllocator); if (auto err = CreateRuntimes(nodes, *nodeRuntimes); !err.IsNone()) { - return {nullptr, AOS_ERROR_WRAP(err)}; + return {nullptr, AOS_ERROR_WRAP(err), RankedError::cHighErrorRank}; } FilterByRuntimeType(instance, *nodeRuntimes); if (nodeRuntimes->IsEmpty()) { - return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested runtime type"))}; + return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested runtime type")), + RankedError::cLowErrorRank}; } FilterByPlatform(instance, *nodeRuntimes); if (nodeRuntimes->IsEmpty()) { - return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested platform"))}; + return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested platform")), + RankedError::cLowErrorRank}; } FilterByCPU(instance, *nodeRuntimes); if (nodeRuntimes->IsEmpty()) { - return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested CPU"))}; + return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested CPU")), + RankedError::cHighErrorRank}; } FilterByRAM(instance, *nodeRuntimes); if (nodeRuntimes->IsEmpty()) { - return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested RAM"))}; + return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested RAM")), + RankedError::cHighErrorRank}; } FilterByNumInstances(*nodeRuntimes); if (nodeRuntimes->IsEmpty()) { - return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with requested RAM"))}; + return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "no runtimes with enough num instances")), + RankedError::cHighErrorRank}; } FilterTopPriorityNodes(*nodeRuntimes); if (nodeRuntimes->IsEmpty()) { - return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "failed top priority nodes filtering"))}; + return {nullptr, AOS_ERROR_WRAP(Error(ErrorEnum::eNotFound, "failed top priority nodes filtering")), + RankedError::cLowErrorRank}; } // Select best node. @@ -262,7 +271,7 @@ RetWithError> Balancer::SelectRuntime(Instance& // Return result. Pair result {bestNode.mFirst, bestNodeRuntimes.Front()}; - return {result, ErrorEnum::eNone}; + return {result, ErrorEnum::eNone, RankedError::cNoErrorRank}; } Error Balancer::CreateRuntimes(Array& nodes, NodeRuntimes& runtimes) diff --git a/src/core/cm/launcher/balancer.hpp b/src/core/cm/launcher/balancer.hpp index 6a4b21d28..0430bdbdd 100644 --- a/src/core/cm/launcher/balancer.hpp +++ b/src/core/cm/launcher/balancer.hpp @@ -14,6 +14,7 @@ #include "imageinfoprovider.hpp" #include "instancemanager.hpp" #include "nodemanager.hpp" +#include "rankederror.hpp" namespace aos::cm::launcher { @@ -66,7 +67,7 @@ class Balancer { Error PerformNodeBalancing(Array>& instances); - Error ScheduleInstance(SharedPtr& instance, const oci::IndexContentDescriptor& imageDescriptor); + RankedError ScheduleInstance(SharedPtr& instance, const oci::IndexContentDescriptor& imageDescriptor); // Selects nodes Error SelectNodes(Instance& instance, Array& nodes); @@ -75,7 +76,7 @@ class Balancer { void FilterNodesByResources(Instance& instance, Array& nodes); // Selects runtime - RetWithError> SelectRuntime(Instance& instance, Array& nodes); + RetWithRankedError> SelectRuntime(Instance& instance, Array& nodes); Error CreateRuntimes(Array& nodes, NodeRuntimes& runtimes); diff --git a/src/core/cm/launcher/rankederror.hpp b/src/core/cm/launcher/rankederror.hpp new file mode 100644 index 000000000..cfe0d9735 --- /dev/null +++ b/src/core/cm/launcher/rankederror.hpp @@ -0,0 +1,111 @@ +/* + * Copyright (C) 2025 EPAM Systems, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef AOS_CORE_CM_LAUNCHER_RANKEDERROR_HPP_ +#define AOS_CORE_CM_LAUNCHER_RANKEDERROR_HPP_ + +#include + +namespace aos::cm::launcher { + +/** + * Error container with explicit error rank. + */ +struct RankedError { + static constexpr int cLowErrorRank = 1; + static constexpr int cHighErrorRank = 2; + static constexpr int cNoErrorRank = 3; + + // cppcheck-suppress noExplicitConstructor + RankedError(const Error& error = ErrorEnum::eNone, int errorRank = cNoErrorRank) + : mError(error) + , mErrorRank(errorRank) + { + } + + bool IsNone() const { return mError.IsNone(); } + + /** + * Selects error with higher rank. + * + * If one of errors is none, returns the other one. + */ + static RankedError SelectHigherRankedError(const RankedError& left, const RankedError& right) + { + if (left.IsNone()) { + return right; + } + + if (right.IsNone()) { + return left; + } + + return left.mErrorRank >= right.mErrorRank ? left : right; + } + + Error mError; + int mErrorRank {}; +}; + +/** + * Container that holds value and ranked error. + * + * @tparam T value type. + */ +template +struct RetWithRankedError { + // cppcheck-suppress noExplicitConstructor + /** + * Constructs return value with error instance and rank. + * + * @param value return value. + * @param error return error. + * @param errorRank return error rank. + */ + RetWithRankedError(const T& value, const Error& error = ErrorEnum::eNone, int errorRank = RankedError::cNoErrorRank) + : mValue(value) + , mRankedError(error, errorRank) + { + } + + // cppcheck-suppress noExplicitConstructor + /** + * Constructs return value with error instance and rank. + * + * @param value return value. + * @param error return error. + * @param errorRank return error rank. + */ + RetWithRankedError(T&& value, const Error& error = ErrorEnum::eNone, int errorRank = RankedError::cNoErrorRank) + : mValue(Move(value)) + , mRankedError(error, errorRank) + { + } + + /** + * Comparison operators. + */ + bool operator==(const RetWithRankedError& other) const + { + return mValue == other.mValue && mRankedError.mError == other.mRankedError.mError + && mRankedError.mErrorRank == other.mRankedError.mErrorRank; + } + bool operator!=(const RetWithRankedError& other) const { return !(*this == other); } + + /** + * Holds returned value. + */ + T mValue; + + /** + * Holds returned ranked error. + */ + RankedError mRankedError; +}; + +} // namespace aos::cm::launcher + +#endif