Skip to content

Commit 4b23ba5

Browse files
committed
[SYNCOPE-1947] Job actuator endpoint
1 parent 41f7a7c commit 4b23ba5

File tree

9 files changed

+115
-14
lines changed

9 files changed

+115
-14
lines changed

client/idrepo/console/src/main/java/org/apache/syncope/client/console/widgets/JobWidget.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,10 +213,7 @@ protected List<JobTO> getUpdatedAvailable() {
213213
List<JobTO> updatedAvailable = new ArrayList<>();
214214

215215
if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.NOTIFICATION_LIST)) {
216-
JobTO notificationJob = notificationRestClient.getJob();
217-
if (notificationJob != null) {
218-
updatedAvailable.add(notificationJob);
219-
}
216+
Optional.ofNullable(notificationRestClient.getJob()).ifPresent(updatedAvailable::add);
220217
}
221218
if (SyncopeConsoleSession.get().owns(IdRepoEntitlement.TASK_LIST)) {
222219
updatedAvailable.addAll(taskRestClient.listJobs());
@@ -425,6 +422,8 @@ public void onClick(final AjaxRequestTarget target, final JobTO ignore) {
425422
taskType = TaskType.SCHEDULED;
426423
} else if (jobTO.getRefDesc().startsWith("PULL")) {
427424
taskType = TaskType.PULL;
425+
} else if (jobTO.getRefDesc().startsWith("LIVE_SYNC")) {
426+
taskType = TaskType.LIVE_SYNC;
428427
} else if (jobTO.getRefDesc().startsWith("PUSH")) {
429428
taskType = TaskType.PUSH;
430429
} else if (jobTO.getRefDesc().startsWith("MACRO")) {

core/provisioning-java/src/main/java/org/apache/syncope/core/provisioning/java/job/SyncopeTaskScheduler.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,16 +39,14 @@
3939

4040
public class SyncopeTaskScheduler {
4141

42-
protected record Key(String domain, String job) {
42+
public record Key(String domain, String job) {
4343

4444
}
4545

46-
protected record Value(Job job, Optional<ScheduledFuture<?>> instant, Optional<ScheduledFuture<?>> cron) {
46+
public record Value(Job job, Optional<ScheduledFuture<?>> instant, Optional<ScheduledFuture<?>> cron) {
4747

4848
}
4949

50-
public static final String CACHE = "jobCache";
51-
5250
protected static final Logger LOG = LoggerFactory.getLogger(SyncopeTaskScheduler.class);
5351

5452
protected final TaskScheduler scheduler;
@@ -152,4 +150,8 @@ public Optional<OffsetDateTime> getNextTrigger(final String domain, final String
152150
public List<String> getJobNames(final String domain) {
153151
return jobs.keySet().stream().filter(key -> domain.equals(key.domain())).map(Key::job).toList();
154152
}
153+
154+
public Map<Key, Value> getJobs() {
155+
return Map.copyOf(jobs);
156+
}
155157
}

core/self-keymaster-starter/src/test/resources/core-debug.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ service.discovery.address=http://localhost:9080/syncope/rest/
2020

2121
logging.config=file://${project.build.testOutputDirectory}/log4j2.xml
2222

23-
management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,metrics
23+
management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,job,metrics
2424

2525
keymaster.address=http://localhost:9080/syncope/rest/keymaster
2626
keymaster.username=${anonymousUser}

core/starter/src/main/java/org/apache/syncope/core/starter/SyncopeCoreApplication.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@
4242
import org.apache.syncope.core.provisioning.api.ConnectorManager;
4343
import org.apache.syncope.core.provisioning.api.ImplementationLookup;
4444
import org.apache.syncope.core.provisioning.api.data.ConnInstanceDataBinder;
45+
import org.apache.syncope.core.provisioning.java.job.SyncopeTaskScheduler;
4546
import org.apache.syncope.core.starter.actuate.DefaultSyncopeCoreInfoContributor;
4647
import org.apache.syncope.core.starter.actuate.DomainsHealthIndicator;
4748
import org.apache.syncope.core.starter.actuate.EntityCacheEndpoint;
4849
import org.apache.syncope.core.starter.actuate.ExternalResourcesHealthIndicator;
50+
import org.apache.syncope.core.starter.actuate.JobEndpoint;
4951
import org.apache.syncope.core.starter.actuate.SyncopeCoreInfoContributor;
5052
import org.springframework.beans.factory.ListableBeanFactory;
5153
import org.springframework.beans.factory.annotation.Qualifier;
@@ -197,6 +199,12 @@ public EntityCacheEndpoint entityCacheEndpoint(final EntityCacheDAO entityCacheD
197199
return new EntityCacheEndpoint(entityCacheDAO);
198200
}
199201

202+
@ConditionalOnMissingBean
203+
@Bean
204+
public JobEndpoint jobEndpoint(final SyncopeTaskScheduler syncopeTaskScheduler) {
205+
return new JobEndpoint(syncopeTaskScheduler);
206+
}
207+
200208
@Bean
201209
public SyncopeStarterEventListener syncopeCoreEventListener(
202210
@Qualifier("syncopeCoreInfoContributor")
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.syncope.core.starter.actuate;
20+
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
import java.util.concurrent.TimeUnit;
24+
import org.apache.syncope.common.lib.types.JobAction;
25+
import org.apache.syncope.core.provisioning.java.job.SyncopeTaskScheduler;
26+
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
27+
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
28+
import org.springframework.boot.actuate.endpoint.annotation.Selector;
29+
import org.springframework.boot.actuate.endpoint.annotation.WriteOperation;
30+
31+
@Endpoint(id = "job")
32+
public class JobEndpoint {
33+
34+
protected final SyncopeTaskScheduler syncopeTaskScheduler;
35+
36+
public JobEndpoint(final SyncopeTaskScheduler syncopeTaskScheduler) {
37+
this.syncopeTaskScheduler = syncopeTaskScheduler;
38+
}
39+
40+
@ReadOperation
41+
public Map<String, Object> status() {
42+
Map<String, Object> status = new HashMap<>();
43+
44+
syncopeTaskScheduler.getJobs().forEach((k, v) -> {
45+
@SuppressWarnings("unchecked")
46+
Map<String, Object> jobs = (Map<String, Object>) status.computeIfAbsent(k.domain(), d -> new HashMap<>());
47+
48+
Map<String, Object> job = new HashMap<>();
49+
jobs.put(k.job(), job);
50+
51+
job.put("executor", v.job().getContext().getExecutor());
52+
job.put("dryRun", v.job().getContext().isDryRun());
53+
job.put("context", v.job().getContext().getData());
54+
55+
v.instant().ifPresent(f -> job.put("delay (seconds)", f.getDelay(TimeUnit.SECONDS)));
56+
57+
v.cron().ifPresent(f -> {
58+
job.put("next schedule (seconds)", f.getDelay(TimeUnit.SECONDS));
59+
job.put("done", f.isDone());
60+
job.put("cancelled", f.isCancelled());
61+
});
62+
});
63+
64+
return status;
65+
}
66+
67+
@WriteOperation
68+
public void action(
69+
final @Selector String domain,
70+
final @Selector String jobName,
71+
final @Selector JobAction action) {
72+
73+
switch (action) {
74+
case START ->
75+
syncopeTaskScheduler.start(domain, jobName);
76+
77+
case STOP ->
78+
syncopeTaskScheduler.stop(domain, jobName);
79+
80+
case DELETE ->
81+
syncopeTaskScheduler.delete(domain, jobName);
82+
83+
default -> {
84+
}
85+
}
86+
}
87+
}

core/starter/src/main/resources/core.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ server.servlet.encoding.force=true
2727
server.servlet.contextPath=/syncope
2828
cxf.path=/rest
2929

30-
management.endpoints.web.exposure.include=health,info,loggers,entityCache
30+
management.endpoints.web.exposure.include=health,info,loggers,entityCache,job
3131
management.endpoint.health.show-details=ALWAYS
3232
management.endpoint.env.show-values=WHEN_AUTHORIZED
3333

fit/core-reference/src/main/resources/core-embedded.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
# under the License.
1717
embedded.databases=syncope,syncopetwo,syncopetest
1818

19-
management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,metrics
19+
management.endpoints.web.exposure.include=health,info,beans,env,loggers,entityCache,job,metrics
2020

2121
keymaster.address=http://localhost:9080/syncope/rest/keymaster
2222
keymaster.username=${anonymousUser}

src/main/asciidoc/reference-guide/concepts/routes.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ The received response, after being post-processed by matching route's _filters_,
5050
==== Predicates
5151

5252
Inside Route definition, each predicate will be referring to some Spring Cloud Gateway's
53-
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/request-predicates-factories.html[Predicate factory^]:
53+
https://docs.spring.io/spring-cloud-gateway/reference/4.2/spring-cloud-gateway/request-predicates-factories.html[Predicate factory^]:
5454

5555
* `AFTER` matches requests that happen after the specified datetime;
5656
* `BEFORE` matches requests that happen before the specified datetime;
@@ -74,7 +74,7 @@ endif::[]
7474
==== Filters
7575

7676
Inside Route definition, each filter will be referring to some Spring Cloud Gateway's
77-
https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/gatewayfilter-factories.html[Filter factory^]:
77+
https://docs.spring.io/spring-cloud-gateway/reference/4.2/spring-cloud-gateway/gatewayfilter-factories.html[Filter factory^]:
7878

7979
* `ADD_REQUEST_HEADER` adds a header to the downstream request's headers;
8080
* `ADD_REQUEST_PARAMETER` adds a parameter too the downstream request's query string;

src/main/asciidoc/reference-guide/usage/actuator.adoc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ a| Allows to work with https://openjpa.apache.org/builds/4.0.1/apache-openjpa/do
4545
* `GET` - shows JPA cache statistics
4646
* `POST {ENABLE,DISABLE,RESET}` - performs the requested operation onto JPA cache
4747
* `DELETE` - clears JPA cache's current content
48+
| `job`
49+
a| Allows to work with the various jobs defined after <<tasks>> and <<reports>>.
50+
51+
* `GET` - shows the existing job data
52+
* `POST {START,STOP,DELETE}` - performs the requested action on a given Job
4853

4954
|===
5055

@@ -80,6 +85,6 @@ a|
8085
* `DELETE {id}` - removes the session with given `id`
8186

8287
| `gateway`
83-
| https://docs.spring.io/spring-cloud-gateway/reference/spring-cloud-gateway/actuator-api.html[More details^]
88+
| https://docs.spring.io/spring-cloud-gateway/reference/4.2/spring-cloud-gateway/actuator-api.html[More details^]
8489

8590
|===

0 commit comments

Comments
 (0)