@itentialopensource/adapter-db_mysql 0.1.12 → 0.1.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,4 +1,20 @@
1
1
 
2
+ ## 0.1.14 [08-01-2022]
3
+
4
+ * Patch/adapt 2277
5
+
6
+ See merge request itentialopensource/adapters/persistence/adapter-db_mysql!8
7
+
8
+ ---
9
+
10
+ ## 0.1.13 [08-01-2022]
11
+
12
+ * Changed connection handling. Added healthcheck.
13
+
14
+ See merge request itentialopensource/adapters/persistence/adapter-db_mysql!7
15
+
16
+ ---
17
+
2
18
  ## 0.1.12 [08-10-2021]
3
19
 
4
20
  * change the connection and base wait
package/adapter.js CHANGED
@@ -19,8 +19,6 @@ const EventEmitter = require('events');
19
19
 
20
20
  let myid = null;
21
21
  let errors = [];
22
- const baseWait = 1000;
23
- const incrWait = 20000;
24
22
 
25
23
  /**
26
24
  * @summary Build a standard error object from the data provided
@@ -103,7 +101,6 @@ class MySQL extends EventEmitter {
103
101
  super();
104
102
 
105
103
  this.props = properties;
106
- this.alive = false;
107
104
  this.id = prongid;
108
105
  myid = prongid;
109
106
 
@@ -119,27 +116,23 @@ class MySQL extends EventEmitter {
119
116
  // Read the action from the file system
120
117
  const errorData = JSON.parse(fs.readFileSync(errorFile, 'utf-8'));
121
118
  ({ errors } = errorData);
122
- }
123
119
 
124
- /**
125
- * Itential connect call for system green/red light
126
- *
127
- * @param {function} callback
128
- */
129
- connect() {
130
- const meth = 'adapter-connect';
131
- const origin = `${this.id}-${meth}`;
132
- log.trace(origin);
120
+ this.setConfiguration();
121
+ }
133
122
 
123
+ setConfiguration() {
134
124
  this.config = {
135
125
  host: this.props.host,
136
126
  port: this.props.port,
137
127
  user: this.props.authentication.username,
138
128
  password: this.props.authentication.password,
139
129
  database: this.props.database,
140
- acquireTimeout: this.props.acquireTimout || 1000000
130
+ ...this.props.connectionOptions
141
131
  };
142
132
 
133
+ this.healthcheckType = this.props.healthcheck.type;
134
+ this.healthcheckInterval = this.props.healthcheck.frequency;
135
+
143
136
  if (this.props.ssl) {
144
137
  if (this.props.ssl.enabled === true) {
145
138
  log.info('Connecting to MySQL with SSL.');
@@ -155,20 +148,16 @@ class MySQL extends EventEmitter {
155
148
  this.config.ssl.ca = fs.readFileSync(this.props.ssl.ca_file);
156
149
  } catch (err) {
157
150
  log.error(`Error: Unable to load MySQL CA file: ${err}`);
158
- this.alive = false;
159
151
  this.emit('OFFLINE', {
160
152
  id: this.id
161
153
  });
162
- return;
163
154
  }
164
155
  } else {
165
156
  log.error('Error: Certificate validation'
166
157
  + 'is enabled but a CA is not specified.');
167
- this.alive = false;
168
158
  this.emit('OFFLINE', {
169
159
  id: this.id
170
160
  });
171
- return;
172
161
  }
173
162
  } else {
174
163
  log.info('SSL MySQL connection without CA certificate validation.');
@@ -182,11 +171,40 @@ class MySQL extends EventEmitter {
182
171
  } else {
183
172
  log.warn('WARNING: Connecting to MySQL without SSL.');
184
173
  }
174
+ }
175
+
176
+ /**
177
+ * Itential connect call for system green/red light
178
+ *
179
+ * @param {function} callback
180
+ */
181
+ connect() {
182
+ const meth = 'adapter-connect';
183
+ const origin = `${this.id}-${meth}`;
184
+ log.trace(origin);
185
+
186
+ // initially set as off
187
+ this.emit('OFFLINE', { id: this.id });
185
188
 
186
- // get connection
187
- this.attempt = 1;
188
- this.waitTime = baseWait;
189
189
  this.dbconnect();
190
+
191
+ if (this.healthcheckType === 'none') {
192
+ log.error(`${origin}: Waiting 1 Seconds to emit Online`);
193
+ setTimeout(() => {
194
+ this.emit('ONLINE', { id: this.id });
195
+ this.healthy = true;
196
+ }, 1000);
197
+ }
198
+
199
+ if (this.healthcheckType === 'startup' || this.healthcheckType === 'intermittent') {
200
+ this.healthCheck();
201
+ }
202
+
203
+ if (this.healthcheckType === 'intermittent') {
204
+ setInterval(() => {
205
+ this.healthCheck();
206
+ }, this.healthcheckInterval);
207
+ }
190
208
  }
191
209
 
192
210
  /**
@@ -199,97 +217,46 @@ class MySQL extends EventEmitter {
199
217
  const origin = `${this.id}-${meth}`;
200
218
  log.trace(origin);
201
219
 
202
- // close connection
203
- if (this.connection) {
204
- this.connection.end();
205
- }
220
+ log.info(`Creating connection pool to mysql on host: ${this.config.host} and port: ${this.config.port}`);
206
221
 
207
- if (this.attempt <= 5) {
208
- setTimeout(() => {
209
- log.info(`connecting to mysql on host: ${this.config.host} and port: ${this.config.port}`);
210
- this.connection = mysql.createConnection(this.config);
211
- this.connection.connect((error) => {
212
- if (error) {
213
- // emit failure
214
- this.alive = false;
215
- this.emit('OFFLINE', {
216
- id: this.id
217
- });
218
- // increase the attempts and the timeout
219
- this.attempt += 1;
220
- this.waitTime += incrWait;
221
- log.error(`error connecting to MySQL: ${error}`);
222
- log.error(`Attampting Reconnect - attempt number ${this.attempt}`);
223
- this.dbconnect();
224
- } else {
225
- // emit success
226
- this.alive = true;
227
- this.emit('ONLINE', {
228
- id: this.id
229
- });
230
- // reset the attempts and timeout
231
- this.attempt = 0;
232
- this.waitTime = baseWait;
233
- log.info(`connected as id: ${this.connection.threadId}`);
234
-
235
- // On failure want to attempt reconnect
236
- this.connection.on('error', (myerr) => {
237
- this.attempt += 1;
238
- log.error(`Connection to MySQL took an error: ${myerr}`);
239
- log.error(`Attampting Reconnect - attempt number ${this.attempt}`);
240
- this.dbconnect();
241
- });
242
- }
243
- });
244
- }, this.waitTime);
245
- } else {
246
- log.error('Maximum retries reached - check mysql and then restart the adapter!');
247
- }
222
+ this.pool = mysql.createPool(this.config);
223
+
224
+ this.pool.on('acquire', (connection) => log.debug('Connection', connection.threadId, 'acquired'));
225
+
226
+ this.pool.on('connection', (connection) => log.debug('New connection made within the pool:', connection.threadId, connection.state, connection.config));
227
+
228
+ this.pool.on('enqueue', () => log.debug('Waiting for available connection slot'));
229
+
230
+ this.pool.on('release', (connection) => log.debug('Connection', connection.threadId, 'released'));
248
231
  }
249
232
 
250
233
  /**
251
234
  * Call to run a healthcheck on the mysql database
252
235
  *
253
236
  * @function healthCheck
254
- * @param {healthCallback} callback - a callback function to return a result
255
- * healthcheck success or failure
256
237
  */
257
- healthCheck(callback) {
238
+ healthCheck() {
258
239
  const meth = 'adapter-healthCheck';
259
240
  const origin = `${this.id}-${meth}`;
260
241
  log.trace(origin);
261
242
 
262
243
  try {
263
- // verify that we are connected to MySQL
264
- if (!this.alive || !this.connection) {
244
+ if (!this.pool) {
265
245
  log.error('Error during healthcheck: Not connected to MySQL Database');
266
- return callback({
267
- id: this.id,
268
- status: 'fail'
269
- });
246
+ return this.emit('OFFLINE', { id: this.id });
270
247
  }
271
248
 
272
- this.connection.query('SELECT "test";', (error, results, fields) => {
249
+ this.pool.query('SELECT 1;', (error, results, fields) => {
273
250
  if (error) {
274
- log.error(`Error during healthcheck: ${error}`);
275
- return callback({
276
- id: this.id,
277
- status: 'fail'
278
- });
251
+ log.error(`HEALTHCHECK FAIL, ${JSON.stringify(error)}`);
252
+ return this.emit('OFFLINE', { id: this.id });
279
253
  }
280
-
281
- log.info(`result from mysql connect: ${JSON.stringify(results)}`);
282
- return callback({
283
- id: this.id,
284
- status: 'success'
285
- });
254
+ log.debug('HEALTHCHECK SUCCESSFUL');
255
+ return this.emit('ONLINE', { id: this.id });
286
256
  });
287
257
  } catch (ex) {
288
- log.error(`Exception during healthcheck: ${ex}`);
289
- return callback({
290
- id: this.id,
291
- status: 'fail'
292
- });
258
+ log.error(`HEALTHCHECK EXCEPTION: ${JSON.stringify(ex)}`);
259
+ return this.emit('OFFLINE', { id: this.id });
293
260
  }
294
261
  }
295
262
 
@@ -333,7 +300,7 @@ class MySQL extends EventEmitter {
333
300
  break;
334
301
  }
335
302
  if (myfunctions[m] !== 'connect' && myfunctions[m] !== 'healthCheck' && myfunctions[m] !== 'dbconnect'
336
- && myfunctions[m] !== 'getAllFunctions' && myfunctions[m] !== 'getWorkflowFunctions') {
303
+ && myfunctions[m] !== 'getAllFunctions' && myfunctions[m] !== 'getWorkflowFunctions' && myfunctions[m] !== 'setConfiguration') {
337
304
  wffunctions.push(myfunctions[m]);
338
305
  }
339
306
  }
@@ -353,49 +320,27 @@ class MySQL extends EventEmitter {
353
320
  log.trace(origin);
354
321
  log.trace(`mysql query started with sql: ${sql}`);
355
322
 
356
- try {
357
- // verify the required data has been provided
358
- if (!sql) {
359
- const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
360
- log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
361
- return callback(null, errorObj);
362
- }
323
+ if (!sql) {
324
+ const errorObj = formatErrorObject(origin, 'Missing Data', ['sql'], null, null, null);
325
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
326
+ return callback(null, errorObj);
327
+ }
363
328
 
364
- // set up a base wait time (very very small since we do not want to delay if all is good)
365
- let reconn = 1;
366
-
367
- // if the connection is not up attempt to reconnect
368
- if (!this.alive || !this.connection) {
369
- // increate timeout since we need to wait for reconnect
370
- reconn = incrWait * 3;
371
- this.dbconnect();
372
- if (!this.alive || !this.connection) {
373
- return callback({
374
- id: this.id,
375
- status: 'fail'
376
- });
329
+ try {
330
+ this.pool.query(sql, (error, results, fields) => {
331
+ log.debug(`result from query: ${JSON.stringify(results)}`);
332
+ log.debug(`fields from query: ${JSON.stringify(fields)}`);
333
+ if (error) {
334
+ const errorObj = formatErrorObject(origin, 'Database Error', [error], null, null, null);
335
+ log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
336
+ return callback(null, errorObj);
377
337
  }
378
- }
379
-
380
- // query connection
381
- setTimeout(() => {
382
- this.connection.query(sql, (error, results, fields) => {
383
- log.debug(`result from query: ${JSON.stringify(results)}`);
384
- log.debug(`result from query: ${JSON.stringify(fields)}`);
385
-
386
- if (error) {
387
- const errorObj = formatErrorObject(origin, 'Database Error', [error], null, null, null);
388
- log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
389
- return callback(null, errorObj);
390
- }
391
-
392
- return callback({
393
- status: 'success',
394
- code: 200,
395
- response: results
396
- });
338
+ return callback({
339
+ status: 'success',
340
+ code: 200,
341
+ response: results
397
342
  });
398
- }, this.reconn);
343
+ });
399
344
  } catch (ex) {
400
345
  const errorObj = formatErrorObject(origin, 'Caught Exception', null, null, null, ex);
401
346
  log.error(`${origin}: ${errorObj.IAPerror.displayString}`);
package/package.json CHANGED
@@ -1,20 +1,20 @@
1
1
  {
2
2
  "name": "@itentialopensource/adapter-db_mysql",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Itential adapter to connect to mysql",
5
5
  "main": "adapter.js",
6
6
  "wizardVersion": "2.41.0",
7
7
  "engineVersion": "1.44.2",
8
8
  "scripts": {
9
9
  "artifactize": "npm i && node utils/packModificationScript.js",
10
- "preinstall": "node utils/setup.js",
10
+ "preinstall": "node utils/setup.js && npm install --package-lock-only --ignore-scripts && npx npm-force-resolutions",
11
11
  "lint": "eslint . --ext .json --ext .js",
12
12
  "lint:errors": "node --max_old_space_size=4096 ./node_modules/eslint/bin/eslint.js . --ext .json --ext .js --quiet",
13
13
  "test:unit": "mocha test/unit/adapterTestUnit.js --LOG=error",
14
14
  "test:integration": "mocha test/integration/adapterTestIntegration.js --LOG=error",
15
15
  "test:cover": "nyc --reporter html --reporter text mocha --reporter dot test/*",
16
16
  "test": "npm run test:unit && npm run test:integration",
17
- "deploy": "npm publish --registry=http://registry.npmjs.org --access=public",
17
+ "deploy": "npm publish --registry=https://registry.npmjs.org --access=public",
18
18
  "build": "npm run deploy"
19
19
  },
20
20
  "keywords": [
@@ -46,16 +46,19 @@
46
46
  "uuid": "^3.0.1"
47
47
  },
48
48
  "devDependencies": {
49
- "chai": "^4.3.4",
49
+ "chai": "^4.3.6",
50
50
  "eslint": "^7.29.0",
51
51
  "eslint-config-airbnb-base": "^14.2.1",
52
- "eslint-plugin-import": "^2.23.4",
52
+ "eslint-plugin-import": "^2.26.0",
53
53
  "eslint-plugin-json": "^3.0.0",
54
- "mocha": "^9.0.1",
54
+ "mocha": "^9.2.2",
55
55
  "nyc": "^15.1.0",
56
56
  "package-json-validator": "^0.6.3",
57
- "testdouble": "^3.16.1",
58
- "winston": "^3.3.3"
57
+ "testdouble": "^3.16.6",
58
+ "winston": "^3.8.1"
59
59
  },
60
- "private": false
60
+ "private": false,
61
+ "resolutions": {
62
+ "minimist": "^1.2.5"
63
+ }
61
64
  }
@@ -27,23 +27,40 @@
27
27
  "maximum": 65535
28
28
  },
29
29
  "authentication": {
30
- "$ref":"#/definitions/authentication"
30
+ "$ref": "#/definitions/authentication",
31
+ "default": {}
31
32
  },
32
33
  "ssl": {
33
- "$ref":"#/definitions/ssl"
34
+ "$ref": "#/definitions/ssl",
35
+ "default": {}
36
+ },
37
+ "connectionOptions": {
38
+ "$ref": "#/definitions/connectionOptions",
39
+ "default": {}
40
+ },
41
+ "healthcheck": {
42
+ "$ref": "#/definitions/healthcheck",
43
+ "default": {}
34
44
  }
35
45
  },
36
- "required" : ["database","host","port"],
46
+ "required": [
47
+ "database",
48
+ "host",
49
+ "port",
50
+ "authentication",
51
+ "healthcheck",
52
+ "connectionOptions"
53
+ ],
37
54
  "definitions": {
38
55
  "authentication": {
39
56
  "$id": "/authentication",
40
57
  "type": "object",
41
58
  "properties": {
42
- "dbAuth" : {
43
- "$id" : "/credentials/properties/dbAuth",
44
- "type" : "boolean",
45
- "description": "whether or not to use user authentication to connect to mysql",
46
- "default" : false
59
+ "dbAuth": {
60
+ "$id": "/credentials/properties/dbAuth",
61
+ "type": "boolean",
62
+ "description": "whether or not to use user authentication to connect to mysql",
63
+ "default": false
47
64
  },
48
65
  "password": {
49
66
  "type": "string",
@@ -62,7 +79,10 @@
62
79
  ]
63
80
  }
64
81
  },
65
- "required" : ["username","password"]
82
+ "required": [
83
+ "username",
84
+ "password"
85
+ ]
66
86
  },
67
87
  "ssl": {
68
88
  "$id": "/ssl",
@@ -72,7 +92,7 @@
72
92
  "$id": "/ssl/properties/enabled",
73
93
  "type": "boolean",
74
94
  "description": "Whether or not ssl is enabled on PostgreSQL",
75
- "default": false
95
+ "default": false
76
96
  },
77
97
  "accept_invalid_cert": {
78
98
  "$id": "/ssl/properties/accept_invalid_cert",
@@ -90,20 +110,133 @@
90
110
  ]
91
111
  }
92
112
  },
93
- "required" : ["enabled"],
113
+ "required": [
114
+ "enabled"
115
+ ],
94
116
  "allOf": [
95
117
  {
96
- "if" : { "properties" : { "enabled": { "enum": [true] } } },
97
- "then" : { "required" : ["acceptInvalidCerts", "checkServerIdentity"] }
118
+ "if": {
119
+ "properties": {
120
+ "enabled": {
121
+ "enum": [
122
+ true
123
+ ]
124
+ }
125
+ }
126
+ },
127
+ "then": {
128
+ "required": [
129
+ "acceptInvalidCerts",
130
+ "checkServerIdentity"
131
+ ]
132
+ }
133
+ },
134
+ {
135
+ "if": {
136
+ "allOf": [
137
+ {
138
+ "properties": {
139
+ "enabled": {
140
+ "enum": [
141
+ true
142
+ ]
143
+ }
144
+ }
145
+ },
146
+ {
147
+ "properties": {
148
+ "acceptInvalidCerts": {
149
+ "enum": [
150
+ false
151
+ ]
152
+ }
153
+ }
154
+ }
155
+ ]
156
+ },
157
+ "then": {
158
+ "required": [
159
+ "sslCA"
160
+ ]
161
+ }
162
+ }
163
+ ]
164
+ },
165
+ "healthcheck": {
166
+ "$id": "/healthcheck",
167
+ "type": "object",
168
+ "properties": {
169
+ "type": {
170
+ "type": "string",
171
+ "description": "The type of healthcheck to run",
172
+ "default": "intermittent",
173
+ "enum": [
174
+ "none",
175
+ "startup",
176
+ "intermittent"
177
+ ]
98
178
  },
179
+ "frequency": {
180
+ "type": "integer",
181
+ "description": "How often the healthcheck should run (in milliseconds).",
182
+ "default": 300000,
183
+ "minimum": 60000,
184
+ "maximum": 3600000
185
+ }
186
+ },
187
+ "required": [
188
+ "type"
189
+ ],
190
+ "allOf": [
99
191
  {
100
- "if" : { "allOf": [
101
- { "properties" : { "enabled": { "enum": [true] } } },
102
- { "properties" : { "acceptInvalidCerts": { "enum": [ false ] } } }
103
- ] },
104
- "then" : { "required" : ["sslCA"] }
192
+ "if": {
193
+ "properties": {
194
+ "type": {
195
+ "enum": [
196
+ "intermittent"
197
+ ]
198
+ }
199
+ }
200
+ },
201
+ "then": {
202
+ "required": [
203
+ "frequency"
204
+ ]
205
+ }
105
206
  }
106
207
  ]
208
+ },
209
+ "connectionOptions": {
210
+ "$id": "/connectionOptions",
211
+ "type": "object",
212
+ "properties": {
213
+ "connectTimeout": {
214
+ "type": "number",
215
+ "description": "The milliseconds before a timeout occurs during the initial connection to the MySQL server.",
216
+ "default": 10000
217
+ },
218
+ "acquireTimeout": {
219
+ "type": "number",
220
+ "description": "The milliseconds before a timeout occurs during the connection acquisition",
221
+ "default": 10000
222
+ },
223
+ "waitForConnections": {
224
+ "type": "boolean",
225
+ "description": "Determines the pool's action when no connections are available and the limit has been reached",
226
+ "default": true
227
+ },
228
+ "connectionLimit": {
229
+ "type": "number",
230
+ "description": "The maximum number of connections to create at once",
231
+ "default": 10
232
+ },
233
+ "queueLimit": {
234
+ "type": "number",
235
+ "description": "The maximum number of connection requests the pool will queue before returning an error from getConnection",
236
+ "default": 0
237
+ }
238
+ },
239
+ "additionalProperties": false
107
240
  }
108
241
  }
109
- }
242
+ }
Binary file
@@ -1,11 +1,14 @@
1
1
  {
2
- "type": "MySQL",
3
2
  "id": "mysql",
3
+ "type": "MySQL",
4
+ "brokers": [],
5
+ "groups": [],
4
6
  "properties": {
5
7
  "database": "your database name",
6
8
  "host": "localhost",
7
9
  "port": 3306,
8
10
  "authentication": {
11
+ "dbAuth": false,
9
12
  "username": "your username",
10
13
  "password": "your password"
11
14
  },
@@ -13,10 +16,21 @@
13
16
  "enabled": false,
14
17
  "accept_invalid_cert": true,
15
18
  "ca_file": ""
19
+ },
20
+ "connectionOptions": {
21
+ "connectTimeout": 10000,
22
+ "acquireTimeout": 10000,
23
+ "waitForConnections": true,
24
+ "connectionLimit": 10,
25
+ "queueLimit": 0
26
+ },
27
+ "healthcheck": {
28
+ "type": "intermittent",
29
+ "frequency": 60000
16
30
  }
17
31
  },
18
32
  "groups": [],
19
33
  "brokers": [],
20
34
  "logLevel": "none",
21
35
  "timeout": 60000
22
- }
36
+ }
@@ -56,6 +56,17 @@ global.pronghornProps = {
56
56
  },
57
57
  replSet: {
58
58
  enabled: false
59
+ },
60
+ connectionOptions: {
61
+ connectTimeout: 10000,
62
+ acquireTimeout: 10000,
63
+ waitForConnections: true,
64
+ connectionLimit: 10,
65
+ queueLimit: 0
66
+ },
67
+ healthcheck: {
68
+ type: 'intermittent',
69
+ frequency: 60000
59
70
  }
60
71
  }
61
72
  }]
@@ -48,9 +48,10 @@ global.pronghornProps = {
48
48
  database: 'mydatabase',
49
49
  host,
50
50
  port,
51
- credentials: {
52
- username,
53
- password
51
+ authentication: {
52
+ dbAuth: false,
53
+ username: 'itential',
54
+ password: 'itential'
54
55
  },
55
56
  ssl: {
56
57
  enabled: sslenable,
@@ -60,6 +61,17 @@ global.pronghornProps = {
60
61
  },
61
62
  replSet: {
62
63
  enabled: false
64
+ },
65
+ connectionOptions: {
66
+ connectTimeout: 10000,
67
+ acquireTimeout: 10000,
68
+ waitForConnections: true,
69
+ connectionLimit: 10,
70
+ queueLimit: 0
71
+ },
72
+ healthcheck: {
73
+ type: 'intermittent',
74
+ frequency: 60000
63
75
  }
64
76
  }
65
77
  }]