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

[ML] Add optional ability to delete target index and index pattern when deleting DFA job #66934

Merged
merged 26 commits into from
Jun 1, 2020

Conversation

qn895
Copy link
Member

@qn895 qn895 commented May 18, 2020

Summary

This PR adds ability to optionally delete 1) the target index and 2) the index pattern associated with the DFA jobs

Client

  • It will check if user has permission to delete the target index. If user doesn't have permission, it won't show the first option.
  • It will also check the index pattern with the same name as the target index exists in the current Kibana's savedObject. If index pattern does exist, it will show. If not, it will hide the option.

Example 1: Index pattern does exist
Screen Shot 2020-05-18 at 4 04 05 PM

Example 2: Index pattern does not exist for this job
Screen Shot 2020-05-18 at 4 03 56 PM

Server

  • Modified the current DELETE `${basePath()}/data_frame/analytics/${analyticsId} route to check for an optional query object which includes the destinationIndex passed from the UI.
  • The route will do another check again to check if user either has permission to delete the index, if yes, then delete.
  • It will also do another check to get the index pattern Id, and if index pattern exists that matches the target index, it will delete

Checklist

Delete any items that are not applicable to this PR.

@elasticmachine
Copy link
Contributor

Pinging @elastic/ml-ui (:ml)


// Check if an user has permission to delete the index & index pattern
useEffect(() => {
const toastNotifications = getToastNotifications();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please access toast notifications from the context instead of importing from the cache

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d14fafa

Comment on lines 193 to 196
{i18n.translate('xpack.ml.dataframe.analyticsList.deleteModalBody', {
defaultMessage: `Are you sure you want to delete this analytics job? The analytics job's destination index and optional Kibana index pattern will not be deleted.`,
defaultMessage: `Are you sure you want to delete this analytics job?`,
})}
</p>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see you just updated the string, but would be better to use FormattedMessage component instead of i18n

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d14fafa

