224 lines
8.2 KiB
Java
224 lines
8.2 KiB
Java
package ru.kirillius.pf.sdn.service;
|
|
|
|
import jakarta.annotation.PostConstruct;
|
|
import jakarta.annotation.PreDestroy;
|
|
import lombok.RequiredArgsConstructor;
|
|
import lombok.extern.slf4j.Slf4j;
|
|
import org.springframework.stereotype.Service;
|
|
import ru.kirillius.java.utils.events.EventListener;
|
|
import ru.kirillius.pf.sdn.api.Networking.Subscriptions.Subscription;
|
|
import ru.kirillius.pf.sdn.api.flow.Flow;
|
|
import ru.kirillius.pf.sdn.api.flow.FlowAction;
|
|
import ru.kirillius.pf.sdn.api.flow.FlowFunction;
|
|
import ru.kirillius.pf.sdn.api.flow.TriggerType;
|
|
import ru.kirillius.pf.sdn.entity.ActionConfig;
|
|
import ru.kirillius.pf.sdn.entity.FlowConfig;
|
|
import ru.kirillius.pf.sdn.entity.SubscriptionGroup;
|
|
import ru.kirillius.pf.sdn.flow.DummyFunction;
|
|
import ru.kirillius.pf.sdn.repository.PipelineConfigRepository;
|
|
|
|
import java.util.Arrays;
|
|
import java.util.Map;
|
|
import java.util.Set;
|
|
import java.util.concurrent.ConcurrentHashMap;
|
|
import java.util.concurrent.ExecutorService;
|
|
import java.util.regex.Pattern;
|
|
|
|
@Slf4j
|
|
@Service
|
|
@RequiredArgsConstructor
|
|
public class FlowService {
|
|
|
|
private final PipelineConfigRepository configRepository;
|
|
private final FlowExecutorService flowExecutorService;
|
|
private final SubscriptionService subscriptionService;
|
|
private final ExecutorService executorService;
|
|
|
|
private final Map<FlowConfig, Flow> flows = new ConcurrentHashMap<>();
|
|
private final Map<String, Class<? extends FlowFunction>> functions = new ConcurrentHashMap<>();
|
|
private EventListener<Flow> executedListener;
|
|
private EventListener<Subscription> subscriptionUpdateListener;
|
|
private EventListener<SubscriptionGroup> subscriptionSetUpdateListener;
|
|
|
|
public void registerFunction(Class<? extends FlowFunction> functionClass, String id) {
|
|
if (functions.containsKey(id)) {
|
|
throw new IllegalStateException("Function with id '" + id + "' already exists");
|
|
}
|
|
functions.put(id, functionClass);
|
|
}
|
|
|
|
public Set<String> getFunctionNames() {
|
|
return functions.keySet();
|
|
}
|
|
|
|
public void trigger(long id) {
|
|
flows.keySet().stream().filter(config -> config.getId().equals(id)).findFirst().ifPresent(this::trigger);
|
|
}
|
|
|
|
public void trigger(FlowConfig flowConfig) {
|
|
if (flows.containsKey(flowConfig)) {
|
|
trigger(flows.get(flowConfig));
|
|
}
|
|
}
|
|
|
|
public void trigger(Flow pipeline) {
|
|
var matchedConditions = pipeline.getConfig().getStartConditions().stream().filter(c -> c.getTrigger() == TriggerType.Manual).toList();
|
|
if (matchedConditions.isEmpty()) {
|
|
log.error("Unable to manual start pipeline {} because it has no manual trigger", pipeline.getConfig().getName());
|
|
return;
|
|
}
|
|
matchedConditions.forEach(c -> {
|
|
if (c.isDontStartIfRunning() && pipeline.isRunning()) {
|
|
return;
|
|
}
|
|
flowExecutorService.triggerExecute(pipeline);
|
|
});
|
|
}
|
|
|
|
public void loadFlow(Flow flow) {
|
|
loadFlow(flow.getConfig());
|
|
}
|
|
|
|
public void loadFlow(FlowConfig flowConfig) {
|
|
if (configRepository.existsById(flowConfig.getId())) {
|
|
reloadFlowInternal(flowConfig);
|
|
} else {
|
|
flowExecutorService.cancel(flows.get(flowConfig));
|
|
flows.remove(flowConfig);
|
|
}
|
|
}
|
|
|
|
private void reloadFlowInternal(FlowConfig flowConfig) {
|
|
if (flows.containsKey(flowConfig)) {
|
|
flowExecutorService.cancel(flows.get(flowConfig));
|
|
}
|
|
flows.put(flowConfig, new Flow(flowConfig, this::buildAction));
|
|
}
|
|
|
|
private FlowAction buildAction(ActionConfig config) {
|
|
return new FlowAction(functions.getOrDefault(config.getFunctionId(), DummyFunction.class), config);
|
|
}
|
|
|
|
private void checkForIntervalExecution(Flow pipeline) {
|
|
|
|
var matchedConditions = pipeline.getConfig().getStartConditions().stream().filter(c -> c.getTrigger() == TriggerType.Interval).toList();
|
|
|
|
if (matchedConditions.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
matchedConditions.forEach(c -> {
|
|
var properties = c.getProperties();
|
|
var timer = properties.getOrDefault("interval", null);
|
|
if (timer == null) {
|
|
return;
|
|
}
|
|
var delay = Long.parseLong(timer);
|
|
if (delay <= 0) {
|
|
return;
|
|
}
|
|
|
|
executorService.submit(() -> {
|
|
try {
|
|
Thread.sleep(delay * 60000L);
|
|
} catch (InterruptedException e) {
|
|
return;
|
|
}
|
|
if (c.isDontStartIfRunning() && pipeline.isRunning()) {
|
|
return;
|
|
}
|
|
flowExecutorService.triggerExecute(pipeline);
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
@PostConstruct
|
|
private void initialize() {
|
|
subscriptionUpdateListener = subscriptionService.getUpdateEvent().add(this::subscriptionUpdate);
|
|
subscriptionSetUpdateListener = subscriptionService.getSetUpdateEvent().add(this::subscriptionSetUpdate);
|
|
executedListener = flowExecutorService.getOnExecuted().add(this::checkForIntervalExecution);
|
|
registerFunction(DummyFunction.class, "Error:fallback");
|
|
configRepository.findAll().forEach(this::reloadFlowInternal);
|
|
flows.values()
|
|
.stream()
|
|
.filter(p -> p
|
|
.getConfig()
|
|
.getStartConditions()
|
|
.stream()
|
|
.anyMatch(c -> c.getTrigger() == TriggerType.OnStart))
|
|
.forEach(flowExecutorService::triggerExecute);
|
|
}
|
|
|
|
private void subscriptionSetUpdate(SubscriptionGroup setEntry) {
|
|
flows.values().forEach(pipeline -> {
|
|
var matchedConditions = pipeline.getConfig().getStartConditions().stream().filter(c -> c.getTrigger() == TriggerType.OnSubscriptionGroupUpdate).toList();
|
|
if (matchedConditions.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
matchedConditions.forEach(c -> {
|
|
if (c.isDontStartIfRunning() && pipeline.isRunning()) {
|
|
return;
|
|
}
|
|
|
|
var properties = c.getProperties();
|
|
var names = properties.getOrDefault("names", null);
|
|
if (names == null || names.isEmpty()) {
|
|
flowExecutorService.triggerExecute(pipeline);
|
|
return;
|
|
}
|
|
|
|
if (Arrays.stream(names.split(Pattern.quote(","))).anyMatch(s -> setEntry.getName().equals(s))) {
|
|
flowExecutorService.triggerExecute(pipeline);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
private void subscriptionUpdate(Subscription subscription) {
|
|
|
|
flows.values().forEach(pipeline -> {
|
|
var matchedConditions = pipeline.getConfig().getStartConditions().stream().filter(c -> c.getTrigger() == TriggerType.OnSubscriptionUpdate).toList();
|
|
if (matchedConditions.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
matchedConditions.forEach(c -> {
|
|
if (c.isDontStartIfRunning() && pipeline.isRunning()) {
|
|
return;
|
|
}
|
|
|
|
var properties = c.getProperties();
|
|
var names = properties.getOrDefault("names", null);
|
|
if (names == null || names.isEmpty()) {
|
|
flowExecutorService.triggerExecute(pipeline);
|
|
return;
|
|
}
|
|
|
|
if (Arrays.stream(names.split(Pattern.quote(","))).anyMatch(s -> subscription.getName().equals(s))) {
|
|
flowExecutorService.triggerExecute(pipeline);
|
|
}
|
|
});
|
|
});
|
|
|
|
}
|
|
|
|
@PreDestroy
|
|
private void destroy() {
|
|
if (executedListener != null) {
|
|
flowExecutorService.getOnExecuted().remove(executedListener);
|
|
executedListener = null;
|
|
}
|
|
if (subscriptionUpdateListener != null) {
|
|
subscriptionService.getUpdateEvent().remove(subscriptionUpdateListener);
|
|
subscriptionUpdateListener = null;
|
|
}
|
|
if (subscriptionSetUpdateListener != null) {
|
|
subscriptionService.getSetUpdateEvent().remove(subscriptionSetUpdateListener);
|
|
subscriptionSetUpdateListener = null;
|
|
}
|
|
}
|
|
|
|
}
|