Skip to content

Commit

Permalink
Common funcs for filter block indices (#716)
Browse files Browse the repository at this point in the history
* common method for filter blocks

Signed-off-by: zhichao-aws <zhichaog@amazon.com>

* add an usage example

Signed-off-by: zhichao-aws <zhichaog@amazon.com>

* fix typos, move interface outside func

Signed-off-by: zhichao-aws <zhichaog@amazon.com>

* use Pick to optimize func

Signed-off-by: zhichao-aws <zhichaog@amazon.com>

---------

Signed-off-by: zhichao-aws <zhichaog@amazon.com>
  • Loading branch information
zhichao-aws authored Apr 28, 2023
1 parent c53eb03 commit e63b43f
Show file tree
Hide file tree
Showing 3 changed files with 308 additions and 1 deletion.
7 changes: 7 additions & 0 deletions public/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,3 +373,10 @@ export const ALIAS_SELECT_RULE = [
},
},
];

export enum IndexOpBlocksType {
Closed = "4",
ReadOnly = "5",
MetaData = "9",
ReadOnlyAllowDelete = "12",
}
199 changes: 198 additions & 1 deletion public/utils/helpers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,46 @@
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/
import { diffJson } from "./helpers";
import {
diffJson,
getBlockedIndices,
indexBlockedPredicate,
aliasBlockedPredicate,
dataStreamBlockedPredicate,
filterBlockedItems,
} from "./helpers";
import { CatIndex, DataStream } from "../../server/models/interfaces";
import { IAlias } from "../pages/Aliases/interface";
import { browserServicesMock } from "../../test/mocks";
import { IndexOpBlocksType } from "./constants";

const exampleBlocksStateResponse = {
cluster_name: "opensearch-cluster",
cluster_uuid: "123",
blocks: {
indices: {
test_index1: {
"4": {
description: "index closed",
retryable: false,
levels: ["read", "write"],
},
"5": {
description: "index read-only (api)",
retryable: false,
levels: ["write", "metadata_write"],
},
},
test_index2: {
"4": {
description: "index closed",
retryable: false,
levels: ["read", "write"],
},
},
},
},
};

