@alwaysai/device-agent 2.0.2-0 → 2.0.3
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/lib/application-control/config.js +1 -1
- package/lib/application-control/config.js.map +1 -1
- package/lib/application-control/install.d.ts.map +1 -1
- package/lib/application-control/install.js +7 -3
- package/lib/application-control/install.js.map +1 -1
- package/lib/cloud-connection/base-message-handler.d.ts.map +1 -1
- package/lib/cloud-connection/base-message-handler.js +5 -4
- package/lib/cloud-connection/base-message-handler.js.map +1 -1
- package/lib/cloud-connection/connection-manager.d.ts +3 -4
- package/lib/cloud-connection/connection-manager.d.ts.map +1 -1
- package/lib/cloud-connection/connection-manager.js +22 -40
- package/lib/cloud-connection/connection-manager.js.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts +3 -1
- package/lib/cloud-connection/device-agent-cloud-connection.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-cloud-connection.js +52 -46
- package/lib/cloud-connection/device-agent-cloud-connection.js.map +1 -1
- package/lib/cloud-connection/device-agent-message-handler.d.ts.map +1 -1
- package/lib/cloud-connection/device-agent-message-handler.js +19 -18
- package/lib/cloud-connection/device-agent-message-handler.js.map +1 -1
- package/lib/cloud-connection/live-updates-handler.d.ts.map +1 -1
- package/lib/cloud-connection/live-updates-handler.js +11 -4
- package/lib/cloud-connection/live-updates-handler.js.map +1 -1
- package/lib/cloud-connection/passthrough-handler.d.ts +3 -3
- package/lib/cloud-connection/passthrough-handler.d.ts.map +1 -1
- package/lib/cloud-connection/passthrough-handler.js +97 -74
- package/lib/cloud-connection/passthrough-handler.js.map +1 -1
- package/lib/cloud-connection/shadow-handler.js +3 -3
- package/lib/cloud-connection/shadow-handler.js.map +1 -1
- package/lib/cloud-connection/shadow.d.ts.map +1 -1
- package/lib/cloud-connection/shadow.js +1 -1
- package/lib/cloud-connection/shadow.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.d.ts.map +1 -1
- package/lib/cloud-connection/transaction-manager.js +17 -7
- package/lib/cloud-connection/transaction-manager.js.map +1 -1
- package/lib/cloud-connection/transaction-manager.test.js +52 -44
- package/lib/cloud-connection/transaction-manager.test.js.map +1 -1
- package/lib/device-control/device-control.d.ts.map +1 -1
- package/lib/device-control/device-control.js +13 -9
- package/lib/device-control/device-control.js.map +1 -1
- package/lib/docker/docker-compose.d.ts.map +1 -1
- package/lib/docker/docker-compose.js +1 -1
- package/lib/docker/docker-compose.js.map +1 -1
- package/lib/environment.d.ts +3 -0
- package/lib/environment.d.ts.map +1 -1
- package/lib/environment.js +12 -1
- package/lib/environment.js.map +1 -1
- package/lib/index.js +12 -0
- package/lib/index.js.map +1 -1
- package/lib/infrastructure/config-check-utility.js +2 -2
- package/lib/infrastructure/config-check-utility.js.map +1 -1
- package/lib/infrastructure/legacy-migration/legacy-migration.d.ts.map +1 -1
- package/lib/infrastructure/legacy-migration/legacy-migration.js +6 -10
- package/lib/infrastructure/legacy-migration/legacy-migration.js.map +1 -1
- package/lib/jobs/job-handler.d.ts +1 -1
- package/lib/jobs/job-handler.d.ts.map +1 -1
- package/lib/jobs/job-handler.js +4 -4
- package/lib/jobs/job-handler.js.map +1 -1
- package/lib/local-connection/rabbitmq-container.d.ts +6 -0
- package/lib/local-connection/rabbitmq-container.d.ts.map +1 -0
- package/lib/local-connection/rabbitmq-container.js +111 -0
- package/lib/local-connection/rabbitmq-container.js.map +1 -0
- package/lib/local-connection/rabbitmq-container.test.d.ts +2 -0
- package/lib/local-connection/rabbitmq-container.test.d.ts.map +1 -0
- package/lib/local-connection/rabbitmq-container.test.js +219 -0
- package/lib/local-connection/rabbitmq-container.test.js.map +1 -0
- package/lib/subcommands/app/analytics.d.ts.map +1 -1
- package/lib/subcommands/app/analytics.js +2 -3
- package/lib/subcommands/app/analytics.js.map +1 -1
- package/lib/subcommands/app/env-vars.d.ts.map +1 -1
- package/lib/subcommands/app/env-vars.js +4 -6
- package/lib/subcommands/app/env-vars.js.map +1 -1
- package/lib/subcommands/app/models.d.ts.map +1 -1
- package/lib/subcommands/app/models.js +2 -3
- package/lib/subcommands/app/models.js.map +1 -1
- package/lib/subcommands/app/shadow.d.ts.map +1 -1
- package/lib/subcommands/app/shadow.js +4 -6
- package/lib/subcommands/app/shadow.js.map +1 -1
- package/lib/subcommands/app/version.d.ts.map +1 -1
- package/lib/subcommands/app/version.js +6 -9
- package/lib/subcommands/app/version.js.map +1 -1
- package/lib/subcommands/device/clean.d.ts.map +1 -1
- package/lib/subcommands/device/clean.js +15 -17
- package/lib/subcommands/device/clean.js.map +1 -1
- package/lib/subcommands/device/index.d.ts.map +1 -1
- package/lib/subcommands/device/index.js +3 -1
- package/lib/subcommands/device/index.js.map +1 -1
- package/lib/subcommands/device/local-connection.d.ts +2 -0
- package/lib/subcommands/device/local-connection.d.ts.map +1 -0
- package/lib/subcommands/device/local-connection.js +17 -0
- package/lib/subcommands/device/local-connection.js.map +1 -0
- package/lib/subcommands/index.d.ts +4 -1
- package/lib/subcommands/index.d.ts.map +1 -1
- package/lib/subcommands/index.js +1 -3
- package/lib/subcommands/index.js.map +1 -1
- package/lib/util/check-for-updates.d.ts.map +1 -1
- package/lib/util/check-for-updates.js +2 -2
- package/lib/util/check-for-updates.js.map +1 -1
- package/lib/util/file.d.ts.map +1 -1
- package/lib/util/file.js +6 -1
- package/lib/util/file.js.map +1 -1
- package/lib/util/file.test.js +1 -1
- package/lib/util/file.test.js.map +1 -1
- package/lib/util/get-device-id.d.ts.map +1 -1
- package/lib/util/get-device-id.js +1 -1
- package/lib/util/get-device-id.js.map +1 -1
- package/package.json +2 -2
- package/readme.md +16 -4
- package/src/application-control/config.ts +1 -1
- package/src/application-control/install.ts +11 -5
- package/src/cloud-connection/base-message-handler.ts +10 -5
- package/src/cloud-connection/connection-manager.ts +23 -45
- package/src/cloud-connection/device-agent-cloud-connection.ts +97 -89
- package/src/cloud-connection/device-agent-message-handler.ts +10 -7
- package/src/cloud-connection/live-updates-handler.ts +12 -5
- package/src/cloud-connection/passthrough-handler.ts +79 -38
- package/src/cloud-connection/shadow-handler.ts +3 -3
- package/src/cloud-connection/shadow.ts +3 -1
- package/src/cloud-connection/transaction-manager.test.ts +60 -41
- package/src/cloud-connection/transaction-manager.ts +26 -12
- package/src/device-control/device-control.ts +23 -13
- package/src/docker/docker-compose.ts +3 -1
- package/src/environment.ts +17 -0
- package/src/index.ts +19 -0
- package/src/infrastructure/config-check-utility.ts +2 -2
- package/src/infrastructure/legacy-migration/legacy-migration.ts +8 -13
- package/src/jobs/job-handler.ts +4 -4
- package/src/local-connection/rabbitmq-container.test.ts +255 -0
- package/src/local-connection/rabbitmq-container.ts +151 -0
- package/src/subcommands/app/analytics.ts +2 -3
- package/src/subcommands/app/env-vars.ts +4 -6
- package/src/subcommands/app/models.ts +2 -3
- package/src/subcommands/app/shadow.ts +4 -6
- package/src/subcommands/app/version.ts +7 -8
- package/src/subcommands/device/clean.ts +20 -19
- package/src/subcommands/device/index.ts +3 -1
- package/src/subcommands/device/local-connection.ts +16 -0
- package/src/subcommands/index.ts +1 -3
- package/src/util/check-for-updates.ts +4 -2
- package/src/util/file.test.ts +1 -1
- package/src/util/file.ts +7 -1
- package/src/util/get-device-id.ts +3 -1
- package/lib/local-connection/rabbitmq-connection.d.ts +0 -7
- package/lib/local-connection/rabbitmq-connection.d.ts.map +0 -1
- package/lib/local-connection/rabbitmq-connection.js +0 -95
- package/lib/local-connection/rabbitmq-connection.js.map +0 -1
- package/lib/subcommands/rabbitmq-connection.d.ts +0 -2
- package/lib/subcommands/rabbitmq-connection.d.ts.map +0 -1
- package/lib/subcommands/rabbitmq-connection.js +0 -14
- package/lib/subcommands/rabbitmq-connection.js.map +0 -1
- package/src/local-connection/rabbitmq-connection.ts +0 -124
- package/src/subcommands/rabbitmq-connection.ts +0 -11
|
@@ -11,9 +11,9 @@ import {
|
|
|
11
11
|
LOCAL_CONNECTION_ROUTING_KEY
|
|
12
12
|
} from '../local-connection/constants';
|
|
13
13
|
import {
|
|
14
|
-
|
|
14
|
+
runRabbitMQContainer,
|
|
15
15
|
stopRabbitMQContainer
|
|
16
|
-
} from '../local-connection/rabbitmq-
|
|
16
|
+
} from '../local-connection/rabbitmq-container';
|
|
17
17
|
import { logger } from '../util/logger';
|
|
18
18
|
import sleep from '../util/sleep';
|
|
19
19
|
import { Publisher } from './publisher';
|
|
@@ -23,6 +23,11 @@ const messageQueue: any[] = [];
|
|
|
23
23
|
const ackQueue: any[] = [];
|
|
24
24
|
const MAX_LOCAL_CONNECTION_ATTEMPTS = 10;
|
|
25
25
|
|
|
26
|
+
/*
|
|
27
|
+
Responsible for managing the lifecycle of the Local Connection container
|
|
28
|
+
(RabbitMQ), the connection to it, and the passthrough of messages received
|
|
29
|
+
from the local connection to endpoints of provided `Publisher`.
|
|
30
|
+
*/
|
|
26
31
|
export class PassthroughHandler {
|
|
27
32
|
public publisher: Publisher;
|
|
28
33
|
public shadowHandler: ShadowHandler;
|
|
@@ -35,8 +40,8 @@ export class PassthroughHandler {
|
|
|
35
40
|
this.shadowHandler = shadowHandler;
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
|
|
39
|
-
logger.debug('Beginning to consume packets');
|
|
43
|
+
private async runLocalConnectionChannel() {
|
|
44
|
+
logger.debug('Beginning to consume packets from Local Connection');
|
|
40
45
|
await this.channel.consume(
|
|
41
46
|
this.packetQueue,
|
|
42
47
|
(msg) => {
|
|
@@ -84,12 +89,12 @@ export class PassthroughHandler {
|
|
|
84
89
|
} else {
|
|
85
90
|
this.channel.ack(msg);
|
|
86
91
|
logger.debug(
|
|
87
|
-
`Received & acknowledged a RabbitMQ
|
|
92
|
+
`Received & acknowledged a RabbitMQ packet of unknown structure: ${parsedPacket}`
|
|
88
93
|
);
|
|
89
94
|
}
|
|
90
95
|
} catch (e) {
|
|
91
96
|
logger.error(
|
|
92
|
-
`There was a problem parsing RabbitMQ packet
|
|
97
|
+
`There was a problem parsing RabbitMQ packet! Error:\n${stringifyError(
|
|
93
98
|
e
|
|
94
99
|
)}`
|
|
95
100
|
);
|
|
@@ -103,12 +108,12 @@ export class PassthroughHandler {
|
|
|
103
108
|
noAck: false // When true, RabbitMQ deletes message as soon as it is consumed
|
|
104
109
|
}
|
|
105
110
|
);
|
|
106
|
-
}
|
|
111
|
+
}
|
|
107
112
|
|
|
108
|
-
async establishLocalConnection(): Promise<void> {
|
|
113
|
+
private async establishLocalConnection(): Promise<void> {
|
|
109
114
|
let connectAttempts = 0;
|
|
110
115
|
let connected = false;
|
|
111
|
-
logger.debug(`Establishing
|
|
116
|
+
logger.debug(`Establishing Local Connection...`);
|
|
112
117
|
while (
|
|
113
118
|
connectAttempts <= MAX_LOCAL_CONNECTION_ATTEMPTS &&
|
|
114
119
|
this.connection === undefined
|
|
@@ -117,14 +122,40 @@ export class PassthroughHandler {
|
|
|
117
122
|
this.connection = await amqp.connect(
|
|
118
123
|
`amqp://${LOCAL_CONNECTION_HOST}:${LOCAL_CONNECTION_PORT}`
|
|
119
124
|
);
|
|
120
|
-
|
|
121
|
-
this.connection.on('error', async () => {
|
|
122
|
-
logger.error(
|
|
123
|
-
|
|
125
|
+
|
|
126
|
+
this.connection.on('error', async (e) => {
|
|
127
|
+
logger.error(
|
|
128
|
+
`Local Connection failed due to ${stringifyError(
|
|
129
|
+
e
|
|
130
|
+
)}. Reconnect will be handled by "close"`
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
this.connection.on('close', async () => {
|
|
135
|
+
// Close should be called for every connection loss, error or not.
|
|
136
|
+
logger.warn(
|
|
137
|
+
`Local Connection closed by server. Attempting to reconnect...`
|
|
138
|
+
);
|
|
139
|
+
const stopped = await stopRabbitMQContainer();
|
|
140
|
+
if (stopped === false) {
|
|
141
|
+
logger.warn(
|
|
142
|
+
'Failed to stop Local Connection container. Restart may fail...'
|
|
143
|
+
);
|
|
144
|
+
}
|
|
124
145
|
this.connection = undefined;
|
|
125
|
-
await this.
|
|
146
|
+
await this.run();
|
|
126
147
|
});
|
|
127
148
|
|
|
149
|
+
this.connection.on('blocked', async () => {
|
|
150
|
+
logger.warn(`Local Connection blocked.`);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
this.connection.on('unblocked', async () => {
|
|
154
|
+
logger.warn(`Local Connection unblocked.`);
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
this.channel = await this.connection.createChannel();
|
|
158
|
+
|
|
128
159
|
connected = true;
|
|
129
160
|
} catch (e) {
|
|
130
161
|
const timeTillNextAttemptMs = 1000 + 1000 * connectAttempts;
|
|
@@ -143,7 +174,7 @@ export class PassthroughHandler {
|
|
|
143
174
|
await this.channel.assertQueue(this.packetQueue, {
|
|
144
175
|
durable: true
|
|
145
176
|
});
|
|
146
|
-
logger.info(`Local
|
|
177
|
+
logger.info(`Local Connection established.`);
|
|
147
178
|
} else {
|
|
148
179
|
throw new Error(
|
|
149
180
|
'Unable to establish connection to alwaysAI Local Connection, please try restarting Device Agent.'
|
|
@@ -166,30 +197,40 @@ export class PassthroughHandler {
|
|
|
166
197
|
);
|
|
167
198
|
}
|
|
168
199
|
|
|
169
|
-
async
|
|
170
|
-
if (ALWAYSAI_ANALYTICS_PASSTHROUGH ===
|
|
171
|
-
logger.debug(
|
|
172
|
-
`Setting up alwaysAI Local Connection on host: ${LOCAL_CONNECTION_HOST} and channel key: ${LOCAL_CONNECTION_ROUTING_KEY}`
|
|
173
|
-
);
|
|
174
|
-
await this.publishPassthroughStatusUpdate('starting');
|
|
175
|
-
await setupRabbitMQContainer();
|
|
176
|
-
try {
|
|
177
|
-
await this.establishLocalConnection();
|
|
178
|
-
await this.runChannel();
|
|
179
|
-
await this.publishPassthroughStatusUpdate(
|
|
180
|
-
'running',
|
|
181
|
-
`Passthrough running on host: ${LOCAL_CONNECTION_HOST} and channel key: ${LOCAL_CONNECTION_ROUTING_KEY}`
|
|
182
|
-
);
|
|
183
|
-
} catch (e) {
|
|
184
|
-
logger.error(
|
|
185
|
-
`There was a problem maintaining RabbitMQ connection!\n${stringifyError(
|
|
186
|
-
e
|
|
187
|
-
)}`
|
|
188
|
-
);
|
|
189
|
-
await this.publishPassthroughStatusUpdate('error', stringifyError(e));
|
|
190
|
-
}
|
|
191
|
-
} else {
|
|
200
|
+
async run() {
|
|
201
|
+
if (ALWAYSAI_ANALYTICS_PASSTHROUGH === false) {
|
|
202
|
+
logger.debug(`alwaysAI Local Connection disabled`);
|
|
192
203
|
await this.publishPassthroughStatusUpdate('disabled');
|
|
204
|
+
return;
|
|
193
205
|
}
|
|
206
|
+
|
|
207
|
+
logger.debug(
|
|
208
|
+
`Setting up alwaysAI Local Connection on host: ${LOCAL_CONNECTION_HOST} and channel key: ${LOCAL_CONNECTION_ROUTING_KEY}`
|
|
209
|
+
);
|
|
210
|
+
await this.publishPassthroughStatusUpdate('starting');
|
|
211
|
+
const containerUp = await runRabbitMQContainer();
|
|
212
|
+
if (!containerUp) {
|
|
213
|
+
await this.publishPassthroughStatusUpdate(
|
|
214
|
+
'error',
|
|
215
|
+
'Local Connection container failed to start!'
|
|
216
|
+
);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
try {
|
|
220
|
+
await this.establishLocalConnection();
|
|
221
|
+
await this.runLocalConnectionChannel();
|
|
222
|
+
} catch (e) {
|
|
223
|
+
logger.error(
|
|
224
|
+
`There was a problem maintaining RabbitMQ connection! Error:\n${stringifyError(
|
|
225
|
+
e
|
|
226
|
+
)}`
|
|
227
|
+
);
|
|
228
|
+
await this.publishPassthroughStatusUpdate('error', stringifyError(e));
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
await this.publishPassthroughStatusUpdate(
|
|
232
|
+
'running',
|
|
233
|
+
`Passthrough running on host: ${LOCAL_CONNECTION_HOST} and channel key: ${LOCAL_CONNECTION_ROUTING_KEY}`
|
|
234
|
+
);
|
|
194
235
|
}
|
|
195
236
|
}
|
|
@@ -125,7 +125,7 @@ export class ShadowHandler {
|
|
|
125
125
|
newAppCfg = JSON.parse(appConfig);
|
|
126
126
|
} catch (e) {
|
|
127
127
|
logger.error(
|
|
128
|
-
`Could not parse the appConfig for transaction ${txId}
|
|
128
|
+
`Could not parse the appConfig for transaction ${txId}! Error:\n${stringifyError(
|
|
129
129
|
e
|
|
130
130
|
)}`
|
|
131
131
|
);
|
|
@@ -172,7 +172,7 @@ export class ShadowHandler {
|
|
|
172
172
|
newEnvVars = JSON.parse(envVars);
|
|
173
173
|
} catch (e) {
|
|
174
174
|
logger.error(
|
|
175
|
-
`Could not parse the environment variables for transaction ${txId}
|
|
175
|
+
`Could not parse the environment variables for transaction ${txId}! Error:\n${stringifyError(
|
|
176
176
|
e
|
|
177
177
|
)}`
|
|
178
178
|
);
|
|
@@ -493,7 +493,7 @@ export class ProjectShadowMessageHandler
|
|
|
493
493
|
})
|
|
494
494
|
.catch((e) => {
|
|
495
495
|
logger.error(
|
|
496
|
-
`There was an issue updating project shadow config for ${projectId}
|
|
496
|
+
`There was an issue updating project shadow config for ${projectId}! Error:\n${stringifyError(
|
|
497
497
|
e
|
|
498
498
|
)}`
|
|
499
499
|
);
|
|
@@ -45,7 +45,9 @@ export const getAppCfgModelsDiff = async ({
|
|
|
45
45
|
});
|
|
46
46
|
} catch (e) {
|
|
47
47
|
logger.error(
|
|
48
|
-
`Error parsing app config update for ${projectId}
|
|
48
|
+
`Error parsing app config update for ${projectId}! Error:\n${stringifyError(
|
|
49
|
+
e
|
|
50
|
+
)}`
|
|
49
51
|
);
|
|
50
52
|
}
|
|
51
53
|
|
|
@@ -115,78 +115,97 @@ describe('Test Transaction Manager', () => {
|
|
|
115
115
|
test('Attempt to start an ongoing transaction, results in failure', async () => {
|
|
116
116
|
const txId = generateTxId();
|
|
117
117
|
const projectId = generateRandomProjectId();
|
|
118
|
+
let error;
|
|
119
|
+
const errorFn = jest.fn().mockImplementation((txid, message) => {
|
|
120
|
+
error = message;
|
|
121
|
+
});
|
|
118
122
|
await txnMgr.runTransactionStep({
|
|
119
123
|
func: func_incomplete,
|
|
120
124
|
projectId,
|
|
121
125
|
txId,
|
|
122
126
|
start: true,
|
|
123
|
-
stepName: 'step1'
|
|
127
|
+
stepName: 'step1',
|
|
128
|
+
errorFn
|
|
129
|
+
});
|
|
130
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
131
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
132
|
+
expect(errorFn).toBeCalledTimes(0);
|
|
133
|
+
|
|
134
|
+
await txnMgr.runTransactionStep({
|
|
135
|
+
func: func_incomplete,
|
|
136
|
+
projectId,
|
|
137
|
+
txId,
|
|
138
|
+
start: true,
|
|
139
|
+
stepName: 'step2',
|
|
140
|
+
errorFn
|
|
124
141
|
});
|
|
125
|
-
try {
|
|
126
|
-
await txnMgr.runTransactionStep({
|
|
127
|
-
func: func_incomplete,
|
|
128
|
-
projectId,
|
|
129
|
-
txId,
|
|
130
|
-
start: true,
|
|
131
|
-
stepName: 'step2'
|
|
132
|
-
});
|
|
133
|
-
throw new Error('Expected starting transaction to fail!');
|
|
134
|
-
} catch (e) {
|
|
135
|
-
console.log(e);
|
|
136
|
-
expect(e.code).toBe(txnMgr.Errors.TRANSACTION_ONGOING);
|
|
137
|
-
}
|
|
138
142
|
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
139
143
|
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
144
|
+
expect(errorFn).toBeCalledTimes(1);
|
|
145
|
+
expect(error.includes(`Transaction ${txId} already ongoing`)).toBeTruthy();
|
|
140
146
|
});
|
|
141
147
|
|
|
142
148
|
test('Attempt to continue a transaction that is not ongoing', async () => {
|
|
143
149
|
const txId = generateTxId();
|
|
144
150
|
const projectId = generateRandomProjectId();
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
}
|
|
151
|
+
let error;
|
|
152
|
+
const errorFn = jest.fn().mockImplementation((txid, message) => {
|
|
153
|
+
error = message;
|
|
154
|
+
});
|
|
155
|
+
await txnMgr.runTransactionStep({
|
|
156
|
+
func: func_incomplete,
|
|
157
|
+
projectId,
|
|
158
|
+
txId,
|
|
159
|
+
start: false,
|
|
160
|
+
stepName: 'step1',
|
|
161
|
+
errorFn
|
|
162
|
+
});
|
|
158
163
|
expect(txnMgr.isOngoingTransaction(txId)).toBe(false);
|
|
159
164
|
expect(txnMgr.isOngoingTransactionForProjectID(projectId)).toBe(false);
|
|
160
165
|
expect(txnMgr.isAnyOngoingTransaction()).toBe(false);
|
|
166
|
+
expect(errorFn).toBeCalledTimes(1);
|
|
167
|
+
expect(
|
|
168
|
+
error.includes(
|
|
169
|
+
`Cannot update transaction ${txId} since it doesn't exist!`
|
|
170
|
+
)
|
|
171
|
+
).toBeTruthy();
|
|
161
172
|
});
|
|
162
173
|
|
|
163
174
|
test('Attempt to start a transaction for a project with an ongoing transaction, results in failure', async () => {
|
|
164
175
|
const txId = generateTxId();
|
|
165
176
|
const txId2 = generateTxId();
|
|
166
177
|
const projectId = generateRandomProjectId();
|
|
178
|
+
let error;
|
|
179
|
+
const errorFn = jest.fn().mockImplementation((txid, message) => {
|
|
180
|
+
error = message;
|
|
181
|
+
});
|
|
167
182
|
await txnMgr.runTransactionStep({
|
|
168
183
|
func: func_incomplete,
|
|
169
184
|
projectId,
|
|
170
185
|
txId,
|
|
171
186
|
start: true,
|
|
172
|
-
stepName: 'step1'
|
|
187
|
+
stepName: 'step1',
|
|
188
|
+
errorFn
|
|
189
|
+
});
|
|
190
|
+
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
191
|
+
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
192
|
+
expect(errorFn).toBeCalledTimes(0);
|
|
193
|
+
|
|
194
|
+
await txnMgr.runTransactionStep({
|
|
195
|
+
func: func_incomplete,
|
|
196
|
+
projectId,
|
|
197
|
+
txId: txId2,
|
|
198
|
+
start: true,
|
|
199
|
+
stepName: 'step2',
|
|
200
|
+
errorFn
|
|
173
201
|
});
|
|
174
|
-
try {
|
|
175
|
-
await txnMgr.runTransactionStep({
|
|
176
|
-
func: func_incomplete,
|
|
177
|
-
projectId,
|
|
178
|
-
txId: txId2,
|
|
179
|
-
start: true,
|
|
180
|
-
stepName: 'step2'
|
|
181
|
-
});
|
|
182
|
-
throw new Error('Expected start transaction to fail!');
|
|
183
|
-
} catch (e) {
|
|
184
|
-
console.log(e);
|
|
185
|
-
expect(e.code).toBe(txnMgr.Errors.PROJECT_TRANSACTION_ONGOING);
|
|
186
|
-
}
|
|
187
202
|
expect(txnMgr.getTransactionFromProject(projectId)).toEqual(txId);
|
|
188
203
|
expect(txnMgr.getProjectFromTransaction(txId)).toEqual(projectId);
|
|
189
204
|
expect(txnMgr.getProjectFromTransaction(txId2)).toBeUndefined();
|
|
205
|
+
expect(errorFn).toBeCalledTimes(1);
|
|
206
|
+
expect(
|
|
207
|
+
error.includes(`Project ${projectId} already has an ongoing transaction`)
|
|
208
|
+
).toBeTruthy();
|
|
190
209
|
});
|
|
191
210
|
|
|
192
211
|
test('Handle error in step function', async () => {
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
2
|
ToClientStatusResponseMessage,
|
|
3
|
-
buildToClientStatusResponseMessage,
|
|
4
3
|
keyMirrors
|
|
5
4
|
} from '@alwaysai/device-agent-schemas';
|
|
6
5
|
import { LiveUpdatesHandler } from './live-updates-handler';
|
|
@@ -39,7 +38,7 @@ export class TransactionManager {
|
|
|
39
38
|
if (this.detailsByTx[txId]) {
|
|
40
39
|
const txnDetails = this.detailsByTx[txId];
|
|
41
40
|
throw new CodedError(
|
|
42
|
-
`Transaction ${txId} already ongoing
|
|
41
|
+
`Transaction ${txId} already ongoing:\n${JSON.stringify(
|
|
43
42
|
txnDetails,
|
|
44
43
|
null,
|
|
45
44
|
2
|
|
@@ -52,7 +51,7 @@ export class TransactionManager {
|
|
|
52
51
|
if (this.detailsByProject[projectId]) {
|
|
53
52
|
const txnDetails = this.detailsByProject[projectId];
|
|
54
53
|
throw new CodedError(
|
|
55
|
-
`Project ${projectId} already has an ongoing transaction
|
|
54
|
+
`Project ${projectId} already has an ongoing transaction:\n${JSON.stringify(
|
|
56
55
|
txnDetails,
|
|
57
56
|
null,
|
|
58
57
|
2
|
|
@@ -126,16 +125,30 @@ export class TransactionManager {
|
|
|
126
125
|
errorFn,
|
|
127
126
|
successFn
|
|
128
127
|
} = props;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
128
|
+
try {
|
|
129
|
+
if (start) {
|
|
130
|
+
await this.startTransaction(
|
|
131
|
+
txId,
|
|
132
|
+
projectId,
|
|
133
|
+
liveUpdatesPublishFn,
|
|
134
|
+
stepName
|
|
135
|
+
);
|
|
136
|
+
} else {
|
|
137
|
+
this.updateTransaction(txId, stepName);
|
|
138
|
+
}
|
|
139
|
+
} catch (e) {
|
|
140
|
+
logger.error(
|
|
141
|
+
`Failed to start or update cmd for ${projectId}! Error:\n${stringifyError(
|
|
142
|
+
e
|
|
143
|
+
)}`
|
|
135
144
|
);
|
|
136
|
-
|
|
137
|
-
|
|
145
|
+
|
|
146
|
+
if (errorFn) {
|
|
147
|
+
errorFn(txId, e.message);
|
|
148
|
+
}
|
|
149
|
+
return;
|
|
138
150
|
}
|
|
151
|
+
|
|
139
152
|
try {
|
|
140
153
|
const completed = await func();
|
|
141
154
|
if (completed) {
|
|
@@ -146,13 +159,14 @@ export class TransactionManager {
|
|
|
146
159
|
}
|
|
147
160
|
} catch (e) {
|
|
148
161
|
logger.error(
|
|
149
|
-
`Failed to execute cmd for ${projectId}
|
|
162
|
+
`Failed to execute cmd for ${projectId}! Error:\n${stringifyError(e)}`
|
|
150
163
|
);
|
|
151
164
|
|
|
152
165
|
this.completeTransaction(txId);
|
|
153
166
|
if (errorFn) {
|
|
154
167
|
errorFn(txId, e.message);
|
|
155
168
|
}
|
|
169
|
+
return;
|
|
156
170
|
}
|
|
157
171
|
}
|
|
158
172
|
|
|
@@ -137,30 +137,40 @@ export async function getNetworkInfo() {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
export async function getDockerVersion() {
|
|
140
|
+
let result: string | undefined;
|
|
140
141
|
try {
|
|
141
142
|
const spawner = JsSpawner();
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
);
|
|
148
|
-
return String(result.ClientInfo.Version);
|
|
143
|
+
result = await spawner.run({
|
|
144
|
+
exe: 'docker',
|
|
145
|
+
args: ['info', '--format', 'json']
|
|
146
|
+
});
|
|
147
|
+
const parsedResult = JSON.parse(result);
|
|
148
|
+
return String(parsedResult.ClientInfo.Version);
|
|
149
149
|
} catch (e) {
|
|
150
|
-
logger.warn(
|
|
150
|
+
logger.warn(
|
|
151
|
+
`Cannot get Docker version! Result: ${result} Error:\n${stringifyError(
|
|
152
|
+
e
|
|
153
|
+
)}`
|
|
154
|
+
);
|
|
151
155
|
return 'Not found';
|
|
152
156
|
}
|
|
153
157
|
}
|
|
154
158
|
|
|
155
159
|
export async function getDockerComposeVersion() {
|
|
160
|
+
let result: string | undefined;
|
|
156
161
|
try {
|
|
157
162
|
const spawner = JsSpawner();
|
|
158
|
-
|
|
163
|
+
result = await spawner.run({
|
|
159
164
|
exe: 'docker',
|
|
160
165
|
args: ['compose', 'version']
|
|
161
166
|
});
|
|
167
|
+
return result;
|
|
162
168
|
} catch (e) {
|
|
163
|
-
logger.warn(
|
|
169
|
+
logger.warn(
|
|
170
|
+
`Cannot get Docker Compose version! Results: ${result} Error:\n${stringifyError(
|
|
171
|
+
e
|
|
172
|
+
)}`
|
|
173
|
+
);
|
|
164
174
|
return 'Not found';
|
|
165
175
|
}
|
|
166
176
|
}
|
|
@@ -173,7 +183,7 @@ export async function getNpmVersion() {
|
|
|
173
183
|
args: ['--version']
|
|
174
184
|
});
|
|
175
185
|
} catch (e) {
|
|
176
|
-
logger.warn(`Cannot get npm version
|
|
186
|
+
logger.warn(`Cannot get npm version! Error:\n${stringifyError(e)}`);
|
|
177
187
|
return 'Not found';
|
|
178
188
|
}
|
|
179
189
|
}
|
|
@@ -186,7 +196,7 @@ export async function getNodeVersion() {
|
|
|
186
196
|
args: ['-v']
|
|
187
197
|
});
|
|
188
198
|
} catch (e) {
|
|
189
|
-
logger.warn(`Cannot get Node version
|
|
199
|
+
logger.warn(`Cannot get Node version! Error:\n${stringifyError(e)}`);
|
|
190
200
|
return 'Not found';
|
|
191
201
|
}
|
|
192
202
|
}
|
|
@@ -230,7 +240,7 @@ export async function getLastBootTime() {
|
|
|
230
240
|
|
|
231
241
|
return String(new Date(`${tokens[2]} ${tokens[3]} ${tokens[4]}`));
|
|
232
242
|
} catch (e) {
|
|
233
|
-
logger.error(`Issue getting last boot time
|
|
243
|
+
logger.error(`Issue getting last boot time! Error:\n${stringifyError(e)}`);
|
|
234
244
|
return undefined;
|
|
235
245
|
}
|
|
236
246
|
}
|
|
@@ -19,7 +19,9 @@ export function importDockerCompose():
|
|
|
19
19
|
execSync('docker-compose -v').toString();
|
|
20
20
|
logger.warn('Using docker-compose V1. Please consider updating to V2.');
|
|
21
21
|
} catch (e) {
|
|
22
|
-
logger.warn(
|
|
22
|
+
logger.warn(
|
|
23
|
+
`Could not determine compose version! Error:\n${stringifyError(e)}`
|
|
24
|
+
);
|
|
23
25
|
}
|
|
24
26
|
}
|
|
25
27
|
dockerCmd = 'docker-compose';
|
package/src/environment.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import { TargetHardware } from 'alwaysai/lib/core/app';
|
|
1
2
|
import { platform } from 'os';
|
|
3
|
+
import { logger } from './util/logger';
|
|
2
4
|
|
|
3
5
|
export const ALWAYSAI_OS_PLATFORM = parseOsPlatform(
|
|
4
6
|
process.env.ALWAYSAI_OS_PLATFORM
|
|
@@ -22,6 +24,21 @@ export const ALWAYSAI_LOCAL_CONNECTION_PORT =
|
|
|
22
24
|
process.env.ALWAYSAI_LOCAL_CONNECTION_PORT;
|
|
23
25
|
export const ALWAYSAI_LOCAL_CONNECTION_ANALYTICS_ROUTING_KEY =
|
|
24
26
|
process.env.ALWAYSAI_LOCAL_CONNECTION_ANALYTICS_ROUTING_KEY;
|
|
27
|
+
export const ALWAYSAI_TARGET_HW_OVERRIDE = process.env.ALWAYSAI_TARGET_HW;
|
|
28
|
+
|
|
29
|
+
export function parseTargetHW(
|
|
30
|
+
hardwareType?: string
|
|
31
|
+
): TargetHardware | undefined {
|
|
32
|
+
const target = hardwareType ? TargetHardware[hardwareType] : undefined;
|
|
33
|
+
if (!target && hardwareType) {
|
|
34
|
+
logger.error(
|
|
35
|
+
`Incorrect TargetHardware: ${hardwareType}. Ignoring the environment variable. Allowed TargetHardware types: ${Object.keys(
|
|
36
|
+
TargetHardware
|
|
37
|
+
)}`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
return target;
|
|
41
|
+
}
|
|
25
42
|
|
|
26
43
|
function parseOsPlatform(str: string | undefined): NodeJS.Platform {
|
|
27
44
|
switch (str) {
|
package/src/index.ts
CHANGED
|
@@ -11,11 +11,30 @@ import { runDeviceAgentCloudInterface } from './cloud-connection/device-agent-cl
|
|
|
11
11
|
import { AgentConfigFile } from './infrastructure/agent-config';
|
|
12
12
|
import { ALWAYSAI_DEVICE_AGENT_MODE } from './environment';
|
|
13
13
|
import { checkForUpdatesAndPrompt } from './util/check-for-updates';
|
|
14
|
+
import { logger } from './util/logger';
|
|
15
|
+
import { getSystemId } from './infrastructure/system-id';
|
|
16
|
+
import { stringifyError } from 'alwaysai/lib/util';
|
|
14
17
|
|
|
15
18
|
if (module === require.main) {
|
|
16
19
|
if (!AgentConfigFile().exists()) {
|
|
17
20
|
AgentConfigFile().initialize();
|
|
18
21
|
}
|
|
22
|
+
logger.debug(`Starting Device Agent with system ID: ${getSystemId()}`);
|
|
23
|
+
|
|
24
|
+
// Catch any unhandled errors to prevent Device Agent crash
|
|
25
|
+
process.on('unhandledRejection', (reason: Error, p: Promise<any>) => {
|
|
26
|
+
logger.error(
|
|
27
|
+
`Unhandled Rejection at: ${JSON.stringify(p)} reason: ${stringifyError(
|
|
28
|
+
reason
|
|
29
|
+
)}`
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
process.on('uncaughtException', (error: Error) => {
|
|
33
|
+
logger.error(
|
|
34
|
+
`Caught exception: ${stringifyError(error)}\n` +
|
|
35
|
+
`Exception origin: ${error.stack}`
|
|
36
|
+
);
|
|
37
|
+
});
|
|
19
38
|
|
|
20
39
|
if (ALWAYSAI_DEVICE_AGENT_MODE === 'cloud') {
|
|
21
40
|
void runDeviceAgentCloudInterface();
|
|
@@ -14,7 +14,7 @@ export const validConfigMessage = 'Device config is present and valid.';
|
|
|
14
14
|
export async function checkDeviceConfigPresent(
|
|
15
15
|
baseDir?: string
|
|
16
16
|
): Promise<boolean> {
|
|
17
|
-
logger.debug('Checking for required device
|
|
17
|
+
logger.debug('Checking for required device configuration file.');
|
|
18
18
|
const deviceConfig = DeviceConfigFile(
|
|
19
19
|
baseDir
|
|
20
20
|
? join(baseDir, getDeviceConfigPath(), DEVICE_CONFIG_FILE_NAME)
|
|
@@ -48,7 +48,7 @@ export async function checkCertificatesPresent(baseDir?: string) {
|
|
|
48
48
|
export async function checkAaiConfigPresent(
|
|
49
49
|
baseDir?: string
|
|
50
50
|
): Promise<boolean> {
|
|
51
|
-
logger.debug('Checking for required
|
|
51
|
+
logger.debug('Checking for required alwaysAI configuration file.');
|
|
52
52
|
const localAaiCfg = new LocalAaiCfg(baseDir);
|
|
53
53
|
|
|
54
54
|
try {
|
|
@@ -17,7 +17,7 @@ import rimraf from 'rimraf';
|
|
|
17
17
|
import {
|
|
18
18
|
checkRabbitMQContainerRunning,
|
|
19
19
|
stopRabbitMQContainer
|
|
20
|
-
} from '../../local-connection/rabbitmq-
|
|
20
|
+
} from '../../local-connection/rabbitmq-container';
|
|
21
21
|
import { copyDir } from '../../util/copy-dir';
|
|
22
22
|
import {
|
|
23
23
|
getDeviceAgentConfigPath,
|
|
@@ -54,7 +54,7 @@ export async function migrateAgentConfigFile(baseDir?: string) {
|
|
|
54
54
|
try {
|
|
55
55
|
parsedAgentConfig = legacyAgentConfigFile.read();
|
|
56
56
|
} catch (e) {
|
|
57
|
-
logger.error(`Error reading agent config
|
|
57
|
+
logger.error(`Error reading agent config! Error:\n${stringifyError(e)}`);
|
|
58
58
|
logger.error(
|
|
59
59
|
`Agent Config Errors: ${JSON.stringify(
|
|
60
60
|
legacyAgentConfigFile.getErrors(),
|
|
@@ -73,7 +73,7 @@ export async function migrateAgentConfigFile(baseDir?: string) {
|
|
|
73
73
|
// we can remove it after migrating it
|
|
74
74
|
await rimraf(legacyAgentConfigFile.path);
|
|
75
75
|
} catch (e) {
|
|
76
|
-
logger.error(`Error writing agent config
|
|
76
|
+
logger.error(`Error writing agent config! Error:\n${stringifyError(e)}`);
|
|
77
77
|
logger.error(
|
|
78
78
|
`Agent Config Errors: ${JSON.stringify(
|
|
79
79
|
newAgentConfig.getErrors(),
|
|
@@ -199,17 +199,12 @@ export async function migrateFromLegacyCertsAndTokens(
|
|
|
199
199
|
await migrateAgentConfigFile(baseDir);
|
|
200
200
|
|
|
201
201
|
// if rabbitmq container is present, stop container and migrate files
|
|
202
|
-
|
|
203
|
-
if (await
|
|
204
|
-
|
|
202
|
+
if (await checkRabbitMQContainerRunning()) {
|
|
203
|
+
if ((await stopRabbitMQContainer()) === false) {
|
|
204
|
+
logger.error(
|
|
205
|
+
`You may need to manually stop the container by running docker-compose down in the following directory: ${getDeviceAgentDockerComposePath()}`
|
|
206
|
+
);
|
|
205
207
|
}
|
|
206
|
-
} catch (e) {
|
|
207
|
-
logger.error(
|
|
208
|
-
`You may need to manually stop the container by running docker-compose down in the following directory: ${getDeviceAgentDockerComposePath()}`
|
|
209
|
-
);
|
|
210
|
-
logger.debug(
|
|
211
|
-
`Error in checking / stopping RabbitMQ container!\n${stringifyError(e)}`
|
|
212
|
-
);
|
|
213
208
|
}
|
|
214
209
|
if (await exists(getDeviceAgentConfigPath(baseDir))) {
|
|
215
210
|
await copyDir({
|