First release

- Added build script and AMPL license file
- Fixed merge errors for the makefile
- Extended the makefile header
- Added initial AMQ message topics
- Tested remote build
- Removed AMPL license file
- Validated build script
- Accepting the metric definition message from the EMS
- Execution control status messages + solver type command line option
- Executing solver component
- Added instructions on use to the Solver Component source file
- Explicit close of subscriptions in destrcutors if network active.
- Correct handling of metric list messages and subscriptions
- Adding correct message and connection properties

Change-Id: If02caff12aacf8a2181c96eb6dca4a19dc23c118
This commit is contained in:
Geir Horn 2024-01-10 16:26:09 +01:00
parent ae588d8955
commit 44d093d2f8
9 changed files with 130 additions and 55 deletions

View File

@ -3,19 +3,15 @@
{ {
"name": "Linux", "name": "Linux",
"includePath": [ "includePath": [
"/usr/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13",
"/home/GHo/Documents/Code/CxxOpts/include", "/home/GHo/Documents/Code/CxxOpts/include",
"/home/GHo/Documents/Code/Theron++", "/home/GHo/Documents/Code/Theron++",
"/home/GHo/Documents/Code/Theron++/Utility",
"/home/GHo/Documents/Code/Theron++/Communication",
"/home/GHo/Documents/Code/Theron++/Communication/AMQ",
"/opt/AMPL/amplapi/include/ampl", "/opt/AMPL/amplapi/include/ampl",
"${workspaceFolder}/**", "${workspaceFolder}/**",
"/home/GHo/Documents/Code/Theron++" "/usr/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13"
], ],
"defines": [], "defines": [],
"cStandard": "c23", "cStandard": "c23",
"intelliSenseMode": "linux-gcc-x64", "intelliSenseMode": "${default}",
"compilerPath": "/usr/bin/g++", "compilerPath": "/usr/bin/g++",
"compilerArgs": [ "compilerArgs": [
"-std=c++23", "-std=c++23",
@ -28,12 +24,9 @@
"configurationProvider": "ms-vscode.makefile-tools", "configurationProvider": "ms-vscode.makefile-tools",
"browse": { "browse": {
"path": [ "path": [
"/usr/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13",
"/home/GHo/Documents/Code/Theron++", "/home/GHo/Documents/Code/Theron++",
"/home/GHo/Documents/Code/CxxOpts/include", "/home/GHo/Documents/Code/CxxOpts/include",
"/home/GHo/Documents/Code/Theron++/Utility", "/usr/lib/gcc/x86_64-redhat-linux/13/../../../../include/c++/13"
"/home/GHo/Documents/Code/Theron++/Communication",
"/home/GHo/Documents/Code/Theron++/Examples"
], ],
"limitSymbolsToIncludedHeaders": false "limitSymbolsToIncludedHeaders": false
} }

1
AMPLTest/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
*.ampl

View File

@ -68,7 +68,7 @@ void ExecutionControl::StopMessageHandler( const StopMessage & Command,
} }
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// Constructor // Constructor & destructor
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// //
// The constructor registers the stop message handler and sets up a publisher // The constructor registers the stop message handler and sets up a publisher
@ -91,4 +91,16 @@ ExecutionControl::ExecutionControl( const std::string & TheActorName )
} }
// The destructor simply closes the publisher if the network is still active
// when the actor closes.
ExecutionControl::~ExecutionControl( void )
{
if( HasNetwork() )
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::ClosePublisher,
std::string( StatusTopic )
), GetSessionLayerAddress() );
}
} // namespace NebulOuS } // namespace NebulOuS

View File

@ -167,7 +167,7 @@ public:
ExecutionControl( const std::string & TheActorName ); ExecutionControl( const std::string & TheActorName );
ExecutionControl() = delete; ExecutionControl() = delete;
virtual ~ExecutionControl() = default; virtual ~ExecutionControl();
}; };
} // namespace NebulOuS } // namespace NebulOuS

View File

