From c10e8bda81f6d33043cf753c632b14e90ad5390d Mon Sep 17 00:00:00 2001 From: Katia Aresti Date: Thu, 16 Sep 2021 23:13:17 +0200 Subject: [PATCH] ISPN-11492 and ISPN-11491 Cluster and Cache enable/disable rebalancing --- src/app/CacheManagers/CacheManagers.tsx | 2 + .../CacheManagers/RebalancingCacheManager.tsx | 51 +++++++++++++ src/app/Caches/DetailCache.tsx | 34 +++++---- src/app/Caches/RebalancingCache.tsx | 71 +++++++++++++++++++ src/app/assets/languages/en.json | 7 +- src/app/providers/CacheDetailProvider.tsx | 40 +++++++---- .../providers/CacheManagerContextProvider.tsx | 6 ++ src/app/services/cachesHook.ts | 4 +- src/app/services/dataContainerHooks.ts | 3 +- src/services/cacheService.ts | 24 +++++++ src/services/dataContainerService.ts | 41 ++++++++--- src/types/InfinispanTypes.ts | 2 + 12 files changed, 245 insertions(+), 40 deletions(-) create mode 100644 src/app/CacheManagers/RebalancingCacheManager.tsx create mode 100644 src/app/Caches/RebalancingCache.tsx diff --git a/src/app/CacheManagers/CacheManagers.tsx b/src/app/CacheManagers/CacheManagers.tsx index 970ed3b80..aa56c6778 100644 --- a/src/app/CacheManagers/CacheManagers.tsx +++ b/src/app/CacheManagers/CacheManagers.tsx @@ -29,6 +29,7 @@ import {useTranslation} from 'react-i18next'; import {useConnectedUser} from "@app/services/userManagementHook"; import {ConsoleServices} from "@services/ConsoleServices"; import {ConsoleACL} from "@services/securityService"; +import {RebalancingCacheManager} from "@app/CacheManagers/RebalancingCacheManager"; const CacheManagers = () => { const { connectedUser } = useConnectedUser(); @@ -207,6 +208,7 @@ const CacheManagers = () => { {buildSiteDisplay(cm.local_site)} + {buildTabs()} diff --git a/src/app/CacheManagers/RebalancingCacheManager.tsx b/src/app/CacheManagers/RebalancingCacheManager.tsx new file mode 100644 index 000000000..2be5adfaa --- /dev/null +++ b/src/app/CacheManagers/RebalancingCacheManager.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import {Divider, FlexItem, Spinner, Switch} from '@patternfly/react-core'; +import {useConnectedUser} from "@app/services/userManagementHook"; +import {ConsoleServices} from "@services/ConsoleServices"; +import {ConsoleACL} from "@services/securityService"; +import {useTranslation} from "react-i18next"; +import {useApiAlert} from "@app/utils/useApiAlert"; +import {useDataContainer} from "@app/services/dataContainerHooks"; + +const RebalancingCacheManager = () => { + const { addAlert } = useApiAlert(); + const { t } = useTranslation(); + const { connectedUser } = useConnectedUser(); + const { cm, loading, reload } = useDataContainer(); + + if (loading || !cm) { + return ( + + + + ); + } + + if (ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser)) { + return ( + + + + { + ConsoleServices.dataContainer().rebalancing(cm.name, !cm.rebalancing_enabled) + .then(r => { + addAlert(r); + reload(); + }) + }} + /> + + + ); + } + + // Return nothing if the connected user is not ADMIN + return (); +}; + +export { RebalancingCacheManager }; diff --git a/src/app/Caches/DetailCache.tsx b/src/app/Caches/DetailCache.tsx index 6f51223d3..c7458f407 100644 --- a/src/app/Caches/DetailCache.tsx +++ b/src/app/Caches/DetailCache.tsx @@ -43,6 +43,7 @@ import {ConsoleServices} from "@services/ConsoleServices"; import {ConsoleACL} from "@services/securityService"; import {useConnectedUser} from "@app/services/userManagementHook"; import {useTranslation} from "react-i18next"; +import {RebalancingCache} from "@app/Caches/RebalancingCache"; const DetailCache = (props: { cacheName: string }) => { const cacheName = props.cacheName; @@ -104,17 +105,7 @@ const DetailCache = (props: { cacheName: string }) => { }; const buildDetailContent = () => { - if (loading) { - return ( - - - - - - ); - } - - if (error.length > 0 || !cache) { + if (error.length > 0) { return ( @@ -142,6 +133,16 @@ const DetailCache = (props: { cacheName: string }) => { ); } + if (loading || !cache) { + return ( + + + + + + ); + } + if(activeTabKey1 == 0 && cache.editable && ConsoleServices.security().hasCacheConsoleACL(ConsoleACL.READ, cacheName, connectedUser)) { @@ -162,6 +163,8 @@ const DetailCache = (props: { cacheName: string }) => { }; const buildRebalancing = () => { + if (!cache) return ; + if (!cache?.rehash_in_progress) { return ( @@ -308,7 +311,7 @@ const DetailCache = (props: { cacheName: string }) => { - {buildRebalancing()} + {buildBackupsManage()} {buildIndexManage()} @@ -416,7 +419,12 @@ const DetailCache = (props: { cacheName: string }) => { activeKey={activeTabKey1} isSecondary={true} component={TabsComponent.nav} - onSelect={(event, tabIndex) => setActiveTabKey1(tabIndex)} + onSelect={(event, tabIndex) => { + setActiveTabKey1(tabIndex); + if (tabIndex == 0) { + loadCache(cacheName); + } + }} > {displayCacheEntries()} {displayConfiguration()} diff --git a/src/app/Caches/RebalancingCache.tsx b/src/app/Caches/RebalancingCache.tsx new file mode 100644 index 000000000..29498693f --- /dev/null +++ b/src/app/Caches/RebalancingCache.tsx @@ -0,0 +1,71 @@ +import React from 'react'; +import {Label, Spinner, Switch, ToolbarItem} from '@patternfly/react-core'; +import {useCacheDetail} from "@app/services/cachesHook"; +import {useConnectedUser} from "@app/services/userManagementHook"; +import {ConsoleServices} from "@services/ConsoleServices"; +import {ConsoleACL} from "@services/securityService"; +import {useTranslation} from "react-i18next"; +import {useApiAlert} from "@app/utils/useApiAlert"; +import {useDataContainer} from "@app/services/dataContainerHooks"; + +const RebalancingCache = () => { + const { addAlert } = useApiAlert(); + const { t } = useTranslation(); + const { connectedUser } = useConnectedUser(); + const { cache, cacheManager, loading, reload } = useCacheDetail(); + + // If rebalancing is not activated at cluster level, don't display anything + if (!cacheManager.rebalancing_enabled) { + return ( ) + } + + if (loading || !cache) { + return ( + + + + ); + } + + /** + * When the rehash is in progress just display the value + */ + if (cache?.rehash_in_progress) { + return ( + + {t('caches.info.rebalancing')} + + ); + } + + /** + * If the user is ADMIN, can enable and disable rebalancing + */ + if (ConsoleServices.security().hasConsoleACL(ConsoleACL.ADMIN, connectedUser)) { + return ( + + { + ConsoleServices.caches().rebalancing(cache.name, !cache.rebalancing_enabled) + .then(r => { + addAlert(r); + reload(); + }) + }} + /> + + ); + } + + return ( + + + + ); +}; + +export { RebalancingCache }; diff --git a/src/app/assets/languages/en.json b/src/app/assets/languages/en.json index 3d45e3a4a..d29e14611 100644 --- a/src/app/assets/languages/en.json +++ b/src/app/assets/languages/en.json @@ -104,7 +104,9 @@ "allowed-role" : "Allowed role", "allowed-role-null" : "-", "no-tasks-status" : "No tasks yet", - "no-tasks-body" : "Create tasks with the CLI or a remote client." + "no-tasks-body" : "Create tasks with the CLI or a remote client.", + "rebalancing-enabled": "Rebalancing is on", + "rebalancing-disabled": "Rebalancing is off" }, "caches" : { "configuration" : { @@ -129,7 +131,8 @@ "loading" : "Loading cache {{cacheName}} ...", "error" : "An error occurred while loading {{cacheName}}", "rebalanced" : "Rebalanced", - "rebalancing" : "Rebalancing" + "rebalancing-enabled" : "Rebalancing is on", + "rebalancing-disabled" : "Rebalancing is off" }, "actions" : { "action-see-less" : "See fewer cache details", diff --git a/src/app/providers/CacheDetailProvider.tsx b/src/app/providers/CacheDetailProvider.tsx index f6960c5bc..a920a82aa 100644 --- a/src/app/providers/CacheDetailProvider.tsx +++ b/src/app/providers/CacheDetailProvider.tsx @@ -7,6 +7,7 @@ import {ContentType} from "@services/infinispanRefData"; const initialContext = { error: '', loading: false, + cacheManager: (undefined as unknown) as CacheManager, cache: (undefined as unknown) as DetailedInfinispanCache, loadCache: (name: string) => {}, reload: () => {}, @@ -23,6 +24,9 @@ export const CacheDetailContext = React.createContext(initialContext); const CacheDetailProvider = ({ children }) => { const {connectedUser} = useConnectedUser(); const [cacheName, setCacheName] = useState(''); + const [cacheManager, setCacheManager] = useState( + initialContext.cacheManager + ); const [cache, setCache] = useState( initialContext.cache ); @@ -52,19 +56,28 @@ const CacheDetailProvider = ({ children }) => { const fetchCache = () => { if (loading) { - ConsoleServices.caches() - .retrieveFullDetail(cacheName) - .then((eitherDetail) => { - if (eitherDetail.isRight()) { - setCache(eitherDetail.value); - } else { - setError(eitherDetail.value.message); - } - }) - .finally(() => { - setLoading(false); - setLoadingEntries(true); - }); + ConsoleServices.dataContainer().getDefaultCacheManager() + .then(maybeCm => { + if (maybeCm.isRight()) { + setCacheManager(maybeCm.value); + ConsoleServices.caches() + .retrieveFullDetail(cacheName) + .then((eitherDetail) => { + if (eitherDetail.isRight()) { + setCache(eitherDetail.value); + } else { + setError(eitherDetail.value.message); + } + }) + .finally(() => { + setLoading(false); + setLoadingEntries(true); + }); + } else { + setError(maybeCm.value.message); + } + }) + } }; @@ -117,6 +130,7 @@ const CacheDetailProvider = ({ children }) => { loadCache: useCallback(loadCache, []), reload: useCallback(() => setLoading(true), []), cache: cache, + cacheManager: cacheManager, cacheEntries: cacheEntries, loadingEntries: loadingEntries, errorEntries: errorEntries, diff --git a/src/app/providers/CacheManagerContextProvider.tsx b/src/app/providers/CacheManagerContextProvider.tsx index bcbbc8ae4..6b547f764 100644 --- a/src/app/providers/CacheManagerContextProvider.tsx +++ b/src/app/providers/CacheManagerContextProvider.tsx @@ -9,6 +9,7 @@ const initialContext = { caches: [] as CacheInfo[], loadingCaches: true, errorCaches: '', + reload: () => {}, reloadCaches: () => {}, }; @@ -76,6 +77,10 @@ const ContainerDataProvider = ({ children }) => { } }, [cm, loadingCaches]); + const reload = () => { + setLoading(true); + }; + const reloadCaches = () => { setLoadingCaches(true); }; @@ -87,6 +92,7 @@ const ContainerDataProvider = ({ children }) => { cm: cm, loadingCaches: loadingCaches, errorCaches: errorCaches, + reload: useCallback(reload, []), reloadCaches: useCallback(reloadCaches, []), }; diff --git a/src/app/services/cachesHook.ts b/src/app/services/cachesHook.ts index 97ab47a14..b0f5a2443 100644 --- a/src/app/services/cachesHook.ts +++ b/src/app/services/cachesHook.ts @@ -54,9 +54,9 @@ export function useCacheEntries() { } export function useCacheDetail() { - const { cache, loading, error, loadCache, reload } = useContext( + const { cache, loading, error, loadCache, reload, cacheManager } = useContext( CacheDetailContext ); - return { cache, loading, error, loadCache, reload }; + return { cache, loading, error, loadCache, reload, cacheManager }; } diff --git a/src/app/services/dataContainerHooks.ts b/src/app/services/dataContainerHooks.ts index 8c1d8d7b8..3c274e647 100644 --- a/src/app/services/dataContainerHooks.ts +++ b/src/app/services/dataContainerHooks.ts @@ -2,11 +2,12 @@ import { useContext } from 'react'; import { DataContainerContext } from '@app/providers/CacheManagerContextProvider'; export function useDataContainer() { - const { cm, loading, error } = useContext(DataContainerContext); + const { cm, loading, error, reload } = useContext(DataContainerContext); return { loading, error, cm, + reload }; } diff --git a/src/services/cacheService.ts b/src/services/cacheService.ts index 3c4868eed..ae9adbe19 100644 --- a/src/services/cacheService.ts +++ b/src/services/cacheService.ts @@ -68,6 +68,7 @@ export class CacheService { size: data['size'], rehash_in_progress: data['rehash_in_progress'], indexing_in_progress: data['indexing_in_progress'], + rebalancing_enabled: data['rebalancing_enabled'], editable: CacheConfigUtils.isEditable(keyValueEncoding.key as EncodingType) && CacheConfigUtils.isEditable(keyValueEncoding.value as EncodingType), @@ -552,4 +553,27 @@ export class CacheService { ) ); } + + /** + * Enables or disables rebalancing on a cache + * @param cacheName + * @param enable, true to enable, false for disable + */ + public async rebalancing( + cacheName: string, + enable: boolean + ): Promise { + const action = enable? 'enable' : 'disable'; + const url = this.endpoint + + '/caches/' + + encodeURIComponent(cacheName) + + '?action=' + action + '-rebalancing'; + return this.fetchCaller + .post( { + url: url, + successMessage: `Cache ${cacheName} rebalancing successfully ${action}d.`, + errorMessage: `Unexpected error when cache ${cacheName} rebalancing ${action}d.` + }); + } + } diff --git a/src/services/dataContainerService.ts b/src/services/dataContainerService.ts index e25d9bce8..bf6ce95e8 100644 --- a/src/services/dataContainerService.ts +++ b/src/services/dataContainerService.ts @@ -5,11 +5,11 @@ import displayUtils from '@services/displayUtils'; export class ContainerService { endpoint: string; - utils: FetchCaller; + fetchCaller: FetchCaller; - constructor(endpoint: string, restUtils: FetchCaller) { + constructor(endpoint: string, fetchCaller: FetchCaller) { this.endpoint = endpoint; - this.utils = restUtils; + this.fetchCaller = fetchCaller; } /** @@ -18,7 +18,7 @@ export class ContainerService { public getDefaultCacheManager(): Promise< Either > { - return this.utils + return this.fetchCaller .get(this.endpoint + '/server/cache-managers/', (data) => data[0]) .then((maybeCmName) => { if (maybeCmName.isRight()) { @@ -31,13 +31,13 @@ export class ContainerService { private getCacheManager( name: string ): Promise> { - let healthPromise: Promise> = this.utils.get( + let healthPromise: Promise> = this.fetchCaller.get( this.endpoint + '/cache-managers/' + name + '/health', (data) => data.cluster_health.health_status ); return healthPromise.then((maybeHealth) => - this.utils.get( + this.fetchCaller.get( this.endpoint + '/cache-managers/' + name, (data) => { @@ -61,6 +61,7 @@ export class ContainerService { ? maybeHealth.value : maybeHealth.value.message, local_site: data.local_site, + rebalancing_enabled: data.rebalancing_enabled } ) ); @@ -82,7 +83,7 @@ export class ContainerService { public async getCacheManagerStats( name: string ): Promise> { - return this.utils.get( + return this.fetchCaller.get( this.endpoint + '/cache-managers/' + name + '/stats', (data) => data ); @@ -96,7 +97,7 @@ export class ContainerService { public async getCacheConfigurationTemplates( name: string ): Promise> { - return this.utils.get( + return this.fetchCaller.get( this.endpoint + '/cache-managers/' + name + '/cache-configs/templates', (data) => data.map( @@ -116,7 +117,7 @@ export class ContainerService { public async getCaches( name: string ): Promise> { - return this.utils.get( + return this.fetchCaller.get( this.endpoint + '/cache-managers/' + name + '/caches', (data) => data @@ -142,6 +143,28 @@ export class ContainerService { ); } + /** + * Enables or disables rebalancing on a cluster + * @param name of the cache manager + * @param enable, true to enable, false for disable + */ + public async rebalancing( + name: string, + enable: boolean + ): Promise { + const action = enable? 'enable' : 'disable'; + const url = this.endpoint + + '/cache-managers/' + + encodeURIComponent(name) + + '?action=' + action + '-rebalancing'; + return this.fetchCaller + .post( { + url: url, + successMessage: `Rebalancing successfully ${action}d.`, + errorMessage: `Unexpected error when rebalancing ${action}d.` + }); + } + private clusterMembers( cluster_members: [string], cluster_members_physical_addresses: [string] diff --git a/src/types/InfinispanTypes.ts b/src/types/InfinispanTypes.ts index 2a6d0368e..8b7c5e7b7 100644 --- a/src/types/InfinispanTypes.ts +++ b/src/types/InfinispanTypes.ts @@ -16,6 +16,7 @@ interface CacheManager { cluster_members: [ClusterMember]; health: string; local_site?: string; + rebalancing_enabled:boolean; } interface ClusterMember { @@ -113,6 +114,7 @@ interface DetailedInfinispanCache { size?: number; rehash_in_progress?: boolean; indexing_in_progress?: boolean; + rebalancing_enabled: boolean; editable: boolean; queryable: boolean; features: Features;