From 9b0a799b0b9be9d33237038f21aaeea1fa8a6a86 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Boric Date: Tue, 26 Feb 2019 15:52:19 +0100 Subject: [PATCH 1/2] Implement tracking commands in libnutclient Signed-off-by: Jean-Baptiste Boric --- clients/nutclient.cpp | 101 ++++++++++++++++++++++++++++++++++++++---- clients/nutclient.h | 50 ++++++++++++++++++--- docs/new-clients.txt | 65 ++++++++++++++++++++++++--- docs/nut.dict | 9 +++- 4 files changed, 202 insertions(+), 23 deletions(-) diff --git a/clients/nutclient.cpp b/clients/nutclient.cpp index 7d80939d7c..4ffde65190 100644 --- a/clients/nutclient.cpp +++ b/clients/nutclient.cpp @@ -406,6 +406,8 @@ void Socket::write(const std::string& str)throw(nut::IOException) * */ +const Feature Client::TRACKING = "TRACKING"; + Client::Client() { } @@ -479,6 +481,19 @@ bool Client::hasDeviceCommand(const std::string& dev, const std::string& name)th return names.find(name) != names.end(); } +bool Client::hasFeature(const Feature& feature)throw(NutException) +{ + try + { + // If feature is known, querying it won't throw an exception. + isFeatureEnabled(feature); + return true; + } + catch(...) + { + return false; + } +} /* * @@ -693,20 +708,20 @@ std::map > > TcpClient return map; } -void TcpClient::setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value)throw(NutException) +TrackingID TcpClient::setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value)throw(NutException) { std::string query = "SET VAR " + dev + " " + name + " " + escape(value); - detectError(sendQuery(query)); + return sendTrackingQuery(query); } -void TcpClient::setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values)throw(NutException) +TrackingID TcpClient::setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values)throw(NutException) { std::string query = "SET VAR " + dev + " " + name; for(size_t n=0; n TcpClient::getDeviceCommandNames(const std::string& dev)throw(NutException) @@ -727,9 +742,9 @@ std::string TcpClient::getDeviceCommandDescription(const std::string& dev, const return get("CMDDESC", dev + " " + name)[0]; } -void TcpClient::executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param)throw(NutException) +TrackingID TcpClient::executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param)throw(NutException) { - detectError(sendQuery("INSTCMD " + dev + " " + name + " " + param)); + return sendTrackingQuery("INSTCMD " + dev + " " + name + " " + param); } void TcpClient::deviceLogin(const std::string& dev)throw(NutException) @@ -753,6 +768,56 @@ int TcpClient::deviceGetNumLogins(const std::string& dev)throw(NutException) return atoi(num.c_str()); } +TrackingResult TcpClient::getTrackingResult(const TrackingID& id)throw(NutException) +{ + if (id.empty()) + { + return TrackingResult::SUCCESS; + } + + std::string result = sendQuery("GET TRACKING " + id); + + if (result == "PENDING") + { + return TrackingResult::PENDING; + } + else if (result == "SUCCESS") + { + return TrackingResult::SUCCESS; + } + else if (result == "ERR UNKNOWN") + { + return TrackingResult::UNKNOWN; + } + else + { + return TrackingResult::FAILURE; + } +} + +bool TcpClient::isFeatureEnabled(const Feature& feature)throw(NutException) +{ + std::string result = sendQuery("GET " + feature); + detectError(result); + + if (result == "ON") + { + return true; + } + else if (result == "OFF") + { + return false; + } + else + { + throw NutException("Unknown feature result " + result); + } +} +void TcpClient::setFeature(const Feature& feature, bool status)throw(NutException) +{ + std::string result = sendQuery("SET " + feature + " " + (status ? "ON" : "OFF")); + detectError(result); +} std::vector TcpClient::get (const std::string& subcmd, const std::string& params) throw(NutException) @@ -968,6 +1033,26 @@ std::string TcpClient::escape(const std::string& str) return res; } +TrackingID TcpClient::sendTrackingQuery(const std::string& req)throw(NutException) +{ + std::string reply = sendQuery(req); + detectError(reply); + std::vector res = explode(reply); + + if (res.size() == 1 && res[0] == "OK") + { + return TrackingID(""); + } + else if (res.size() == 3 && res[0] == "OK" && res[1] == "TRACKING") + { + return TrackingID(res[2]); + } + else + { + throw NutException("Unknown query result"); + } +} + /* * * Device implementation @@ -1142,10 +1227,10 @@ Command Device::getCommand(const std::string& name)throw(NutException) return Command(NULL, ""); } -void Device::executeCommand(const std::string& name, const std::string& param)throw(NutException) +TrackingID Device::executeCommand(const std::string& name, const std::string& param)throw(NutException) { if (!isOk()) throw NutException("Invalid device"); - getClient()->executeDeviceCommand(getName(), name, param); + return getClient()->executeDeviceCommand(getName(), name, param); } void Device::login()throw(NutException) diff --git a/clients/nutclient.h b/clients/nutclient.h index 125a52c023..9cd53096ea 100644 --- a/clients/nutclient.h +++ b/clients/nutclient.h @@ -111,6 +111,24 @@ class TimeoutException : public IOException virtual ~TimeoutException() throw() {} }; +/** + * Cookie given when performing async action, used to redeem result at a later date. + */ +typedef std::string TrackingID; + +/** + * Result of an async action. + */ +typedef enum +{ + UNKNOWN, + PENDING, + SUCCESS, + FAILURE, +} TrackingResult; + +typedef std::string Feature; + /** * A nut client is the starting point to dialog to NUTD. * It can connect to an NUTD then retrieve its device list. @@ -232,14 +250,14 @@ class Client * \param name Variable name * \param value Variable value */ - virtual void setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value)throw(NutException)=0; + virtual TrackingID setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value)throw(NutException)=0; /** * Intend to set the value of a variable. * \param dev Device name * \param name Variable name * \param value Variable value */ - virtual void setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values)throw(NutException)=0; + virtual TrackingID setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values)throw(NutException)=0; /** \} */ /** @@ -273,7 +291,7 @@ class Client * \param name Command name * \param param Additional command parameter */ - virtual void executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param="")throw(NutException)=0; + virtual TrackingID executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param="")throw(NutException)=0; /** \} */ /** @@ -294,6 +312,18 @@ class Client virtual void deviceMaster(const std::string& dev)throw(NutException)=0; virtual void deviceForcedShutdown(const std::string& dev)throw(NutException)=0; + /** + * Retrieve the result of a tracking ID. + * \param id Tracking ID. + */ + virtual TrackingResult getTrackingResult(const TrackingID& id)throw(NutException)=0; + + virtual bool hasFeature(const Feature& feature)throw(NutException); + virtual bool isFeatureEnabled(const Feature& feature)throw(NutException)=0; + virtual void setFeature(const Feature& feature, bool status)throw(NutException)=0; + + static const Feature TRACKING; + protected: Client(); }; @@ -378,22 +408,28 @@ class TcpClient : public Client virtual std::vector getDeviceVariableValue(const std::string& dev, const std::string& name)throw(NutException); virtual std::map > getDeviceVariableValues(const std::string& dev)throw(NutException); virtual std::map > > getDevicesVariableValues(const std::set& devs)throw(NutException); - virtual void setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value)throw(NutException); - virtual void setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values)throw(NutException); + virtual TrackingID setDeviceVariable(const std::string& dev, const std::string& name, const std::string& value)throw(NutException); + virtual TrackingID setDeviceVariable(const std::string& dev, const std::string& name, const std::vector& values)throw(NutException); virtual std::set getDeviceCommandNames(const std::string& dev)throw(NutException); virtual std::string getDeviceCommandDescription(const std::string& dev, const std::string& name)throw(NutException); - virtual void executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param="")throw(NutException); + virtual TrackingID executeDeviceCommand(const std::string& dev, const std::string& name, const std::string& param="")throw(NutException); virtual void deviceLogin(const std::string& dev)throw(NutException); virtual void deviceMaster(const std::string& dev)throw(NutException); virtual void deviceForcedShutdown(const std::string& dev)throw(NutException); virtual int deviceGetNumLogins(const std::string& dev)throw(NutException); + virtual TrackingResult getTrackingResult(const TrackingID& id)throw(NutException); + + virtual bool isFeatureEnabled(const Feature& feature)throw(NutException); + virtual void setFeature(const Feature& feature, bool status)throw(NutException); + protected: std::string sendQuery(const std::string& req)throw(nut::IOException); void sendAsyncQueries(const std::vector& req)throw(IOException); static void detectError(const std::string& req)throw(nut::NutException); + TrackingID sendTrackingQuery(const std::string& req)throw(nut::NutException); std::vector get(const std::string& subcmd, const std::string& params = "") throw(nut::NutException); @@ -541,7 +577,7 @@ class Device * \param name Command name. * \param param Additional command parameter */ - void executeCommand(const std::string& name, const std::string& param="")throw(NutException); + TrackingID executeCommand(const std::string& name, const std::string& param="")throw(NutException); /** * Login current client's user for the device. diff --git a/docs/new-clients.txt b/docs/new-clients.txt index 7b41364c9a..8fa9aaf183 100644 --- a/docs/new-clients.txt +++ b/docs/new-clients.txt @@ -52,27 +52,78 @@ and commands with an object-oriented API in C++ and C. For more information, refer to the linkman:libnutclient[3] manual page. #include + #include + #include + #include + using namespace nut; using namespace std; - int main(int argc, char** argv) + int main(int argc, char **argv) { + Client *client; try { // Connection - Client* client = new TcpClient("localhost", 3493); - Device mydev = client->getDevice("myups"); - cout << mydev.getDescription() << endl; - Variable var = mydev.getVariable("device.model"); - cout << var.getValue()[0] << endl; + client = new TcpClient("localhost", 3493); + + if (argc >= 2) + { + // Reading data from device + Device mydev = client->getDevice(argv[1]); + cout << "Description: " << mydev.getDescription() << endl; + Variable var = mydev.getVariable("device.model"); + cout << "Model: " << var.getValue()[0] << endl; + + if (argc >= 3) + { + // Authenticate to NUT server + const char *user = getenv("NUT_USER"); + const char *password = getenv("NUT_PASSWD"); + client->authenticate(user ? user : "", password ? password : ""); + + // Enable command tracking, if available + if (client->hasFeature(Client::TRACKING)) + { + cout << "Server can do command tracking" << std::endl; + client->setFeature(Client::TRACKING, true); + } + else + { + std::cout << "Server can't do command tracking" << std::endl; + } + + // Perform an asynchronous command + TrackingID id = mydev.executeCommand(argv[2]); + TrackingResult result; + do + { + sleep(1); + result = client->getTrackingResult(id); + } + while (result == PENDING); + + // Display result of command + const char *output = ""; + switch (result) + { + case SUCCESS: output = "SUCCESS"; break; + case FAILURE: output = "FAILURE"; break; + case UNKNOWN: output = "UNKNOWN"; break; + } + cout << "Command sent, result=" << output << endl; + } + } } - catch(NutException& ex) + catch (NutException &ex) { cerr << "Unexpected problem : " << ex.str() << endl; } + delete client; return 0; } + Configuration helpers ~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/nut.dict b/docs/nut.dict index db8f027385..bbbe4c0ea0 100644 --- a/docs/nut.dict +++ b/docs/nut.dict @@ -1,4 +1,4 @@ -personal_ws-1.1 en 2440 utf-8 +personal_ws-1.1 en 2447 utf-8 AAS ACFAIL ACFREQ @@ -1031,6 +1031,8 @@ Tnn Tomek TopGuard Toth +TrackingID +TrackingResult Tripp TrippLite Tru @@ -1495,6 +1497,7 @@ everyone's everything's evilhack executables +executeCommand execve extendedhistory extradata @@ -1549,8 +1552,10 @@ gentoo gestion getDescription getDevice +getTrackingResult getValue getVariable +getenv getopt getvar gitignore @@ -1573,6 +1578,7 @@ gz gzip hal hardcoded +hasFeature hb hcl hg @@ -2109,6 +2115,7 @@ ser seria serialnumber servicebypass +setFeature setaux setflags setgid From b30941414729dca64e19c7850e7f0b8fbbf22b4e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Boric Date: Mon, 11 Mar 2019 15:03:28 +0100 Subject: [PATCH 2/2] Add TrackingResult::INVALID_ARGUMENT Signed-off-by: Jean-Baptiste Boric --- clients/nutclient.cpp | 4 ++++ clients/nutclient.h | 1 + 2 files changed, 5 insertions(+) diff --git a/clients/nutclient.cpp b/clients/nutclient.cpp index 4ffde65190..b597e0e1e6 100644 --- a/clients/nutclient.cpp +++ b/clients/nutclient.cpp @@ -789,6 +789,10 @@ TrackingResult TcpClient::getTrackingResult(const TrackingID& id)throw(NutExcept { return TrackingResult::UNKNOWN; } + else if (result == "ERR INVALID-ARGUMENT") + { + return TrackingResult::INVALID_ARGUMENT; + } else { return TrackingResult::FAILURE; diff --git a/clients/nutclient.h b/clients/nutclient.h index 9cd53096ea..f4117496dd 100644 --- a/clients/nutclient.h +++ b/clients/nutclient.h @@ -124,6 +124,7 @@ typedef enum UNKNOWN, PENDING, SUCCESS, + INVALID_ARGUMENT, FAILURE, } TrackingResult;