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

[Ingest Manager] Ingest setup upgrade #78081

Merged
merged 30 commits into from
Sep 28, 2020
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
76df74d
Adding bulk upgrade api
jonathan-buttner Sep 17, 2020
6dea606
Addressing comments
jonathan-buttner Sep 18, 2020
268c526
Removing todo
jonathan-buttner Sep 18, 2020
04a39bd
Resolving conflicts
jonathan-buttner Sep 18, 2020
f111406
Changing body field
jonathan-buttner Sep 18, 2020
2846b76
Adding helper for getting the bulk install route
jonathan-buttner Sep 18, 2020
20357fd
Adding request spec
jonathan-buttner Sep 18, 2020
9b29802
Pulling in Johns changes
jonathan-buttner Sep 21, 2020
3d2a686
Merge branch 'master' of github.com:elastic/kibana into ingest-bulk-u…
jonathan-buttner Sep 21, 2020
7cb80e9
Removing test for same package upgraded multiple times
jonathan-buttner Sep 21, 2020
6e333ab
Adding upgrade to setup route
jonathan-buttner Sep 21, 2020
99f6aee
Fixing merge conflicts
jonathan-buttner Sep 22, 2020
8a41396
Adding setup integration test
jonathan-buttner Sep 22, 2020
219cfa9
Clean up error handling
jonathan-buttner Sep 24, 2020
ba00737
Beginning to add tests
jonathan-buttner Sep 24, 2020
267931b
Merge branch 'master' of github.com:elastic/kibana into ingest-setup-…
jonathan-buttner Sep 24, 2020
037ba98
Failing jest mock tests
jonathan-buttner Sep 24, 2020
a76006f
Break up tests & modules for easier testing.
Sep 25, 2020
5c259d1
Add test confirming update error result will throw
Sep 25, 2020
06cfadc
Keep orig error. Add status code in http handler
Sep 26, 2020
a3c77ce
Leave error as-is
Sep 27, 2020
25c3cd0
Removing accidental code changes. File rename.
Sep 27, 2020
202b7a2
Missed a function when moving to a new file
Sep 27, 2020
ee4c774
Add missing type imports
Sep 27, 2020
c906aea
Lift .map lambda into named outer function
Sep 27, 2020
11b677d
Merge pull request #7 from jfsiii/ingest-setup-upgrade
jonathan-buttner Sep 28, 2020
4afbb56
Merge branch 'master' of github.com:elastic/kibana into ingest-setup-…
jonathan-buttner Sep 28, 2020
3f8295d
Adding additional test
jonathan-buttner Sep 28, 2020
7f02b5f
Fixing type error
jonathan-buttner Sep 28, 2020
0427b9d
Merge branch 'master' into ingest-setup-upgrade
elasticmachine Sep 28, 2020
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
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export interface InstallPackageResponse {
export interface IBulkInstallPackageError {
name: string;
statusCode: number;
error: string | Error;
error: string;
}

export interface BulkInstallPackageInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,19 +63,41 @@ export async function installLatestPackage(options: {
}
}

function isBulkInstallError(resp: BulkInstallResponse): resp is IBulkInstallPackageError {
return 'error' in resp && resp.error !== undefined;
}

async function getInstallationAndName(
savedObjectsClient: SavedObjectsClientContract,
pkgName: string
) {
return {
pkgName,
installation: await getInstallation({ savedObjectsClient, pkgName }),
};
}

type GetInstallationReturnType = UnwrapPromise<ReturnType<typeof getInstallationAndName>>;
export async function ensureInstalledDefaultPackages(
savedObjectsClient: SavedObjectsClientContract,
callCluster: CallESAsCurrentUser
): Promise<Installation[]> {
): Promise<GetInstallationReturnType[]> {
const installations = [];
for (const pkgName in DefaultPackages) {
if (!DefaultPackages.hasOwnProperty(pkgName)) continue;
const installation = ensureInstalledPackage({
savedObjectsClient,
pkgName,
callCluster,
});
installations.push(installation);
const bulkResponse = await bulkInstallPackages({
savedObjectsClient,
packagesToUpgrade: Object.values(DefaultPackages),
callCluster,
});

for (const resp of bulkResponse) {
if (isBulkInstallError(resp)) {
throw new Error(resp.error);
} else {
// getInstallation can return undefined so we'll tack on the name so when we throw an error in setup we can
// reference the name of the package that we failed to retrieve the installation information for
const installation = getInstallationAndName(savedObjectsClient, resp.name);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Would it be better to await the getInstallation call here? If we did then I wouldn't have to change the return value and would just check if the result was undefined and throw the error here instead of in setup.ts.

installations.push(installation);
}
}

return Promise.all(installations);
Expand Down
6 changes: 5 additions & 1 deletion x-pack/plugins/ingest_manager/server/services/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ async function createSetupSideEffects(
) {
throw new Error('Policy not found');
}
for (const installedPackage of installedPackages) {
for (const installedPackageInfo of installedPackages) {
const installedPackage = installedPackageInfo.installation;
if (!installedPackage) {
throw new Error(`could not get installation ${installedPackageInfo.pkgName} during setup`);
}
const packageShouldBeInstalled = DEFAULT_AGENT_POLICIES_PACKAGES.some(
(packageName) => installedPackage.name === packageName
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
export default function loadTests({ loadTestFile }) {
describe('EPM Endpoints', () => {
loadTestFile(require.resolve('./list'));
loadTestFile(require.resolve('./setup'));
loadTestFile(require.resolve('./get'));
loadTestFile(require.resolve('./file'));
//loadTestFile(require.resolve('./template'));
Expand Down
48 changes: 48 additions & 0 deletions x-pack/test/ingest_manager_api_integration/apis/epm/setup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { skipIfNoDockerRegistry } from '../../helpers';
import { GetInfoResponse, Installed } from '../../../../plugins/ingest_manager/common';

export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const log = getService('log');

describe('setup api', async () => {
Copy link
Contributor

@jfsiii jfsiii Sep 22, 2020

Choose a reason for hiding this comment

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

Can you add some tests that show the failure case(s)? I'm unclear what we intend to be returned from a POST /setup that has a failed upgrade at some point. Is it a 2xx, 4xx, or 5xx response? What's in the response body?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah so adding the failure case might be tricky. Any ideas on how to trigger a failure? This is what comes to mind:

  1. Override the default packages variable used to define which packages are installed/updated during /setup so I can create a custom one and make it malformed. Is this possible with mocha?
  2. Use an invalid registry address so kibana fails to download the packages. I believe this would require creating a new x-pack/test directory because the tests in this directory all use the docker container registry
  3. Create a malformed endpoint package in this directory and make the version really big like 100000.0.0 so it's always the latest. The number needs to be large so it's unlikely to be superseded in production. The test would start failing as soon as we created a production package with version 100000.0.1 though 🤔

Copy link
Contributor

Choose a reason for hiding this comment

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

If we can map out the desired behavior I am happy to help with testing. I did a few different types recently working on Registry and /setup changes.

If this is run on every setup I really want to understand/document what it should/does do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll have to do some digging to figure out what should be returned in each failure scenario. After thinking about this more, the code changes will currently swallow the error :( because of this:

if (isBulkInstallError(resp)) {
      throw new Error(resp.error);
    }

from here: https://github.com/elastic/kibana/pull/78081/files#diff-bae6a46773afa98cb0927abf59bdd003R93

That's probably not what we want but in the current state if an error occurs it will always 500 and the response body will look like this:

body: {
  message: "some error"
}

https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/server/errors/handlers.ts#L100

The success case will always return:
200

{
  isInitialized: true
}

For the error case we could probably create a new error class that is a child of IngestManagerError and modify this function: https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/server/errors/handlers.ts#L48

to extract out the passed in status code. That way we can preserve the status code and message that occurred when the upgrade/install failed for a package and return that as a response for /setup. That way the failure functionality will change.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@skh @neptunian any ideas on how to force a failure scenario in /setup? Is there a way to mock out DefaultPackages so that I can craft a fake package that causes a failure when being installed?
https://github.com/elastic/kibana/blob/master/x-pack/plugins/ingest_manager/common/types/models/epm.ts#L265

skipIfNoDockerRegistry(providerContext);
describe('setup performs upgrades', async () => {
const oldEndpointVersion = '0.13.0';
beforeEach(async () => {
await supertest
.post(`/api/ingest_manager/epm/packages/endpoint-${oldEndpointVersion}`)
.set('kbn-xsrf', 'xxxx')
.send({ force: true })
.expect(200);
});
it('upgrades the endpoint package from 0.13.0 to the latest version available', async function () {
let { body }: { body: GetInfoResponse } = await supertest
.get(`/api/ingest_manager/epm/packages/endpoint-${oldEndpointVersion}`)
.expect(200);
const latestEndpointVersion = body.response.latestVersion;
log.info(`Endpoint package latest version: ${latestEndpointVersion}`);
// make sure we're actually doing an upgrade
expect(latestEndpointVersion).not.eql(oldEndpointVersion);
await supertest.post(`/api/ingest_manager/setup`).set('kbn-xsrf', 'xxxx').expect(200);

({ body } = await supertest
.get(`/api/ingest_manager/epm/packages/endpoint-${latestEndpointVersion}`)
.expect(200));
expect(body.response).to.have.property('savedObject');
expect((body.response as Installed).savedObject.attributes.install_version).to.eql(
latestEndpointVersion
);
});
});
});
}