Skip to content

Commit 5f858ac

Browse files
Enable OlpClient caching ApiLookupClient (#1107)
This enables SDK to route user requests to the same OlpClient instances. Needed to implement a proper request merging. Another benefit for users is fewer cache lookups. The client is cached for a max-age time provided by the server or one hour. When the URL is resolved from the cache, we assume it will expire in five minutes. Move DefaultLookupEndpointProvider source code to the cpp file. Resolves: OLPEDGE-2230 Signed-off-by: Mykhailo Kuchma <[email protected]>
1 parent a4e3776 commit 5f858ac

File tree

9 files changed

+369
-111
lines changed

9 files changed

+369
-111
lines changed

olp-cpp-sdk-core/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,7 @@ set(OLP_SDK_CLIENT_SOURCES
238238
./src/client/ApiLookupClientImpl.cpp
239239
./src/client/ApiLookupClientImpl.h
240240
./src/client/CancellationToken.cpp
241+
./src/client/DefaultLookupEndpointProvider.cpp
241242
./src/client/HRN.cpp
242243
./src/client/OlpClient.cpp
243244
./src/client/OlpClientFactory.cpp

olp-cpp-sdk-core/include/olp/core/client/DefaultLookupEndpointProvider.h

Lines changed: 1 addition & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -33,26 +33,7 @@ namespace client {
3333
*/
3434
struct CORE_API DefaultLookupEndpointProvider {
3535
public:
36-
std::string operator()(const std::string& partition) {
37-
constexpr struct {
38-
const char* partition;
39-
const char* url;
40-
} kDatastoreServerUrl[4] = {
41-
{"here", "https://api-lookup.data.api.platform.here.com/lookup/v1"},
42-
{"here-dev",
43-
"https://api-lookup.data.api.platform.in.here.com/lookup/v1"},
44-
{"here-cn",
45-
"https://api-lookup.data.api.platform.hereolp.cn/lookup/v1"},
46-
{"here-cn-dev",
47-
"https://api-lookup.data.api.platform.in.hereolp.cn/lookup/v1"}};
48-
49-
for (const auto& it : kDatastoreServerUrl) {
50-
if (partition == it.partition)
51-
return it.url;
52-
}
53-
54-
return std::string();
55-
}
36+
std::string operator()(const std::string& partition);
5637
};
5738

5839
} // namespace client

olp-cpp-sdk-core/include/olp/core/client/OlpClient.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ class CORE_API OlpClient {
6262
*
6363
* @return The base URL.
6464
*/
65-
const std::string& GetBaseUrl() const;
65+
std::string GetBaseUrl() const;
6666

6767
/**
6868
* @brief Gets the default headers that are added to each request.

olp-cpp-sdk-core/src/client/ApiLookupClientImpl.cpp

Lines changed: 134 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
#include "ApiLookupClientImpl.h"
2121

22+
#include <olp/core/client/HRN.h>
2223
#include <olp/core/logging/Log.h>
2324
#include "client/api/PlatformApi.h"
2425
#include "client/api/ResourcesApi.h"
@@ -29,6 +30,8 @@ namespace client {
2930

3031
namespace {
3132
constexpr auto kLogTag = "ApiLookupClientImpl";
33+
constexpr time_t kLookupApiDefaultExpiryTime = 3600;
34+
constexpr time_t kLookupApiShortExpiryTime = 300;
3235

3336
std::string FindApi(const Apis& apis, const std::string& service,
3437
const std::string& version) {
@@ -42,6 +45,14 @@ std::string FindApi(const Apis& apis, const std::string& service,
4245
return it->GetBaseUrl();
4346
}
4447

48+
OlpClient CreateClient(const std::string& base_url,
49+
const OlpClientSettings& settings) {
50+
OlpClient client;
51+
client.SetBaseUrl(base_url);
52+
client.SetSettings(settings);
53+
return client;
54+
}
55+
4556
olp::client::OlpClient GetStaticUrl(
4657
const olp::client::HRN& catalog,
4758
const olp::client::OlpClientSettings& settings) {
@@ -50,25 +61,37 @@ olp::client::OlpClient GetStaticUrl(
5061
auto url = provider(catalog);
5162
if (!url.empty()) {
5263
url += "/catalogs/" + catalog.ToCatalogHRNString();
53-
OlpClient result_client;
54-
result_client.SetBaseUrl(url);
55-
result_client.SetSettings(settings);
56-
return result_client;
64+
return CreateClient(url, settings);
5765
}
5866
}
5967

6068
return {};
6169
}
6270

71+
ApiLookupClient::LookupApiResponse NotFoundInCacheError() {
72+
return ApiError(client::ErrorCode::NotFound,
73+
"CacheOnly: resource not found in cache");
74+
}
75+
76+
ApiLookupClient::LookupApiResponse ServiceNotAvailable() {
77+
return ApiError(client::ErrorCode::ServiceUnavailable,
78+
"Service/Version not available for given HRN");
79+
}
80+
81+
std::string ClientCacheKey(const std::string& service,
82+
const std::string& service_version) {
83+
return service + service_version;
84+
}
6385
} // namespace
6486

6587
ApiLookupClientImpl::ApiLookupClientImpl(const HRN& catalog,
6688
const OlpClientSettings& settings)
67-
: catalog_(catalog), settings_(settings) {
89+
: catalog_(catalog),
90+
catalog_string_(catalog_.ToString()),
91+
settings_(settings) {
6892
auto provider = settings_.api_lookup_settings.lookup_endpoint_provider;
6993
const auto& base_url = provider(catalog_.GetPartition());
70-
lookup_client_.SetBaseUrl(base_url);
71-
lookup_client_.SetSettings(settings_);
94+
lookup_client_ = CreateClient(base_url, settings_);
7295
}
7396

7497
ApiLookupClient::LookupApiResponse ApiLookupClientImpl::LookupApi(
@@ -79,68 +102,54 @@ ApiLookupClient::LookupApiResponse ApiLookupClientImpl::LookupApi(
79102
return result_client;
80103
}
81104

82-
repository::ApiCacheRepository repository(catalog_, settings_.cache);
83-
const auto hrn = catalog_.ToCatalogHRNString();
84-
85105
if (options != OnlineOnly && options != CacheWithUpdate) {
86-
auto url = repository.Get(service, service_version);
87-
if (url) {
88-
OLP_SDK_LOG_DEBUG_F(kLogTag, "LookupApi(%s/%s) found in cache, hrn='%s'",
89-
service.c_str(), service_version.c_str(),
90-
hrn.c_str());
91-
result_client.SetBaseUrl(*url);
92-
result_client.SetSettings(settings_);
93-
return result_client;
106+
auto client = GetCachedClient(service, service_version);
107+
if (client) {
108+
return *client;
94109
} else if (options == CacheOnly) {
95-
return {{client::ErrorCode::NotFound,
96-
"CacheOnly: resource not found in cache"}};
110+
return NotFoundInCacheError();
97111
}
98112
}
99113

100-
OLP_SDK_LOG_DEBUG_F(kLogTag,
101-
"LookupApi(%s/%s) cache miss, requesting, hrn='%s'",
102-
service.c_str(), service_version.c_str(), hrn.c_str());
103-
104114
PlatformApi::ApisResponse api_response;
105115
if (service == "config") {
106116
api_response = PlatformApi::GetApis(lookup_client_, context);
107117
} else {
108-
api_response = ResourcesApi::GetApis(lookup_client_, hrn, context);
118+
api_response =
119+
ResourcesApi::GetApis(lookup_client_, catalog_string_, context);
109120
}
110121

111122
if (!api_response.IsSuccessful()) {
112-
OLP_SDK_LOG_WARNING_F(kLogTag,
113-
"LookupApi(%s/%s) unsuccessful, hrn='%s', error='%s'",
114-
service.c_str(), service_version.c_str(), hrn.c_str(),
115-
api_response.GetError().GetMessage().c_str());
123+
OLP_SDK_LOG_WARNING_F(
124+
kLogTag, "LookupApi(%s/%s) unsuccessful, hrn='%s', error='%s'",
125+
service.c_str(), service_version.c_str(), catalog_string_.c_str(),
126+
api_response.GetError().GetMessage().c_str());
116127
return api_response.GetError();
117128
}
118129

119130
const auto& api_result = api_response.GetResult();
120131
if (options != OnlineOnly && options != CacheWithUpdate) {
121-
for (const auto& service_api : api_result.first) {
122-
repository.Put(service_api.GetApi(), service_api.GetVersion(),
123-
service_api.GetBaseUrl(), api_result.second);
124-
}
132+
PutToDiskCache(api_result);
125133
}
126134

127135
auto url = FindApi(api_result.first, service, service_version);
128136
if (url.empty()) {
129137
OLP_SDK_LOG_WARNING_F(
130138
kLogTag, "LookupApi(%s/%s) service not found, hrn='%s'",
131-
service.c_str(), service_version.c_str(), hrn.c_str());
139+
service.c_str(), service_version.c_str(), catalog_string_.c_str());
132140

133-
return {{client::ErrorCode::ServiceUnavailable,
134-
"Service/Version not available for given HRN"}};
141+
return ServiceNotAvailable();
135142
}
136143

137-
OLP_SDK_LOG_DEBUG_F(
138-
kLogTag, "LookupApi(%s/%s) found, hrn='%s', service_url='%s'",
139-
service.c_str(), service_version.c_str(), hrn.c_str(), url.c_str());
144+
OLP_SDK_LOG_DEBUG_F(kLogTag,
145+
"LookupApi(%s/%s) found, hrn='%s', service_url='%s'",
146+
service.c_str(), service_version.c_str(),
147+
catalog_string_.c_str(), url.c_str());
140148

141-
result_client.SetBaseUrl(url);
142-
result_client.SetSettings(settings_);
143-
return result_client;
149+
const auto expiration = api_result.second;
150+
151+
return CreateAndCacheClient(url, ClientCacheKey(service, service_version),
152+
expiration);
144153
}
145154

146155
CancellationToken ApiLookupClientImpl::LookupApi(
@@ -152,76 +161,124 @@ CancellationToken ApiLookupClientImpl::LookupApi(
152161
return CancellationToken();
153162
}
154163

155-
repository::ApiCacheRepository repository(catalog_, settings_.cache);
156-
const auto hrn = catalog_.ToCatalogHRNString();
157-
158164
if (options != OnlineOnly && options != CacheWithUpdate) {
159-
auto url = repository.Get(service, service_version);
160-
if (url) {
161-
OLP_SDK_LOG_DEBUG_F(kLogTag, "LookupApi(%s/%s) found in cache, hrn='%s'",
162-
service.c_str(), service_version.c_str(),
163-
hrn.c_str());
164-
result_client.SetBaseUrl(*url);
165-
result_client.SetSettings(settings_);
166-
callback(result_client);
165+
auto client = GetCachedClient(service, service_version);
166+
if (client) {
167+
callback(*client);
167168
return CancellationToken();
168169
} else if (options == CacheOnly) {
169-
ApiLookupClient::LookupApiResponse response{
170-
{client::ErrorCode::NotFound,
171-
"CacheOnly: resource not found in cache"}};
172-
callback(response);
170+
callback(NotFoundInCacheError());
173171
return CancellationToken();
174172
}
175173
}
176174

177-
OLP_SDK_LOG_DEBUG_F(kLogTag,
178-
"LookupApi(%s/%s) cache miss, requesting, hrn='%s'",
179-
service.c_str(), service_version.c_str(), hrn.c_str());
180-
181175
PlatformApi::ApisCallback lookup_callback =
182176
[=](PlatformApi::ApisResponse response) mutable -> void {
183177
if (!response.IsSuccessful()) {
184178
OLP_SDK_LOG_WARNING_F(
185179
kLogTag, "LookupApi(%s/%s) unsuccessful, hrn='%s', error='%s'",
186-
service.c_str(), service_version.c_str(), hrn.c_str(),
180+
service.c_str(), service_version.c_str(), catalog_string_.c_str(),
187181
response.GetError().GetMessage().c_str());
188182
callback(response.GetError());
189183
return;
190184
}
191185

192186
const auto& api_result = response.GetResult();
193187
if (options != OnlineOnly && options != CacheWithUpdate) {
194-
for (const auto& service_api : api_result.first) {
195-
repository.Put(service_api.GetApi(), service_api.GetVersion(),
196-
service_api.GetBaseUrl(), api_result.second);
197-
}
188+
PutToDiskCache(api_result);
198189
}
199190

200191
const auto url = FindApi(api_result.first, service, service_version);
201192
if (url.empty()) {
202193
OLP_SDK_LOG_WARNING_F(
203194
kLogTag, "LookupApi(%s/%s) service not found, hrn='%s'",
204-
service.c_str(), service_version.c_str(), hrn.c_str());
195+
service.c_str(), service_version.c_str(), catalog_string_.c_str());
205196

206-
callback({{client::ErrorCode::ServiceUnavailable,
207-
"Service/Version not available for given HRN"}});
197+
callback(ServiceNotAvailable());
208198
return;
209199
}
210200

211-
OLP_SDK_LOG_DEBUG_F(
212-
kLogTag, "LookupApi(%s/%s) found, hrn='%s', service_url='%s'",
213-
service.c_str(), service_version.c_str(), hrn.c_str(), url.c_str());
201+
OLP_SDK_LOG_DEBUG_F(kLogTag,
202+
"LookupApi(%s/%s) found, hrn='%s', service_url='%s'",
203+
service.c_str(), service_version.c_str(),
204+
catalog_string_.c_str(), url.c_str());
214205

215-
OlpClient result_client;
216-
result_client.SetBaseUrl(url);
217-
result_client.SetSettings(settings_);
218-
callback(result_client);
206+
callback(CreateAndCacheClient(url, ClientCacheKey(service, service_version),
207+
api_result.second));
219208
};
220209

221210
if (service == "config") {
222211
return PlatformApi::GetApis(lookup_client_, lookup_callback);
223212
}
224-
return ResourcesApi::GetApis(lookup_client_, hrn, lookup_callback);
213+
return ResourcesApi::GetApis(lookup_client_, catalog_string_,
214+
lookup_callback);
215+
}
216+
217+
OlpClient ApiLookupClientImpl::CreateAndCacheClient(
218+
const std::string& base_url, const std::string& cache_key,
219+
boost::optional<time_t> expiration) {
220+
std::lock_guard<std::mutex> lock(cached_clients_mutex_);
221+
ClientWithExpiration& client_with_expiration = cached_clients_[cache_key];
222+
223+
const auto current_base_url = client_with_expiration.client.GetBaseUrl();
224+
if (current_base_url.empty()) {
225+
client_with_expiration.client.SetSettings(settings_);
226+
}
227+
if (current_base_url != base_url) {
228+
client_with_expiration.client.SetBaseUrl(base_url);
229+
}
230+
231+
client_with_expiration.expire_at =
232+
std::chrono::steady_clock::now() +
233+
std::chrono::seconds(expiration.value_or(kLookupApiDefaultExpiryTime));
234+
return client_with_expiration.client;
235+
}
236+
237+
boost::optional<OlpClient> ApiLookupClientImpl::GetCachedClient(
238+
const std::string& service, const std::string& service_version) {
239+
const std::string key = ClientCacheKey(service, service_version);
240+
241+
{
242+
std::lock_guard<std::mutex> lock(cached_clients_mutex_);
243+
const auto client_it = cached_clients_.find(key);
244+
if (client_it != cached_clients_.end()) {
245+
const ClientWithExpiration& client_with_expirtation = client_it->second;
246+
if (client_with_expirtation.expire_at >
247+
std::chrono::steady_clock::now()) {
248+
OLP_SDK_LOG_DEBUG_F(
249+
kLogTag, "LookupApi(%s/%s) found in client cache, hrn='%s'",
250+
service.c_str(), service_version.c_str(), catalog_string_.c_str());
251+
return client_with_expirtation.client;
252+
}
253+
}
254+
}
255+
256+
repository::ApiCacheRepository cache_repository_(catalog_, settings_.cache);
257+
const auto base_url = cache_repository_.Get(service, service_version);
258+
if (base_url) {
259+
OLP_SDK_LOG_DEBUG_F(
260+
kLogTag, "LookupApi(%s/%s) found in disk cache, hrn='%s'",
261+
service.c_str(), service_version.c_str(), catalog_string_.c_str());
262+
} else {
263+
OLP_SDK_LOG_DEBUG_F(
264+
kLogTag, "LookupApi(%s/%s) cache miss in disk cache, hrn='%s'",
265+
service.c_str(), service_version.c_str(), catalog_string_.c_str());
266+
return boost::none;
267+
}
268+
269+
// When the service url is retrieved from disk cache we assume it is valid for
270+
// five minutes, after five minutes we repeat. This is because we cannot
271+
// retrieve the exact expiration from cache so to not use a possibly expired
272+
// URL for 1h we use it for 5min and check again.
273+
return CreateAndCacheClient(*base_url, key, kLookupApiShortExpiryTime);
274+
}
275+
276+
void ApiLookupClientImpl::PutToDiskCache(const ApisResult& available_services) {
277+
repository::ApiCacheRepository cache_repository_(catalog_, settings_.cache);
278+
for (const auto& service_api : available_services.first) {
279+
cache_repository_.Put(service_api.GetApi(), service_api.GetVersion(),
280+
service_api.GetBaseUrl(), available_services.second);
281+
}
225282
}
226283

227284
} // namespace client

0 commit comments

Comments
 (0)