From 123fc4c3d117560067b6896c80fdef22cc287a52 Mon Sep 17 00:00:00 2001 From: Thomas Zerr Date: Thu, 2 Nov 2023 10:51:32 +0100 Subject: [PATCH] Invalidation of caches and search index MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In the general admin settings, the user can find two button to either invalidate the cache or rebuild the search index. The endpoints are defined in the InvalidationResource class in scm-webapp. Co-authored-by: René Pfeuffer --- .../user/admin/assets/cache_invalidation.png | Bin 0 -> 14742 bytes docs/de/user/admin/assets/rebuild_index.png | Bin 0 -> 17196 bytes docs/de/user/admin/index.md | 1 + docs/de/user/admin/troubleshooting.md | 30 +++++ .../user/admin/assets/cache_invalidation.png | Bin 0 -> 13630 bytes docs/en/user/admin/assets/rebuild_index.png | Bin 0 -> 16136 bytes docs/en/user/admin/index.md | 1 + docs/en/user/admin/troubleshooting.md | 28 ++++ gradle/changelog/invalidation.yaml | 2 + .../java/sonia/scm/cache/CacheManager.java | 6 + .../java/sonia/scm/cache/MapCacheManager.java | 7 + scm-ui/ui-api/src/index.ts | 1 + scm-ui/ui-api/src/useInvalidation.tsx | 50 +++++++ .../ui-webapp/public/locales/de/config.json | 12 ++ .../ui-webapp/public/locales/en/config.json | 12 ++ .../src/admin/components/form/ConfigForm.tsx | 20 ++- .../components/form/InvalidateCaches.tsx | 52 ++++++++ .../components/form/InvalidateSearchIndex.tsx | 52 ++++++++ .../src/admin/containers/GlobalConfig.tsx | 7 +- .../api/v2/resources/IndexDtoGenerator.java | 4 + .../v2/resources/InvalidationResource.java | 102 ++++++++++++++ .../scm/api/v2/resources/ResourceLinks.java | 21 +++ .../sonia/scm/cache/GuavaCacheManager.java | 7 + .../java/sonia/scm/search/IndexRebuilder.java | 46 +++++++ .../v2/resources/IndexDtoGeneratorTest.java | 62 +++++++++ .../resources/InvalidationResourceTest.java | 126 ++++++++++++++++++ .../sonia/scm/cache/CacheManagerTestBase.java | 14 ++ 27 files changed, 660 insertions(+), 3 deletions(-) create mode 100644 docs/de/user/admin/assets/cache_invalidation.png create mode 100644 docs/de/user/admin/assets/rebuild_index.png create mode 100644 docs/de/user/admin/troubleshooting.md create mode 100644 docs/en/user/admin/assets/cache_invalidation.png create mode 100644 docs/en/user/admin/assets/rebuild_index.png create mode 100644 docs/en/user/admin/troubleshooting.md create mode 100644 gradle/changelog/invalidation.yaml create mode 100644 scm-ui/ui-api/src/useInvalidation.tsx create mode 100644 scm-ui/ui-webapp/src/admin/components/form/InvalidateCaches.tsx create mode 100644 scm-ui/ui-webapp/src/admin/components/form/InvalidateSearchIndex.tsx create mode 100644 scm-webapp/src/main/java/sonia/scm/api/v2/resources/InvalidationResource.java create mode 100644 scm-webapp/src/main/java/sonia/scm/search/IndexRebuilder.java create mode 100644 scm-webapp/src/test/java/sonia/scm/api/v2/resources/InvalidationResourceTest.java diff --git a/docs/de/user/admin/assets/cache_invalidation.png b/docs/de/user/admin/assets/cache_invalidation.png new file mode 100644 index 0000000000000000000000000000000000000000..f252eb169277ad090f3b58b830815c308791f75d GIT binary patch literal 14742 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 ### Information diff --git a/docs/de/user/admin/troubleshooting.md b/docs/de/user/admin/troubleshooting.md new file mode 100644 index 0000000000..927258dcf9 --- /dev/null +++ b/docs/de/user/admin/troubleshooting.md @@ -0,0 +1,30 @@ +--- +title: Fehlerbehebung +--- + +## Caches invalidieren + +Um die Performance des SCM-Managers zu verbessern, werden viele Daten zusätzlich als Cache im Arbeitsspeicher gehalten. +Es kann passieren, dass die Daten im Cache nicht invalidiert werden, obwohl sich die zugrundeliegenden Daten geändert +haben. Dies kann zu Fehlern führen, z. B. könnten manche Ansichten versuchen ein Repository zu laden, welches bereits +gelöscht wurde. Um dieses Problem manuell zu lösen, können Administratoren den internen Cache des SCM-Managers +invalidieren. Allerdings kann diese Operation den SCM-Manager für eine Zeit verlangsamen. Dementsprechend sollte diese +Operation nur bedacht genutzt werden. + +Die Option zur Invalidierung findet sich in den generellen Einstellungen: + +![Screenshot der generellen Einstellungen für die Cache Invalidierung](assets/cache_invalidation.png) + +## Suchindex neu aufbauen + +Unter hoher Server-Last kann es passieren, dass der Suchindex nicht korrekt invalidiert wird, obwohl sich die +zugrundeliegenden Daten geändert haben. Dementsprechend kann es passieren, dass veraltete Daten gefunden werden. Dies +kann zu Fehlern in der Suchkomponente führen. Um dieses Problem manuell zu lösen, können Administratoren den Suchindex +neu erstellen lassen. Allerdings ist diese Operation zeitaufwändig und könnte den SCM-Manager für eine Zeit +verlangsamen. Dementsprechend sollte diese Operation nur bedacht genutzt werden. Wenn die Probleme bei der Suche nur ein +Repository betrifft, dann sollten Administratoren stattdessen nur den Suchindex für dieses Repository neu aufbauen +lassen. Dies kann in den generellen Einstellungen des Repositories gemacht werden. + +Die Option zum Neuaufbau findet sich in den generellen Einstellungen: + +![Screenshot der generellen Einstellungen für das erneute Aufbauen des Suchindex](assets/rebuild_index.png) diff --git a/docs/en/user/admin/assets/cache_invalidation.png b/docs/en/user/admin/assets/cache_invalidation.png new file mode 100644 index 0000000000000000000000000000000000000000..006416e3e05c96221e52ec8aee62b200829a485a GIT binary patch literal 13630 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 literal 0 HcmV?d00001 diff --git a/docs/en/user/admin/assets/rebuild_index.png b/docs/en/user/admin/assets/rebuild_index.png new file mode 100644 index 0000000000000000000000000000000000000000..5b54cc6953bd4ba2fca4666f8d2002a7b41e0094 GIT binary patch literal 16136 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 Cache getCache(String name); + + /** + * Clears (aka invalidates) all caches. + * @since 2.48.0 + */ + void clearAllCaches(); } diff --git a/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java b/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java index d5633de9c3..b6cc38558e 100644 --- a/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java +++ b/scm-test/src/main/java/sonia/scm/cache/MapCacheManager.java @@ -75,6 +75,13 @@ public class MapCacheManager return (MapCache) cacheMap.computeIfAbsent(name, k -> new MapCache()); } + @Override + public void clearAllCaches() { + for(MapCache cache : cacheMap.values()) { + cache.clear(); + } + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-ui/ui-api/src/index.ts b/scm-ui/ui-api/src/index.ts index 5ae171362d..8f108ec90d 100644 --- a/scm-ui/ui-api/src/index.ts +++ b/scm-ui/ui-api/src/index.ts @@ -61,6 +61,7 @@ export * from "./contentType"; export * from "./annotations"; export * from "./search"; export * from "./loginInfo"; +export * from "./useInvalidation"; export * from "./usePluginCenterAuthInfo"; export * from "./compare"; export * from "./utils"; diff --git a/scm-ui/ui-api/src/useInvalidation.tsx b/scm-ui/ui-api/src/useInvalidation.tsx new file mode 100644 index 0000000000..070dab2847 --- /dev/null +++ b/scm-ui/ui-api/src/useInvalidation.tsx @@ -0,0 +1,50 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import { useMutation } from "react-query"; +import { apiClient } from "./apiclient"; +import { useRequiredIndexLink } from "./base"; + +const useInvalidation = (link: string) => { + const { mutate, isLoading, error, isSuccess } = useMutation((link) => + apiClient.post(link, {}) + ); + + return { + invalidate: () => mutate(link), + isLoading, + isSuccess, + error, + }; +}; + +export const useInvalidateAllCaches = () => { + const invalidateCacheLink = useRequiredIndexLink("invalidateCaches"); + return useInvalidation(invalidateCacheLink); +}; + +export const useInvalidateSearchIndices = () => { + const invalidateSearchIndexLink = useRequiredIndexLink("invalidateSearchIndex"); + return useInvalidation(invalidateSearchIndexLink); +}; diff --git a/scm-ui/ui-webapp/public/locales/de/config.json b/scm-ui/ui-webapp/public/locales/de/config.json index d2128a9a19..fd0798b7c1 100644 --- a/scm-ui/ui-webapp/public/locales/de/config.json +++ b/scm-ui/ui-webapp/public/locales/de/config.json @@ -93,6 +93,18 @@ "login-attempt-limit-invalid": "Dies ist keine Zahl", "plugin-url-invalid": "Dies ist keine gültige URL" }, + "invalidateCaches": { + "success": "Invalidierung von Caches war erfolgreich", + "subtitle": "Caches invalidieren", + "description": "Invalidieren sie Caches manuell, um bestimmte Probleme zu beheben. Achtung: Nach der Invalidierung ist der SCM-Manager verlangsamt.", + "button": "Invalidierung von Caches starten" + }, + "invalidateSearchIndex": { + "success": "Neuaufbau vom Suchindex erfolgreich angestoßen", + "subtitle": "Suchindex neu aufbauen", + "description": "Bauen Sie den Suchindex neu auf, um Probleme mit den Suchergebnissen zu beheben. Achtung: während des Neuaufbaus ist der SCM-Manager verlangsamt.", + "button": "Neuaufbau vom Suchindex starten" + }, "help": { "realmDescriptionHelpText": "Beschreibung des Authentication Realm.", "dateFormatHelpText": "Moments Datumsformat. Zulässige Formate sind in der MomentJS Dokumentation beschrieben.", diff --git a/scm-ui/ui-webapp/public/locales/en/config.json b/scm-ui/ui-webapp/public/locales/en/config.json index 437c5ae12c..e320746c3d 100644 --- a/scm-ui/ui-webapp/public/locales/en/config.json +++ b/scm-ui/ui-webapp/public/locales/en/config.json @@ -93,6 +93,18 @@ "login-attempt-limit-invalid": "This is not a number", "plugin-url-invalid": "This is not a valid url" }, + "invalidateCaches": { + "success": "Successfully invalidated caches", + "subtitle": "Invalidate Caches", + "description": "Invalidate caches manually to fix certain issues. Warning: After invalidation the SCM-Manager is slowed down.", + "button": "Start cache invalidation" + }, + "invalidateSearchIndex": { + "success": "Rebuild of the search index has been triggered", + "subtitle": "Rebuild Search Index", + "description": "Rebuild the search index to fix certain issues with search results. Warning: While rebuilding the search index the SCM-Manager is slowed down.", + "button": "Start recreation of search index" + }, "help": { "realmDescriptionHelpText": "Enter authentication realm description.", "dateFormatHelpText": "Moments date format. Please have a look at the MomentJS documentation.", diff --git a/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx b/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx index ed28589d05..1bcd46cfd3 100644 --- a/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx +++ b/scm-ui/ui-webapp/src/admin/components/form/ConfigForm.tsx @@ -23,7 +23,7 @@ */ import React, { FC, FormEvent, useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; -import { Config, ConfigChangeHandler, NamespaceStrategies } from "@scm-manager/ui-types"; +import { Config, ConfigChangeHandler, Link, NamespaceStrategies } from "@scm-manager/ui-types"; import { Level, Notification, SubmitButton } from "@scm-manager/ui-components"; import ProxySettings from "./ProxySettings"; import GeneralSettings from "./GeneralSettings"; @@ -31,6 +31,8 @@ import BaseUrlSettings from "./BaseUrlSettings"; import LoginAttempt from "./LoginAttempt"; import PluginSettings from "./PluginSettings"; import FunctionSettings from "./FunctionSettings"; +import InvalidateCaches from "./InvalidateCaches"; +import InvalidateSearchIndex from "./InvalidateSearchIndex"; type Props = { submitForm: (p: Config) => void; @@ -39,6 +41,8 @@ type Props = { configReadPermission: boolean; configUpdatePermission: boolean; namespaceStrategies?: NamespaceStrategies; + invalidateCachesLink?: Link; + invalidateSearchIndexLink?: Link; }; const ConfigForm: FC = ({ @@ -48,6 +52,8 @@ const ConfigForm: FC = ({ configReadPermission, configUpdatePermission, namespaceStrategies, + invalidateCachesLink, + invalidateSearchIndexLink, }) => { const [t] = useTranslation("config"); const [innerConfig, setInnerConfig] = useState({ @@ -196,6 +202,18 @@ const ConfigForm: FC = ({ hasUpdatePermission={configUpdatePermission} />


