Configuration and DDS example/tools updates

- Update DDS example command UI and extract it from example.
 - Unify address handling via DDS properties for dynamic deployment.
 - Update DDS docs with the new approach.
 - Allow `--config-key` to be used to access common config in JSON.
 - Allow common channel properties to be specified for all sockets.
 - Update MQ examples and Tuto3 with new config options.
 - Add start scripts to MQ examples for easier use.
This commit is contained in:
Alexey Rybalchenko
2016-03-31 14:41:05 +02:00
parent 151d3b5de8
commit b9883d3b13
21 changed files with 1082 additions and 701 deletions

View File

@@ -0,0 +1,62 @@
################################################################################
# Copyright (C) 2014 GSI Helmholtzzentrum fuer Schwerionenforschung GmbH #
# #
# This software is distributed under the terms of the #
# GNU Lesser General Public Licence version 3 (LGPL) version 3, #
# copied verbatim in the file "LICENSE" #
################################################################################
Set(INCLUDE_DIRECTORIES
${CMAKE_SOURCE_DIR}/fairmq
${CMAKE_SOURCE_DIR}/fairmq/zeromq
${CMAKE_SOURCE_DIR}/fairmq/nanomsg
${CMAKE_SOURCE_DIR}/fairmq/devices
${CMAKE_SOURCE_DIR}/fairmq/tools
${CMAKE_SOURCE_DIR}/fairmq/options
${CMAKE_SOURCE_DIR}/fairmq/deployment
${CMAKE_CURRENT_BINARY_DIR}
)
Set(SYSTEM_INCLUDE_DIRECTORIES
${SYSTEM_INCLUDE_DIRECTORIES}
${Boost_INCLUDE_DIR}
${DDS_INCLUDE_DIR}
${ZMQ_INCLUDE_DIR}
)
Include_Directories(${INCLUDE_DIRECTORIES})
Include_Directories(SYSTEM ${SYSTEM_INCLUDE_DIRECTORIES})
Set(LINK_DIRECTORIES
${LINK_DIRECTORIES}
${Boost_LIBRARY_DIRS}
${DDS_LIBRARY_DIR}
)
Link_Directories(${LINK_DIRECTORIES})
Install(FILES FairMQDDSTools.h DESTINATION include)
Set(Exe_Names
${Exe_Names}
fairmq-dds-command-ui
)
Set(Exe_Source
${Exe_Source}
runDDSCommandUI.cxx
)
list(LENGTH Exe_Names _length)
math(EXPR _length ${_length}-1)
ForEach(_file RANGE 0 ${_length})
list(GET Exe_Names ${_file} _name)
list(GET Exe_Source ${_file} _src)
set(EXE_NAME ${_name})
set(SRCS ${_src})
set(DEPENDENCIES FairMQ dds_intercom_lib)
GENERATE_EXECUTABLE()
EndForEach(_file RANGE 0 ${_length})

View File

