initial repository creation
This commit is contained in:
commit
7d11a0a4ca
|
|
@ -0,0 +1,16 @@
|
||||||
|
cmake_minimum_required (VERSION 3.1.3 FATAL_ERROR)
|
||||||
|
project (etcd-cpp-api)
|
||||||
|
|
||||||
|
find_library(CPPREST_LIB NAMES cpprest)
|
||||||
|
find_package(Boost REQUIRED COMPONENTS system thread locale random)
|
||||||
|
|
||||||
|
set (etcd-cpp-api_VERSION_MAJOR 0)
|
||||||
|
set (etcd-cpp-api_VERSION_MINOR 1)
|
||||||
|
|
||||||
|
enable_testing()
|
||||||
|
|
||||||
|
include_directories(${CMAKE_CURRENT_SOURCE_DIR})
|
||||||
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
|
||||||
|
|
||||||
|
add_subdirectory(src)
|
||||||
|
add_subdirectory(tst)
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
Copyright (c) 2015, Nokia
|
||||||
|
All rights reserved.
|
||||||
|
|
||||||
|
Redistribution and use in source and binary forms, with or without modification, are permitted
|
||||||
|
provided that the following conditions are met:
|
||||||
|
|
||||||
|
1. Redistributions of source code must retain the above copyright notice, this list of conditions
|
||||||
|
and the following disclaimer.
|
||||||
|
|
||||||
|
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
|
||||||
|
and the following disclaimer in the documentation and/or other materials provided with the
|
||||||
|
distribution.
|
||||||
|
|
||||||
|
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse
|
||||||
|
or promote products derived from this software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
|
||||||
|
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
|
||||||
|
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
|
||||||
|
THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
@ -0,0 +1,250 @@
|
||||||
|
etcd-cpp-api is a C++ API for [etcd](https://github.com/coreos/etcd).
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
* [C++ REST SDK](http://casablanca.codeplex.com/)
|
||||||
|
* Boost libraries
|
||||||
|
* [Catch](https://github.com/philsquared/Catch) for testing
|
||||||
|
|
||||||
|
## generic notes
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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).
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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
|
||||||
|
```
|
||||||
|
|
||||||
|
## etcd operations
|
||||||
|
|
||||||
|
### reading a value
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### modifying a value
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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)
|
||||||
|
|
||||||
|
### deleting a value
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
### handling directory nodes
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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```.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd.rmdir("/test", true).get();
|
||||||
|
```
|
||||||
|
|
||||||
|
### watching for changes
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```c++
|
||||||
|
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.
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
#ifndef __ETCD_CLIENT_HPP__
|
||||||
|
#define __ETCD_CLIENT_HPP__
|
||||||
|
|
||||||
|
#include "etcd/Response.hpp"
|
||||||
|
|
||||||
|
#include <cpprest/http_client.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace etcd
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Client is responsible for maintaining a connection towards an etcd server.
|
||||||
|
* Etcd operations can be reached via the methods of the client.
|
||||||
|
*/
|
||||||
|
class Client
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Constructs an etcd client object.
|
||||||
|
* @param etcd_url is the url of the etcd server to connect to, like "http://127.0.0.1:4001"
|
||||||
|
*/
|
||||||
|
Client(std::string const & etcd_url);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a get request to the etcd server
|
||||||
|
* @param key is the key to be read
|
||||||
|
*/
|
||||||
|
pplx::task<Response> get(std::string const & key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the value of a key. The key will be modified if already exists or created
|
||||||
|
* if it does not exists.
|
||||||
|
* @param key is the key to be created or modified
|
||||||
|
* @param value is the new value to be set
|
||||||
|
*/
|
||||||
|
pplx::task<Response> set(std::string const & key, std::string const & value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new key and sets it's value. Fails if the key already exists.
|
||||||
|
* @param key is the key to be created
|
||||||
|
* @param value is the value to be set
|
||||||
|
*/
|
||||||
|
pplx::task<Response> add(std::string const & key, std::string const & value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies an existing key. Fails if the key does not exists.
|
||||||
|
* @param key is the key to be modified
|
||||||
|
* @param value is the new value to be set
|
||||||
|
*/
|
||||||
|
pplx::task<Response> modify(std::string const & key, std::string const & value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies an existing key only if it has a specific value. Fails if the key does not exists
|
||||||
|
* or the original value differs from the expected one.
|
||||||
|
* @param key is the key to be modified
|
||||||
|
* @param value is the new value to be set
|
||||||
|
* @param old_value is the value to be replaced
|
||||||
|
*/
|
||||||
|
pplx::task<Response> modify_if(std::string const & key, std::string const & value, std::string const & old_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies an existing key only if it has a specific modification index value. Fails if the key
|
||||||
|
* does not exists or the modification index of the previous value differs from the expected one.
|
||||||
|
* @param key is the key to be modified
|
||||||
|
* @param value is the new value to be set
|
||||||
|
* @param old_index is the expected index of the original value
|
||||||
|
*/
|
||||||
|
pplx::task<Response> modify_if(std::string const & key, std::string const & value, int old_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a single key. The key has to point to a plain, non directory entry.
|
||||||
|
* @param key is the key to be deleted
|
||||||
|
*/
|
||||||
|
pplx::task<Response> rm(std::string const & key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a single key but only if it has a specific value. Fails if the key does not exists
|
||||||
|
* or the its value differs from the expected one.
|
||||||
|
* @param key is the key to be deleted
|
||||||
|
*/
|
||||||
|
pplx::task<Response> rm_if(std::string const & key, std::string const & old_value);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes an existing key only if it has a specific modification index value. Fails if the key
|
||||||
|
* does not exists or the modification index of it differs from the expected one.
|
||||||
|
* @param key is the key to be deleted
|
||||||
|
* @param old_index is the expected index of the existing value
|
||||||
|
*/
|
||||||
|
pplx::task<Response> rm_if(std::string const & key, int old_index);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a directory listing of the directory identified by the key.
|
||||||
|
* @param key is the key to be listed
|
||||||
|
*/
|
||||||
|
pplx::task<Response> ls(std::string const & key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new directory node. Fails if the parent directory dos not exists or not a directory.
|
||||||
|
* @param key is the directory to be created to be listed
|
||||||
|
*/
|
||||||
|
pplx::task<Response> mkdir(std::string const & key);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a directory node. Fails if the parent directory dos not exists or not a directory.
|
||||||
|
* @param key is the directory to be created to be listed
|
||||||
|
* @param recursive if true then delete a whole subtree, otherwise deletes only an empty directory.
|
||||||
|
*/
|
||||||
|
pplx::task<Response> rmdir(std::string const & key, bool recursive = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches for changes of a key or a subtree. Please note that if you watch e.g. "/testdir" and
|
||||||
|
* a new key is created, like "/testdir/newkey" then no change happened in the value of
|
||||||
|
* "/testdir" so your watch will not detect this. If you want to detect addition and deletion of
|
||||||
|
* directory entries then you have to do a recursive watch.
|
||||||
|
* @param key is the value or directory to be watched
|
||||||
|
* @param recursive if true watch a whole subtree
|
||||||
|
*/
|
||||||
|
pplx::task<Response> watch(std::string const & key, bool recursive = false);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Watches for changes of a key or a subtree from a specific index. The index value can be in the "past".
|
||||||
|
* @param key is the value or directory to be watched
|
||||||
|
* @param fromIndex the first index we are interested in
|
||||||
|
* @param recursive if true watch a whole subtree
|
||||||
|
*/
|
||||||
|
pplx::task<Response> watch(std::string const & key, int fromIndex, bool recursive = false);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
|
||||||
|
pplx::task<Response> send_get_request(web::http::uri_builder & uri);
|
||||||
|
pplx::task<Response> send_del_request(web::http::uri_builder & uri);
|
||||||
|
pplx::task<Response> send_put_request(web::http::uri_builder & uri, std::string const & key, std::string const & value);
|
||||||
|
|
||||||
|
web::http::client::http_client client;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,93 @@
|
||||||
|
#ifndef __ETCD_RESPONSE_HPP__
|
||||||
|
#define __ETCD_RESPONSE_HPP__
|
||||||
|
|
||||||
|
#include <cpprest/http_client.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "etcd/Value.hpp"
|
||||||
|
|
||||||
|
namespace etcd
|
||||||
|
{
|
||||||
|
typedef std::vector<std::string> Keys;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Reponse object received for the requests of etcd::Client
|
||||||
|
*/
|
||||||
|
class Response
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static pplx::task<Response> create(pplx::task<web::http::http_response> response_task);
|
||||||
|
|
||||||
|
Response();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this is a successful response
|
||||||
|
*/
|
||||||
|
bool is_ok() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the error code received from the etcd server. In case of success the error code is 0.
|
||||||
|
*/
|
||||||
|
int error_code() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the string representation of the error code
|
||||||
|
*/
|
||||||
|
std::string const & error_message() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the action type of the operation that this response belongs to.
|
||||||
|
*/
|
||||||
|
std::string const & action() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current index value of etcd
|
||||||
|
*/
|
||||||
|
int index() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the value object of the response to a get/set/modify operation.
|
||||||
|
*/
|
||||||
|
Value const & value() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the previous value object of the response to a set/modify/rm operation.
|
||||||
|
*/
|
||||||
|
Value const & prev_value() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index-th value of the response to an 'ls' operation. Equivalent to values()[index]
|
||||||
|
*/
|
||||||
|
Value const & value(int index) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the vector of values in a directory in response to an 'ls' operation.
|
||||||
|
*/
|
||||||
|
Values const & values() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the vector of keys in a directory in response to an 'ls' operation.
|
||||||
|
*/
|
||||||
|
Keys const & keys() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index-th key in a directory listing. Same as keys()[index]
|
||||||
|
*/
|
||||||
|
std::string const & key(int index) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
Response(web::http::http_response http_response, web::json::value json_value);
|
||||||
|
|
||||||
|
int _error_code;
|
||||||
|
std::string _error_message;
|
||||||
|
int _index;
|
||||||
|
std::string _action;
|
||||||
|
Value _value;
|
||||||
|
Value _prev_value;
|
||||||
|
Values _values;
|
||||||
|
Keys _keys;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
#ifndef __ETCD_VECTOR_HPP__
|
||||||
|
#define __ETCD_VECTOR_HPP__
|
||||||
|
|
||||||
|
#include <cpprest/http_client.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace etcd
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Represents a value object received from the etcd server
|
||||||
|
*/
|
||||||
|
class Value
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* Returns true if this value represents a directory on the server. If true the as_string()
|
||||||
|
* method is meaningless.
|
||||||
|
*/
|
||||||
|
bool is_dir() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the key of this value as an "absolute path".
|
||||||
|
*/
|
||||||
|
std::string const & key() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the string representation of the value
|
||||||
|
*/
|
||||||
|
std::string const & as_string() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the creation index of this value.
|
||||||
|
*/
|
||||||
|
int created_index() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the last modification's index of this value.
|
||||||
|
*/
|
||||||
|
int modified_index() const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
friend class Response;
|
||||||
|
Value();
|
||||||
|
Value(web::json::value const & json_value);
|
||||||
|
std::string _key;
|
||||||
|
bool dir;
|
||||||
|
std::string value;
|
||||||
|
int created;
|
||||||
|
int modified;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef std::vector<Value> Values;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
add_library(etcd-cpp-api SHARED Client.cpp Response.cpp Value.cpp json_constants.cpp)
|
||||||
|
set_property(TARGET etcd-cpp-api PROPERTY CXX_STANDARD 11)
|
||||||
|
|
||||||
|
target_link_libraries(etcd-cpp-api ${CPPREST_LIB})
|
||||||
|
|
||||||
|
install (TARGETS etcd-cpp-api DESTINATION lib)
|
||||||
|
install (FILES ../etcd/Client.hpp
|
||||||
|
../etcd/Response.hpp
|
||||||
|
../etcd/Value.hpp
|
||||||
|
DESTINATION include/etcd)
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
#include "etcd/Client.hpp"
|
||||||
|
|
||||||
|
etcd::Client::Client(std::string const & address)
|
||||||
|
: client(address)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::send_get_request(web::http::uri_builder & uri)
|
||||||
|
{
|
||||||
|
return Response::create(client.request(web::http::methods::GET, uri.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::send_del_request(web::http::uri_builder & uri)
|
||||||
|
{
|
||||||
|
return Response::create(client.request(web::http::methods::DEL, uri.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::send_put_request(web::http::uri_builder & uri, std::string const & key, std::string const & value)
|
||||||
|
{
|
||||||
|
std::string data = key + "=" + value;
|
||||||
|
std::string content_type = "application/x-www-form-urlencoded; param=" + key;
|
||||||
|
return Response::create(client.request(web::http::methods::PUT, uri.to_string(), data.c_str(), content_type.c_str()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::get(std::string const & key)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
return send_get_request(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::set(std::string const & key, std::string const & value)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
return send_put_request(uri, "value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::add(std::string const & key, std::string const & value)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("prevExist=false");
|
||||||
|
return send_put_request(uri, "value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::modify(std::string const & key, std::string const & value)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("prevExist=true");
|
||||||
|
return send_put_request(uri, "value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::modify_if(std::string const & key, std::string const & value, std::string const & old_value)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("prevValue", old_value);
|
||||||
|
return send_put_request(uri, "value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::modify_if(std::string const & key, std::string const & value, int old_index)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("prevIndex", old_index);
|
||||||
|
return send_put_request(uri, "value", value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::rm(std::string const & key)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("dir=false");
|
||||||
|
return Response::create(client.request("DELETE", uri.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::rm_if(std::string const & key, std::string const & old_value)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("dir=false");
|
||||||
|
uri.append_query("prevValue", old_value);
|
||||||
|
return send_del_request(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::rm_if(std::string const & key, int old_index)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("dir=false");
|
||||||
|
uri.append_query("prevIndex", old_index);
|
||||||
|
return send_del_request(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::mkdir(std::string const & key)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
return send_put_request(uri, "dir", "true");
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::rmdir(std::string const & key, bool recursive)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("dir=true");
|
||||||
|
if (recursive)
|
||||||
|
uri.append_query("recursive=true");
|
||||||
|
return send_del_request(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::ls(std::string const & key)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("sorted=true");
|
||||||
|
return send_get_request(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::watch(std::string const & key, bool recursive)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("wait=true");
|
||||||
|
if (recursive)
|
||||||
|
uri.append_query("recursive=true");
|
||||||
|
return send_get_request(uri);
|
||||||
|
}
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Client::watch(std::string const & key, int fromIndex, bool recursive)
|
||||||
|
{
|
||||||
|
web::http::uri_builder uri("/v2/keys" + key);
|
||||||
|
uri.append_query("wait=true");
|
||||||
|
uri.append_query("waitIndex", fromIndex);
|
||||||
|
if (recursive)
|
||||||
|
uri.append_query("recursive=true");
|
||||||
|
return send_get_request(uri);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
#include "etcd/Response.hpp"
|
||||||
|
#include "json_constants.hpp"
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> etcd::Response::create(pplx::task<web::http::http_response> response_task)
|
||||||
|
{
|
||||||
|
return pplx::task<etcd::Response> ([response_task](){
|
||||||
|
auto json_task = response_task.get().extract_json();
|
||||||
|
return etcd::Response(response_task.get(), json_task.get());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
etcd::Response::Response()
|
||||||
|
: _error_code(0),
|
||||||
|
_index(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
etcd::Response::Response(web::http::http_response http_response, web::json::value json_value)
|
||||||
|
: _error_code(0),
|
||||||
|
_index(0)
|
||||||
|
{
|
||||||
|
if (http_response.headers().has(JSON_ETCD_INDEX))
|
||||||
|
_index = atoi(http_response.headers()[JSON_ETCD_INDEX].c_str());
|
||||||
|
|
||||||
|
if (json_value.has_field(JSON_ERROR_CODE))
|
||||||
|
{
|
||||||
|
_error_code = json_value[JSON_ERROR_CODE].as_number().to_int64();
|
||||||
|
_error_message = json_value[JSON_MESSAGE].as_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_value.has_field(JSON_ACTION))
|
||||||
|
_action = json_value[JSON_ACTION].as_string();
|
||||||
|
|
||||||
|
if (json_value.has_field(JSON_NODE))
|
||||||
|
{
|
||||||
|
if (json_value[JSON_NODE].has_field(JSON_NODES))
|
||||||
|
{
|
||||||
|
std::string prefix = json_value[JSON_NODE][JSON_KEY].as_string();
|
||||||
|
for (auto & node : json_value[JSON_NODE][JSON_NODES].as_array())
|
||||||
|
{
|
||||||
|
_values.push_back(Value(node));
|
||||||
|
_keys.push_back(node[JSON_KEY].as_string().substr(prefix.length() + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
_value = Value(json_value.at(JSON_NODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (json_value.has_field(JSON_PREV_NODE))
|
||||||
|
_prev_value = Value(json_value.at(JSON_PREV_NODE));
|
||||||
|
}
|
||||||
|
|
||||||
|
int etcd::Response::error_code() const
|
||||||
|
{
|
||||||
|
return _error_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const & etcd::Response::error_message() const
|
||||||
|
{
|
||||||
|
return _error_message;
|
||||||
|
}
|
||||||
|
|
||||||
|
int etcd::Response::index() const
|
||||||
|
{
|
||||||
|
return _index;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const & etcd::Response::action() const
|
||||||
|
{
|
||||||
|
return _action;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool etcd::Response::is_ok() const
|
||||||
|
{
|
||||||
|
return error_code() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
etcd::Value const & etcd::Response::value() const
|
||||||
|
{
|
||||||
|
return _value;
|
||||||
|
}
|
||||||
|
|
||||||
|
etcd::Value const & etcd::Response::prev_value() const
|
||||||
|
{
|
||||||
|
return _prev_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
etcd::Values const & etcd::Response::values() const
|
||||||
|
{
|
||||||
|
return _values;
|
||||||
|
}
|
||||||
|
|
||||||
|
etcd::Value const & etcd::Response::value(int index) const
|
||||||
|
{
|
||||||
|
return _values[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
etcd::Keys const & etcd::Response::keys() const
|
||||||
|
{
|
||||||
|
return _keys;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const & etcd::Response::key(int index) const
|
||||||
|
{
|
||||||
|
return _keys[index];
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
#include "etcd/Value.hpp"
|
||||||
|
#include "json_constants.hpp"
|
||||||
|
|
||||||
|
etcd::Value::Value()
|
||||||
|
: dir(false),
|
||||||
|
created(0),
|
||||||
|
modified(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
etcd::Value::Value(web::json::value const & json_value)
|
||||||
|
: _key(json_value.has_field(JSON_KEY) ? json_value.at(JSON_KEY).as_string() : ""),
|
||||||
|
dir(json_value.has_field(JSON_DIR)),
|
||||||
|
value(json_value.has_field(JSON_VALUE) ? json_value.at(JSON_VALUE).as_string() : ""),
|
||||||
|
created(json_value.has_field(JSON_CREATED) ? json_value.at(JSON_CREATED).as_number().to_int64() : 0),
|
||||||
|
modified(json_value.has_field(JSON_MODIFIED) ? json_value.at(JSON_MODIFIED).as_number().to_int64() : 0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const & etcd::Value::key() const
|
||||||
|
{
|
||||||
|
return _key;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool etcd::Value::is_dir() const
|
||||||
|
{
|
||||||
|
return dir;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string const & etcd::Value::as_string() const
|
||||||
|
{
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
int etcd::Value::created_index() const
|
||||||
|
{
|
||||||
|
return created;
|
||||||
|
}
|
||||||
|
|
||||||
|
int etcd::Value::modified_index() const
|
||||||
|
{
|
||||||
|
return modified;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#include "json_constants.hpp"
|
||||||
|
|
||||||
|
char const * etcd::JSON_KEY = "key";
|
||||||
|
char const * etcd::JSON_DIR = "dir";
|
||||||
|
char const * etcd::JSON_VALUE = "value";
|
||||||
|
char const * etcd::JSON_CREATED = "createdIndex";
|
||||||
|
char const * etcd::JSON_MODIFIED = "modifiedIndex";
|
||||||
|
char const * etcd::JSON_ERROR_CODE = "errorCode";
|
||||||
|
char const * etcd::JSON_MESSAGE = "message";
|
||||||
|
char const * etcd::JSON_ACTION = "action";
|
||||||
|
char const * etcd::JSON_NODE = "node";
|
||||||
|
char const * etcd::JSON_NODES = "nodes";
|
||||||
|
char const * etcd::JSON_PREV_NODE = "prevNode";
|
||||||
|
char const * etcd::JSON_ETCD_INDEX = "X-Etcd-Index";
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#ifndef __ETCD_JSON_CONSTANTS_HPP__
|
||||||
|
#define __ETCD_JSON_CONSTANTS_HPP__
|
||||||
|
|
||||||
|
namespace etcd
|
||||||
|
{
|
||||||
|
extern char const * JSON_KEY;
|
||||||
|
extern char const * JSON_DIR;
|
||||||
|
extern char const * JSON_VALUE;
|
||||||
|
extern char const * JSON_CREATED;
|
||||||
|
extern char const * JSON_MODIFIED;
|
||||||
|
extern char const * JSON_ERROR_CODE;
|
||||||
|
extern char const * JSON_MESSAGE;
|
||||||
|
extern char const * JSON_ACTION;
|
||||||
|
extern char const * JSON_NODE;
|
||||||
|
extern char const * JSON_NODES;
|
||||||
|
extern char const * JSON_PREV_NODE;
|
||||||
|
extern char const * JSON_ETCD_INDEX;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
add_executable(etcd_test EtcdTest.cpp)
|
||||||
|
set_property(TARGET etcd_test PROPERTY CXX_STANDARD 11)
|
||||||
|
|
||||||
|
target_link_libraries(etcd_test etcd-cpp-api)
|
||||||
|
|
||||||
|
add_test(etcd_test etcd_test)
|
||||||
|
|
@ -0,0 +1,251 @@
|
||||||
|
#define CATCH_CONFIG_MAIN
|
||||||
|
#include <catch.hpp>
|
||||||
|
|
||||||
|
#include "etcd/Client.hpp"
|
||||||
|
|
||||||
|
TEST_CASE("setup")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd.rmdir("/test", true).wait();
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("add a new key")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd::Response resp = etcd.add("/test/key1", "42").get();
|
||||||
|
REQUIRE(0 == resp.error_code());
|
||||||
|
CHECK("create" == resp.action());
|
||||||
|
etcd::Value const & val = resp.value();
|
||||||
|
CHECK("42" == val.as_string());
|
||||||
|
CHECK("/test/key1" == val.key());
|
||||||
|
CHECK(!val.is_dir());
|
||||||
|
CHECK(0 < val.created_index());
|
||||||
|
CHECK(0 < val.modified_index());
|
||||||
|
CHECK(0 < resp.index()); // X-Etcd-Index header value
|
||||||
|
CHECK(105 == etcd.add("/test/key1", "43").get().error_code()); // Key already exists
|
||||||
|
CHECK(105 == etcd.add("/test/key1", "42").get().error_code()); // Key already exists
|
||||||
|
CHECK("Key already exists" == etcd.add("/test/key1", "42").get().error_message());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("read a value from etcd")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd::Response resp = etcd.get("/test/key1").get();
|
||||||
|
CHECK("get" == resp.action());
|
||||||
|
REQUIRE(resp.is_ok());
|
||||||
|
REQUIRE(0 == resp.error_code());
|
||||||
|
CHECK("42" == resp.value().as_string());
|
||||||
|
|
||||||
|
CHECK("" == etcd.get("/test").get().value().as_string()); // key points to a directory
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("simplified read")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
CHECK("42" == etcd.get("/test/key1").get().value().as_string());
|
||||||
|
CHECK(100 == etcd.get("/test/key2").get().error_code()); // Key not found
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("modify a key")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd::Response resp = etcd.modify("/test/key1", "43").get();
|
||||||
|
REQUIRE(0 == resp.error_code()); // overwrite
|
||||||
|
CHECK("update" == resp.action());
|
||||||
|
CHECK(100 == etcd.modify("/test/key2", "43").get().error_code()); // Key not found
|
||||||
|
CHECK("43" == etcd.modify("/test/key1", "42").get().prev_value().as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("set a key")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd::Response resp = etcd.set("/test/key1", "43").get();
|
||||||
|
REQUIRE(0 == resp.error_code()); // overwrite
|
||||||
|
CHECK("set" == resp.action());
|
||||||
|
CHECK(0 == etcd.set("/test/key2", "43").get().error_code()); // create new
|
||||||
|
CHECK("43" == etcd.set("/test/key2", "44").get().prev_value().as_string());
|
||||||
|
CHECK("" == etcd.set("/test/key3", "44").get().prev_value().as_string());
|
||||||
|
CHECK(102 == etcd.set("/test", "42").get().error_code()); // Not a file
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("delete a value")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
CHECK(3 == etcd.ls("/test").get().keys().size());
|
||||||
|
etcd::Response resp = etcd.rm("/test/key1").get();
|
||||||
|
CHECK("43" == resp.prev_value().as_string());
|
||||||
|
CHECK("delete" == resp.action());
|
||||||
|
CHECK(2 == etcd.ls("/test").get().keys().size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("create a directory")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd::Response resp = etcd.mkdir("/test/new_dir").get();
|
||||||
|
CHECK("set" == resp.action());
|
||||||
|
CHECK(resp.value().is_dir());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("list a directory")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
CHECK(0 == etcd.ls("/test/new_dir").get().keys().size());
|
||||||
|
|
||||||
|
etcd.set("/test/new_dir/key1", "value1").wait();
|
||||||
|
etcd.set("/test/new_dir/key2", "value2").wait();
|
||||||
|
etcd.mkdir("/test/new_dir/sub_dir").wait();
|
||||||
|
|
||||||
|
etcd::Response resp = etcd.ls("/test/new_dir").get();
|
||||||
|
CHECK("get" == resp.action());
|
||||||
|
REQUIRE(3 == resp.keys().size());
|
||||||
|
CHECK("key1" == resp.key(0));
|
||||||
|
CHECK("key2" == resp.key(1));
|
||||||
|
CHECK("sub_dir" == resp.key(2));
|
||||||
|
CHECK("value1" == resp.value(0).as_string());
|
||||||
|
CHECK("value2" == resp.value(1).as_string());
|
||||||
|
CHECK(resp.values()[2].is_dir());
|
||||||
|
|
||||||
|
CHECK(0 == etcd.ls("/test/new_dir/key1").get().error_code());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("delete a directory")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
CHECK(108 == etcd.rmdir("/test/new_dir").get().error_code()); // Directory not empty
|
||||||
|
CHECK(0 == etcd.rmdir("/test/new_dir", true).get().error_code());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("wait for a value change")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd.set("/test/key1", "42").wait();
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> res = etcd.watch("/test/key1");
|
||||||
|
CHECK(!res.is_done());
|
||||||
|
sleep(1);
|
||||||
|
CHECK(!res.is_done());
|
||||||
|
|
||||||
|
etcd.set("/test/key1", "43").get();
|
||||||
|
sleep(1);
|
||||||
|
REQUIRE(res.is_done());
|
||||||
|
REQUIRE("set" == res.get().action());
|
||||||
|
CHECK("43" == res.get().value().as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("wait for a directory change")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> res = etcd.watch("/test", true);
|
||||||
|
CHECK(!res.is_done());
|
||||||
|
sleep(1);
|
||||||
|
CHECK(!res.is_done());
|
||||||
|
|
||||||
|
etcd.add("/test/key4", "44").wait();
|
||||||
|
sleep(1);
|
||||||
|
REQUIRE(res.is_done());
|
||||||
|
CHECK("create" == res.get().action());
|
||||||
|
CHECK("44" == res.get().value().as_string());
|
||||||
|
|
||||||
|
pplx::task<etcd::Response> res2 = etcd.watch("/test", true);
|
||||||
|
CHECK(!res2.is_done());
|
||||||
|
sleep(1);
|
||||||
|
CHECK(!res2.is_done());
|
||||||
|
|
||||||
|
etcd.set("/test/key4", "45").wait();
|
||||||
|
REQUIRE(res2.is_done());
|
||||||
|
CHECK("set" == res2.get().action());
|
||||||
|
CHECK("45" == res2.get().value().as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("watch changes in the past")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
|
||||||
|
int index = etcd.set("/test/key1", "42").get().index();
|
||||||
|
|
||||||
|
etcd.set("/test/key1", "43").wait();
|
||||||
|
etcd.set("/test/key1", "44").wait();
|
||||||
|
etcd.set("/test/key1", "45").wait();
|
||||||
|
|
||||||
|
etcd::Response res = etcd.watch("/test/key1", ++index).get();
|
||||||
|
CHECK("set" == res.action());
|
||||||
|
CHECK("43" == res.value().as_string());
|
||||||
|
|
||||||
|
res = etcd.watch("/test/key1", ++index).get();
|
||||||
|
CHECK("set" == res.action());
|
||||||
|
CHECK("44" == res.value().as_string());
|
||||||
|
|
||||||
|
res = etcd.watch("/test", ++index, true).get();
|
||||||
|
CHECK("set" == res.action());
|
||||||
|
CHECK("45" == res.value().as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("atomic compare-and-swap")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd.set("/test/key1", "42").wait();
|
||||||
|
|
||||||
|
// modify success
|
||||||
|
etcd::Response res = etcd.modify_if("/test/key1", "43", "42").get();
|
||||||
|
int index = res.index();
|
||||||
|
REQUIRE(res.is_ok());
|
||||||
|
CHECK("compareAndSwap" == res.action());
|
||||||
|
CHECK("43" == res.value().as_string());
|
||||||
|
|
||||||
|
// modify fails the second time
|
||||||
|
res = etcd.modify_if("/test/key1", "44", "42").get();
|
||||||
|
CHECK(!res.is_ok());
|
||||||
|
CHECK(101 == res.error_code());
|
||||||
|
CHECK("Compare failed" == res.error_message());
|
||||||
|
|
||||||
|
// succes with the correct index
|
||||||
|
res = etcd.modify_if("/test/key1", "44", index).get();
|
||||||
|
REQUIRE(res.is_ok());
|
||||||
|
CHECK("compareAndSwap" == res.action());
|
||||||
|
CHECK("44" == res.value().as_string());
|
||||||
|
|
||||||
|
// index changes so second modify fails
|
||||||
|
res = etcd.modify_if("/test/key1", "45", index).get();
|
||||||
|
CHECK(!res.is_ok());
|
||||||
|
CHECK(101 == res.error_code());
|
||||||
|
CHECK("Compare failed" == res.error_message());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("atomic compare-and-delete based on prevValue")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
etcd.set("/test/key1", "42").wait();
|
||||||
|
|
||||||
|
etcd::Response res = etcd.rm_if("/test/key1", "43").get();
|
||||||
|
CHECK(!res.is_ok());
|
||||||
|
CHECK(101 == res.error_code());
|
||||||
|
CHECK("Compare failed" == res.error_message());
|
||||||
|
|
||||||
|
res = etcd.rm_if("/test/key1", "42").get();
|
||||||
|
REQUIRE(res.is_ok());
|
||||||
|
CHECK("compareAndDelete" == res.action());
|
||||||
|
CHECK("42" == res.prev_value().as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("atomic compare-and-delete based on prevIndex")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
int index = etcd.set("/test/key1", "42").get().index();
|
||||||
|
|
||||||
|
etcd::Response res = etcd.rm_if("/test/key1", index - 1).get();
|
||||||
|
CHECK(!res.is_ok());
|
||||||
|
CHECK(101 == res.error_code());
|
||||||
|
CHECK("Compare failed" == res.error_message());
|
||||||
|
|
||||||
|
res = etcd.rm_if("/test/key1", index).get();
|
||||||
|
REQUIRE(res.is_ok());
|
||||||
|
CHECK("compareAndDelete" == res.action());
|
||||||
|
CHECK("42" == res.prev_value().as_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_CASE("cleanup")
|
||||||
|
{
|
||||||
|
etcd::Client etcd("http://127.0.0.1:4001");
|
||||||
|
REQUIRE(0 == etcd.rmdir("/test", true).get().error_code());
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue