Skip to content

Commit

Permalink
Initial Etcd v3 implementation, refactor v2 as well
Browse files Browse the repository at this point in the history
  • Loading branch information
Oipo committed Feb 5, 2024
1 parent 6790b47 commit 5a9c164
Show file tree
Hide file tree
Showing 13 changed files with 1,252 additions and 203 deletions.
3 changes: 2 additions & 1 deletion examples/etcd_example/UsingEtcdService.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#pragma once

#include <ichor/services/logging/Logger.h>
#include <ichor/services/etcd/IEtcd.h>
#include <ichor/services/etcd/IEtcdV2.h>
#include <ichor/dependency_management/IService.h>
#include <ichor/event_queues/IEventQueue.h>
#include <ichor/events/RunFunctionEvent.h>

using namespace Ichor;
using namespace Ichor::Etcd::v2;

class UsingEtcdV2Service final {
public:
Expand Down
2 changes: 1 addition & 1 deletion examples/etcd_example/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ int main(int argc, char *argv[]) {
dm.createServiceManager<SpdlogSharedService, ISpdlogSharedService>();
#endif
dm.createServiceManager<LoggerFactory<LOGGER_TYPE>, ILoggerFactory>(Properties{{"DefaultLogLevel", Ichor::make_any<LogLevel>(LogLevel::LOG_INFO)}});
dm.createServiceManager<EtcdV2Service, IEtcd>(Properties{{"Address", Ichor::make_any<std::string>("127.0.0.1")}, {"Port", Ichor::make_any<uint16_t>(static_cast<uint16_t>(2379))}, {"TimeoutMs", Ichor::make_any<uint64_t>(1'000ul)}});
dm.createServiceManager<Etcd::v2::EtcdService, Etcd::v2::IEtcd>(Properties{{"Address", Ichor::make_any<std::string>("127.0.0.1")}, {"Port", Ichor::make_any<uint16_t>(static_cast<uint16_t>(2379))}, {"TimeoutMs", Ichor::make_any<uint64_t>(1'000ul)}});
dm.createServiceManager<UsingEtcdV2Service>();
dm.createServiceManager<AsioContextService, IAsioContextService>();
dm.createServiceManager<ClientFactory<HttpConnectionService, IHttpConnectionService>, IClientFactory>();
Expand Down
12 changes: 6 additions & 6 deletions include/ichor/services/etcd/EtcdV2Service.h
Original file line number Diff line number Diff line change
@@ -1,31 +1,31 @@
#pragma once

#include <ichor/services/etcd/IEtcd.h>
#include <ichor/services/etcd/IEtcdV2.h>
#include <ichor/services/logging/Logger.h>
#include <ichor/coroutines/AsyncManualResetEvent.h>
#include <ichor/dependency_management/AdvancedService.h>
#include <ichor/services/network/http/IHttpConnectionService.h>
#include <ichor/services/network/IClientFactory.h>
#include <stack>

namespace Ichor {
namespace Ichor::Etcd::v2 {

struct ConnRequest final {
IHttpConnectionService *conn{};
AsyncManualResetEvent event{};
};

/**
* Service for the redis protocol using the v2 REST API. Requires an IHttpConnectionService factory and a logger. See https://etcd.io/docs/v2.3/api/ for a detailed look at the etcd v2 API.
* Service for the etcd protocol using the v2 REST API. Requires an IHttpConnectionService factory and a logger. See https://etcd.io/docs/v2.3/api/ for a detailed look at the etcd v2 API.
*
* Properties:
* - "Address" - What address to connect to (required)
* - "Port" - What port to connect to (required)
*/
class EtcdV2Service final : public IEtcd, public AdvancedService<EtcdV2Service> {
class EtcdService final : public IEtcd, public AdvancedService<EtcdService> {
public:
EtcdV2Service(DependencyRegister &reg, Properties props);
~EtcdV2Service() final = default;
EtcdService(DependencyRegister &reg, Properties props);
~EtcdService() final = default;

[[nodiscard]] Task<tl::expected<EtcdReply, EtcdError>> put(std::string_view key, std::string_view value, tl::optional<std::string_view> previous_value, tl::optional<uint64_t> previous_index, tl::optional<bool> previous_exists, tl::optional<uint64_t> ttl_second, bool refresh, bool dir, bool in_order) final;
[[nodiscard]] Task<tl::expected<EtcdReply, EtcdError>> put(std::string_view key, std::string_view value, tl::optional<uint64_t> ttl_second, bool refresh) final;
Expand Down
63 changes: 63 additions & 0 deletions include/ichor/services/etcd/EtcdV3Service.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
#pragma once

#include <ichor/services/etcd/IEtcdV3.h>
#include <ichor/services/logging/Logger.h>
#include <ichor/coroutines/AsyncManualResetEvent.h>
#include <ichor/dependency_management/AdvancedService.h>
#include <ichor/services/network/http/IHttpConnectionService.h>
#include <ichor/services/network/IClientFactory.h>
#include <stack>

namespace Ichor::Etcd::v3 {

struct ConnRequest final {
IHttpConnectionService *conn{};
AsyncManualResetEvent event{};
};

/**
* Service for the etcd protocol using the v3 REST API. Requires an IHttpConnectionService factory and a logger. See https://etcd.io/docs/v2.3/api/ for a detailed look at the etcd v2 API.
*
* Properties:
* - "Address" - What address to connect to (required)
* - "Port" - What port to connect to (required)
*/
class EtcdService final : public IEtcd, public AdvancedService<EtcdService> {
public:
EtcdService(DependencyRegister &reg, Properties props);
~EtcdService() final = default;

[[nodiscard]] Task<tl::expected<EtcdPutResponse, EtcdError>> put(EtcdPutRequest const &req) final;
[[nodiscard]] Task<tl::expected<EtcdRangeResponse, EtcdError>> range(EtcdRangeRequest const &req) final;
[[nodiscard]] Task<tl::expected<EtcdDeleteRangeResponse, EtcdError>> deleteRange(EtcdDeleteRangeRequest const &req) final;
[[nodiscard]] Task<tl::expected<EtcdVersionReply, EtcdError>> version() final;
[[nodiscard]] Version getDetectedVersion() const final;
[[nodiscard]] Task<tl::expected<bool, EtcdError>> health() final;
void setAuthentication(std::string_view user, std::string_view pass) final;
void clearAuthentication() final;
[[nodiscard]] tl::optional<std::string> getAuthenticationUser() const final;

private:
Task<tl::expected<void, Ichor::StartError>> start() final;
Task<void> stop() final;

void addDependencyInstance(ILogger &logger, IService &isvc);
void removeDependencyInstance(ILogger &logger, IService &isvc);

void addDependencyInstance(IHttpConnectionService &conn, IService &isvc);
void removeDependencyInstance(IHttpConnectionService &conn, IService &isvc);

void addDependencyInstance(IClientFactory &conn, IService &isvc);
void removeDependencyInstance(IClientFactory &conn, IService &isvc);

friend DependencyRegister;

ILogger *_logger{};
IHttpConnectionService* _mainConn{};
IClientFactory *_clientFactory{};
std::stack<ConnRequest> _connRequests{};
tl::optional<std::string> _auth;
Version _detectedVersion{};
std::string_view _versionSpecificUrl{"/v3"};
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#include <tl/expected.h>
#include <fmt/core.h>

namespace Ichor {
namespace Ichor::Etcd::v2 {
enum class EtcdError : uint_fast16_t {
HTTP_RESPONSE_ERROR,
JSON_PARSE_ERROR,
Expand Down Expand Up @@ -394,41 +394,41 @@ namespace Ichor {
}

template <>
struct fmt::formatter<Ichor::EtcdError>
struct fmt::formatter<Ichor::Etcd::v2::EtcdError>
{
constexpr auto parse(format_parse_context& ctx)
{
return ctx.end();
}

template <typename FormatContext>
auto format(const Ichor::EtcdError& state, FormatContext& ctx)
auto format(const Ichor::Etcd::v2::EtcdError& state, FormatContext& ctx)
{
switch(state)
{
case Ichor::EtcdError::HTTP_RESPONSE_ERROR:
case Ichor::Etcd::v2::EtcdError::HTTP_RESPONSE_ERROR:
return fmt::format_to(ctx.out(), "HTTP_RESPONSE_ERROR");
case Ichor::EtcdError::JSON_PARSE_ERROR:
case Ichor::Etcd::v2::EtcdError::JSON_PARSE_ERROR:
return fmt::format_to(ctx.out(), "JSON_PARSE_ERROR");
case Ichor::EtcdError::TIMEOUT:
case Ichor::Etcd::v2::EtcdError::TIMEOUT:
return fmt::format_to(ctx.out(), "TIMEOUT");
case Ichor::EtcdError::CONNECTION_CLOSED_PREMATURELY_TRY_AGAIN:
case Ichor::Etcd::v2::EtcdError::CONNECTION_CLOSED_PREMATURELY_TRY_AGAIN:
return fmt::format_to(ctx.out(), "CONNECTION_CLOSED_PREMATURELY_TRY_AGAIN");
case Ichor::EtcdError::ROOT_USER_NOT_YET_CREATED:
case Ichor::Etcd::v2::EtcdError::ROOT_USER_NOT_YET_CREATED:
return fmt::format_to(ctx.out(), "ROOT_USER_NOT_YET_CREATED");
case Ichor::EtcdError::REMOVING_ROOT_NOT_ALLOWED_WITH_AUTH_ENABLED:
case Ichor::Etcd::v2::EtcdError::REMOVING_ROOT_NOT_ALLOWED_WITH_AUTH_ENABLED:
return fmt::format_to(ctx.out(), "REMOVING_ROOT_NOT_ALLOWED_WITH_AUTH_ENABLED");
case Ichor::EtcdError::NO_AUTHENTICATION_SET:
case Ichor::Etcd::v2::EtcdError::NO_AUTHENTICATION_SET:
return fmt::format_to(ctx.out(), "NO_AUTHENTICATION_SET");
case Ichor::EtcdError::UNAUTHORIZED:
case Ichor::Etcd::v2::EtcdError::UNAUTHORIZED:
return fmt::format_to(ctx.out(), "UNAUTHORIZED");
case Ichor::EtcdError::NOT_FOUND:
case Ichor::Etcd::v2::EtcdError::NOT_FOUND:
return fmt::format_to(ctx.out(), "NOT_FOUND");
case Ichor::EtcdError::DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT:
case Ichor::Etcd::v2::EtcdError::DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT:
return fmt::format_to(ctx.out(), "DUPLICATE_PERMISSION_OR_REVOKING_NON_EXISTENT");
case Ichor::EtcdError::CANNOT_DELETE_ROOT_WHILE_AUTH_IS_ENABLED:
case Ichor::Etcd::v2::EtcdError::CANNOT_DELETE_ROOT_WHILE_AUTH_IS_ENABLED:
return fmt::format_to(ctx.out(), "CANNOT_DELETE_ROOT_WHILE_AUTH_IS_ENABLED");
case Ichor::EtcdError::QUITTING:
case Ichor::Etcd::v2::EtcdError::QUITTING:
return fmt::format_to(ctx.out(), "QUITTING");
default:
return fmt::format_to(ctx.out(), "error, please file a bug in Ichor");
Expand All @@ -437,49 +437,49 @@ struct fmt::formatter<Ichor::EtcdError>
};

template <>
struct fmt::formatter<Ichor::EtcdErrorCodes>
struct fmt::formatter<Ichor::Etcd::v2::EtcdErrorCodes>
{
constexpr auto parse(format_parse_context& ctx)
{
return ctx.end();
}

template <typename FormatContext>
auto format(const Ichor::EtcdErrorCodes& state, FormatContext& ctx)
auto format(const Ichor::Etcd::v2::EtcdErrorCodes& state, FormatContext& ctx)
{
switch(state)
{
case Ichor::EtcdErrorCodes::KEY_DOES_NOT_EXIST:
case Ichor::Etcd::v2::EtcdErrorCodes::KEY_DOES_NOT_EXIST:
return fmt::format_to(ctx.out(), "KEY_DOES_NOT_EXIST");
case Ichor::EtcdErrorCodes::COMPARE_AND_SWAP_FAILED:
case Ichor::Etcd::v2::EtcdErrorCodes::COMPARE_AND_SWAP_FAILED:
return fmt::format_to(ctx.out(), "COMPARE_AND_SWAP_FAILED");
case Ichor::EtcdErrorCodes::NOT_A_FILE:
case Ichor::Etcd::v2::EtcdErrorCodes::NOT_A_FILE:
return fmt::format_to(ctx.out(), "NOT_A_FILE");
case Ichor::EtcdErrorCodes::NOT_A_DIRECTORY:
case Ichor::Etcd::v2::EtcdErrorCodes::NOT_A_DIRECTORY:
return fmt::format_to(ctx.out(), "NOT_A_DIRECTORY");
case Ichor::EtcdErrorCodes::KEY_ALREADY_EXISTS:
case Ichor::Etcd::v2::EtcdErrorCodes::KEY_ALREADY_EXISTS:
return fmt::format_to(ctx.out(), "KEY_ALREADY_EXISTS");
case Ichor::EtcdErrorCodes::ROOT_IS_READ_ONLY:
case Ichor::Etcd::v2::EtcdErrorCodes::ROOT_IS_READ_ONLY:
return fmt::format_to(ctx.out(), "ROOT_IS_READ_ONLY");
case Ichor::EtcdErrorCodes::DIRECTORY_NOT_EMPTY:
case Ichor::Etcd::v2::EtcdErrorCodes::DIRECTORY_NOT_EMPTY:
return fmt::format_to(ctx.out(), "DIRECTORY_NOT_EMPTY");
case Ichor::EtcdErrorCodes::PREVIOUS_VALUE_REQUIRED:
case Ichor::Etcd::v2::EtcdErrorCodes::PREVIOUS_VALUE_REQUIRED:
return fmt::format_to(ctx.out(), "PREVIOUS_VALUE_REQUIRED");
case Ichor::EtcdErrorCodes::TTL_IS_NOT_A_NUMBER:
case Ichor::Etcd::v2::EtcdErrorCodes::TTL_IS_NOT_A_NUMBER:
return fmt::format_to(ctx.out(), "TTL_IS_NOT_A_NUMBER");
case Ichor::EtcdErrorCodes::INDEX_IS_NOT_A_NUMBER:
case Ichor::Etcd::v2::EtcdErrorCodes::INDEX_IS_NOT_A_NUMBER:
return fmt::format_to(ctx.out(), "INDEX_IS_NOT_A_NUMBER");
case Ichor::EtcdErrorCodes::INVALID_FIELD:
case Ichor::Etcd::v2::EtcdErrorCodes::INVALID_FIELD:
return fmt::format_to(ctx.out(), "INVALID_FIELD");
case Ichor::EtcdErrorCodes::INVALID_FORM:
case Ichor::Etcd::v2::EtcdErrorCodes::INVALID_FORM:
return fmt::format_to(ctx.out(), "INVALID_FORM");
case Ichor::EtcdErrorCodes::RAFT_INTERNAL_ERROR:
case Ichor::Etcd::v2::EtcdErrorCodes::RAFT_INTERNAL_ERROR:
return fmt::format_to(ctx.out(), "RAFT_INTERNAL_ERROR");
case Ichor::EtcdErrorCodes::LEADER_ELECTRION_ERROR:
case Ichor::Etcd::v2::EtcdErrorCodes::LEADER_ELECTRION_ERROR:
return fmt::format_to(ctx.out(), "LEADER_ELECTRION_ERROR");
case Ichor::EtcdErrorCodes::WATCHER_CLEARED_DUE_TO_RECOVERY:
case Ichor::Etcd::v2::EtcdErrorCodes::WATCHER_CLEARED_DUE_TO_RECOVERY:
return fmt::format_to(ctx.out(), "WATCHER_CLEARED_DUE_TO_RECOVERY");
case Ichor::EtcdErrorCodes::EVENT_INDEX_OUTDATED_AND_CLEARED:
case Ichor::Etcd::v2::EtcdErrorCodes::EVENT_INDEX_OUTDATED_AND_CLEARED:
return fmt::format_to(ctx.out(), "EVENT_INDEX_OUTDATED_AND_CLEARED");
default:
return fmt::format_to(ctx.out(), "error, please file a bug in Ichor");
Expand Down
Loading

0 comments on commit 5a9c164

Please sign in to comment.