TRUNK-6409 Initializer module should not re-run initialisation on each node#319
TRUNK-6409 Initializer module should not re-run initialisation on each node#319rkorytkowski wants to merge 2 commits intomekomsolutions:mainfrom
Conversation
|
@IamMujuziMoses could you please review my fixes? |
b8f0431 to
7bff103
Compare
|
@mseaton, @brandones, @wikumChamith I'd appreciate your reviews as well since you contributed here. |
| .deleteChecksumFile(replaceExtension(translationFile.getName(), ConfigDirUtil.CHECKSUM_FILE_EXT)); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
It seems like we should not remove this block? Certainly we can change how we interact with checksums, but it seems like the point of this block is to delete checksums to force forms to reload that this deems to have change via some transitory change.
| } else { | ||
| boolean throwError = PROPS_STARTUP_LOAD_FAIL_ON_ERROR.equalsIgnoreCase(startupLoadingMode); | ||
| log.info("OpenMRS config loading process started..."); | ||
| boolean lockAquired = false; |
There was a problem hiding this comment.
typo
| boolean lockAquired = false; | |
| boolean lockAcquired = false; |
| relConfigFilePath = relConfigFilePath.replaceFirst(File.separator, "!!!") //change domain separator | ||
| .replace(File.separator, "_") | ||
| .replaceFirst("!!!", File.separator); //revert domain separator |
There was a problem hiding this comment.
Would this work on Windows? replaceFirst() uses regex, and on Windows, File.separator is \, which is a special regex character.
There was a problem hiding this comment.
Probably not. It can be easily done without regex. Let me fix it.
| @@ -40,5 +40,40 @@ | |||
| <comment>Deleting 'concepts' domain checksums</comment> | |||
| <customChange class="org.openmrs.module.initializer.liquibase.DeleteDomainChecksumsChangeset" domainName="concepts"></customChange> | |||
There was a problem hiding this comment.
Do we still need this?
There was a problem hiding this comment.
Correct, needs to be removed.
|
thanks for the updates, LGTM the changes make the initialization process more robust. |
| try { | ||
| AddressConfigurationLoader.loadAddressConfiguration(Paths.get(iniz.getConfigDirPath()), | ||
| Paths.get(iniz.getChecksumsDirPath())); | ||
| AddressConfigurationLoader.loadAddressConfiguration(Paths.get(iniz.getConfigDirPath()), null); |
There was a problem hiding this comment.
I see this is going to break. It re-implements checksum checks and uses the checksum dir. Need to fix it.
There was a problem hiding this comment.
It's implemented in such a strange way... anyway I'm going to pass a map from getAllChecksums() to loadAddressConfiguration and have loadAddressConfiguration return a map with new or updated checksums to be persisted by the iniz module.
There was a problem hiding this comment.
Fixed it! Changes in AddressHierarchy module committed in openmrs/openmrs-module-addresshierarchy@b291b07
7bff103 to
3da348c
Compare
| catch (Exception e) { | ||
| throw new ModuleException("An error occurred loading initializer configuration", e); | ||
| } | ||
| getInitializerService().runInitializer(); |
|
|
||
| // Replay | ||
| loader.load(); | ||
| AddressConfigurationLoader.loadAddressConfiguration(); |
There was a problem hiding this comment.
This line is actually handled by loader.load().
| <url>https://oss.sonatype.org/content/groups/public</url> | ||
| <snapshots> | ||
| <enabled>true</enabled> | ||
| </snapshots> |
3da348c to
c833a7b
Compare
c833a7b to
6034c9e
Compare
|
Tested with reference application. Migration from initializer 2.21.0 to 2.22.0-SNAPSHOT succeeded. Setup from scratch as well. |
| import org.slf4j.LoggerFactory; | ||
|
|
||
| @OpenmrsProfile(modules = { "addresshierarchy:2.17.* - 9.*" }) | ||
| @OpenmrsProfile(modules = { "addresshierarchy:2.22.* - 9.*" }) |
There was a problem hiding this comment.
This is not a good solution because everyone who is running addresshierarchy version 2.17-2.21 will all of a sudden have their configurations no longer loaded by initializer if they upgrade, right? We should try to retain compatibility and / or have 2 different versions of this Loader that target 2 different AH version ranges.
(Note: I didn't see any PR to review for AH - did you just commit that directly?)
There was a problem hiding this comment.
I see no way of supporting both versions easily. I would just create a loader for 2.17 - 2.21 that throws an error from a constructor saying you need to upgrade addresshierarchy. I linked the direct commit in AH from this PR.
There was a problem hiding this comment.
The problem is, there is no way for Iniz to indicate that it no longer supports versions 2.17 - 2.21. It only has an aware-of dependency, not a require module dependency. So, if we can't support it, we should at least throw an Exception if someone tries to use the loader with versions that were supported and now are no longer supported, so that it is obvious that behavior no longer works and they need to take action.
It would also make sense to ensure the version associated with this PR is a new major version of Iniz, to reflect this loss of compatibility
There was a problem hiding this comment.
It's why I suggested having a loader for 2.17-2.21 throwing an error and asking to upgrade. I'll add that.
| Paths.get(iniz.getChecksumsDirPath())); | ||
| Map<String, String> updatedChecksums = AddressConfigurationLoader | ||
| .loadAddressConfiguration(Paths.get(iniz.getConfigDirPath()), iniz.getAllChecksums()); | ||
| updatedChecksums.forEach((key, value) -> iniz.saveOrUpdateChecksum(new InitializerChecksum(key, value))); |
There was a problem hiding this comment.
I'm not really sure this fix is needed here to be honest. This would be the way we would do it if initializer took responsibility over the addresshierarchy checksums and decision on whether to load or reload, but it doesn't. We might (and probably do) want to move in a direction in which it does, but it doesn't currently. Everything is managed in AH itself, it just happens to use the same configuration and configuration_checksums directory for consistency with the rest of the configuration.
What I would do is this:
- Keep the existing AH Loader supporting 2.17.* - 2.999.* and keep the exact same behavior as before. If you have gotten rid of the
iniz.getChecksumsDirPath(), then just make an equivalent within this loader that points to the same place as it did before for backwards compatibility - Create a new AH Loader supporting AH version 3.* (3.0.0-SNAPSHOT is the master branch). This should do what you are aiming to do, however I don't think you actually did things entirely correctly in AH yet, since AH has it's own logic for checksums and file loading, and you didn't do anything in AH to change that. I would think you actually need to make the same changes to checksum handing within AH that you are doing in Iniz or you need to make AH fully depend on Iniz and have it delegate to the Iniz functionality.
(Note: I can see how all this might be a bit confusing since AH has files like ConfigUtil that are essentially copies from Iniz, but it is important to note that these are copies - AH does not currently depend on Iniz and has no knowledge of or ability to use it's functionality)
There was a problem hiding this comment.
I see where you are going with supporting old versions of AddressHierarchy. I would then need to exclude the addresshierarchy domain from migrating to checksums stored in DB if addresshierarchy is not 2.22+ and do migrate as soon as you upgrade... It gets messy in my opinion. I would just ask to upgrade to newer adresshierarchy.
AH logic is here openmrs/openmrs-module-addresshierarchy@1bbf1da
It sill lets AH handle checksums in any way it wants including how it calculates checksums, but delegates the loading of checksums and persistence to the initializer module. The way things are architected is that, initializer is made aware of addresshierarchy to be started after addresshierarchy so I cannot make adress hierarchy depend on initializer...
There was a problem hiding this comment.
Given the way that Iniz has evolved in the years since AH first created this mechanism, we are long overdue to remove the loading functionality from AH and move it entirely into Iniz, which is what I think we should do here.
If your overall goal is for the refapp platform to support horizontal scaling, addresshierarchy will need much the same changes that you are making to Iniz anyway.
What I think we should do is:
- Create a new major version release of AH. In this version:
** Remove all checksum-related logic from AH, and also remove any knowledge of the "configuration" and "configuration_checksums" directories altogether, and just keep the functionality that loads/reloads data from configuration files if invoked.
** Remove the functionality from the AH activator to load address files at startup - In Iniz:
** Add a Processor with an OpenMRS profile that targets this new version of AH
** Move the logic from AH to this new processor as appropriate.
There was a problem hiding this comment.
Okay, so you would like me to refactor and move the entire initialisation logic from AH to the iniz module. I think it's how it should have been implemented from the beginning. It was a wired construct with AH handling its own checksums and be called by iniz. I thought it was for a reason. I didn't want to make more changes than needed in AH to support persisting checksums in DB.
There was a problem hiding this comment.
| @@ -1,93 +0,0 @@ | |||
| package org.openmrs.module.initializer.liquibase; | |||
There was a problem hiding this comment.
Why are we getting rid of this ChangeSet? Because it isn't needed, or because we have changed how checksums are stored? Because if it is the latter, that's incorrect. We should just update this ChangeSet to delete the checksums from the new location.
There was a problem hiding this comment.
It's a very old changeset from 2021... I'm fine keeping it around or deleting. Definitely no use in DB checksums. I would need to keep the old ConfigDirUtil.deleteChecksums method if it is to stay.
| public void runInitializer(String lockName) { | ||
| getSelf().migrateChecksumsFromFilesToDB(); | ||
|
|
||
| boolean throwError = PROPS_STARTUP_LOAD_FAIL_ON_ERROR |
There was a problem hiding this comment.
I think we want this boolean to be passed into this method, and then for the activator to pass it in from the initializer config property, since that config is about what to do at startup vs. what to do when this method is called generally.
| if (PROPS_STARTUP_LOAD_DISABLED.equalsIgnoreCase(startupLoadingMode)) { | ||
| log.info("OpenMRS config loading process disabled at initializer startup"); | ||
| } else { | ||
| boolean throwError = PROPS_STARTUP_LOAD_FAIL_ON_ERROR.equalsIgnoreCase(startupLoadingMode); |
There was a problem hiding this comment.
As I indicated above, we should continue to load this throwError property here, and pass it in
| } | ||
| } | ||
|
|
||
| @Test |
There was a problem hiding this comment.
These tests still seem relevant?
There was a problem hiding this comment.
We have subdirectories covered in migration tests, but missing in DB checksums. I'll add this case to BaseFileLoaderTest.java.
There was a problem hiding this comment.
Done in getFiles_shouldReturnNestedFiles and loadUnsafe_shouldHandleNestedFiles
| protected void initModules() { | ||
| { | ||
| Module mod = new Module("", "addresshierarchy", "", "", "", "2.17.0"); | ||
| Module mod = new Module("", "addresshierarchy", "", "", "", "2.22.0"); |
|
|
||
| <!-- For tests --> | ||
| <addresshierarchyVersion>2.17.0</addresshierarchyVersion> | ||
| <addresshierarchyVersion>2.22.0-SNAPSHOT</addresshierarchyVersion> |
|
@mseaton I think I responded to all your comments now. Looking forward to your reply on deleting the old changeset, addresshierarchy changes and locking for loadUnsafe. |
| import org.slf4j.LoggerFactory; | ||
|
|
||
| @OpenmrsProfile(modules = { "addresshierarchy:2.17.* - 9.*" }) | ||
| @OpenmrsProfile(modules = { "addresshierarchy:2.22.* - 9.*" }) |
There was a problem hiding this comment.
The problem is, there is no way for Iniz to indicate that it no longer supports versions 2.17 - 2.21. It only has an aware-of dependency, not a require module dependency. So, if we can't support it, we should at least throw an Exception if someone tries to use the loader with versions that were supported and now are no longer supported, so that it is obvious that behavior no longer works and they need to take action.
It would also make sense to ensure the version associated with this PR is a new major version of Iniz, to reflect this loss of compatibility
| Paths.get(iniz.getChecksumsDirPath())); | ||
| Map<String, String> updatedChecksums = AddressConfigurationLoader | ||
| .loadAddressConfiguration(Paths.get(iniz.getConfigDirPath()), iniz.getAllChecksums()); | ||
| updatedChecksums.forEach((key, value) -> iniz.saveOrUpdateChecksum(new InitializerChecksum(key, value))); |
There was a problem hiding this comment.
Given the way that Iniz has evolved in the years since AH first created this mechanism, we are long overdue to remove the loading functionality from AH and move it entirely into Iniz, which is what I think we should do here.
If your overall goal is for the refapp platform to support horizontal scaling, addresshierarchy will need much the same changes that you are making to Iniz anyway.
What I think we should do is:
- Create a new major version release of AH. In this version:
** Remove all checksum-related logic from AH, and also remove any knowledge of the "configuration" and "configuration_checksums" directories altogether, and just keep the functionality that loads/reloads data from configuration files if invoked.
** Remove the functionality from the AH activator to load address files at startup - In Iniz:
** Add a Processor with an OpenMRS profile that targets this new version of AH
** Move the logic from AH to this new processor as appropriate.
| .equalsIgnoreCase(getInitializerConfig().getStartupLoadingMode()); | ||
| boolean lockAcquired = false; | ||
|
|
||
| try { |
There was a problem hiding this comment.
Not sure I follow. Different nodes will process different domains during the same loading cycle?
6034c9e to
a6f8594
Compare
a6f8594 to
284bce5
Compare
|
@mseaton addressed all your review comments. I didn't make any changes to AH. No AH upgrade is needed as well. In iniz 3.0.0 all the AH loading is done in the iniz module. |
| } | ||
| } | ||
|
|
||
| public void loadAddressConfiguration(Path configPath, Map<String, String> previousChecksums) { |
There was a problem hiding this comment.
All below is copied from AddressConfigurationLoader with just a few lines changed to use DB checksums instead of files.
| * {@link org.openmrs.module.addresshierarchy.config.AddressConfigurationLoader} except for | ||
| * checksums loading and persisting. | ||
| */ | ||
| @OpenmrsProfile(modules = { "addresshierarchy:2.17.* - 9.*" }) |
|
@rbuisson It would be great to have your team review it as well. |
284bce5 to
7622b7f
Compare
| } | ||
| getService().initI18nCache(); | ||
| } | ||
|
|
There was a problem hiding this comment.
I would think that any/all of the methods below that are unrelated to checksums and which are already present in the AH module should just be used directly from the AH module, and not re-copied into here. Assuming these methods are adequately maintained in the AH module, and may be useful to use independently on the Initialilzer module, I don't see why we would duplicate/copy them into here?
There was a problem hiding this comment.
I'm very confused with this AH module altogether. Probably not the best person to do refactoring there. I understand you want to get rid of technical debt there.
I don't know why AH calls loadAddressConfiguration in its activator and then iniz calls loadAddressConfiguration again... Is the activator call disabled in some way when you have iniz installed?
There was a problem hiding this comment.
Yeah, @mseaton I'm unsure how to interpret this. The AH re-running loadAddressConfiguration() is safe when Iniz is generating the same checksum files because the checksums should match. But it seems like either we're moving the initialization stuff into Iniz or we shouldn't have Iniz worry about AH at all.
Keeping the initialization logic in AH seems to require that AH depend on Iniz, but that's the exact opposite of the paradigm we've adopted for Iniz. I think the sane thing to do here is move all of the initialization parts into Iniz and remove them from AH. But I'm curious how you're envisioning things working?
There was a problem hiding this comment.
@ibacher / @rkorytkowski - all I'm saying is that if there are existing methods in AH that don't deal with checksums, but just take responsibility for the actual loading of a config file, then we should just use the existing. Why are we copying methods like:
public static void installAddressHierarchyLevels(List<AddressComponent> addressComponents)
or
AddressConfiguration readFromString(String configuration)
or
String writeToString(AddressConfiguration configuration)
or
XStream getSerializer()
or
void setGlobalProperty(String propertyName, String propertyValue)
etc, etc. ?
Ideally AH would retain the capabilities to delete / load Address Level and Address Hierarchy information from appropriate files, and Iniz is just invoking that functionality based on configuration files located in the configuration directory and if they are deemed to have changed based on previous checksums.
There was a problem hiding this comment.
I don't know why AH calls loadAddressConfiguration in its activator and then iniz calls loadAddressConfiguration again... Is the activator call disabled in some way when you have iniz installed?
Legacy. I think we just never noticed this because of the way the checksums work - i.e. if it is loaded by Iniz, then when the AH activator tries to load it again it will see the checksums have not changed and so won't load it again.
There was a problem hiding this comment.
Okay, so in the current approach (in this PR) no changes to AH are needed (except for fixing the AH startup procedure to not load address hierarchy if iniz is installed). I can call all public methods from the AH AddressConfigurationLoader instead of copying them to the iniz module except for loadAddressConfiguration. I keep a copy of loadAddressConfiguration in the Iniz module and modify it to use DB checksums and call public methods from AH AddressConfigurationLoader. Would that work?
Or do you want me to refactor loadAddressConfiguration in the AH module to not do any checksum checks and simply import given xml and csv. I would probably need to divide the loading into 2 separate methods (one for xml and one for csv) to be called from the Iniz module. The iniz module needs to know, which xml and csv files to check and pass to AH to load. In this approach the AH needs to be upgraded to work with the 3.0.x iniz module.
There was a problem hiding this comment.
I can call all public methods from the AH AddressConfigurationLoader instead of copying them to the iniz module except for loadAddressConfiguration. I keep a copy of loadAddressConfiguration in the Iniz module and modify it to use DB checksums and call public methods from AH AddressConfigurationLoader. Would that work?
Yes, this is all I am saying. If there is an existing method in AH that can be used as-is, just use it, don't copy it over from there unnecessarily.
7622b7f to
b89a3ee
Compare
|
@mseaton @kipchubett @ibacher please let me know if you still see any issues with this change |
kipchubett
left a comment
There was a problem hiding this comment.
Generally, looks good. I've left some comments.
| String globalProperty = adminService.getGlobalProperty("initializer.checksumsMigrated"); | ||
| if (globalProperty == null) { |
There was a problem hiding this comment.
If the global property exists but is set to "", "false", or any value other than null (there's a chance that someone might mess up the settings by mistake), the check will fails thus migration won't run. It may be better to explicitly check for true.
| String relConfigFilePath = base.relativize(path).toString(); | ||
| //Replace all '/' with '_' except for the first one separating domain | ||
| relConfigFilePath = StringUtils.substringBefore(relConfigFilePath, File.separator) + File.separator | ||
| + StringUtils.substringAfter(relConfigFilePath, File.separator).replace("/", "_"); |
There was a problem hiding this comment.
On Windows, paths use \, possibly this will not work for Windows users.
b89a3ee to
f1a8d24
Compare
|
@rkorytkowski did you take a look at the build failure? |
|
@dkayiwa they didn't run for me and I had no permissions to trigger them... Just checked and the failure doesn't seem to be related to this PR. Do you have permissions to run tests on the main branch to verify if they still pass? They haven't run for a month... I'm not able to reproduce the failure locally. |
|
@kipchubett do you have permissions to run those tests? |
|
@dkayiwa I'd also try re-triggering tests on this PR. I don't have permissions to do that either. |
|
I've re-triggered on main branch here: https://github.com/mekomsolutions/openmrs-module-initializer/actions/runs/22675174310/job/69163801641 |
|
@kipchubett was it caused by the changes in this pull request? Or is it a failure that already existed? In other words, do the changes in this pull requests compile successfully when you try them out locally? |
|
@kipchubett are we then ready to merge this pull request? |
I'll do some further tests to figure out the issue. It doesn't seem to be related with this PR |
|
@kipchubett is there anything left before you can merge this? |
|
@dkayiwa @rkorytkowski This seems fine to me. We can merge it in. @rkorytkowski could you update this branch with the latest |
PR with fixes for #307
I'll continue with testing against reference application in docker compose and in cluster setup.