From 17133f547c722bdfe790d01a40ef9f481edd727a Mon Sep 17 00:00:00 2001 From: Jesse Nelson Date: Tue, 21 Jan 2025 11:07:52 -0600 Subject: [PATCH] Add datasource to rest for primary sql instance (#10153) * add datasource pointing to primary instance --------- Signed-off-by: Jesse Nelson --- .../templates/secret-passwords.yaml | 3 + docs/configuration.md | 1 + .../__tests__/integrationDbOps.js | 1 + hedera-mirror-rest/config/application.yml | 1 + hedera-mirror-rest/dbpool.js | 63 +++++++++++++++++++ hedera-mirror-rest/server.js | 29 +-------- hedera-mirror-rest/service/baseService.js | 6 +- .../service/recordFileService.js | 5 ++ 8 files changed, 81 insertions(+), 28 deletions(-) create mode 100644 hedera-mirror-rest/dbpool.js diff --git a/charts/hedera-mirror/templates/secret-passwords.yaml b/charts/hedera-mirror/templates/secret-passwords.yaml index 20e26f3e42d..a9e836e1666 100644 --- a/charts/hedera-mirror/templates/secret-passwords.yaml +++ b/charts/hedera-mirror/templates/secret-passwords.yaml @@ -50,6 +50,9 @@ stringData: HEDERA_MIRROR_REST_DB_HOST: "{{ $dbHost }}" HEDERA_MIRROR_REST_DB_NAME: "{{ $dbName }}" HEDERA_MIRROR_REST_DB_PASSWORD: "{{ $restPassword }}" + {{ if .Values.stackgres.enabled -}} + HEDERA_MIRROR_REST_DB_PRIMARYHOST: "{{ $dbHostPrimary }}" + {{- end }} HEDERA_MIRROR_REST_DB_USERNAME: "{{ $restUsername }}" HEDERA_MIRROR_RESTJAVA_DB_HOST: "{{ $dbHost }}" HEDERA_MIRROR_RESTJAVA_DB_NAME: "{{ $dbName }}" diff --git a/docs/configuration.md b/docs/configuration.md index dcbdc9bc3cf..71d0b1f794d 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -519,6 +519,7 @@ value, it is recommended to only populate overridden properties in the custom `a | `hedera.mirror.rest.db.pool.maxConnections` | 10 | The maximum number of clients the database pool can contain | | `hedera.mirror.rest.db.pool.statementTimeout` | 20000 | The number of milliseconds to wait before timing out a query statement | | `hedera.mirror.rest.db.port` | 5432 | The port used to connect to the database | +| `hedera.mirror.rest.db.primaryHost` | "" | Optional IP or hostname used to connect to the primary instance | | `hedera.mirror.rest.db.sslMode` | DISABLE | The ssl level of protection against Eavesdropping, Man-in-the-middle (MITM) and Impersonation on the db connection. Accepts either DISABLE, ALLOW, PREFER, REQUIRE, VERIFY_CA or VERIFY_FULL. | | `hedera.mirror.rest.db.tls.ca` | "" | The path to the certificate authority used by the database for secure connections | | `hedera.mirror.rest.db.tls.cert` | "" | The path to the public key the client should use to securely connect to the database | diff --git a/hedera-mirror-rest/__tests__/integrationDbOps.js b/hedera-mirror-rest/__tests__/integrationDbOps.js index 0e68cd3ed59..e057b7803e9 100644 --- a/hedera-mirror-rest/__tests__/integrationDbOps.js +++ b/hedera-mirror-rest/__tests__/integrationDbOps.js @@ -126,6 +126,7 @@ const createPool = async () => { password: readOnlyPassword, user: readOnlyUser, }); + global.primaryPool = global.pool; }; /** diff --git a/hedera-mirror-rest/config/application.yml b/hedera-mirror-rest/config/application.yml index 3d8f51036ed..7c70c61d908 100644 --- a/hedera-mirror-rest/config/application.yml +++ b/hedera-mirror-rest/config/application.yml @@ -20,6 +20,7 @@ hedera: maxConnections: 10 statementTimeout: 20000 port: 5432 + primaryHost: "" sslMode: DISABLE tls: ca: "" diff --git a/hedera-mirror-rest/dbpool.js b/hedera-mirror-rest/dbpool.js new file mode 100644 index 00000000000..9d9e10bb554 --- /dev/null +++ b/hedera-mirror-rest/dbpool.js @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 Hedera Hashgraph, LLC + * + * Licensed 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 config from './config.js'; +import fs from 'fs'; +import {getPoolClass} from './utils.js'; + +const poolConfig = { + user: config.db.username, + host: config.db.host, + database: config.db.name, + password: config.db.password, + port: config.db.port, + connectionTimeoutMillis: config.db.pool.connectionTimeout, + max: config.db.pool.maxConnections, + statement_timeout: config.db.pool.statementTimeout, +}; + +if (config.db.tls.enabled) { + poolConfig.ssl = { + ca: fs.readFileSync(config.db.tls.ca).toString(), + cert: fs.readFileSync(config.db.tls.cert).toString(), + key: fs.readFileSync(config.db.tls.key).toString(), + rejectUnauthorized: false, + }; +} + +const Pool = getPoolClass(); + +const handlePoolError = (dbPool) => { + dbPool.on('error', (error) => { + logger.error(`error event emitted on pool for host ${dbPool.options.host}. ${error.stack}`); + }); +}; + +const initializePool = () => { + global.pool = new Pool(poolConfig); + handlePoolError(global.pool); + + if (config.db.primaryHost) { + const primaryPoolConfig = {...poolConfig}; + primaryPoolConfig.host = config.db.primaryHost; + global.primaryPool = new Pool(primaryPoolConfig); + handlePoolError(global.primaryPool); + } else { + global.primaryPool = pool; + } +}; + +export {initializePool}; diff --git a/hedera-mirror-rest/server.js b/hedera-mirror-rest/server.js index 91c4a0752bf..9304a73e892 100644 --- a/hedera-mirror-rest/server.js +++ b/hedera-mirror-rest/server.js @@ -21,7 +21,6 @@ import {createTerminus} from '@godaddy/terminus'; import {addAsync} from '@awaitjs/express'; import cors from 'cors'; import httpContext from 'express-http-context'; -import fs from 'fs'; import compression from 'compression'; // local files @@ -53,6 +52,7 @@ import { // routes import {AccountRoutes, BlockRoutes, ContractRoutes, NetworkRoutes} from './routes'; import {handleRejection, handleUncaughtException} from './middleware/httpErrorHandler'; +import {initializePool} from './dbpool.js'; // use a dummy port for jest unit tests const port = isTestEnv() ? 3000 : config.port; @@ -62,32 +62,7 @@ if (port === undefined || Number.isNaN(Number(port))) { } // Postgres pool -const poolConfig = { - user: config.db.username, - host: config.db.host, - database: config.db.name, - password: config.db.password, - port: config.db.port, - connectionTimeoutMillis: config.db.pool.connectionTimeout, - max: config.db.pool.maxConnections, - statement_timeout: config.db.pool.statementTimeout, -}; - -if (config.db.tls.enabled) { - poolConfig.ssl = { - ca: fs.readFileSync(config.db.tls.ca).toString(), - cert: fs.readFileSync(config.db.tls.cert).toString(), - key: fs.readFileSync(config.db.tls.key).toString(), - rejectUnauthorized: false, - }; -} - -const Pool = getPoolClass(); -const pool = new Pool(poolConfig); -pool.on('error', (error) => { - logger.error(`error event emitted on pg pool. ${error.stack}`); -}); -global.pool = pool; +initializePool(); // Express configuration. Prior to v0.5 all sets should be configured before use or they won't be picked up const app = addAsync(express()); diff --git a/hedera-mirror-rest/service/baseService.js b/hedera-mirror-rest/service/baseService.js index 4b6215f37a3..2ccf1073699 100644 --- a/hedera-mirror-rest/service/baseService.js +++ b/hedera-mirror-rest/service/baseService.js @@ -66,7 +66,7 @@ class BaseService { } async getRows(query, params) { - return (await pool.queryQuietly(query, params)).rows; + return (await this.pool().queryQuietly(query, params)).rows; } async getSingleRow(query, params) { @@ -105,6 +105,10 @@ class BaseService { limitClause, ].join('\n'); } + + pool() { + return pool; + } } export default BaseService; diff --git a/hedera-mirror-rest/service/recordFileService.js b/hedera-mirror-rest/service/recordFileService.js index d302134bb74..f0f6cd63f97 100644 --- a/hedera-mirror-rest/service/recordFileService.js +++ b/hedera-mirror-rest/service/recordFileService.js @@ -172,6 +172,7 @@ class RecordFileService extends BaseService { order by ${filters.orderBy} ${filters.order} limit ${filters.limit} `; + const rows = await super.getRows(query, params); return rows.map((recordFile) => new RecordFile(recordFile)); } @@ -214,6 +215,10 @@ class RecordFileService extends BaseService { order: orderFilterValues.ASC, }; } + + pool() { + return primaryPool; + } } export default new RecordFileService();