describe("helpers spec", () => {
it(`diffJson`, async () => {
Expand Down Expand Up @@ -38,4 +77,162 @@ describe("helpers spec", () => {
)
).toEqual(2);
});

it(`getBlockedIndices normal case`, async () => {
browserServicesMock.commonService.apiCaller = jest.fn().mockResolvedValue({
ok: true,
response: exampleBlocksStateResponse,
});
expect(getBlockedIndices(browserServicesMock)).resolves.toEqual({
test_index1: ["4", "5"],
test_index2: ["4"],
});
});

it(`getBlockedIndices empty case`, async () => {
browserServicesMock.commonService.apiCaller = jest.fn().mockResolvedValue({
ok: true,
response: {
cluster_name: "opensearch-cluster",
cluster_uuid: "123",
blocks: {},
},
});
expect(getBlockedIndices(browserServicesMock)).resolves.toEqual({});
});

it(`getBlockedIndices error case`, async () => {
browserServicesMock.commonService.apiCaller = jest.fn().mockResolvedValue({
ok: false,
error: "test",
});
try {
await getBlockedIndices(browserServicesMock);
throw "fail";
} catch (err) {
expect(err).toEqual("test");
}
});

it(`indexBlockedPredicate`, async () => {
const blockedItemsSet = new Set(["index_1", "index_2"]);
expect(indexBlockedPredicate({ index: "index_1" }, blockedItemsSet)).toEqual(true);
expect(indexBlockedPredicate({ index: "index_3" }, blockedItemsSet)).toEqual(false);
});

it(`aliasBlockedPredicate`, async () => {
const blockedItemsSet = new Set(["index_1", "index_2"]);
expect(aliasBlockedPredicate({ indexArray: ["index_1", "index_3"] }, blockedItemsSet)).toEqual(true);
expect(aliasBlockedPredicate({ indexArray: ["index_3", "index_4"] }, blockedItemsSet)).toEqual(false);
});

it(`dataStreamBlockedPredicate`, async () => {
const blockedItemsSet = new Set(["index_1", "index_2"]);
expect(dataStreamBlockedPredicate({ indices: [{ index_name: "index_1" }, { index_name: "index_3" }] }, blockedItemsSet)).toEqual(true);
expect(dataStreamBlockedPredicate({ indices: [{ index_name: "index_4" }, { index_name: "index_3" }] }, blockedItemsSet)).toEqual(false);
});

it(`filterBlockedItems index`, async () => {
browserServicesMock.commonService.apiCaller = jest.fn().mockResolvedValue({
ok: true,
response: exampleBlocksStateResponse,
});
const selectedItems = [{ index: "test_index1" }, { index: "test_index2" }, { index: "test_index3" }];
expect(
filterBlockedItems<CatIndex>(browserServicesMock, selectedItems, IndexOpBlocksType.Closed, indexBlockedPredicate)
).resolves.toEqual({
blockedItems: [{ index: "test_index1" }, { index: "test_index2" }],
unBlockedItems: [{ index: "test_index3" }],
});

expect(
filterBlockedItems<CatIndex>(
browserServicesMock,
selectedItems,
[IndexOpBlocksType.ReadOnly, IndexOpBlocksType.MetaData],
indexBlockedPredicate
)
).resolves.toEqual({
blockedItems: [{ index: "test_index1" }],
unBlockedItems: [{ index: "test_index2" }, { index: "test_index3" }],
});

browserServicesMock.commonService.apiCaller = jest.fn().mockResolvedValue({
ok: true,
response: { blocks: {} },
});

expect(
filterBlockedItems<CatIndex>(
browserServicesMock,
selectedItems,
[IndexOpBlocksType.ReadOnly, IndexOpBlocksType.MetaData],
indexBlockedPredicate
)
).resolves.toEqual({
blockedItems: [],
unBlockedItems: selectedItems,
});
});

it(`filterBlockedItems alias`, async () => {
browserServicesMock.commonService.apiCaller = jest.fn().mockResolvedValue({
ok: true,
response: exampleBlocksStateResponse,
});
const selectedItems = [
{ indexArray: ["test_index1", "test_index3"] },
{ indexArray: ["test_index1", "test_index3"] },
{ indexArray: ["test_index2", "test_index3"] },
];

expect(
filterBlockedItems<IAlias>(browserServicesMock, selectedItems, IndexOpBlocksType.Closed, aliasBlockedPredicate)
).resolves.toEqual({
blockedItems: selectedItems,
unBlockedItems: [],
});

expect(
filterBlockedItems<IAlias>(
browserServicesMock,
selectedItems,
[IndexOpBlocksType.ReadOnly, IndexOpBlocksType.MetaData],
aliasBlockedPredicate
)
).resolves.toEqual({
blockedItems: [selectedItems[0], selectedItems[1]],
unBlockedItems: [selectedItems[2]],
});
});

it(`filterBlockedItems dataStream`, async () => {
browserServicesMock.commonService.apiCaller = jest.fn().mockResolvedValue({
ok: true,
response: exampleBlocksStateResponse,
});
const selectedItems = [
{ indices: [{ index_name: "test_index1" }, { index_name: "test_index2" }] },
{ indices: [{ index_name: "test_index1" }, { index_name: "test_index3" }] },
{ indices: [{ index_name: "test_index2" }, { index_name: "test_index3" }] },
];
expect(
filterBlockedItems<DataStream>(browserServicesMock, selectedItems, IndexOpBlocksType.Closed, dataStreamBlockedPredicate)
).resolves.toEqual({
blockedItems: selectedItems,
unBlockedItems: [],
});

expect(
filterBlockedItems<DataStream>(
browserServicesMock,
selectedItems,
[IndexOpBlocksType.ReadOnly, IndexOpBlocksType.MetaData],
dataStreamBlockedPredicate
)
).resolves.toEqual({
blockedItems: [selectedItems[0], selectedItems[1]],
unBlockedItems: [selectedItems[2]],
});
});
});
103 changes: 103 additions & 0 deletions public/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
// @ts-ignore
import { htmlIdGenerator } from "@elastic/eui/lib/services";
import { isEqual } from "lodash";
import { BrowserServices } from "../models/interfaces";
import { IndexOpBlocksType } from "./constants";
import { CatIndex, DataStream } from "../../server/models/interfaces";
import { IAlias } from "../pages/Aliases/interface";

