Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
3ff732d
Add HazelcastHttpCache
enozcan Mar 26, 2020
c39db10
Secure body key uniqueness and set defaults.
enozcan Mar 27, 2020
f359a8e
Use Envoy's random for versioning.
enozcan Mar 30, 2020
d6f2568
Handle all connection failures. Use parameterized test for common ones.
enozcan Apr 1, 2020
8630701
Add local cache to use during tests.
enozcan Apr 3, 2020
2232761
Add plugin doc and fix spell check.
enozcan Apr 5, 2020
8ca71fc
Fix code format & clean up.
enozcan Apr 7, 2020
a3a1af4
Fix clang_tidy errors.
enozcan Apr 15, 2020
31c0b3c
Apply upstream changes
enozcan Apr 17, 2020
cabb409
Disable PartitionAware for body entries.
enozcan May 8, 2020
fc5c3ee
Merge branch 'master' into hazelcast_http_cache
enozcan May 8, 2020
7334709
Fix format and revert LookupRequest changes back.
enozcan May 8, 2020
839c418
Add serialization tests.
enozcan May 11, 2020
2576ebc
Move remote calls to cache accessors.
enozcan May 20, 2020
fde9e19
Change key serialization from UTF to byte array.
enozcan May 20, 2020
104f8e0
Update plugin doc.
enozcan May 20, 2020
1f152b5
Fix asan errors other than bitswap.
enozcan May 21, 2020
3ebb754
Add header eviction listener.
enozcan May 23, 2020
7665fa0
Allow overriding cached responses.
enozcan May 28, 2020
c2801f4
Add Bits.h patch for ubsan fix.
enozcan May 28, 2020
9172525
Fix clang warns.
enozcan May 29, 2020
bb9ce1e
Cover remote calls over offline client.
enozcan May 29, 2020
ce6651b
tsan fix
enozcan May 29, 2020
8e3129a
Address reviews.
enozcan Jun 10, 2020
570e362
Use max_body_bytes from CacheConfig.
enozcan Jun 6, 2020
66a1f01
Use core.SocketAddress for member address.
enozcan Jun 10, 2020
0acc4d1
Fix format.
enozcan Jun 11, 2020
d3b0cf5
Fix Windows lib name.
enozcan Jun 11, 2020
d40185f
Merge branch 'master' into hazelcast_http_cache
enozcan Jun 11, 2020
eb50942
Fix format.
enozcan Jun 11, 2020
7413c54
Remove accessor cast and move logs to accessor.
enozcan Jun 12, 2020
a1167a1
Pass accessor via cache::start
enozcan Jun 13, 2020
639bfbb
Revert back to unit64 max body bytes.
enozcan Jun 13, 2020
ab2f3fd
Add tests.
enozcan Jun 13, 2020
49ca85b
Add Windows build defines.
enozcan Jun 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions bazel/foreign_cc/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,19 @@ envoy_cmake_external(
}),
)

envoy_cmake_external(
name = "hazelcast_cpp_client",
cache_entries = {
"HZ_LIB_TYPE": "STATIC",
"CMAKE_BUILD_TYPE": "RELEASE",
},
lib_source = "@com_github_hazelcast_cpp_client//:all",
static_libraries = select({
"//bazel:windows_x86_64": ["libHazelcastClient3.12.1_64.lib"],
"//conditions:default": ["libHazelcastClient3.12.1_64.a"],
}),
)

