diff --git a/.travis.yml b/.travis.yml index 9a3e98a42..1b636cb65 100644 --- a/.travis.yml +++ b/.travis.yml @@ -38,12 +38,74 @@ before_install: - wget https://sonarcloud.io/static/cpp/build-wrapper-linux-x86.zip && unzip build-wrapper-linux-x86.zip before_script: - - docker run --name mysql_db -e MYSQL_ROOT_PASSWORD=root -d tangocs/mysql:9.2.2 --sql-mode="" - - CONTAINER=$(docker run --name tango_cs -e TANGO_HOST=127.0.0.1:10000 -e MYSQL_HOST=mysql_db:3306 -e MYSQL_USER=tango -e MYSQL_PASSWORD=tango -e MYSQL_DATABASE=tango --link mysql_db:mysql_db -d tangocs/tango-cs:latest) - - IPADDR=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' $CONTAINER) - - TANGO_HOST=${IPADDR}:10000 + - > + docker run + --rm + --name mysql_db + -e MYSQL_ROOT_PASSWORD=root + -e MYSQL_INITDB_SKIP_TZINFO=1 + -d + tangocs/mysql:9.2.2 + --sql-mode="" + --innodb=OFF + --default-storage-engine=MyISAM + - > + docker run + --rm + --name mysql_db2 + -e MYSQL_ROOT_PASSWORD=root + -e MYSQL_INITDB_SKIP_TZINFO=1 + -d + tangocs/mysql:9.2.2 + --sql-mode="" + --innodb=OFF + --default-storage-engine=MyISAM + - > + docker run + --rm + --name tango_cs + -e TANGO_HOST=127.0.0.1:10000 + -e MYSQL_HOST=mysql_db:3306 + -e MYSQL_USER=tango + -e MYSQL_PASSWORD=tango + -e MYSQL_DATABASE=tango + --link mysql_db:mysq_db + -d + tangocs/tango-cs:latest + - > + docker run + --rm + --name tango_cs2 + -e TANGO_HOST=127.0.0.1:10000 + -e MYSQL_HOST=mysql_db2:3306 + -e MYSQL_USER=tango + -e MYSQL_PASSWORD=tango + -e MYSQL_DATABASE=tango + --link mysql_db2:mysq_db2 + -d + tangocs/tango-cs:latest + - TANGO_HOST_IP=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' tango_cs) + - TANGO_HOST_IP2=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' tango_cs2) + - TANGO_HOST=${TANGO_HOST_IP}:10000 - docker build --build-arg APP_UID=$(id -u) --build-arg APP_GID=$(id -g) -t cpp_tango .travis/${OS_TYPE} - - docker run --name cpp_tango -e TANGO_HOST=${TANGO_HOST} -e BINTRAY_USER_NAME=tango-ci -e BINTRAY_API_KEY=${CI_BINTRAY_API_KEY} -e COVERALLS_REPO_TOKEN=${COVERALLS_REPO_TOKEN} --link tango_cs:tango_cs -v `pwd`:/home/tango/src -v `pwd`/idl:/home/tango/idl -v `pwd`/cppzmq:/home/tango/cppzmq -v `pwd`/coveralls-cmake:/home/tango/coveralls-cmake -v `pwd`/build-wrapper-linux-x86:/home/tango/build-wrapper-linux-x86 -dit cpp_tango + - > + docker run + --rm + --name cpp_tango + -e TANGO_HOST=${TANGO_HOST} + -e TANGO_HOST2=${TANGO_HOST_IP2}:10000 + -e BINTRAY_USER_NAME=tango-ci + -e BINTRAY_API_KEY=${CI_BINTRAY_API_KEY} + -e COVERALLS_REPO_TOKEN=${COVERALLS_REPO_TOKEN} + --link tango_cs + --link tango_cs2 + -v `pwd`:/home/tango/src + -v `pwd`/idl:/home/tango/idl + -v `pwd`/cppzmq:/home/tango/cppzmq + -v `pwd`/coveralls-cmake:/home/tango/coveralls-cmake + -v `pwd`/build-wrapper-linux-x86:/home/tango/build-wrapper-linux-x86 + -dit + cpp_tango - .travis/install_tango_idl.sh - (test ${STOCK_CPPZMQ} = "OFF" && .travis/install_cppzmq.sh) || true #work around gcov ignored by sonar @@ -67,8 +129,5 @@ deploy: after-script: - docker stop cpp_tango - - docker rm cpp_tango - - docker stop tango_cs - - docker rm tango_cs - - docker stop mysql_db - - docker rm mysql_db + - docker stop tango_cs tango_cs2 + - docker stop mysql_db mysql_db2 diff --git a/cpp_test_suite/environment/setup.sh b/cpp_test_suite/environment/setup.sh index 4357d52f9..88357625a 100755 --- a/cpp_test_suite/environment/setup.sh +++ b/cpp_test_suite/environment/setup.sh @@ -2,36 +2,58 @@ echo "Setup test environment" -#run mysql-tango docker -docker run --name mysql_db \ - -e MYSQL_ROOT_PASSWORD=root \ - -d tangocs/mysql:9.2.2 --sql-mode="" - -#run tango-cs docker -CONTAINER=$(docker run --name tango_cs \ - -p 10000:10000 \ - -e TANGO_HOST=127.0.0.1:10000 \ - -e MYSQL_HOST=mysql_db:3306 \ - -e MYSQL_USER=tango \ - -e MYSQL_PASSWORD=tango \ - -e MYSQL_DATABASE=tango \ - --link mysql_db:mysql_db \ - -d tangocs/tango-cs:latest) - -echo "CONTAINER=$CONTAINER" - -IPADDR=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' $CONTAINER) -#export TANGO_HOST -TANGO_HOST=$IPADDR:10000 +function run_mysql_container { + docker run \ + --rm \ + --name $1 \ + -e MYSQL_ROOT_PASSWORD=root \ + -e MYSQL_INITDB_SKIP_TZINFO=1 \ + -d \ + tangocs/mysql:9.2.2 \ + --sql-mode="" \ + --innodb=OFF \ + --default-storage-engine=MyISAM +} + +function run_tango_container { + docker run \ + --rm \ + --name $1 \ + -e TANGO_HOST=127.0.0.1:10000 \ + -e MYSQL_HOST=$2:3306 \ + -e MYSQL_USER=tango \ + -e MYSQL_PASSWORD=tango \ + -e MYSQL_DATABASE=tango \ + --link $2:$2 \ + -d \ + tangocs/tango-cs:latest +} + +run_mysql_container mysql_db +run_mysql_container mysql_db2 +run_tango_container tango_cs mysql_db +run_tango_container tango_cs2 mysql_db2 + +IPADDR=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' tango_cs) +IPADDR2=$(docker inspect -f '{{ .NetworkSettings.IPAddress }}' tango_cs2) + +export TANGO_HOST=$IPADDR:10000 +export TANGO_HOST2=$IPADDR2:10000 + echo "TANGO_HOST=$TANGO_HOST" -export TANGO_HOST echo "Create tango_host file" -echo "#!/bin/bash" > tango_host -echo "export TANGO_HOST=$TANGO_HOST" >> tango_host -echo "Wait till tango-cs is online" -#TODO notification? -sleep 30 +cat << EOF > tango_host +#!/bin/bash +export TANGO_HOST=$TANGO_HOST +export TANGO_HOST2=$TANGO_HOST2 +EOF -#ctest \ No newline at end of file +echo "Wait till tango-cs is online" +if hash tango_admin 2>/dev/null; then + tango_admin --ping-database 30 + TANGO_HOST=$TANGO_HOST2 tango_admin --ping-database 30 +else + sleep 30 +fi diff --git a/cpp_test_suite/environment/shutdown.sh b/cpp_test_suite/environment/shutdown.sh index 771151e52..46b771c4f 100755 --- a/cpp_test_suite/environment/shutdown.sh +++ b/cpp_test_suite/environment/shutdown.sh @@ -1,16 +1,10 @@ #!/usr/bin/env bash echo "Shutdown test environment" - -#kill tango-cs docker -docker stop tango_cs -docker rm tango_cs - -#kill mysql-tango docker -docker stop mysql_db -docker rm mysql_db +docker stop tango_cs tango_cs2 +docker stop mysql_db mysql_db2 echo "Revert tango_host file" echo "TANGO_HOST=$TANGO_HOST" echo "#!/bin/bash" > tango_host -echo "export TANGO_HOST=$TANGO_HOST" >> tango_host \ No newline at end of file +echo "export TANGO_HOST=$TANGO_HOST" >> tango_host diff --git a/cpp_test_suite/new_tests/cxx_group.cpp b/cpp_test_suite/new_tests/cxx_group.cpp index b4941de52..db9f3dd67 100644 --- a/cpp_test_suite/new_tests/cxx_group.cpp +++ b/cpp_test_suite/new_tests/cxx_group.cpp @@ -827,6 +827,39 @@ class GroupTestSuite: public CxxTest::TestSuite CxxTest::TangoPrinter::restore_unset("double_attr_value"); } + /* Verifies that a group can contain devices from a remote TANGO_HOST + * (a Tango instance different from client's default TANGO_HOST). + * An issue was reported when resolving names containing wildcards. */ + + void test_use_devices_from_remote_tango_host() + { + const std::string original_tango_host = std::getenv("TANGO_HOST"); + const std::string external_tango_host = std::getenv("TANGO_HOST2"); + + const bool force_update = true; + + TS_ASSERT_EQUALS(0, setenv("TANGO_HOST", external_tango_host.c_str(), force_update)); + ApiUtil::instance()->cleanup(); + + Group group("group"); + group.add("tango://" + original_tango_host + "/" + device1_name + "*"); + + GroupCmdReplyList command_results = group.command_inout("State"); + GroupCmdReply command_result = command_results[0]; + + TS_ASSERT(not command_results.has_failed()); + TS_ASSERT_EQUALS(1u, command_results.size()); + + TS_ASSERT(not command_result.has_failed()); + + DevState state; + TS_ASSERT(command_result >> state); + TS_ASSERT_EQUALS(ON, state); + + TS_ASSERT_EQUALS(0, setenv("TANGO_HOST", original_tango_host.c_str(), force_update)); + ApiUtil::instance()->cleanup(); + } + }; #undef cout #endif // GroupTestSuite_h diff --git a/cppapi/client/group.cpp b/cppapi/client/group.cpp index bad32753e..669ec82f3 100644 --- a/cppapi/client/group.cpp +++ b/cppapi/client/group.cpp @@ -32,6 +32,7 @@ //============================================================================= #include +#include //----------------------------------------------------------------------------- // LOCAL DEBUGGING MACRO @@ -48,63 +49,109 @@ bool GroupReply::exception_enabled = false; //============================================================================= // class GroupElementFactory //============================================================================= -GroupElements GroupElementFactory::instanciate (const std::string& p, int tmo_ms) +GroupElements GroupElementFactory::instanciate( + const std::string& name_or_pattern, + int timeout_millis) { -#if defined(_LOCAL_DEBUGGING) - cout << "GroupElementFactory::instanciate::pattern [" << p << "]" << std::endl; -#endif - //- a vector to store device names - std::vector dnl(0); - //- is