export function getErrorMessage(err: any, defaultMessage: string) {
if (err && err.message) return err.message;
Expand Down Expand Up @@ -45,3 +49,102 @@ export function diffJson(oldJson?: Record<string, any>, newJson?: Record<string,
}
return initial + addOrChanged + oldKeys.length;
}

// code related to filter blocked index/alias/datastream
// an example to use:
// import { aliasBlockedPredicate, filterBlockedItems } from "./helpers";
// import { IndexOpBlocksType } from "./constants";
// const result = filterBlockedItems<IAlias>(services, selectedItems, IndexOpBlocksType.Closed, aliasBlockedPredicate)

interface BlockedIndices {
[indexName: string]: String[];
}

interface ClusterBlocksStateResponse {
blocks: {
indices?: {
[indexName: string]: {
[blockId: string]: {
description: string;
retryable: boolean;
levels: string[];
};
};
};
};
}

export async function getBlockedIndices(broswerServices: BrowserServices): Promise<BlockedIndices> {
const result = await broswerServices.commonService.apiCaller<ClusterBlocksStateResponse>({
endpoint: "cluster.state",
data: {
metric: "blocks",
},
});
if (!result.ok) {
throw result.error;
}

const blocksResponse = result.response.blocks;
if (!blocksResponse.indices) {
return {};
}

const blockedIndices: BlockedIndices = {};
Object.keys(blocksResponse.indices).forEach((indexName) => {
const innerBlocksInfo = blocksResponse.indices![indexName];
const indexOpBlocksIds = Object.keys(innerBlocksInfo);
blockedIndices[indexName] = indexOpBlocksIds;
});
return blockedIndices;
}

export interface FilteredBlockedItems<T> {
unBlockedItems: T[];
blockedItems: T[];
}

export function indexBlockedPredicate(item: Pick<CatIndex, "index">, blockedItemsSet: Set<string>): boolean {
return blockedItemsSet.has(item.index);
}

export function aliasBlockedPredicate(item: Pick<IAlias, "indexArray">, blockedItemsSet: Set<String>): boolean {
return !!item.indexArray.find((indexName) => blockedItemsSet.has(indexName));
}

export function dataStreamBlockedPredicate(item: Pick<DataStream, "indices">, blockedItemsSet: Set<String>): boolean {
return !!item.indices.find((dataStreamIndex) => blockedItemsSet.has(dataStreamIndex.index_name));
}

export async function filterBlockedItems<T>(
broswerServices: BrowserServices,
inputItems: T[],
blocksTypes: IndexOpBlocksType | IndexOpBlocksType[],
blocksPredicate: (item: T, blockedItemsSet: Set<string>) => boolean
): Promise<FilteredBlockedItems<T>> {
const blocksTypesSet = new Set(Array.isArray(blocksTypes) ? blocksTypes : [blocksTypes]);
const blockedIndices = await getBlockedIndices(broswerServices);
// we only care about the indices with blocks type in blocksTypesSet
// use set to accelarate execution
const filteredBlockedIndicesSet = new Set(
Object.entries(blockedIndices)
.filter(
// blockedIndex is like this: ["index_name": ["4","5"]]
(blockedIndex) => blockedIndex[1].find((s) => blocksTypesSet.has(s as IndexOpBlocksType))
// we only take index name, do not need blocksType
)
.map((blockedIndex) => blockedIndex[0])
);
const result: FilteredBlockedItems<T> = {
unBlockedItems: [],
blockedItems: [],
};
inputItems.forEach((item) => {
if (blocksPredicate(item, filteredBlockedIndicesSet)) {
result.blockedItems.push(item);
} else {
result.unBlockedItems.push(item);
}
});
return result;
}

0 comments on commit e63b43f

Please sign in to comment.