@@ -0,0 +1,197 @@
#ifndef FAIRMQDDSTOOLS_H_
#define FAIRMQDDSTOOLS_H_
#include "FairMQLogger.h"
#include "FairMQDevice.h"
#include "FairMQChannel.h"
#include <vector>
#include <map>
#include <string>
#include <exception>
#include <condition_variable>
#include <mutex>
#include <cstdlib>
#include "dds_intercom.h" // DDS
using namespace std;
using namespace dds::intercom_api;
// container to hold channel config and corresponding dds key values
struct DDSConfig
{
// container of sub channels, e.g. 'i' in data[i]
vector<FairMQChannel*> subChannels;
// dds values for the channel
CKeyValue::valuesMap_t ddsValues;
};
/// Handles channels addresses of the device with configuration from DDS
/// Addresses of binding channels are published via DDS using channels names as keys
/// Addresses of connecting channels are collected from DDS using channels names as keys
/// \param device Reference to FairMQDevice whose channels to handle
void HandleConfigViaDDS(FairMQDevice& device)
{
// container for binding channels
vector<FairMQChannel*> bindingChans;
// container for connecting channels
map<string, DDSConfig> connectingChans;
// fill the containers
for (auto& mi : device.fChannels)
{
if ((mi.second).at(0).GetMethod() == "bind")
{
for (auto& vi : mi.second)
{
bindingChans.push_back(&vi);
}
}
else if ((mi.second).at(0).GetMethod() == "connect")
{
// try some trickery with forwarding emplacing values into map
connectingChans.emplace(piecewise_construct, forward_as_tuple(mi.first), forward_as_tuple());
for (auto& vi : mi.second)
{
connectingChans.at(mi.first).subChannels.push_back(&vi);
}
}
else
{
LOG(ERROR) << "Cannot update address configuration. Socket method (bind/connect) not specified.";
return;
}
}
// Wait for the binding channels to bind
device.WaitForInitialValidation();
// DDS key value store
CKeyValue ddsKV;
// publish bound addresses via DDS at keys corresponding to the channel prefixes, e.g. 'data' in data[i]
for (const auto& i : bindingChans)
{
// LOG(INFO) << "Publishing " << i->GetChannelPrefix() << " address to DDS under '" << i->GetProperty() << "' property name.";
ddsKV.putValue(i->GetProperty(), i->GetAddress());
}
// receive connect addresses from DDS via keys corresponding to channel prefixes, e.g. 'data' in data[i]
if (connectingChans.size() > 0)
{
mutex keyMutex;
condition_variable keyCV;
LOG(DEBUG) << "Subscribing for DDS properties.";
ddsKV.subscribe([&] (const string& /*key*/, const string& /*value*/)
{
keyCV.notify_all();
});
// scope based locking
{
unique_lock<mutex> lock(keyMutex);
keyCV.wait_for(lock, chrono::milliseconds(1000), [&] ()
{
// receive new properties
for (auto& mi : connectingChans)
{
for (auto& vi : mi.second.subChannels)
{
// LOG(INFO) << "Waiting for " << vi->GetChannelPrefix() << " address from DDS.";
ddsKV.getValues(vi->GetProperty(), &(mi.second.ddsValues));
}
}
// update channels and remove them from unfinished container
for (auto mi = connectingChans.begin(); mi != connectingChans.end(); /* no increment */)
{
if (mi->second.subChannels.size() == mi->second.ddsValues.size())
{
auto it = mi->second.ddsValues.begin();
for (int i = 0; i < mi->second.subChannels.size(); ++i)
{
mi->second.subChannels.at(i)->UpdateAddress(it->second);
++it;
}
// when multiple subChannels are used, their order on every device should be the same, irregardless of arrival order from DDS.
device.SortChannel(mi->first);
connectingChans.erase(mi++);
}
else
{
++mi;
}
}
if (connectingChans.empty())
{
LOG(DEBUG) << "Successfully received all required DDS properties!";
}
return connectingChans.empty();
});
}
}
}
/// Controls device state via DDS custom commands interface
/// \param device Reference to FairMQDevice whose state to control
void runDDSStateHandler(FairMQDevice& device)
{
mutex mtx;
condition_variable stopCondition;
string id = device.GetProperty(FairMQDevice::Id, "");
string pid(to_string(getpid()));
try
{
const set<string> events = { "INIT_DEVICE", "INIT_TASK", "PAUSE", "RUN", "STOP", "RESET_TASK", "RESET_DEVICE" };
CCustomCmd ddsCustomCmd;
ddsCustomCmd.subscribe([&](const string& cmd, const string& cond, uint64_t senderId)
{
LOG(INFO) << "Received command: " << cmd;
if (cmd == "check-state")
{
ddsCustomCmd.send(id + ": " + device.GetCurrentStateName() + " (pid: " + pid + ")", to_string(senderId));
}
else if (events.find(cmd) != events.end())
{
ddsCustomCmd.send(id + ": attempting to " + cmd, to_string(senderId));
device.ChangeState(cmd);
}
else if (cmd == "END")
{
ddsCustomCmd.send(id + ": attempting to " + cmd, to_string(senderId));
device.ChangeState(cmd);
ddsCustomCmd.send(id + ": " + device.GetCurrentStateName(), to_string(senderId));
if (device.GetCurrentStateName() == "EXITING")
{
unique_lock<mutex> lock(mtx);
stopCondition.notify_one();
}
}
else
{
LOG(WARN) << "Unknown command: " << cmd;
LOG(WARN) << "Origin: " << senderId;
LOG(WARN) << "Destination: " << cond;
}
});
LOG(INFO) << "Listening for commands from DDS...";
unique_lock<mutex> lock(mtx);
stopCondition.wait(lock);
}
catch (exception& e)
{
cerr << "Error: " << e.what() << endl;
return;
}
}
#endif /* FAIRMQDDSTOOLS_H_ */