a device name or a device name pattern ? - if (p.find('*', 0) == std::string::npos) - { -#if defined(_LOCAL_DEBUGGING) - cout << "\t|- pattern is pure device name" << std::endl; -#endif - dnl.push_back(p); - } - else - { - int db_port = 0; - std::string db_host, new_pattern; - DbDatum dbd; - parse_name(p, db_host, db_port, new_pattern); - - if (db_host.size() == 0) { - Database db; - //- ask the db the list of device matching pattern p - dbd = db.get_device_exported(const_cast(p)); - } - else { - ApiUtil *au = ApiUtil::instance(); - int db_ind = au->get_db_ind(db_host,db_port); - dbd = ((au->get_db_vect())[db_ind])->get_device_exported(const_cast(new_pattern)); - } - - //- extract device names from dbd - dbd >> dnl; -#if defined(_LOCAL_DEBUGGING) - cout << "\t|- db.get_device_exported::found "; - cout << dnl.size() << " device names matching pattern" << std::endl; - for (unsigned int dn = 0; dn < dnl.size(); dn++) { - cout << "\t\t|- " << dnl[dn] << std::endl; + return create_group_elements(resolve_device_names(name_or_pattern), timeout_millis); +} + +GroupElements GroupElementFactory::create_group_elements(const DeviceNames& names, int timeout_millis) +{ + GroupElements group_elements; + group_elements.reserve(names.size()); + + for (DeviceNames::const_iterator name = names.begin(); name != names.end(); ++name) + { + group_elements.push_back(new GroupDeviceElement(*name, timeout_millis)); } -#endif - } - //- build the returned GroupElementList - GroupElements tel(0); - GroupDeviceElement* tde; - for (unsigned int i = 0; i < dnl.size(); i++) { - tde = new GroupDeviceElement(dnl[i],tmo_ms); - if (tde) { - tel.push_back(tde); + + return group_elements; +} + +DeviceNames GroupElementFactory::resolve_device_names(const std::string& name_or_pattern) +{ + const bool is_name = name_or_pattern.find('*') == std::string::npos; + + if (is_name) + { + return DeviceNames(1, name_or_pattern); } - } -#if defined(_LOCAL_DEBUGGING) - cout << "\t|- GroupElementList contains " << tel.size() << " elements" << std::endl; -#endif - //- return the list to the caller - return tel; + else + { + int db_port = 0; + std::string db_host = ""; + std::string name_or_pattern_without_host = ""; + parse_name(name_or_pattern, db_host, db_port, name_or_pattern_without_host); + + const bool is_local_device = db_host.empty(); + + if (is_local_device) + { + return resolve_local_device_names(name_or_pattern); + } + else + { + return resolve_remote_device_names( + db_port, + db_host, + name_or_pattern_without_host); + } + } +} + +DeviceNames GroupElementFactory::resolve_local_device_names(const std::string& name_or_pattern) +{ + std::string& name_or_patern_non_const = const_cast(name_or_pattern); + Database db; + DbDatum db_data = db.get_device_exported(name_or_patern_non_const); + + DeviceNames device_names; + device_names.reserve(db_data.size()); + db_data >> device_names; + return device_names; +} + +DeviceNames GroupElementFactory::resolve_remote_device_names( + int db_port, + std::string& db_host, + std::string& name_or_pattern_without_host) +{ + ApiUtil& api_util = *ApiUtil::instance(); + const int db_index = api_util.get_db_ind(db_host, db_port); + Database& db = *(api_util.get_db_vect()[db_index]); + DbDatum db_data = db.get_device_exported(name_or_pattern_without_host); + + DeviceNames device_names; + device_names.reserve(db_data.size()); + db_data >> device_names; + return add_host_and_port_to_device_names(device_names, db_host, db_port); +} + +DeviceNames GroupElementFactory::add_host_and_port_to_device_names( + const DeviceNames& names, + const std::string& host, + int port) +{ + DeviceNames full_names; + full_names.reserve(names.size()); + + for (DeviceNames::const_iterator name = names.begin(); name != names.end(); ++name) + { + full_names.push_back(build_full_device_name(host, port, *name)); + } + + return full_names; +} + +std::string GroupElementFactory::build_full_device_name( + const std::string& host, + int port, + const std::string& device_name) +{ + std::ostringstream buffer; + buffer << "tango://" << host << ":" << port << "/" << device_name; + return buffer.str(); } void GroupElementFactory::parse_name (const std::string& p, std::string &db_host,int &db_port,std::string &dev_pattern) @@ -854,7 +901,7 @@ void Group::remove_i (const std::string& p, bool fwd) #endif if (name_equals(p)) { #if defined(_LOCAL_DEBUGGING) - cout << "Group::remove_i::failed to remove " << _p << " (can't remove self)" << std::endl; + cout << "Group::remove_i::failed to remove " << p << " (can't remove self)" << std::endl; #endif return; } diff --git a/cppapi/client/group.h b/cppapi/client/group.h index 52b2434eb..58beb0a01 100644 --- a/cppapi/client/group.h +++ b/cppapi/client/group.h @@ -55,13 +55,11 @@ class GroupElement; //============================================================================= // Misc. Typedefs //============================================================================= -//- group content (individual devices and/or sub-groups) typedef std::vector GroupElements; -//- group content iterator typedef GroupElements::iterator GroupElementsIterator; -//----------------------------------------------------------------------------- -//- define what is a list of token (for name pattern management) typedef std::vector TokenList; +typedef std::vector DeviceNames; + //============================================================================= // class ExtRequestDesc : an asynch. request holder for groups //----------------------------------------------------------------------------- @@ -575,14 +573,24 @@ class GroupAttrReplyList : public std::vector //============================================================================= class GroupElementFactory { - friend class Group; - +public: //- instanciatethe GroupElement which name matches the specified pattern with the specified timeout //- timeout = -1 => do not change the timeout - static GroupElements instanciate (const std::string& p, int tmo = -1); + static GroupElements instanciate (const std::string& name_or_pattern, int timeout_millis = -1); +private: static void parse_name (const std::string& p, std::string &db_host, int &db_port, std::string &dev_pattern); + static GroupElements create_group_elements(const DeviceNames&, int timeout_millis); + static DeviceNames resolve_device_names(const std::string& name_or_pattern); + static DeviceNames resolve_local_device_names(const std::string& name_or_pattern); + static DeviceNames resolve_remote_device_names( + int db_port, + std::string& db_host, + std::string& name_or_pattern_without_host); + static DeviceNames add_host_and_port_to_device_names(const DeviceNames&, const std::string& db_host, int db_port); + static std::string build_full_device_name(const std::string& host, int port, const std::string& device_name); + //- forbidden methods GroupElementFactory(); ~GroupElementFactory();