-
Notifications
You must be signed in to change notification settings - Fork 0
/
SolverComponent.cpp
375 lines (297 loc) · 15 KB
/
SolverComponent.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
/*==============================================================================
Solver Component
This is the main file for the Solver Component executable including the parsing
of command line arguments and the AMQ network interface. It first starts the
AMQ interface actors of the Network Endpoint, then creates the actors of the
solver component: The Metric Updater and the Solution Manager, which in turn
will start the solver actor(s). All actors are executing on proper operating
system threads, and they are scheduled for execution whenever they have a
pending message.
The command line arguments that can be givne to the Solver Component are
-A or --AMPLDir <installation directory> for the AMPL model interpreter
-B or --broker <URL> for the location of the AMQ broker
-E or --endpoint <name> The endpoint name = application identifier
-M ir --ModelDir <directory> for model and data files
-N or --name The AMQ identity of the solver (see below)
-P or --port <n> the port to use on the AMQ broker URL
-S or --Solver <label> The back-end solver used by AMPL
-U or --user <user> the user to authenticate for the AMQ broker
-Pw or --password <password> the AMQ broker password for the user
-? or --Help prints a help message for the options
Default values:
-A taken from the standard AMPL environment variables if omitted
-B localhost
-E <no default - must be given>
-M <temporary directory created by the OS>
-N "NebulOuS::Solver"
-P 5672
-S couenne
-U admin
-Pw admin
A note on the mandatory endpoint name defining the extension used for the
solver component when connecting to the AMQ server. Typically the connection
will be established as "name@endpoint" and so if there are several
solver components running, the endpoint is the only way for the AMQ solvers to
distinguish the different solver component subscriptions.
Notes on use:
The path to the AMPL API shared libray must be in the LIB path environment
variable. For instance, the installation of AMPL on the author's machine is in
/opt/AMPL and so the first thing to ensure is that the path to the API library
directory is added to the link library path, e.g.,
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/opt/AMPL/amplapi/lib
The AMPL directory also needs to be in the path variable, and the path must
be extended with the AMPL execution file path, e.g.,
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 \
--ModelDir AMPLTest/ --Endpoint f81ee-b42a8-a13d56-e28ec9-2f5578
Debugging after a coredump
coredumpctl debug SolverComponent
Author and Copyright: Geir Horn, University of Oslo
Contact: [email protected]
License: MPL2.0 (https://www.mozilla.org/en-US/MPL/2.0/)
==============================================================================*/
// Standard headers
#include <string> // For standard strings
#include <source_location> // Making informative error messages
#include <sstream> // To format error messages
#include <stdexcept> // standard exceptions
#include <filesystem> // Access to the file system
#include <map> // For extended AMQ properties
// Theron++ headers
#include "Actor.hpp"
#include "Utility/StandardFallbackHandler.hpp"
#include "Utility/ConsolePrint.hpp"
#include "Communication/PolymorphicMessage.hpp"
#include "Communication/NetworkingActor.hpp"
// AMQ protocol related headers
#include "proton/symbol.hpp" // AMQ symbols
#include "proton/connection_options.hpp" // Options for the Broker
#include "proton/message.hpp" // AMQ messages definitions
#include "proton/source_options.hpp" // App ID filters
#include "proton/source.hpp" // The filter map
#include "proton/types.hpp" // Type definitions
#include "Communication/AMQ/AMQMessage.hpp" // The AMQP messages
#include "Communication/AMQ/AMQEndpoint.hpp" // The AMP endpoint
#include "Communication/AMQ/AMQjson.hpp" // Transparent JSON-AMQP
// The cxxopts command line options parser that can be cloned from
// https://github.com/jarro2783/cxxopts
#include "cxxopts.hpp"
// AMPL Application Programmer Interface (API)
#include "ampl/ampl.h"
// NegulOuS related headers
#include "MetricUpdater.hpp"
#include "SolverManager.hpp"
#include "AMPLSolver.hpp"
/*==============================================================================
Main
==============================================================================*/
int main( int NumberOfCLIOptions, char ** CLIOptionStrings )
{
// --------------------------------------------------------------------------
// Defining and parsing the Command Line Interface (CLI) options
// --------------------------------------------------------------------------
cxxopts::Options CLIOptions("./SolverComponent",
"The NebulOuS Solver component");
CLIOptions.add_options()
("A,AMPLDir", "The AMPL installation path",
cxxopts::value<std::string>()->default_value("") )
("B,Broker", "The URL of the AMQ broker",
cxxopts::value<std::string>()->default_value("localhost") )
("E,Endpoint", "The endpoint name", cxxopts::value<std::string>() )
("M,ModelDir", "Directory to store the model and its data",
cxxopts::value<std::string>()->default_value("") )
("N,Name", "The name of the Solver Component",
cxxopts::value<std::string>()->default_value("NebulOuS::Solver") )
("P,Port", "TCP port on AMQ Broker",
cxxopts::value<unsigned int>()->default_value("5672") )
("S,Solver", "Solver to use, default Couenne",
cxxopts::value<std::string>()->default_value("couenne") )
("U,User", "The user name used for the AMQ Broker connection",
cxxopts::value<std::string>()->default_value("admin") )
("Pw,Password", "The password for the AMQ Broker connection",
cxxopts::value<std::string>()->default_value("admin") )
("h,help", "Print help information");
CLIOptions.allow_unrecognised_options();
auto CLIValues = CLIOptions.parse( NumberOfCLIOptions, CLIOptionStrings );
if( CLIValues.count("help") )
{
std::cout << CLIOptions.help() << std::endl;
exit( EXIT_SUCCESS );
}
// --------------------------------------------------------------------------
// Validating directories
// --------------------------------------------------------------------------
//
// The directories are given as strings and they must be validated to see if
// the provided values correspond to an existing directory in the case of the
// AMPL directory. The model directory will be created if it is not an empty
// string, for which a temparary directory will be created.
std::filesystem::path TheAMPLDirectory( CLIValues["AMPLDir"].as<std::string>() );
if( !std::filesystem::exists( TheAMPLDirectory ) )
{
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 AMPL installation driectory is given as ["
<< CLIValues["AMPLDir"].as<std::string>()
<< "] but this directory does not ezist!";
throw std::invalid_argument( ErrorMessage.str() );
}
std::filesystem::path ModelDirectory( CLIValues["ModelDir"].as<std::string>() );
if( ModelDirectory.empty() )
ModelDirectory = std::filesystem::temp_directory_path();
else if ( !std::filesystem::exists( ModelDirectory ) )
{
if( !std::filesystem::create_directory( ModelDirectory ) )
{
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 requested model directory " << ModelDirectory
<< " does not exist and cannot be created";
throw std::runtime_error( ErrorMessage.str() );
}
}
// --------------------------------------------------------------------------
// AMQ options
// --------------------------------------------------------------------------
//
// In order to be general and flexible, the various AMQ options must be
// provided as a user specified class to allow the user full fexibility in
// deciding on the connection properties. This class should keep the user
// name, the password, and the application identifier, which is identical
// to the endpoint.
class AMQOptions
: public Theron::AMQ::NetworkLayer::AMQProperties
{
private:
const std::string User, Password, ApplicationID;
protected:
// The connection options just sets the user and the password to be used
// when the first connection is established with the AMQ broker
virtual proton::connection_options ConnectionOptions(void) const override
{
proton::connection_options Options( AMQProperties::ConnectionOptions() );
Options.user( User );
Options.password( Password );
return Options;
};
// Setting the application filter is slightly more complicated as it
// involves setting the filter map for the sender. However, this is not
// well documented and the current implmenentation is based on the
// example for an earlier Proton version (0.32.0) and the example at
// https://qpid.apache.org/releases/qpid-proton-0.32.0/proton/cpp/examples/selected_recv.cpp.html
virtual proton::receiver_options ReceiverOptions( void ) const override
{
proton::source::filter_map TheFilter;
proton::source_options TheSourceOptions;
proton::symbol FilterKey("selector");
proton::value FilterValue;
proton::codec::encoder EncodedFilter( FilterValue );
proton::receiver_options TheOptions( AMQProperties::ReceiverOptions() );
std::ostringstream SelectorString;
SelectorString << "application = '" << ApplicationID << "'";
EncodedFilter << proton::codec::start::described()
<< proton::symbol("apache.org:selector-filter:string")
<< SelectorString.str()
<< proton::codec::finish();
TheFilter.put( FilterKey, FilterValue );
TheSourceOptions.filters( TheFilter );
TheOptions.source( TheSourceOptions );
return TheOptions;
}
// The application identifier must also be provided in every message to
// allow other receivers to filter on this. First will the default
// properties from the base class be set before the new application
// identifier property will be added.
virtual std::map<std::string, proton::scalar> MessageProperties(
const proton::message::property_map & CurrentProperties
= proton::message::property_map() ) const override
{
std::map<std::string, proton::scalar>
TheProperties( AMQProperties::MessageProperties( CurrentProperties ) );
TheProperties["application"] = ApplicationID;
return TheProperties;
}
public:
AMQOptions( const std::string & TheUser, const std::string & ThePassword,
const std::string & TheAppID )
: User( TheUser ), Password( ThePassword ), ApplicationID( TheAppID )
{}
AMQOptions( const AMQOptions & Other )
: User( Other.User ), Password( Other.Password ),
ApplicationID( Other.ApplicationID )
{}
virtual ~AMQOptions() = default;
};
// --------------------------------------------------------------------------
// AMQ communication
// --------------------------------------------------------------------------
//
// The AMQ communication is managed by the standard communication actors of
// the Theron++ Actor framework. Thus, it is just a matter of starting the
// endpoint actors with the given command line parameters.
//
// The network endpoint takes the endpoint name as the first argument, then
// the URL for the broker and the port number. Then the network endpoint can
// 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 AMQ options.
Theron::AMQ::NetworkEndpoint AMQNetWork(
CLIValues["Endpoint"].as< std::string >(),
CLIValues["Broker"].as< std::string >(),
CLIValues["Port"].as< unsigned int >(),
CLIValues["Name"].as< std::string >(),
Theron::AMQ::Network::SessionLayerLabel,
Theron::AMQ::Network::PresentationLayerLabel,
std::make_shared< AMQOptions >(
CLIValues["User"].as< std::string >(),
CLIValues["Password"].as< std::string >(),
CLIValues["Endpoint"].as< std::string >()
)
);
// --------------------------------------------------------------------------
// Solver component actors
// --------------------------------------------------------------------------
//
// The solver managager must be started first since its address should be
// a parameter to the constructor of the Metric Updater so the latter actor
// knows where to send application execution contexts whenever a new solution
// is requested by the SLO Violation Detector through the Optimzer Controller.
// Then follows the number of solvers to use in the solver pool and the root
// name of the solvers. This root name string will be extended with _n where n
// where n is a sequence number from 1.As all solvers are of the same type
// given by the template parameter (here AMPLSolver), they are assumed to need
// the same set of constructor arguments and the constructor arguments follow
// the root solver name.
NebulOuS::SolverManager< NebulOuS::AMPLSolver >
WorkloadMabager( CLIValues["Name"].as<std::string>(),
NebulOuS::Solver::Solution::AMQTopic,
NebulOuS::Solver::ApplicationExecutionContext::AMQTopic,
1, "AMPLSolver",
ampl::Environment( TheAMPLDirectory.native() ), ModelDirectory,
CLIValues["Solver"].as<std::string>() );
NebulOuS::MetricUpdater
ContextMabager( "MetricUpdater", WorkloadMabager.GetAddress() );
// --------------------------------------------------------------------------
// Termination management
// --------------------------------------------------------------------------
//
// The critical part is to wait for the global shut down message from the
// Optimiser controller. That message will trigger the network to shut down
// and the Solver Component may terminate when the actor system has finished.
// Thus, the actors can still be running for some time after the global shut
// down message has been received, and it is therefore necessary to also wait
// for the actors to terminate.
NebulOuS::ExecutionControl::WaitForTermination();
Theron::Actor::WaitForGlobalTermination();
return EXIT_SUCCESS;
}