Remove dependency on doc versions (#29906)
See https://github.com/elastic/elasticsearch/pull/38254 Using the `version` parameter to implement optimistic concurrency is not going to be supported in 7.0, so we need to replace our usage of document version with the new `_seq_no` and `_primary_term` parameters. These fields are returned in the same way that `_version` was returned on all read/write requests except for search, where it needs to be requested by sending `seq_no_primary_term: true` in the body of the search request. These parameters are sent back to Elasticsearch on write requests with the `if_seq_no` and `if_primary_term` parameters, and are functionally equivalent to sending a `version` in a write request before elastic/elasticsearch#38254. To make these updates I searched the code base for uses of a `version` and `_version`, then triaged each usage, so I'm fairly confident that I got everything but it's possible something slipped through the cracks, so if you know of any usage of the document version field please help me out by double checking that I converted it. - [x] **Saved Objects**: @elastic/kibana-platform, @elastic/es-security - for BWC and ergonomics the `version` provided by the Saved Objects client/API was not removed, it was converted from a number to a string whose value is `base64(json([_seq_no, _primary_term]))`. This allows the Saved Objects API and its consumers to remain mostly unmodified, as long as the underlying value in the version field is irrelevant. This was the case for all usages in Kibana, only thing that needed updating was tests and TS types. - [x] **Reporting/esqueue**: @joelgriffith, @tsullivan - the version parameter was used here specifically for implementing optimistic concurrency, and since its usage was contained within the esqueue module I just updated it to use the new `_seq_no` and `_primary_term` fields. - [x] **Task Manager**: @tsullivan @njd5475 - Like esqueue this module uses version for optimistic concurrency but the usage is contained with the module so I just updated it to use, store, and request the `_seq_no` and `_primary_term` fields. - [ ] **ML**: @elastic/ml-ui - Best I could tell the only "version" in the ML code refers to the stack version,077245fed8
- [ ] **Beats CM**: @elastic/beats - Looks like the references to `_version` in the code is only in the types but not in the code itself. I updated the types to use `_seq_no` and `_primary_term`, and their camelCase equivalents where appropriate. I did find a method that used one of the types referencing version but when investigating its usage it seemed the only consumer of that method was itself so i removed it.52d890fed7
- [x] **Spaces (tests)**: @elastic/kibana-security - The spaces test helpers use saved objects with versions in a number of places, so I updated them to use the new string versions where the version was predictable, and removed the assertion on version where it wasn't. We test the version in the saved objects code so this should be fine.
This commit is contained in:
parent
de2d0b647c
commit
b4725b7d34
|
@ -61,7 +61,6 @@ describe('plugins/elasticsearch', () => {
|
|||
|
||||
cluster = { callWithInternalUser: sinon.stub() };
|
||||
cluster.callWithInternalUser.withArgs('index', sinon.match.any).returns(Promise.resolve());
|
||||
cluster.callWithInternalUser.withArgs('create', sinon.match.any).returns(Promise.resolve({ _id: '1', _version: 1 }));
|
||||
cluster.callWithInternalUser.withArgs('mget', sinon.match.any).returns(Promise.resolve({ ok: true }));
|
||||
cluster.callWithInternalUser.withArgs('get', sinon.match.any).returns(Promise.resolve({ found: false }));
|
||||
cluster.callWithInternalUser.withArgs('search', sinon.match.any).returns(Promise.resolve({ hits: { hits: [] } }));
|
||||
|
|
|
@ -35,7 +35,7 @@ export const createBulkCreateRoute = prereqs => ({
|
|||
type: Joi.string().required(),
|
||||
id: Joi.string(),
|
||||
attributes: Joi.object().required(),
|
||||
version: Joi.number(),
|
||||
version: Joi.string(),
|
||||
migrationVersion: Joi.object().optional(),
|
||||
references: Joi.array().items(
|
||||
Joi.object()
|
||||
|
|
|
@ -59,7 +59,7 @@ describe('POST /api/saved_objects/_bulk_get', () => {
|
|||
id: 'abc123',
|
||||
type: 'index-pattern',
|
||||
title: 'logstash-*',
|
||||
version: 2,
|
||||
version: 'foo',
|
||||
references: [],
|
||||
}]
|
||||
};
|
||||
|
|
|
@ -32,7 +32,7 @@ export const createUpdateRoute = (prereqs) => {
|
|||
}).required(),
|
||||
payload: Joi.object({
|
||||
attributes: Joi.object().required(),
|
||||
version: Joi.number().min(1),
|
||||
version: Joi.string(),
|
||||
references: Joi.array().items(
|
||||
Joi.object()
|
||||
.keys({
|
||||
|
|
|
@ -67,7 +67,7 @@ describe('PUT /api/saved_objects/{type}/{id?}', () => {
|
|||
|
||||
it('calls upon savedObjectClient.update', async () => {
|
||||
const attributes = { title: 'Testing' };
|
||||
const options = { version: 2, references: [] };
|
||||
const options = { version: 'foo', references: [] };
|
||||
const request = {
|
||||
method: 'PUT',
|
||||
url: '/api/saved_objects/index-pattern/logstash-*',
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
|
||||
import uuid from 'uuid';
|
||||
import { SavedObjectsSchema } from '../schema';
|
||||
import { decodeVersion, encodeVersion } from '../version';
|
||||
|
||||
/**
|
||||
* A raw document as represented directly in the saved object index.
|
||||
|
@ -32,7 +33,8 @@ export interface RawDoc {
|
|||
_id: string;
|
||||
_source: any;
|
||||
_type?: string;
|
||||
_version?: number;
|
||||
_seq_no?: number;
|
||||
_primary_term?: number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -64,7 +66,7 @@ interface SavedObjectDoc {
|
|||
type: string;
|
||||
namespace?: string;
|
||||
migrationVersion?: MigrationVersion;
|
||||
version?: number;
|
||||
version?: string;
|
||||
updated_at?: Date;
|
||||
|
||||
[rootProp: string]: any;
|
||||
|
@ -116,8 +118,15 @@ export class SavedObjectsSerializer {
|
|||
*
|
||||
* @param {RawDoc} rawDoc - The raw ES document to be converted to saved object format.
|
||||
*/
|
||||
public rawToSavedObject({ _id, _source, _version }: RawDoc): SanitizedSavedObjectDoc {
|
||||
public rawToSavedObject(doc: RawDoc): SanitizedSavedObjectDoc {
|
||||
const { _id, _source, _seq_no, _primary_term } = doc;
|
||||
const { type, namespace } = _source;
|
||||
|
||||
const version =
|
||||
_seq_no != null || _primary_term != null
|
||||
? encodeVersion(_seq_no!, _primary_term!)
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
type,
|
||||
id: this.trimIdPrefix(namespace, type, _id),
|
||||
|
@ -126,7 +135,7 @@ export class SavedObjectsSerializer {
|
|||
references: _source.references || [],
|
||||
...(_source.migrationVersion && { migrationVersion: _source.migrationVersion }),
|
||||
...(_source.updated_at && { updated_at: _source.updated_at }),
|
||||
...(_version != null && { version: _version }),
|
||||
...(version && { version }),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -158,7 +167,7 @@ export class SavedObjectsSerializer {
|
|||
return {
|
||||
_id: this.generateRawId(namespace, type, id),
|
||||
_source: source,
|
||||
...(version != null && { _version: version }),
|
||||
...(version != null && decodeVersion(version)),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
import _ from 'lodash';
|
||||
import { SavedObjectsSerializer } from '.';
|
||||
import { SavedObjectsSchema } from '../schema';
|
||||
import { encodeVersion } from '../version';
|
||||
|
||||
describe('saved object conversion', () => {
|
||||
describe('#rawToSavedObject', () => {
|
||||
|
@ -86,7 +87,8 @@ describe('saved object conversion', () => {
|
|||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'hello:world',
|
||||
_version: 3,
|
||||
_seq_no: 3,
|
||||
_primary_term: 1,
|
||||
_source: {
|
||||
type: 'hello',
|
||||
hello: {
|
||||
|
@ -103,7 +105,7 @@ describe('saved object conversion', () => {
|
|||
const expected = {
|
||||
id: 'world',
|
||||
type: 'hello',
|
||||
version: 3,
|
||||
version: encodeVersion(3, 1),
|
||||
attributes: {
|
||||
a: 'b',
|
||||
c: 'd',
|
||||
|
@ -130,17 +132,46 @@ describe('saved object conversion', () => {
|
|||
expect(actual).not.toHaveProperty('version');
|
||||
});
|
||||
|
||||
test(`if specified it copies _version to version`, () => {
|
||||
test(`if specified it encodes _seq_no and _primary_term to version`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const actual = serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_version: 4,
|
||||
_seq_no: 4,
|
||||
_primary_term: 1,
|
||||
_source: {
|
||||
type: 'foo',
|
||||
hello: {},
|
||||
},
|
||||
});
|
||||
expect(actual).toHaveProperty('version', 4);
|
||||
expect(actual).toHaveProperty('version', encodeVersion(4, 1));
|
||||
});
|
||||
|
||||
test(`if only _seq_no is specified it throws`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
expect(() =>
|
||||
serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_seq_no: 4,
|
||||
_source: {
|
||||
type: 'foo',
|
||||
hello: {},
|
||||
},
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(`"_primary_term from elasticsearch must be an integer"`);
|
||||
});
|
||||
|
||||
test(`if only _primary_term is throws`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
expect(() =>
|
||||
serializer.rawToSavedObject({
|
||||
_id: 'foo:bar',
|
||||
_primary_term: 1,
|
||||
_source: {
|
||||
type: 'foo',
|
||||
hello: {},
|
||||
},
|
||||
})
|
||||
).toThrowErrorMatchingInlineSnapshot(`"_seq_no from elasticsearch must be an integer"`);
|
||||
});
|
||||
|
||||
test('if specified it copies the _source.updated_at property to updated_at', () => {
|
||||
|
@ -222,7 +253,8 @@ describe('saved object conversion', () => {
|
|||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const raw = {
|
||||
_id: 'foo-namespace:foo:bar',
|
||||
_version: 24,
|
||||
_primary_term: 24,
|
||||
_seq_no: 42,
|
||||
_source: {
|
||||
type: 'foo',
|
||||
foo: {
|
||||
|
@ -473,25 +505,38 @@ describe('saved object conversion', () => {
|
|||
expect(actual._source).not.toHaveProperty('migrationVersion');
|
||||
});
|
||||
|
||||
test('it copies the version property to _version', () => {
|
||||
test('it decodes the version property to _seq_no and _primary_term', () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
version: 4,
|
||||
version: encodeVersion(1, 2),
|
||||
} as any);
|
||||
|
||||
expect(actual).toHaveProperty('_version', 4);
|
||||
expect(actual).toHaveProperty('_seq_no', 1);
|
||||
expect(actual).toHaveProperty('_primary_term', 2);
|
||||
});
|
||||
|
||||
test(`if unspecified it doesn't add _version property`, () => {
|
||||
test(`if unspecified it doesn't add _seq_no or _primary_term properties`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
const actual = serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
} as any);
|
||||
|
||||
expect(actual).not.toHaveProperty('_version');
|
||||
expect(actual).not.toHaveProperty('_seq_no');
|
||||
expect(actual).not.toHaveProperty('_primary_term');
|
||||
});
|
||||
|
||||
test(`if version invalid it throws`, () => {
|
||||
const serializer = new SavedObjectsSerializer(new SavedObjectsSchema());
|
||||
expect(() =>
|
||||
serializer.savedObjectToRaw({
|
||||
type: '',
|
||||
attributes: {},
|
||||
version: 'foo',
|
||||
} as any)
|
||||
).toThrowErrorMatchingInlineSnapshot(`"Invalid version [foo]"`);
|
||||
});
|
||||
|
||||
test('it copies attributes to _source[type]', () => {
|
||||
|
|
|
@ -25,3 +25,6 @@ export function isNotFoundError(maybeError: any): boolean;
|
|||
export function isConflictError(maybeError: any): boolean;
|
||||
export function isEsUnavailableError(maybeError: any): boolean;
|
||||
export function isEsAutoCreateIndexError(maybeError: any): boolean;
|
||||
|
||||
export function createInvalidVersionError(version: any): Error;
|
||||
export function isInvalidVersionError(maybeError: Error): boolean;
|
||||
|
|
|
@ -50,6 +50,15 @@ export function isBadRequestError(error) {
|
|||
return error && error[code] === CODE_BAD_REQUEST;
|
||||
}
|
||||
|
||||
// 400 - invalid version
|
||||
const CODE_INVALID_VERSION = 'SavedObjectsClient/invalidVersion';
|
||||
export function createInvalidVersionError(versionInput) {
|
||||
return decorate(Boom.badRequest(`Invalid version [${versionInput}]`), CODE_INVALID_VERSION, 400);
|
||||
}
|
||||
export function isInvalidVersionError(error) {
|
||||
return error && error[code] === CODE_INVALID_VERSION;
|
||||
}
|
||||
|
||||
|
||||
// 401 - Not Authorized
|
||||
const CODE_NOT_AUTHORIZED = 'SavedObjectsClient/notAuthorized';
|
||||
|
|
|
@ -23,6 +23,7 @@ import { getSearchDsl } from './search_dsl';
|
|||
import { includedFields } from './included_fields';
|
||||
import { decorateEsError } from './decorate_es_error';
|
||||
import * as errors from './errors';
|
||||
import { decodeRequestVersion, encodeVersion, encodeHitVersion } from '../../version';
|
||||
|
||||
// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
|
||||
// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
|
||||
|
@ -173,7 +174,8 @@ export class SavedObjectsRepository {
|
|||
const {
|
||||
error,
|
||||
_id: responseId,
|
||||
_version: version,
|
||||
_seq_no: seqNo,
|
||||
_primary_term: primaryTerm,
|
||||
} = Object.values(response)[0];
|
||||
|
||||
const {
|
||||
|
@ -208,7 +210,7 @@ export class SavedObjectsRepository {
|
|||
id,
|
||||
type,
|
||||
updated_at: time,
|
||||
version,
|
||||
version: encodeVersion(seqNo, primaryTerm),
|
||||
attributes,
|
||||
references,
|
||||
};
|
||||
|
@ -262,7 +264,6 @@ export class SavedObjectsRepository {
|
|||
* @returns {promise} - { took, timed_out, total, deleted, batches, version_conflicts, noops, retries, failures }
|
||||
*/
|
||||
async deleteByNamespace(namespace) {
|
||||
|
||||
if (!namespace || typeof namespace !== 'string') {
|
||||
throw new TypeError(`namespace is required, and must be a string`);
|
||||
}
|
||||
|
@ -338,7 +339,7 @@ export class SavedObjectsRepository {
|
|||
ignore: [404],
|
||||
rest_total_hits_as_int: true,
|
||||
body: {
|
||||
version: true,
|
||||
seq_no_primary_term: true,
|
||||
...getSearchDsl(this._mappings, this._schema, {
|
||||
search,
|
||||
defaultSearchOperator,
|
||||
|
@ -423,7 +424,7 @@ export class SavedObjectsRepository {
|
|||
id,
|
||||
type,
|
||||
...time && { updated_at: time },
|
||||
version: doc._version,
|
||||
version: encodeHitVersion(doc),
|
||||
attributes: doc._source[type],
|
||||
references: doc._source.references || [],
|
||||
migrationVersion: doc._source.migrationVersion,
|
||||
|
@ -466,7 +467,7 @@ export class SavedObjectsRepository {
|
|||
id,
|
||||
type,
|
||||
...updatedAt && { updated_at: updatedAt },
|
||||
version: response._version,
|
||||
version: encodeHitVersion(response),
|
||||
attributes: response._source[type],
|
||||
references: response._source.references || [],
|
||||
migrationVersion: response._source.migrationVersion,
|
||||
|
@ -479,7 +480,7 @@ export class SavedObjectsRepository {
|
|||
* @param {string} type
|
||||
* @param {string} id
|
||||
* @param {object} [options={}]
|
||||
* @property {integer} options.version - ensures version matches that of persisted object
|
||||
* @property {string} options.version - ensures version matches that of persisted object
|
||||
* @property {string} [options.namespace]
|
||||
* @property {array} [options.references] - [{ name, type, id }]
|
||||
* @returns {promise}
|
||||
|
@ -496,7 +497,7 @@ export class SavedObjectsRepository {
|
|||
id: this._serializer.generateRawId(namespace, type, id),
|
||||
type: this._type,
|
||||
index: this._index,
|
||||
version,
|
||||
...(version && decodeRequestVersion(version)),
|
||||
refresh: 'wait_for',
|
||||
ignore: [404],
|
||||
body: {
|
||||
|
@ -517,7 +518,7 @@ export class SavedObjectsRepository {
|
|||
id,
|
||||
type,
|
||||
updated_at: time,
|
||||
version: response._version,
|
||||
version: encodeHitVersion(response),
|
||||
references,
|
||||
attributes
|
||||
};
|
||||
|
@ -593,7 +594,7 @@ export class SavedObjectsRepository {
|
|||
type,
|
||||
updated_at: time,
|
||||
references: response.get._source.references,
|
||||
version: response._version,
|
||||
version: encodeHitVersion(response),
|
||||
attributes: response.get._source[type],
|
||||
};
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ import * as errors from './errors';
|
|||
import elasticsearch from 'elasticsearch';
|
||||
import { SavedObjectsSchema } from '../../schema';
|
||||
import { SavedObjectsSerializer } from '../../serialization';
|
||||
import { encodeHitVersion } from '../../version';
|
||||
|
||||
// BEWARE: The SavedObjectClient depends on the implementation details of the SavedObjectsRepository
|
||||
// so any breaking changes to this repository are considered breaking changes to the SavedObjectsClient.
|
||||
|
@ -39,6 +40,8 @@ describe('SavedObjectsRepository', () => {
|
|||
let migrator;
|
||||
const mockTimestamp = '2017-08-14T15:49:14.886Z';
|
||||
const mockTimestampFields = { updated_at: mockTimestamp };
|
||||
const mockVersionProps = { _seq_no: 1, _primary_term: 1 };
|
||||
const mockVersion = encodeHitVersion(mockVersionProps);
|
||||
const noNamespaceSearchResults = {
|
||||
hits: {
|
||||
total: 4,
|
||||
|
@ -47,6 +50,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'index-pattern:logstash-*',
|
||||
_score: 1,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
type: 'index-pattern',
|
||||
...mockTimestampFields,
|
||||
|
@ -61,6 +65,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'config:6.0.0-alpha1',
|
||||
_score: 1,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
type: 'config',
|
||||
...mockTimestampFields,
|
||||
|
@ -74,6 +79,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'index-pattern:stocks-*',
|
||||
_score: 1,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
type: 'index-pattern',
|
||||
...mockTimestampFields,
|
||||
|
@ -88,6 +94,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'globaltype:something',
|
||||
_score: 1,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
type: 'globaltype',
|
||||
...mockTimestampFields,
|
||||
|
@ -107,6 +114,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'foo-namespace:index-pattern:logstash-*',
|
||||
_score: 1,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
namespace: 'foo-namespace',
|
||||
type: 'index-pattern',
|
||||
|
@ -122,6 +130,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'foo-namespace:config:6.0.0-alpha1',
|
||||
_score: 1,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
namespace: 'foo-namespace',
|
||||
type: 'config',
|
||||
|
@ -136,6 +145,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'foo-namespace:index-pattern:stocks-*',
|
||||
_score: 1,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
namespace: 'foo-namespace',
|
||||
type: 'index-pattern',
|
||||
|
@ -151,6 +161,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'globaltype:something',
|
||||
_score: 1,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
type: 'globaltype',
|
||||
...mockTimestampFields,
|
||||
|
@ -237,7 +248,7 @@ describe('SavedObjectsRepository', () => {
|
|||
callAdminCluster.callsFake((method, params) => ({
|
||||
_type: '_doc',
|
||||
_id: params.id,
|
||||
_version: 2
|
||||
...mockVersionProps,
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -270,7 +281,7 @@ describe('SavedObjectsRepository', () => {
|
|||
type: 'index-pattern',
|
||||
id: 'logstash-*',
|
||||
...mockTimestampFields,
|
||||
version: 2,
|
||||
version: mockVersion,
|
||||
attributes: {
|
||||
title: 'Logstash',
|
||||
},
|
||||
|
@ -471,14 +482,16 @@ describe('SavedObjectsRepository', () => {
|
|||
create: {
|
||||
error: false,
|
||||
_id: '1',
|
||||
_version: 1,
|
||||
_seq_no: 1,
|
||||
_primary_term: 1,
|
||||
}
|
||||
},
|
||||
{
|
||||
create: {
|
||||
error: false,
|
||||
_id: '2',
|
||||
_version: 1,
|
||||
_seq_no: 1,
|
||||
_primary_term: 1,
|
||||
}
|
||||
}
|
||||
],
|
||||
|
@ -522,7 +535,7 @@ describe('SavedObjectsRepository', () => {
|
|||
{
|
||||
id: 'one',
|
||||
type: 'config',
|
||||
version: 1,
|
||||
version: mockVersion,
|
||||
updated_at: mockTimestamp,
|
||||
attributes: {
|
||||
title: 'Test One!!',
|
||||
|
@ -532,7 +545,7 @@ describe('SavedObjectsRepository', () => {
|
|||
{
|
||||
id: 'two',
|
||||
type: 'index-pattern',
|
||||
version: 1,
|
||||
version: mockVersion,
|
||||
updated_at: mockTimestamp,
|
||||
attributes: {
|
||||
title: 'Test Two!!',
|
||||
|
@ -589,7 +602,7 @@ describe('SavedObjectsRepository', () => {
|
|||
create: {
|
||||
_type: '_doc',
|
||||
_id: 'index-pattern:two',
|
||||
_version: 2
|
||||
...mockVersionProps,
|
||||
}
|
||||
}]
|
||||
}));
|
||||
|
@ -608,7 +621,7 @@ describe('SavedObjectsRepository', () => {
|
|||
}, {
|
||||
id: 'two',
|
||||
type: 'index-pattern',
|
||||
version: 2,
|
||||
version: mockVersion,
|
||||
...mockTimestampFields,
|
||||
attributes: { title: 'Test Two' },
|
||||
references: [],
|
||||
|
@ -624,13 +637,13 @@ describe('SavedObjectsRepository', () => {
|
|||
create: {
|
||||
_type: '_doc',
|
||||
_id: 'config:one',
|
||||
_version: 2
|
||||
...mockVersionProps
|
||||
}
|
||||
}, {
|
||||
create: {
|
||||
_type: '_doc',
|
||||
_id: 'index-pattern:two',
|
||||
_version: 2
|
||||
...mockVersionProps
|
||||
}
|
||||
}]
|
||||
}));
|
||||
|
@ -647,14 +660,14 @@ describe('SavedObjectsRepository', () => {
|
|||
{
|
||||
id: 'one',
|
||||
type: 'config',
|
||||
version: 2,
|
||||
version: mockVersion,
|
||||
...mockTimestampFields,
|
||||
attributes: { title: 'Test One' },
|
||||
references: [],
|
||||
}, {
|
||||
id: 'two',
|
||||
type: 'index-pattern',
|
||||
version: 2,
|
||||
version: mockVersion,
|
||||
...mockTimestampFields,
|
||||
attributes: { title: 'Test Two' },
|
||||
references: [],
|
||||
|
@ -952,7 +965,7 @@ describe('SavedObjectsRepository', () => {
|
|||
id: doc._id.replace(/(index-pattern|config|globaltype)\:/, ''),
|
||||
type: doc._source.type,
|
||||
...mockTimestampFields,
|
||||
version: doc._version,
|
||||
version: mockVersion,
|
||||
attributes: doc._source[doc._source.type],
|
||||
references: [],
|
||||
});
|
||||
|
@ -976,7 +989,7 @@ describe('SavedObjectsRepository', () => {
|
|||
id: doc._id.replace(/(foo-namespace\:)?(index-pattern|config|globaltype)\:/, ''),
|
||||
type: doc._source.type,
|
||||
...mockTimestampFields,
|
||||
version: doc._version,
|
||||
version: mockVersion,
|
||||
attributes: doc._source[doc._source.type],
|
||||
references: [],
|
||||
});
|
||||
|
@ -1022,7 +1035,7 @@ describe('SavedObjectsRepository', () => {
|
|||
const noNamespaceResult = {
|
||||
_id: 'index-pattern:logstash-*',
|
||||
_type: '_doc',
|
||||
_version: 2,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
type: 'index-pattern',
|
||||
specialProperty: 'specialValue',
|
||||
|
@ -1035,7 +1048,7 @@ describe('SavedObjectsRepository', () => {
|
|||
const namespacedResult = {
|
||||
_id: 'foo-namespace:index-pattern:logstash-*',
|
||||
_type: '_doc',
|
||||
_version: 2,
|
||||
...mockVersionProps,
|
||||
_source: {
|
||||
namespace: 'foo-namespace',
|
||||
type: 'index-pattern',
|
||||
|
@ -1064,7 +1077,7 @@ describe('SavedObjectsRepository', () => {
|
|||
id: 'logstash-*',
|
||||
type: 'index-pattern',
|
||||
updated_at: mockTimestamp,
|
||||
version: 2,
|
||||
version: mockVersion,
|
||||
attributes: {
|
||||
title: 'Testing'
|
||||
},
|
||||
|
@ -1080,7 +1093,7 @@ describe('SavedObjectsRepository', () => {
|
|||
id: 'logstash-*',
|
||||
type: 'index-pattern',
|
||||
updated_at: mockTimestamp,
|
||||
version: 2,
|
||||
version: mockVersion,
|
||||
attributes: {
|
||||
title: 'Testing'
|
||||
},
|
||||
|
@ -1209,7 +1222,7 @@ describe('SavedObjectsRepository', () => {
|
|||
_type: '_doc',
|
||||
_id: 'config:good',
|
||||
found: true,
|
||||
_version: 2,
|
||||
...mockVersionProps,
|
||||
_source: { ...mockTimestampFields, config: { title: 'Test' } }
|
||||
}, {
|
||||
_type: '_doc',
|
||||
|
@ -1230,7 +1243,7 @@ describe('SavedObjectsRepository', () => {
|
|||
id: 'good',
|
||||
type: 'config',
|
||||
...mockTimestampFields,
|
||||
version: 2,
|
||||
version: mockVersion,
|
||||
attributes: { title: 'Test' },
|
||||
references: [],
|
||||
});
|
||||
|
@ -1245,14 +1258,13 @@ describe('SavedObjectsRepository', () => {
|
|||
describe('#update', () => {
|
||||
const id = 'logstash-*';
|
||||
const type = 'index-pattern';
|
||||
const newVersion = 2;
|
||||
const attributes = { title: 'Testing' };
|
||||
|
||||
beforeEach(() => {
|
||||
callAdminCluster.returns(Promise.resolve({
|
||||
_id: `${type}:${id}`,
|
||||
_type: '_doc',
|
||||
_version: newVersion,
|
||||
...mockVersionProps,
|
||||
result: 'updated'
|
||||
}));
|
||||
});
|
||||
|
@ -1267,7 +1279,7 @@ describe('SavedObjectsRepository', () => {
|
|||
sinon.assert.calledOnce(migrator.awaitMigration);
|
||||
});
|
||||
|
||||
it('returns current ES document version', async () => {
|
||||
it('returns current ES document _seq_no and _primary_term encoded as version', async () => {
|
||||
const response = await savedObjectsRepository.update('index-pattern', 'logstash-*', attributes, {
|
||||
namespace: 'foo-namespace',
|
||||
references: [{
|
||||
|
@ -1280,7 +1292,7 @@ describe('SavedObjectsRepository', () => {
|
|||
id,
|
||||
type,
|
||||
...mockTimestampFields,
|
||||
version: newVersion,
|
||||
version: mockVersion,
|
||||
attributes,
|
||||
references: [{
|
||||
name: 'ref_0',
|
||||
|
@ -1295,12 +1307,18 @@ describe('SavedObjectsRepository', () => {
|
|||
type,
|
||||
id,
|
||||
{ title: 'Testing' },
|
||||
{ version: newVersion - 1 }
|
||||
{
|
||||
version: encodeHitVersion({
|
||||
_seq_no: 100,
|
||||
_primary_term: 200
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
sinon.assert.calledOnce(callAdminCluster);
|
||||
sinon.assert.calledWithExactly(callAdminCluster, sinon.match.string, sinon.match({
|
||||
version: newVersion - 1
|
||||
if_seq_no: 100,
|
||||
if_primary_term: 200,
|
||||
}));
|
||||
});
|
||||
|
||||
|
@ -1320,7 +1338,6 @@ describe('SavedObjectsRepository', () => {
|
|||
sinon.assert.calledWithExactly(callAdminCluster, 'update', {
|
||||
type: '_doc',
|
||||
id: 'foo-namespace:index-pattern:logstash-*',
|
||||
version: undefined,
|
||||
body: {
|
||||
doc: {
|
||||
updated_at: mockTimestamp,
|
||||
|
@ -1355,7 +1372,6 @@ describe('SavedObjectsRepository', () => {
|
|||
sinon.assert.calledWithExactly(callAdminCluster, 'update', {
|
||||
type: '_doc',
|
||||
id: 'index-pattern:logstash-*',
|
||||
version: undefined,
|
||||
body: {
|
||||
doc: {
|
||||
updated_at: mockTimestamp,
|
||||
|
@ -1391,7 +1407,6 @@ describe('SavedObjectsRepository', () => {
|
|||
sinon.assert.calledWithExactly(callAdminCluster, 'update', {
|
||||
type: '_doc',
|
||||
id: 'globaltype:foo',
|
||||
version: undefined,
|
||||
body: {
|
||||
doc: {
|
||||
updated_at: mockTimestamp,
|
||||
|
@ -1417,7 +1432,7 @@ describe('SavedObjectsRepository', () => {
|
|||
callAdminCluster.callsFake((method, params) => ({
|
||||
_type: '_doc',
|
||||
_id: params.id,
|
||||
_version: 2,
|
||||
...mockVersionProps,
|
||||
_index: '.kibana',
|
||||
get: {
|
||||
found: true,
|
||||
|
@ -1437,7 +1452,7 @@ describe('SavedObjectsRepository', () => {
|
|||
callAdminCluster.callsFake((method, params) => ({
|
||||
_type: '_doc',
|
||||
_id: params.id,
|
||||
_version: 2,
|
||||
...mockVersionProps,
|
||||
_index: '.kibana',
|
||||
get: {
|
||||
found: true,
|
||||
|
@ -1466,7 +1481,7 @@ describe('SavedObjectsRepository', () => {
|
|||
type: 'config',
|
||||
id: '6.0.0-alpha1',
|
||||
...mockTimestampFields,
|
||||
version: 2,
|
||||
version: mockVersion,
|
||||
attributes: {
|
||||
buildNum: 8468,
|
||||
defaultIndex: 'logstash-*'
|
||||
|
@ -1539,7 +1554,7 @@ describe('SavedObjectsRepository', () => {
|
|||
callAdminCluster.callsFake((method, params) => ({
|
||||
_type: '_doc',
|
||||
_id: params.id,
|
||||
_version: 2,
|
||||
...mockVersionProps,
|
||||
_index: '.kibana',
|
||||
get: {
|
||||
found: true,
|
||||
|
|
|
@ -58,7 +58,7 @@ export interface FindResponse<T extends SavedObjectAttributes = any> {
|
|||
}
|
||||
|
||||
export interface UpdateOptions extends BaseOptions {
|
||||
version?: number;
|
||||
version?: string;
|
||||
}
|
||||
|
||||
export interface BulkGetObject {
|
||||
|
@ -78,7 +78,7 @@ export interface SavedObjectAttributes {
|
|||
export interface SavedObject<T extends SavedObjectAttributes = any> {
|
||||
id: string;
|
||||
type: string;
|
||||
version?: number;
|
||||
version?: string;
|
||||
updated_at?: string;
|
||||
error?: {
|
||||
message: string;
|
||||
|
|
21
src/server/saved_objects/version/base64.ts
Normal file
21
src/server/saved_objects/version/base64.ts
Normal file
|
@ -0,0 +1,21 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export const decodeBase64 = (base64: string) => Buffer.from(base64, 'base64').toString('utf8');
|
||||
export const encodeBase64 = (utf8: string) => Buffer.from(utf8, 'utf8').toString('base64');
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
jest.mock('./decode_version', () => ({
|
||||
decodeVersion: jest.fn().mockReturnValue({ _seq_no: 1, _primary_term: 2 }),
|
||||
}));
|
||||
|
||||
import { decodeRequestVersion } from './decode_request_version';
|
||||
import { decodeVersion } from './decode_version';
|
||||
|
||||
it('renames decodeVersion() return value to use if_seq_no and if_primary_term', () => {
|
||||
expect(decodeRequestVersion('foobar')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"if_primary_term": 2,
|
||||
"if_seq_no": 1,
|
||||
}
|
||||
`);
|
||||
expect(decodeVersion).toHaveBeenCalledWith('foobar');
|
||||
});
|
32
src/server/saved_objects/version/decode_request_version.ts
Normal file
32
src/server/saved_objects/version/decode_request_version.ts
Normal file
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { decodeVersion } from './decode_version';
|
||||
|
||||
/**
|
||||
* Helper for decoding version to request params that are driven
|
||||
* by the version info
|
||||
*/
|
||||
export function decodeRequestVersion(version?: string) {
|
||||
const decoded = decodeVersion(version);
|
||||
return {
|
||||
if_seq_no: decoded._seq_no,
|
||||
if_primary_term: decoded._primary_term,
|
||||
};
|
||||
}
|
102
src/server/saved_objects/version/decode_version.test.ts
Normal file
102
src/server/saved_objects/version/decode_version.test.ts
Normal file
|
@ -0,0 +1,102 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
|
||||
import { decodeVersion } from './decode_version';
|
||||
|
||||
describe('decodeVersion', () => {
|
||||
it('parses version back into {_seq_no,_primary_term} object', () => {
|
||||
expect(decodeVersion('WzQsMV0=')).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"_primary_term": 1,
|
||||
"_seq_no": 4,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws Boom error if not in base64', () => {
|
||||
let error;
|
||||
try {
|
||||
decodeVersion('[1,4]');
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error.message).toMatchInlineSnapshot(`"Invalid version [[1,4]]"`);
|
||||
expect(Boom.isBoom(error)).toBe(true);
|
||||
expect(error.output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"headers": Object {},
|
||||
"payload": Object {
|
||||
"error": "Bad Request",
|
||||
"message": "Invalid version [[1,4]]",
|
||||
"statusCode": 400,
|
||||
},
|
||||
"statusCode": 400,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws if not JSON encoded', () => {
|
||||
let error;
|
||||
try {
|
||||
decodeVersion('MSwy');
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error.message).toMatchInlineSnapshot(`"Invalid version [MSwy]"`);
|
||||
expect(Boom.isBoom(error)).toBe(true);
|
||||
expect(error.output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"headers": Object {},
|
||||
"payload": Object {
|
||||
"error": "Bad Request",
|
||||
"message": "Invalid version [MSwy]",
|
||||
"statusCode": 400,
|
||||
},
|
||||
"statusCode": 400,
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
it('throws if either value is not an integer', () => {
|
||||
let error;
|
||||
try {
|
||||
decodeVersion('WzEsMy41XQ==');
|
||||
} catch (err) {
|
||||
error = err;
|
||||
}
|
||||
|
||||
expect(error.message).toMatchInlineSnapshot(`"Invalid version [WzEsMy41XQ==]"`);
|
||||
expect(Boom.isBoom(error)).toBe(true);
|
||||
expect(error.output).toMatchInlineSnapshot(`
|
||||
Object {
|
||||
"headers": Object {},
|
||||
"payload": Object {
|
||||
"error": "Bad Request",
|
||||
"message": "Invalid version [WzEsMy41XQ==]",
|
||||
"statusCode": 400,
|
||||
},
|
||||
"statusCode": 400,
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
51
src/server/saved_objects/version/decode_version.ts
Normal file
51
src/server/saved_objects/version/decode_version.ts
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { createInvalidVersionError } from '../service/lib/errors';
|
||||
import { decodeBase64 } from './base64';
|
||||
|
||||
/**
|
||||
* Decode the "opaque" version string to the sequence params we
|
||||
* can use to activate optimistic concurrency in Elasticsearch
|
||||
*/
|
||||
export function decodeVersion(version?: string) {
|
||||
try {
|
||||
if (typeof version !== 'string') {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
const seqParams = JSON.parse(decodeBase64(version)) as [number, number];
|
||||
|
||||
if (
|
||||
!Array.isArray(seqParams) ||
|
||||
seqParams.length !== 2 ||
|
||||
!Number.isInteger(seqParams[0]) ||
|
||||
!Number.isInteger(seqParams[1])
|
||||
) {
|
||||
throw new TypeError();
|
||||
}
|
||||
|
||||
return {
|
||||
_seq_no: seqParams[0],
|
||||
_primary_term: seqParams[1],
|
||||
};
|
||||
} catch (_) {
|
||||
throw createInvalidVersionError(version);
|
||||
}
|
||||
}
|
30
src/server/saved_objects/version/encode_hit_version.test.ts
Normal file
30
src/server/saved_objects/version/encode_hit_version.test.ts
Normal file
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
jest.mock('./encode_version', () => ({
|
||||
encodeVersion: jest.fn().mockReturnValue('foo'),
|
||||
}));
|
||||
|
||||
import { encodeHitVersion } from './encode_hit_version';
|
||||
import { encodeVersion } from './encode_version';
|
||||
|
||||
it('renames decodeVersion() return value to use if_seq_no and if_primary_term', () => {
|
||||
expect(encodeHitVersion({ _seq_no: 1, _primary_term: 2 })).toMatchInlineSnapshot(`"foo"`);
|
||||
expect(encodeVersion).toHaveBeenCalledWith(1, 2);
|
||||
});
|
28
src/server/saved_objects/version/encode_hit_version.ts
Normal file
28
src/server/saved_objects/version/encode_hit_version.ts
Normal file
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { encodeVersion } from './encode_version';
|
||||
|
||||
/**
|
||||
* Helper for encoding a version from a "hit" (hits.hits[#] from _search) or
|
||||
* "doc" (body from GET, update, etc) object
|
||||
*/
|
||||
export function encodeHitVersion(response: { _seq_no: number; _primary_term: number }) {
|
||||
return encodeVersion(response._seq_no, response._primary_term);
|
||||
}
|
62
src/server/saved_objects/version/encode_version.test.ts
Normal file
62
src/server/saved_objects/version/encode_version.test.ts
Normal file
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { encodeVersion } from './encode_version';
|
||||
|
||||
describe('encodeVersion', () => {
|
||||
it('throws if primaryTerm is not an integer', () => {
|
||||
expect(() => encodeVersion(1, undefined as any)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_primary_term from elasticsearch must be an integer"`
|
||||
);
|
||||
expect(() => encodeVersion(1, null as any)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_primary_term from elasticsearch must be an integer"`
|
||||
);
|
||||
expect(() => encodeVersion(1, {} as any)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_primary_term from elasticsearch must be an integer"`
|
||||
);
|
||||
expect(() => encodeVersion(1, [] as any)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_primary_term from elasticsearch must be an integer"`
|
||||
);
|
||||
expect(() => encodeVersion(1, 2.5 as any)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_primary_term from elasticsearch must be an integer"`
|
||||
);
|
||||
});
|
||||
|
||||
it('throws if seqNo is not an integer', () => {
|
||||
expect(() => encodeVersion(undefined as any, 1)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_seq_no from elasticsearch must be an integer"`
|
||||
);
|
||||
expect(() => encodeVersion(null as any, 1)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_seq_no from elasticsearch must be an integer"`
|
||||
);
|
||||
expect(() => encodeVersion({} as any, 1)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_seq_no from elasticsearch must be an integer"`
|
||||
);
|
||||
expect(() => encodeVersion([] as any, 1)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_seq_no from elasticsearch must be an integer"`
|
||||
);
|
||||
expect(() => encodeVersion(2.5 as any, 1)).toThrowErrorMatchingInlineSnapshot(
|
||||
`"_seq_no from elasticsearch must be an integer"`
|
||||
);
|
||||
});
|
||||
|
||||
it('returns a base64 encoded, JSON string of seqNo and primaryTerm', () => {
|
||||
expect(encodeVersion(123, 456)).toMatchInlineSnapshot(`"WzEyMyw0NTZd"`);
|
||||
});
|
||||
});
|
37
src/server/saved_objects/version/encode_version.ts
Normal file
37
src/server/saved_objects/version/encode_version.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
import { encodeBase64 } from './base64';
|
||||
|
||||
/**
|
||||
* Encode the sequence params into an "opaque" version string
|
||||
* that can be used in the saved object API in place of numeric
|
||||
* version numbers
|
||||
*/
|
||||
export function encodeVersion(seqNo: number, primaryTerm: number) {
|
||||
if (!Number.isInteger(primaryTerm)) {
|
||||
throw new TypeError('_primary_term from elasticsearch must be an integer');
|
||||
}
|
||||
|
||||
if (!Number.isInteger(seqNo)) {
|
||||
throw new TypeError('_seq_no from elasticsearch must be an integer');
|
||||
}
|
||||
|
||||
return encodeBase64(JSON.stringify([seqNo, primaryTerm]));
|
||||
}
|
23
src/server/saved_objects/version/index.ts
Normal file
23
src/server/saved_objects/version/index.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Licensed to Elasticsearch B.V. under one or more contributor
|
||||
* license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright
|
||||
* ownership. Elasticsearch B.V. licenses this file to you under
|
||||
* the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
export * from './encode_version';
|
||||
export * from './encode_hit_version';
|
||||
export * from './decode_version';
|
||||
export * from './decode_request_version';
|
|
@ -42,13 +42,13 @@ describe('Saved Object', function () {
|
|||
* that can be used to stub es calls.
|
||||
* @param indexPatternId
|
||||
* @param additionalOptions - object that will be assigned to the mocked doc response.
|
||||
* @returns {{attributes: {}, type: string, id: *, _version: integer}}
|
||||
* @returns {{attributes: {}, type: string, id: *, _version: string}}
|
||||
*/
|
||||
function getMockedDocResponse(indexPatternId, additionalOptions = {}) {
|
||||
return {
|
||||
type: 'dashboard',
|
||||
id: indexPatternId,
|
||||
_version: 2,
|
||||
_version: 'foo',
|
||||
attributes: {},
|
||||
...additionalOptions
|
||||
};
|
||||
|
@ -242,7 +242,11 @@ describe('Saved Object', function () {
|
|||
return createInitializedSavedObject({ type: 'dashboard' }).then(savedObject => {
|
||||
const mockDocResponse = getMockedDocResponse('myId');
|
||||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({ type: 'dashboard', id: 'myId', _version: 2 });
|
||||
return BluebirdPromise.resolve({
|
||||
type: 'dashboard',
|
||||
id: 'myId',
|
||||
_version: 'foo'
|
||||
});
|
||||
});
|
||||
|
||||
stubESResponse(mockDocResponse);
|
||||
|
@ -261,7 +265,9 @@ describe('Saved Object', function () {
|
|||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
expect(savedObject.isSaving).to.be(true);
|
||||
return BluebirdPromise.resolve({
|
||||
type: 'dashboard', id, version: 2
|
||||
type: 'dashboard',
|
||||
id,
|
||||
version: 'foo'
|
||||
});
|
||||
});
|
||||
expect(savedObject.isSaving).to.be(false);
|
||||
|
@ -300,7 +306,7 @@ describe('Saved Object', function () {
|
|||
sinon.stub(savedObjectsClientStub, 'create').callsFake(() => {
|
||||
return BluebirdPromise.resolve({
|
||||
id,
|
||||
version: 2,
|
||||
version: 'foo',
|
||||
type: 'dashboard',
|
||||
});
|
||||
});
|
||||
|
@ -544,7 +550,7 @@ describe('Saved Object', function () {
|
|||
attributes: {
|
||||
title: 'testIndexPattern'
|
||||
},
|
||||
_version: 2
|
||||
_version: 'foo'
|
||||
});
|
||||
|
||||
const savedObject = new SavedObject(config);
|
||||
|
|
|
@ -220,7 +220,6 @@ export function SavedObjectProvider(Promise, Private, Notifier, confirmModalProm
|
|||
return savedObjectsClient.get(esType, this.id)
|
||||
.then(resp => {
|
||||
// temporary compatability for savedObjectsClient
|
||||
|
||||
return {
|
||||
_id: resp.id,
|
||||
_type: resp.type,
|
||||
|
|
|
@ -86,7 +86,7 @@ jest.mock('../unsupported_time_patterns', () => ({
|
|||
|
||||
jest.mock('../../saved_objects', () => {
|
||||
const object = {
|
||||
_version: 1,
|
||||
_version: 'foo',
|
||||
_id: 'foo',
|
||||
attributes: {
|
||||
title: 'something'
|
||||
|
@ -106,10 +106,11 @@ jest.mock('../../saved_objects', () => {
|
|||
}
|
||||
|
||||
object.attributes.title = body.title;
|
||||
object._version += 'a';
|
||||
|
||||
return {
|
||||
id: object._id,
|
||||
_version: ++object._version,
|
||||
_version: object._version,
|
||||
};
|
||||
}
|
||||
},
|
||||
|
@ -137,13 +138,13 @@ describe('IndexPattern', () => {
|
|||
const pattern = new IndexPattern('foo');
|
||||
await pattern.init();
|
||||
|
||||
expect(pattern.version).toBe(2);
|
||||
expect(pattern.version).toBe('fooa');
|
||||
|
||||
// Create the same one - we're going to handle concurrency
|
||||
const samePattern = new IndexPattern('foo');
|
||||
await samePattern.init();
|
||||
|
||||
expect(samePattern.version).toBe(3);
|
||||
expect(samePattern.version).toBe('fooaa');
|
||||
|
||||
// This will conflict because samePattern did a save (from refreshFields)
|
||||
// but the resave should work fine
|
||||
|
|
|
@ -47,7 +47,6 @@ describe('SavedObject', () => {
|
|||
|
||||
const client = sinon.stub();
|
||||
const savedObject = new SavedObject(client, { version });
|
||||
|
||||
expect(savedObject._version).to.be(version);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -29,7 +29,7 @@ describe('SavedObjectsClient', () => {
|
|||
id: 'AVwSwFxtcMV38qjDZoQg',
|
||||
type: 'config',
|
||||
attributes: { title: 'Example title' },
|
||||
version: 2
|
||||
version: 'foo'
|
||||
};
|
||||
|
||||
let kfetchStub;
|
||||
|
@ -228,8 +228,8 @@ describe('SavedObjectsClient', () => {
|
|||
|
||||
test('makes HTTP call', () => {
|
||||
const attributes = { foo: 'Foo', bar: 'Bar' };
|
||||
const body = { attributes, version: 2 };
|
||||
const options = { version: 2 };
|
||||
const body = { attributes, version: 'foo' };
|
||||
const options = { version: 'foo' };
|
||||
|
||||
savedObjectsClient.update('index-pattern', 'logstash-*', attributes, options);
|
||||
sinon.assert.calledOnce(kfetchStub);
|
||||
|
|
|
@ -41,7 +41,7 @@ describe('uiSettings/createOrUpgradeSavedConfig', function () {
|
|||
create: sinon.stub().callsFake(async (type, attributes, options = {}) => ({
|
||||
type,
|
||||
id: options.id,
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
}))
|
||||
};
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ export default function ({ getService }) {
|
|||
type: 'dashboard',
|
||||
id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e',
|
||||
updated_at: resp.body.saved_objects[1].updated_at,
|
||||
version: 1,
|
||||
version: 'WzgsMV0=',
|
||||
attributes: {
|
||||
title: 'A great new dashboard'
|
||||
},
|
||||
|
@ -99,7 +99,7 @@ export default function ({ getService }) {
|
|||
type: 'visualization',
|
||||
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
|
||||
updated_at: resp.body.saved_objects[0].updated_at,
|
||||
version: 1,
|
||||
version: 'WzAsMV0=',
|
||||
attributes: {
|
||||
title: 'An existing visualization'
|
||||
},
|
||||
|
@ -109,7 +109,7 @@ export default function ({ getService }) {
|
|||
type: 'dashboard',
|
||||
id: 'a01b2f57-fcfd-4864-b735-09e28f0d815e',
|
||||
updated_at: resp.body.saved_objects[1].updated_at,
|
||||
version: 1,
|
||||
version: 'WzEsMV0=',
|
||||
attributes: {
|
||||
title: 'A great new dashboard'
|
||||
},
|
||||
|
|
|
@ -51,7 +51,7 @@ export default function ({ getService }) {
|
|||
visualization: '7.0.0'
|
||||
},
|
||||
updated_at: resp.body.updated_at,
|
||||
version: 1,
|
||||
version: 'WzgsMV0=',
|
||||
attributes: {
|
||||
title: 'My favorite vis'
|
||||
},
|
||||
|
@ -96,7 +96,7 @@ export default function ({ getService }) {
|
|||
visualization: '7.0.0'
|
||||
},
|
||||
updated_at: resp.body.updated_at,
|
||||
version: 1,
|
||||
version: 'WzAsMV0=',
|
||||
attributes: {
|
||||
title: 'My favorite vis'
|
||||
},
|
||||
|
|
|
@ -42,7 +42,7 @@ export default function ({ getService }) {
|
|||
{
|
||||
type: 'visualization',
|
||||
id: 'dd7caf20-9efd-11e7-acb3-3dab96693fab',
|
||||
version: 1,
|
||||
version: 'WzIsMV0=',
|
||||
attributes: {
|
||||
'title': 'Count of requests'
|
||||
},
|
||||
|
|
|
@ -48,7 +48,7 @@ export default function ({ getService }) {
|
|||
id: resp.body.id,
|
||||
type: 'visualization',
|
||||
updated_at: resp.body.updated_at,
|
||||
version: 2,
|
||||
version: 'WzgsMV0=',
|
||||
attributes: {
|
||||
title: 'My second favorite vis'
|
||||
},
|
||||
|
|
|
@ -101,7 +101,8 @@ export interface DatabaseSearchResponse<T> {
|
|||
_id: string;
|
||||
_score: number;
|
||||
_source: T;
|
||||
_version?: number;
|
||||
_seq_no?: number;
|
||||
_primary_term?: number;
|
||||
_explanation?: DatabaseExplanation;
|
||||
fields?: any;
|
||||
highlight?: any;
|
||||
|
@ -129,7 +130,8 @@ export interface DatabaseGetDocumentResponse<Source> {
|
|||
_index: string;
|
||||
_type: string;
|
||||
_id: string;
|
||||
_version: number;
|
||||
_seq_no: number;
|
||||
_primary_term: number;
|
||||
found: boolean;
|
||||
_source: Source;
|
||||
}
|
||||
|
@ -183,8 +185,8 @@ export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams {
|
|||
refresh?: DatabaseRefresh;
|
||||
routing?: string;
|
||||
timeout?: string;
|
||||
version?: number;
|
||||
versionType?: DatabaseVersionType;
|
||||
ifSeqNo?: number;
|
||||
ifPrimaryTerm?: number;
|
||||
index: string;
|
||||
type: string;
|
||||
id: string;
|
||||
|
@ -195,7 +197,8 @@ export interface DatabaseIndexDocumentResponse {
|
|||
_index: string;
|
||||
_type: string;
|
||||
_id: string;
|
||||
_version: number;
|
||||
_seq_no: number;
|
||||
_primary_term: number;
|
||||
result: string;
|
||||
}
|
||||
|
||||
|
@ -204,7 +207,8 @@ export interface DatabaseUpdateDocumentResponse {
|
|||
_index: string;
|
||||
_type: string;
|
||||
_id: string;
|
||||
_version: number;
|
||||
_seq_no: number;
|
||||
_primary_term: number;
|
||||
result: string;
|
||||
}
|
||||
|
||||
|
@ -213,7 +217,8 @@ export interface DatabaseDeleteDocumentResponse {
|
|||
_index: string;
|
||||
_type: string;
|
||||
_id: string;
|
||||
_version: number;
|
||||
_seq_no: number;
|
||||
_primary_term: number;
|
||||
result: string;
|
||||
}
|
||||
|
||||
|
@ -226,8 +231,8 @@ export interface DatabaseIndexDocumentParams<T> extends DatabaseGenericParams {
|
|||
timeout?: string;
|
||||
timestamp?: Date | number;
|
||||
ttl?: string;
|
||||
version?: number;
|
||||
versionType?: DatabaseVersionType;
|
||||
ifSeqNo?: number;
|
||||
ifPrimaryTerm?: number;
|
||||
pipeline?: string;
|
||||
id?: string;
|
||||
index: string;
|
||||
|
@ -247,8 +252,8 @@ export interface DatabaseCreateDocumentParams extends DatabaseGenericParams {
|
|||
timeout?: string;
|
||||
timestamp?: Date | number;
|
||||
ttl?: string;
|
||||
version?: number;
|
||||
versionType?: DatabaseVersionType;
|
||||
ifSeqNo?: number;
|
||||
ifPrimaryTerm?: number;
|
||||
pipeline?: string;
|
||||
id?: string;
|
||||
index: string;
|
||||
|
@ -266,8 +271,8 @@ export interface DatabaseDeleteDocumentParams extends DatabaseGenericParams {
|
|||
refresh?: DatabaseRefresh;
|
||||
routing?: string;
|
||||
timeout?: string;
|
||||
version?: number;
|
||||
versionType?: DatabaseVersionType;
|
||||
ifSeqNo?: number;
|
||||
ifPrimaryTerm?: number;
|
||||
index: string;
|
||||
type: string;
|
||||
id: string;
|
||||
|
@ -283,8 +288,8 @@ export interface DatabaseGetParams extends DatabaseGenericParams {
|
|||
_source?: DatabaseNameList;
|
||||
_sourceExclude?: DatabaseNameList;
|
||||
_source_includes?: DatabaseNameList;
|
||||
version?: number;
|
||||
versionType?: DatabaseVersionType;
|
||||
ifSeqNo?: number;
|
||||
ifPrimaryTerm?: number;
|
||||
id: string;
|
||||
index: string;
|
||||
type: string;
|
||||
|
@ -292,7 +297,6 @@ export interface DatabaseGetParams extends DatabaseGenericParams {
|
|||
|
||||
export type DatabaseNameList = string | string[] | boolean;
|
||||
export type DatabaseRefresh = boolean | 'true' | 'false' | 'wait_for' | '';
|
||||
export type DatabaseVersionType = 'internal' | 'external' | 'external_gte' | 'force';
|
||||
export type ExpandWildcards = 'open' | 'closed' | 'none' | 'all';
|
||||
export type DefaultOperator = 'AND' | 'OR';
|
||||
export type DatabaseConflicts = 'abort' | 'proceed';
|
||||
|
@ -311,6 +315,7 @@ export interface DatabaseDeleteDocumentResponse {
|
|||
_index: string;
|
||||
_type: string;
|
||||
_id: string;
|
||||
_version: number;
|
||||
_seq_no: number;
|
||||
_primary_term: number;
|
||||
result: string;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { get } from 'lodash';
|
||||
import { FrameworkUser } from '../framework/adapter_types';
|
||||
import { internalAuthData } from './../framework/adapter_types';
|
||||
import {
|
||||
|
@ -113,43 +112,6 @@ export class KibanaDatabaseAdapter implements DatabaseAdapter {
|
|||
return result;
|
||||
}
|
||||
|
||||
private async fetchAllFromScroll<Source>(
|
||||
user: FrameworkUser,
|
||||
response: DatabaseSearchResponse<Source>,
|
||||
hits: DatabaseSearchResponse<Source>['hits']['hits'] = []
|
||||
): Promise<
|
||||
Array<{
|
||||
_index: string;
|
||||
_type: string;
|
||||
_id: string;
|
||||
_score: number;
|
||||
_source: Source;
|
||||
_version?: number;
|
||||
fields?: any;
|
||||
highlight?: any;
|
||||
inner_hits?: any;
|
||||
sort?: string[];
|
||||
}>
|
||||
> {
|
||||
const newHits = get(response, 'hits.hits', []);
|
||||
const scrollId = get(response, '_scroll_id');
|
||||
|
||||
if (newHits.length > 0) {
|
||||
hits.push(...newHits);
|
||||
|
||||
return this.callWithUser(user, 'scroll', {
|
||||
body: {
|
||||
scroll: '30s',
|
||||
scroll_id: scrollId,
|
||||
},
|
||||
}).then((innerResponse: DatabaseSearchResponse<Source>) => {
|
||||
return this.fetchAllFromScroll(user, innerResponse, hits);
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(hits);
|
||||
}
|
||||
|
||||
private callWithUser(user: FrameworkUser, esMethod: string, options: any = {}): any {
|
||||
if (user.kind === 'authenticated') {
|
||||
return this.es.callWithRequest(
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
"name": "version",
|
||||
"description": "The version number the source configuration was last persisted with",
|
||||
"args": [],
|
||||
"type": { "kind": "SCALAR", "name": "Float", "ofType": null },
|
||||
"type": { "kind": "SCALAR", "name": "String", "ofType": null },
|
||||
"isDeprecated": false,
|
||||
"deprecationReason": null
|
||||
},
|
||||
|
@ -430,6 +430,16 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "Float",
|
||||
|
@ -511,16 +521,6 @@
|
|||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "SCALAR",
|
||||
"name": "String",
|
||||
"description": "The `String` scalar type represents textual data, represented as UTF-8 character sequences. The String type is most often used by GraphQL to represent free-form human-readable text.",
|
||||
"fields": null,
|
||||
"inputFields": null,
|
||||
"interfaces": null,
|
||||
"enumValues": null,
|
||||
"possibleTypes": null
|
||||
},
|
||||
{
|
||||
"kind": "OBJECT",
|
||||
"name": "InfraSourceFields",
|
||||
|
|
|
@ -19,7 +19,7 @@ export interface InfraSource {
|
|||
/** The id of the source */
|
||||
id: string;
|
||||
/** The version number the source configuration was last persisted with */
|
||||
version?: number | null;
|
||||
version?: string | null;
|
||||
/** The timestamp the source configuration was last persisted at */
|
||||
updatedAt?: number | null;
|
||||
/** The raw configuration of the source */
|
||||
|
|
|
@ -12,7 +12,7 @@ export const sourcesSchema = gql`
|
|||
"The id of the source"
|
||||
id: ID!
|
||||
"The version number the source configuration was last persisted with"
|
||||
version: Float
|
||||
version: String
|
||||
"The timestamp the source configuration was last persisted at"
|
||||
updatedAt: Float
|
||||
"The raw configuration of the source"
|
||||
|
|
|
@ -47,7 +47,7 @@ export interface InfraSource {
|
|||
/** The id of the source */
|
||||
id: string;
|
||||
/** The version number the source configuration was last persisted with */
|
||||
version?: number | null;
|
||||
version?: string | null;
|
||||
/** The timestamp the source configuration was last persisted at */
|
||||
updatedAt?: number | null;
|
||||
/** The raw configuration of the source */
|
||||
|
@ -606,7 +606,7 @@ export namespace InfraSourceResolvers {
|
|||
/** The id of the source */
|
||||
id?: IdResolver<string, TypeParent, Context>;
|
||||
/** The version number the source configuration was last persisted with */
|
||||
version?: VersionResolver<number | null, TypeParent, Context>;
|
||||
version?: VersionResolver<string | null, TypeParent, Context>;
|
||||
/** The timestamp the source configuration was last persisted at */
|
||||
updatedAt?: UpdatedAtResolver<number | null, TypeParent, Context>;
|
||||
/** The raw configuration of the source */
|
||||
|
@ -635,7 +635,7 @@ export namespace InfraSourceResolvers {
|
|||
Context
|
||||
>;
|
||||
export type VersionResolver<
|
||||
R = number | null,
|
||||
R = string | null,
|
||||
Parent = InfraSource,
|
||||
Context = InfraContext
|
||||
> = Resolver<R, Parent, Context>;
|
||||
|
|
|
@ -14,7 +14,7 @@ describe('the InfraSources lib', () => {
|
|||
configuration: createMockStaticConfiguration({}),
|
||||
savedObjects: createMockSavedObjectsService({
|
||||
id: 'TEST_ID',
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
|
@ -34,7 +34,7 @@ describe('the InfraSources lib', () => {
|
|||
|
||||
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
|
||||
id: 'TEST_ID',
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
updatedAt: 946684800000,
|
||||
configuration: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
|
@ -66,7 +66,7 @@ describe('the InfraSources lib', () => {
|
|||
}),
|
||||
savedObjects: createMockSavedObjectsService({
|
||||
id: 'TEST_ID',
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {
|
||||
fields: {
|
||||
|
@ -80,7 +80,7 @@ describe('the InfraSources lib', () => {
|
|||
|
||||
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
|
||||
id: 'TEST_ID',
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
updatedAt: 946684800000,
|
||||
configuration: {
|
||||
metricAlias: 'METRIC_ALIAS',
|
||||
|
@ -101,7 +101,7 @@ describe('the InfraSources lib', () => {
|
|||
configuration: createMockStaticConfiguration({}),
|
||||
savedObjects: createMockSavedObjectsService({
|
||||
id: 'TEST_ID',
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
updated_at: '2000-01-01T00:00:00.000Z',
|
||||
attributes: {},
|
||||
}),
|
||||
|
@ -111,7 +111,7 @@ describe('the InfraSources lib', () => {
|
|||
|
||||
expect(await sourcesLib.getSourceConfiguration(request, 'TEST_ID')).toMatchObject({
|
||||
id: 'TEST_ID',
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
updatedAt: 946684800000,
|
||||
configuration: {
|
||||
metricAlias: expect.any(String),
|
||||
|
|
|
@ -51,7 +51,7 @@ export const InfraSavedSourceConfigurationRuntimeType = runtimeTypes.intersectio
|
|||
attributes: PartialInfraSourceConfigurationRuntimeType,
|
||||
}),
|
||||
runtimeTypes.partial({
|
||||
version: runtimeTypes.number,
|
||||
version: runtimeTypes.string,
|
||||
updated_at: TimestampFromString,
|
||||
}),
|
||||
]);
|
||||
|
|
|
@ -17,7 +17,7 @@ describe('field format map', function () {
|
|||
const indexPatternSavedObject = {
|
||||
id: 'logstash-*',
|
||||
type: 'index-pattern',
|
||||
version: 4,
|
||||
version: 'abc',
|
||||
attributes: {
|
||||
title: 'logstash-*',
|
||||
timeFieldName: '@timestamp',
|
||||
|
|
|
@ -18,7 +18,8 @@ ClientMock.prototype.index = function (params = {}) {
|
|||
_index: params.index || 'index',
|
||||
_type: params.type || constants.DEFAULT_SETTING_DOCTYPE,
|
||||
_id: params.id || uniqueId('testDoc'),
|
||||
_version: 1,
|
||||
_seq_no: 1,
|
||||
_primary_term: 1,
|
||||
_shards: { total: shardCount, successful: shardCount, failed: 0 },
|
||||
created: true
|
||||
});
|
||||
|
@ -53,7 +54,8 @@ ClientMock.prototype.get = function (params = {}, source = {}) {
|
|||
_index: params.index || 'index',
|
||||
_type: params.type || constants.DEFAULT_SETTING_DOCTYPE,
|
||||
_id: params.id || 'AVRPRLnlp7Ur1SZXfT-T',
|
||||
_version: params.version || 1,
|
||||
_seq_no: params._seq_no || 1,
|
||||
_primary_term: params._primary_term || 1,
|
||||
found: true,
|
||||
_source: _source
|
||||
});
|
||||
|
@ -65,7 +67,8 @@ ClientMock.prototype.search = function (params = {}, count = 5, source = {}) {
|
|||
_index: params.index || 'index',
|
||||
_type: params.type || constants.DEFAULT_SETTING_DOCTYPE,
|
||||
_id: uniqueId('documentId'),
|
||||
_version: random(1, 5),
|
||||
_seq_no: random(1, 5),
|
||||
_primar_term: random(1, 5),
|
||||
_score: null,
|
||||
_source: {
|
||||
created_at: new Date().toString(),
|
||||
|
@ -96,7 +99,8 @@ ClientMock.prototype.update = function (params = {}) {
|
|||
_index: params.index || 'index',
|
||||
_type: params.type || constants.DEFAULT_SETTING_DOCTYPE,
|
||||
_id: params.id || uniqueId('testDoc'),
|
||||
_version: params.version + 1 || 2,
|
||||
_seq_no: params.if_seq_no + 1 || 2,
|
||||
_primary_term: params.if_primary_term + 1 || 2,
|
||||
_shards: { total: shardCount, successful: shardCount, failed: 0 },
|
||||
created: true
|
||||
});
|
||||
|
|
|
@ -135,7 +135,8 @@ describe('Job Class', function () {
|
|||
expect(jobDoc).to.have.property('id');
|
||||
expect(jobDoc).to.have.property('index');
|
||||
expect(jobDoc).to.have.property('type');
|
||||
expect(jobDoc).to.have.property('version');
|
||||
expect(jobDoc).to.have.property('_seq_no');
|
||||
expect(jobDoc).to.have.property('_primary_term');
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
|
@ -383,7 +384,8 @@ describe('Job Class', function () {
|
|||
expect(doc).to.have.property('index', index);
|
||||
expect(doc).to.have.property('type', jobDoc.type);
|
||||
expect(doc).to.have.property('id', jobDoc.id);
|
||||
expect(doc).to.have.property('version', jobDoc.version);
|
||||
expect(doc).to.have.property('_seq_no', jobDoc._seq_no);
|
||||
expect(doc).to.have.property('_primary_term', jobDoc._primary_term);
|
||||
expect(doc).to.have.property('created_by', defaultCreatedBy);
|
||||
|
||||
expect(doc).to.have.property('payload');
|
||||
|
|
|
@ -336,11 +336,6 @@ describe('Worker class', function () {
|
|||
searchStub = sinon.stub(mockQueue.client, 'search').callsFake(() => Promise.resolve({ hits: { hits: [] } }));
|
||||
});
|
||||
|
||||
it('should query with version', function () {
|
||||
const params = getSearchParams();
|
||||
expect(params).to.have.property('version', true);
|
||||
});
|
||||
|
||||
it('should query by default doctype', function () {
|
||||
const params = getSearchParams();
|
||||
expect(params).to.have.property('type', constants.DEFAULT_SETTING_DOCTYPE);
|
||||
|
@ -367,6 +362,11 @@ describe('Worker class', function () {
|
|||
clock.restore();
|
||||
});
|
||||
|
||||
it('should query with seq_no_primary_term', function () {
|
||||
const { body } = getSearchParams(jobtype);
|
||||
expect(body).to.have.property('seq_no_primary_term', true);
|
||||
});
|
||||
|
||||
it('should filter unwanted source data', function () {
|
||||
const excludedFields = [ 'output.content' ];
|
||||
const { body } = getSearchParams(jobtype);
|
||||
|
@ -432,7 +432,6 @@ describe('Worker class', function () {
|
|||
index: 'myIndex',
|
||||
type: 'test',
|
||||
id: 12345,
|
||||
version: 3
|
||||
};
|
||||
return mockQueue.client.get(params)
|
||||
.then((jobDoc) => {
|
||||
|
@ -446,13 +445,14 @@ describe('Worker class', function () {
|
|||
clock.restore();
|
||||
});
|
||||
|
||||
it('should use version on update', function () {
|
||||
it('should use seqNo and primaryTerm on update', function () {
|
||||
worker._claimJob(job);
|
||||
const query = updateSpy.firstCall.args[0];
|
||||
expect(query).to.have.property('index', job._index);
|
||||
expect(query).to.have.property('type', job._type);
|
||||
expect(query).to.have.property('id', job._id);
|
||||
expect(query).to.have.property('version', job._version);
|
||||
expect(query).to.have.property('if_seq_no', job._seq_no);
|
||||
expect(query).to.have.property('if_primary_term', job._primary_term);
|
||||
});
|
||||
|
||||
it('should increment the job attempts', function () {
|
||||
|
@ -500,7 +500,7 @@ describe('Worker class', function () {
|
|||
expect(msg).to.equal(false);
|
||||
});
|
||||
|
||||
it('should reject the promise on version errors', function () {
|
||||
it('should reject the promise on conflict errors', function () {
|
||||
mockQueue.client.update.restore();
|
||||
sinon.stub(mockQueue.client, 'update').returns(Promise.reject({ statusCode: 409 }));
|
||||
return worker._claimJob(job)
|
||||
|
@ -524,7 +524,8 @@ describe('Worker class', function () {
|
|||
_index: 'myIndex',
|
||||
_type: 'test',
|
||||
_id: 12345,
|
||||
_version: 3,
|
||||
_seq_no: 3,
|
||||
_primary_term: 3,
|
||||
found: true,
|
||||
_source: {
|
||||
jobtype: 'jobtype',
|
||||
|
@ -608,13 +609,14 @@ describe('Worker class', function () {
|
|||
clock.restore();
|
||||
});
|
||||
|
||||
it('should use version on update', function () {
|
||||
it('should use _seq_no and _primary_term on update', function () {
|
||||
worker._failJob(job);
|
||||
const query = updateSpy.firstCall.args[0];
|
||||
expect(query).to.have.property('index', job._index);
|
||||
expect(query).to.have.property('type', job._type);
|
||||
expect(query).to.have.property('id', job._id);
|
||||
expect(query).to.have.property('version', job._version);
|
||||
expect(query).to.have.property('if_seq_no', job._seq_no);
|
||||
expect(query).to.have.property('if_primary_term', job._primary_term);
|
||||
});
|
||||
|
||||
it('should set status to failed', function () {
|
||||
|
@ -631,7 +633,7 @@ describe('Worker class', function () {
|
|||
expect(doc.output).to.have.property('content', msg);
|
||||
});
|
||||
|
||||
it('should return true on version mismatch errors', function () {
|
||||
it('should return true on conflict errors', function () {
|
||||
mockQueue.client.update.restore();
|
||||
sinon.stub(mockQueue.client, 'update').returns(Promise.reject({ statusCode: 409 }));
|
||||
return worker._failJob(job)
|
||||
|
@ -735,7 +737,8 @@ describe('Worker class', function () {
|
|||
expect(query).to.have.property('index', job._index);
|
||||
expect(query).to.have.property('type', job._type);
|
||||
expect(query).to.have.property('id', job._id);
|
||||
expect(query).to.have.property('version', job._version);
|
||||
expect(query).to.have.property('if_seq_no', job._seq_no);
|
||||
expect(query).to.have.property('if_primary_term', job._primary_term);
|
||||
expect(query.body.doc).to.have.property('output');
|
||||
expect(query.body.doc.output).to.have.property('content_type', false);
|
||||
expect(query.body.doc.output).to.have.property('content', payload);
|
||||
|
|
|
@ -82,7 +82,8 @@ export class Job extends events.EventEmitter {
|
|||
id: doc._id,
|
||||
type: doc._type,
|
||||
index: doc._index,
|
||||
version: doc._version,
|
||||
_seq_no: doc._seq_no,
|
||||
_primary_term: doc._primary_term,
|
||||
};
|
||||
this.debug(`Job created in index ${this.index}`);
|
||||
|
||||
|
@ -118,7 +119,8 @@ export class Job extends events.EventEmitter {
|
|||
index: doc._index,
|
||||
id: doc._id,
|
||||
type: doc._type,
|
||||
version: doc._version,
|
||||
_seq_no: doc._seq_no,
|
||||
_primary_term: doc._primary_term,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -130,7 +130,8 @@ export class Worker extends events.EventEmitter {
|
|||
index: job._index,
|
||||
type: job._type,
|
||||
id: job._id,
|
||||
version: job._version,
|
||||
if_seq_no: job._seq_no,
|
||||
if_primary_term: job._primary_term,
|
||||
body: { doc }
|
||||
})
|
||||
.then((response) => {
|
||||
|
@ -167,7 +168,8 @@ export class Worker extends events.EventEmitter {
|
|||
index: job._index,
|
||||
type: job._type,
|
||||
id: job._id,
|
||||
version: job._version,
|
||||
if_seq_no: job._seq_no,
|
||||
if_primary_term: job._primary_term,
|
||||
body: { doc }
|
||||
})
|
||||
.then(() => true)
|
||||
|
@ -244,7 +246,8 @@ export class Worker extends events.EventEmitter {
|
|||
index: job._index,
|
||||
type: job._type,
|
||||
id: job._id,
|
||||
version: job._version,
|
||||
if_seq_no: job._seq_no,
|
||||
if_primary_term: job._primary_term,
|
||||
body: { doc }
|
||||
})
|
||||
.then(() => {
|
||||
|
@ -351,6 +354,7 @@ export class Worker extends events.EventEmitter {
|
|||
_getPendingJobs() {
|
||||
const nowTime = moment().toISOString();
|
||||
const query = {
|
||||
seq_no_primary_term: true,
|
||||
_source: {
|
||||
excludes: [ 'output.content' ]
|
||||
},
|
||||
|
@ -385,7 +389,6 @@ export class Worker extends events.EventEmitter {
|
|||
return this.client.search({
|
||||
index: `${this.queue.index}-*`,
|
||||
type: this.doctype,
|
||||
version: true,
|
||||
body: query
|
||||
})
|
||||
.then((results) => {
|
||||
|
|
|
@ -217,7 +217,7 @@ export class SpacesSavedObjectsClient implements SavedObjectsClient {
|
|||
* @param {string} type
|
||||
* @param {string} id
|
||||
* @param {object} [options={}]
|
||||
* @property {integer} options.version - ensures version matches that of persisted object
|
||||
* @property {string} options.version - ensures version matches that of persisted object
|
||||
* @property {string} [options.namespace]
|
||||
* @returns {promise}
|
||||
*/
|
||||
|
|
|
@ -20,7 +20,8 @@ const getMockTaskInstance = () => ({
|
|||
const getMockConcreteTaskInstance = () => {
|
||||
const concrete: {
|
||||
id: string;
|
||||
version: number;
|
||||
sequenceNumber: number;
|
||||
primaryTerm: number;
|
||||
attempts: number;
|
||||
status: TaskStatus;
|
||||
runAt: Date;
|
||||
|
@ -29,7 +30,8 @@ const getMockConcreteTaskInstance = () => {
|
|||
params: any;
|
||||
} = {
|
||||
id: 'hy8o99o83',
|
||||
version: 1,
|
||||
sequenceNumber: 1,
|
||||
primaryTerm: 1,
|
||||
attempts: 0,
|
||||
status: 'idle',
|
||||
runAt: new Date(moment('2018-09-18T05:33:09.588Z').valueOf()),
|
||||
|
@ -146,11 +148,12 @@ Object {
|
|||
"params": Object {
|
||||
"abc": "def",
|
||||
},
|
||||
"primaryTerm": 1,
|
||||
"runAt": 2018-09-18T05:33:09.588Z,
|
||||
"sequenceNumber": 1,
|
||||
"state": Object {},
|
||||
"status": "idle",
|
||||
"taskType": "nice_task",
|
||||
"version": 1,
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
|
|
@ -206,9 +206,14 @@ export interface ConcreteTaskInstance extends TaskInstance {
|
|||
id: string;
|
||||
|
||||
/**
|
||||
* The version of the Elaticsearch document.
|
||||
* The sequence number from the Elaticsearch document.
|
||||
*/
|
||||
version: number;
|
||||
sequenceNumber: number;
|
||||
|
||||
/**
|
||||
* The primary term from the Elaticsearch document.
|
||||
*/
|
||||
primaryTerm: number;
|
||||
|
||||
/**
|
||||
* The number of unsuccessful attempts since the last successful run. This
|
||||
|
|
|
@ -230,7 +230,8 @@ describe('TaskManagerRunner', () => {
|
|||
{
|
||||
id: 'foo',
|
||||
taskType: 'bar',
|
||||
version: 32,
|
||||
sequenceNumber: 32,
|
||||
primaryTerm: 32,
|
||||
runAt: new Date(),
|
||||
attempts: 0,
|
||||
params: {},
|
||||
|
|
|
@ -42,7 +42,8 @@ describe('TaskStore', () => {
|
|||
const callCluster = sinon.spy(() =>
|
||||
Promise.resolve({
|
||||
_id: 'testid',
|
||||
_version: 3344,
|
||||
_seq_no: 3344,
|
||||
_primary_term: 3344,
|
||||
})
|
||||
);
|
||||
const store = new TaskStore({
|
||||
|
@ -89,7 +90,8 @@ describe('TaskStore', () => {
|
|||
|
||||
expect(result).toMatchObject({
|
||||
...task,
|
||||
version: 3344,
|
||||
sequenceNumber: 3344,
|
||||
primaryTerm: 3344,
|
||||
id: 'testid',
|
||||
});
|
||||
});
|
||||
|
@ -255,7 +257,8 @@ describe('TaskStore', () => {
|
|||
status: 'idle',
|
||||
taskType: 'foo',
|
||||
user: 'jimbo',
|
||||
version: undefined,
|
||||
sequenceNumber: undefined,
|
||||
primaryTerm: undefined,
|
||||
},
|
||||
{
|
||||
attempts: 2,
|
||||
|
@ -268,7 +271,8 @@ describe('TaskStore', () => {
|
|||
status: 'running',
|
||||
taskType: 'bar',
|
||||
user: 'dabo',
|
||||
version: undefined,
|
||||
sequenceNumber: undefined,
|
||||
primaryTerm: undefined,
|
||||
},
|
||||
],
|
||||
searchAfter: ['b', 2],
|
||||
|
@ -345,7 +349,7 @@ describe('TaskStore', () => {
|
|||
},
|
||||
size: 10,
|
||||
sort: { 'task.runAt': { order: 'asc' } },
|
||||
version: true,
|
||||
seq_no_primary_term: true,
|
||||
},
|
||||
index,
|
||||
});
|
||||
|
@ -405,7 +409,8 @@ describe('TaskStore', () => {
|
|||
status: 'idle',
|
||||
taskType: 'foo',
|
||||
user: 'jimbo',
|
||||
version: undefined,
|
||||
sequenceNumber: undefined,
|
||||
primaryTerm: undefined,
|
||||
},
|
||||
{
|
||||
attempts: 2,
|
||||
|
@ -418,7 +423,8 @@ describe('TaskStore', () => {
|
|||
status: 'running',
|
||||
taskType: 'bar',
|
||||
user: 'dabo',
|
||||
version: undefined,
|
||||
sequenceNumber: undefined,
|
||||
primaryTerm: undefined,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
@ -433,12 +439,17 @@ describe('TaskStore', () => {
|
|||
params: { hello: 'world' },
|
||||
state: { foo: 'bar' },
|
||||
taskType: 'report',
|
||||
version: 2,
|
||||
sequenceNumber: 2,
|
||||
primaryTerm: 2,
|
||||
attempts: 3,
|
||||
status: 'idle' as TaskStatus,
|
||||
};
|
||||
|
||||
const callCluster = sinon.spy(async () => ({ _version: task.version + 1 }));
|
||||
const callCluster = sinon.spy(async () => ({
|
||||
_seq_no: task.sequenceNumber + 1,
|
||||
_primary_term: task.primaryTerm + 1,
|
||||
}));
|
||||
|
||||
const store = new TaskStore({
|
||||
callCluster,
|
||||
index: 'tasky',
|
||||
|
@ -454,12 +465,13 @@ describe('TaskStore', () => {
|
|||
expect(callCluster.args[0][1]).toMatchObject({
|
||||
id: task.id,
|
||||
index: 'tasky',
|
||||
version: 2,
|
||||
if_seq_no: 2,
|
||||
if_primary_term: 2,
|
||||
refresh: true,
|
||||
body: {
|
||||
doc: {
|
||||
task: {
|
||||
...['id', 'version'].reduce((acc, prop) => _.omit(acc, prop), task),
|
||||
..._.omit(task, ['id', 'sequenceNumber', 'primaryTerm']),
|
||||
params: JSON.stringify(task.params),
|
||||
state: JSON.stringify(task.state),
|
||||
},
|
||||
|
@ -467,7 +479,11 @@ describe('TaskStore', () => {
|
|||
},
|
||||
});
|
||||
|
||||
expect(result).toEqual({ ...task, version: 3 });
|
||||
expect(result).toEqual({
|
||||
...task,
|
||||
sequenceNumber: 3,
|
||||
primaryTerm: 3,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -478,7 +494,8 @@ describe('TaskStore', () => {
|
|||
Promise.resolve({
|
||||
_index: 'myindex',
|
||||
_id: id,
|
||||
_version: 32,
|
||||
_seq_no: 32,
|
||||
_primary_term: 32,
|
||||
result: 'deleted',
|
||||
})
|
||||
);
|
||||
|
@ -496,7 +513,8 @@ describe('TaskStore', () => {
|
|||
expect(result).toEqual({
|
||||
id,
|
||||
index: 'myindex',
|
||||
version: 32,
|
||||
sequenceNumber: 32,
|
||||
primaryTerm: 32,
|
||||
result: 'deleted',
|
||||
});
|
||||
|
||||
|
|
|
@ -33,7 +33,8 @@ export interface FetchResult {
|
|||
export interface RemoveResult {
|
||||
index: string;
|
||||
id: string;
|
||||
version: string;
|
||||
sequenceNumber: number;
|
||||
primaryTerm: number;
|
||||
result: string;
|
||||
}
|
||||
|
||||
|
@ -41,7 +42,8 @@ export interface RemoveResult {
|
|||
export interface RawTaskDoc {
|
||||
_id: string;
|
||||
_index: string;
|
||||
_version: number;
|
||||
_seq_no: number;
|
||||
_primary_term: number;
|
||||
_source: {
|
||||
type: string;
|
||||
task: {
|
||||
|
@ -179,7 +181,8 @@ export class TaskStore {
|
|||
return {
|
||||
...taskInstance,
|
||||
id: result._id,
|
||||
version: result._version,
|
||||
sequenceNumber: result._seq_no,
|
||||
primaryTerm: result._primary_term,
|
||||
attempts: 0,
|
||||
status: task.status,
|
||||
runAt: task.runAt,
|
||||
|
@ -225,7 +228,7 @@ export class TaskStore {
|
|||
},
|
||||
size: 10,
|
||||
sort: { 'task.runAt': { order: 'asc' } },
|
||||
version: true,
|
||||
seq_no_primary_term: true,
|
||||
});
|
||||
|
||||
return docs;
|
||||
|
@ -241,13 +244,14 @@ export class TaskStore {
|
|||
public async update(doc: ConcreteTaskInstance): Promise<ConcreteTaskInstance> {
|
||||
const rawDoc = taskDocToRaw(doc, this.index);
|
||||
|
||||
const { _version } = await this.callCluster('update', {
|
||||
const result = await this.callCluster('update', {
|
||||
body: {
|
||||
doc: rawDoc._source,
|
||||
},
|
||||
id: doc.id,
|
||||
index: this.index,
|
||||
version: doc.version,
|
||||
if_seq_no: doc.sequenceNumber,
|
||||
if_primary_term: doc.primaryTerm,
|
||||
// The refresh is important so that if we immediately look for work,
|
||||
// we don't pick up this task.
|
||||
refresh: true,
|
||||
|
@ -255,7 +259,8 @@ export class TaskStore {
|
|||
|
||||
return {
|
||||
...doc,
|
||||
version: _version,
|
||||
sequenceNumber: result._seq_no,
|
||||
primaryTerm: result._primary_term,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -277,7 +282,8 @@ export class TaskStore {
|
|||
return {
|
||||
index: result._index,
|
||||
id: result._id,
|
||||
version: result._version,
|
||||
sequenceNumber: result._seq_no,
|
||||
primaryTerm: result._primary_term,
|
||||
result: result.result,
|
||||
};
|
||||
}
|
||||
|
@ -333,7 +339,8 @@ function rawSource(doc: TaskInstance) {
|
|||
};
|
||||
|
||||
delete (source as any).id;
|
||||
delete (source as any).version;
|
||||
delete (source as any).sequenceNumber;
|
||||
delete (source as any).primaryTerm;
|
||||
delete (source as any).type;
|
||||
|
||||
return {
|
||||
|
@ -350,7 +357,8 @@ function taskDocToRaw(doc: ConcreteTaskInstance, index: string): RawTaskDoc {
|
|||
_id: doc.id,
|
||||
_index: index,
|
||||
_source: { type, task },
|
||||
_version: doc.version,
|
||||
_seq_no: doc.sequenceNumber,
|
||||
_primary_term: doc.primaryTerm,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -358,7 +366,8 @@ function rawToTaskDoc(doc: RawTaskDoc): ConcreteTaskInstance {
|
|||
return {
|
||||
...doc._source.task,
|
||||
id: doc._id,
|
||||
version: doc._version,
|
||||
sequenceNumber: doc._seq_no,
|
||||
primaryTerm: doc._primary_term,
|
||||
params: parseJSONField(doc._source.task.params, 'params', doc),
|
||||
state: parseJSONField(doc._source.task.state, 'state', doc),
|
||||
};
|
||||
|
|
|
@ -87,7 +87,7 @@ describe('ReindexActions', () => {
|
|||
type: REINDEX_OP_TYPE,
|
||||
id: '9',
|
||||
attributes: { indexName: 'hi', locked: moment().format() },
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
} as ReindexSavedObject,
|
||||
{ newIndexName: 'test' }
|
||||
);
|
||||
|
@ -97,7 +97,7 @@ describe('ReindexActions', () => {
|
|||
expect(args[1]).toEqual('9');
|
||||
expect(args[2].indexName).toEqual('hi');
|
||||
expect(args[2].newIndexName).toEqual('test');
|
||||
expect(args[3]).toEqual({ version: 1 });
|
||||
expect(args[3]).toEqual({ version: 'foo' });
|
||||
});
|
||||
|
||||
it('throws if the reindexOp is not locked', async () => {
|
||||
|
@ -107,7 +107,7 @@ describe('ReindexActions', () => {
|
|||
type: REINDEX_OP_TYPE,
|
||||
id: '10',
|
||||
attributes: { indexName: 'hi', locked: null },
|
||||
version: 1,
|
||||
version: 'foo',
|
||||
} as ReindexSavedObject,
|
||||
{ newIndexName: 'test' }
|
||||
)
|
||||
|
|
|
@ -73,7 +73,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const { version, updatedAt, configuration, status } =
|
||||
response.data && response.data.createSource.source;
|
||||
|
||||
expect(version).to.be.greaterThan(0);
|
||||
expect(version).to.be.a('string');
|
||||
expect(updatedAt).to.be.greaterThan(0);
|
||||
expect(configuration.name).to.be('NAME');
|
||||
expect(configuration.description).to.be('DESCRIPTION');
|
||||
|
@ -102,7 +102,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const { version, updatedAt, configuration, status } =
|
||||
response.data && response.data.createSource.source;
|
||||
|
||||
expect(version).to.be.greaterThan(0);
|
||||
expect(version).to.be.a('string');
|
||||
expect(updatedAt).to.be.greaterThan(0);
|
||||
expect(configuration.name).to.be('NAME');
|
||||
expect(configuration.description).to.be('');
|
||||
|
@ -163,7 +163,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
|
||||
const { version } = creationResponse.data && creationResponse.data.createSource.source;
|
||||
|
||||
expect(version).to.be.greaterThan(0);
|
||||
expect(version).to.be.a('string');
|
||||
|
||||
const deletionResponse = await client.mutate<any>({
|
||||
mutation: deleteSourceMutation,
|
||||
|
@ -193,7 +193,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const { version: initialVersion, updatedAt: createdAt } =
|
||||
creationResponse.data && creationResponse.data.createSource.source;
|
||||
|
||||
expect(initialVersion).to.be.greaterThan(0);
|
||||
expect(initialVersion).to.be.a('string');
|
||||
expect(createdAt).to.be.greaterThan(0);
|
||||
|
||||
const updateResponse = await client.mutate<any>({
|
||||
|
@ -233,7 +233,8 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const { version, updatedAt, configuration, status } =
|
||||
updateResponse.data && updateResponse.data.updateSource.source;
|
||||
|
||||
expect(version).to.be.greaterThan(initialVersion);
|
||||
expect(version).to.be.a('string');
|
||||
expect(version).to.not.be(initialVersion);
|
||||
expect(updatedAt).to.be.greaterThan(createdAt);
|
||||
expect(configuration.name).to.be('UPDATED_NAME');
|
||||
expect(configuration.description).to.be('UPDATED_DESCRIPTION');
|
||||
|
@ -262,7 +263,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const { version: initialVersion, updatedAt: createdAt } =
|
||||
creationResponse.data && creationResponse.data.createSource.source;
|
||||
|
||||
expect(initialVersion).to.be.greaterThan(0);
|
||||
expect(initialVersion).to.be.a('string');
|
||||
expect(createdAt).to.be.greaterThan(0);
|
||||
|
||||
const updateResponse = await client.mutate<any>({
|
||||
|
@ -282,7 +283,8 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const { version, updatedAt, configuration, status } =
|
||||
updateResponse.data && updateResponse.data.updateSource.source;
|
||||
|
||||
expect(version).to.be.greaterThan(initialVersion);
|
||||
expect(version).to.be.a('string');
|
||||
expect(version).to.not.be(initialVersion);
|
||||
expect(updatedAt).to.be.greaterThan(createdAt);
|
||||
expect(configuration.metricAlias).to.be('metricbeat-**');
|
||||
expect(configuration.logAlias).to.be('filebeat-*');
|
||||
|
@ -304,7 +306,7 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const { version: initialVersion, updatedAt: createdAt } =
|
||||
creationResponse.data && creationResponse.data.createSource.source;
|
||||
|
||||
expect(initialVersion).to.be.greaterThan(0);
|
||||
expect(initialVersion).to.be.a('string');
|
||||
expect(createdAt).to.be.greaterThan(0);
|
||||
|
||||
const updateResponse = await client.mutate<any>({
|
||||
|
@ -324,7 +326,8 @@ const sourcesTests: KbnTestProvider = ({ getService }) => {
|
|||
const { version, updatedAt, configuration } =
|
||||
updateResponse.data && updateResponse.data.updateSource.source;
|
||||
|
||||
expect(version).to.be.greaterThan(initialVersion);
|
||||
expect(version).to.be.a('string');
|
||||
expect(version).to.not.be(initialVersion);
|
||||
expect(updatedAt).to.be.greaterThan(createdAt);
|
||||
expect(configuration.fields.container).to.be('UPDATED_CONTAINER');
|
||||
expect(configuration.fields.host).to.be('host.name');
|
||||
|
|
|
@ -84,7 +84,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest:
|
|||
type: 'dashboard',
|
||||
id: `${getIdPrefix(spaceId)}a01b2f57-fcfd-4864-b735-09e28f0d815e`,
|
||||
updated_at: resp.body.saved_objects[1].updated_at,
|
||||
version: 1,
|
||||
version: resp.body.saved_objects[1].version,
|
||||
attributes: {
|
||||
title: 'A great new dashboard',
|
||||
},
|
||||
|
@ -94,7 +94,7 @@ export function bulkCreateTestSuiteFactory(es: any, esArchiver: any, supertest:
|
|||
type: 'globaltype',
|
||||
id: `05976c65-1145-4858-bbf0-d225cc78a06e`,
|
||||
updated_at: resp.body.saved_objects[2].updated_at,
|
||||
version: 1,
|
||||
version: resp.body.saved_objects[2].version,
|
||||
attributes: {
|
||||
name: 'A new globaltype object',
|
||||
},
|
||||
|
|
|
@ -63,7 +63,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe
|
|||
},
|
||||
type: spaceAwareType,
|
||||
updated_at: resp.body.updated_at,
|
||||
version: 1,
|
||||
version: resp.body.version,
|
||||
attributes: {
|
||||
title: 'My favorite vis',
|
||||
},
|
||||
|
@ -104,7 +104,7 @@ export function createTestSuiteFactory(es: any, esArchiver: any, supertest: Supe
|
|||
id: resp.body.id,
|
||||
type: notSpaceAwareType,
|
||||
updated_at: resp.body.updated_at,
|
||||
version: 1,
|
||||
version: resp.body.version,
|
||||
attributes: {
|
||||
name: `Can't be contained to a space`,
|
||||
},
|
||||
|
|
|
@ -63,7 +63,7 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>)
|
|||
{
|
||||
type: 'globaltype',
|
||||
id: `8121a00-8efd-21e7-1cb3-34ab966434445`,
|
||||
version: 1,
|
||||
version: resp.body.saved_objects[0].version,
|
||||
attributes: {
|
||||
name: 'My favorite global object',
|
||||
},
|
||||
|
@ -96,7 +96,7 @@ export function findTestSuiteFactory(esArchiver: any, supertest: SuperTest<any>)
|
|||
{
|
||||
type: 'visualization',
|
||||
id: `${getIdPrefix(spaceId)}dd7caf20-9efd-11e7-acb3-3dab96693fab`,
|
||||
version: 1,
|
||||
version: resp.body.saved_objects[0].version,
|
||||
attributes: {
|
||||
title: 'Count of requests',
|
||||
},
|
||||
|
|
|
@ -74,7 +74,7 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
|
|||
id: resp.body.id,
|
||||
type: 'globaltype',
|
||||
updated_at: resp.body.updated_at,
|
||||
version: 2,
|
||||
version: resp.body.version,
|
||||
attributes: {
|
||||
name: 'My second favorite',
|
||||
},
|
||||
|
@ -99,7 +99,7 @@ export function updateTestSuiteFactory(esArchiver: any, supertest: SuperTest<any
|
|||
id: resp.body.id,
|
||||
type: 'visualization',
|
||||
updated_at: resp.body.updated_at,
|
||||
version: 2,
|
||||
version: resp.body.version,
|
||||
attributes: {
|
||||
title: 'My second favorite vis',
|
||||
},
|
||||
|
|
Loading…
Reference in a new issue