Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
0d2f294
Adds support for Tribe nodes
tylersmalley Dec 9, 2016
881d5df
Merge branch 'master' of github.com:elastic/kibana into tribe-node-su…
spalger Dec 10, 2016
0ac6c11
@spalger review feedback
tylersmalley Dec 12, 2016
8068031
[tribe] Use class syntax on new data sources
jbudz Dec 13, 2016
c541bf0
[tribe] Use includes instead of indexOf in call_client
jbudz Dec 13, 2016
654779e
[tribe] DocRequest --> AbstractDocRequest
jbudz Dec 13, 2016
4deefdd
[tribe] Fix AbstractDoc test rename
jbudz Dec 13, 2016
0cfffb7
Removes factory objects and adds addClientPlugin to Cluster (#9467)
tylersmalley Dec 14, 2016
8930f7d
Merge remote-tracking branch 'upstream/master' into tribe-node-support
tylersmalley Dec 14, 2016
9cd3ea5
Merge remote-tracking branch 'upstream/master' into tribe-node-support
tylersmalley Dec 14, 2016
7eaf1d8
Resolves eslint error
tylersmalley Dec 14, 2016
ebd06ae
Use properties on the instance instead of class properties
tylersmalley Dec 15, 2016
30cbf87
[tribe] Remove disabled dev tools app, do not bundle console when tri…
jbudz Dec 16, 2016
10277a7
Merge branch 'master' into tribe-node-support
jbudz Dec 16, 2016
77af89d
[tribe] Use destructuring, don't reassign args
jbudz Dec 16, 2016
1e4f1e6
[tribe] Use class syntax for client request wrapper
jbudz Dec 16, 2016
e3b4fd7
[tribe] callAsKibanaUser -> callWithInternalUser
jbudz Dec 16, 2016
e21723b
[tribe] Remove clients from module context, service is a singleton
jbudz Dec 16, 2016
51672fa
[tribe] Use instance property shorthand for admin and data DocRequests
jbudz Dec 16, 2016
fb866a8
Removes questions
tylersmalley Dec 19, 2016
b0fbd65
Fixes typo in tests
tylersmalley Dec 19, 2016
0d52a04
Correctly names test case
tylersmalley Dec 19, 2016
256738d
Merge branch 'master' into tribe-node-support
jbudz Dec 19, 2016
927e775
Revert "Use properties on the instance instead of class properties"
jbudz Dec 16, 2016
9855321
Adds tests for create_{admin,data}_cluster
tylersmalley Dec 19, 2016
ae39e60
Merge branch 'master' of github.com:elastic/kibana into tribe-node-su…
spalger Dec 19, 2016
97ff6ce
Persists clusters to server
tylersmalley Dec 20, 2016
ad85ab3
[tribe] Move cluster config requests to distinct getters
jbudz Dec 20, 2016
8a8c18f
Adds getClient and removes addClientPlugin
tylersmalley Dec 20, 2016
fd72137
Expose createClient, consolidate config parsing
tylersmalley Dec 21, 2016
f52c887
Removes createClients from Cluster
tylersmalley Dec 21, 2016
b5ce38a
Prevent status change from red to red
tylersmalley Dec 21, 2016
40a4a4b
Updates esvm:tribe ports to be consistant with dev
tylersmalley Dec 22, 2016
27c0fcd
Merge remote-tracking branch 'upstream/master' into tribe-node-support
tylersmalley Dec 22, 2016
d0610a3
[tribe] Get ssl.ca from serverConfig
jbudz Dec 22, 2016
f177300
[tribe/esvm] Remove plugin configuration
jbudz Dec 26, 2016
1707383
Removes unused variable
tylersmalley Dec 28, 2016
ec854cf
Merge remote-tracking branch 'upstream/master' into tribe-node-support
tylersmalley Dec 28, 2016
a6496a6
[tribe] Named exports for creating clusters
jbudz Dec 28, 2016
7dd8d79
[tribe] Named exports for client logger, cluster
jbudz Dec 28, 2016
2c1f7e3
[tribe] Named exports for health check
jbudz Dec 28, 2016
cc90a92
Remove invalid comment
tylersmalley Dec 28, 2016
978d467
[tribe] Comment explaining difference between admin and data browser …
jbudz Dec 28, 2016
cb5736a
Rename ES checks to be consistant with functionality
tylersmalley Dec 28, 2016
08cb58c
Organize NOOP functions
tylersmalley Dec 28, 2016
94e4364
Removing function comments
tylersmalley Dec 28, 2016
1b818da
Explicitly check for presence of url in tribe
tylersmalley Dec 30, 2016
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
48 changes: 43 additions & 5 deletions src/core_plugins/elasticsearch/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { trim, trimRight } from 'lodash';
import { trim, trimRight, bindKey, get } from 'lodash';
import { methodNotAllowed } from 'boom';

import healthCheck from './lib/health_check';
import exposeClient from './lib/expose_client';
import { createDataCluster } from './lib/create_data_cluster';
import { createAdminCluster } from './lib/create_admin_cluster';
import { clientLogger } from './lib/client_logger';
import { createClusters } from './lib/create_clusters';
import filterHeaders from './lib/filter_headers';

import createProxy, { createPath } from './lib/create_proxy';

const DEFAULT_REQUEST_HEADERS = [ 'authorization' ];
Expand All @@ -26,13 +31,37 @@ module.exports = function ({ Plugin }) {
customHeaders: object().default({}),
pingTimeout: number().default(ref('requestTimeout')),
startupTimeout: number().default(5000),
logQueries: boolean().default(false),
ssl: object({
verify: boolean().default(true),
ca: array().single().items(string()),
cert: string(),
key: string()
}).default(),
apiVersion: Joi.string().default('master'),
healthCheck: object({
delay: number().default(2500)
}).default(),
tribe: object({
url: string().uri({ scheme: ['http', 'https'] }),
preserveHost: boolean().default(true),
username: string(),
password: string(),
shardTimeout: number().default(0),
requestTimeout: number().default(30000),
requestHeadersWhitelist: array().items().single().default(DEFAULT_REQUEST_HEADERS),
customHeaders: object().default({}),
pingTimeout: number().default(ref('requestTimeout')),
startupTimeout: number().default(5000),
logQueries: boolean().default(false),
ssl: object({
verify: boolean().default(true),
ca: array().single().items(string()),
cert: string(),
key: string()
}).default(),
apiVersion: Joi.string().default('master'),
}).default()
}).default();
},

Expand All @@ -42,15 +71,24 @@ module.exports = function ({ Plugin }) {
esRequestTimeout: options.requestTimeout,
esShardTimeout: options.shardTimeout,
esApiVersion: options.apiVersion,
esDataIsTribe: get(options, 'tribe.url') ? true : false,
};
}
},

init(server, options) {
const kibanaIndex = server.config().get('kibana.index');
const clusters = createClusters(server);

server.expose('getCluster', clusters.get);
server.expose('createCluster', clusters.create);

server.expose('filterHeaders', filterHeaders);
server.expose('ElasticsearchClientLogging', clientLogger(server));

createDataCluster(server);
createAdminCluster(server);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Is this the code that gets the testing stuff to work and set up the test data and admin clusters?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

It wasn't necessary for testing to work but it isolated the code the made it easier to test.

Both of these call server.plugins.elasticsearch.createCluster() which expose the data and admin clusters to the rest of the application. They are then accessible via server.plugins.elasticsearch.getCluster()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ah okay makes sense, I initially thought they were actually creating the clusters (booting the instances up). Resolved.


// Expose the client to the server
exposeClient(server);
createProxy(server, 'GET', '/{paths*}');
createProxy(server, 'POST', '/_mget');
createProxy(server, 'POST', '/{index}/_search');
Expand All @@ -69,7 +107,7 @@ module.exports = function ({ Plugin }) {

function noDirectIndex({ path }, reply) {
const requestPath = trimRight(trim(path), '/');
const matchPath = createPath(kibanaIndex);
const matchPath = createPath('/elasticsearch', kibanaIndex);

if (requestPath === matchPath) {
return reply(methodNotAllowed('You cannot modify the primary kibana index through this interface.'));
Expand Down
134 changes: 134 additions & 0 deletions src/core_plugins/elasticsearch/lib/__tests__/cluster.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import expect from 'expect.js';
import { Cluster } from '../cluster';
import sinon from 'sinon';
import { errors as esErrors } from 'elasticsearch';
import { set, partial, cloneDeep } from 'lodash';
import Boom from 'boom';

describe('plugins/elasticsearch', function () {
describe('cluster', function () {
let cluster;
const config = {
url: 'http://localhost:9200',
ssl: { verify: false },
requestHeadersWhitelist: [ 'authorization' ]
};

beforeEach(() => {
cluster = new Cluster(config);
});

it('persists the config', () => {
expect(cluster._config).to.eql(config);
});

it('exposes error definitions', () => {
expect(cluster.errors).to.be(esErrors);
});

it('closes the clients', () => {
cluster._client.close = sinon.spy();
cluster._noAuthClient.close = sinon.spy();
cluster.close();

sinon.assert.calledOnce(cluster._client.close);
sinon.assert.calledOnce(cluster._noAuthClient.close);
});

it('protects the config from changes', () => {
const localRequestHeadersWhitelist = cluster.getRequestHeadersWhitelist();
expect(localRequestHeadersWhitelist.length).to.not.equal(config.requestHeadersWhitelist);
});

describe('callWithInternalUser', () => {
let client;

beforeEach(() => {
client = cluster._client = sinon.stub();
set(client, 'nodes.info', sinon.stub().returns(Promise.resolve()));
});

it('should return a function', () => {
expect(cluster.callWithInternalUser).to.be.a('function');
});

it('throws an error for an invalid endpoint', () => {
const fn = partial(cluster.callWithInternalUser, 'foo');
expect(fn).to.throwException(/called with an invalid endpoint: foo/);
});

it('calls the client with params', () => {
const params = { foo: 'Foo' };
cluster.callWithInternalUser('nodes.info', params);

sinon.assert.calledOnce(client.nodes.info);
expect(client.nodes.info.getCall(0).args[0]).to.eql(params);
});
});

describe('callWithRequest', () => {
let client;

beforeEach(() => {
client = cluster._noAuthClient = sinon.stub();
set(client, 'nodes.info', sinon.stub().returns(Promise.resolve()));
});

it('should return a function', () => {
expect(cluster.callWithRequest).to.be.a('function');
});

it('throws an error for an invalid endpoint', () => {
const fn = partial(cluster.callWithRequest, {}, 'foo');
expect(fn).to.throwException(/called with an invalid endpoint: foo/);
});

it('calls the client with params', () => {
const params = { foo: 'Foo' };
cluster.callWithRequest({}, 'nodes.info', params);

sinon.assert.calledOnce(client.nodes.info);
expect(client.nodes.info.getCall(0).args[0]).to.eql(params);
});

it('passes only whitelisted headers', () => {
const headers = { authorization: 'Basic TEST' };
const request = { headers: Object.assign({}, headers, { foo: 'Foo' }) };

cluster.callWithRequest(request, 'nodes.info');

sinon.assert.calledOnce(client.nodes.info);
expect(client.nodes.info.getCall(0).args[0]).to.eql({
headers: headers
});
});

describe('wrap401Errors', () => {
let handler;
const error = new Error('Authentication required');
error.statusCode = 401;

beforeEach(() => {
handler = sinon.stub();
});

it('ensures WWW-Authenticate header', async () => {
set(client, 'mock.401', sinon.stub().returns(Promise.reject(error)));
await cluster.callWithRequest({}, 'mock.401', {}, { wrap401Errors: true }).catch(handler);

sinon.assert.calledOnce(handler);
expect(handler.getCall(0).args[0].output.headers['WWW-Authenticate']).to.eql('Basic realm="Authorization Required"');
});

it('persists WWW-Authenticate header', async () => {
set(error, 'body.error.header[WWW-Authenticate]', 'Basic realm="Test"');
set(client, 'mock.401', sinon.stub().returns(Promise.reject(error)));
await cluster.callWithRequest({}, 'mock.401', {}, { wrap401Errors: true }).catch(handler);

sinon.assert.calledOnce(handler);
expect(handler.getCall(0).args[0].output.headers['WWW-Authenticate']).to.eql('Basic realm="Test"');
});
});
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import expect from 'expect.js';
import sinon from 'sinon';
import { bindKey, set, get, partial } from 'lodash';
import { createAdminCluster } from '../create_admin_cluster';

describe('plugins/elasticsearch', function () {
describe('create_admin_cluster', function () {
let cluster;
let server;

beforeEach(() => {
const config = {
elasticsearch: {
url: 'http://localhost:9200',
logQueries: true
}
};

server = sinon.spy();

cluster = {
close: sinon.spy()
};

set(server, 'plugins.elasticsearch.createCluster', sinon.mock().returns(cluster));
set(server, 'on', sinon.spy());

server.config = () => {
return { get: partial(get, config) };
};

createAdminCluster(server);
});

it('creates the cluster', () => {
const { createCluster } = server.plugins.elasticsearch;

sinon.assert.calledOnce(createCluster);
expect(createCluster.getCall(0).args[0]).to.eql('admin');
expect(createCluster.getCall(0).args[1].url).to.eql('http://localhost:9200');
});

it('sets client logger for cluster options', () => {
const { createCluster } = server.plugins.elasticsearch;
const firstCall = createCluster.getCall(0);
const Log = firstCall.args[1].log;
const logger = new Log;

sinon.assert.calledOnce(createCluster);
expect(firstCall.args[0]).to.eql('admin');
expect(firstCall.args[1].url).to.eql('http://localhost:9200');
expect(logger.tags).to.eql(['admin']);
expect(logger.logQueries).to.eql(true);
});

it('close cluster of server close', () => {
const clusterClose = server.on.getCall(0).args[1];

clusterClose();

sinon.assert.calledOnce(cluster.close);
sinon.assert.calledOnce(server.on);
expect(server.on.getCall(0).args[0]).to.eql('close');
});
});
});
50 changes: 50 additions & 0 deletions src/core_plugins/elasticsearch/lib/__tests__/create_clusters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import expect from 'expect.js';
import { createClusters } from '../create_clusters';
import { Cluster } from '../cluster';
import sinon from 'sinon';
import { partial } from 'lodash';

describe('plugins/elasticsearch', function () {
describe('createClusters', function () {
let clusters;
let server;

beforeEach(() => {
server = {
plugins: {
elasticsearch: {}
},
expose: sinon.mock()
};

clusters = createClusters(server);
});

describe('createCluster', () => {
let cluster;
const config = {
url: 'http://localhost:9200',
ssl: {
verify: false
}
};

beforeEach(() => {
cluster = clusters.create('admin', config);
});

it('returns a cluster', () => {
expect(cluster).to.be.a(Cluster);
});

it('persists the cluster', () => {
expect(clusters.get('admin')).to.be.a(Cluster);
});

it('throws if cluster already exists', () => {
const fn = partial(clusters.create, 'admin', config);
expect(fn).to.throwException(/cluster \'admin\' already exists/);
});
});
});
});
Loading