From 02c1ee03b8d84e39c0101f1dadcd6222c6c178b5 Mon Sep 17 00:00:00 2001 From: Grzegorz Grzybek Date: Wed, 13 Nov 2024 11:57:36 +0100 Subject: [PATCH] fix(Jolokia): Support native Jolokia 2.1.x optimization mode (fixes #3663) (#1221) (cherry picked from commit a7ec9110ac7d24d573b02fa78a917afd82037530) --- packages/hawtio/package.json | 4 +- .../hawtio/src/plugins/rbac/tree-processor.ts | 7 ++- .../plugins/shared/jolokia-service.test.ts | 33 +++++++++++- .../src/plugins/shared/jolokia-service.ts | 50 +++++++++++++++---- yarn.lock | 22 ++++---- 5 files changed, 92 insertions(+), 24 deletions(-) diff --git a/packages/hawtio/package.json b/packages/hawtio/package.json index b864aec9..60f1bb54 100644 --- a/packages/hawtio/package.json +++ b/packages/hawtio/package.json @@ -35,7 +35,7 @@ "dependencies": { "@hawtio/camel-model-v4_4": "npm:@hawtio/camel-model@~4.4.3", "@hawtio/camel-model-v4_8": "npm:@hawtio/camel-model@~4.8.1", - "@jolokia.js/simple": "^2.1.7", + "@jolokia.js/simple": "^2.1.8", "@module-federation/utilities": "^3.1.16", "@monaco-editor/react": "^4.6.0", "@patternfly/react-charts": "~7.3.0", @@ -57,7 +57,7 @@ "@types/react-router-dom": "^5.3.3", "dagre": "^0.8.5", "eventemitter3": "^5.0.1", - "jolokia.js": "^2.1.7", + "jolokia.js": "^2.1.8", "jquery": "^3.7.1", "js-logger": "^1.6.1", "jwt-decode": "^4.0.0", diff --git a/packages/hawtio/src/plugins/rbac/tree-processor.ts b/packages/hawtio/src/plugins/rbac/tree-processor.ts index f9c370e0..819a3175 100644 --- a/packages/hawtio/src/plugins/rbac/tree-processor.ts +++ b/packages/hawtio/src/plugins/rbac/tree-processor.ts @@ -48,8 +48,13 @@ export const rbacTreeProcessor: TreeProcessor = async (tree: MBeanTree) => { const mbeans = tree.flatten() const listMethod = await jolokiaService.getListMethod() switch (listMethod) { + case JolokiaListMethod.NATIVE: case JolokiaListMethod.OPTIMISED: { - log.debug('Process JMX tree: optimised list mode') + if (listMethod === JolokiaListMethod.NATIVE) { + log.debug('Process JMX tree: native list mode') + } else { + log.debug('Process JMX tree: optimised list mode') + } // Check if RBACDecorator has been already applied to the MBean tree at server side. const decorated = Object.values(mbeans).every(node => node.isRBACDecorated()) if (decorated) { diff --git a/packages/hawtio/src/plugins/shared/jolokia-service.test.ts b/packages/hawtio/src/plugins/shared/jolokia-service.test.ts index c9169860..c0500b62 100644 --- a/packages/hawtio/src/plugins/shared/jolokia-service.test.ts +++ b/packages/hawtio/src/plugins/shared/jolokia-service.test.ts @@ -22,7 +22,7 @@ describe('JolokiaService', () => { await expect(jolokiaService.getJolokiaUrl()).resolves.toBeNull() }, 10000) - test('getJolokia - optimised', async () => { + test('getJolokia - optimised (mbean)', async () => { jolokiaService.getJolokiaUrl = jest.fn(async () => '/test') Jolokia.prototype.list = jest.fn( (path?: string | string[] | jolokia.RequestOptions, opts?: SimpleRequestOptions) => { @@ -42,11 +42,34 @@ describe('JolokiaService', () => { return null }, ) + Jolokia.prototype.version = jest.fn((opts?: SimpleRequestOptions) => { + ;(opts!.success! as SimpleResponseCallback)({ + agent: 'test', + protocol: '7.3', + info: {}, + config: {}, + }) + }) await expect(jolokiaService.getJolokia()).resolves.not.toThrow() await expect(jolokiaService.getListMethod()).resolves.toEqual(JolokiaListMethod.OPTIMISED) }) + test('getJolokia - optimised (native)', async () => { + jolokiaService.getJolokiaUrl = jest.fn(async () => '/test') + Jolokia.prototype.version = jest.fn((opts?: SimpleRequestOptions) => { + ;(opts!.success! as SimpleResponseCallback)({ + agent: 'test', + protocol: '8.0', + info: {}, + config: {}, + }) + }) + + await expect(jolokiaService.getJolokia()).resolves.not.toThrow() + await expect(jolokiaService.getListMethod()).resolves.toEqual(JolokiaListMethod.NATIVE) + }) + test('getJolokia - default', async () => { jolokiaService.getJolokiaUrl = jest.fn(async () => '/test') Jolokia.prototype.list = jest.fn((...params: (string[] | string | SimpleRequestOptions)[]) => { @@ -82,6 +105,14 @@ describe('JolokiaService', () => { ) return null }) + Jolokia.prototype.version = jest.fn((opts?: SimpleRequestOptions) => { + ;(opts!.success! as SimpleResponseCallback)({ + agent: 'test', + protocol: '7.3', + info: {}, + config: {}, + }) + }) await expect(jolokiaService.getJolokia()).resolves.not.toThrow() await expect(jolokiaService.getListMethod()).resolves.toEqual(JolokiaListMethod.DEFAULT) diff --git a/packages/hawtio/src/plugins/shared/jolokia-service.ts b/packages/hawtio/src/plugins/shared/jolokia-service.ts index fb298ce7..53e88dc7 100644 --- a/packages/hawtio/src/plugins/shared/jolokia-service.ts +++ b/packages/hawtio/src/plugins/shared/jolokia-service.ts @@ -63,8 +63,10 @@ const JOLOKIA_PATHS = ['jolokia', '/hawtio/jolokia', '/jolokia'] as const export enum JolokiaListMethod { /** The default LIST+EXEC Jolokia operations. */ DEFAULT, - /** The optimised list operations provided by Hawtio RBACRegistry MBean. */ + /** The optimised list operation provided by Hawtio RBACRegistry MBean. */ OPTIMISED, + /** THe optimised list operation provided directly by Jolokia 2.1+ */ + NATIVE, /** Not determined. */ UNDETERMINED, } @@ -437,11 +439,38 @@ class JolokiaService implements IJolokiaService { log.debug('Check if we can call optimised jolokia.list() operation') // hawtio/hawtio-next#635: we pass an executor which accepts only resolve cb - we never call reject cb even // on error, but we ensure that resolve cb is called + + const path = escapeMBeanPath(this.config.mbean) + async function checkCapabilities( + successFn: SimpleResponseCallback, + errorFn: ErrorCallback, + options: SimpleRequestOptions, + ) { + // check for Jolokia version (TODO: use single call) + await jolokia.version(onVersionSuccessAndError(successFn, errorFn, options)) + // check for special MBean + await jolokia.list(path, onListSuccessAndError(successFn, errorFn, options)) + } + return new Promise(resolve => { const successFn: SimpleResponseCallback = (value: JolokiaResponseValue) => { - // check if the MBean exists by testing whether the returned value has - // the 'op' property - if (isMBeanInfo(value) && isObject(value.op)) { + // check if this is a version response - since protocol version 8.0 we can get optimized response directly + if (this.config.method === JolokiaListMethod.NATIVE) { + resolve() + return + } + if (value && typeof value === 'object' && 'protocol' in value) { + const protocolVersion = value.protocol + if (parseFloat(protocolVersion as string) >= 8.0) { + this.config.method = JolokiaListMethod.NATIVE + log.debug('Jolokia list method:', JolokiaListMethod[this.config.method]) + resolve() + } + // return without resolve + return + } else if (isMBeanInfo(value) && isObject(value.op)) { + // check if the MBean exists by testing whether the returned value has + // the 'op' property this.config.method = JolokiaListMethod.OPTIMISED } else { // we could get 403 error, mark the method as special case, @@ -458,10 +487,7 @@ class JolokiaService implements IJolokiaService { resolve() // optimisation not happening } - jolokia.list( - escapeMBeanPath(this.config.mbean), - onListSuccessAndError(successFn, errorFn, { fetchError: this.fetchError(resolve) }), - ) + checkCapabilities(successFn, errorFn, { fetchError: this.fetchError(resolve) }) }) } @@ -582,8 +608,14 @@ class JolokiaService implements IJolokiaService { } case JolokiaListMethod.DEFAULT: case JolokiaListMethod.UNDETERMINED: + case JolokiaListMethod.NATIVE: default: { - log.debug('Invoke Jolokia list MBean in default mode:', paths) + if (method === JolokiaListMethod.NATIVE) { + options.listCache = true + log.debug('Invoke Jolokia list MBean in native mode:', paths) + } else { + log.debug('Invoke Jolokia list MBean in default mode:', paths) + } const listOptions = onListSuccessAndError( value => { // For empty or single list, the first path should be enough diff --git a/yarn.lock b/yarn.lock index 0632b558..2f48b289 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2019,7 +2019,7 @@ __metadata: dependencies: "@hawtio/camel-model-v4_4": "npm:@hawtio/camel-model@~4.4.3" "@hawtio/camel-model-v4_8": "npm:@hawtio/camel-model@~4.8.1" - "@jolokia.js/simple": "npm:^2.1.7" + "@jolokia.js/simple": "npm:^2.1.8" "@module-federation/utilities": "npm:^3.1.16" "@monaco-editor/react": "npm:^4.6.0" "@patternfly/react-charts": "npm:~7.3.0" @@ -2047,7 +2047,7 @@ __metadata: jest-environment-jsdom: "npm:^29.7.0" jest-fetch-mock: "npm:^3.0.3" jest-watch-typeahead: "npm:^2.2.2" - jolokia.js: "npm:^2.1.7" + jolokia.js: "npm:^2.1.8" jquery: "npm:^3.7.1" js-logger: "npm:^1.6.1" jwt-decode: "npm:^4.0.0" @@ -2382,12 +2382,12 @@ __metadata: languageName: node linkType: hard -"@jolokia.js/simple@npm:^2.1.7": - version: 2.1.7 - resolution: "@jolokia.js/simple@npm:2.1.7" +"@jolokia.js/simple@npm:^2.1.8": + version: 2.1.8 + resolution: "@jolokia.js/simple@npm:2.1.8" dependencies: - jolokia.js: "npm:^2.1.7" - checksum: 10/26bc017c64df1600325f5185c39d48f600d0f3e43c5bed9e79c738c9f248eba03c9bf8a68303d7aee9bfcb9f34d44cafba705f4c5ebdd74adfdda238b29826f3 + jolokia.js: "npm:^2.1.8" + checksum: 10/b60f874c93c55444998702e2948c7b5821cdecce8e12d93506260ca5787bb6d7fad81542cbf509cd9112360b6c31839bab970669b77df57d48459b1aa434ace3 languageName: node linkType: hard @@ -10210,10 +10210,10 @@ __metadata: languageName: node linkType: hard -"jolokia.js@npm:^2.1.7": - version: 2.1.7 - resolution: "jolokia.js@npm:2.1.7" - checksum: 10/7e10e0b842c96853ea2815d46cc7cea278470d259d8660711fbfda58c3a730764790ee28dd9ad346ebcb8324bd500f109408675f19d8c0a75578703522956d91 +"jolokia.js@npm:^2.1.8": + version: 2.1.8 + resolution: "jolokia.js@npm:2.1.8" + checksum: 10/4dd3db2954abbaf875a3630591257056c866a201df5f3bab36148eee9e2e1520061c3b14d6873f3f0af71b2938323ecaa9540a9e37c3d952711bee077b99aca5 languageName: node linkType: hard