x-pack/plugins/ml/server/routes/data_frame_analytics.ts Outdated Show resolved Hide resolved
if (
err.message === 'no handler found for uri [/_security/user/_has_privileges] and method [POST]'
) {
return err;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your function should return a Promise<boolean>, so returning an error seems wrong 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in b867ef2

return privilege.index[destinationIndex].delete_index === true;
} catch (err) {
if (
err.message === 'no handler found for uri [/_security/user/_has_privileges] and method [POST]'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the error message could change at any time. you might want to check for status code instead if it's possible

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at verifyHasPrivileges from x-pack/plugins/monitoring/server/lib/elasticsearch/verify_monitoring_auth.js I think there's a particular reason why it's checked it like that. Let me follow up with the authors of that API.

Copy link
Member

@jgowdyelastic jgowdyelastic May 19, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we move to a check similar to our other privilege checks, this error message comparison won't be needed.
I believe the reason this text is checked it because ignoreUnavailable is set, so the error returned could be a 404 rather than just a privilege error.
Our other privilege checks first confirm that security is enabled before attempting a privilege check so this error message comparison isn't needed.
See my comment further down in this PR regarding our
api/ml/ml_node_count endpoint.

x-pack/plugins/ml/server/routes/data_frame_analytics.ts Outdated Show resolved Hide resolved
const indexPatternId = await getIndexPatternId(context, destinationIndex);

if (indexPatternId) {
const result = deleteIndexPatternById(context, indexPatternId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't anything with result, so a variable definition is redundant. You should get an error from eslint about issues like this one 🤔 . Worth to set up a eslint plugin for your IDE of choice

import { IndexPatternAttributes } from 'src/plugins/data/server';
// import { findObjectByTitle } from 'src/plugins/saved_objects/';
import { mlLog } from '../../client/log';
export const SAVED_OBJECT_TYPES = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We also define these in ml/server/models/data_recognizer/data_recognizer.ts. Probably worth moving them out to the common folder - in ml/common/types/kibana.ts perhaps? But is it being used here?

};

export class IndexPatternHandler {
modulesDir = `${__dirname}/modules`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this needed?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in d14fafa

indexPatternId: string | undefined = undefined;

constructor(
private callAsCurrentUser: APICaller,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you are using callAsCurrentUser

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

label={i18n.translate(
'xpack.ml.dataframe.analyticsList.deleteTargetIndexTitle',
{
defaultMessage: 'Delete index pattern',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth clarifying which index pattern is deleted here, using a message of Delete destination index pattern {indexName}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e5e618f

{userCanDeleteIndex && indexPatternExists && (
<EuiSwitch
label={i18n.translate(
'xpack.ml.dataframe.analyticsList.deleteTargetIndexTitle',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like it is using the same i18n ID as the message above xpack.ml.dataframe.analyticsList.deleteTargetIndexTitle

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e5e618f

label={i18n.translate(
'xpack.ml.dataframe.analyticsList.deleteTargetIndexTitle',
{
defaultMessage: 'Delete destination index',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is worth clarifying the name of the index name that is being deleted here Delete destination index {indexName}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in e5e618f

validate: {
params: analyticsIdSchema,
query: analyticsIdIndexSchema,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if rather than passing the name of the index in the query, it should instead pass a boolean deleteDestinationIndex, which if true then goes and deletes the index. As it is, someone could in theory pass a completely different index to the endpoint, which seems a bit dangerous. It might make sense to pass two parameters - deleteDestinationIndex and deleteDestinationIndexPattern as the modal does allow zero, one or both to be deleted. It would mean one extra query on the server side, to get the destination index for the job, but this approach would make more sense to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right! This could be a security issue. I'll refactor it to use the boolean instead.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in d14fafa

): Promise<boolean> {
let privilege;
try {
privilege = await context.ml!.mlClient.callAsCurrentUser('transport.request', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this can be callAsCurrentUser('ml.privilegeCheck', { body: ... });

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switched call to ml.privilgeCheck in d14fafa

if (destinationIndex) {
// Verify if user has privilege to delete the destination index

const userCanDeleteDestIndex = userCanDeleteIndex(context, destinationIndex);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/api/ml/ml_node_count might be a good example of how this endpoint can be written.
If the delete index flag is true, you'll need to look up the destination index and if they don't have permission to delete, we should return a response.forbidden(); immediately.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@qn895
Copy link
Member Author

qn895 commented May 21, 2020

@elasticmachine merge upstream

@@ -60,6 +60,14 @@ export const analyticsIdSchema = schema.object({
analyticsId: schema.string(),
});

export const analyticsIdIndexSchema = schema.object({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be renamed to something like deleteDataFrameAnalyticsJobSchema as it is specific to deleting the job.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in ba82079

});
return privilege.index[destinationIndex].delete_index === true;
} catch (err) {
throw err;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this try/catch is redundant as throwing err is the same as not having the catch

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in b867ef2


return ip !== undefined ? ip.id : undefined;
} catch (error) {
throw error;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these try/catches are redundant as just throwing error is the same as not having the try/catch

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in ba82079

obj => obj.attributes.title.toLowerCase() === indexName.toLowerCase()
);

return ip !== undefined ? ip.id : undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit, could be return ip?.id

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated in ba82079

deleteTargetIndexAcknowledged = true;
} catch (deleteIndexError) {
errorsEncountered.push({
msg: i18n.translate(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't translate error messages from endpoints

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed in ba82079

@qn895
Copy link
Member Author

qn895 commented May 25, 2020

@elasticmachine merge upstream

],
},
});
return privilege.index[destinationIndex].delete_index === true;
Copy link
Contributor

@alvarezmelissa87 alvarezmelissa87 May 28, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will privilege.index[destinationIndex] always be defined? Might be worth ensuring privilege and privilege.index[destinationIndex] are defined to avoid a possible TypeError if one of these is undefined.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

privilege.has_all_requested can be used here too

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 5bd97e9

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change has also introduced a check for privilege.securityDisabled === true which doesn't exist on the server side.

@alvarezmelissa87
Copy link
Contributor

Looks good overall 👍 Just left a few minor comments.

@qn895
Copy link
Member Author

qn895 commented May 28, 2020

@elasticmachine merge upstream

@elasticmachine
Copy link
Contributor

merge conflict between base and head

if (!privilege) {
return false;
}
return privilege.securityDisabled === true || privilege.has_all_requested === true;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

securityDisabled does not exist in privilege, it is only part of our client side ml.hasPrivileges function.

Copy link
Member

@pheyos pheyos left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

API integration tests LGTM

@@ -46,4 +67,22 @@ describe('DeleteAction', () => {

expect(getByTestId('mlAnalyticsJobDeleteButton')).toHaveAttribute('disabled');
});

test('When delete modal is open, modal lets user delete target index by default.', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually, test suite starts with should, e.g. should allow to delete target index by default.
If you have a common arrangement for tests you should combine them in group, for example

describe('When delete model is open', () => {
  beforeEach(async () => {
    // your arrangment here to prepare desired condition  
  })
 
 test('should allow to delete target index by default', async () => {
 })
})

success: boolean;
error?: CustomHttpResponseOptions<ResponseError>;
}
interface DeleteDataFrameAnalyticsWithIndexResponse {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess it'd be better to define somewhere in x-pack/plugins/ml/common/types because we need this interface both server and client-side

Copy link
Member

@jgowdyelastic jgowdyelastic left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

Copy link
Contributor

@peteharverson peteharverson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested after latest edits and LGTM

Copy link
Contributor

@alvarezmelissa87 alvarezmelissa87 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Latest edits LGTM ⚡

@qn895
Copy link
Member Author

qn895 commented Jun 1, 2020

@elasticmachine merge upstream

@kibanamachine
Copy link
Contributor

💚 Build Succeeded

History

To update your PR or re-run it, just comment with:
@elasticmachine merge upstream

@qn895 qn895 merged commit f31330a into elastic:master Jun 1, 2020
@qn895 qn895 deleted the add-opt-del-index-when-del-job branch June 1, 2020 15:53
qn895 added a commit to qn895/kibana that referenced this pull request Jun 1, 2020
… DFA job (elastic#66934)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
qn895 added a commit that referenced this pull request Jun 2, 2020
…leting DFA job (#66934) (#67875)

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants