yp-client Example Applications
Files Required to Build Applications
The api-*.hpp files are needed for an application to use the libraries.
Note
If building the code from source the
PTHREADS=1
make flag is required.If using a binary distribution the appropriate "yumapro-pthreads" package for your platform is required.
Example Code Preparation
There are several example programs in
the /usr/share/yumapro/src/yp-client
folder.
They are provided to show how the supplied library is
used to connect and exchange information with servers, and how
notifications from the server are handled.
The C++ Examples:
Directory Name |
Function |
action |
YANG 1.1 Action example |
callhome-server |
NETCONF Call Home Server example |
libtest |
Test code for Users, Devices, Sessions |
sget-system |
Connect to a server using SSH and retrieve the system information |
sget-system-tls |
Connect to a server using TLS and retrieve the system information |
toaster |
An app following the toaster example in the YumaPro Quickstart Guide |
The C wrapper examples:
Directory Name |
Function |
c-toaster |
A c-wrapper app following the toaster example in the YumaPro Quickstart Guide, using SSH |
c-toaster-tls |
A c-wrapper app following the toaster example in the YumaPro Quickstart Guide, using TLS |
The examples have three parameters that are needed to connect to a server:
Parameter |
Usage |
Default Value |
cServer |
Server name |
“localhost” |
cUsername |
User name |
“user” |
cPassword |
User password |
None – the user is prompted for the password |
Note
Before running the code the .cpp or .c file needs to be updated with the correct connection parameters.
If using the netconfd-pro server then make sure it is running.
Example Code: sget-system
The sget-system.cpp example code demonstrates the following:
setup code to deal with any exceptions
start a NETCONF session to a server for a user – prompt for a password
some YangAPI object settings are made to make things more readable
send the request "sget /netconf-state/statistics" to the server to request a node of the server’s YANG datastore
receive the response from the server into a buffer that can be processed
close the session to the server
sget-system.cpp
/*
* Copyright (c) 2016 - 2019, YumaWorks, Inc., All Rights Reserved.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "api-yangapi.hpp"
#include "api-session.hpp"
#include <iostream>
#include <string>
// For the prompt-for-password code.
#include <unistd.h>
#include <termios.h>
// All of the YumaWorks C++ code is in namespace yuma, to avoid naming clashes
// and cluttering the default namespace.
using namespace yuma;
// Used to tell this code to prompt for a password. Don't change this.
const xstring cPromptForPassword { "*" };
// The server to use. Note that the yuma::xstring class requires that its
// initializers be in UTF-8 format. There's no need to explicitly mark the
// strings here as UTF-8 since they're entirely ASCII, we've done so solely for
// explanatory purposes.
const xstring cServer { u8"localhost" };
// The username to use.
const xstring cUsername { u8"user" };
// The password to use. The cPrompForPassword here is a signal to the code below
// that it should prompt the user for his password. You can replace this with an
// actual password if desired, or nullptr to use no password. (You can also
// achieve the no-password effect by omitting the password() call to the
// SessionFactory object entirely.)
const xstring cPassword { cPromptForPassword };
// Some convenience declarations.
using std::cout;
using std::endl;
using std::cin;
using std::string;
static Session session;
// This class will receive notifications for the session, through its operator()
// function. It also holds them and manages access to them from multiple
// threads, necessary because they're usually added from a background thread.
//
// To the outside world, this class will act like std::stack<xstring>.
class NotificationStack {
public:
NotificationStack(): mData(std::make_shared<data_t>()) { }
// Functions to emulate std::stack<xstring>.
bool empty() const { lock_t lock(mData->mutex); return mData->notes.empty(); }
size_t size() const { lock_t lock(mData->mutex); return mData->notes.size(); }
const xstring& top() const { lock_t lock(mData->mutex); return mData->notes.front(); }
void push(const xstring &n) { lock_t lock(mData->mutex); mData->notes.push_front(n); }
void pop() { lock_t lock(mData->mutex); mData->notes.pop_front(); }
// This function blocks until a notification is received or the provided
// wait time runs out.
bool waitForNotification(const std::chrono::duration<clock_t> &maxWaitTime) {
// Start waiting. The condition variable eliminates the need for
// wasteful polling.
lock_t lock(mData->mutex);
return mData->waiter.wait_until(lock, clock_t::now() + maxWaitTime,
[=]() { return !mData->notes.empty(); });
}
// This function turns objects created from this class into "functors" --
// objects that act like a function, but can hold a non-global state as
// well. We're exploiting that capability to create an object that we can
// use as a notification function.
bool operator()(const xstring &newNotification) {
cout << "Received notification:" << endl << newNotification << endl
<< endl;
// Must lock the mutex before all accesses to mData->notes, because this
// function will usually be called from a background thread.
{
lock_t lock(mData->mutex);
mData->notes.push_back(newNotification);
}
mData->waiter.notify_one();
// We could return false here, if we were only looking for a particular
// notification and had found it. That would remove this notification
// function from the Session's list and prevent it from receiving any
// later notifications. Since we want to continue receiving them, we'll
// return true instead.
return true;
}
private:
typedef std::mutex mutex_t;
typedef std::unique_lock<mutex_t> lock_t;
typedef std::chrono::steady_clock clock_t;
struct data_t {
mutable mutex_t mutex;
std::deque<xstring> notes;
std::condition_variable waiter;
};
// Since we're using this class as a functor, it has to be copyable, and
// neither std::mutex nor std::condition_variable is. This works around that
// problem.
std::shared_ptr<data_t> mData;
};
// Ask user for input, returns True if user inputs "No", False otherwise
//
static bool userInputNo() {
string input;
const char cN ='N', cn = 'n';
getline (cin, input);
// Anything starting with "N" or "n" will exit, else continue
if ((input[0] == cN) || (input[0] == cn)){
return true;
}
else {
return false;
}
}
// A function to request a password from the user, showing only asterisks on the
// screen.
xstring fetchPassword(const xstring &prompt, bool showAsterisk = true) {
const char cReturn = 10, cBackspace = 127;
std::cout << prompt << ' ';
xstring r;
while (true) {
struct termios t_old;
tcgetattr(STDIN_FILENO, &t_old);
struct termios t_new = t_old;
t_new.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &t_new);
xmlChar ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &t_old);
if (ch == cReturn) {
break;
} else if (ch == cBackspace) {
if (r.length() != 0) {
if (showAsterisk)
std::cout << "\b \b";
r = r.substr(0, r.length() - 1);
}
} else {
r += ch;
if (showAsterisk)
cout << '*';
}
}
std::cout << std::endl;
return r;
}
// A convenience function for sending a command and reporting its results.
static bool sendCommand(const xstring &command) {
try {
cout << " Sending: " << command << endl;
Response response = session.command(command);
if (response.error) {
cout << "Received error:" << endl << response.result << endl << endl;
return false;
}
if (response.result.empty())
cout << "Received: OK" << endl << endl;
else
cout << "Received:" << endl << response.result << endl << endl;
return true;
} catch (StatusFailure &e) {
cout << "StatusFailure exception: " << e.what() << endl << endl;
return false;
} catch (std::exception &e) {
cout << "other exception: " << e.what() << endl << endl;
return false;
}
}
int main() {
// The first step is to initialize the API by creating a yuma::YangAPI
// object. No parameters are needed for this, but the object must persist
// as long as you want to use the library.
YangAPI api;
// You can change many settings using the YangAPI object. As an example,
// we'll set the indent value to four spaces instead of the default two.
api.indent(4);
// for XML structured response output add
api.displayMode(displaymode::xml);
// We enclose most of the API code in a try/catch structure, to catch any
// exceptions detected in it. The code will generally use the
// yuma::StatusFailure exception, but a few other std::exception-based
// classes might also appear on occasion.
int r = EXIT_FAILURE;
try {
// We'll start by creating a notification stack to receive notifications
// for the session. We'll tie it into the session through a
// SessionFactory call. We could do it with the Session object's
// addNotificationFunction member function instead, but we don't need
// the Token to remove it later, which is the only advantage of doing
// it that way.
NotificationStack notifications;
// If the user has requested that we prompt for a password, we'll do so
// here.
xstring password = (cPassword == cPromptForPassword ?
fetchPassword("Enter your password for server " + cServer + ":",
true) :
cPassword);
// Now we'll create a User. The User class holds information about the
// user account on a server -- its username and credentials. There's no
// requirement for this, we could specify all of these options when
// creating the Session instead, but a User can be saved to a file for
// later reloading and reuse.
User user = UserFactory("me", cUsername).password(password).create();
// We'll create a Device too. The Device class holds information about
// the server we're trying to contact: its type, address information,
// and protocols. Note that we've omitted the port parameter (it would
// normally be specified after the server's address in the DeviceFactory
// constructor), which tells the API to use the default port for the
// session type.
//
// As with the User object, there's no requirement to create this. We
// could specify all this information when creating a Session. The only
// advantage to using a Device is that it can be saved to a file for
// later reloading and reuse.
Device server = NetconfDeviceFactory("myDevice", cServer).create();
// Now we can create a session. We use one of the SessionFactory classes
// for this, ending with a call to SessionFactory::create().
cout << "Attempting to establish connection to server " << cServer <<
"..." << endl;
session = DeviceSessionFactory(user, server)
.notificationFunction(notifications)
.create();
cout << "Session established!" << endl << endl;
do {
// Collect the response from the server to the "sget /netconf-state/statistics" request
Response response = session.command("sget /netconf-state/statistics");
if (response.error) {
cout << "Received error:" << endl << response.result << endl << endl;
break;
}
cout << "error:" << response.error << endl << "response:" << endl << response.result << endl;
// If we get this far, we've succeeded.
r = EXIT_SUCCESS;
} while (0);
// Finally, close the session.
if (!sendCommand("close-session")) {
cout << "Unexpected failure, aborting." << endl;
return EXIT_FAILURE;
}
// When the Session object goes out of scope, the session is terminated
// and automatically cleaned up.
} catch (StatusFailure &e) {
cout << "StatusFailure exception: " << e.what() << endl;
} catch (std::exception &e) {
cout << "other exception: " << e.what() << endl;
}
// When the YangAPI object goes out of scope, the library is deinitialized.
return r;
}
When sget-system.cpp is built the following is displayed (example):
user@YW-U18W sget-system$ make
g++ -g -std=c++11 -I/usr/include/yumapro/ycli -I/usr/include/yumapro/mgr
-I/usr/include/yumapro/ncx -I/usr/include/yumapro/platform
-I/usr/include/libxml2/libxml -I/usr/include/libxml2 -pthread -o
sget-system sget-system.cpp -lyumapro_ycli -lyumapro_mgr -lyumapro_ncx
-lyumapro_subsys-pro -lssl -lcrypto -lssh2 -lcrypt
When sget-system.cpp is executed the following is displayed (example):
user@YW-U18W sget-system$ ./sget-system
Enter your password for server localhost: ***********
Attempting to establish connection to server localhost...
Session established!
error:0
response:
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
<statistics>
<netconf-start-time>2019-12-13T23:26:08Z</netconf-start-time>
<in-bad-hellos>0</in-bad-hellos>
<in-sessions>4</in-sessions>
<dropped-sessions>0</dropped-sessions>
<in-rpcs>9</in-rpcs>
<in-bad-rpcs>0</in-bad-rpcs>
<out-rpc-errors>0</out-rpc-errors>
<out-notifications>0</out-notifications>
</statistics>
</netconf-state>
</data>
</rpc-reply>
Sending: close-session
Received: OK
user@YW-U18W sget-system$
In this example, the session is setup with a username and password:
session = DeviceSessionFactory(user, server)
.create();
The user is defined by:
User user = UserFactory("me", cUsername).password(password).create();
The device is defined by:
Device server = NetconfDeviceFactory("myDevice", cServer).create();
Example Code: sget-system-tls
To use TLS instead of SSH, use the sget-system-tls example. The User creation is different:
User user = UserFactory("me", cUsername)
.sslClientCertificate("$HOME/certs/client.crt")
.sslClientCertificateKeyFile("$HOME/certs/client.key")
.sslTrustStore("/etc/ssl/certs/")
.fallbackOkay(false).create();
To configure TLS certificates for YumaPro SDK see the “Configure TLS” section of the YumaPro SDK Installation Guide.
Specify the port and protocol the communication with the device will use with:
Device server = NetconfDeviceFactory("myDevice",
cServer,
6513,
netconf-tls").create();
When building sget-system-tls the output will look something like:
user@YW-U18W sget-system-tls$ make
g++ -g -std=c++11 -I/usr/include/yumapro/ycli -I/usr/include/yumapro/mgr
-I/usr/include/yumapro/ncx -I/usr/include/yumapro/platform
-I/usr/include/libxml2/libxml -I/usr/include/libxml2 -pthread -o
sget-system-tls sget-system-tls.cpp -lyumapro_ycli -lyumapro_mgr
-lyumapro_ncx -lyumapro_subsys-pro -lssl -lcrypto -lssh2 -lcrypt
When invoking sget-system-tls the output will look something like:
user@YW-U18W sget-system-tls$ ./sget-system-tls
Attempting to establish connection to server localhost...
Session established!
Sending: $$display-mode = cli
Received: OK
error:0
response:
<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
<data>
<netconf-state xmlns="urn:ietf:params:xml:ns:yang:ietf-netconf-monitoring">
<statistics>
<netconf-start-time>2019-12-14T00:17:57Z</netconf-start-time>
<in-bad-hellos>0</in-bad-hellos>
<in-sessions>1</in-sessions>
<dropped-sessions>0</dropped-sessions>
<in-rpcs>1</in-rpcs>
<in-bad-rpcs>0</in-bad-rpcs>
<out-rpc-errors>0</out-rpc-errors>
<out-notifications>0</out-notifications>
</statistics>
</netconf-state>
</data>
</rpc-reply>
Sending: close-session
Received: OK
user@YW-U18W sget-system-tls$
Example Code: toaster
The toaster.cpp example code demonstrates the following, see also the example of libtoaster in the section “Getting Started with toaster.yang” in the YumaPro Quickstart Guide.
setup code to deal with any exceptions
start a NETCONF session to a server for a user – ask them for their password
display the session number (if you are running the server in debug mode you’ll see the session numbers match)
setup to receive notifications for this session
setup a subscription with the server for this session
messages received from the server will display the message number that will match the message numbers displayed in the debug output from the server)
set some session globals and local variables to show how that is done
load the toaster.yang module to the server
mgrload the toaster.yang module
lock the datastore
create the /toaster node (if it already exists, due to running the program several times, ignore the error)
unlock the datastore
display the /toaster node by sending the request “sget /toaster”
display the /toaster node by sending the request “xget /toaster”
send the command to make-toast with specific parameters
receive the toastStatus=done notification (after about 12seconds)
resend the command to make-toast with specific parameters
don’t wait for the toastStatus=done notification, send the cancel-toast command
receive the toastStatus=cancelled notification
close the session to the server
** toaster.cpp**
/*
* Copyright (c) 2016 - 2018, YumaWorks, Inc., All Rights Reserved.
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "api-yangapi.hpp"
#include "api-session.hpp"
#include <iostream>
// For the prompt-for-password code.
#include <unistd.h>
#include <termios.h>
// All of the YumaWorks C++ code is in namespace yuma, to avoid naming clashes
// and cluttering the default namespace.
using namespace yuma;
// Used to tell this code to prompt for a password. Don't change this.
const xstring cPromptForPassword { "*" };
// The server to use. Note that the yuma::xstring class requires that its
// initializers be in UTF-8 format. There's no need to explicitly mark the
// strings here as UTF-8 since they're entirely ASCII, we've done so solely for
// explanatory purposes.
const xstring cServer { u8"localhost" };
// The username to use.
const xstring cUsername { u8"user" };
// The password to use. The cPrompForPassword here is a signal to the code below
// that it should prompt the user for his password. You can replace this with an
// actual password if desired, or nullptr to use no password. (You can also
// achieve the no-password effect by omitting the password() call to the
// SessionFactory object entirely.)
const xstring cPassword { cPromptForPassword };
// Some convenience declarations.
using std::cout;
using std::endl;
static Session session;
// This class will receive notifications for the session, through its operator()
// function. It also holds them and manages access to them from multiple
// threads, necessary because they're usually added from a background thread.
//
// To the outside world, this class will act like std::stack<xstring>.
class NotificationStack {
public:
NotificationStack(): mData(std::make_shared<data_t>()) { }
// Functions to emulate std::stack<xstring>.
bool empty() const { lock_t lock(mData->mutex); return mData->notes.empty(); }
size_t size() const { lock_t lock(mData->mutex); return mData->notes.size(); }
const xstring& top() const { lock_t lock(mData->mutex); return mData->notes.front(); }
void push(const xstring &n) { lock_t lock(mData->mutex); mData->notes.push_front(n); }
void pop() { lock_t lock(mData->mutex); mData->notes.pop_front(); }
// This function blocks until a notification is received or the provided
// wait time runs out.
bool waitForNotification(const std::chrono::duration<clock_t> &maxWaitTime) {
// Start waiting. The condition variable eliminates the need for
// wasteful polling.
lock_t lock(mData->mutex);
return mData->waiter.wait_until(lock, clock_t::now() + maxWaitTime,
[=]() { return !mData->notes.empty(); });
}
// This function turns objects created from this class into "functors" --
// objects that act like a function, but can hold a non-global state as
// well. We're exploiting that capability to create an object that we can
// use as a notification function.
bool operator()(const xstring &newNotification) {
cout << "Received notification:" << endl << newNotification << endl
<< endl;
// Must lock the mutex before all accesses to mData->notes, because this
// function will usually be called from a background thread.
{
lock_t lock(mData->mutex);
mData->notes.push_back(newNotification);
}
mData->waiter.notify_one();
// We could return false here, if we were only looking for a particular
// notification and had found it. That would remove this notification
// function from the Session's list and prevent it from receiving any
// later notifications. Since we want to continue receiving them, we'll
// return true instead.
return true;
}
private:
typedef std::mutex mutex_t;
typedef std::unique_lock<mutex_t> lock_t;
// The C++11 standard defines std::chrono::steady_clock, but before it was
// finalized it defined std::chrono::monotonic_clock instead. This code must
// compile with pre-standard g++ versions too.
#if __GNUC_PREREQ(4,7)
typedef std::chrono::steady_clock clock_t;
#else
typedef std::chrono::monotonic_clock clock_t;
#endif
struct data_t {
mutable mutex_t mutex;
std::deque<xstring> notes;
std::condition_variable waiter;
};
// Since we're using this class as a functor, it has to be copyable, and
// neither std::mutex nor std::condition_variable is. This works around that
// problem.
std::shared_ptr<data_t> mData;
};
// A function to request a password from the user, showing only asterisks on the
// screen.
xstring fetchPassword(const xstring &prompt, bool showAsterisk = true) {
const char cReturn = 10, cBackspace = 127;
std::cout << prompt << ' ';
xstring r;
while (true) {
struct termios t_old;
tcgetattr(STDIN_FILENO, &t_old);
struct termios t_new = t_old;
t_new.c_lflag &= ~(ICANON | ECHO);
tcsetattr(STDIN_FILENO, TCSANOW, &t_new);
xmlChar ch = getchar();
tcsetattr(STDIN_FILENO, TCSANOW, &t_old);
if (ch == cReturn) {
break;
} else if (ch == cBackspace) {
if (r.length() != 0) {
if (showAsterisk)
std::cout << "\b \b";
r = r.substr(0, r.length() - 1);
}
} else {
r += ch;
if (showAsterisk)
cout << '*';
}
}
std::cout << std::endl;
return r;
}
// A convenience function for sending a command and reporting its results.
static bool sendCommand(const xstring &command) {
try {
cout << "Sending command: " << command << endl;
Response response = session.command(command);
xstring id = (response.internalRequestId &&
!response.internalRequestId.empty() ?
" (" + response.internalRequestId + ")" : "");
if (response.error) {
cout << "Received error" << id << ":" << endl << response.result
<< endl << endl;
return false;
}
if (response.result.empty())
cout << "Received OK" << id << endl << endl;
else
cout << "Received response" << id << ":" << endl << response.result
<< endl << endl;
return true;
} catch (StatusFailure &e) {
cout << "StatusFailure exception: " << e.what() << endl << endl;
return false;
} catch (std::exception &e) {
cout << "other exception: " << e.what() << endl << endl;
return false;
}
}
int main() {
// The first step is to initialize the API by creating a yuma::YangAPI
// object. No parameters are needed for this, but the object must persist
// as long as you want to use the library.
YangAPI api;
// You can change many settings using the YangAPI object. As an example,
// we'll set the indent value to four spaces instead of the default two.
api.indent(4);
// We enclose most of the API code in a try/catch structure, to catch any
// exceptions detected in it. The code will generally use the
// yuma::StatusFailure exception, but a few other std::exception-based
// classes might also appear on occasion.
int r = EXIT_FAILURE;
try {
// We'll start by creating a notification stack to receive notifications
// for the session. We'll tie it into the session through a
// SessionFactory call. We could do it with the Session object's
// addNotificationFunction member function instead, but we don't need
// the Token to remove it later, which is the only advantage of doing
// it that way.
NotificationStack notifications;
// If the user has requested that we prompt for a password, we'll do so
// here.
xstring password = (cPassword == cPromptForPassword ?
fetchPassword("Enter your password for server " + cServer + ":",
true) :
cPassword);
// Now we'll create a User. The User class holds information about the
// user account on a server -- its username and credentials. There's no
// requirement for this, we could specify all of these options when
// creating the Session instead, but a User can be saved to a file for
// later reloading and reuse.
User user = UserFactory("me", cUsername).password(password).create();
// We'll create a Device too. The Device class holds information about
// the server we're trying to contact: its type, address information,
// and protocols. Note that we've omitted the port parameter (it would
// normally be specified after the server's address in the DeviceFactory
// constructor), which tells the API to use the default port for the
// session type.
//
// As with the User object, there's no requirement to create this. We
// could specify all this information when creating a Session. The only
// advantage to using a Device is that it can be saved to a file for
// later reloading and reuse.
Device server = NetconfDeviceFactory("myDevice", cServer).create();
// Now we can create a session. We use one of the SessionFactory classes
// for this, ending with a call to SessionFactory::create().
//
// The notificationFunction option tells it where to send notifications
// (messages that are not direct replies to commands). The timeout
// option tells it how long to wait before deciding that something has
// gone wrong with the connection; it defaults to sixty seconds, but
// telling it to time out after fifteen will let it fail more quickly,
// if we know that the connection to the device we're trying to contact
// doesn't have an inordinate amount of latency.
cout << "Attempting to establish connection to server " << cServer <<
"..." << endl;
session = DeviceSessionFactory(user, server)
.notificationFunction(notifications)
.timeout(std::chrono::seconds(15))
.create();
cout << "Session " << session.serverSessionId() << " established!"
<< endl << endl;
// Setup a subscription with the server
if (!sendCommand("create-subscription")) {
cout << "Unexpected failure, aborting." << endl;
return EXIT_FAILURE;
}
do {
// You can create or read global and local variables through the
// Session class as well. These variables can contain anything
// except the null character.
session.global("param1", "parameter value including \nnewlines, 'single-' and \"double-quotes\", and \tt\ta\tb\ts");
cout << "param1 is currently set to '" << session.global("param1")
<< "'" << endl << endl;
// If this one fails (maybe the 'toaster' module isn't installed?),
// we'll skip the rest of the tests and just close the session. If
// it succeeds, it should receive (and print) the toaster module's
// revision, though we're not checking for that.
if (!sendCommand("ysys:load toaster")) {
cout << "Unexpected failure, skipping toaster tests." << endl;
break;
}
// This one should succeed.
if (!sendCommand("get-locks")) {
cout << "Unexpected failure, aborting." << endl;
break;
}
// This one will fail if the /toaster has already been created.
// For our purposes, that's not an error. If it succeeds, save the
// changes.
if (sendCommand("create /toaster")) {
if (!sendCommand("save")) {
cout << "Unexpected failure, aborting." << endl;
break;
}
}
// This one should succeed.
if (!sendCommand("release-locks")) {
cout << "Unexpected failure, aborting." << endl;
break;
}
// These two commands should return (identical) data. We don't check
// the data, just that there's no error.
if (!sendCommand("sget /toaster") || !sendCommand("xget /toaster")) {
cout << "Unexpected failure, aborting." << endl;
break;
}
// Let's make some toast! We'll check for any recent notifications
// first, to ensure that there aren't any that might throw us off.
while (!notifications.empty()) {
notifications.pop();
}
if (sendCommand("make-toast toasterDoneness=1 toasterToastType=toast:wonder-bread")) {
// This will take about twelve seconds, if all goes well, so
// we'll use a twenty-second timeout.
cout << "Toast should be done in about 12 seconds..." << endl;
if (!notifications.waitForNotification(std::chrono::seconds(20))) {
cout << "Did not receive toastDone notification within provided time limit, aborting."
<< endl;
break;
} else {
// Remove the toastDone notification from the stack, so it
// doesn't interfere with the later call.
notifications.pop();
}
} else {
cout << "Unexpected failure, aborting." << endl;
break;
}
// We'll repeat the make-toast command, but this time we'll cancel
// it immediately.
if (!notifications.empty()) {
cout << "Unexpected notification in stack, aborting." << endl;
break;
} else if (sendCommand("make-toast toasterDoneness=1 toasterToastType=toast:frozen-waffle")) {
if (sendCommand("cancel-toast")) {
// This notification should be sent almost immediately.
// We'll only use a five-second timeout, and no message.
if (!notifications.waitForNotification(std::chrono::seconds(5))) {
cout << "Did not receive toastDone notification within provided time limit, aborting."
<< endl;
break;
} else {
// Remove the toastDone notification from the stack.
notifications.pop();
}
} else {
cout << "Unexpected failure, aborting." << endl;
break;
}
} else {
cout << "Unexpected failure, aborting." << endl;
break;
}
// If we get this far, we've succeeded.
r = EXIT_SUCCESS;
} while (0);
// Finally, close the session.
if (!sendCommand("close-session")) {
cout << "Unexpected failure, aborting." << endl;
return EXIT_FAILURE;
}
// When the Session object goes out of scope, the session is terminated
// and automatically cleaned up.
} catch (StatusFailure &e) {
cout << "StatusFailure exception: " << e.what() << endl;
} catch (std::exception &e) {
cout << "other exception: " << e.what() << endl;
}
// When the YangAPI object goes out of scope, the library is deinitialized.
return r;
}
When toaster is built the following is displayed (example):
user@YW-U18W toaster$ make
g++ -g -std=c++11 -I/usr/include/yumapro/ycli -I/usr/include/yumapro/mgr
-I/usr/include/yumapro/ncx -I/usr/include/yumapro/platform
-I/usr/include/libxml2/libxml -I/usr/include/libxml2 -pthread -o toaster
toaster.cpp -lyumapro_ycli -lyumapro_mgr -lyumapro_ncx
-lyumapro_subsys-pro -lssl -lcrypto -lssh2 -lcrypt
user@YW-U18W toaster$
When toaster is executed the following is displayed (example):
user@YW-U18W toaster$ ./toaster
Enter your password for server localhost: ***********
Attempting to establish connection to server localhost...
Session 4 established!
Sending command: create-subscription
Received OK (2)
param1 is currently set to 'parameter value including
newlines, 'single-' and "double-quotes", and t a b s'
Sending command: ysys:load toaster
Received notification:
notification {
eventTime 2019-12-14T00:28:53Z
yang-library-change {
module-set-id 3755
}
}
Received notification:
notification {
eventTime 2019-12-14T00:28:53Z
netconf-capability-change {
changed-by {
username john
session-id 4
source-host 127.0.0.1
}
added-capability http://netconfcentral.org/ns/toaster?module=toaster&revision=2009-11-20
}
}
Received response (3):
rpc-reply {
mod-revision 2009-11-20
}
Sending command: get-locks
Received OK (4)
Sending command: create /toaster
Received OK (6)
Sending command: save
Received notification:
notification {
eventTime 2019-12-14T00:28:54Z
netconf-config-change {
changed-by {
username john
session-id 4
source-host 127.0.0.1
}
datastore running
edit {
target /toast:toaster
operation create
}
}
}
Received OK (7)
Sending command: release-locks
Received OK (8)
Sending command: sget /toaster
Received response (10):
rpc-reply {
data {
toaster {
toasterManufacturer 'Acme, Inc.'
toasterModelNumber 'Super Toastamatic 2000'
toasterStatus up
}
}
}
Sending command: xget /toaster
Received response (11):
rpc-reply {
data {
toaster {
toasterManufacturer 'Acme, Inc.'
toasterModelNumber 'Super Toastamatic 2000'
toasterStatus up
}
}
}
Sending command: make-toast toasterDoneness=1 toasterToastType=toast:wonder-bread
Received OK (12)
Toast should be done in about 12 seconds...
Received notification:
notification {
eventTime 2019-12-14T00:29:07Z
toastDone {
toastStatus done
}
}
Sending command: make-toast toasterDoneness=1 toasterToastType=toast:frozen-waffle
Received OK (13)
Sending command: cancel-toast
Received notification:
notification {
eventTime 2019-12-14T00:29:08Z
toastDone {
toastStatus cancelled
}
}
Received OK (14)
Sending command: close-session
Received OK (15)
user@YW-U18W toaster$