envoy_cmake_external(
name = "nghttp2",
cache_entries = {
Expand Down
12 changes: 12 additions & 0 deletions bazel/foreign_cc/hazelcast_cpp_client.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--- CMakeLists.txt 2020-02-27 11:35:35.000000000 +0300
+++ CMakeLists.txt 2020-02-27 11:37:15.000000000 +0300
@@ -279,3 +279,9 @@
ADD_SUBDIRECTORY(examples)
message(STATUS "Configured to build the examples.")
ENDIF(HZ_BUILD_EXAMPLES)
+INSTALL(TARGETS ${HZ_LIB_NAME} DESTINATION lib)
+INSTALL(DIRECTORY ${PROJECT_SOURCE_DIR}/hazelcast/generated-sources/include/hazelcast DESTINATION include FILES_MATCHING PATTERN "*.h")
+INSTALL(DIRECTORY ${PROJECT_SOURCE_DIR}/hazelcast/include/hazelcast DESTINATION include FILES_MATCHING PATTERN "*.h" PATTERN "*.inl")
+INSTALL(DIRECTORY ${PROJECT_SOURCE_DIR}/external/release_include/boost DESTINATION include FILES_MATCHING PATTERN "*.hpp" PATTERN "*.h")
+INSTALL(DIRECTORY ${PROJECT_SOURCE_DIR}/external/release_include/easylogging++ DESTINATION include FILES_MATCHING PATTERN "*.h")
+INSTALL(DIRECTORY ${PROJECT_SOURCE_DIR}/external/include/asio/asio/include/asio DESTINATION include FILES_MATCHING PATTERN "*.hpp")
15 changes: 15 additions & 0 deletions bazel/repositories.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ def envoy_dependencies(skip_targets = []):
_com_github_google_libprotobuf_mutator()
_com_github_gperftools_gperftools()
_com_github_grpc_grpc()
_com_github_hazelcast_cpp_client()
_com_github_jbeder_yaml_cpp()
_com_github_libevent_libevent()
_com_github_luajit_luajit()
Expand Down Expand Up @@ -289,6 +290,20 @@ def _com_github_google_libprotobuf_mutator():
build_file = "@envoy//bazel/external:libprotobuf_mutator.BUILD",
)

def _com_github_hazelcast_cpp_client():
location = REPOSITORY_LOCATIONS["com_github_hazelcast_cpp_client"]
http_archive(
name = "com_github_hazelcast_cpp_client",
build_file_content = BUILD_ALL_CONTENT,
patch_args = ["-p0"],
patches = ["@envoy//bazel/foreign_cc:hazelcast_cpp_client.patch"],
**location
)
native.bind(
name = "hazelcast_cpp_client",
actual = "@envoy//bazel/foreign_cc:hazelcast_cpp_client",
)

def _com_github_jbeder_yaml_cpp():
location = REPOSITORY_LOCATIONS["com_github_jbeder_yaml_cpp"]
http_archive(
Expand Down
7 changes: 7 additions & 0 deletions bazel/repository_locations.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,13 @@ REPOSITORY_LOCATIONS = dict(
strip_prefix = "grpc-d8f4928fa779f6005a7fe55a176bdb373b0f910f",
urls = ["https://github.com/grpc/grpc/archive/d8f4928fa779f6005a7fe55a176bdb373b0f910f.tar.gz"],
),
com_github_hazelcast_cpp_client = dict(
sha256 = "3c43c81135e415ce708486564dc125bde93c2c9f8965d5af4b603ec91ff52f6e",
strip_prefix = "hazelcast-cpp-client-3.12.1",
# Using non official tarball due to missing submodule files in the official release.
# TODO(enozcan): Use official release with init & updating submodules

@enozcan enozcan Apr 7, 2020

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to update submodules before building the external dependency?

urls = ["https://github.com/enozcan/envoy-hazelcast-cpp-client/raw/master/hazelcast-cpp-client-3.12.1.zip"],
),
com_github_luajit_luajit = dict(
sha256 = "409f7fe570d3c16558e594421c47bdd130238323c9d6fd6c83dedd2aaeb082a8",
strip_prefix = "LuaJIT-2.1.0-beta3",
Expand Down
1 change: 1 addition & 0 deletions source/common/common/logger.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ namespace Logger {
FUNCTION(filter) \
FUNCTION(forward_proxy) \
FUNCTION(grpc) \
FUNCTION(hazelcast_http_cache) \
FUNCTION(hc) \
FUNCTION(health_checker) \
FUNCTION(http) \
Expand Down
186 changes: 186 additions & 0 deletions source/docs/filters/http/cache/hazelcast_cache_plugin.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
### Hazelcast Http Cache Plugin

@enozcan enozcan Apr 7, 2020

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the docs here for the filter are kind of for developers. This doc targets the users, not developers. Is that the right place?

Work in Progress--Cache filter has not implemented features. The corresponding ones are not ready for the plugin too.

Hazelcast Http Cache provides a pluggable storage implementation backed by Hazelcast In Memory Data Grid for the http
cache filter. Using Hazelcast C++ client, the plugin does not store any http response locally but in a distributed map
provided by Hazelcast cluster. To enable the cache plugin, the network configuration belongs to a cluster to be
Comment thread
enozcan marked this conversation as resolved.
Outdated
connected must be set on the cache plugin configuration.

## Offered cache modes
The plugin comes with two modes:

- **Unified**
A cached http response is stored as a single entry in the cache. On a range http request, regardless of the requested
range, the whole response body is fetched from the cache and then only the desired bytes are served along with the
headers and trailers (if any). This mode is handy where response body sizes are reasonably large (up to 10 KB), or
Comment thread
enozcan marked this conversation as resolved.
Outdated
range requests are not frequent, or they are not allowed at all.

- **Divided**
A cached http response is stored as multiple entries in the cache. Two separate maps are used to store a single
response. In one of them, response headers, body size, and trailers (if any) are stored. In the other one, the
corresponding response body is stored in multiple entries each of which has a certain size configured via `partition
size` in the plugin configuration. That is, for a response of size 50 KB, if the configured partition size is 20 KB,
then three different entries will be created to store the body of this response:
- Body<1> : 0 - 19 KB
- Body<2> : 20 - 39 KB
- Body<3> : 40 - 50 KB

On a range request, not the whole body for a response but only the necessary partitions are fetched from the
cache. This option helps to serve range requests faster and in a stream-like fashion but comes with a cost. Every
body entry has its own fixed memory cost and hence partitioned entries need larger memory than the actual body
size. Also, to keep these partitioned cache entries even, extra operations - not necessarily asynchronous, might
be needed (i.e. cleaning up a malformed body sequence, recovery from a mismatch between body and header, etc.).

## Connecting to a Hazelcast cluster
**NOTE:** The plugin uses the client with version 3.12.1 and hence it is not yet compatible with Hazelcast 4.x.
Hazelcast version 3.12.x is recommended for the server-side.

Before starting the cache plugin, there must be a running Hazelcast cluster. Hazelcast instances might be started
as a sidecar to Envoy, form up a cluster using Hazelcast Kubernetes plugin, etc. The only information the plugin needs
will be the addresses and ports of the cluster members and the group information of the cluster. Providing the address
of only one member in the cluster will be enough for the connection but using more than one is recommended.

Related links: [Hazelcast Docker Hub](https://hub.docker.com/r/hazelcast/hazelcast/), [Hazelcast Kubernetes Plugin]
(https://github.com/hazelcast/hazelcast-kubernetes)

## Configuring Hazelcast cluster for the cache
Eviction, maximum size, and other related properties for the cache must be configured on the server-side
via programmatic configuration or `hazelcast.xml`.

- **Unified Mode**

```xml
<!-- use wildcard for the map name to configure the cache since the full name is determined by the plugin -->
<map name="<app_prefix>*uni">

<!--

Customizable http cache configurations.
For instance, for the configuration below:

- 25% of the http responses will be evicted according to LRU policy when the map size hits 1000.
- Each http responses will live at most 180 seconds in the cache.
- If an http responses is not called for the last 90 seconds, it will be evicted immediately regardless of the TTL.

-->
<max-size policy="PER_NODE">1000</max-size>
<eviction-percentage>25</eviction-percentage>
<eviction-policy>LRU</eviction-policy>
<time-to-live-seconds>180</time-to-live-seconds>
<max-idle-seconds>90</max-idle-seconds>

<!--

For more configuration options, see Hazelcast doc:

https://docs.hazelcast.org/docs/3.12.6/manual/html-single/index.html#map

OBJECT in-memory format will not have any advantage but extra serialization cost here. Setting it to
BINARY is the best fit for the plugin. The two configurations below are also the default values.

-->
<in-memory-format>BINARY</in-memory-format>
<statistics-enabled>true</statistics-enabled>
</map>
```

- **Divided Mode**

```xml
<!-- use wildcard for the map name to configure the cache since the full name is determined by the plugin.default -->
<map name="<app_prefix>*div">

<!--

Customizable http cache configurations for header map. The properties below will determine the
characteristics of the http cache, not only response headers cache.

For instance, for the configuration below:

- 25% of the http responses will be evicted according to LRU policy when the map size hits 100.
- Each http responses will live at most 180 seconds in the cache.

NOTE: Although works fine, divided mode is not optimized for idle-time based eviction.

-->
<max-size policy="PER_NODE">100</max-size>
<eviction-percentage>25</eviction-percentage>
<eviction-policy>LRU</eviction-policy>
<time-to-live-seconds>180</time-to-live-seconds>

<!--

For more configuration options, see Hazelcast doc:

https://docs.hazelcast.org/docs/3.12.6/manual/html-single/index.html#map

OBJECT in-memory format will not have any advantage but extra serialization cost here. Setting it to
BINARY is the best fit for the plugin. The two configurations below are also the default values.

-->
<in-memory-format>BINARY</in-memory-format>
<statistics-enabled>true</statistics-enabled>
</map>

<map name="<app_prefix>*body">

<!--

Customizable http cache configurations for body map.

Do not set the cache configuration here. Instead, configure `<app_prefix>*div` first and set
the properties here accordingly.

- Do not use max-size configuration here. They will be evicted according to TTL when their header is evicted.
Instead, the max size is (indirectly) ensured with max allowed body size configuration along with the partition
size.
- Use the same eviction configuration with the header map.
- Keep TTL slightly longer than the header map (i.e. 15 seconds).

NOTE: Although works fine, divided mode is not optimized for idle-time based eviction.

-->
<max-size policy="PER_NODE">0</max-size>
<eviction-percentage>25</eviction-percentage>
<eviction-policy>LRU</eviction-policy>
<time-to-live-seconds>195</time-to-live-seconds>

<!--

OBJECT in-memory format will not have any advantage but extra serialization cost here. Setting it to
BINARY is the best fit for the plugin. The two configurations below are also the default values.

Statistics of this map will not have meaningful information about the cache and can be disabled if not needed.
However, it might be useful to observe how the configured `partition_size` fits for the cached responses.

-->
<in-memory-format>BINARY</in-memory-format>
<statistics-enabled>false</statistics-enabled>
</map>
```

When one of the clients connected to the cluster loses its connection, if the client has acquired the lock for a
key, this will cause this key to be unusable. To prevent such a scenario in a possible connection failure, the
maximum time limit for the locks should be set on the server-side (not necessarily to be 60 seconds). The default
value for this property is `Long.MAX`. Hence, if it is not set, on a connection failure a locked key will
be unusable permanently:
```xml
<properties>
...
<!-- required for both of the cache modes -->
<property name="hazelcast.lock.max.lease.time.seconds">60</property>
...
</properties>
```
**NOTE**: Setting this property will affect not only the http cache but all other data structures in the cluster.

## Statistics
Cache statistics are not collected locally. Instead, cluster-wide statistics should be observed on Hazelcast
Management Center. When the cache plugin starts, one of the very first logs will be saying the map name used for
the cache. The statistics can be observed with that name under the `maps` section on the management center.

## Using a single cache for multiple filters
Each distributed map in a Hazelcast cluster is differentiated by its name for the same key and value types. Thus,
all the plugins connected to the same cluster will use the same map for responses only if they have the same cache
mode and the app prefix (and the same partition size for divided mode) in the plugin configuration. The filters
configured with the same partition size and cache mode but different prefixes will create two different http caches.
1 change: 1 addition & 0 deletions source/extensions/extensions_build_config.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,7 @@ EXTENSIONS = {
# CacheFilter plugins
#

"envoy.filters.http.cache.hazelcast_http_cache": "//source/extensions/filters/http/cache/hazelcast_http_cache:hazelcast_http_cache_lib",
"envoy.filters.http.cache.simple_http_cache": "//source/extensions/filters/http/cache/simple_http_cache:simple_http_cache_lib",
}

Expand Down
43 changes: 43 additions & 0 deletions source/extensions/filters/http/cache/hazelcast_http_cache/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
licenses(["notice"]) # Apache 2

load(
"//bazel:envoy_build_system.bzl",
"envoy_cc_extension",
"envoy_package",
"envoy_proto_library",
)

envoy_package()

envoy_cc_extension(
name = "hazelcast_http_cache_lib",
srcs = [
"hazelcast_cache_entry.cc",
"hazelcast_context.cc",
"hazelcast_http_cache_impl.cc",
],
hdrs = [
"hazelcast_cache.h",
"hazelcast_cache_entry.h",
"hazelcast_context.h",
"hazelcast_http_cache_impl.h",
"util.h",
],
external_deps = ["hazelcast_cpp_client"],
security_posture = "robust_to_untrusted_downstream_and_upstream",
status = "wip",
deps = [
":config_cc_proto",
"//include/envoy/registry",
"//source/common/buffer:buffer_lib",
"//source/common/common:logger_lib",
"//source/common/http:header_map_lib",
"//source/common/runtime:runtime_lib",
"//source/extensions/filters/http/cache:http_cache_lib",
],
)

envoy_proto_library(
name = "config",
srcs = ["config.proto"],
)
Loading