Ppconsul C++ Client Library for Consul

repository·master·Indexed 41 days ago

https://github.com/oliora/ppconsul

A C++11 client library for Consul that provides a modular API for service discovery and configuration via Consul's HTTP API. It includes support for the Agent API (service registration and health checks), Key-Value (KV) storage with blocking queries, the Status API for Raft consensus state, and atomic transactions (Txn). Requires a C++11 compatible compiler, CMake 3.1+, Boost 1.55+, and libCURL.

Tokens
4.1K
Snippets
10
Records
14
Agent score
50%

What's inside Ppconsul

  1. How to handle multi-threaded usage and libCURL

    master

    Thread Safety

    Each ppconsul::Consul object maintains a pool of HTTP(S) clients. It is safe to call endpoint objects (like Kv, Agent, etc.) or the Consul object itself from multiple threads simultaneously.

    Aborting Blocking Queries

    To gracefully shut down and abort ongoing blocking queries, use Consul::stop().

    • Warning: This call is irreversible. Once called, the Consul object enters a stopped state forever and no further requests can be made.
    • Requirement: You must enable this feature at construction time using kw::enable_stop = true.
    Consul consul(kw::enable_stop = true);
    // ... perform work ...
    consul.stop(); // Aborts pending requests

    libCURL Initialization

    Ppconsul calls curl_global_init automatically when makeDefaultHttpClientFactory() is first called (usually during Consul construction).

    If you need manual control over libCURL initialization (e.g., to ensure it happens before any threads are spawned), you have two options:

    1. Manual Global Init: Call curl_global_init and curl_global_cleanup yourself in your main() function.
    2. Skip Ppconsul Auto-Init: Create a factory explicitly with makeDefaultHttpClientFactory(false) and pass it to the Consul constructor.
    // Option 2: Skip auto-init
    Consul consul(makeDefaultHttpClientFactory(false), ...);
    // Enable stopping and abort queries
    Consul consul(kw::enable_stop = true);
    consul.stop();
    
    // Skip automatic libCURL initialization
    Consul consul(makeDefaultHttpClientFactory(false), ...);
  2. Install and build Ppconsul

    master

    Ppconsul requires a C++11 compatible compiler (Clang 5+, GCC 4.8+, or Visual Studio 2013+) and CMake 3.1+.

    Dependencies

    • Boost: 1.55 or later (headers only, unless using GCC 4.8, which requires Boost.Regex).
    • libCURL: Required for HTTP/HTTPS.
    • git: Optional, used to deduce .so versions.

    Build Steps (using Conan)

    # Install dependencies
    conan install .
    
    # Create workspace directory
    mkdir workspace
    cd workspace
    
    # Configure
    cmake ..
    # Optionally override installation destination:
    # cmake -DCMAKE_INSTALL_PREFIX=<install_dir> ..
    
    # Build
    cmake --build . --config Release
    
    # Install
    cmake --build . --config Release --target install

    Build Options

    • Static Library: Pass -DBUILD_STATIC_LIB=ON. Note that on Windows, Ppconsul can only be built as a static library.
    • Boost Path: Set -DBOOST_ROOT=<path_to_boost> or BOOST_ROOT env var.
    • libCURL Path: Set -DCURL_ROOT=<path_to_curl> or CURL_ROOT env var.
    • Install Prefix: Set -DCMAKE_INSTALL_PREFIX=<prefix>.
    conan install .
    mkdir workspace
    cd workspace
    cmake ..
    cmake --build . --config Release
    cmake --build . --config Release --target install
  3. Register checks and services using the Agent interface

    master

    The ppconsul::agent::Agent provides methods to register service checks and services in Consul. The library supports several styles of parameter passing, but the recommended approach (New Style 4) uses highly granulated keywords and specific check types like TtlCheck or ScriptCheck to ensure clarity and type safety.

    Check Types

    When registering a check, you can use different types depending on the mechanism:

    • TTL: Uses TtlCheck.
    • Script: Uses ScriptCheck (requires a script path and interval).
    • HTTP: Uses HttpCheck (requires a URL and interval).
    • Docker: Uses Docker-specific parameters including docker_container_id, shell, script, and interval.

    Registration Patterns

    You can register a check directly or as part of a service registration. The most descriptive pattern uses specific check objects passed via the params::check keyword.

    // Registering a standalone check
    agent.registerCheck("check1", TtlCheck{std::chrono::seconds(10)});
    agent.registerCheck("check1", ScriptCheck{"bla-bla.py", std::chrono::seconds(10), params::timeout=std::chrono::seconds(2)}, params::notes="");
    
    // Registering a service with an attached check
    agent.registerService("service1", params::tags={"udp", "service1"}, params::check=TtlCheck{std::chrono::seconds(10)});
    agent.registerService("service1", params::tags={"udp"}, params::check=ScriptCheck{"bla-bla.py", std::chrono::seconds(10), params::timeout=std::chrono::seconds(2)});
  4. Integrate Ppconsul into a CMake project

    master

    You can integrate Ppconsul into your project using two methods: via find_package (if installed on the system) or as a Git submodule.

    Method 1: Using find_package

    If Ppconsul is already built and installed on your system, use standard CMake commands:

    Method 2: Using Git Submodule

    Clone the repository as a submodule and add it as a subdirectory. You can control the build configuration using CMake variables like BUILD_TESTS and BUILD_STATIC_LIB.

    ### Method 1: find_package
    ```cmake
    find_package(ppconsul)
    add_executable(<your_app> ...)
    target_link_libraries(<your_app> ppconsul)

    Method 2: Git Submodule

    git submodule add https://github.com/oliora/ppconsul.git
    set(BUILD_TESTS OFF)
    set(BUILD_STATIC_LIB ON)
    add_subdirectory(ppconsul)
    
    # ... your app definition ...
    target_link_libraries(<your_app> ppconsul)
  5. Run Ppconsul tests

    master

    To run the Ppconsul test suite, you must first have Consul 0.4 or newer installed (0.7+ is recommended).

    1. Start Consul in development mode:

      • For Consul 0.9+: consul agent -dev -datacenter=ppconsul_test -enable-script-checks=true
      • For Consul 0.7/0.8: consul agent -dev -datacenter=ppconsul_test
    2. Execute tests using ctest or make.

    Note on failures: If catalog tests fail on the assertion REQUIRE(index1 == resp1.headers().index());, rerun the tests. This is caused by Consul's internal idempotent writes causing spurious wakeups in blocking queries.

    # Run Consul (0.9+)
    consul agent -dev -datacenter=ppconsul_test -enable-script-checks=true
    
    # Run Tests
    ctest -C Release
    # OR
    make test
  6. Configure Consul connection settings

    master

    You can configure connection timeouts and TLS/SSL settings when constructing the ppconsul::Consul object.

    Timeouts

    • kw::connect_timeout: Time to establish the connection (default: 5s).
    • kw::request_timeout: Time for the request to complete (default: unlimited).

    Note: If using blocking queries, request_timeout must be longer than the block_for interval.

    Consul consul("https://localhost:8080",
                  kw::connect_timeout = std::chrono::milliseconds{15000},
                  kw::request_timeout = std::chrono::milliseconds{5000});

    HTTPS/TLS Configuration

    To connect via HTTPS, provide the certificate, key, and CA info:

    Consul consul("https://localhost:8080",
                  kw::tls::cert="path/to/cert",
                  kw::tls::key="path/to/private/key",
                  kw::tls::ca_info="path/to/ca/cert");
    using namespace ppconsul;
    
    // Timeouts
    Consul consul("https://localhost:8080",
                  kw::connect_timeout = std::chrono::milliseconds{15000},
                  kw::request_timeout = std::chrono::milliseconds{5000});
    
    // TLS
    Consul consul_tls("https://localhost:8080",
                      kw::tls::cert="path/to/cert",
                      kw::tls::key="path/to/private/key",
                      kw::tls::ca_info="path/to/ca/cert");
  7. Handle Transaction (Txn) results and errors

    master

    When performing atomic transactions in Consul, the response contains either successful results or errors.

    Successful Transactions

    Successful transactions return a list of KeyValue objects. In the JSON response, these are nested under a Results array, where each item contains a KV object.

    Failed Transactions

    If a transaction fails, it returns a list of errors. Each error contains:

    • OpIndex: The index of the operation that failed.
    • What: A description of the error.

    Errors are returned in an Errors array in the JSON response.

  8. Register and manage services with the Agent API

    master

    Use the ppconsul::agent::Agent class to manage service registration, deregistration, and health reporting in Consul.

    Service Registration with HTTP Check

    Register a service and associate it with an HTTP health check:

    Agent agent(consul);
    agent.registerService(
        kw::name = "my-service",
        kw::port = 9876,
        kw::tags = {"tcp", "super_server"},
        kw::check = HttpCheck{"http://localhost:80/", std::chrono::seconds(2)}
    );

    Service Registration with TTL Check

    Register a service that relies on a Time-To-Live (TTL) check:

    agent.registerService(
        kw::name = "my-service",
        kw::port = 9876,
        kw::id = "my-service-1",
        kw::check = TtlCheck{std::chrono::seconds(5)}
    );
    
    // Report service status
    agent.servicePass("my-service-1");
    agent.serviceFail("my-service-1", "Disk is full");

    Deregister Service

    agent.deregisterService("my-service");
    #include "ppconsul/agent.h"
    using ppconsul::Consul;
    using namespace ppconsul::agent;
    
    Consul consul;
    Agent agent(consul);
    
    agent.registerService(
        kw::name = "my-service",
        kw::port = 9876,
        kw::tags = {"tcp", "super_server"},
        kw::check = HttpCheck{"http://localhost:80/", std::chrono::seconds(2)}
    );
    
    agent.deregisterService("my-service");
  9. Use Key-Value (KV) storage

    master

    The ppconsul::kv::Kv class provides access to Consul's Key-Value storage.

    Basic KV Operations

    using namespace ppconsul::kv;
    Kv kv(consul);
    
    // Set a value
    kv.set("settings.something", "new-value");
    
    // Get a value (with default if key doesn't exist)
    std::string val = kv.get("settings.something", "default-value");
    
    // Get a value with specific consistency mode
    val = kv.get("settings.something", "default-value", kw::consistency = Consistency::Consistent);
    
    // Erase a key
    kv.erase("settings.something-else");

    Blocking Queries for KV

    To wait for changes to a key, use the item method with kw::block_for. This requires passing the modifyIndex of the current item to wait for subsequent changes.

    // Get key+value+metadata item
    KeyValue item = kv.item("status.last-event-id");
    
    // Wait for the item change for no more than 1 minute:
    item = kv.item("status.last-event-id", kw::block_for = {std::chrono::minutes(1), item.modifyIndex});
    
    if (item) {
        std::cout << item.key << "=" << item.value << "\n";
    }
    #include "ppconsul/kv.h"
    using namespace ppconsul::kv;
    
    Kv kv(consul);
    kv.set("settings.something", "new-value");
    std::string val = kv.get("settings.something", "default-value");
    
    KeyValue item = kv.item("status.last-event-id");
    item = kv.item("status.last-event-id", kw::block_for = {std::chrono::minutes(1), item.modifyIndex});
  10. Determine Raft leader and peers with Status API

    master

    Use the ppconsul::status::Status class to inspect the Raft consensus state of the Consul cluster.

    #include "ppconsul/status.h"
    using namespace ppconsul::status;
    
    Status status(consul);
    
    // Check if a leader is electedool isLeaderElected = status.isLeaderElected();
    
    // Get the actual raft leader
    auto leader = status.leader();
    
    // Get the raft peers
    auto peers = status.peers();
    #include "ppconsul/status.h"
    using namespace ppconsul::status;
    
    Status status(consul);
    bool isLeaderElected = status.isLeaderElected();
    auto leader = status.leader();
    auto peers = status.peers();
  11. Reference of Agent registration parameters

    master

    When using agent.registerCheck or agent.registerService, the following parameter namespaces and keywords are used to configure the registration:

    Common Keywords

    • params::notes: A string containing notes for the check/service.
    • params::id: A unique identifier.
    • params::tags: A collection of tags for a service.
    • params::check: Used in registerService to attach a specific check type (e.g., TtlCheck, ScriptCheck).

    Check-Specific Parameters

    • TTL Checks: TtlCheck{duration}
    • Script Checks: ScriptCheck{script_path, interval, params::timeout}
    • HTTP Checks: HttpCheck{url, interval, params::timeout}
  12. Configure Ppconsul test environment variables

    master

    Use the following environment variables to configure the network and datacenter settings for the test suite:

    | Name | Default Value | Description |
    |------|---------------|-------------|
    | `PPCONSUL_TEST_ADDR` | "127.0.0.1:8500" | The Consul network address |
    | `PPCONSUL_TEST_DC` | "ppconsul_test" | The Consul datacenter |
    | `PPCONSUL_TEST_LEADER_ADDR` | "127.0.0.1:8300" | The Consul raft leader address |
    
    **Warning**: Never set `PPCONSUL_TEST_DC` to a datacenter you cannot discard, as tests will modify it extensively.