Fix the problem not allowing logging in after upgrading Halo (#3603)

#### What type of PR is this?

/kind bug
/area core

#### What this PR does / why we need it:

This PR makes extension initialization before starting reconcilers to prevent modification conflicts.

#### Which issue(s) this PR fixes:

Fixes https://github.com/halo-dev/halo/issues/3601

#### Special notes for your reviewer:

1. Try to run Halo 2.3.2 with command `docker run --rm -it -v ~/halo2-dev:/root/.halo2 -p 8090:8090 halohub/halo:2.3.2`
2. Then run Halo 2.4.0-SNAPSHOT with dev profile.

    ```bash
    ./gradlew bootRun --args="--spring.profiles.active=dev"
    ```
4. Check logs and logging functionality
5. Repeat steps above

#### Does this PR introduce a user-facing change?

```release-note
None
```
pull/3623/head
John Niang 2023-03-29 13:46:14 +08:00 committed by GitHub
parent 1e6992eef1
commit 79f1393395
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 44 additions and 30 deletions

View File

@ -11,11 +11,11 @@ import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import run.halo.app.extension.ExtensionClient; import run.halo.app.extension.ExtensionClient;
import run.halo.app.extension.controller.Reconciler.Request; import run.halo.app.extension.controller.Reconciler.Request;
import run.halo.app.infra.SchemeInitializedEvent; import run.halo.app.infra.ExtensionInitializedEvent;
@Slf4j @Slf4j
public class DefaultControllerManager public class DefaultControllerManager
implements ApplicationListener<SchemeInitializedEvent>, implements ApplicationListener<ExtensionInitializedEvent>,
ApplicationContextAware, DisposableBean, ControllerManager { ApplicationContextAware, DisposableBean, ControllerManager {
private final ExtensionClient client; private final ExtensionClient client;
@ -68,7 +68,7 @@ public class DefaultControllerManager
} }
@Override @Override
public void onApplicationEvent(SchemeInitializedEvent event) { public void onApplicationEvent(ExtensionInitializedEvent event) {
// register reconcilers in system after scheme initialized // register reconcilers in system after scheme initialized
applicationContext.<Reconciler<Request>>getBeanProvider( applicationContext.<Reconciler<Request>>getBeanProvider(
forClassWithGenerics(Reconciler.class, Request.class)) forClassWithGenerics(Reconciler.class, Request.class))

View File

@ -4,11 +4,11 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import run.halo.app.extension.controller.Controller; import run.halo.app.extension.controller.Controller;
import run.halo.app.infra.SchemeInitializedEvent; import run.halo.app.infra.ExtensionInitializedEvent;
@Component @Component
public class GcControllerInitializer public class GcControllerInitializer
implements ApplicationListener<SchemeInitializedEvent>, DisposableBean { implements ApplicationListener<ExtensionInitializedEvent>, DisposableBean {
private final Controller gcController; private final Controller gcController;
@ -17,7 +17,7 @@ public class GcControllerInitializer
} }
@Override @Override
public void onApplicationEvent(SchemeInitializedEvent event) { public void onApplicationEvent(ExtensionInitializedEvent event) {
gcController.start(); gcController.start();
} }

View File

@ -0,0 +1,16 @@
package run.halo.app.infra;
import org.springframework.context.ApplicationEvent;
/**
* ExtensionInitializedEvent is fired after extensions have been initialized completely.
*
* @author johnniang
*/
public class ExtensionInitializedEvent extends ApplicationEvent {
public ExtensionInitializedEvent(Object source) {
super(source);
}
}

View File

@ -1,21 +1,18 @@
package run.halo.app.infra; package run.halo.app.infra;
import java.io.IOException; import java.io.IOException;
import java.time.Duration;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.event.EventListener; import org.springframework.context.event.EventListener;
import org.springframework.core.io.Resource; import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.dao.OptimisticLockingFailureException;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.util.retry.Retry;
import run.halo.app.extension.ReactiveExtensionClient; import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.extension.Unstructured; import run.halo.app.extension.Unstructured;
import run.halo.app.infra.properties.HaloProperties; import run.halo.app.infra.properties.HaloProperties;
@ -38,14 +35,18 @@ public class ExtensionResourceInitializer {
private final HaloProperties haloProperties; private final HaloProperties haloProperties;
private final ReactiveExtensionClient extensionClient; private final ReactiveExtensionClient extensionClient;
private final ApplicationEventPublisher eventPublisher;
public ExtensionResourceInitializer(HaloProperties haloProperties, public ExtensionResourceInitializer(HaloProperties haloProperties,
ReactiveExtensionClient extensionClient) { ReactiveExtensionClient extensionClient,
ApplicationEventPublisher eventPublisher) {
this.haloProperties = haloProperties; this.haloProperties = haloProperties;
this.extensionClient = extensionClient; this.extensionClient = extensionClient;
this.eventPublisher = eventPublisher;
} }
@EventListener(ApplicationReadyEvent.class) @EventListener(SchemeInitializedEvent.class)
public Mono<Void> initialize(ApplicationReadyEvent readyEvent) { public Mono<Void> initialize(SchemeInitializedEvent initializedEvent) {
var locations = new HashSet<String>(); var locations = new HashSet<String>();
if (!haloProperties.isRequiredExtensionDisabled()) { if (!haloProperties.isRequiredExtensionDisabled()) {
locations.addAll(REQUIRED_EXTENSION_LOCATIONS); locations.addAll(REQUIRED_EXTENSION_LOCATIONS);
@ -80,7 +81,8 @@ public class ExtensionResourceInitializer {
extension.getMetadata().getName()); extension.getMetadata().getName());
} }
}) })
.then(); .then(Mono.fromRunnable(
() -> eventPublisher.publishEvent(new ExtensionInitializedEvent(this))));
} }
private Mono<Unstructured> createOrUpdate(Unstructured extension) { private Mono<Unstructured> createOrUpdate(Unstructured extension) {
@ -88,17 +90,11 @@ public class ExtensionResourceInitializer {
.flatMap(ext -> extensionClient.fetch(extension.groupVersionKind(), .flatMap(ext -> extensionClient.fetch(extension.groupVersionKind(),
extension.getMetadata().getName())) extension.getMetadata().getName()))
.flatMap(existingExt -> { .flatMap(existingExt -> {
// force update
extension.getMetadata().setVersion(existingExt.getMetadata().getVersion()); extension.getMetadata().setVersion(existingExt.getMetadata().getVersion());
return extensionClient.update(extension); return extensionClient.update(extension);
}) })
.switchIfEmpty(Mono.defer(() -> extensionClient.create(extension))) .switchIfEmpty(Mono.defer(() -> extensionClient.create(extension)));
.retryWhen(Retry.fixedDelay(3, Duration.ofMillis(100))
.filter(t -> t instanceof OptimisticLockingFailureException))
.onErrorContinue(OptimisticLockingFailureException.class, (throwable, o) -> {
log.warn("Failed to create or update extension resource: {}/{} due to modification "
+ "conflict",
extension.groupVersionKind(), extension.getMetadata().getName());
});
} }
private List<Resource> listResources(String location) { private List<Resource> listResources(String location) {

View File

@ -19,10 +19,11 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor; import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.skyscreamer.jsonassert.JSONAssert; import org.skyscreamer.jsonassert.JSONAssert;
import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationEventPublisher;
import org.springframework.util.FileSystemUtils; import org.springframework.util.FileSystemUtils;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
import reactor.test.StepVerifier; import reactor.test.StepVerifier;
@ -42,21 +43,22 @@ import run.halo.app.infra.utils.JsonUtils;
class ExtensionResourceInitializerTest { class ExtensionResourceInitializerTest {
@Mock @Mock
private ReactiveExtensionClient extensionClient; ReactiveExtensionClient extensionClient;
@Mock @Mock
private HaloProperties haloProperties; HaloProperties haloProperties;
@Mock @Mock
private ApplicationReadyEvent applicationReadyEvent; SchemeInitializedEvent applicationReadyEvent;
private ExtensionResourceInitializer extensionResourceInitializer; @Mock
ApplicationEventPublisher eventPublisher;
@InjectMocks
ExtensionResourceInitializer extensionResourceInitializer;
List<Path> dirsToClean; List<Path> dirsToClean;
@BeforeEach @BeforeEach
void setUp() throws IOException { void setUp() throws IOException {
extensionResourceInitializer =
new ExtensionResourceInitializer(haloProperties, extensionClient);
dirsToClean = new ArrayList<>(2); dirsToClean = new ArrayList<>(2);
Path tempDirectory = Files.createTempDirectory("extension-resource-initializer-test"); Path tempDirectory = Files.createTempDirectory("extension-resource-initializer-test");