View File

@@ -0,0 +1,113 @@
#include "dds_intercom.h"
#include <termios.h> // raw mode console input
#include <iostream>
#include <exception>
#include <sstream>
#include <thread>
#include <atomic>
using namespace std;
using namespace dds::intercom_api;
void PrintControlsHelp()
{
cout << "Use keys to control the devices:" << endl;
cout << "[c] check states, [h] help, [p] pause, [r] run, [s] stop, [t] reset task, [d] reset device, [q] end, [j] init task, [i] init device" << endl;
cout << "To quit press Ctrl+C" << endl;
}
int main(int argc, char* argv[])
{
try
{
CCustomCmd ddsCustomCmd;
// subscribe to receive messages from DDS
ddsCustomCmd.subscribe([](const string& msg, const string& condition, uint64_t senderId)
{
cout << "Received: \"" << msg << "\"" << endl;
});
char c;
// setup reading from cin (enable raw mode)
struct termios t;
tcgetattr(STDIN_FILENO, &t); // get the current terminal I/O structure
t.c_lflag &= ~ICANON; // disable canonical input
tcsetattr(STDIN_FILENO, TCSANOW, &t); // apply the new settings
PrintControlsHelp();
while (cin >> c)
{
int result = 0; // result of the dds send
switch (c)
{
case 'c':
cout << " > checking state of the devices" << endl;
result = ddsCustomCmd.send("check-state", "");
break;
case 'i':
cout << " > init devices" << endl;
result = ddsCustomCmd.send("INIT_DEVICE", "");
break;
case 'j':
cout << " > init tasks" << endl;
result = ddsCustomCmd.send("INIT_TASK", "");
break;
case 'p':
cout << " > pause devices" << endl;
result = ddsCustomCmd.send("PAUSE", "");
break;
case 'r':
cout << " > run tasks" << endl;
result = ddsCustomCmd.send("RUN", "");
break;
case 's':
cout << " > stop devices" << endl;
result = ddsCustomCmd.send("STOP", "");
break;
case 't':
cout << " > reset tasks" << endl;
result = ddsCustomCmd.send("RESET_TASK", "");
break;
case 'd':
cout << " > reset devices" << endl;
result = ddsCustomCmd.send("RESET_DEVICE", "");
break;
case 'h':
cout << " > help" << endl;
PrintControlsHelp();
break;
case 'q':
cout << " > end" << endl;
result = ddsCustomCmd.send("END", "");
break;
default:
cout << "Invalid input: [" << c << "]" << endl;
PrintControlsHelp();
break;
}
if (result == 1)
{
cerr << "Error sending custom command" << endl;
}
}
// disable raw mode
tcgetattr(STDIN_FILENO, &t); // get the current terminal I/O structure
t.c_lflag |= ICANON; // re-enable canonical input
tcsetattr(STDIN_FILENO, TCSANOW, &t); // apply the new settings
}
catch (exception& e)
{
cerr << "Error: " << e.what() << endl;
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}