mirror of https://github.com/halo-dev/halo
refactor: plugin reconciler to fix entry path when optimistic lock occurred (#2625)
#### What type of PR is this? /kind improvement /area core /milestone 2.0 #### What this PR does / why we need it: 重构 PluginReconciler,避免因为每一段逻辑的执行时间太长而增加乐观锁错误的发生概率 修复判断逻辑问题导致启动时因为发生乐关锁错误造成没有填充 entry 和 stylesheet 字段的问题 #### Which issue(s) this PR fixes: Fixes #2616 #### Special notes for your reviewer: /cc @halo-dev/sig-halo #### Does this PR introduce a user-facing change? ```release-note None ```pull/2629/head
parent
160dd909cc
commit
7c3fc3ac02
|
@ -189,6 +189,8 @@ public class PluginEndpoint implements CustomEndpoint {
|
|||
.flatMap(this::transferToTemp)
|
||||
.flatMap(tempJarFilePath -> {
|
||||
var plugin = new YamlPluginFinder().find(tempJarFilePath);
|
||||
// Disable auto enable during installation
|
||||
plugin.getSpec().setEnabled(false);
|
||||
return client.fetch(Plugin.class, plugin.getMetadata().getName())
|
||||
.switchIfEmpty(Mono.defer(() -> client.create(plugin)))
|
||||
.publishOn(Schedulers.boundedElastic())
|
||||
|
@ -204,12 +206,11 @@ public class PluginEndpoint implements CustomEndpoint {
|
|||
FileUtils.copy(tempJarFilePath, pluginFilePath);
|
||||
return created;
|
||||
})
|
||||
.doOnError(error -> {
|
||||
log.error("Failed to install plugin", error);
|
||||
client.fetch(Plugin.class, plugin.getMetadata().getName())
|
||||
.map(client::delete)
|
||||
.subscribe();
|
||||
})
|
||||
.onErrorResume(
|
||||
error -> client.fetch(Plugin.class, plugin.getMetadata().getName())
|
||||
.flatMap(client::delete)
|
||||
.then(Mono.error(error))
|
||||
)
|
||||
.doFinally(signalType -> {
|
||||
try {
|
||||
Files.deleteIfExists(tempJarFilePath);
|
||||
|
|
|
@ -7,7 +7,6 @@ import java.time.Instant;
|
|||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -44,67 +43,49 @@ public class PluginReconciler implements Reconciler<Request> {
|
|||
|
||||
@Override
|
||||
public Result reconcile(Request request) {
|
||||
return client.fetch(Plugin.class, request.name())
|
||||
.map(plugin -> {
|
||||
client.fetch(Plugin.class, request.name())
|
||||
.ifPresent(plugin -> {
|
||||
if (plugin.getMetadata().getDeletionTimestamp() != null) {
|
||||
cleanUpResourcesAndRemoveFinalizer(request.name());
|
||||
return new Result(false, null);
|
||||
return;
|
||||
}
|
||||
addFinalizerIfNecessary(plugin);
|
||||
|
||||
final Plugin oldPlugin = JsonUtils.deepCopy(plugin);
|
||||
try {
|
||||
reconcilePluginState(plugin);
|
||||
// TODO: reconcile other plugin resources
|
||||
|
||||
if (!Objects.equals(oldPlugin, plugin)) {
|
||||
// update plugin when attributes changed
|
||||
client.update(plugin);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// update plugin and requeue
|
||||
client.update(plugin);
|
||||
log.error(e.getMessage(), e);
|
||||
return new Result(true, null);
|
||||
}
|
||||
return new Result(false, null);
|
||||
})
|
||||
.orElse(new Result(false, null));
|
||||
reconcilePluginState(plugin.getMetadata().getName());
|
||||
});
|
||||
return new Result(false, null);
|
||||
}
|
||||
|
||||
private void reconcilePluginState(Plugin plugin) {
|
||||
Plugin.PluginStatus pluginStatus = plugin.statusNonNull();
|
||||
String name = plugin.getMetadata().getName();
|
||||
PluginWrapper pluginWrapper = haloPluginManager.getPlugin(name);
|
||||
if (pluginWrapper == null) {
|
||||
private void reconcilePluginState(String name) {
|
||||
if (haloPluginManager.getPlugin(name) == null) {
|
||||
ensurePluginLoaded();
|
||||
pluginWrapper = haloPluginManager.getPlugin(name);
|
||||
}
|
||||
|
||||
if (pluginWrapper == null) {
|
||||
pluginStatus.setPhase(PluginState.FAILED);
|
||||
pluginStatus.setReason("PluginNotFound");
|
||||
pluginStatus.setMessage("Plugin " + name + " not found in plugin manager");
|
||||
return;
|
||||
}
|
||||
client.fetch(Plugin.class, name).ifPresent(plugin -> {
|
||||
Plugin oldPlugin = JsonUtils.deepCopy(plugin);
|
||||
Plugin.PluginStatus pluginStatus = plugin.statusNonNull();
|
||||
PluginWrapper pluginWrapper = haloPluginManager.getPlugin(name);
|
||||
if (pluginWrapper == null) {
|
||||
pluginStatus.setPhase(PluginState.FAILED);
|
||||
pluginStatus.setReason("PluginNotFound");
|
||||
pluginStatus.setMessage("Plugin " + name + " not found in plugin manager");
|
||||
} else {
|
||||
// Set to the correct state
|
||||
pluginStatus.setPhase(pluginWrapper.getPluginState());
|
||||
|
||||
if (!Objects.equals(pluginStatus.getPhase(), pluginWrapper.getPluginState())) {
|
||||
// Set to the correct state
|
||||
pluginStatus.setPhase(pluginWrapper.getPluginState());
|
||||
}
|
||||
if (haloPluginManager.getUnresolvedPlugins().contains(pluginWrapper)) {
|
||||
// load and resolve plugin
|
||||
haloPluginManager.loadPlugin(pluginWrapper.getPluginPath());
|
||||
}
|
||||
}
|
||||
|
||||
if (haloPluginManager.getUnresolvedPlugins().contains(pluginWrapper)) {
|
||||
// load and resolve plugin
|
||||
haloPluginManager.loadPlugin(pluginWrapper.getPluginPath());
|
||||
}
|
||||
if (!plugin.equals(oldPlugin)) {
|
||||
client.update(plugin);
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldReconcileStartState(plugin)) {
|
||||
startPlugin(plugin);
|
||||
}
|
||||
startPlugin(name);
|
||||
|
||||
if (shouldReconcileStopState(plugin)) {
|
||||
stopPlugin(plugin);
|
||||
}
|
||||
stopPlugin(name);
|
||||
}
|
||||
|
||||
private void ensurePluginLoaded() {
|
||||
|
@ -126,19 +107,28 @@ public class PluginReconciler implements Reconciler<Request> {
|
|||
&& plugin.statusNonNull().getPhase() != PluginState.STARTED;
|
||||
}
|
||||
|
||||
private void startPlugin(Plugin plugin) {
|
||||
String pluginName = plugin.getMetadata().getName();
|
||||
PluginState currentState = haloPluginManager.startPlugin(pluginName);
|
||||
handleStatus(plugin, currentState, PluginState.STARTED);
|
||||
Plugin.PluginStatus status = plugin.statusNonNull();
|
||||
private void startPlugin(String pluginName) {
|
||||
client.fetch(Plugin.class, pluginName).ifPresent(plugin -> {
|
||||
final Plugin oldPlugin = JsonUtils.deepCopy(plugin);
|
||||
if (shouldReconcileStartState(plugin)) {
|
||||
PluginState currentState = haloPluginManager.startPlugin(pluginName);
|
||||
handleStatus(plugin, currentState, PluginState.STARTED);
|
||||
plugin.statusNonNull().setLastStartTime(Instant.now());
|
||||
}
|
||||
|
||||
String jsBundlePath = BundleResourceUtils.getJsBundlePath(haloPluginManager, pluginName);
|
||||
status.setEntry(jsBundlePath);
|
||||
Plugin.PluginStatus status = plugin.statusNonNull();
|
||||
String jsBundlePath =
|
||||
BundleResourceUtils.getJsBundlePath(haloPluginManager, pluginName);
|
||||
status.setEntry(jsBundlePath);
|
||||
|
||||
String cssBundlePath = BundleResourceUtils.getCssBundlePath(haloPluginManager, pluginName);
|
||||
status.setStylesheet(cssBundlePath);
|
||||
String cssBundlePath =
|
||||
BundleResourceUtils.getCssBundlePath(haloPluginManager, pluginName);
|
||||
status.setStylesheet(cssBundlePath);
|
||||
|
||||
status.setLastStartTime(Instant.now());
|
||||
if (!plugin.equals(oldPlugin)) {
|
||||
client.update(plugin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private boolean shouldReconcileStopState(Plugin plugin) {
|
||||
|
@ -146,10 +136,19 @@ public class PluginReconciler implements Reconciler<Request> {
|
|||
&& plugin.statusNonNull().getPhase() == PluginState.STARTED;
|
||||
}
|
||||
|
||||
private void stopPlugin(Plugin plugin) {
|
||||
String pluginName = plugin.getMetadata().getName();
|
||||
PluginState currentState = haloPluginManager.stopPlugin(pluginName);
|
||||
handleStatus(plugin, currentState, PluginState.STOPPED);
|
||||
private void stopPlugin(String pluginName) {
|
||||
client.fetch(Plugin.class, pluginName).ifPresent(plugin -> {
|
||||
Plugin oldPlugin = JsonUtils.deepCopy(plugin);
|
||||
|
||||
if (shouldReconcileStopState(plugin)) {
|
||||
PluginState currentState = haloPluginManager.stopPlugin(pluginName);
|
||||
handleStatus(plugin, currentState, PluginState.STOPPED);
|
||||
}
|
||||
|
||||
if (!plugin.equals(oldPlugin)) {
|
||||
client.update(plugin);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleStatus(Plugin plugin, PluginState currentState,
|
||||
|
|
|
@ -26,9 +26,11 @@ public class PluginBeforeStopSyncListener {
|
|||
@EventListener
|
||||
public Mono<Void> onApplicationEvent(@NonNull HaloPluginBeforeStopEvent event) {
|
||||
var pluginWrapper = event.getPlugin();
|
||||
var pluginContext = ExtensionContextRegistry.getInstance()
|
||||
.getByPluginId(pluginWrapper.getPluginId());
|
||||
|
||||
ExtensionContextRegistry registry = ExtensionContextRegistry.getInstance();
|
||||
if (!registry.containsContext(pluginWrapper.getPluginId())) {
|
||||
return Mono.empty();
|
||||
}
|
||||
var pluginContext = registry.getByPluginId(pluginWrapper.getPluginId());
|
||||
return cleanUpPluginExtensionResources(pluginContext);
|
||||
}
|
||||
|
||||
|
|
|
@ -59,7 +59,7 @@ public class PermalinkRefreshHandler implements ApplicationListener<PermalinkRul
|
|||
String oldPermalink = post.getStatusOrDefault().getPermalink();
|
||||
String permalink = postPermalinkPolicy.permalink(post);
|
||||
post.getStatusOrDefault().setPermalink(permalink);
|
||||
if (oldPermalink.equals(permalink)) {
|
||||
if (StringUtils.equals(oldPermalink, permalink)) {
|
||||
return;
|
||||
}
|
||||
// update permalink
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package run.halo.app.core.extension.reconciler;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
|
@ -17,6 +18,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
|||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.pf4j.PluginRuntimeException;
|
||||
import org.pf4j.PluginState;
|
||||
import org.pf4j.PluginWrapper;
|
||||
import run.halo.app.core.extension.Plugin;
|
||||
|
@ -64,6 +66,7 @@ class PluginReconcilerTest {
|
|||
when(pluginWrapper.getPluginState()).thenReturn(PluginState.STOPPED);
|
||||
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileWithoutRequeue();
|
||||
verify(extensionClient, times(2)).update(any());
|
||||
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
|
@ -87,18 +90,22 @@ class PluginReconcilerTest {
|
|||
PluginStartingError.of("apples", "error message", "dev message");
|
||||
when(haloPluginManager.getPluginStartingError(any())).thenReturn(pluginStartingError);
|
||||
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileNeedRequeue();
|
||||
assertThatThrownBy(() -> {
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileNeedRequeue();
|
||||
|
||||
// Verify the state before the update plugin
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
assertThat(updateArgs.getSpec().getEnabled()).isTrue();
|
||||
// Verify the state before the update plugin
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
assertThat(updateArgs.getSpec().getEnabled()).isTrue();
|
||||
|
||||
Plugin.PluginStatus status = updateArgs.getStatus();
|
||||
assertThat(status.getPhase()).isEqualTo(PluginState.FAILED);
|
||||
assertThat(status.getReason()).isEqualTo("error message");
|
||||
assertThat(status.getMessage()).isEqualTo("dev message");
|
||||
assertThat(status.getLastStartTime()).isNull();
|
||||
}).isInstanceOf(PluginRuntimeException.class)
|
||||
.hasMessage("error message");
|
||||
|
||||
Plugin.PluginStatus status = updateArgs.getStatus();
|
||||
assertThat(status.getPhase()).isEqualTo(PluginState.FAILED);
|
||||
assertThat(status.getReason()).isEqualTo("error message");
|
||||
assertThat(status.getMessage()).isEqualTo("dev message");
|
||||
assertThat(status.getLastStartTime()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -111,6 +118,7 @@ class PluginReconcilerTest {
|
|||
when(pluginWrapper.getPluginState()).thenReturn(PluginState.STARTED);
|
||||
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileWithoutRequeue();
|
||||
verify(extensionClient, times(2)).update(any());
|
||||
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
|
@ -145,6 +153,7 @@ class PluginReconcilerTest {
|
|||
when(pluginWrapper.getPluginState()).thenReturn(PluginState.STARTED);
|
||||
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileWithoutRequeue();
|
||||
verify(extensionClient, times(3)).update(any());
|
||||
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
|
@ -168,16 +177,19 @@ class PluginReconcilerTest {
|
|||
PluginStartingError.of("apples", "error message", "dev message");
|
||||
when(haloPluginManager.getPluginStartingError(any())).thenReturn(pluginStartingError);
|
||||
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileNeedRequeue();
|
||||
assertThatThrownBy(() -> {
|
||||
ArgumentCaptor<Plugin> pluginCaptor = doReconcileNeedRequeue();
|
||||
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
assertThat(updateArgs.getSpec().getEnabled()).isFalse();
|
||||
Plugin updateArgs = pluginCaptor.getValue();
|
||||
assertThat(updateArgs).isNotNull();
|
||||
assertThat(updateArgs.getSpec().getEnabled()).isFalse();
|
||||
|
||||
Plugin.PluginStatus status = updateArgs.getStatus();
|
||||
assertThat(status.getPhase()).isEqualTo(PluginState.FAILED);
|
||||
assertThat(status.getReason()).isEqualTo("error message");
|
||||
assertThat(status.getMessage()).isEqualTo("dev message");
|
||||
Plugin.PluginStatus status = updateArgs.getStatus();
|
||||
assertThat(status.getPhase()).isEqualTo(PluginState.FAILED);
|
||||
assertThat(status.getReason()).isEqualTo("error message");
|
||||
assertThat(status.getMessage()).isEqualTo("dev message");
|
||||
}).isInstanceOf(PluginRuntimeException.class)
|
||||
.hasMessage("error message");
|
||||
}
|
||||
|
||||
private ArgumentCaptor<Plugin> doReconcileNeedRequeue() {
|
||||
|
@ -201,8 +213,6 @@ class PluginReconcilerTest {
|
|||
Reconciler.Result result = pluginReconciler.reconcile(new Reconciler.Request("apples"));
|
||||
assertThat(result).isNotNull();
|
||||
assertThat(result.reEnqueue()).isEqualTo(false);
|
||||
|
||||
verify(extensionClient, times(2)).update(any());
|
||||
return pluginCaptor;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue