Skip to content

Commit

Permalink
Merge branch 'master' into test_spaces_permissions
Browse files Browse the repository at this point in the history
  • Loading branch information
elasticmachine committed Sep 3, 2020
2 parents 3e39e0c + 2de9f44 commit bd6a230
Show file tree
Hide file tree
Showing 30 changed files with 601 additions and 212 deletions.
2 changes: 1 addition & 1 deletion docs/settings/dev-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ They are enabled by default.

[cols="2*<"]
|===
| `xpack.grokdebugger.enabled`
| `xpack.grokdebugger.enabled` {ess-icon}
| Set to `true` to enable the <<xpack-grokdebugger,Grok Debugger>>. Defaults to `true`.

|===
Expand Down
13 changes: 9 additions & 4 deletions docs/settings/monitoring-settings.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ For more information, see
monitoring back-end does not run and {kib} stats are not sent to the monitoring
cluster.

a|`monitoring.cluster_alerts.`
`email_notifications.email_address` {ess-icon}
| Specifies the email address where you want to receive cluster alerts.
See <<cluster-alert-email-notifications, email notifications>> for details.

| `monitoring.ui.elasticsearch.hosts`
| Specifies the location of the {es} cluster where your monitoring data is stored.
By default, this is the same as `elasticsearch.hosts`. This setting enables
Expand Down Expand Up @@ -85,7 +90,7 @@ These settings control how data is collected from {kib}.
| Set to `true` (default) to enable data collection from the {kib} NodeJS server
for {kib} dashboards to be featured in *{stack-monitor-app}*.

| `monitoring.kibana.collection.interval`
| `monitoring.kibana.collection.interval` {ess-icon}
| Specifies the number of milliseconds to wait in between data sampling on the
{kib} NodeJS server for the metrics that are displayed in the {kib} dashboards.
Defaults to `10000` (10 seconds).
Expand All @@ -111,7 +116,7 @@ about configuring {kib}, see
| Set to `false` to hide *{stack-monitor-app}*. The monitoring back-end
continues to run as an agent for sending {kib} stats to the monitoring
cluster. Defaults to `true`.

| `monitoring.ui.logs.index`
| Specifies the name of the indices that are shown on the
<<logs-monitor-page,*Logs*>> page in *{stack-monitor-app}*. The default value
Expand All @@ -124,7 +129,7 @@ about configuring {kib}, see
{ref}/search-aggregations-bucket-terms-aggregation.html#search-aggregations-bucket-terms-aggregation-size[Terms Aggregation].
Defaults to `10000`.

| `monitoring.ui.min_interval_seconds`
| `monitoring.ui.min_interval_seconds` {ess-icon}
| Specifies the minimum number of seconds that a time bucket in a chart can
represent. Defaults to 10. If you modify the
`monitoring.ui.collection.interval` in `elasticsearch.yml`, use the same
Expand All @@ -143,7 +148,7 @@ container, then Cgroup statistics are not useful.

[cols="2*<"]
|===
| `monitoring.ui.container.elasticsearch.enabled`
| `monitoring.ui.container.elasticsearch.enabled` {ess-icon}
| For {es} clusters that are running in containers, this setting changes the
*Node Listing* to display the CPU utilization based on the reported Cgroup
statistics. It also adds the calculated Cgroup CPU utilization to the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ export class IndexPattern implements IIndexPattern {
fields: this.mapping.fields._serialize!(this.fields),
fieldFormatMap: this.mapping.fieldFormatMap._serialize!(this.fieldFormatMap),
type: this.type,
typeMeta: this.mapping.typeMeta._serialize!(this.mapping),
typeMeta: this.mapping.typeMeta._serialize!(this.typeMeta),
};
}

Expand Down
8 changes: 6 additions & 2 deletions test/functional/page_objects/settings_page.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,11 +282,15 @@ export function SettingsPageProvider({ getService, getPageObjects }: FtrProvider
await PageObjects.header.waitUntilLoadingHasFinished();
}

async clickIndexPatternLogstash() {
const indexLink = await find.byXPath(`//a[descendant::*[text()='logstash-*']]`);
async clickIndexPatternByName(name: string) {
const indexLink = await find.byXPath(`//a[descendant::*[text()='${name}']]`);
await indexLink.click();
}

async clickIndexPatternLogstash() {
await this.clickIndexPatternByName('logstash-*');
}

async getIndexPatternList() {
await testSubjects.existOrFail('indexPatternTable', { timeout: 5000 });
return await find.allByCssSelector(
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/ingest_manager/common/constants/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ export const AGENT_TYPE_PERMANENT = 'PERMANENT';
export const AGENT_TYPE_EPHEMERAL = 'EPHEMERAL';
export const AGENT_TYPE_TEMPORARY = 'TEMPORARY';

export const AGENT_POLLING_REQUEST_TIMEOUT_MS = 300000; // 5 minutes
export const AGENT_POLLING_THRESHOLD_MS = 30000;
export const AGENT_POLLING_INTERVAL = 1000;
export const AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS = 30000;
Expand Down
11 changes: 5 additions & 6 deletions x-pack/plugins/ingest_manager/server/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@ export const getHTTPResponseCode = (error: IngestManagerError): number => {
return 502; // Bad Gateway
}
if (error instanceof PackageNotFoundError) {
return 404;
}
if (error instanceof PackageOutdatedError) {
return 400;
} else {
return 400; // Bad Request
return 404; // Not Found
}

return 400; // Bad Request
};

export class RegistryError extends IngestManagerError {}
export class RegistryConnectionError extends RegistryError {}
export class RegistryResponseError extends RegistryError {}
export class PackageNotFoundError extends IngestManagerError {}
export class PackageOutdatedError extends IngestManagerError {}
6 changes: 5 additions & 1 deletion x-pack/plugins/ingest_manager/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { IngestManagerPlugin } from './plugin';
import {
AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS,
AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL,
AGENT_POLLING_REQUEST_TIMEOUT_MS,
} from '../common';
export { AgentService, ESIndexPatternService, getRegistryUrl } from './services';
export {
Expand All @@ -29,7 +30,10 @@ export const config = {
fleet: schema.object({
enabled: schema.boolean({ defaultValue: true }),
tlsCheckDisabled: schema.boolean({ defaultValue: false }),
pollingRequestTimeout: schema.number({ defaultValue: 60000 }),
pollingRequestTimeout: schema.number({
defaultValue: AGENT_POLLING_REQUEST_TIMEOUT_MS,
min: 5000,
}),
maxConcurrentConnections: schema.number({ defaultValue: 0 }),
kibana: schema.object({
host: schema.maybe(
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/ingest_manager/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -238,7 +238,7 @@ export class IngestManagerPlugin
// we currently only use this global interceptor if fleet is enabled
// since it would run this func on *every* req (other plugins, CSS, etc)
registerLimitedConcurrencyRoutes(core, config);
registerAgentRoutes(router);
registerAgentRoutes(router, config);
registerEnrollmentApiKeyRoutes(router);
registerInstallScriptRoutes({
router,
Expand Down
15 changes: 13 additions & 2 deletions x-pack/plugins/ingest_manager/server/routes/agent/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ import * as AgentService from '../../services/agents';
import { postNewAgentActionHandlerBuilder } from './actions_handlers';
import { appContextService } from '../../services';
import { postAgentsUnenrollHandler } from './unenroll_handler';
import { IngestManagerConfigType } from '../..';

export const registerRoutes = (router: IRouter) => {
export const registerRoutes = (router: IRouter, config: IngestManagerConfigType) => {
// Get one
router.get(
{
Expand Down Expand Up @@ -80,12 +81,22 @@ export const registerRoutes = (router: IRouter) => {
getAgentsHandler
);

const pollingRequestTimeout = config.fleet.pollingRequestTimeout;
// Agent checkin
router.post(
{
path: AGENT_API_ROUTES.CHECKIN_PATTERN,
validate: PostAgentCheckinRequestSchema,
options: { tags: [] },
options: {
tags: [],
...(pollingRequestTimeout
? {
timeout: {
idleSocket: pollingRequestTimeout,
},
}
: {}),
},
},
postAgentCheckinHandler
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ export function agentCheckinStateNewActionsFactory() {
}

const stream$ = agentPolicy$.pipe(
timeout(appContextService.getConfig()?.fleet.pollingRequestTimeout || 0),
timeout(
// Set a timeout 3s before the real timeout to have a chance to respond an empty response before socket timeout
Math.max((appContextService.getConfig()?.fleet.pollingRequestTimeout ?? 0) - 3000, 3000)
),
filter((agentPolicy) => shouldCreateAgentPolicyAction(agent, agentPolicy)),
rateLimiter(),
mergeMap((agentPolicy) => createAgentActionFromAgentPolicy(soClient, agent, agentPolicy)),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { fetchUrl } from './requests';
import { RegistryError } from '../../../errors';
import { RegistryError, RegistryConnectionError, RegistryResponseError } from '../../../errors';
jest.mock('node-fetch');

const { Response, FetchError } = jest.requireActual('node-fetch');
Expand Down Expand Up @@ -53,13 +53,7 @@ describe('setupIngestManager', () => {
throw new FetchError('message 3', 'system', { code: 'ESOMETHING' });
})
// this one succeeds
.mockImplementationOnce(() => Promise.resolve(new Response(successValue)))
.mockImplementationOnce(() => {
throw new FetchError('message 5', 'system', { code: 'ESOMETHING' });
})
.mockImplementationOnce(() => {
throw new FetchError('message 6', 'system', { code: 'ESOMETHING' });
});
.mockImplementationOnce(() => Promise.resolve(new Response(successValue)));

const promise = fetchUrl('');
await expect(promise).resolves.toEqual(successValue);
Expand All @@ -69,7 +63,7 @@ describe('setupIngestManager', () => {
expect(actualResultsOrder).toEqual(['throw', 'throw', 'throw', 'return']);
});

it('or error after 1 failure & 5 retries with RegistryError', async () => {
it('or error after 1 failure & 5 retries with RegistryConnectionError', async () => {
fetchMock
.mockImplementationOnce(() => {
throw new FetchError('message 1', 'system', { code: 'ESOMETHING' });
Expand All @@ -88,21 +82,93 @@ describe('setupIngestManager', () => {
})
.mockImplementationOnce(() => {
throw new FetchError('message 6', 'system', { code: 'ESOMETHING' });
})
.mockImplementationOnce(() => {
throw new FetchError('message 7', 'system', { code: 'ESOMETHING' });
})
.mockImplementationOnce(() => {
throw new FetchError('message 8', 'system', { code: 'ESOMETHING' });
});

const promise = fetchUrl('');
await expect(promise).rejects.toThrow(RegistryError);
await expect(promise).rejects.toThrow(RegistryConnectionError);
// doesn't retry after 1 failure & 5 failed retries
expect(fetchMock).toHaveBeenCalledTimes(6);
const actualResultsOrder = fetchMock.mock.results.map(({ type }: { type: string }) => type);
expect(actualResultsOrder).toEqual(['throw', 'throw', 'throw', 'throw', 'throw', 'throw']);
});
});

describe('4xx or 5xx from Registry become RegistryResponseError', () => {
it('404', async () => {
fetchMock.mockImplementationOnce(() => ({
ok: false,
status: 404,
statusText: 'Not Found',
url: 'https://example.com',
}));
const promise = fetchUrl('');
await expect(promise).rejects.toThrow(RegistryResponseError);
await expect(promise).rejects.toThrow(
`'404 Not Found' error response from package registry at https://example.com`
);
expect(fetchMock).toHaveBeenCalledTimes(1);
});

it('429', async () => {
fetchMock.mockImplementationOnce(() => ({
ok: false,
status: 429,
statusText: 'Too Many Requests',
url: 'https://example.com',
}));
const promise = fetchUrl('');
await expect(promise).rejects.toThrow(RegistryResponseError);
await expect(promise).rejects.toThrow(
`'429 Too Many Requests' error response from package registry at https://example.com`
);
expect(fetchMock).toHaveBeenCalledTimes(1);
});

it('500', async () => {
fetchMock.mockImplementationOnce(() => ({
ok: false,
status: 500,
statusText: 'Internal Server Error',
url: 'https://example.com',
}));
const promise = fetchUrl('');
await expect(promise).rejects.toThrow(RegistryResponseError);
await expect(promise).rejects.toThrow(
`'500 Internal Server Error' error response from package registry at https://example.com`
);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});

describe('url in RegistryResponseError message is response.url || requested_url', () => {
it('given response.url, use that', async () => {
fetchMock.mockImplementationOnce(() => ({
ok: false,
status: 404,
statusText: 'Not Found',
url: 'https://example.com/?from_response=true',
}));
const promise = fetchUrl('https://example.com/?requested=true');
await expect(promise).rejects.toThrow(RegistryResponseError);
await expect(promise).rejects.toThrow(
`'404 Not Found' error response from package registry at https://example.com/?from_response=true`
);
expect(fetchMock).toHaveBeenCalledTimes(1);
});

it('no response.url, use requested url', async () => {
fetchMock.mockImplementationOnce(() => ({
ok: false,
status: 404,
statusText: 'Not Found',
}));
const promise = fetchUrl('https://example.com/?requested=true');
await expect(promise).rejects.toThrow(RegistryResponseError);
await expect(promise).rejects.toThrow(
`'404 Not Found' error response from package registry at https://example.com/?requested=true`
);
expect(fetchMock).toHaveBeenCalledTimes(1);
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import fetch, { FetchError, Response } from 'node-fetch';
import pRetry from 'p-retry';
import { streamToString } from './streams';
import { RegistryError } from '../../../errors';
import { RegistryError, RegistryConnectionError, RegistryResponseError } from '../../../errors';

type FailedAttemptErrors = pRetry.FailedAttemptError | FetchError | Error;

Expand All @@ -19,10 +19,13 @@ async function registryFetch(url: string) {
return response;
} else {
// 4xx & 5xx responses
// exit without retry & throw RegistryError
throw new pRetry.AbortError(
new RegistryError(`Error connecting to package registry at ${url}: ${response.statusText}`)
);
const { status, statusText, url: resUrl } = response;
const message = `'${status} ${statusText}' error response from package registry at ${
resUrl || url
}`;
const responseError = new RegistryResponseError(message);

throw new pRetry.AbortError(responseError);
}
}

Expand All @@ -38,17 +41,24 @@ export async function getResponse(url: string): Promise<Response> {
// and let the others through without retrying
//
// throwing in onFailedAttempt will abandon all retries & fail the request
// we only want to retry system errors, so throw a RegistryError for everything else
// we only want to retry system errors, so re-throw for everything else
if (!isSystemError(error)) {
throw new RegistryError(
`Error connecting to package registry at ${url}: ${error.message}`
);
throw error;
}
},
});
return response;
} catch (e) {
throw new RegistryError(`Error connecting to package registry at ${url}: ${e.message}`);
} catch (error) {
// isSystemError here means we didn't succeed after max retries
if (isSystemError(error)) {
throw new RegistryConnectionError(`Error connecting to package registry: ${error.message}`);
}
// don't wrap our own errors
if (error instanceof RegistryError) {
throw error;
} else {
throw new RegistryError(error);
}
}
}

Expand Down
Loading

0 comments on commit bd6a230

Please sign in to comment.