etcd-cpp-api is a C++ API for etcd.
- C++ REST SDK
- Boost libraries
- Catch for testing
etcd::Client etcd("http://127.0.0.1:4001");
etcd::Response response = etcd.get("/test/key1").get();
std::cout << response.value().as_string();
Methods of the etcd client object are sending the corresponding HTTP requests and are returning
immediatelly with a pplx::task
object. The task object is responsible for handling the
reception of the HTTP response as well as parsing the JSON body of the response. All of this is done
asynchronously in a background thread so you can continue your code to do other operations while the
current etcd operation is executing in the background or you can wait for the response with the
wait()
or get()
methods if a synchron behaviour is enough for your needs. These methods
are blocking until the HTTP response arrives or some error situation happens. get()
method
also returns the etcd::Response
object.
etcd::Client etcd("http://127.0.0.1:4001");
pplx::task<etcd::Response> response_task = etcd.get("/test/key1").get();
// ... do something else
etcd::Response response = response_task.get();
std::cout << response.value().as_string();
The pplx library allows to do even more. You can attach continuation ojects to the task if you do
not care about when the response is coming you only want to specify what to do then. This
can be achieved by calling the then
method of the task, giving a funcion object parameter to
it that can be used as a callback when the response is arrived and processed. The parameter of this
callback should be either a etcd::Response
or a pplx::task<etcd:Response>
. You should
probably use a C++ lambda funcion here as a callback.
etcd::Client etcd("http://127.0.0.1:4001");
etcd.get("/test/key1").then([](etcd::Response response)
{
std::cout << response.value().as_string();
});
// ... your code can continue here without any delay
Your lambda function should have a parameter of type etcd::Response
or
pplx::task<etcd::Response>
. In the latter case you can get the actual etcd::Response
object with the get()
function of the task. Calling get can raise exeptions so this is the way
how you can catch the errors generated by the REST interface. The get()
call will not block in
this case since the respose has been already arrived (we are inside the callback).
etcd::Client etcd("http://127.0.0.1:4001");
etcd.get("/test/key1").then([](pplx::task<etcd::Response> response_task)
{
try
{
etcd::Response response = response.task.get(); // can throw
std::cout << response.value().as_string();
}
catch (std::ecxeption const & ex)
{
std::cerr << ex.what();
}
});
// ... your code can continue here without any delay
You can read a value with the get
method of the clinent instance. The only parameter is the
key to be read. If the read operation is successful then the value of the key can be acquired with
the value()
method of the response. Success of the operation can be checked with the
is_ok()
method of the response. In case of an error, the error_code()
and
error_message()
methods can be called for some further detail.
Please note that there can be two kind of error situations. There can be some problem with the
communication between the client and the etcd server. In this case the get()
method of the
response task will throw an exception as shown above. If the communication is ok but there is some
problem with the content of the actual operation, like attemp to read a non-existing key then the
response object will give you all the details. Let's see this in an example.
The Value object of the response also holds some extra information besides the string value of the
key. You can also get the index number of the creation and the last modification of this key with
the created_index()
and the modofied_index()
methods.
etcd::Client etcd("http://127.0.0.1:4001");
pplx::task<etcd::Response> response_task = etcd.get("/test/key1");
try
{
etcd::Response response = response_task.get(); // can throw
if (response.is_ok())
std::cout << "successful read, value=" << response.value().as_string();
else
std::cout << "operation failed, details: " << response.error_message();
}
catch (std::ecxeption const & ex)
{
std::cerr << "communication problem, details: " << ex.what();
}
Setting the value of a key can be done with the set()
method of the client. You simply pass
the key and the value as string parameters and you are done. The newly set value object can be asked
from the response object exactly the same way as in case of the reading (with the value()
method). This way you can check for example the index value of your modification. You can also check
what was the previous value that this operation was overwritten. You can do that with the
prev_value()
method of the response object.
etcd::Client etcd("http://127.0.0.1:4001");
pplx::task<etcd::Response> response_task = etcd.set("/test/key1", "42");
try
{
etcd::Response response = response_task.get();
if (response.is_ok())
std::cout << "The new value is successfully set, previous value was "
<< response.prev_value().as_string();
else
std::cout << "operation failed, details: " << response.error_message();
}
catch (std::ecxeption const & ex)
{
std::cerr << "communication problem, details: " << ex.what();
}
The set method creates a new leaf node if it weren't exists already or modifies an existing one. There are a couple of other modification methods that are executing the write operation only upon some specific conditions.
add(key, value)
creates a new value if it's key does not exists and returns a "Key already exists" error otherwise (error code 105)modify(key, value)
modifies an already existing value or returns a "Key not found" error otherwise (error code 100)modify_if(key, value, old_value)
modifies an already existing value but only if the previous value equals with old_value. If the values does not match returns with "Compare failed" error (code 101)modify_if(key, value, old_index)
modifies an already existing value but only if the index of the previous value equals with old_index. If the indices does not match returns with "Compare failed" error (code 101)
Values can be deleted with the rm
method passing the key to be deleted as a parameter. The key
should point to an existing value. There are conditional variations for deletion too.
rm_if(key, value, old_value)
deletes an already existing value but only if the previous value equals with old_value. If the values does not match returns with "Compare failed" error (code 101)rm_if(key, value, old_index)
deletes an already existing value but only if the index of the previous value equals with old_index. If the indices does not match returns with "Compare failed" error (code 101)
Directory nodes can be created, listed and deleted with the mkdir, ls and rmdir methods. For directory creation you just have to specify the full path of the new directory. Naturally the parent has to exists and it has to be another directory.
etcd::Client etcd("http://127.0.0.1:4001");
etcd::Response resp = etcd.mkdir("/test").get();
When you list a directory the response object's keys()
and values()
methods gives you a
vector of directory entry names and values. The value()
method with an integer parameter also
returns with the i-th element of the values vector, so response.values()[i] == response.value(i)
. Entry names in the keys vector are relative to the parent directory. Elements
in the values vector can be subdirectories or actual string values. To decide which one you can use
the is_dir()
method.
etcd::Client etcd("http://127.0.0.1:4001");
etcd::Response resp = etcd.ls("/test/new_dir").get();
for (int i = 0; i < resp.keys().size(); ++i)
{
std::cout << resp.keys(i);
if (resp.value(i).is_dir())
std::cout << "/" << std::endl;
else
std::cout << " = " << resp.value(i).as_string() << std::endl;
}
Directories can only be deleted if they are empty by default. If you want the delete recursively
then you have to pass a second true
parameter to rmdir. This parameter defaults to false
.
etcd::Client etcd("http://127.0.0.1:4001");
etcd.rmdir("/test", true).get();
Watching for a change is possible with the watch()
operation of the client. The watch method
simply does not deliver a response object until the watched value changes in any way (modified or
deleted). When a change happens the returned result object will be the same as the result object of
the modification operation. So if the change is triggered by a value change, then
response.action()
will return "set" or "modify", response.value()
will hold the new
value and response.prev_value()
will contain the previous value. In case of a delete
response.action()
will return "delete", response.value()
will be empty and should not be
called at all and response.prev_value()
will contain the deleted value.
It is also possible to watch a whole directory subtree for changes with passing true
to the second
recursive
parameter of watch
(this parameter defaults to false
if omitted). In
this case the modified value object's key()
method can be handy to determine what key is
actually changed. Since this can be a long lasting operation you have to be prepared that is
terminated by an exception and you have to restart the watch operation.
The watch also accepts an index parameter that specifies what is the first change we are interested about. Since etcd stores the last couple of modifications with this feature you can ensure that your client does not miss a single change.
Here is an example how you can watch continuously for changes of one specific key.
void watch_for_changes()
{
etcd.watch("/nodes", index + 1, true).then([this](pplx::task<etcd::Response> resp_task)
{
try
{
etcd::Response resp = resp_task.get();
index = resp.index();
std::cout << resp.action() << " " << resp.value().as_string() << std::endl;
}
catch(...) {}
watch_for_changes();
});
}
At first glance it seems that watch_for_changes()
calls itself on every value change but in
fact it just sends the asynchron request, sets up a callback for the response and then returns. The
callback is executed by some thread from the pplx library's thread pool and the callback (in this
case a small lambda function actually) will call watch_for_changes
again from there.