From 72231250ed81e10d66bfe70701e64fa5fe50f712 Mon Sep 17 00:00:00 2001 From: Jon Bratseth Date: Wed, 15 Jun 2016 23:09:44 +0200 Subject: Publish --- configutil/src/.gitignore | 4 + configutil/src/apps/configstatus/.gitignore | 4 + configutil/src/apps/configstatus/CMakeLists.txt | 9 + configutil/src/apps/configstatus/main.cpp | 104 ++++++ configutil/src/apps/modelinspect/.gitignore | 4 + configutil/src/apps/modelinspect/CMakeLists.txt | 9 + configutil/src/apps/modelinspect/main.cpp | 131 ++++++++ configutil/src/lib/.gitignore | 2 + configutil/src/lib/CMakeLists.txt | 9 + configutil/src/lib/configstatus.cpp | 237 +++++++++++++ configutil/src/lib/configstatus.h | 31 ++ configutil/src/lib/modelinspect.cpp | 366 +++++++++++++++++++++ configutil/src/lib/modelinspect.h | 52 +++ configutil/src/lib/tags.cpp | 39 +++ configutil/src/lib/tags.h | 12 + configutil/src/testlist.txt | 3 + configutil/src/tests/config_status/.gitignore | 4 + configutil/src/tests/config_status/CMakeLists.txt | 8 + .../src/tests/config_status/config_status_test.cpp | 109 ++++++ configutil/src/tests/model_inspect/.gitignore | 4 + configutil/src/tests/model_inspect/CMakeLists.txt | 8 + configutil/src/tests/model_inspect/FILES | 1 + configutil/src/tests/model_inspect/model.cfg | 159 +++++++++ .../src/tests/model_inspect/model_inspect_test.cpp | 267 +++++++++++++++ configutil/src/tests/tags/.gitignore | 4 + configutil/src/tests/tags/CMakeLists.txt | 8 + configutil/src/tests/tags/FILES | 1 + configutil/src/tests/tags/tags_test.cpp | 20 ++ 28 files changed, 1609 insertions(+) create mode 100644 configutil/src/.gitignore create mode 100644 configutil/src/apps/configstatus/.gitignore create mode 100644 configutil/src/apps/configstatus/CMakeLists.txt create mode 100644 configutil/src/apps/configstatus/main.cpp create mode 100644 configutil/src/apps/modelinspect/.gitignore create mode 100644 configutil/src/apps/modelinspect/CMakeLists.txt create mode 100644 configutil/src/apps/modelinspect/main.cpp create mode 100644 configutil/src/lib/.gitignore create mode 100644 configutil/src/lib/CMakeLists.txt create mode 100644 configutil/src/lib/configstatus.cpp create mode 100644 configutil/src/lib/configstatus.h create mode 100644 configutil/src/lib/modelinspect.cpp create mode 100644 configutil/src/lib/modelinspect.h create mode 100644 configutil/src/lib/tags.cpp create mode 100644 configutil/src/lib/tags.h create mode 100644 configutil/src/testlist.txt create mode 100644 configutil/src/tests/config_status/.gitignore create mode 100644 configutil/src/tests/config_status/CMakeLists.txt create mode 100644 configutil/src/tests/config_status/config_status_test.cpp create mode 100644 configutil/src/tests/model_inspect/.gitignore create mode 100644 configutil/src/tests/model_inspect/CMakeLists.txt create mode 100644 configutil/src/tests/model_inspect/FILES create mode 100644 configutil/src/tests/model_inspect/model.cfg create mode 100644 configutil/src/tests/model_inspect/model_inspect_test.cpp create mode 100644 configutil/src/tests/tags/.gitignore create mode 100644 configutil/src/tests/tags/CMakeLists.txt create mode 100644 configutil/src/tests/tags/FILES create mode 100644 configutil/src/tests/tags/tags_test.cpp (limited to 'configutil/src') diff --git a/configutil/src/.gitignore b/configutil/src/.gitignore new file mode 100644 index 00000000000..f7e40c87b26 --- /dev/null +++ b/configutil/src/.gitignore @@ -0,0 +1,4 @@ +/Makefile.ini +/config_command.sh +/configutil.mak +/project.dsw diff --git a/configutil/src/apps/configstatus/.gitignore b/configutil/src/apps/configstatus/.gitignore new file mode 100644 index 00000000000..5d04dfe43bd --- /dev/null +++ b/configutil/src/apps/configstatus/.gitignore @@ -0,0 +1,4 @@ +/.depend +/Makefile +/vespa-config-status +vespa-config-status-bin diff --git a/configutil/src/apps/configstatus/CMakeLists.txt b/configutil/src/apps/configstatus/CMakeLists.txt new file mode 100644 index 00000000000..58bea4e0d70 --- /dev/null +++ b/configutil/src/apps/configstatus/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(configutil_vespa-config-status_app + SOURCES + main.cpp + OUTPUT_NAME vespa-config-status-bin + INSTALL bin + DEPENDS + configutil_util +) diff --git a/configutil/src/apps/configstatus/main.cpp b/configutil/src/apps/configstatus/main.cpp new file mode 100644 index 00000000000..b3fb611ffa4 --- /dev/null +++ b/configutil/src/apps/configstatus/main.cpp @@ -0,0 +1,104 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include +#include +#include +LOG_SETUP("vespa-config-status"); +#include +#include + +class Application : public FastOS_Application +{ + ConfigStatus::Flags _flags; + vespalib::string _cfgId; + vespalib::string _specString; + int parseOpts(); + vespalib::string getSources(); +public: + void usage(void); + int Main(void); + + Application() : _flags(), _cfgId("admin/model"), _specString("") {} +}; + +int +Application::parseOpts() +{ + char c = '?'; + const char *optArg = NULL; + int optInd = 0; + while ((c = GetOpt("c:s:vC:", optArg, optInd)) != -1) { + switch (c) { + case 'v': + _flags.verbose = true; + break; + case 'C': + _cfgId = optArg; + break; + case 'c': + _specString = optArg; + break; + case 'h': + usage(); + exit(0); + default: + usage(); + exit(1); + } + } + if (_specString.empty()) { + _specString = getSources(); + } + return optInd; +} + + +void +Application::usage(void) +{ + std::cerr << + "vespa-config-status version 1.0" << std::endl << + "Usage: " << _argv[0] << " [options] " << std::endl << + "options: [-v] for verbose" << std::endl << + " [-c host] or [-c host:port] to specify config server" << std::endl << + std::endl; +} + +int +Application::Main(void) +{ + parseOpts(); + + config::ServerSpec spec(_specString); + config::ConfigUri uri = config::ConfigUri::createFromSpec(_cfgId, spec); + ConfigStatus status(_flags, uri); + + return status.action(); +} + +vespalib::string +Application::getSources(void) +{ + vespalib::string cmd = vespa::Defaults::vespaHome(); + cmd.append("libexec/vespa/vespa-config.pl -configsources"); + FILE* fp = popen(cmd.c_str(), "r"); + if (fp == 0) { + std::cerr << "Failed to run " << cmd << " (" + << errno << "): " << strerror(errno) << "\n"; + return ""; + } + vespalib::asciistream specs; + char data[500]; + while (fgets(data, 500, fp) != 0) { + specs << &data[0] << "\n"; + } + pclose(fp); + return specs.str(); +} + +int +main(int argc, char **argv) +{ + Application app; + return app.Entry(argc, argv); +} diff --git a/configutil/src/apps/modelinspect/.gitignore b/configutil/src/apps/modelinspect/.gitignore new file mode 100644 index 00000000000..89d2d6112b6 --- /dev/null +++ b/configutil/src/apps/modelinspect/.gitignore @@ -0,0 +1,4 @@ +/.depend +/Makefile +/vespa-model-inspect +vespa-model-inspect-bin diff --git a/configutil/src/apps/modelinspect/CMakeLists.txt b/configutil/src/apps/modelinspect/CMakeLists.txt new file mode 100644 index 00000000000..be65aa67701 --- /dev/null +++ b/configutil/src/apps/modelinspect/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(configutil_vespa-model-inspect_app + SOURCES + main.cpp + OUTPUT_NAME vespa-model-inspect-bin + INSTALL bin + DEPENDS + configutil_util +) diff --git a/configutil/src/apps/modelinspect/main.cpp b/configutil/src/apps/modelinspect/main.cpp new file mode 100644 index 00000000000..f5a1e28e218 --- /dev/null +++ b/configutil/src/apps/modelinspect/main.cpp @@ -0,0 +1,131 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include +#include +#include +LOG_SETUP("vespa-model-inspect"); +#include +#include +#include +#include + +class Application : public FastOS_Application +{ + ModelInspect::Flags _flags; + vespalib::string _cfgId; + vespalib::string _specString; + int parseOpts(); + vespalib::string getSources(); +public: + void usage(void); + int Main(void); + + Application() : _flags(), _cfgId("admin/model"), _specString("") {} +}; + +int +Application::parseOpts() +{ + char c = '?'; + const char *optArg = NULL; + int optInd = 0; + while ((c = GetOpt("hvut:c:C:", optArg, optInd)) != -1) { + switch (c) { + case 'v': + _flags.verbose = true; + break; + case 'u': + _flags.makeuri = true; + break; + case 't': + _flags.tagFilter.push_back(optArg); + _flags.tagfilt = true; + break; + case 'C': + _cfgId = optArg; + break; + case 'c': + _specString = optArg; + break; + case 'h': + return _argc; + default: + usage(); + exit(1); + } + } + if (_specString.empty()) { + _specString = getSources(); + } + return optInd; +} + +vespalib::string +Application::getSources(void) +{ + vespalib::string cmd = vespa::Defaults::vespaHome(); + cmd.append("libexec/vespa/vespa-config.pl -configsources"); + FILE* fp = popen(cmd.c_str(), "r"); + if (fp == 0) { + std::cerr << "Failed to run " << cmd << " (" + << errno << "): " << strerror(errno) << "\n"; + return ""; + } + vespalib::asciistream specs; + char data[500]; + while (fgets(data, 500, fp) != 0) { + specs << &data[0] << "\n"; + } + pclose(fp); + return specs.str(); +} + +void +Application::usage(void) +{ + std::cerr << + "vespa-model-inspect version 2.0" << std::endl << + "Usage: " << _argv[0] << " [options] " << std::endl << + "options: [-u] for URLs, [-v] for verbose" << std::endl << + " [-c host] or [-c host:port] to specify server" << std::endl << + " [-t tag] to filter on a port tag" << std::endl << + "Where command is:" << std::endl << + " hosts - show all hosts" << std::endl << + " services - show all services" << std::endl << + " clusters - show all cluster names" << std::endl << + " configids - show all config IDs" << std::endl << + " filter:ports - list ports matching filter options" << std::endl << + " host - show services on a given host" << std::endl << + " service [cluster:]" << + " - show all instances of a given servicetype" << std::endl << + " cluster " << + " - show all services associated with the cluster" << std::endl << + " configid " << + " - show service using configid" << std::endl << + " get-index-of " << + " - show all indexes for instances of the servicetype on the host" << std::endl << + std::endl; +} + +int +Application::Main(void) +{ + int cnt = parseOpts(); + if (_argc == cnt) { + usage(); + return 0; + } + + config::ServerSpec spec(_specString); + config::ConfigUri uri = config::ConfigUri::createFromSpec(_cfgId, spec); + ModelInspect model(_flags, uri, std::cout); + return model.action(_argc - cnt, &_argv[cnt]); +} + +int +main(int argc, char** argv) +{ + vespa::Defaults::bootstrap(argv[0]); + Application app; + return app.Entry(argc, argv); +} diff --git a/configutil/src/lib/.gitignore b/configutil/src/lib/.gitignore new file mode 100644 index 00000000000..7e7c0fe7fae --- /dev/null +++ b/configutil/src/lib/.gitignore @@ -0,0 +1,2 @@ +/.depend +/Makefile diff --git a/configutil/src/lib/CMakeLists.txt b/configutil/src/lib/CMakeLists.txt new file mode 100644 index 00000000000..290957bf9f3 --- /dev/null +++ b/configutil/src/lib/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_library(configutil_util STATIC + SOURCES + tags.cpp + modelinspect.cpp + configstatus.cpp + DEPENDS + configdefinitions +) diff --git a/configutil/src/lib/configstatus.cpp b/configutil/src/lib/configstatus.cpp new file mode 100644 index 00000000000..f889c436a97 --- /dev/null +++ b/configutil/src/lib/configstatus.cpp @@ -0,0 +1,237 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include +#include +LOG_SETUP("vespa-config-status"); +#include + +#include +#include +#include +#include +#include + +#include + +#include "configstatus.h" + +using configdefinitions::tagsContain; + +struct ComponentTraverser : public vespalib::slime::ObjectTraverser +{ + const std::string _configId; + std::string _component; + enum { + ROOT, + COMPONENT + } _state; + std::map &_generations; + + ComponentTraverser(std::string configId, + std::map &generations) + : _configId(configId), _state(ROOT), _generations(generations) + {} + + void object(const vespalib::slime::Inspector &inspector) { + inspector.traverse(*this); + } + + static void collect(const std::string configId, const vespalib::Slime &slime, + std::map &generations) { + ComponentTraverser traverser(configId, generations); + slime.get()["config"].traverse(traverser); + } + + virtual void + field(const vespalib::slime::Memory &symbol_name, const vespalib::slime::Inspector &inspector) { + switch (_state) { + case ROOT: + _component = symbol_name.make_string(); + _state = COMPONENT; + inspector.traverse(*this); + _state = ROOT; + break; + case COMPONENT: + const std::string key = symbol_name.make_string(); + int64_t value; + if (key == "generation") { + if (inspector.type().getId() == vespalib::slime::DOUBLE::ID) { + value = (int64_t) inspector.asDouble(); + _generations[_component] = value; + } else if (inspector.type().getId() == vespalib::slime::LONG::ID) { + value = inspector.asLong(); + _generations[_component] = value; + } else { + value = 0; + std::cerr << _configId << ":" << _component << + "Generation has wrong type" << std::endl; + } + } + + break; + } + } +}; + +class MyHttpHandler : public vbench::HttpResultHandler { +private: + std::string _json; + std::string _error; + std::string _configId; + +public: + + MyHttpHandler(std::string configId) + : _json(), _error(), _configId(configId) + {} + + virtual void handleHeader(const vbench::string &name, const vbench::string &value) { + (void) name; + (void) value; + } + + virtual void handleContent(const vbench::Memory &data) { + _json += std::string(data.data, data.size); + } + + virtual void handleFailure(const vbench::string &reason) { + std::cerr << _configId << ": Failed to fetch json: " << reason << std::endl; + _error = reason; + } + + bool failed() { + return(_error.size() > 0); + } + + std::string getJson() { + return _json; + } +}; + +ConfigStatus::ConfigStatus(Flags flags, const config::ConfigUri uri) + : _cfg(), _flags(flags), _generation(0) +{ + if (_flags.verbose) { + std::cerr << "Subscribing to model config with config id " << + uri.getConfigId() << std::endl; + } + try { + config::ConfigSubscriber subscriber(uri.getContext()); + config::ConfigHandle::UP handle = + subscriber.subscribe(uri.getConfigId()); + subscriber.nextConfig(0); + _cfg = handle->getConfig(); + _generation = subscriber.getGeneration(); + } catch(config::ConfigRuntimeException &e) { + std::cerr << e.getMessage() << std::endl; + } + + if (_cfg.get() == NULL) { + std::cerr << "FATAL ERROR: failed to get model configuration." << std::endl; + exit(1); + } +} + +int +ConfigStatus::action() +{ + bool allUpToDate = true; + + for (size_t i = 0; i < _cfg->hosts.size(); i++) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + + for (size_t j = 0; j < hconf.services.size(); j++) { + const cloud::config::ModelConfig::Hosts::Services &svc = hconf.services[j]; + + if (svc.type == "configserver") { + continue; + } + + for (size_t k = 0; k < svc.ports.size(); k++) { + std::string tags = svc.ports[k].tags; + if (tagsContain(tags, "http") && + tagsContain(tags, "state")) { + bool upToDate = checkServiceGeneration(svc.configid, hconf.name, + svc.ports[k].number, + "/state/v1/config"); + + if (!upToDate) { + if (svc.type == "searchnode" || + svc.type == "filedistributorservice" || + svc.type == "topleveldispatch") + { + std::cerr << "[generation not up-to-date ignored]" << std::endl; + } else { + allUpToDate = false; + } + } + break; + } + } + } + } + + return allUpToDate ? 0 : 1; +} + +bool +ConfigStatus::fetch_json(std::string configId, std::string host, int port, + std::string path, std::string &data) +{ + MyHttpHandler myHandler(configId); + bool ok = vbench::HttpClient::fetch(vbench::ServerSpec(host, port), path, myHandler); + + if (ok) { + data = myHandler.getJson(); + return true; + } else { + return false; + } +} + +bool +ConfigStatus::checkServiceGeneration(std::string configId, std::string host, int port, std::string path) +{ + std::string data; + vespalib::Slime slime; + + if (!fetch_json(configId, host, port, path, data)) { + return false; + } + + size_t size = vespalib::slime::JsonFormat::decode(data, slime); + + if (size == 0) { + std::cerr << configId << ": JSON parsing failed" << std::endl; + return false; + } + + vespalib::slime::SimpleBuffer buf; + vespalib::slime::JsonFormat::encode(slime, buf, false); + + if (slime.get()["config"].valid()) { + std::map generations; + ComponentTraverser::collect(configId, slime, generations); + bool upToDate = true; + + std::map::iterator iter; + for (iter = generations.begin(); iter != generations.end(); iter++) { + if (iter->second != _generation) { + std::cout << configId << ":" << iter->first << " has generation " << + iter->second << " not " << _generation << std::endl; + upToDate = false; + } else { + if (_flags.verbose) { + std::cout << configId << ":" << iter->first << + " has the latest generation " << iter->second << std::endl; + } + } + } + + return upToDate; + } else { + std::cerr << configId << ": No valid config object" << std::endl; + + return false; + } +} diff --git a/configutil/src/lib/configstatus.h b/configutil/src/lib/configstatus.h new file mode 100644 index 00000000000..41e3d2fe782 --- /dev/null +++ b/configutil/src/lib/configstatus.h @@ -0,0 +1,31 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include +#include +#include + +class ConfigStatus +{ +public: + struct Flags { + bool verbose; + Flags() + : verbose(false) + {} + }; + + ConfigStatus(Flags flags, const config::ConfigUri uri); + int action(); + +private: + std::unique_ptr _cfg; + Flags _flags; + int64_t _generation; + + bool fetch_json(std::string configId, std::string host, int port, std::string path, + std::string &data); + bool checkServiceGeneration(std::string configId, std::string host, int port, + std::string path); +}; + diff --git a/configutil/src/lib/modelinspect.cpp b/configutil/src/lib/modelinspect.cpp new file mode 100644 index 00000000000..69fd2b804df --- /dev/null +++ b/configutil/src/lib/modelinspect.cpp @@ -0,0 +1,366 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include +#include +#include +LOG_SETUP("vespa-model-inspect"); +#include +#include "modelinspect.h" +#include +#include + +using configdefinitions::tagsContain; +using configdefinitions::upcase; + +ModelInspect::ModelInspect(Flags flags, const config::ConfigUri uri, std::ostream &out) + : _cfg(), _flags(flags), _out(out) +{ + if (_flags.verbose) { + std::cerr << "subscribing to model config with configid " << uri.getConfigId() << "\n"; + } + + try { + _cfg = config::ConfigGetter::getConfig(uri.getConfigId(), + uri.getContext()); + } catch(config::ConfigRuntimeException &e) { + std::cerr << e.getMessage() << "\n"; + } + if (_cfg.get() != NULL) { + if (_flags.verbose) std::cerr << "success!\n"; + } else { + std::cerr << "FATAL ERROR: failed to get model configuration.\n"; + exit(1); + } +} + +ModelInspect::~ModelInspect() +{ +} + +void +ModelInspect::printPort(const vespalib::string &host, int port, + const vespalib::string &tags) +{ + if (_flags.tagfilt) { + for (size_t i = 0; i < _flags.tagFilter.size(); ++i) { + if (! tagsContain(tags, _flags.tagFilter[i])) { + return; + } + } + } + if (_flags.makeuri && tagsContain(tags, "HTTP")) { + _out << " http://" << host << ":" << port << "/"; + } else { + _out << " tcp/" << host << ":" << port; + } + if (_flags.tagfilt) { + _out << "\n"; + } else { + vespalib::string upper = upcase(tags); + _out << " (" << upper << ")\n"; + } +} + +void +ModelInspect::printService(const cloud::config::ModelConfig::Hosts::Services &svc, + const vespalib::string &host) +{ + if (!_flags.tagfilt) { + _out << svc.name << " @ " << host << " : " << svc.clustertype << std::endl; + _out << svc.configid << std::endl; + } + for (size_t i = 0; i < svc.ports.size(); ++i) { + printPort(host, svc.ports[i].number, svc.ports[i].tags); + } +} + +int +ModelInspect::action(int cnt, char **argv) +{ + const vespalib::string cmd = *argv++; + if (cnt == 1) { + if (cmd == "yamldump") { + yamlDump(); + return 0; + } + if (cmd == "hosts") { + listHosts(); + return 0; + } + if (cmd == "services") { + listServices(); + return 0; + } + if (cmd == "clusters") { + listClusters(); + return 0; + } + if (cmd == "configids") { + listConfigIds(); + return 0; + } + if (cmd == "filter:hosts") { + if (!_flags.tagfilt) { + std::cerr << "filter needs some filter options" << std::endl; + return 1; + } + std::cerr << "not implemented" << std::endl; + return 1; + } + if (cmd == "filter:ports") { + if (!_flags.tagfilt) { + std::cerr << "filter needs some filter options" << std::endl; + return 1; + } + return listAllPorts(); + } + } + if (cnt == 2) { + vespalib::string arg = *argv++; + if (cmd == "host") { + return listHost(arg); + } + if (cmd == "cluster") { + return listCluster(arg); + } + if (cmd == "service") { + size_t colon = arg.find(':'); + if (colon != vespalib::string::npos) { + return listService(arg.substr(0, colon), + arg.substr(colon + 1)); + } else { + return listService(arg); + } + } + if (cmd == "configid") { + return listConfigId(arg); + } + } + if (cnt == 3) { + vespalib::string arg1 = *argv++; + vespalib::string arg2 = *argv++; + if (cmd == "get-index-of") { + return getIndexOf(arg1, arg2); + } + }; + std::cerr << "bad args '" << cmd << "' (got " << cnt << " arguments)" << std::endl; + return 1; +} + +void +ModelInspect::dumpService(const cloud::config::ModelConfig::Hosts::Services &svc, + const vespalib::string &host) +{ + _out << "- servicename: " << svc.name << "\n"; + _out << " servicetype: " << svc.type << "\n"; + _out << " clustertype: " << svc.clustertype << "\n"; + _out << " clustername: " << svc.clustername << "\n"; + _out << " index: " << svc.index << "\n"; + _out << " hostname: " << host << "\n"; + _out << " config-id: " << svc.configid << "\n"; + + if (svc.ports.size() > 0) { + _out << " ports: \n"; + for (size_t i = 0; i < svc.ports.size(); ++i) { + _out << " - " << svc.ports[i].number << "\n"; + } + } +} + +void +ModelInspect::yamlDump() +{ + _out << "--- \n"; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + dumpService(hconf.services[j], hconf.name); + } + } +} + +void +ModelInspect::listHosts() +{ + std::vector hosts; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + hosts.push_back(hconf.name); + } + std::sort(hosts.begin(), hosts.end()); + for (size_t i = 0; i < hosts.size(); ++i) { + _out << hosts[i] << std::endl; + } +} + +void +ModelInspect::listServices() +{ + typedef std::set Set; + Set services; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + services.insert(hconf.services[j].type); + } + } + for (Set::const_iterator it = services.begin(); it != services.end(); ++it) { + _out << (*it) << std::endl; + } +} + +void +ModelInspect::listClusters() +{ + typedef std::set Set; + Set clusters; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + clusters.insert(hconf.services[j].clustername); + } + } + for (Set::const_iterator it = clusters.begin(); it != clusters.end(); ++it) { + _out << (*it) << std::endl; + } +} + +void +ModelInspect::listConfigIds() +{ + std::vector configids; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + configids.push_back(hconf.services[j].configid); + } + } + std::sort(configids.begin(), configids.end()); + for (size_t i = 0; i < configids.size(); ++i) { + _out << configids[i] << std::endl; + } +} + +int +ModelInspect::listHost(const vespalib::string host) +{ + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + if (host == hconf.name) { + for (size_t j = 0; j < hconf.services.size(); ++j) { + printService(hconf.services[j], host); + } + return 0; + } + } + std::cerr << "no config found for host '" << host << "'\n"; + return 1; +} + +int +ModelInspect::listCluster(const vespalib::string cluster) +{ + bool found = false; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + if (cluster == hconf.services[j].clustername) { + found = true; + printService(hconf.services[j], hconf.name); + } + } + } + if (found) return 0; + std::cerr << "no config found for cluster '" << cluster << "'\n"; + return 1; +} + +int +ModelInspect::listAllPorts() +{ + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + printService(hconf.services[j], hconf.name); + } + } + return 0; +} + +int +ModelInspect::listService(const vespalib::string svctype) +{ + bool found = false; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + if (svctype == hconf.services[j].type) { + found = true; + printService(hconf.services[j], hconf.name); + } + } + } + if (found) return 0; + std::cerr << "no services found with type '" << svctype << "'\n"; + return 1; +} + + +int +ModelInspect::listService(const vespalib::string cluster, + const vespalib::string svctype) +{ + bool found = false; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + if (cluster == hconf.services[j].clustername && + svctype == hconf.services[j].type) + { + found = true; + printService(hconf.services[j], hconf.name); + } + } + } + if (found) return 0; + std::cerr << "no services found with type '" << svctype << "' in cluster '" << cluster << "'\n"; + return 1; +} + +int +ModelInspect::listConfigId(const vespalib::string configid) +{ + bool found = false; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + for (size_t j = 0; j < hconf.services.size(); ++j) { + if (configid == hconf.services[j].configid) { + found = true; + printService(hconf.services[j], hconf.name); + } + } + } + if (found) return 0; + std::cerr << "no services found with configid '" << configid << "'\n"; + return 1; +} + +int +ModelInspect::getIndexOf(const vespalib::string service, const vespalib::string host) +{ + bool found = false; + for (size_t i = 0; i < _cfg->hosts.size(); ++i) { + const cloud::config::ModelConfig::Hosts &hconf = _cfg->hosts[i]; + if (host == hconf.name) { + for (size_t j = 0; j < hconf.services.size(); ++j) { + if (service == hconf.services[j].type) { + found = true; + _out << hconf.services[j].index << std::endl; + } + } + } + } + if (found) return 0; + std::cerr << "no service of type '" << service << "' found for host '" << host << "'\n"; + return 1; +} diff --git a/configutil/src/lib/modelinspect.h b/configutil/src/lib/modelinspect.h new file mode 100644 index 00000000000..b50ea04e197 --- /dev/null +++ b/configutil/src/lib/modelinspect.h @@ -0,0 +1,52 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include +#include +#include + +class ModelInspect + +{ +public: + struct Flags { + bool verbose; + bool makeuri; + bool tagfilt; + std::vector tagFilter; + Flags() + : verbose(false), makeuri(false), tagfilt(false), + tagFilter() + {} + }; + + ModelInspect(Flags flags, const config::ConfigUri uri, std::ostream &out); + virtual ~ModelInspect(); + + int action(int cnt, char *argv[]); + + virtual void yamlDump(); + virtual void listHosts(); + virtual void listServices(); + virtual void listClusters(); + virtual void listConfigIds(); + virtual int listHost(const vespalib::string host); + virtual int listCluster(const vespalib::string cluster); + virtual int listAllPorts(); + virtual int listService(const vespalib::string svctype); + virtual int listService(const vespalib::string cluster, + const vespalib::string svctype); + virtual int listConfigId(const vespalib::string configid); + virtual int getIndexOf(const vespalib::string service, const vespalib::string host); + +private: + std::unique_ptr _cfg; + Flags _flags; + std::ostream &_out; + + void printService(const cloud::config::ModelConfig::Hosts::Services &svc, + const vespalib::string &host); + void printPort(const vespalib::string &host, int port, + const vespalib::string &tags); + void dumpService(const cloud::config::ModelConfig::Hosts::Services &svc, + const vespalib::string &host); + +}; diff --git a/configutil/src/lib/tags.cpp b/configutil/src/lib/tags.cpp new file mode 100644 index 00000000000..50b01c80ba2 --- /dev/null +++ b/configutil/src/lib/tags.cpp @@ -0,0 +1,39 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. + +#include + +#include + +#include "tags.h" + +namespace configdefinitions { + +vespalib::string upcase(const vespalib::string &orig) +{ + vespalib::string upper(orig); + for (size_t i = 0; i < orig.size(); ++i) { + int l = (unsigned char)orig[i]; + upper[i] = (unsigned char)toupper(l); + } + return upper; +} + +bool tagsContain(const vespalib::string &tags, const vespalib::string &tag) +{ + vespalib::string allupper = upcase(tags); + vespalib::string tagupper = upcase(tag); + + for (;;) { + size_t pos = allupper.rfind(' '); + if (pos == vespalib::string::npos) { + break; + } + if (allupper.substr(pos+1) == tagupper) { + return true; + } + allupper.resize(pos); + } + return (allupper == tagupper); +} + +} diff --git a/configutil/src/lib/tags.h b/configutil/src/lib/tags.h new file mode 100644 index 00000000000..11764fe7af1 --- /dev/null +++ b/configutil/src/lib/tags.h @@ -0,0 +1,12 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#pragma once + +#include + +namespace configdefinitions { + +vespalib::string upcase(const vespalib::string &orig); +bool tagsContain(const vespalib::string &tags, const vespalib::string &tag); + +} + diff --git a/configutil/src/testlist.txt b/configutil/src/testlist.txt new file mode 100644 index 00000000000..79108b8c618 --- /dev/null +++ b/configutil/src/testlist.txt @@ -0,0 +1,3 @@ +tests/config_status +tests/model_inspect +tests/tags diff --git a/configutil/src/tests/config_status/.gitignore b/configutil/src/tests/config_status/.gitignore new file mode 100644 index 00000000000..921ed89e386 --- /dev/null +++ b/configutil/src/tests/config_status/.gitignore @@ -0,0 +1,4 @@ +/.depend +/Makefile +/config_status_test +configutil_config_status_test_app diff --git a/configutil/src/tests/config_status/CMakeLists.txt b/configutil/src/tests/config_status/CMakeLists.txt new file mode 100644 index 00000000000..2eda052bfd8 --- /dev/null +++ b/configutil/src/tests/config_status/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(configutil_config_status_test_app + SOURCES + config_status_test.cpp + DEPENDS + configutil_util +) +vespa_add_test(NAME configutil_config_status_test_app COMMAND configutil_config_status_test_app) diff --git a/configutil/src/tests/config_status/config_status_test.cpp b/configutil/src/tests/config_status/config_status_test.cpp new file mode 100644 index 00000000000..af3f343a0b7 --- /dev/null +++ b/configutil/src/tests/config_status/config_status_test.cpp @@ -0,0 +1,109 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace config; + +class HTTPStatus : private Fast_HTTPServer { +private: + std::string _reply; + bool _fail; + + virtual void onGetRequest(const string &, const string &, + Fast_HTTPConnection &conn) { + if (_fail) { + conn.Output(conn.GetHTTPVersion().c_str()); + conn.Output(" 500 Error\r\n"); + conn.Output("Connection: close\r\n"); + conn.Output("\r\n"); + } else { + conn.Output(conn.GetHTTPVersion().c_str()); + conn.Output(" 200 OK\r\n"); + conn.Output("Content-Type: application/json\r\n\r\n"); + conn.Output(_reply.c_str()); + } + }; + + +public: + HTTPStatus(std::string reply) + : Fast_HTTPServer(0), _reply(reply), _fail(false) + { + Start(); + }; + HTTPStatus(bool fail) + : Fast_HTTPServer(0), _reply(""), _fail(fail) + { + Start(); + }; + + int getListenPort() { return Fast_HTTPServer::getListenPort(); } + + ~HTTPStatus() { + Stop(); + }; +}; + +class Status { +public: + ConfigStatus::Flags flags; + std::unique_ptr status; + + Status(int httpport) : flags() { + flags.verbose = true; + ConfigSet set; + ConfigContext::SP ctx(new ConfigContext(set)); + cloud::config::ModelConfigBuilder builder; + + cloud::config::ModelConfigBuilder::Hosts::Services::Ports port; + port.number = httpport; + port.tags = "http state"; + + cloud::config::ModelConfigBuilder::Hosts::Services service; + service.name = "qrserver"; + service.type = "qrserver"; + service.configid = "qrserver/cluster.default"; + service.clustertype = "qrserver"; + service.clustername = "default"; + service.ports.push_back(port); + + cloud::config::ModelConfigBuilder::Hosts host; + host.services.push_back(service); + host.name = "localhost"; + + builder.hosts.push_back(host); + + set.addBuilder("admin/model", &builder); + config::ConfigUri uri("admin/model", ctx); + std::unique_ptr s(new ConfigStatus(flags, uri)); + status = std::move(s); + }; + + ~Status() { + }; +}; + +TEST_FF("all ok", HTTPStatus(std::string("{\"config\": { \"all\": { \"generation\": 1 } }}")), Status(f1.getListenPort())) { + ASSERT_EQUAL(0, f2.status->action()); +} + +TEST_FF("generation too old", HTTPStatus(std::string("{\"config\": { \"all\": { \"generation\": 0 } }}")), Status(f1.getListenPort())) { + ASSERT_EQUAL(1, f2.status->action()); +} + +TEST_FF("bad json", HTTPStatus(std::string("{")), Status(f1.getListenPort())) { + ASSERT_EQUAL(1, f2.status->action()); +} + +TEST_FF("http failure", HTTPStatus(true), Status(f1.getListenPort())) { + ASSERT_EQUAL(1, f2.status->action()); +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/configutil/src/tests/model_inspect/.gitignore b/configutil/src/tests/model_inspect/.gitignore new file mode 100644 index 00000000000..083a3fd3de8 --- /dev/null +++ b/configutil/src/tests/model_inspect/.gitignore @@ -0,0 +1,4 @@ +/.depend +/Makefile +/model_inspect_test +configutil_model_inspect_test_app diff --git a/configutil/src/tests/model_inspect/CMakeLists.txt b/configutil/src/tests/model_inspect/CMakeLists.txt new file mode 100644 index 00000000000..a2890b5ae23 --- /dev/null +++ b/configutil/src/tests/model_inspect/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(configutil_model_inspect_test_app + SOURCES + model_inspect_test.cpp + DEPENDS + configutil_util +) +vespa_add_test(NAME configutil_model_inspect_test_app COMMAND configutil_model_inspect_test_app) diff --git a/configutil/src/tests/model_inspect/FILES b/configutil/src/tests/model_inspect/FILES new file mode 100644 index 00000000000..fb9a8362cdd --- /dev/null +++ b/configutil/src/tests/model_inspect/FILES @@ -0,0 +1 @@ +tags_test.cpp diff --git a/configutil/src/tests/model_inspect/model.cfg b/configutil/src/tests/model_inspect/model.cfg new file mode 100644 index 00000000000..76377e75825 --- /dev/null +++ b/configutil/src/tests/model_inspect/model.cfg @@ -0,0 +1,159 @@ +hosts[0].name "example.yahoo.com" +hosts[0].services[0].name "logd" +hosts[0].services[0].type "logd" +hosts[0].services[0].configid "hosts/example.yahoo.com/logd" +hosts[0].services[0].clustertype "hosts" +hosts[0].services[0].clustername "admin" +hosts[0].services[0].index 999999 +hosts[0].services[2].name "configproxy" +hosts[0].services[2].type "configproxy" +hosts[0].services[2].configid "hosts/example.yahoo.com/configproxy" +hosts[0].services[2].clustertype "hosts" +hosts[0].services[2].clustername "admin" +hosts[0].services[2].index 999999 +hosts[0].services[2].ports[0].number 19090 +hosts[0].services[2].ports[0].tags "admin rpc status client rpc" +hosts[0].services[3].name "config-sentinel" +hosts[0].services[3].type "config-sentinel" +hosts[0].services[3].configid "hosts/example.yahoo.com/sentinel" +hosts[0].services[3].clustertype "hosts" +hosts[0].services[3].clustername "admin" +hosts[0].services[3].index 999999 +hosts[0].services[3].ports[0].number 19097 +hosts[0].services[3].ports[0].tags "notyet rpc" +hosts[0].services[3].ports[1].number 19098 +hosts[0].services[3].ports[1].tags "interactive telnet" +hosts[0].services[4].name "logserver" +hosts[0].services[4].type "logserver" +hosts[0].services[4].configid "admin/logserver" +hosts[0].services[4].clustertype "admin" +hosts[0].services[4].clustername "admin" +hosts[0].services[4].index 999999 +hosts[0].services[4].ports[0].number 19080 +hosts[0].services[4].ports[0].tags "unused" +hosts[0].services[4].ports[1].number 19081 +hosts[0].services[4].ports[1].tags "logtp" +hosts[0].services[4].ports[2].number 19082 +hosts[0].services[4].ports[2].tags "unused" +hosts[0].services[4].ports[3].number 19083 +hosts[0].services[4].ports[3].tags "telnet logtp" +hosts[0].services[5].name "configserver" +hosts[0].services[5].type "configserver" +hosts[0].services[5].configid "admin/configservers/configserver.0" +hosts[0].services[5].clustertype "admin" +hosts[0].services[5].clustername "admin" +hosts[0].services[5].index 0 +hosts[0].services[5].ports[0].number 19070 +hosts[0].services[5].ports[0].tags "config rpc" +hosts[0].services[5].ports[1].number 19071 +hosts[0].services[5].ports[1].tags "config http" +hosts[0].services[6].name "adminserver" +hosts[0].services[6].type "adminserver" +hosts[0].services[6].configid "admin/adminserver" +hosts[0].services[6].clustertype "admin" +hosts[0].services[6].clustername "admin" +hosts[0].services[6].index 999999 +hosts[0].services[7].name "slobrok" +hosts[0].services[7].type "slobrok" +hosts[0].services[7].configid "admin/slobrok.0" +hosts[0].services[7].clustertype "slobrok" +hosts[0].services[7].clustername "admin" +hosts[0].services[7].index 0 +hosts[0].services[7].ports[0].number 19099 +hosts[0].services[7].ports[0].tags "status admin rpc" +hosts[0].services[8].name "qrserver" +hosts[0].services[8].type "qrserver" +hosts[0].services[8].configid "search/qrsclusters/default/qrserver.0" +hosts[0].services[8].clustertype "" +hosts[0].services[8].clustername "default" +hosts[0].services[8].index 0 +hosts[0].services[8].ports[0].number 4080 +hosts[0].services[8].ports[0].tags "state external query http" +hosts[0].services[8].ports[1].number 4081 +hosts[0].services[8].ports[1].tags "external status http" +hosts[0].services[8].ports[2].number 4082 +hosts[0].services[8].ports[2].tags "messaging rpc" +hosts[0].services[8].ports[3].number 4083 +hosts[0].services[8].ports[3].tags "external fileserver http" +hosts[0].services[8].ports[4].number 4084 +hosts[0].services[8].ports[4].tags "admin rpc" +hosts[0].services[8].ports[5].number 4085 +hosts[0].services[8].ports[5].tags "rmiregistry rmi" +hosts[0].services[8].ports[6].number 4086 +hosts[0].services[8].ports[6].tags "jmx rmi" +hosts[0].services[9].name "searchnode" +hosts[0].services[9].type "searchnode" +hosts[0].services[9].configid "search/cluster.music/c0/r0" +hosts[0].services[9].clustertype "search" +hosts[0].services[9].clustername "music" +hosts[0].services[9].index 0 +hosts[0].services[9].ports[0].number 19100 +hosts[0].services[9].ports[0].tags "status admin rtc rpc" +hosts[0].services[9].ports[1].number 19101 +hosts[0].services[9].ports[1].tags "fs4" +hosts[0].services[9].ports[2].number 19102 +hosts[0].services[9].ports[2].tags "metrics xml http" +hosts[0].services[9].ports[3].number 19103 +hosts[0].services[9].ports[3].tags "persistence-provider rpc" +hosts[0].services[9].ports[4].number 19104 +hosts[0].services[9].ports[4].tags "health json http" +hosts[0].services[10].name "transactionlogserver" +hosts[0].services[10].type "transactionlogserver" +hosts[0].services[10].configid "search/cluster.music/c0/r0/transactionlogserver" +hosts[0].services[10].clustertype "search" +hosts[0].services[10].clustername "music" +hosts[0].services[10].index 999999 +hosts[0].services[10].ports[0].number 19105 +hosts[0].services[10].ports[0].tags "tls" +hosts[0].services[11].name "clustercontroller" +hosts[0].services[11].type "clustercontroller" +hosts[0].services[11].configid "search/cluster.music/rtx/0" +hosts[0].services[11].clustertype "search" +hosts[0].services[11].clustername "music" +hosts[0].services[11].index 0 +hosts[0].services[11].ports[0].number 19106 +hosts[0].services[11].ports[0].tags "status admin rpc rtx" +hosts[0].services[11].ports[1].number 19107 +hosts[0].services[11].ports[1].tags "unused" +hosts[0].services[12].name "topleveldispatch" +hosts[0].services[12].type "topleveldispatch" +hosts[0].services[12].configid "search/cluster.music/tlds/tld.0" +hosts[0].services[12].clustertype "search" +hosts[0].services[12].clustername "music" +hosts[0].services[12].index 0 +hosts[0].services[12].ports[0].number 19108 +hosts[0].services[12].ports[0].tags "admin rpc" +hosts[0].services[12].ports[1].number 19109 +hosts[0].services[12].ports[1].tags "fs4" +hosts[0].services[12].ports[2].number 19110 +hosts[0].services[12].ports[2].tags "health json http" +hosts[0].services[13].name "docprocservice" +hosts[0].services[13].type "docprocservice" +hosts[0].services[13].configid "docproc/cluster.music.indexing/0" +hosts[0].services[13].clustertype "" +hosts[0].services[13].clustername "music.indexing" +hosts[0].services[13].index 0 +hosts[0].services[13].ports[0].number 19111 +hosts[0].services[13].ports[0].tags "state external query http" +hosts[0].services[13].ports[1].number 19112 +hosts[0].services[13].ports[1].tags "external status http" +hosts[0].services[13].ports[2].number 19113 +hosts[0].services[13].ports[2].tags "messaging rpc" +hosts[0].services[13].ports[3].number 19114 +hosts[0].services[13].ports[3].tags "external fileserver http" +hosts[0].services[13].ports[4].number 19115 +hosts[0].services[13].ports[4].tags "admin rpc" +hosts[0].services[13].ports[5].number 19116 +hosts[0].services[13].ports[5].tags "rmiregistry rmi" +hosts[0].services[13].ports[6].number 19117 +hosts[0].services[13].ports[6].tags "jmx rmi" +hosts[0].services[14].name "filedistributorservice" +hosts[0].services[14].type "filedistributorservice" +hosts[0].services[14].configid "filedistribution/example.yahoo.com" +hosts[0].services[14].clustertype "filedistribution" +hosts[0].services[14].clustername "admin" +hosts[0].services[14].index 999999 +hosts[0].services[14].ports[0].number 19118 +hosts[0].services[14].ports[0].tags "rpc" +hosts[0].services[14].ports[1].number 19119 +hosts[0].services[14].ports[1].tags "torrent" diff --git a/configutil/src/tests/model_inspect/model_inspect_test.cpp b/configutil/src/tests/model_inspect/model_inspect_test.cpp new file mode 100644 index 00000000000..283d1f0c4f2 --- /dev/null +++ b/configutil/src/tests/model_inspect/model_inspect_test.cpp @@ -0,0 +1,267 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include +#include +#include +#include + +class Model { +public: + config::ConfigUri uri; + ModelInspect::Flags flags; + std::stringstream stream; + ModelInspect model; + + Model() : uri("file:model.cfg"), flags(), stream(), + model(flags, uri, stream) { + }; + + Model(ModelInspect::Flags _flags) + : uri("file:model.cfg"), flags(_flags), stream(), + model(flags, uri, stream) { + }; + + void contains(std::string needle) { + ASSERT_TRUE(stream.str().find(needle) != std::string::npos); + }; + + void missing(std::string needle) { + ASSERT_TRUE(stream.str().find(needle) == std::string::npos); + }; + + ~Model() { + }; +}; + +class MakeUriFlags : public ModelInspect::Flags { +public: + MakeUriFlags() : ModelInspect::Flags() { + makeuri = true; + } +}; + +class TagFilterFlags : public ModelInspect::Flags { +public: + TagFilterFlags(vespalib::string tag) : ModelInspect::Flags() { + tagfilt = true; + tagFilter.push_back(tag); + } +}; + +class ModelDummy : public ModelInspect { +public: + bool _yamlDump, + _listHosts, + _listServices, + _listCluster, + _listConfigIds, + _listHost, + _listClusters, + _listAllPorts, + _listService, + _listService2, + _listConfigId, + _getIndexOf; + ModelDummy(std::stringstream &stream) + : ModelInspect(ModelInspect::Flags(), + config::ConfigUri("file:model.cfg"), + stream) { + _yamlDump = + _listHosts = + _listServices = + _listCluster = + _listConfigIds = + _listHost = + _listClusters = + _listAllPorts = + _listService = + _listService2 = + _listConfigId = + _getIndexOf = false; + }; + + void yamlDump() { _yamlDump = true; }; + void listHosts() { _listHosts = true; }; + void listServices() { _listServices = true; }; + void listClusters() { _listClusters = true; }; + void listConfigIds() { _listConfigIds = true; }; + int listHost(const vespalib::string) { _listHost = true; return 0; }; + int listAllPorts() { _listAllPorts = true; return 0; }; + int listCluster(const vespalib::string) { _listCluster = true; return 0; }; + int listService(const vespalib::string) { _listService = true; return 0; }; + int listService(const vespalib::string, const vespalib::string) { _listService2 = true; return 0; }; + int listConfigId(const vespalib::string) { _listConfigId = true; return 0; }; + int getIndexOf(const vespalib::string, const vespalib::string) { _getIndexOf = true; return 0; }; + + ~ModelDummy() {}; +}; + + +TEST_F("yamlDump", Model) { + ModelInspect & inspect = f1.model; + inspect.yamlDump(); + + // Make sure that we got at least some yaml data out + // TODO: Parse yaml + f1.contains("- servicename: logd"); +} + +TEST_F("listHosts", Model) { + f1.model.listHosts(); + f1.contains("example.yahoo.com"); +} + +TEST_F("listServices", Model) { + f1.model.listServices(); + f1.contains("logd"); + f1.contains("qrserver"); +} + +TEST_F("listClusters", Model) { + f1.model.listClusters(); + f1.contains("admin"); + f1.contains("default"); + f1.contains("music"); +} + +TEST_F("listConfigIds", Model) { + f1.model.listConfigIds(); + f1.contains("search/qrsclusters/default/qrserver.0"); + f1.contains("admin/configservers/configserver.0"); +} + +TEST_F("listHost", Model) { + ASSERT_EQUAL(0, f1.model.listHost("example.yahoo.com")); + f1.contains("search/qrsclusters/default/qrserver.0"); + ASSERT_EQUAL(1, f1.model.listHost("nothere")); +} + +TEST_F("listCluster", Model) { + ASSERT_EQUAL(0, f1.model.listCluster("default")); + f1.contains("search/qrsclusters/default/qrserver.0"); + + ASSERT_EQUAL(1, f1.model.listCluster("nothere")); +} + +TEST_F("listAllPorts", Model) { + f1.model.listAllPorts(); + f1.contains("example.yahoo.com:19080"); +} + +TEST_F("listService", Model) { + ASSERT_EQUAL(0, f1.model.listService("qrserver")); + f1.contains("search/qrsclusters/default/qrserver.0"); + ASSERT_EQUAL(1, f1.model.listService("nothere")); +} + +TEST_F("listService with cluster", Model) { + ASSERT_EQUAL(0, f1.model.listService("default", "qrserver")); + f1.contains("search/qrsclusters/default/qrserver.0"); + ASSERT_EQUAL(1, f1.model.listService("notacluster", "qrserver")); +} + +TEST_F("listConfigId", Model) { + ASSERT_EQUAL(0, f1.model.listConfigId("hosts/example.yahoo.com/logd")); + f1.contains("logd @ example.yahoo.com : hosts"); + ASSERT_EQUAL(1, f1.model.listConfigId("nothere")); +} + +TEST_F("getIndexOf", Model) { + ASSERT_EQUAL(0, f1.model.getIndexOf("logd", "example.yahoo.com")); + ASSERT_EQUAL(1, f1.model.getIndexOf("nothere", "example.yahoo.com")); + ASSERT_EQUAL(1, f1.model.getIndexOf("nothere", "nothere")); +} + +TEST_FF("tag filter match", TagFilterFlags("http"), Model(f1)) { + f2.model.listService("qrserver"); + f2.contains("tcp/example.yahoo.com:4080"); +} + +TEST_FF("tag filter no match", TagFilterFlags("nothing"), Model(f1)) { + f2.model.listService("qrserver"); + f2.missing("tcp/example.yahoo.com:4080"); +} + +TEST_FF("makeuri", MakeUriFlags(), Model(f1)) { + f2.model.listService("qrserver"); + f2.contains("http://example.yahoo.com:4080"); +} + +TEST_FF("action no command", std::stringstream, ModelDummy(f1)) { + char *argv[] = { strdup("notacommand"), NULL, NULL }; + ASSERT_EQUAL(1, f2.action(1, argv)); + ASSERT_EQUAL(1, f2.action(2, argv)); + ASSERT_EQUAL(1, f2.action(3, argv)); + free(argv[0]); +} + +TEST_FF("action", std::stringstream, ModelDummy(f1)) { + { + char *arg[] = { strdup("yamldump") }; + ASSERT_EQUAL(0, f2.action(1, arg)); + ASSERT_TRUE(f2._yamlDump); + free(arg[0]); + } + { + char *arg[] = { strdup("hosts") }; + ASSERT_EQUAL(0, f2.action(1, arg)); + ASSERT_TRUE(f2._listHosts); + free(arg[0]); + } + { + char *arg[] = { strdup("services") }; + ASSERT_EQUAL(0, f2.action(1, arg)); + ASSERT_TRUE(f2._listServices); + free(arg[0]); + } + { + char *arg[] = { strdup("clusters") }; + ASSERT_EQUAL(0, f2.action(1, arg)); + ASSERT_TRUE(f2._listClusters); + free(arg[0]); + } + { + char *arg[] = { strdup("configids") }; + ASSERT_EQUAL(0, f2.action(1, arg)); + ASSERT_TRUE(f2._listConfigIds); + free(arg[0]); + } + { + char *arg[] = { strdup("host"), NULL }; + ASSERT_EQUAL(0, f2.action(2, arg)); + ASSERT_TRUE(f2._listHost); + free(arg[0]); + } + { + char *arg[] = { strdup("cluster"), NULL }; + ASSERT_EQUAL(0, f2.action(2, arg)); + ASSERT_TRUE(f2._listCluster); + free(arg[0]); + } + { + char *arg[] = { strdup("service"), NULL }; + ASSERT_EQUAL(0, f2.action(2, arg)); + ASSERT_TRUE(f2._listService); + free(arg[0]); + } + { + char *arg[] = { strdup("configid"), NULL }; + ASSERT_EQUAL(0, f2.action(2, arg)); + ASSERT_TRUE(f2._listConfigId); + free(arg[0]); + } + { + char *arg[] = { strdup("service"), strdup("a:b") }; + ASSERT_EQUAL(0, f2.action(2, arg)); + ASSERT_TRUE(f2._listService2); + free(arg[0]); + free(arg[1]); + } + { + char *arg[] = { strdup("get-index-of"), NULL, NULL }; + ASSERT_EQUAL(0, f2.action(3, arg)); + ASSERT_TRUE(f2._getIndexOf); + free(arg[0]); + } +} + +TEST_MAIN() { TEST_RUN_ALL(); } diff --git a/configutil/src/tests/tags/.gitignore b/configutil/src/tests/tags/.gitignore new file mode 100644 index 00000000000..6d21ec0c34d --- /dev/null +++ b/configutil/src/tests/tags/.gitignore @@ -0,0 +1,4 @@ +/.depend +/Makefile +/tags_test +configutil_tags_test_app diff --git a/configutil/src/tests/tags/CMakeLists.txt b/configutil/src/tests/tags/CMakeLists.txt new file mode 100644 index 00000000000..5e43bee2ecc --- /dev/null +++ b/configutil/src/tests/tags/CMakeLists.txt @@ -0,0 +1,8 @@ +# Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +vespa_add_executable(configutil_tags_test_app + SOURCES + tags_test.cpp + DEPENDS + configutil_util +) +vespa_add_test(NAME configutil_tags_test_app COMMAND configutil_tags_test_app) diff --git a/configutil/src/tests/tags/FILES b/configutil/src/tests/tags/FILES new file mode 100644 index 00000000000..fb9a8362cdd --- /dev/null +++ b/configutil/src/tests/tags/FILES @@ -0,0 +1 @@ +tags_test.cpp diff --git a/configutil/src/tests/tags/tags_test.cpp b/configutil/src/tests/tags/tags_test.cpp new file mode 100644 index 00000000000..61c23490a8a --- /dev/null +++ b/configutil/src/tests/tags/tags_test.cpp @@ -0,0 +1,20 @@ +// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +#include +#include + +using namespace configdefinitions; + +TEST("upcase") { + EXPECT_EQUAL(vespalib::string("A"), upcase(vespalib::string("a"))); + EXPECT_EQUAL(vespalib::string("A"), upcase(vespalib::string("A"))); +} + +TEST("tagsContain") { + EXPECT_TRUE(tagsContain("a b c", "a")); + EXPECT_TRUE(tagsContain("a b c", "b")); + EXPECT_TRUE(tagsContain("a b c", "c")); + + EXPECT_FALSE(tagsContain("a b c", "d")); +} + +TEST_MAIN() { TEST_RUN_ALL(); } -- cgit v1.2.3