-
Notifications
You must be signed in to change notification settings - Fork 38
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Use instance of LockService instantiated in JobScheduler through Guice #677
Changes from all commits
6095e0c
6a5d01b
bc6544b
35c5807
3de07ff
b5d63f2
0a0377f
dfcc031
d7fd6c5
6c1d855
83677cd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,7 +14,6 @@ | |
import java.util.concurrent.atomic.AtomicReference; | ||
|
||
import org.opensearch.OpenSearchException; | ||
import org.opensearch.client.Client; | ||
import org.opensearch.cluster.service.ClusterService; | ||
import org.opensearch.core.action.ActionListener; | ||
import org.opensearch.jobscheduler.spi.LockModel; | ||
|
@@ -30,17 +29,19 @@ public class Ip2GeoLockService { | |
public static final long LOCK_DURATION_IN_SECONDS = 300l; | ||
public static final long RENEW_AFTER_IN_SECONDS = 120l; | ||
private final ClusterService clusterService; | ||
private final LockService lockService; | ||
private LockService lockService; | ||
|
||
/** | ||
* Constructor | ||
* | ||
* @param clusterService the cluster service | ||
* @param client the client | ||
*/ | ||
public Ip2GeoLockService(final ClusterService clusterService, final Client client) { | ||
public Ip2GeoLockService(final ClusterService clusterService) { | ||
this.clusterService = clusterService; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We might need to add lockService not initialized check in every Ip2GeoLockService methods. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should never happen (i.e. onNodeStarted will always be called and the lock service will get set accordingly) but these checks would be good to put in none-the-less. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added the checks |
||
this.lockService = new LockService(client, clusterService); | ||
} | ||
|
||
public void initialize(final LockService lockService) { | ||
this.lockService = lockService; | ||
} | ||
|
||
/** | ||
|
@@ -54,6 +55,9 @@ public Ip2GeoLockService(final ClusterService clusterService, final Client clien | |
* @param listener the listener | ||
*/ | ||
public void acquireLock(final String datasourceName, final Long lockDurationSeconds, final ActionListener<LockModel> listener) { | ||
if (lockService == null) { | ||
throw new OpenSearchException("Ip2GeoLockService is not initialized"); | ||
} | ||
lockService.acquireLockWithId(JOB_INDEX_NAME, lockDurationSeconds, datasourceName, listener); | ||
} | ||
|
||
|
@@ -65,6 +69,9 @@ public void acquireLock(final String datasourceName, final Long lockDurationSeco | |
* @return lock model | ||
*/ | ||
public Optional<LockModel> acquireLock(final String datasourceName, final Long lockDurationSeconds) { | ||
if (lockService == null) { | ||
throw new OpenSearchException("Ip2GeoLockService is not initialized"); | ||
} | ||
AtomicReference<LockModel> lockReference = new AtomicReference(); | ||
CountDownLatch countDownLatch = new CountDownLatch(1); | ||
lockService.acquireLockWithId(JOB_INDEX_NAME, lockDurationSeconds, datasourceName, new ActionListener<>() { | ||
|
@@ -95,6 +102,9 @@ public void onFailure(final Exception e) { | |
* @param lockModel the lock model | ||
*/ | ||
public void releaseLock(final LockModel lockModel) { | ||
if (lockService == null) { | ||
throw new OpenSearchException("Ip2GeoLockService is not initialized"); | ||
} | ||
lockService.release( | ||
lockModel, | ||
ActionListener.wrap(released -> {}, exception -> log.error("Failed to release the lock", exception)) | ||
|
@@ -108,6 +118,9 @@ public void releaseLock(final LockModel lockModel) { | |
* @return renewed lock if renew succeed and null otherwise | ||
*/ | ||
public LockModel renewLock(final LockModel lockModel) { | ||
if (lockService == null) { | ||
throw new OpenSearchException("Ip2GeoLockService is not initialized"); | ||
} | ||
AtomicReference<LockModel> lockReference = new AtomicReference(); | ||
CountDownLatch countDownLatch = new CountDownLatch(1); | ||
lockService.renewLock(lockModel, new ActionListener<>() { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -16,10 +16,14 @@ | |
import org.opensearch.action.ActionRequest; | ||
import org.opensearch.client.Client; | ||
import org.opensearch.cluster.metadata.IndexNameExpressionResolver; | ||
import org.opensearch.cluster.node.DiscoveryNode; | ||
import org.opensearch.cluster.node.DiscoveryNodes; | ||
import org.opensearch.cluster.service.ClusterService; | ||
import org.opensearch.common.collect.MapBuilder; | ||
import org.opensearch.common.inject.Inject; | ||
import org.opensearch.common.lifecycle.Lifecycle; | ||
import org.opensearch.common.lifecycle.LifecycleComponent; | ||
import org.opensearch.common.lifecycle.LifecycleListener; | ||
import org.opensearch.common.settings.ClusterSettings; | ||
import org.opensearch.common.settings.IndexScopedSettings; | ||
import org.opensearch.common.settings.Setting; | ||
|
@@ -75,7 +79,9 @@ | |
import org.opensearch.index.mapper.Mapper; | ||
import org.opensearch.indices.SystemIndexDescriptor; | ||
import org.opensearch.ingest.Processor; | ||
import org.opensearch.jobscheduler.spi.utils.LockService; | ||
import org.opensearch.plugins.ActionPlugin; | ||
import org.opensearch.plugins.ClusterPlugin; | ||
import org.opensearch.plugins.IngestPlugin; | ||
import org.opensearch.plugins.MapperPlugin; | ||
import org.opensearch.plugins.Plugin; | ||
|
@@ -96,11 +102,22 @@ | |
* to interact with Cluster. | ||
*/ | ||
@Log4j2 | ||
public class GeospatialPlugin extends Plugin implements IngestPlugin, ActionPlugin, MapperPlugin, SearchPlugin, SystemIndexPlugin { | ||
public class GeospatialPlugin extends Plugin | ||
implements | ||
IngestPlugin, | ||
ActionPlugin, | ||
MapperPlugin, | ||
SearchPlugin, | ||
SystemIndexPlugin, | ||
ClusterPlugin { | ||
private Ip2GeoCachedDao ip2GeoCachedDao; | ||
private DatasourceDao datasourceDao; | ||
private GeoIpDataDao geoIpDataDao; | ||
private URLDenyListChecker urlDenyListChecker; | ||
private ClusterService clusterService; | ||
private Ip2GeoLockService ip2GeoLockService; | ||
private Ip2GeoExecutor ip2GeoExecutor; | ||
private DatasourceUpdateService datasourceUpdateService; | ||
|
||
@Override | ||
public Collection<SystemIndexDescriptor> getSystemIndexDescriptors(Settings settings) { | ||
|
@@ -129,7 +146,10 @@ public void onIndexModule(IndexModule indexModule) { | |
|
||
@Override | ||
public Collection<Class<? extends LifecycleComponent>> getGuiceServiceClasses() { | ||
return List.of(Ip2GeoListener.class); | ||
final List<Class<? extends LifecycleComponent>> services = new ArrayList<>(2); | ||
services.add(Ip2GeoListener.class); | ||
services.add(GuiceHolder.class); | ||
return services; | ||
} | ||
|
||
@Override | ||
|
@@ -158,20 +178,10 @@ public Collection<Object> createComponents( | |
IndexNameExpressionResolver indexNameExpressionResolver, | ||
Supplier<RepositoriesService> repositoriesServiceSupplier | ||
) { | ||
DatasourceUpdateService datasourceUpdateService = new DatasourceUpdateService( | ||
clusterService, | ||
datasourceDao, | ||
geoIpDataDao, | ||
urlDenyListChecker | ||
); | ||
Ip2GeoExecutor ip2GeoExecutor = new Ip2GeoExecutor(threadPool); | ||
Ip2GeoLockService ip2GeoLockService = new Ip2GeoLockService(clusterService, client); | ||
/** | ||
* We don't need to return datasource runner because it is used only by job scheduler and job scheduler | ||
* does not use DI but it calls DatasourceExtension#getJobRunner to get DatasourceRunner instance. | ||
*/ | ||
DatasourceRunner.getJobRunnerInstance() | ||
.initialize(clusterService, datasourceUpdateService, ip2GeoExecutor, datasourceDao, ip2GeoLockService); | ||
this.clusterService = clusterService; | ||
this.datasourceUpdateService = new DatasourceUpdateService(clusterService, datasourceDao, geoIpDataDao, urlDenyListChecker); | ||
this.ip2GeoExecutor = new Ip2GeoExecutor(threadPool); | ||
this.ip2GeoLockService = new Ip2GeoLockService(clusterService); | ||
|
||
return List.of( | ||
UploadStats.getInstance(), | ||
|
@@ -265,4 +275,48 @@ public List<AggregationSpec> getAggregations() { | |
|
||
return List.of(geoHexGridSpec); | ||
} | ||
|
||
@Override | ||
public void onNodeStarted(DiscoveryNode localNode) { | ||
LockService lockService = GuiceHolder.getLockService(); | ||
ip2GeoLockService.initialize(lockService); | ||
|
||
DatasourceRunner.getJobRunnerInstance() | ||
.initialize(this.clusterService, this.datasourceUpdateService, this.ip2GeoExecutor, this.datasourceDao, this.ip2GeoLockService); | ||
} | ||
|
||
public static class GuiceHolder implements LifecycleComponent { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we move this class to its own file? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Its a static inner class so I think it needs to be defined in this file. This is following a similar pattern present in the security plugin. This is a way to do dependency injection outside of the transport layer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It does not matter where and how the class is defined. As long as you return your class in
Anyway, I think having this class inside Plugin class might make sense if we want it to be used only inside this Plugin class. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Switched to package-private There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Looks like it needs to be public:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In such case, making methods as package private might be enough. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any of the overridden methods need to be marked public. I marked There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes. I was talking only for those static methods :) Sorry for the confusion. |
||
|
||
private static LockService lockService; | ||
|
||
@Inject | ||
public GuiceHolder(final LockService lockService) { | ||
GuiceHolder.lockService = lockService; | ||
} | ||
|
||
static LockService getLockService() { | ||
return lockService; | ||
} | ||
|
||
@Override | ||
public void close() {} | ||
|
||
@Override | ||
public Lifecycle.State lifecycleState() { | ||
return null; | ||
} | ||
|
||
@Override | ||
public void addLifecycleListener(LifecycleListener listener) {} | ||
|
||
@Override | ||
public void removeLifecycleListener(LifecycleListener listener) {} | ||
|
||
@Override | ||
public void start() {} | ||
|
||
@Override | ||
public void stop() {} | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* Copyright OpenSearch Contributors | ||
* SPDX-License-Identifier: Apache-2.0 | ||
*/ | ||
|
||
package org.opensearch.geospatial; | ||
|
||
import java.util.ArrayList; | ||
import java.util.Collection; | ||
import java.util.List; | ||
|
||
import org.opensearch.common.lifecycle.LifecycleComponent; | ||
import org.opensearch.geospatial.ip2geo.listener.Ip2GeoListener; | ||
import org.opensearch.geospatial.plugin.GeospatialPlugin; | ||
|
||
/** | ||
* This class is needed for ClusterSettingsHelper.createMockNode to instantiate a test instance of the | ||
* GeospatialPlugin without the JobSchedulerPlugin installed. Without overriding this class, the | ||
* GeospatialPlugin would try to Inject JobScheduler's LockService in the GuiceHolder which will | ||
* fail because JobScheduler is not installed | ||
*/ | ||
public class TestGeospatialPlugin extends GeospatialPlugin { | ||
@Override | ||
public Collection<Class<? extends LifecycleComponent>> getGuiceServiceClasses() { | ||
final List<Class<? extends LifecycleComponent>> services = new ArrayList<>(1); | ||
services.add(Ip2GeoListener.class); | ||
return services; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't it be better to make Ip2GeoLockService to be LifecycleComponent and get injected both cluster service and lock service?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is still being returned from
createComponents
so that its injectable. The difference in this PR is that it now implementsonNodeStarted
to get the instance of LockService instantiated by the job scheduler from the GuiceHolder and then sets it on the Ip2GeoLockService (Lock Service Wrapper) instance.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking like
This way, we don't need an additional class.
However, this approach opens up the possibility for the
public static Ip2GeoLockService getLockService()
method to be used across different parts of the code which is not desirable. Therefore, I think having a separate class might be better in that regard.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That looks like a good solution, but how do you refer to the instance of the LifecycleComponent that's created?
For instance this line:
It takes the
ip2GeoLockService
as the last arg. When using the service class, how do you get a hold of an instance?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can call
Ip2GeoLockService.getLockService()
However, I think having a
GuiceHolder
class might be better not to expose that static method.