CASSSIDECAR-424: Add ConfigurationProvider interfaces#351
CASSSIDECAR-424: Add ConfigurationProvider interfaces#351pauloricardomg wants to merge 11 commits into
Conversation
| public CassandraConfigurationOverlay updated(@Nullable Map<String, JsonNode> cassandraYamlUpdates, | ||
| @Nullable List<String> addJvmOpts, | ||
| @Nullable List<String> removeJvmOpts) |
There was a problem hiding this comment.
Right now this function provides differing approaches for updating values between cassandraYaml and extraJvmOpts.
For cassandraYaml, if I want to update an existing override, I pass the new value in through the cassandraYamlUpdates map.
For extraJvmpOpts, I need to add the existing JVM option to removeJvmOpts and the new value to addJvmOpts.
Is there any way to offer a similar approach for both so that the semantics are consistent for callers?
There was a problem hiding this comment.
Changed extraJvmOpts from List to Map<String, String> to provide a consistent update API between cassandraYaml and extraJvmOpts. Both now follow the same semantics in the updated() method: pass a key with a non-null value to upsert, or a null value to remove. This eliminates the need for separate addJvmOpts/removeJvmOpts parameters. Keys use the full JVM flag format (e.g. -Dcassandra.jmx.local.port -> 7199). See commit 0f17ae7.
| @NotNull | ||
| public JsonNode cassandraYaml() | ||
| { | ||
| return cassandraYaml; |
There was a problem hiding this comment.
It's technically possible to mutate cassandraYaml by accessing the value from here, casting it to a ObjectNode, then using ObjectNode#put to update a key.
To preserve the immutable property of this object, we may want to consider returning a copy of the cassandraYaml. That said, this could create a lot of unnecessary objects if the immutability properties aren't necessary.
There was a problem hiding this comment.
Since this is an internal API, I opted to avoid defensive copies on every accessor call to minimize unnecessary object creation. I updated the constructor to deep-copy the input ObjectNode to prevent external mutation through the original reference, and updated the accessor Javadoc to mention that callers must not mutate the returned node (use updated() instead). This gives us immutability guarantees at construction time without the overhead of copying on every read. See commit e95958e.
| node.put("hash", hash()); | ||
| node.put("lastModified", lastModified.toString()); | ||
| node.set("configuration", MAPPER.valueToTree(configuration)); | ||
| return node.toString(); |
There was a problem hiding this comment.
Nit: Do we want to consider using a pretty print string here? (i.e. toPrettyString()). Could make debugging easier.
| * @return the configuration overlay snapshot, or {@code null} if no overlay exists for the instance | ||
| */ | ||
| @Nullable | ||
| ConfigurationOverlaySnapshot getConfiguration(InstanceMetadata instance); |
There was a problem hiding this comment.
Did you consider getOverlay / storeOverlay instead of getConfiguration / storeConfiguration? The use of overlay can indicate to the reader of callers of this interface that we are not getting a fully materialized config, but it may not be a big deal.
9ab8bb4 to
f651dd8
Compare
| CassandraConfigurationOverlay overlay1 = new CassandraConfigurationOverlay(yaml1, Arrays.asList("-Xmx4G")); | ||
| CassandraConfigurationOverlay overlay2 = new CassandraConfigurationOverlay(yaml2, Arrays.asList("-Xmx4G")); |
There was a problem hiding this comment.
Looks like CI is failing here. Needs an import? import java.util.Arrays
a45830f to
bd32867
Compare
|
+1 (nb) |
| */ | ||
| public class CassandraConfigurationOverlay | ||
| { | ||
| private static final ObjectMapper MAPPER = new ObjectMapper(); |
There was a problem hiding this comment.
ideally we should try to use the framework's JSON functionality to avoid allocating additional resources (for JSON processing) etc.
There was a problem hiding this comment.
Good call, this is more fluent and simplify things. The reason I used jackson is to perform json schema validation in the future but we can convert from vertx json to jackson vertx if needed in the future.
| Object lock = locks.computeIfAbsent(instance.id(), k -> new Object()); | ||
| synchronized (lock) | ||
| { | ||
| ConfigurationOverlaySnapshot current = overlays.get(instance.id()); | ||
|
|
||
| if (current == null && originalHash != null) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| if (current != null && (originalHash == null || !current.hash().equals(originalHash))) | ||
| { | ||
| return false; | ||
| } | ||
|
|
||
| overlays.put(instance.id(), newSnapshot); | ||
| return true; | ||
| } |
There was a problem hiding this comment.
do we really need locks here? Isn't the concurrent hashmap already a DS that supports this natively?
| Object lock = locks.computeIfAbsent(instance.id(), k -> new Object()); | |
| synchronized (lock) | |
| { | |
| ConfigurationOverlaySnapshot current = overlays.get(instance.id()); | |
| if (current == null && originalHash != null) | |
| { | |
| return false; | |
| } | |
| if (current != null && (originalHash == null || !current.hash().equals(originalHash))) | |
| { | |
| return false; | |
| } | |
| overlays.put(instance.id(), newSnapshot); | |
| return true; | |
| } | |
| return overlays.compute(instance.id(), (k, current) -> { | |
| if (current == null && originalHash != null) | |
| { | |
| return null; | |
| } | |
| if (current != null && (originalHash == null || !current.hash().equals(originalHash))) | |
| { | |
| return current; | |
| } | |
| return newSnapshot; | |
| }) == newSnapshot; |
There was a problem hiding this comment.
The reason I used an explicit lock is to prevent contention within the CHM bucket but I guess this is premature optimization so I'm ok to remove and rely only on CHM.
bd32867 to
ad56510
Compare
frankgh
left a comment
There was a problem hiding this comment.
+1 looks good. I think we need to rebase and ensure CI is green
Introduce the pluggable overlay storage abstraction as defined in CEP-62 with ConfigurationProvider interface, CassandraConfigurationOverlay model, ConfigurationOverlaySnapshot wrapper, and InMemoryConfigurationProvider sample implementation.
… String> Make update semantics consistent between cassandraYaml and extraJvmOpts: both now use a map where a null value removes the entry and a non-null value upserts it. Keys use the full JVM flag (e.g. -Dcassandra.jmx.local.port).
…mmutability The constructor now deep-copies the input ObjectNode so that external mutation of the original reference cannot corrupt the overlay. The accessor documents that callers must not mutate the returned node.
Improves readability of CassandraConfigurationOverlay and ConfigurationOverlaySnapshot string representations in logs and debugging output.
…rlay/storeOverlay
… String> extraJvmOpts
…<String, String>" This reverts commit 53b2108.
… String> Make update semantics consistent between cassandraYaml and extraJvmOpts: both now use a map where a null value removes the entry and a non-null value upserts it. Keys use the full JVM flag (e.g. -Dcassandra.jmx.local.port).
…y merge Detect and reject conflicting -XX:+Option / -XX:-Option pairs after merging extraJvmOpts updates. Users can still replace one form with the other by removing the old key in the same update.
ad56510 to
c7b8446
Compare
Summary
Introduces the pluggable overlay storage abstraction as defined in CEP-62 for managing Cassandra configuration via Sidecar.
cassandraYaml(JsonNode) andextraJvmOpts, withupdated()centralizing merge logic for both componentsgetConfigurationandstoreConfigurationusing hash-based optimistic concurrencyTest Plan