Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Upgrade Assistant] Check for ML upgrade mode before enabling flyout actions #112555

Merged
merged 9 commits into from
Sep 27, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/core/public/doc_links/doc_links_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ export class DocLinksService {
outlierDetectionRoc: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-roc`,
regressionEvaluation: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-regression-evaluation`,
classificationAucRoc: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/ml-dfanalytics-evaluate.html#ml-dfanalytics-class-aucroc`,
setUpgradeMode: `${ELASTICSEARCH_DOCS}ml-set-upgrade-mode.html`,
},
transforms: {
guide: `${ELASTICSEARCH_DOCS}transforms.html`,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,23 @@ describe('ES deprecations table', () => {
const mlDeprecation = esDeprecationsMockResponse.deprecations[0];
const reindexDeprecation = esDeprecationsMockResponse.deprecations[3];

// Since upgradeStatusMockResponse includes ML and reindex actions (which require fetching status), there will be 3 requests made
expect(server.requests.length).toBe(totalRequests + 3);
expect(server.requests[server.requests.length - 3].url).toBe(
// Since upgradeStatusMockResponse includes ML and reindex actions (which require fetching status), there will be 4 requests made
expect(server.requests.length).toBe(totalRequests + 4);
expect(server.requests[server.requests.length - 4].url).toBe(
`${API_BASE_PATH}/es_deprecations`
);
expect(server.requests[server.requests.length - 2].url).toBe(
expect(server.requests[server.requests.length - 3].url).toBe(
`${API_BASE_PATH}/ml_snapshots/${(mlDeprecation.correctiveAction as MlAction).jobId}/${
(mlDeprecation.correctiveAction as MlAction).snapshotId
}`
);
expect(server.requests[server.requests.length - 1].url).toBe(
expect(server.requests[server.requests.length - 2].url).toBe(
sabarasaba marked this conversation as resolved.
Show resolved Hide resolved
`${API_BASE_PATH}/reindex/${reindexDeprecation.index}`
);

expect(server.requests[server.requests.length - 1].url).toBe(
`${API_BASE_PATH}/ml_upgrade_mode`
);
});

it('shows critical and warning deprecations count', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ describe('Machine learning deprecation flyout', () => {

beforeEach(async () => {
httpRequestsMockHelpers.setLoadEsDeprecationsResponse(esDeprecationsMockResponse);
httpRequestsMockHelpers.setLoadMlUpgradeModeResponse({ mlUpgradeModeEnabled: false });
httpRequestsMockHelpers.setUpgradeMlSnapshotStatusResponse({
nodeId: 'my_node',
snapshotId: MOCK_SNAPSHOT_ID,
Expand Down Expand Up @@ -131,6 +132,27 @@ describe('Machine learning deprecation flyout', () => {
// Verify the upgrade button text changes
expect(find('mlSnapshotDetails.upgradeSnapshotButton').text()).toEqual('Retry upgrade');
});

it('Disables actions if ml_upgrade_mode is enabled', async () => {
httpRequestsMockHelpers.setLoadMlUpgradeModeResponse({ mlUpgradeModeEnabled: true });

await act(async () => {
testBed = await setupElasticsearchPage({ isReadOnlyMode: false });
});

const { actions, exists, component } = testBed;

component.update();

await actions.table.clickDeprecationRowAt('mlSnapshot', 0);

// Shows an error callout with a docs link
expect(exists('mlSnapshotDetails.mlUpgradeModeEnabledError')).toBe(true);
expect(exists('mlSnapshotDetails.setUpgradeModeDocsLink')).toBe(true);
// Flyout actions should be hidden
expect(exists('mlSnapshotDetails.upgradeSnapshotButton')).toBe(false);
expect(exists('mlSnapshotDetails.deleteSnapshotButton')).toBe(false);
});
});

describe('delete snapshots', () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,17 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
]);
};

const setLoadMlUpgradeModeResponse = (response?: object, error?: ResponseError) => {
const status = error ? error.statusCode || 400 : 200;
const body = error ? error : response;

server.respondWith('GET', `${API_BASE_PATH}/ml_upgrade_mode`, [
status,
{ 'Content-Type': 'application/json' },
JSON.stringify(body),
]);
};

return {
setLoadCloudBackupStatusResponse,
setLoadEsDeprecationsResponse,
Expand All @@ -136,6 +147,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => {
setDeleteMlSnapshotResponse,
setUpgradeMlSnapshotStatusResponse,
setLoadDeprecationLogsCountResponse,
setLoadMlUpgradeModeResponse,
};
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useSnapshotState, SnapshotState } from './use_snapshot_state';

export interface MlSnapshotContext {
snapshotState: SnapshotState;
mlUpgradeModeEnabled: boolean;
upgradeSnapshot: () => Promise<void>;
deleteSnapshot: () => Promise<void>;
}
Expand All @@ -31,12 +32,14 @@ interface Props {
children: React.ReactNode;
snapshotId: string;
jobId: string;
mlUpgradeModeEnabled: boolean;
}

export const MlSnapshotsStatusProvider: React.FunctionComponent<Props> = ({
api,
snapshotId,
jobId,
mlUpgradeModeEnabled,
children,
}) => {
const { updateSnapshotStatus, snapshotState, upgradeSnapshot, deleteSnapshot } = useSnapshotState(
Expand All @@ -57,6 +60,7 @@ export const MlSnapshotsStatusProvider: React.FunctionComponent<Props> = ({
snapshotState,
upgradeSnapshot,
deleteSnapshot,
mlUpgradeModeEnabled,
}}
>
{children}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import React from 'react';
import { i18n } from '@kbn/i18n';
import { FormattedMessage } from '@kbn/i18n/react';
import { METRIC_TYPE } from '@kbn/analytics';

import {
Expand All @@ -32,6 +33,7 @@ import {
} from '../../../../lib/ui_metric';
import { DeprecationBadge } from '../../../shared';
import { MlSnapshotContext } from './context';
import { useAppContext } from '../../../../app_context';
import { SnapshotState } from './use_snapshot_state';

export interface FixSnapshotsFlyoutProps extends MlSnapshotContext {
Expand Down Expand Up @@ -103,6 +105,28 @@ const i18nTexts = {
defaultMessage: 'Learn more about this deprecation',
}
),
upgradeModeEnabledErrorTitle: i18n.translate(
'xpack.upgradeAssistant.esDeprecations.mlSnapshots.upgradeModeEnabledErrorTitle',
{
defaultMessage: 'Machine Learning upgrade mode is enabled',
}
),
upgradeModeEnabledErrorDescription: (docsLink: string) => (
<FormattedMessage
id="xpack.upgradeAssistant.esDeprecations.mlSnapshots.upgradeModeEnabledErrorDescription"
defaultMessage="No actions can be taken on Machine Learning snapshots while upgrade mode is enabled. {docsLink}."
values={{
docsLink: (
<EuiLink href={docsLink} target="_blank" data-test-subj="setUpgradeModeDocsLink">
<FormattedMessage
id="xpack.upgradeAssistant.esDeprecations.mlSnapshots.upgradeModeEnabledDocsLink"
defaultMessage="Learn more"
/>
</EuiLink>
),
}}
/>
),
};

const getDeleteButtonLabel = (snapshotState: SnapshotState) => {
Expand Down Expand Up @@ -145,7 +169,13 @@ export const FixSnapshotsFlyout = ({
snapshotState,
upgradeSnapshot,
deleteSnapshot,
mlUpgradeModeEnabled,
}: FixSnapshotsFlyoutProps) => {
const {
services: {
core: { docLinks },
},
} = useAppContext();
const isResolved = snapshotState.status === 'complete';

const onUpgradeSnapshot = () => {
Expand Down Expand Up @@ -187,6 +217,23 @@ export const FixSnapshotsFlyout = ({
<EuiSpacer />
</>
)}

{mlUpgradeModeEnabled && (
<>
<EuiCallOut
title={i18nTexts.upgradeModeEnabledErrorTitle}
color="warning"
iconType="alert"
data-test-subj="mlUpgradeModeEnabledError"
>
<p>
{i18nTexts.upgradeModeEnabledErrorDescription(docLinks.links.ml.setUpgradeMode)}
</p>
</EuiCallOut>
<EuiSpacer />
</>
)}

<EuiText>
<p>{deprecation.details}</p>
<p>
Expand All @@ -204,7 +251,7 @@ export const FixSnapshotsFlyout = ({
</EuiButtonEmpty>
</EuiFlexItem>

{!isResolved && (
{!isResolved && !mlUpgradeModeEnabled && (
<EuiFlexItem grow={false}>
<EuiFlexGroup>
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ const { useGlobalFlyout } = GlobalFlyout;
interface TableRowProps {
deprecation: EnrichedDeprecationInfo;
rowFieldNames: DeprecationTableColumns[];
mlUpgradeModeEnabled: boolean;
}

export const MlSnapshotsTableRowCells: React.FunctionComponent<TableRowProps> = ({
Expand Down Expand Up @@ -85,6 +86,7 @@ export const MlSnapshotsTableRow: React.FunctionComponent<TableRowProps> = (prop
<MlSnapshotsStatusProvider
snapshotId={(props.deprecation.correctiveAction as MlAction).snapshotId}
jobId={(props.deprecation.correctiveAction as MlAction).jobId}
mlUpgradeModeEnabled={props.mlUpgradeModeEnabled}
api={api}
>
<MlSnapshotsTableRowCells {...props} />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
Query,
} from '@elastic/eui';
import { EnrichedDeprecationInfo } from '../../../../common/types';
import { useAppContext } from '../../app_context';
import {
MlSnapshotsTableRow,
DefaultTableRow,
Expand Down Expand Up @@ -101,10 +102,19 @@ const cellToLabelMap = {
const cellTypes = Object.keys(cellToLabelMap) as DeprecationTableColumns[];
const pageSizeOptions = PAGINATION_CONFIG.pageSizeOptions;

const renderTableRowCells = (deprecation: EnrichedDeprecationInfo) => {
const renderTableRowCells = (
deprecation: EnrichedDeprecationInfo,
mlUpgradeModeEnabled: boolean
) => {
switch (deprecation.correctiveAction?.type) {
case 'mlSnapshot':
return <MlSnapshotsTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
return (
<MlSnapshotsTableRow
deprecation={deprecation}
rowFieldNames={cellTypes}
mlUpgradeModeEnabled={mlUpgradeModeEnabled}
/>
);

case 'indexSetting':
return <IndexSettingsTableRow deprecation={deprecation} rowFieldNames={cellTypes} />;
Expand Down Expand Up @@ -146,6 +156,13 @@ export const EsDeprecationsTable: React.FunctionComponent<Props> = ({
deprecations = [],
reload,
}) => {
const {
services: { api },
} = useAppContext();

const { data } = api.useLoadMlUpgradeMode();
sabarasaba marked this conversation as resolved.
Show resolved Hide resolved
const mlUpgradeModeEnabled = !!data?.mlUpgradeModeEnabled;

const [sortConfig, setSortConfig] = useState<SortConfig>({
isSortAscending: true,
sortField: 'isCritical',
Expand Down Expand Up @@ -291,7 +308,7 @@ export const EsDeprecationsTable: React.FunctionComponent<Props> = ({
{visibleDeprecations.map((deprecation, index) => {
return (
<EuiTableRow data-test-subj="deprecationTableRow" key={`deprecation-row-${index}`}>
{renderTableRowCells(deprecation)}
{renderTableRowCells(deprecation, mlUpgradeModeEnabled)}
</EuiTableRow>
);
})}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,15 @@ export class ApiService {
method: 'post',
});
}

public useLoadMlUpgradeMode() {
return this.useRequest<{
mlUpgradeModeEnabled: boolean;
}>({
path: `${API_BASE_PATH}/ml_upgrade_mode`,
method: 'get',
});
}
}

export const apiService = new ApiService();
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,28 @@ describe('ML snapshots APIs', () => {
});
});

describe('GET /api/upgrade_assistant/ml_upgrade_mode', () => {
it('Retrieves ml upgrade mode', async () => {
(
routeHandlerContextMock.core.elasticsearch.client.asCurrentUser.ml.info as jest.Mock
).mockResolvedValue({
body: {
upgrade_mode: true,
},
});

const resp = await routeDependencies.router.getHandler({
method: 'get',
pathPattern: '/api/upgrade_assistant/ml_upgrade_mode',
})(routeHandlerContextMock, createRequestMock({}), kibanaResponseFactory);

expect(resp.status).toEqual(200);
expect(resp.payload).toEqual({
mlUpgradeModeEnabled: true,
});
});
});

describe('GET /api/upgrade_assistant/ml_snapshots/:jobId/:snapshotId', () => {
it('returns "idle" status if saved object does not exist', async () => {
(
Expand Down
31 changes: 31 additions & 0 deletions x-pack/plugins/upgrade_assistant/server/routes/ml_snapshots.ts
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,37 @@ export function registerMlSnapshotRoutes({ router, lib: { handleEsError } }: Rou
)
);

// Get the ml upgrade mode
router.get(
{
path: `${API_BASE_PATH}/ml_upgrade_mode`,
validate: false,
},
versionCheckHandlerWrapper(
async (
{
core: {
elasticsearch: { client: esClient },
},
},
request,
response
) => {
try {
const { body: mlInfo } = await esClient.asCurrentUser.ml.info();

return response.ok({
body: {
mlUpgradeModeEnabled: mlInfo.upgrade_mode,
},
});
} catch (e) {
return handleEsError({ error: e, response });
}
}
)
);

// Delete ML model snapshot
router.delete(
{
Expand Down