@ -18,6 +18,7 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
#include <ranges> // Container ranges #include <ranges> // Container ranges
#include <algorithm> // Algorithms #include <algorithm> // Algorithms
#include "Utility/ConsolePrint.hpp" // For logging
#include "Communication/AMQ/AMQEndpoint.hpp" // For Topic subscriptions #include "Communication/AMQ/AMQEndpoint.hpp" // For Topic subscriptions
#include "MetricUpdater.hpp" #include "MetricUpdater.hpp"
@ -41,36 +42,34 @@ namespace NebulOuS
void MetricUpdater::AddMetricSubscription( const MetricTopic & TheMetrics, void MetricUpdater::AddMetricSubscription( const MetricTopic & TheMetrics,
const Address OptimiserController ) const Address OptimiserController )
{ {
if( TheMetrics.is_object() && if( TheMetrics.is_object() &&
TheMetrics.at( NebulOuS::MetricList ).is_object() ) TheMetrics.at( NebulOuS::MetricList ).is_array() )
{ {
JSON MetricList = TheMetrics.at( NebulOuS::MetricList ); for (auto & MetricRecord : TheMetrics.at( NebulOuS::MetricList ) )
for( const JSON MetricDefinition : MetricList.items() )
{
auto [ MetricRecord, NewMetric ] = MetricValues.try_emplace(
MetricDefinition.at( NebulOuS::MetricName ), JSON() );
if( NewMetric )
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
MetricRecord->first ),
Theron::AMQ::Network::GetAddress( Theron::Network::Layer::Session) );
}
}
else
{ {
std::source_location Location = std::source_location::current(); auto [ MetricRecordPointer, NewMetric ] = MetricValues.try_emplace(
std::ostringstream ErrorMessage; MetricRecord.at( NebulOuS::MetricName ), JSON() );
ErrorMessage << "[" << Location.file_name() << " at line " << Location.line() if( NewMetric )
<< "in function " << Location.function_name() <<"] " Send( Theron::AMQ::NetworkLayer::TopicSubscription(
<< "The message to define a new metric subscription is given as " Theron::AMQ::NetworkLayer::TopicSubscription::Action::Subscription,
<< std::endl << TheMetrics.dump(2) << std::endl std::string( MetricValueRootString ) + MetricRecordPointer->first ),
<< "this is not as expected!"; Theron::AMQ::Network::GetAddress( Theron::Network::Layer::Session) );
throw std::invalid_argument( ErrorMessage.str() );
} }
}
else
{
std::source_location Location = std::source_location::current();
std::ostringstream ErrorMessage;
ErrorMessage << "[" << Location.file_name() << " at line " << Location.line()
<< " in function " << Location.function_name() <<"] "
<< "The message to define a new metric subscription is given as "
<< std::endl << TheMetrics.dump(2) << std::endl
<< "this is not as expected!";
throw std::invalid_argument( ErrorMessage.str() );
}
} }
// The metric update value is received whenever any of subscribed forecasters // The metric update value is received whenever any of subscribed forecasters
@ -102,10 +101,17 @@ void MetricUpdater::AddMetricSubscription( const MetricTopic & TheMetrics,
void MetricUpdater::UpdateMetricValue( void MetricUpdater::UpdateMetricValue(
const MetricValueUpdate & TheMetricValue, const Address TheMetricTopic) const MetricValueUpdate & TheMetricValue, const Address TheMetricTopic)
{ {
Theron::ConsoleOutput Output;
Output << "Metric topic: " << TheMetricTopic.AsString() << std::endl;
Theron::AMQ::TopicName TheTopic Theron::AMQ::TopicName TheTopic
= TheMetricTopic.AsString().erase( 0, = TheMetricTopic.AsString().erase( 0,
NebulOuS::MetricValueRootString.size() ); NebulOuS::MetricValueRootString.size() );
Output << "The metric: " << TheTopic << " has new value "
<< TheMetricValue[ NebulOuS::ValueLabel ] << std::endl;
if( MetricValues.contains( TheTopic ) ) if( MetricValues.contains( TheTopic ) )
{ {
MetricValues.at( TheTopic ) = TheMetricValue[ NebulOuS::ValueLabel ]; MetricValues.at( TheTopic ) = TheMetricValue[ NebulOuS::ValueLabel ];

View File

@ -88,7 +88,7 @@ constexpr std::string_view TimePoint = "predictionTime";
// defined next. // defined next.
constexpr std::string_view MetricSubscriptions constexpr std::string_view MetricSubscriptions
= "eu.nebulouscloud.monitoring.metric_lists"; = "eu.nebulouscloud.monitoring.metric_list";
// The JSON message attribute for the list of metrics is another JSON object // The JSON message attribute for the list of metrics is another JSON object
// stored under the following key, see the Event type III defined in // stored under the following key, see the Event type III defined in
@ -211,6 +211,10 @@ private:
: JSONTopicMessage( Other ) : JSONTopicMessage( Other )
{} {}
// MetricTopic( const JSONTopicMessage & Other )
// : JSONTopicMessage( Other )
// {}
virtual ~MetricTopic() = default; virtual ~MetricTopic() = default;
}; };

View File

@ -54,6 +54,17 @@ be extended with the AMPL execution file path, e.g.,
export PATH=$PATH:/opt/AMPL export PATH=$PATH:/opt/AMPL
The parameters to the application are used as described above, and typically the
endpoint is set to some unique identifier of the application for which this
solver is used, e.g.,
./SolverComponent --AMPLDir /opt/AMPL \
--Endpoint f81ee-b42a8-a13d56-e28ec9-2f5578 --ModelDir AMPLTest/
Debugging after a coredump
coredumpctl debug SolverComponent
Author and Copyright: Geir Horn, University of Oslo Author and Copyright: Geir Horn, University of Oslo
Contact: Geir.Horn@mn.uio.no Contact: Geir.Horn@mn.uio.no
License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/) License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
@ -66,6 +77,7 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
#include <sstream> // To format error messages #include <sstream> // To format error messages
#include <stdexcept> // standard exceptions #include <stdexcept> // standard exceptions
#include <filesystem> // Access to the file system #include <filesystem> // Access to the file system
#include <map> // For extended AMQ properties
// Theron++ headers // Theron++ headers
@ -78,7 +90,9 @@ License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
// AMQ protocol related headers // AMQ protocol related headers
#include "proton/symbol.hpp" // AMQ symbols
#include "proton/connection_options.hpp" // Options for the Broker #include "proton/connection_options.hpp" // Options for the Broker
#include "proton/message.hpp" // AMQ messages definitions
#include "Communication/AMQ/AMQMessage.hpp" // The AMQP messages #include "Communication/AMQ/AMQMessage.hpp" // The AMQP messages
#include "Communication/AMQ/AMQEndpoint.hpp" // The AMP endpoint #include "Communication/AMQ/AMQEndpoint.hpp" // The AMP endpoint
#include "Communication/AMQ/AMQjson.hpp" // Transparent JSON-AMQP #include "Communication/AMQ/AMQjson.hpp" // Transparent JSON-AMQP
@ -127,6 +141,8 @@ int main( int NumberOfCLIOptions, char ** CLIOptionStrings )
cxxopts::value<unsigned int>()->default_value("5672") ) cxxopts::value<unsigned int>()->default_value("5672") )
("S,Solver", "Solver to use, devault Couenne", ("S,Solver", "Solver to use, devault Couenne",
cxxopts::value<std::string>()->default_value("couenne") ) cxxopts::value<std::string>()->default_value("couenne") )
("T,Tenant", "Tenant identifier for messages",
cxxopts::value<std::string>()->default_value("TheTenant"))
("U,User", "The user name used for the AMQ Broker connection", ("U,User", "The user name used for the AMQ Broker connection",
cxxopts::value<std::string>()->default_value("admin") ) cxxopts::value<std::string>()->default_value("admin") )
("Pw,Password", "The password for the AMQ Broker connection", ("Pw,Password", "The password for the AMQ Broker connection",
@ -180,29 +196,51 @@ int main( int NumberOfCLIOptions, char ** CLIOptionStrings )
// The AMQ communication is managed by the standard communication actors of // The AMQ communication is managed by the standard communication actors of
// the Theron++ Actor framewokr. Thus, it is just a matter of starting the // the Theron++ Actor framewokr. Thus, it is just a matter of starting the
// endpoint actors with the given command line parameters. // endpoint actors with the given command line parameters.
//
// The network endpoint takes the endpoint name as the first argument, then // There are certain properties defined for the NebulOuS communication
// the URL for the broker and the port number. The user name and the password // protocol where the endpoint name is taken as the application identifier
// are defined in the AMQ Qpid Proton connection options, and the values are // and the tenant identifier and the protocol used. The same information
// therefore set for the connection options. // has to be given as different maps for the connection options and for
// the message properties.
std::map< proton::symbol, proton::value > NebulOuSProperties{
{"x-tenant-id", CLIValues["Tenant"].as< std::string >() },
{"x-application-id", CLIValues["Endpoint"].as< std::string >()},
{"x-protocol", "AMQP"}
};
proton::message::property_map MessageProperties{
{"x-tenant-id", CLIValues["Tenant"].as< std::string >() },
{"x-application-id", CLIValues["Endpoint"].as< std::string >()},
{"x-protocol", "AMQP"}
};
// The user name and the password are defined in the AMQ Qpid Proton
// connection options, and the values are therefore set for the connection
// options.
proton::connection_options AMQOptions; proton::connection_options AMQOptions;
AMQOptions.user( CLIValues["User"].as< std::string >() ); AMQOptions.user( CLIValues["User"].as< std::string >() );
AMQOptions.password( CLIValues["Password"].as< std::string >() ); AMQOptions.password( CLIValues["Password"].as< std::string >() );
AMQOptions.properties( NebulOuSProperties );
// Then the network endpoint cna be constructed using the default names for // The network endpoint takes the endpoint name as the first argument, then
// the various network endpoint servers in order to pass the defined // the URL for the broker and the port number. Then the network endpoint can
// connection options. // be constructed using the default names for the Session Layer and the
// Presentation layer servers, but calling the endpoint for "Solver" to make
// it more visible at the AMQ broker listing of subscribers. The endpoint
// will be a unique application identifier. The server names are followed
// by the defined connection options and the message options.
Theron::AMQ::NetworkEndpoint AMQNetWork( Theron::AMQ::NetworkEndpoint AMQNetWork(
CLIValues["Endpoint"].as< std::string >(), CLIValues["Endpoint"].as< std::string >(),
CLIValues["Broker"].as< std::string >(), CLIValues["Broker"].as< std::string >(),
CLIValues["Port"].as< unsigned int >(), CLIValues["Port"].as< unsigned int >(),
Theron::AMQ::Network::NetworkLayerLabel, "Solver",
Theron::AMQ::Network::SessionLayerLabel, Theron::AMQ::Network::SessionLayerLabel,
Theron::AMQ::Network::PresentationLayerLabel, Theron::AMQ::Network::PresentationLayerLabel,
AMQOptions AMQOptions, MessageProperties
); );
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------

View File

@ -97,11 +97,12 @@ class SolverManager
{ {
// There is a topic name used to publish solutions found by the solvers. This // There is a topic name used to publish solutions found by the solvers. This
// topic is given to the constructor and kept as a constant during the class // topic is given to the constructor and kept as a constant during the class
// execution. // execution. The same goes for the topic on which application execution
// contexts will arrive for processing.
private: private:
const Theron::AMQ::TopicName SolutionReceiver; const Theron::AMQ::TopicName SolutionReceiver, ContextTopic;
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Solver management // Solver management
@ -259,6 +260,7 @@ public:
NetworkingActor( Actor::GetAddress().AsString() ), NetworkingActor( Actor::GetAddress().AsString() ),
ExecutionControl( Actor::GetAddress().AsString() ), ExecutionControl( Actor::GetAddress().AsString() ),
SolutionReceiver( SolutionTopic ), SolutionReceiver( SolutionTopic ),
ContextTopic( ContextPublisherTopic ),
SolverPool(), ActiveSolvers(), PassiveSolvers(), SolverPool(), ActiveSolvers(), PassiveSolvers(),
Contexts(), ContextExecutionQueue() Contexts(), ContextExecutionQueue()
{ {
@ -322,6 +324,25 @@ public:
} }
} }
// The destructor closes all the open topics if the network is still open
// when the destructor is invoked.
virtual ~SolverManager( void )
{
if( HasNetwork() )
{
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::ClosePublisher,
SolutionReceiver
), GetSessionLayerAddress() );
Send( Theron::AMQ::NetworkLayer::TopicSubscription(
Theron::AMQ::NetworkLayer::TopicSubscription::Action::CloseSubscription,
ContextTopic
), GetSessionLayerAddress() );
}
}
}; };
} // namespace NebulOuS } // namespace NebulOuS