+ {invalidateCachesLink ? ( + <> + +
+ + ) : null} + {invalidateSearchIndexLink ? ( + <> + +
+ + ) : null} { + const { invalidate, isLoading, error, isSuccess } = useInvalidateAllCaches(); + const [t] = useTranslation("config"); + + return ( +
+ + {isSuccess ? {t("invalidateCaches.success")} : null} + +

{t("invalidateCaches.description")}

+ + {t("invalidateCaches.button")} + + } + /> +
+ ); +}; + +export default InvalidateCaches; diff --git a/scm-ui/ui-webapp/src/admin/components/form/InvalidateSearchIndex.tsx b/scm-ui/ui-webapp/src/admin/components/form/InvalidateSearchIndex.tsx new file mode 100644 index 0000000000..86e08d158e --- /dev/null +++ b/scm-ui/ui-webapp/src/admin/components/form/InvalidateSearchIndex.tsx @@ -0,0 +1,52 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +import React, { FC } from "react"; +import { ErrorNotification, Level, Notification, Subtitle } from "@scm-manager/ui-components"; +import { useTranslation } from "react-i18next"; +import { Button, ButtonVariants } from "@scm-manager/ui-buttons"; +import { useInvalidateSearchIndices } from "@scm-manager/ui-api"; + +const InvalidateSearchIndex: FC = () => { + const { invalidate, isLoading, error, isSuccess } = useInvalidateSearchIndices(); + const [t] = useTranslation("config"); + + return ( +
+ + {isSuccess ? {t("invalidateSearchIndex.success")} : null} + +

{t("invalidateSearchIndex.description")}

+ + {t("invalidateSearchIndex.button")} + + } + /> +
+ ); +}; + +export default InvalidateSearchIndex; diff --git a/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx b/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx index 35a05cc9a7..507607c131 100644 --- a/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx +++ b/scm-ui/ui-webapp/src/admin/containers/GlobalConfig.tsx @@ -26,15 +26,16 @@ import { useTranslation } from "react-i18next"; import { Link } from "@scm-manager/ui-types"; import { ErrorNotification, Loading, Title } from "@scm-manager/ui-components"; import ConfigForm from "../components/form/ConfigForm"; -import { useConfig, useNamespaceStrategies, useUpdateConfig } from "@scm-manager/ui-api"; +import { useConfig, useIndexLinks, useNamespaceStrategies, useUpdateConfig } from "@scm-manager/ui-api"; const GlobalConfig: FC = () => { + const indexLinks = useIndexLinks(); const { data: config, error: configLoadingError, isLoading: isLoadingConfig } = useConfig(); const { isLoading: isUpdating, error: updateError, isUpdated, update, reset } = useUpdateConfig(); const { data: namespaceStrategies, error: namespaceStrategiesLoadingError, - isLoading: isLoadingNamespaceStrategies + isLoading: isLoadingNamespaceStrategies, } = useNamespaceStrategies(); const [t] = useTranslation("config"); const error = configLoadingError || namespaceStrategiesLoadingError || updateError || undefined; @@ -67,6 +68,8 @@ const GlobalConfig: FC = () => { namespaceStrategies={namespaceStrategies} configUpdatePermission={canUpdateConfig} configReadPermission={!!config} + invalidateCachesLink={indexLinks.invalidateCaches as Link | undefined} + invalidateSearchIndexLink={indexLinks.invalidateSearchIndex as Link | undefined} /> ); diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java index e29e1687ba..968171159e 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/IndexDtoGenerator.java @@ -135,6 +135,10 @@ public class IndexDtoGenerator extends HalAppenderMapper { } if (ConfigurationPermissions.list().isPermitted()) { builder.single(link("config", resourceLinks.config().self())); + if (ConfigurationPermissions.write(configuration.getId()).isPermitted()) { + builder.single(link("invalidateCaches", resourceLinks.invalidationLinks().caches())); + builder.single(link("invalidateSearchIndex", resourceLinks.invalidationLinks().searchIndex())); + } if (!Strings.isNullOrEmpty(configuration.getReleaseFeedUrl())) { builder.single(link("updateInfo", resourceLinks.adminInfo().updateInfo())); } diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InvalidationResource.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InvalidationResource.java new file mode 100644 index 0000000000..2ac041e7ca --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/InvalidationResource.java @@ -0,0 +1,102 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import io.swagger.v3.oas.annotations.OpenAPIDefinition; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.tags.Tag; +import sonia.scm.cache.CacheManager; +import sonia.scm.config.ConfigurationPermissions; +import sonia.scm.search.IndexRebuilder; +import sonia.scm.web.VndMediaType; + +import javax.inject.Inject; +import javax.ws.rs.POST; +import javax.ws.rs.Path; + +@OpenAPIDefinition(tags = { + @Tag(name = "Invalidations", description = "Invalidations of different resources like caches and search index") +}) +@Path("v2/invalidations") +public class InvalidationResource { + + private final CacheManager cacheManager; + private final IndexRebuilder indexRebuilder; + + @Inject + public InvalidationResource(CacheManager cacheManager, IndexRebuilder indexRebuilder) { + this.cacheManager = cacheManager; + this.indexRebuilder = indexRebuilder; + } + + @POST + @Path("/caches") + @Operation( + summary = "Invalidates the caches of every store", + description = "Deletes every cached object of every store from the cache", + tags = "Invalidations" + ) + @ApiResponse(responseCode = "204", description = "Invalidated cache successfully") + @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") + @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:write:global\" privilege") + @ApiResponse( + responseCode = "500", + description = "internal server error", + content = @Content( + mediaType = VndMediaType.ERROR_TYPE, + schema = @Schema(implementation = ErrorDto.class) + ) + ) + public void invalidateCaches() { + ConfigurationPermissions.write("global").check(); + cacheManager.clearAllCaches(); + } + + @POST + @Path("/search-index") + @Operation( + summary = "Invalidates the search index", + description = "Invalidates the search index, by completely recreating it", + tags = "Invalidations" + ) + @ApiResponse(responseCode = "204", description = "Invalidated search index successfully") + @ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials") + @ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"configuration:write:global\" privilege") + @ApiResponse( + responseCode = "500", + description = "internal server error", + content = @Content( + mediaType = VndMediaType.ERROR_TYPE, + schema = @Schema(implementation = ErrorDto.class) + ) + ) + public void invalidateSearchIndex() { + ConfigurationPermissions.write("global").check(); + indexRebuilder.rebuildAll(); + } +} diff --git a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java index a66fefc751..064b8d2288 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java +++ b/scm-webapp/src/main/java/sonia/scm/api/v2/resources/ResourceLinks.java @@ -347,6 +347,27 @@ class ResourceLinks { } } + InvalidationLinks invalidationLinks() { + return new InvalidationLinks(accessScmPathInfoStore().get()); + } + + static class InvalidationLinks { + private final LinkBuilder invalidationLinkBuilder; + + InvalidationLinks(ScmPathInfo pathInfo) { + this.invalidationLinkBuilder = new LinkBuilder(pathInfo, InvalidationResource.class); + } + + String caches() { + return invalidationLinkBuilder.method("invalidateCaches").parameters().href(); + } + + + String searchIndex() { + return invalidationLinkBuilder.method("invalidateSearchIndex").parameters().href(); + } + } + AdminInfoLinks adminInfo() { return new AdminInfoLinks(accessScmPathInfoStore().get()); } diff --git a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java index cdbdc787ab..35a8a4e4f3 100644 --- a/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java +++ b/scm-webapp/src/main/java/sonia/scm/cache/GuavaCacheManager.java @@ -79,6 +79,13 @@ public class GuavaCacheManager implements CacheManager, org.apache.shiro.cache.C }); } + @Override + public void clearAllCaches() { + for(GuavaCache cache : caches.values()) { + cache.clear(); + } + } + @Override public void close() throws IOException { LOG.info("close guava cache manager"); diff --git a/scm-webapp/src/main/java/sonia/scm/search/IndexRebuilder.java b/scm-webapp/src/main/java/sonia/scm/search/IndexRebuilder.java new file mode 100644 index 0000000000..51fdbf25ec --- /dev/null +++ b/scm-webapp/src/main/java/sonia/scm/search/IndexRebuilder.java @@ -0,0 +1,46 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.search; + +import javax.inject.Inject; +import java.util.Set; + +public class IndexRebuilder { + + private final SearchEngine searchEngine; + private final Set indexers; + + @Inject + public IndexRebuilder(SearchEngine searchEngine, Set indexers) { + this.searchEngine = searchEngine; + this.indexers = indexers; + } + + public void rebuildAll() { + for (Indexer indexer : indexers) { + searchEngine.forType(indexer.getType()).update(indexer.getReIndexAllTask()); + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexDtoGeneratorTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexDtoGeneratorTest.java index 51bb031d57..425eb593ed 100644 --- a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexDtoGeneratorTest.java +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/IndexDtoGeneratorTest.java @@ -155,6 +155,68 @@ class IndexDtoGeneratorTest { Link.linkBuilder("search", "/api/v2/search/query/group").withName("group").build() ); } + + @Nested + class InvalidationLinks { + @Test + void shouldAppendInvalidationLinks() { + when(subject.isAuthenticated()).thenReturn(true); + when(subject.isPermitted("configuration:list")).thenReturn(true); + when(subject.isPermitted("configuration:write:1")).thenReturn(true); + mockOtherPermissions(); + when(configuration.getId()).thenReturn("1"); + + IndexDto dto = generator.generate(); + assertThat(dto.getLinks().getLinkBy("invalidateCaches")).contains( + Link.linkBuilder("invalidateCaches", "/api/v2/invalidations/caches").build() + ); + assertThat(dto.getLinks().getLinkBy("invalidateSearchIndex")).contains( + Link.linkBuilder("invalidateSearchIndex", "/api/v2/invalidations/search-index").build() + ); + } + + @Test + void shouldNotAppendInvalidationsIfWritePermissionIsMissing() { + when(subject.isAuthenticated()).thenReturn(true); + when(subject.isPermitted("configuration:list")).thenReturn(true); + when(subject.isPermitted("configuration:write:1")).thenReturn(false); + mockOtherPermissions(); + when(configuration.getId()).thenReturn("1"); + + IndexDto dto = generator.generate(); + assertThat(dto.getLinks().getLinkBy("invalidateCaches")).isEmpty(); + assertThat(dto.getLinks().getLinkBy("invalidateSearchIndex")).isEmpty(); + } + + @Test + void shouldNotAppendInvalidationsIfListPermissionIsMissing() { + when(subject.isAuthenticated()).thenReturn(true); + when(subject.isPermitted("configuration:list")).thenReturn(false); + mockOtherPermissions(); + + IndexDto dto = generator.generate(); + assertThat(dto.getLinks().getLinkBy("invalidateCaches")).isEmpty(); + assertThat(dto.getLinks().getLinkBy("invalidateSearchIndex")).isEmpty(); + } + + @Test + void shouldNotAppendInvalidationsIfUnauthenticated() { + when(subject.isAuthenticated()).thenReturn(false); + + IndexDto dto = generator.generate(); + assertThat(dto.getLinks().getLinkBy("invalidateCaches")).isEmpty(); + assertThat(dto.getLinks().getLinkBy("invalidateSearchIndex")).isEmpty(); + } + + private void mockOtherPermissions() { + when(subject.isPermitted("plugin:read")).thenReturn(false); + when(subject.isPermitted("plugin:write")).thenReturn(false); + when(subject.isPermitted("user:list")).thenReturn(false); + when(subject.isPermitted("user:autocomplete")).thenReturn(false); + when(subject.isPermitted("group:autocomplete")).thenReturn(false); + when(subject.isPermitted("group:list")).thenReturn(false); + } + } } private SearchableType searchableType(String name) { diff --git a/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InvalidationResourceTest.java b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InvalidationResourceTest.java new file mode 100644 index 0000000000..30e80216e9 --- /dev/null +++ b/scm-webapp/src/test/java/sonia/scm/api/v2/resources/InvalidationResourceTest.java @@ -0,0 +1,126 @@ +/* + * MIT License + * + * Copyright (c) 2020-present Cloudogu GmbH and Contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package sonia.scm.api.v2.resources; + +import org.github.sdorra.jse.ShiroExtension; +import org.github.sdorra.jse.SubjectAware; +import org.jboss.resteasy.mock.MockHttpRequest; +import org.jboss.resteasy.mock.MockHttpResponse; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import sonia.scm.cache.CacheManager; +import sonia.scm.search.IndexRebuilder; +import sonia.scm.web.RestDispatcher; + +import java.net.URISyntaxException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoInteractions; + + +@ExtendWith({MockitoExtension.class, ShiroExtension.class}) +@SubjectAware("TrainerRed") +class InvalidationResourceTest { + + @Mock + private CacheManager cacheManager; + @Mock + private IndexRebuilder indexRebuilder; + + private RestDispatcher dispatcher; + + private final String basePath = "/v2/invalidations"; + + @BeforeEach + void init() { + InvalidationResource invalidationResource = new InvalidationResource(cacheManager, indexRebuilder); + + dispatcher = new RestDispatcher(); + dispatcher.addSingletonResource(invalidationResource); + } + + @Nested + class InvalidateCaches { + + @Test + void shouldReturnForbiddenBecauseOfMissingPermission() throws URISyntaxException { + MockHttpResponse response = invokeInvalidateCaches(); + assertThat(response.getStatus()).isEqualTo(403); + verifyNoInteractions(cacheManager); + } + + @Test + @SubjectAware(permissions = {"configuration:write:global"}) + void shouldClearCaches() throws URISyntaxException { + MockHttpResponse response = invokeInvalidateCaches(); + assertThat(response.getStatus()).isEqualTo(204); + verify(cacheManager).clearAllCaches(); + } + + private MockHttpResponse invokeInvalidateCaches() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post(basePath + "/caches"); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + return response; + } + } + + @Nested + class ReIndex { + + @Test + void shouldReturnForbiddenBecauseOfMissingPermission() throws URISyntaxException { + MockHttpResponse response = invokeReIndex(); + + assertThat(response.getStatus()).isEqualTo(403); + + verifyNoInteractions(indexRebuilder); + } + + @Test + @SubjectAware(permissions = {"configuration:write:global"}) + void shouldReIndexAll() throws URISyntaxException { + MockHttpResponse response = invokeReIndex(); + + assertThat(response.getStatus()).isEqualTo(204); + + verify(indexRebuilder).rebuildAll(); + } + + private MockHttpResponse invokeReIndex() throws URISyntaxException { + MockHttpRequest request = MockHttpRequest.post(basePath + "/search-index"); + MockHttpResponse response = new MockHttpResponse(); + + dispatcher.invoke(request, response); + return response; + } + } +} diff --git a/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java b/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java index f3a4b28fe0..c06de3877f 100644 --- a/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java +++ b/scm-webapp/src/test/java/sonia/scm/cache/CacheManagerTestBase.java @@ -87,6 +87,20 @@ public abstract class CacheManagerTestBase assertIsSame(c1, c2); } + @Test + public void shouldClearCache() { + Cache c1 = cacheManager.getCache("test-1"); + c1.put("key1", "value1"); + + Cache c2 = cacheManager.getCache("test-2"); + c2.put("key2", "value2"); + + cacheManager.clearAllCaches(); + + assertEquals(c1.size(), 0); + assertEquals(c2.size(), 0); + } + /** * Method description *