CASSSIDECAR-427: Implement basic configuration retrieval in ConfigurationManager#358
CASSSIDECAR-427: Implement basic configuration retrieval in ConfigurationManager#358pauloricardomg wants to merge 13 commits into
Conversation
e18a529 to
28ccef4
Compare
| other.configuration().cassandraYaml()); | ||
|
|
||
| Map<String, String> mergedOpts = new LinkedHashMap<>(configuration.extraJvmOpts()); | ||
| mergedOpts.putAll(other.configuration().extraJvmOpts()); |
There was a problem hiding this comment.
This will add new keys for any key which is not present in the mergedOpts map. Is that the behavior we want to expose? Or do we want to limit overlays to known keys in the base template?
Either way worth explicitly documenting in the javadoc.
There was a problem hiding this comment.
overlays can add keys not present in the base template, added it to javadoc on 7c81cc8
| Map<String, String> mergedOpts = new LinkedHashMap<>(configuration.extraJvmOpts()); | ||
| mergedOpts.putAll(other.configuration().extraJvmOpts()); | ||
|
|
||
| Instant mergedLastModified = lastModified.isAfter(other.lastModified) |
There was a problem hiding this comment.
Since the merge produces a new ConfigurationOverlaySnapshot, shouldn't lastModified be Instant.now()? If not, what are the intended semantics of lastModified?
There was a problem hiding this comment.
The javadoc in https://github.com/apache/cassandra-sidecar/pull/358/changes#diff-e190c28aea1ad1a802b4cbcdadc180917c653b3b9aac34b0eb23eb4c680e277bR48-R58 helps clarify this a bit.
There was a problem hiding this comment.
Just overlaying the configuration over a base template does not produce a new configuration. last modified is the maximum modification timestamp of the base template and the overlay. lastModified is only instant.now when updating the overlay or the base template by modifying the file on disk. Added clarification to javadoc on 99072c3
|
|
||
| JsonObject result = base.copy(); | ||
| JsonObject overlayCopy = overlay.copy(); | ||
| for (Map.Entry<String, Object> field : overlayCopy) |
There was a problem hiding this comment.
Similar question as above about the intended semantics when a overlay contains keys not in the base configuration.
| } | ||
| else | ||
| { | ||
| result.put(fieldName, overlayValue); |
There was a problem hiding this comment.
Looks like if base value for a key is not null and overlay value for the same is null, this line still picks overlay value null. Is this expected? We are not doing this for JsonObjects as line 114 above picks base copy. But we are doing this differently for individual keys.
There was a problem hiding this comment.
My assumption is overlay contains only overridden keys, while base contains all. For example,
If base is
a: 'a'
b: 'b'
c: 'c'
Then if overlay is
b:'b2'
Does it not mean admins want to only override b, while picking base value for others? In that case, the line 136 missing this case right?
There was a problem hiding this comment.
Add null handling on ad29f2c and also added test case to ensure overlay will be correct for that case.
| Instant lastModified; | ||
| try | ||
| { | ||
| lastModified = Files.getLastModifiedTime(yamlPath).toInstant(); |
There was a problem hiding this comment.
There is no synchronization to avoid race condition between loading yaml at line 65 and retrieving last modified time at line 69. There is a possibility of associating different modified time for older yaml if the file was modified in between.
As we are reading from disk,
- either we need to read both contents and timestamp in a single call if such an API exists
- or do optimistic synchronization
t1 = read modified time
data = read contents
t2 = read modified time
if t2 != t1, file was modified and repeat the action.
There was a problem hiding this comment.
good catch, added optimistic concurrency on 031b999
| other.configuration().cassandraYaml()); | ||
|
|
||
| Map<String, String> mergedOpts = new LinkedHashMap<>(configuration.extraJvmOpts()); | ||
| mergedOpts.putAll(other.configuration().extraJvmOpts()); |
There was a problem hiding this comment.
Don't we need to call validateNoConflictingBooleanOpts on these merged JVM opts?
There was a problem hiding this comment.
Makes sense, updated 6ec34b8 to prefer base if there's a conflicting boolean jvm opt on overlay.
| public JsonObject toJson() | ||
| { | ||
| return new JsonObject() | ||
| .put("hash", hash()) |
There was a problem hiding this comment.
fromJson doesn't have "hash", while toJson having it
There was a problem hiding this comment.
Yes, that's expected. hash is computed from the configuration contents and cached — it's not stored state. toJson() includes it for observability (e.g., logging, API responses), but fromJson() correctly skips it since it will be recomputed on demand from the deserialized configuration.
| @NotNull | ||
| public ConfigurationOverlaySnapshot getEffectiveConfiguration(InstanceMetadata instance) | ||
| { | ||
| ConfigurationOverlaySnapshot baseSnapshot = ConfigUtils.loadConfiguration(baseTemplatePath); |
There was a problem hiding this comment.
Do we plan to cache base snapshot in upcoming PRs, as it doesn't change frequently, and we are involving IO every time here.
| } | ||
|
|
||
| JsonObject result = base.copy(); | ||
| JsonObject overlayCopy = overlay.copy(); |
There was a problem hiding this comment.
Why do we need to make a copy of overlay when we are not modifying it?
79da900 to
e98459d
Compare
|
@isaacreath @skoppu22 thanks for the helfpul comments, addressed them |
| throw new UnsupportedOperationException(); | ||
| } | ||
|
|
||
| /** |
There was a problem hiding this comment.
Looks like older JavaDoc left over here
| String key = entry.getKey(); | ||
| if (CassandraConfigurationOverlay.hasConflictingBooleanOpt(mergedOpts, key)) | ||
| { | ||
| continue; |
There was a problem hiding this comment.
Are we preferring base option if there is a conflict? Either we need to log a warning or throw an error. And javadoc says 'merges', doesn't mention that we are picking base option here on conflict. It can be confusing for the users, better to callout whatever the approach we pick here.
There was a problem hiding this comment.
Normally it shouldn't be possible to add conflicting option, since we validate that when updating configs. However in case the remote provider adds a conflicting option it's better to ignore and log a warning then fail, otherwise this can prevent Cassandra from starting. I logged a warning and updated javadoc on f95be70
e98459d to
f95be70
Compare
|
|
||
| /** | ||
| * Deep-merges the overlay onto the base configuration. For nested objects both base and overlay | ||
| * contain, fields are merged recursively. For all other node types (scalars, arrays, nulls), |
There was a problem hiding this comment.
[Minor] 'nulls' can be removed
There was a problem hiding this comment.
Good catch, fixed on c9d81a5. Thanks for the review 🙏
f95be70 to
c9d81a5
Compare
|
+1 (nb). |
c9d81a5 to
6f68038
Compare
… retrieval Introduce ConfigurationManager that computes the effective configuration for a Cassandra instance by fetching the overlay from the ConfigurationProvider and merging it with the base cassandra.yaml template. New classes: - ConfigurationManager: retrieves effective configuration by loading the base template via ConfigUtils.loadConfiguration and merging it with the provider overlay via ConfigurationOverlaySnapshot.overlay() - ConfigurationManagerException: wraps provider failures with a descriptive message - ConfigUtils: utilities for YAML loading (loadYaml), deep merge of configurations (mergeConfigurations), and loading a YAML file as a ConfigurationOverlaySnapshot (loadConfiguration) ConfigurationOverlaySnapshot gains an overlay() method that produces a new snapshot by deep-merging another snapshot on top, combining cassandraYaml recursively, merging extraJvmOpts, and using the max of both lastModified timestamps.
…t in the base configuration
6f68038 to
827a8cd
Compare
Summary
Implement the basic GET configuration flow in the Configuration Manager. When handling retrieval requests, the manager fetches the current overlay from the
ConfigurationProvider, merges it with the base template to compute the effective configuration, and returns the result with its SHA-256 hash and last modified timestamp.New classes
ConfigurationManager: Retrieves effective configuration by loading the base template viaConfigUtils.loadConfigurationand merging it with the provider overlay viaConfigurationOverlaySnapshot.overlay(). Wraps provider failures inConfigurationManagerException.ConfigurationManagerException: Unchecked exception wrapping provider failures with a descriptive message.ConfigUtils: Utilities for YAML loading (loadYaml), deep merge of configurations (mergeConfigurations), and loading a YAML file as aConfigurationOverlaySnapshot(loadConfiguration).Changes to existing classes
ConfigurationOverlaySnapshot: Adds anoverlay()method that produces a new snapshot by deep-merging another snapshot on top, combiningcassandraYamlrecursively, mergingextraJvmOpts, and using the max of bothlastModifiedtimestamps. Renamesoverlayfield/accessor toconfigurationto better reflect its role when representing the effective (merged) configuration.Test plan
ConfigurationManagerTest: Verifies effective configuration retrieval with no overlay, with overlay (merge semantics), and provider failure propagationConfigUtilsTest: Verifies YAML loading, deep merge logic (overlay wins, new keys, nested objects, empty overlay), andloadConfigurationsnapshot creationConfigurationOverlaySnapshotTest: Verifiesoverlay()merge of yaml keys, JVM opts, max lastModified, and deep nested object mergingFileBasedConfigurationProviderTest: Updated to use renamedconfiguration()accessor./gradlew :server:test --tests "org.apache.cassandra.sidecar.configmanagement.*"- all 50 tests pass