View File

@ -132,7 +132,7 @@ SOLVER_OBJECTS = $(addprefix $(OBJECTS_DIR)/, $(SOLVER_SOURCE:.cpp=.o) )
# component's objective file, they can be built by a general rule # component's objective file, they can be built by a general rule
$(OBJECTS_DIR)/%.o : %.cpp $(OBJECTS_DIR)/%.o : %.cpp
$(CXX) $(CXXFLAGS) -c $< -o $@ $(INCLUDE_DIRECTORIES) $(CC) $(CXXFLAGS) -c $< -o $@ $(INCLUDE_DIRECTORIES)
#------------------------------------------------------------------------------ #------------------------------------------------------------------------------
# Solver component # Solver component
@ -143,7 +143,7 @@ $(OBJECTS_DIR)/%.o : %.cpp
# the object files or the solver actors. # the object files or the solver actors.
SolverComponent: $(SOLVER_OBJECTS) $(THERON)/Theron++.a SolverComponent: $(SOLVER_OBJECTS) $(THERON)/Theron++.a
$(CXX) -o SolverComponent $(CXXFLAGS) $(SOLVER_OBJECTS) $(LDFLAGS) $(CC) -o SolverComponent $(CXXFLAGS) $(SOLVER_OBJECTS) $(LDFLAGS)
# There is also a standard target to clean the automatically generated build # There is also a standard target to clean the automatically generated build
# files # files