Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ of `^7.10.0`).

### Compatibility

The library is compatible with all Elasticsearch versions since 5.x, and you should use the same major version of the Elasticsearch instance that you are using.
Elastic language clients are guaranteed to be able to communicate with Elasticsearch or Elastic solutions running on the same major version and greater or equal minor version.

Language clients are forward compatible; meaning that clients support communicating with greater minor versions of Elasticsearch. Elastic language clients are not guaranteed to be backwards compatible.

| Elasticsearch Version | Client Version |
| --------------------- |----------------|
Expand Down
15 changes: 9 additions & 6 deletions docs/installation.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ To install a specific major version of the client, run the following command:
npm install @elastic/elasticsearch@<major>
----

To learn more about the supported major versions, please refer to the
To learn more about the supported major versions, please refer to the
<<js-compatibility-matrix>>.

[discrete]
Expand All @@ -37,7 +37,7 @@ to support that version for at least another minor release. If you are using the
with a version of Node.js that will be unsupported soon, you will see a warning
in your logs (the client will start logging the warning with two minors in advance).

Unless you are *always* using a supported version of Node.js,
Unless you are *always* using a supported version of Node.js,
we recommend defining the client dependency in your
`package.json` with the `~` instead of `^`. In this way, you will lock the
dependency on the minor release and not the major. (for example, `~7.10.0` instead
Expand All @@ -62,9 +62,12 @@ of `^7.10.0`).
[[js-compatibility-matrix]]
=== Compatibility matrix

The library is compatible with all {es} versions since 5.x. We recommend you to
use the same major version of the client as the {es} instance that you are
using.
Elastic language clients are guaranteed to be able to communicate with Elasticsearch
or Elastic solutions running on the same major version and greater or equal minor version.

Language clients are forward compatible; meaning that clients support communicating
with greater minor versions of Elasticsearch. Elastic language clients are not
guaranteed to be backwards compatible.

[%header,cols=2*]
|===
Expand All @@ -91,4 +94,4 @@ using.
WARNING: There is no official support for the browser environment. It exposes
your {es} instance to everyone, which could lead to security issues. We
recommend you to write a lightweight proxy that uses this client instead,
you can see a proxy example https://github.com/elastic/elasticsearch-js/tree/master/docs/examples/proxy[here].
you can see a proxy example https://github.com/elastic/elasticsearch-js/tree/master/docs/examples/proxy[here].
40 changes: 26 additions & 14 deletions lib/Transport.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ class Transport {
this.generateRequestId = opts.generateRequestId || generateRequestId()
this.name = opts.name
this.opaqueIdPrefix = opts.opaqueIdPrefix
this[kProductCheck] = 0 // 0 = to be checked, 1 = checking, 2 = checked-ok, 3 checked-notok
this[kProductCheck] = 0 // 0 = to be checked, 1 = checking, 2 = checked-ok, 3 checked-notok, 4 checked-nodefault
this[kApiVersioning] = process.env.ELASTIC_CLIENT_APIVERSIONING === 'true'

this.nodeFilter = opts.nodeFilter || defaultNodeFilter
Expand Down Expand Up @@ -455,9 +455,12 @@ class Transport {
prepareRequest()
} else {
// wait for product check to finish
productCheckEmitter.once('product-check', status => {
productCheckEmitter.once('product-check', (error, status) => {
if (status === false) {
const err = new ProductNotSupportedError(result)
const err = error || new ProductNotSupportedError(result)
if (this[kProductCheck] === 4) {
err.message = 'The client noticed that the server is not a supported distribution of Elasticsearch'
}
this.emit('request', err, result)
process.nextTick(callback, err, result)
} else {
Expand All @@ -470,8 +473,11 @@ class Transport {
}
}
// the product check is finished and it's not Elasticsearch
} else if (this[kProductCheck] === 3) {
} else if (this[kProductCheck] === 3 || this[kProductCheck] === 4) {
const err = new ProductNotSupportedError(result)
if (this[kProductCheck] === 4) {
err.message = 'The client noticed that the server is not a supported distribution of Elasticsearch'
}
this.emit('request', err, result)
process.nextTick(callback, err, result)
// the product check finished and it's Elasticsearch
Expand Down Expand Up @@ -550,42 +556,48 @@ class Transport {
if (err.statusCode === 401 || err.statusCode === 403) {
this[kProductCheck] = 2
process.emitWarning('The client is unable to verify that the server is Elasticsearch due to security privileges on the server side. Some functionality may not be compatible if the server is running an unsupported product.')
productCheckEmitter.emit('product-check', true)
productCheckEmitter.emit('product-check', null, true)
} else {
this[kProductCheck] = 0
productCheckEmitter.emit('product-check', false)
productCheckEmitter.emit('product-check', err, false)
}
} else {
debug('Checking elasticsearch version', result.body, result.headers)
if (result.body.version == null || typeof result.body.version.number !== 'string') {
debug('Can\'t access Elasticsearch version')
return productCheckEmitter.emit('product-check', false)
return productCheckEmitter.emit('product-check', null, false)
}
const tagline = result.body.tagline
const version = result.body.version.number.split('.')
const major = Number(version[0])
const minor = Number(version[1])
if (major < 6) {
return productCheckEmitter.emit('product-check', false)
return productCheckEmitter.emit('product-check', null, false)
} else if (major >= 6 && major < 7) {
if (tagline !== 'You Know, for Search') {
debug('Bad tagline')
return productCheckEmitter.emit('product-check', false)
return productCheckEmitter.emit('product-check', null, false)
}
} else if (major === 7 && minor < 14) {
if (tagline !== 'You Know, for Search' || result.body.version.build_flavor !== 'default') {
debug('Bad tagline or build_flavor')
return productCheckEmitter.emit('product-check', false)
if (tagline !== 'You Know, for Search') {
debug('Bad tagline')
return productCheckEmitter.emit('product-check', null, false)
}

if (result.body.version.build_flavor !== 'default') {
debug('Bad build_flavor')
this[kProductCheck] = 4
return productCheckEmitter.emit('product-check', null, false)
}
} else {
if (result.headers['x-elastic-product'] !== 'Elasticsearch') {
debug('x-elastic-product not recognized')
return productCheckEmitter.emit('product-check', false)
return productCheckEmitter.emit('product-check', null, false)
}
}
debug('Valid Elasticsearch distribution')
this[kProductCheck] = 2
productCheckEmitter.emit('product-check', true)
productCheckEmitter.emit('product-check', null, true)
}
})
}
Expand Down
126 changes: 121 additions & 5 deletions test/acceptance/product-check.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const { Client } = require('../../')
const {
connection: {
MockConnectionTimeout,
MockConnectionError,
buildMockConnection
}
} = require('../utils')
Expand Down Expand Up @@ -210,7 +211,7 @@ test('No errors ≤v7.13', t => {
})
})

test('Errors ≤v7.13', t => {
test('Errors ≤v7.13 (tagline)', t => {
t.plan(3)
const MockConnection = buildMockConnection({
onRequest (params) {
Expand All @@ -222,7 +223,7 @@ test('Errors ≤v7.13', t => {
cluster_uuid: 'cQ5pAMvRRTyEzObH4L5mTA',
version: {
number: '7.13.0-SNAPSHOT',
build_flavor: 'other',
build_flavor: 'default',
build_type: 'docker',
build_hash: '5fb4c050958a6b0b6a70a6fb3e616d0e390eaac3',
build_date: '2021-07-10T01:45:02.136546168Z',
Expand Down Expand Up @@ -271,6 +272,83 @@ test('Errors ≤v7.13', t => {
})
})

test('Errors ≤v7.13 (build flavor)', t => {
t.plan(5)
const MockConnection = buildMockConnection({
onRequest (params) {
return {
statusCode: 200,
body: {
name: '1ef419078577',
cluster_name: 'docker-cluster',
cluster_uuid: 'cQ5pAMvRRTyEzObH4L5mTA',
version: {
number: '7.13.0-SNAPSHOT',
build_flavor: 'other',
build_type: 'docker',
build_hash: '5fb4c050958a6b0b6a70a6fb3e616d0e390eaac3',
build_date: '2021-07-10T01:45:02.136546168Z',
build_snapshot: true,
lucene_version: '8.9.0',
minimum_wire_compatibility_version: '7.15.0',
minimum_index_compatibility_version: '7.0.0'
},
tagline: 'You Know, for Search'
}
}
}
})

const requests = [{
method: 'GET',
path: '/'
}, {
method: 'POST',
path: '/foo/_search'
}, {
method: 'POST',
path: '/foo/_search'
}]

const client = new Client({
node: 'http://localhost:9200',
Connection: MockConnection
})

client.on('request', (err, event) => {
const req = requests.shift()
if (req.method === 'GET') {
t.error(err)
} else {
t.equal(err.message, 'The client noticed that the server is not a supported distribution of Elasticsearch')
}
})

client.search({
index: 'foo',
body: {
query: {
match_all: {}
}
}
}, (err, result) => {
t.equal(err.message, 'The client noticed that the server is not a supported distribution of Elasticsearch')
})

setTimeout(() => {
client.search({
index: 'foo',
body: {
query: {
match_all: {}
}
}
}, (err, result) => {
t.equal(err.message, 'The client noticed that the server is not a supported distribution of Elasticsearch')
})
}, 100)
})

test('No errors v6', t => {
t.plan(7)
const MockConnection = buildMockConnection({
Expand Down Expand Up @@ -571,7 +649,7 @@ test('500 error', t => {
}
}
}, (err, result) => {
t.equal(err.message, 'The client noticed that the server is not Elasticsearch and we do not support this unknown product.')
t.equal(err.message, 'Response Error')

client.search({
index: 'foo',
Expand Down Expand Up @@ -608,7 +686,7 @@ test('TimeoutError', t => {
if (req.method === 'GET') {
t.error(err)
} else {
t.equal(err.message, 'The client noticed that the server is not Elasticsearch and we do not support this unknown product.')
t.equal(err.message, 'Request timed out')
}
})

Expand All @@ -620,7 +698,45 @@ test('TimeoutError', t => {
}
}
}, (err, result) => {
t.equal(err.message, 'The client noticed that the server is not Elasticsearch and we do not support this unknown product.')
t.equal(err.message, 'Request timed out')
})
})

test('ConnectionError', t => {
t.plan(3)

const requests = [{
method: 'GET',
path: '/'
}, {
method: 'POST',
path: '/foo/_search'
}]

const client = new Client({
node: 'http://localhost:9200',
Connection: MockConnectionError,
maxRetries: 0
})

client.on('request', (err, event) => {
const req = requests.shift()
if (req.method === 'GET') {
t.error(err)
} else {
t.equal(err.message, 'Kaboom')
}
})

client.search({
index: 'foo',
body: {
query: {
match_all: {}
}
}
}, (err, result) => {
t.equal(err.message, 'Kaboom')
})
})

Expand Down