Issue
-
I developed a module to track system configuration changes using a model listener that implements ConfigurationModelListener. While everything is functioning correctly, there's an issue where this module executes on both nodes. Typically, when a request occurs on node 1, the corresponding action takes place on node 1. However, in this scenario, it runs on both node 1 and node 2.
Steps to reproduce:
1. Set up a cluster of 2 nodes.
2. In one of the nodes, go to Control Panel > System Settings > Security > Audit, uncheck the Enabled checkbox and save. (just to do an initial saving of this setting)
3. Deploy the attached PoC module on both nodes. (Liferay-configlistener-clusterIssue.zip)
4. In one of the nodes, enable that setting again and watch the logs of both nodes, look for a log starting with "oldauditEnabled:::".
Result: it is printed in both nodes, indicating that the call occurs in both nodes.
Expected: The expectation is that, upon calling the deployable JAR, the log should only be printed in one node of a clustered server.
Environment
- 7.4
Resolution
-
Our team is working on redesigning the
ConfigurationModelListener, even removing some methods that should have never have been added to the API. The current design was only intended to validate configurations, not to be executed across the cluster. Since this redesign can take time, we are suggesting an alternative to what you are trying to achieve, using ConfigurationModelListener, something like this:
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.ConfigurationListener;
import org.osgi.service.component.annotations.Component;
@Component(service = ConfigurationListener.class)
public class TracingConfigurationListener implements ConfigurationListener {
@Override
public void configurationEvent(ConfigurationEvent configurationEvent) {
System.out.println("############Configuration Event : " + configurationEvent.getType() + " for " + configurationEvent.getPid());
}
}
This solution is based on creating aConfigurationListenerinstead of aConfigurationModelListener,and listen to a configuration event. You should only implement theconfigurationEventmethod.
In the PoC provided earlier the classGenericConfigurationListenerwas created and methodonBeforeSavewas implemented. With our suggestion you would need to:
- Adapt the class to implement
ConfigurationListenerinstead ofConfigurationModelListener. - Use the logic of
onBeforeSavein the new needed to implement methodconfigurationEvent - Check that the
configurationEvent.getPid()is equal to the configuration you want to listen (com.liferay.portal.security.audit.configuration.AuditConfiguration) - You should only apply the logic for the kind of
configurationEventyou want (ConfigurationEvent.CM_UPDATED,ConfigurationEvent.CM_DELETED)
To clarify things you can take a look, for example, to our class https://github.com/liferay/liferay-portal/blob/88cf28caf54d71b332b6fd1bc2bb07ed29b847a7/modules/apps/portal-security/portal-security-ldap-impl/src/main/java/com/liferay/portal/security/ldap/internal/configuration/LDAPConfigurationListener.java that also implementsConfigurationListener.
Also, if you need that your code is executed on each node you can extract them to a separate class and use either theClusterableannotation or theClusterExecutorUtilas explained here: https://help.liferay.com/hc/en-us/articles/360018154832-Liferay-s-Clustering-API
To summarize:
- The
ConfigurationModelListenerlogic is going to be changed and most likely it will not be executed in the cluster. - If you want to listen for configuration changes you can use your current approach (taking into account that it is going to be redesigned) or follow our approach described above.
- Once you have listened to the configuration change you can execute whatever code you want with
@Clusterableannotation orClusterExecutorUtil
- Adapt the class to implement
Additional Information
- https://liferay.atlassian.net/browse/LPD-1755
- https://liferay.atlassian.net/browse/LPD-8476