testaro 29.1.1 → 30.0.0
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/README.md +38 -30
- package/call-old.js +86 -0
- package/call.js +21 -20
- package/dirWatch.js +1 -1
- package/package.json +1 -1
- package/procs/tellServer.js +1 -0
- package/run.js +2 -2
- package/test copy.js +38 -0
- package/watch-old.js +275 -0
- package/watch-temp.js +45 -0
- package/watch.js +291 -262
package/README.md
CHANGED
|
@@ -762,58 +762,67 @@ The `call` module will find the first job file with a matching name, execute the
|
|
|
762
762
|
|
|
763
763
|
#### Watch
|
|
764
764
|
|
|
765
|
-
In watch mode, Testaro periodically checks for a job to run
|
|
765
|
+
In watch mode, Testaro periodically checks for a job to run and, when a job is obtained, performs it.
|
|
766
766
|
|
|
767
|
-
#####
|
|
767
|
+
##### Directory watch
|
|
768
768
|
|
|
769
|
-
|
|
770
|
-
const {cycle} = require('./watch');
|
|
771
|
-
cycle(true, true, 30);
|
|
772
|
-
```
|
|
769
|
+
Testaro can watch for a job in a directory, with the `dirWatch` function, which can be executed by either a module or a user.
|
|
773
770
|
|
|
774
|
-
|
|
771
|
+
###### By a module
|
|
775
772
|
|
|
776
773
|
```javaScript
|
|
777
|
-
|
|
774
|
+
const {dirWatch} = require('./watch');
|
|
775
|
+
dirWatch(true, 300);
|
|
778
776
|
```
|
|
779
777
|
|
|
780
|
-
|
|
778
|
+
In this example, a module asks Testaro to check a directory for a job every 300 seconds, to perform the jobs in the directory if any are found, and then to continue checking. If the first argument is `false`, Testaro will stop checking after performing 1 job. If it is `true`, Testaro continues checking until the process is stopped.
|
|
781
779
|
|
|
782
|
-
The
|
|
783
|
-
- whether to watch a directory (`true`) or the network (`false`)
|
|
784
|
-
- whether to continue watching indefinitely after the first report (`true` or `false`)
|
|
785
|
-
- how many seconds to wait after finding no job before checking again (a nonnegative number)
|
|
786
|
-
- optionally, where the watched jobs are located
|
|
780
|
+
The directory where Testaro checks for jobs is specified by `process.env.JOBDIR`. Testaro checks for jobs in its `todo` subdirectory and, when it has performed a job, moves it into the `done` subdirectory.
|
|
787
781
|
|
|
788
|
-
|
|
782
|
+
Testaro creates a report for each job and saves the report in the directory specified by `process.env.REPORTDIR`.
|
|
783
|
+
|
|
784
|
+
###### By a user
|
|
785
|
+
|
|
786
|
+
A user can choose between two methods:
|
|
787
|
+
|
|
788
|
+
```javaScript
|
|
789
|
+
node call dirWatch true 300
|
|
790
|
+
```
|
|
789
791
|
|
|
790
|
-
|
|
792
|
+
```javaScript
|
|
793
|
+
node dirWatch true 300
|
|
794
|
+
```
|
|
791
795
|
|
|
792
|
-
|
|
796
|
+
The arguments and behaviors described above for execution by a module apply here, too.
|
|
793
797
|
|
|
794
|
-
|
|
798
|
+
The second, shorter method spawns a new watch subprocess after each job performance, to decrease the risk of process corruption involving bogus timeout messages from Playwright during jobs. That method requires you to enter `CTRL-c` to stop the watching.
|
|
795
799
|
|
|
796
800
|
##### Network watch
|
|
797
801
|
|
|
798
|
-
|
|
799
|
-
- A managing server may be able to give work to multiple workstations that run Testaro.
|
|
800
|
-
- A workstation running Testaro can contact a managing server, but the server may not be able to contact a workstation.
|
|
802
|
+
Testaro can poll servers for jobs to be performed.
|
|
801
803
|
|
|
802
|
-
|
|
804
|
+
An instance of Testaro is an _agent_ and has an identifier specified by `process.env.AGENT`. A Testaro instance identifies itself when polling servers, allowing servers to decide whether to give the instance a job to do.
|
|
803
805
|
|
|
804
|
-
|
|
806
|
+
The URLs polled by Testaro are specified by `process.env.JOB_URLS`. The format of that environment variable is a `+`-delimited list of URLs, including schemes. If one of the URLs is `https://testrunner.org/a11ytest/api/job`, and if a Testaro instance has the agent ID `tester3`, then a job request is a `GET` request to `https://testrunner.org/a11ytest/api/job?agent=tester3`.
|
|
805
807
|
|
|
806
|
-
|
|
808
|
+
Once a Testaro instance obtains a network job, the report is sent in a `POST` request to the URL specified by the `sources.sendReportTo` property of the job.
|
|
807
809
|
|
|
808
|
-
|
|
810
|
+
###### By a module
|
|
809
811
|
|
|
810
|
-
|
|
812
|
+
```javaScript
|
|
813
|
+
const {netWatch} = require('./watch');
|
|
814
|
+
netWatch(true, 300);
|
|
815
|
+
```
|
|
816
|
+
|
|
817
|
+
In this example, a module asks Testaro to check the servers for a job every 300 seconds, to perform any jobs obtained from the servers, and then to continue checking until the process is stopped. If the first argument is `false`, Testaro will stop checking after performing 1 job.
|
|
811
818
|
|
|
812
|
-
|
|
819
|
+
###### By a user
|
|
813
820
|
|
|
814
|
-
|
|
821
|
+
```javaScript
|
|
822
|
+
node call netWatch true 300
|
|
823
|
+
```
|
|
815
824
|
|
|
816
|
-
|
|
825
|
+
The arguments and behaviors described above for execution by a module apply here, too. If the first argument is `true`, you can terminate the process by entering `CTRL-c`.
|
|
817
826
|
|
|
818
827
|
### Environment variables
|
|
819
828
|
|
|
@@ -824,7 +833,6 @@ Before making Testaro run a job, you can optionally also set `process.env.DEBUG`
|
|
|
824
833
|
You may store environment variables in an untracked `.env` file if you wish, and Testaro will recognize them. Here is a template for a `.env` file:
|
|
825
834
|
|
|
826
835
|
```conf
|
|
827
|
-
URL_INJECT=yes
|
|
828
836
|
WAVE_KEY=yourwavekey
|
|
829
837
|
JOB_URLs=https://yourserver.tld/job+http://localhost:3004/testapp
|
|
830
838
|
JOBDIR=../testing/jobs/ThisWorkstation
|
package/call-old.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/*
|
|
2
|
+
call.js
|
|
3
|
+
Invokes Testaro modules with arguments.
|
|
4
|
+
This is the universal module for use of Testaro from a command line.
|
|
5
|
+
Arguments:
|
|
6
|
+
0. function to execute.
|
|
7
|
+
1+. arguments to pass to the function.
|
|
8
|
+
Usage examples:
|
|
9
|
+
node call run ts25
|
|
10
|
+
node call watch true true 30
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// ########## IMPORTS
|
|
14
|
+
|
|
15
|
+
// Module to keep secrets.
|
|
16
|
+
require('dotenv').config();
|
|
17
|
+
// Module to process files.
|
|
18
|
+
const fs = require('fs/promises');
|
|
19
|
+
// Function to process a testing request.
|
|
20
|
+
const {doJob} = require('./run');
|
|
21
|
+
// Function to watch for jobs.
|
|
22
|
+
const {watch} = require('./watch');
|
|
23
|
+
|
|
24
|
+
// ########## CONSTANTS
|
|
25
|
+
|
|
26
|
+
const fn = process.argv[2];
|
|
27
|
+
const fnArgs = process.argv.slice(3);
|
|
28
|
+
const jobDir = process.env.JOBDIR;
|
|
29
|
+
const todoDir = `${jobDir}/todo`;
|
|
30
|
+
const reportDir = process.env.REPORTDIR;
|
|
31
|
+
const rawDir = `${reportDir}/raw`;
|
|
32
|
+
|
|
33
|
+
// ########## FUNCTIONS
|
|
34
|
+
|
|
35
|
+
// Fulfills a testing request.
|
|
36
|
+
const callRun = async jobIDStart => {
|
|
37
|
+
// Find the job.
|
|
38
|
+
const jobDirFileNames = await fs.readdir(todoDir);
|
|
39
|
+
const jobFileName = jobDirFileNames.find(fileName => fileName.startsWith(jobIDStart));
|
|
40
|
+
// If it exists:
|
|
41
|
+
if (jobFileName) {
|
|
42
|
+
// Get it.
|
|
43
|
+
const jobJSON = await fs.readFile(`${todoDir}/${jobFileName}`, 'utf8');
|
|
44
|
+
const report = JSON.parse(jobJSON);
|
|
45
|
+
// Run it.
|
|
46
|
+
await doJob(report);
|
|
47
|
+
// Archive it.
|
|
48
|
+
await fs.rename(`${todoDir}/${jobFileName}`, `${jobDir}/done/${jobFileName}`);
|
|
49
|
+
// Save the report.
|
|
50
|
+
await fs.writeFile(`${rawDir}/${jobFileName}`, JSON.stringify(report, null, 2));
|
|
51
|
+
console.log(`Job completed and report ${report.id}.json saved in ${rawDir}`);
|
|
52
|
+
}
|
|
53
|
+
// Otherwise, i.e. if the job does not exist.
|
|
54
|
+
else {
|
|
55
|
+
// Report the error.
|
|
56
|
+
console.log(`ERROR: No to-do job ID starts with ${jobIDStart}`);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
// Starts a watch.
|
|
60
|
+
const callWatch = async (isDirWatch, interval) => {
|
|
61
|
+
const whenType = interval > -1 ? 'repeating' : 'one-time';
|
|
62
|
+
const whereType = isDirWatch === 'true' ? 'directory' : 'network';
|
|
63
|
+
console.log(`Starting ${whenType} ${whereType} watch`);
|
|
64
|
+
await watch(isDirWatch === 'true', Number.parseInt(interval, 10));
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// ########## OPERATION
|
|
68
|
+
|
|
69
|
+
// Execute the requested function.
|
|
70
|
+
if (fn === 'run' && fnArgs.length === 1) {
|
|
71
|
+
callRun(fnArgs)
|
|
72
|
+
.then(() => {
|
|
73
|
+
console.log('Execution completed\n');
|
|
74
|
+
process.exit(0);
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
else if (fn === 'watch' && fnArgs.length === 2) {
|
|
78
|
+
callWatch(... fnArgs)
|
|
79
|
+
.then(() => {
|
|
80
|
+
console.log('Execution completed\n');
|
|
81
|
+
process.exit(0);
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
console.log('ERROR: Invalid statement');
|
|
86
|
+
}
|
package/call.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
|
+
|
|
1
2
|
/*
|
|
2
3
|
call.js
|
|
3
|
-
Invokes Testaro modules
|
|
4
|
+
Invokes Testaro modules.
|
|
4
5
|
This is the universal module for use of Testaro from a command line.
|
|
5
6
|
Arguments:
|
|
6
7
|
0. function to execute.
|
|
7
8
|
1+. arguments to pass to the function.
|
|
8
9
|
Usage examples:
|
|
9
|
-
node call run
|
|
10
|
-
node call
|
|
10
|
+
node call run ts99
|
|
11
|
+
node call dirWatch true 30
|
|
11
12
|
*/
|
|
12
13
|
|
|
13
|
-
//
|
|
14
|
+
// IMPORTS
|
|
14
15
|
|
|
15
16
|
// Module to keep secrets.
|
|
16
17
|
require('dotenv').config();
|
|
@@ -19,9 +20,9 @@ const fs = require('fs/promises');
|
|
|
19
20
|
// Function to process a testing request.
|
|
20
21
|
const {doJob} = require('./run');
|
|
21
22
|
// Function to watch for jobs.
|
|
22
|
-
const {
|
|
23
|
+
const {dirWatch, netWatch} = require('./watch');
|
|
23
24
|
|
|
24
|
-
//
|
|
25
|
+
// CONSTANTS
|
|
25
26
|
|
|
26
27
|
const fn = process.argv[2];
|
|
27
28
|
const fnArgs = process.argv.slice(3);
|
|
@@ -30,7 +31,7 @@ const todoDir = `${jobDir}/todo`;
|
|
|
30
31
|
const reportDir = process.env.REPORTDIR;
|
|
31
32
|
const rawDir = `${reportDir}/raw`;
|
|
32
33
|
|
|
33
|
-
//
|
|
34
|
+
// FUNCTIONS
|
|
34
35
|
|
|
35
36
|
// Fulfills a testing request.
|
|
36
37
|
const callRun = async jobIDStart => {
|
|
@@ -56,15 +57,16 @@ const callRun = async jobIDStart => {
|
|
|
56
57
|
console.log(`ERROR: No to-do job ID starts with ${jobIDStart}`);
|
|
57
58
|
}
|
|
58
59
|
};
|
|
59
|
-
// Starts a watch.
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
60
|
+
// Starts a directory watch, converting the interval argument to a number.
|
|
61
|
+
const callDirWatch = async (isForever, interval) => {
|
|
62
|
+
await dirWatch(isForever === 'true', Math.max(5, Number.parseInt(interval, 10)));
|
|
63
|
+
};
|
|
64
|
+
// Starts a network watch, converting the interval argument to a number.
|
|
65
|
+
const callNetWatch = async(isForever, interval) => {
|
|
66
|
+
netWatch(isForever === 'true', Number.parseInt(interval, 10));
|
|
65
67
|
};
|
|
66
68
|
|
|
67
|
-
//
|
|
69
|
+
// OPERATION
|
|
68
70
|
|
|
69
71
|
// Execute the requested function.
|
|
70
72
|
if (fn === 'run' && fnArgs.length === 1) {
|
|
@@ -74,12 +76,11 @@ if (fn === 'run' && fnArgs.length === 1) {
|
|
|
74
76
|
process.exit(0);
|
|
75
77
|
});
|
|
76
78
|
}
|
|
77
|
-
else if (fn === '
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
});
|
|
79
|
+
else if (fn === 'dirWatch' && fnArgs.length === 2) {
|
|
80
|
+
callDirWatch(... fnArgs);
|
|
81
|
+
}
|
|
82
|
+
else if (fn === 'netWatch' && fnArgs.length === 2) {
|
|
83
|
+
callNetWatch(... fnArgs);
|
|
83
84
|
}
|
|
84
85
|
else {
|
|
85
86
|
console.log('ERROR: Invalid statement');
|
package/dirWatch.js
CHANGED
|
@@ -18,7 +18,7 @@ const interval = process.argv[2];
|
|
|
18
18
|
const spawnWatch = (command, args) => spawn(command, args, {stdio: ['inherit', 'inherit', 'pipe']});
|
|
19
19
|
// Repeatedly spawns a one-time directory watch.
|
|
20
20
|
const reWatch = () => {
|
|
21
|
-
const watcher = spawnWatch('node', ['call', 'watch', '
|
|
21
|
+
const watcher = spawnWatch('node', ['call', 'watch', 'false', interval]);
|
|
22
22
|
let error = '';
|
|
23
23
|
watcher.stderr.on('data', data => {
|
|
24
24
|
error += data.toString();
|
package/package.json
CHANGED
package/procs/tellServer.js
CHANGED
|
@@ -11,6 +11,7 @@ const agent = process.env.AGENT;
|
|
|
11
11
|
|
|
12
12
|
// FUNCTIONS
|
|
13
13
|
|
|
14
|
+
// Send a notification to an observer.
|
|
14
15
|
exports.tellServer = (report, messageParams, logMessage) => {
|
|
15
16
|
const observer = report.sources.sendReportTo.replace(/report$/, 'granular');
|
|
16
17
|
const whoParams = `agent=${agent}&jobID=${report.id || ''}`;
|
package/run.js
CHANGED
|
@@ -464,7 +464,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
464
464
|
}
|
|
465
465
|
// If granular reporting has been specified:
|
|
466
466
|
if (report.observe) {
|
|
467
|
-
// Notify the
|
|
467
|
+
// Notify the observer of the act.
|
|
468
468
|
const whichParam = act.which ? `&which=${act.which}` : '';
|
|
469
469
|
const messageParams = `act=${act.type}${whichParam}`;
|
|
470
470
|
tellServer(report, messageParams, `>>>> ${act.type}: ${actInfo}`);
|
|
@@ -744,7 +744,7 @@ const doActs = async (report, actIndex, page) => {
|
|
|
744
744
|
// Remove it, except any important property.
|
|
745
745
|
if (act.result.important) {
|
|
746
746
|
act.data = act.result.important;
|
|
747
|
-
console.log(
|
|
747
|
+
console.log(`>>>>>> Important ${act.which} data included in report`);
|
|
748
748
|
}
|
|
749
749
|
delete act.result;
|
|
750
750
|
}
|
package/test copy.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/*
|
|
2
|
+
watch.js
|
|
3
|
+
Module for watching for a job and running it when found.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ########## IMPORTS
|
|
7
|
+
|
|
8
|
+
const httpClient = require('http');
|
|
9
|
+
|
|
10
|
+
// ########## FUNCTIONS
|
|
11
|
+
|
|
12
|
+
// Waits.
|
|
13
|
+
const wait = ms => {
|
|
14
|
+
return new Promise(resolve => {
|
|
15
|
+
setTimeout(() => {
|
|
16
|
+
resolve('');
|
|
17
|
+
}, ms);
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
// Checks servers for a network job.
|
|
21
|
+
const checkNetJob = () => {
|
|
22
|
+
const request = httpClient.request('http://localhost:3008/testu', response => {
|
|
23
|
+
const chunks = [];
|
|
24
|
+
response.on('data', chunk => {
|
|
25
|
+
chunks.push(chunk);
|
|
26
|
+
})
|
|
27
|
+
// When response arrives:
|
|
28
|
+
.on('end', () => {
|
|
29
|
+
// Report it.
|
|
30
|
+
const content = chunks.join('');
|
|
31
|
+
console.log(content);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
request.end();
|
|
35
|
+
};
|
|
36
|
+
exports.watch = () => {
|
|
37
|
+
checkNetJob();
|
|
38
|
+
}
|
package/watch-old.js
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/*
|
|
2
|
+
watch.js
|
|
3
|
+
Module for watching for a job and running it when found.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ########## IMPORTS
|
|
7
|
+
|
|
8
|
+
// Module to keep secrets local.
|
|
9
|
+
require('dotenv').config();
|
|
10
|
+
// Module to read and write files.
|
|
11
|
+
const fs = require('fs/promises');
|
|
12
|
+
// Module to perform tests.
|
|
13
|
+
const {doJob} = require('./run');
|
|
14
|
+
// HTTP and HTTPS clients.
|
|
15
|
+
const httpClient = require('http');
|
|
16
|
+
const httpsClient = require('https');
|
|
17
|
+
|
|
18
|
+
// ########## CONSTANTS
|
|
19
|
+
|
|
20
|
+
const jobURLs = process.env.JOB_URLs;
|
|
21
|
+
const agent = process.env.AGENT;
|
|
22
|
+
const jobDir = process.env.JOBDIR;
|
|
23
|
+
const reportDir = process.env.REPORTDIR;
|
|
24
|
+
// Get a randomized array of servers to watch from the environment.
|
|
25
|
+
const servers = jobURLs
|
|
26
|
+
.split('+')
|
|
27
|
+
.map(url => [Math.random(), url])
|
|
28
|
+
.sort((a, b) => a[0] - b[0])
|
|
29
|
+
.map(pair => pair[1]);
|
|
30
|
+
|
|
31
|
+
// ########## FUNCTIONS
|
|
32
|
+
|
|
33
|
+
// Returns a string representing the date and time.
|
|
34
|
+
const nowString = () => (new Date()).toISOString().slice(0, 19);
|
|
35
|
+
// Writes a directory report.
|
|
36
|
+
const writeDirReport = async report => {
|
|
37
|
+
const jobID = report && report.id;
|
|
38
|
+
if (jobID) {
|
|
39
|
+
try {
|
|
40
|
+
const reportJSON = JSON.stringify(report, null, 2);
|
|
41
|
+
const reportName = `${jobID}.json`;
|
|
42
|
+
const rawDir = `${reportDir}/raw`;
|
|
43
|
+
await fs.mkdir(rawDir, {recursive: true});
|
|
44
|
+
await fs.writeFile(`${rawDir}/${reportName}`, reportJSON);
|
|
45
|
+
console.log(`Report ${reportName} saved in ${rawDir}`);
|
|
46
|
+
}
|
|
47
|
+
catch(error) {
|
|
48
|
+
console.log(`ERROR: Failed to write report ${jobID} in ${rawDir} (${error.message})`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
console.log('ERROR: Job has no ID');
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
// Waits.
|
|
56
|
+
const wait = ms => {
|
|
57
|
+
return new Promise(resolve => {
|
|
58
|
+
setTimeout(() => {
|
|
59
|
+
resolve('');
|
|
60
|
+
}, ms);
|
|
61
|
+
});
|
|
62
|
+
};
|
|
63
|
+
// Archives a job.
|
|
64
|
+
const archiveJob = async job => {
|
|
65
|
+
const {id} = job;
|
|
66
|
+
const jobJSON = JSON.stringify(job, null, 2);
|
|
67
|
+
await fs.writeFile(`${jobDir}/done/${id}.json`, jobJSON);
|
|
68
|
+
await fs.rm(`${jobDir}/todo/${id}.json`);
|
|
69
|
+
};
|
|
70
|
+
// Checks for a directory job and, if found, performs and reports it, once or repeatedly.
|
|
71
|
+
const checkDirJob = async (interval) => {
|
|
72
|
+
try {
|
|
73
|
+
// If there are any jobs to do in the watched directory:
|
|
74
|
+
const toDoFileNames = await fs.readdir(`${jobDir}/todo`);
|
|
75
|
+
const jobFileNames = toDoFileNames.filter(fileName => fileName.endsWith('.json'));
|
|
76
|
+
if (jobFileNames.length) {
|
|
77
|
+
// Get the first one.
|
|
78
|
+
const jobJSON = await fs.readFile(`${jobDir}/todo/${jobFileNames[0]}`, 'utf8');
|
|
79
|
+
try {
|
|
80
|
+
const job = JSON.parse(jobJSON, null, 2);
|
|
81
|
+
const {id} = job;
|
|
82
|
+
// Perform it.
|
|
83
|
+
console.log(`Directory job ${id} found (${nowString()})`);
|
|
84
|
+
await doJob(job);
|
|
85
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
86
|
+
// Report it.
|
|
87
|
+
await writeDirReport(job);
|
|
88
|
+
// Archive it.
|
|
89
|
+
await archiveJob(job);
|
|
90
|
+
console.log(`Job ${id} archived in ${jobDir} (${nowString()})`);
|
|
91
|
+
// If watching is repetitive:
|
|
92
|
+
if (interval > -1) {
|
|
93
|
+
// Wait for the specified interval.
|
|
94
|
+
await wait(1000 * interval);
|
|
95
|
+
// Check the servers again.
|
|
96
|
+
checkDirJob(interval);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch(error) {
|
|
100
|
+
console.log(`ERROR processing directory job (${error.message})`);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
// Otherwise, i.e. if there are no more jobs to do in the watched directory:
|
|
104
|
+
else {
|
|
105
|
+
console.log(`No job to do in ${jobDir} (${nowString()})`);
|
|
106
|
+
// If checking is repetitive:
|
|
107
|
+
if (interval > -1) {
|
|
108
|
+
// Wait for the specified interval.
|
|
109
|
+
await wait(1000 * interval);
|
|
110
|
+
// Check the directory again.
|
|
111
|
+
await checkDirJob(interval);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
catch(error) {
|
|
116
|
+
console.log(`ERROR: Directory watching failed (${error.message})`);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
// Checks servers for a job and, if obtained, performs and reports it, once or repeatedly.
|
|
120
|
+
const checkNetJob = async (serverIndex, interval) => {
|
|
121
|
+
// If any servers remain to be checked:
|
|
122
|
+
if (serverIndex < servers.length) {
|
|
123
|
+
// Request a job from the indexed server.
|
|
124
|
+
const server = servers[serverIndex];
|
|
125
|
+
const logStart = `Asked ${server} for a job and got `;
|
|
126
|
+
const wholeURL = `${server}?agent=${agent}`;
|
|
127
|
+
let client = server.startsWith('https://') ? httpsClient : httpClient;
|
|
128
|
+
console.log(`About to make request to ${wholeURL}`);
|
|
129
|
+
httpClient.request(
|
|
130
|
+
'http://localhost:8000', response => console.log(JSON.stringify(response, null, 2))
|
|
131
|
+
).end();
|
|
132
|
+
/*
|
|
133
|
+
const jobRequest = client.request(wholeURL, {timeout: 1000}, response => {
|
|
134
|
+
console.log('Request made');
|
|
135
|
+
console.log(`Status: ${response.statusCode}`);
|
|
136
|
+
const chunks = [];
|
|
137
|
+
response.on('data', chunk => {
|
|
138
|
+
chunks.push(chunk);
|
|
139
|
+
})
|
|
140
|
+
// When the response is completed:
|
|
141
|
+
.on('end', async () => {
|
|
142
|
+
// If the response was JSON-formatted:
|
|
143
|
+
const responseJSON = chunks.join('');
|
|
144
|
+
try {
|
|
145
|
+
const responseObj = JSON.parse(responseJSON);
|
|
146
|
+
// If the server sent a valid job:
|
|
147
|
+
const {id, sources} = responseObj;
|
|
148
|
+
if (id && sources) {
|
|
149
|
+
const {sendReportTo} = sources;
|
|
150
|
+
if (sendReportTo) {
|
|
151
|
+
console.log(`Network job ${id} received from ${server} (${nowString()})`);
|
|
152
|
+
// Perform it.
|
|
153
|
+
await doJob(responseObj);
|
|
154
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
155
|
+
// Send the report to the server and report its response.
|
|
156
|
+
console.log(`Sending report to ${sendReportTo}`);
|
|
157
|
+
client = sendReportTo.startsWith('https://') ? httpsClient : httpClient;
|
|
158
|
+
const request = client.request(sendReportTo, {method: 'POST'}, response => {
|
|
159
|
+
const chunks = [];
|
|
160
|
+
response.on('data', chunk => {
|
|
161
|
+
chunks.push(chunk);
|
|
162
|
+
});
|
|
163
|
+
// When the response arrives:
|
|
164
|
+
response.on('end', async () => {
|
|
165
|
+
const content = chunks.join('');
|
|
166
|
+
const logStart = `Sent report to ${sendReportTo} and got `;
|
|
167
|
+
try {
|
|
168
|
+
const ack = JSON.parse(content);
|
|
169
|
+
console.log(`${logStart}${ack}`);
|
|
170
|
+
// Archive the job.
|
|
171
|
+
await archiveJob(responseObj);
|
|
172
|
+
console.log(`Job ${id} archived (${nowString()})`);
|
|
173
|
+
// If watching is repetitive:
|
|
174
|
+
if (interval > -1) {
|
|
175
|
+
// Wait for the specified interval.
|
|
176
|
+
await wait(1000 * interval);
|
|
177
|
+
// Check the next server.
|
|
178
|
+
await checkNetJob(serverIndex + 1, interval);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
catch(error) {
|
|
182
|
+
console.log(
|
|
183
|
+
`ERROR: ${logStart}status ${response.statusCode}, error message ${error.message}, and body ${content.slice(0,1000)}`
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
})
|
|
188
|
+
.on('error', error => {
|
|
189
|
+
console.log(`ERROR submitting job report (${error.message})`);
|
|
190
|
+
});
|
|
191
|
+
report.jobData.agent = agent;
|
|
192
|
+
const reportJSON = JSON.stringify(report, null, 2);
|
|
193
|
+
request.end(reportJSON);
|
|
194
|
+
}
|
|
195
|
+
// Otherwise, if the server sent a job without a report destination:
|
|
196
|
+
else {
|
|
197
|
+
// Report this.
|
|
198
|
+
console.log(`ERROR: ${logStart}a job with no report destination`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
// Otherwise, if the server sent a message instead of a job:
|
|
202
|
+
else if (responseObj.message) {
|
|
203
|
+
// Report it.
|
|
204
|
+
console.log(`${logStart}${responseObj.message}`);
|
|
205
|
+
}
|
|
206
|
+
// Otherwise, if the server sent any other JSON response:
|
|
207
|
+
else {
|
|
208
|
+
// Report it.
|
|
209
|
+
console.log(`${logStart} ${JSON.stringify(responseObj, null, 2)}`);
|
|
210
|
+
// Check the next server.
|
|
211
|
+
await checkNetJob(serverIndex + 1, interval);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch(error) {
|
|
215
|
+
// Report any error.
|
|
216
|
+
console.log(
|
|
217
|
+
`${logStart}status ${response.statusCode} and reply ${responseJSON.slice(0, 1000)}`
|
|
218
|
+
);
|
|
219
|
+
// Check the next server.
|
|
220
|
+
await checkNetJob(serverIndex + 1, interval);
|
|
221
|
+
}
|
|
222
|
+
})
|
|
223
|
+
// If the response throws an error:
|
|
224
|
+
.on('error', async error => {
|
|
225
|
+
// Report it.
|
|
226
|
+
console.log(
|
|
227
|
+
`${logStart}status code ${response.statusCode} and error message ${error.message}`
|
|
228
|
+
);
|
|
229
|
+
// Check the next server.
|
|
230
|
+
await checkNetJob(serverIndex + 1, interval);
|
|
231
|
+
});
|
|
232
|
+
})
|
|
233
|
+
// If the request throws an error:
|
|
234
|
+
.on('error', async error => {
|
|
235
|
+
// Report it.
|
|
236
|
+
console.log(`${logStart} error ${error.message}`);
|
|
237
|
+
// Check the next server.
|
|
238
|
+
await checkNetJob(serverIndex + 1, interval);
|
|
239
|
+
})
|
|
240
|
+
// If the request times out:
|
|
241
|
+
.on('timeout', async () => {
|
|
242
|
+
// Report this.
|
|
243
|
+
console.log(`${logStart} a timeout`);
|
|
244
|
+
// Check the next server.
|
|
245
|
+
await checkNetJob(serverIndex + 1, interval);
|
|
246
|
+
});
|
|
247
|
+
// Close the request.
|
|
248
|
+
jobRequest.end();
|
|
249
|
+
console.log(`Request to ${server} ended`);
|
|
250
|
+
}
|
|
251
|
+
// Otherwise, i.e. if no servers remain to be checked:
|
|
252
|
+
else {
|
|
253
|
+
// If checking is repetitive:
|
|
254
|
+
if (interval > -1) {
|
|
255
|
+
// Wait for the specified interval.
|
|
256
|
+
await wait(1000 * interval);
|
|
257
|
+
// Check the servers again.
|
|
258
|
+
await checkNetJob(0, interval);
|
|
259
|
+
}
|
|
260
|
+
*/
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
// Checks for a job, performs it, and submits a report, once or repeatedly.
|
|
264
|
+
exports.watch = async (isDirWatch, interval = 300) => {
|
|
265
|
+
const intervalSpec = interval > -1 ? `repeatedly, with ${interval}-second intervals ` : '';
|
|
266
|
+
console.log(`Watching started ${intervalSpec}(${nowString()})\n`);
|
|
267
|
+
// Start the checking.
|
|
268
|
+
if (isDirWatch) {
|
|
269
|
+
await checkDirJob(interval);
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
await checkNetJob(0, interval);
|
|
273
|
+
}
|
|
274
|
+
console.log(`Watching ended (${nowString()})`);
|
|
275
|
+
};
|
package/watch-temp.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/*
|
|
2
|
+
watch.js
|
|
3
|
+
Module for watching for a job and running it when found.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// ########## IMPORTS
|
|
7
|
+
|
|
8
|
+
const httpClient = require('http');
|
|
9
|
+
|
|
10
|
+
// ########## FUNCTIONS
|
|
11
|
+
|
|
12
|
+
// Returns a string representing the date and time.
|
|
13
|
+
const nowString = () => (new Date()).toISOString().slice(0, 19);
|
|
14
|
+
// Checks servers for a network job.
|
|
15
|
+
const checkNetJob = (serverIndex, interval) => {
|
|
16
|
+
const request = httpClient.request('http://localhost:3008/testu', response => {
|
|
17
|
+
console.log('Response fn being defined');
|
|
18
|
+
const chunks = [];
|
|
19
|
+
response.on('data', chunk => {
|
|
20
|
+
chunks.push(chunk);
|
|
21
|
+
})
|
|
22
|
+
// When response arrives:
|
|
23
|
+
.on('end', () => {
|
|
24
|
+
// Report it.
|
|
25
|
+
const content = chunks.join('');
|
|
26
|
+
console.log(content);
|
|
27
|
+
console.log(`Watching ended (${nowString()})`);
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
console.log('About to close request');
|
|
31
|
+
request.end();
|
|
32
|
+
console.log('Closed request');
|
|
33
|
+
};
|
|
34
|
+
// Checks for a job, performs it, and submits a report, once or repeatedly.
|
|
35
|
+
exports.watch = async (isDirWatch, interval = 300) => {
|
|
36
|
+
const intervalSpec = interval > -1 ? `repeatedly, with ${interval}-second intervals ` : '';
|
|
37
|
+
console.log(`Watching started ${intervalSpec}(${nowString()})\n`);
|
|
38
|
+
// Start the checking.
|
|
39
|
+
if (isDirWatch) {
|
|
40
|
+
await checkDirJob(interval);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
checkNetJob(0, interval);
|
|
44
|
+
}
|
|
45
|
+
};
|
package/watch.js
CHANGED
|
@@ -5,146 +5,39 @@
|
|
|
5
5
|
|
|
6
6
|
// ########## IMPORTS
|
|
7
7
|
|
|
8
|
-
// Module to keep secrets
|
|
8
|
+
// Module to keep secrets.
|
|
9
9
|
require('dotenv').config();
|
|
10
10
|
// Module to read and write files.
|
|
11
11
|
const fs = require('fs/promises');
|
|
12
|
-
// Module to
|
|
12
|
+
// Module to make requests to servers.
|
|
13
|
+
const httpClient = require('http');
|
|
14
|
+
const httpsClient = require('https');
|
|
15
|
+
// Module to perform jobs.
|
|
13
16
|
const {doJob} = require('./run');
|
|
14
|
-
// HTTP and HTTPS clients.
|
|
15
|
-
const http = require('http');
|
|
16
|
-
const https = require('https');
|
|
17
17
|
|
|
18
|
-
//
|
|
18
|
+
// CONSTANTS
|
|
19
19
|
|
|
20
|
-
const httpClient = require('http');
|
|
21
|
-
const httpsClient = require('https');
|
|
22
|
-
const jobURLs = process.env.JOB_URLs;
|
|
23
|
-
const agent = process.env.AGENT;
|
|
24
20
|
const jobDir = process.env.JOBDIR;
|
|
21
|
+
const jobURLs = process.env.JOB_URLS;
|
|
25
22
|
const reportDir = process.env.REPORTDIR;
|
|
23
|
+
const agent = process.env.AGENT;
|
|
26
24
|
|
|
27
25
|
// ########## FUNCTIONS
|
|
28
26
|
|
|
29
27
|
// Returns a string representing the date and time.
|
|
30
28
|
const nowString = () => (new Date()).toISOString().slice(0, 19);
|
|
31
|
-
//
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
const jobJSON = await fs.readFile(`${watchJobDir}/todo/${jobFileNames[0]}`, 'utf8');
|
|
39
|
-
try {
|
|
40
|
-
const job = JSON.parse(jobJSON, null, 2);
|
|
41
|
-
return job;
|
|
42
|
-
}
|
|
43
|
-
catch(error) {
|
|
44
|
-
return {
|
|
45
|
-
error: `ERROR parsing job as JSON (${error.message})`,
|
|
46
|
-
message: error.message
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
else {
|
|
51
|
-
console.log(`No job to do (${nowString()})`);
|
|
52
|
-
return {};
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
catch(error) {
|
|
56
|
-
console.log('ERROR: Directory watching failed');
|
|
57
|
-
return {};
|
|
58
|
-
}
|
|
29
|
+
// Waits.
|
|
30
|
+
const wait = ms => {
|
|
31
|
+
return new Promise(resolve => {
|
|
32
|
+
setTimeout(() => {
|
|
33
|
+
resolve('');
|
|
34
|
+
}, ms);
|
|
35
|
+
});
|
|
59
36
|
};
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
watchJobURLs = jobURLs
|
|
65
|
-
.split('+')
|
|
66
|
-
.map(url => [Math.random(), url])
|
|
67
|
-
.sort((a, b) => a[0] - b[0])
|
|
68
|
-
.map(pair => pair[1]);
|
|
69
|
-
}
|
|
70
|
-
// For each watchee:
|
|
71
|
-
for (const watchJobURL of watchJobURLs) {
|
|
72
|
-
const job = await new Promise(resolve => {
|
|
73
|
-
// Request a job from it.
|
|
74
|
-
const wholeURL = `${watchJobURL}?agent=${agent}`;
|
|
75
|
-
const client = wholeURL.startsWith('https://') ? httpsClient : httpClient;
|
|
76
|
-
const request = client.request(wholeURL, {timeout: 1000}, response => {
|
|
77
|
-
const chunks = [];
|
|
78
|
-
response.on('data', chunk => {
|
|
79
|
-
chunks.push(chunk);
|
|
80
|
-
})
|
|
81
|
-
// When response arrives:
|
|
82
|
-
.on('end', () => {
|
|
83
|
-
// If the response was JSON-formatted:
|
|
84
|
-
try {
|
|
85
|
-
const jobJSON = chunks.join('');
|
|
86
|
-
const job = JSON.parse(jobJSON);
|
|
87
|
-
// Make it the response of the watchee.
|
|
88
|
-
return resolve(job);
|
|
89
|
-
}
|
|
90
|
-
// Otherwise, i.e. if the response was not JSON-formatted:
|
|
91
|
-
catch(error) {
|
|
92
|
-
// Make an error report the response of the watchee.
|
|
93
|
-
const errorMessage = `ERROR: Response of ${watchJobURL} was not JSON`;
|
|
94
|
-
console.log(errorMessage);
|
|
95
|
-
return resolve({
|
|
96
|
-
error: errorMessage,
|
|
97
|
-
message: error.message,
|
|
98
|
-
status: response.statusCode
|
|
99
|
-
});
|
|
100
|
-
}
|
|
101
|
-
})
|
|
102
|
-
.on('error', error => {
|
|
103
|
-
return resolve({
|
|
104
|
-
error: 'ERROR getting network job',
|
|
105
|
-
message: error.message,
|
|
106
|
-
status: response.statusCode
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
// If the check threw an error:
|
|
111
|
-
request.on('error', error => {
|
|
112
|
-
// Make an error report the response of the watchee.
|
|
113
|
-
const errorMessage = `ERROR checking ${watchJobURL} for a network job`;
|
|
114
|
-
console.log(`${errorMessage} (${error.message})`);
|
|
115
|
-
return resolve({
|
|
116
|
-
error: errorMessage,
|
|
117
|
-
message: error.message
|
|
118
|
-
});
|
|
119
|
-
})
|
|
120
|
-
.on('timeout', () => {
|
|
121
|
-
const errorMessage = `ERROR: Request to ${watchJobURL} timed out`;
|
|
122
|
-
console.log(errorMessage);
|
|
123
|
-
return resolve({
|
|
124
|
-
error: errorMessage
|
|
125
|
-
});
|
|
126
|
-
});
|
|
127
|
-
request.end();
|
|
128
|
-
});
|
|
129
|
-
// If the watchee sent a job:
|
|
130
|
-
if (job.id) {
|
|
131
|
-
// Stop checking and return it.
|
|
132
|
-
console.log(`Network job ${job.id} received from ${watchJobURL} (${nowString()})`);
|
|
133
|
-
return job;
|
|
134
|
-
}
|
|
135
|
-
// Otherwise, if the watchee sent a message:
|
|
136
|
-
else if (job.message) {
|
|
137
|
-
// Report it and continue checking.
|
|
138
|
-
console.log(job.message);
|
|
139
|
-
}
|
|
140
|
-
// Otherwise, i.e. if the watchee sent neither a job nor a message:
|
|
141
|
-
else {
|
|
142
|
-
// Report this and continue checking.
|
|
143
|
-
console.log(`No network job at ${watchJobURL} to do (${nowString()})`);
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
// If no watchee sent a job, return this.
|
|
147
|
-
return {};
|
|
37
|
+
// Serves an object in JSON format.
|
|
38
|
+
const serveObject = (object, response) => {
|
|
39
|
+
response.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
40
|
+
response.end(JSON.stringify(object));
|
|
148
41
|
};
|
|
149
42
|
// Writes a directory report.
|
|
150
43
|
const writeDirReport = async report => {
|
|
@@ -153,166 +46,302 @@ const writeDirReport = async report => {
|
|
|
153
46
|
try {
|
|
154
47
|
const reportJSON = JSON.stringify(report, null, 2);
|
|
155
48
|
const reportName = `${jobID}.json`;
|
|
156
|
-
|
|
157
|
-
await fs.writeFile(`${
|
|
158
|
-
console.log(`Report ${reportName} saved in ${
|
|
49
|
+
await fs.mkdir(reportDir, {recursive: true});
|
|
50
|
+
await fs.writeFile(`${reportDir}/${reportName}`, reportJSON);
|
|
51
|
+
console.log(`Report ${reportName} saved in ${reportDir}`);
|
|
159
52
|
}
|
|
160
53
|
catch(error) {
|
|
161
|
-
console.log(`ERROR: Failed to write report ${jobID} (${error.message})`);
|
|
54
|
+
console.log(`ERROR: Failed to write report ${jobID} in ${reportDir} (${error.message})`);
|
|
162
55
|
}
|
|
163
56
|
}
|
|
164
57
|
else {
|
|
165
58
|
console.log('ERROR: Job has no ID');
|
|
166
59
|
}
|
|
167
60
|
};
|
|
168
|
-
//
|
|
169
|
-
const
|
|
170
|
-
const
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
});
|
|
204
|
-
});
|
|
205
|
-
const reportJSON = JSON.stringify(report, null, 2);
|
|
206
|
-
request.end(reportJSON);
|
|
207
|
-
console.log(`Report ${report.id} submitted (${nowString()})`);
|
|
61
|
+
// Archives a job.
|
|
62
|
+
const archiveJob = async job => {
|
|
63
|
+
const {id} = job;
|
|
64
|
+
const jobJSON = JSON.stringify(job, null, 2);
|
|
65
|
+
await fs.writeFile(`${jobDir}/done/${id}.json`, jobJSON);
|
|
66
|
+
await fs.rm(`${jobDir}/todo/${id}.json`);
|
|
67
|
+
};
|
|
68
|
+
// Checks for a directory job and, if found, performs and reports it, once or repeatedly.
|
|
69
|
+
const checkDirJob = async (isForever, interval) => {
|
|
70
|
+
try {
|
|
71
|
+
// If there are any jobs to do in the watched directory:
|
|
72
|
+
const toDoFileNames = await fs.readdir(`${jobDir}/todo`);
|
|
73
|
+
const jobFileNames = toDoFileNames.filter(fileName => fileName.endsWith('.json'));
|
|
74
|
+
if (jobFileNames.length) {
|
|
75
|
+
// Get the first one.
|
|
76
|
+
const jobJSON = await fs.readFile(`${jobDir}/todo/${jobFileNames[0]}`, 'utf8');
|
|
77
|
+
try {
|
|
78
|
+
const job = JSON.parse(jobJSON, null, 2);
|
|
79
|
+
const {id} = job;
|
|
80
|
+
// Perform it.
|
|
81
|
+
console.log(`Directory job ${id} found (${nowString()})`);
|
|
82
|
+
await doJob(job);
|
|
83
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
84
|
+
// Report it.
|
|
85
|
+
await writeDirReport(job);
|
|
86
|
+
// Archive it.
|
|
87
|
+
await archiveJob(job);
|
|
88
|
+
console.log(`Job ${id} archived in ${jobDir} (${nowString()})`);
|
|
89
|
+
// If watching is repetitive:
|
|
90
|
+
if (isForever) {
|
|
91
|
+
// Wait 2 seconds.
|
|
92
|
+
await wait(2000);
|
|
93
|
+
// Check the directory again.
|
|
94
|
+
checkDirJob(true, interval);
|
|
95
|
+
}
|
|
208
96
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// Report this.
|
|
212
|
-
console.log('ERROR: Report specifies no submission destination');
|
|
97
|
+
catch(error) {
|
|
98
|
+
console.log(`ERROR processing directory job (${error.message})`);
|
|
213
99
|
}
|
|
214
100
|
}
|
|
101
|
+
// Otherwise, i.e. if there are no more jobs to do in the watched directory:
|
|
215
102
|
else {
|
|
216
|
-
console.log(
|
|
103
|
+
console.log(`No job to do in ${jobDir} (${nowString()})`);
|
|
104
|
+
// Wait for the specified interval.
|
|
105
|
+
await wait(1000 * interval);
|
|
106
|
+
// Check the directory again.
|
|
107
|
+
await checkDirJob(true, interval);
|
|
217
108
|
}
|
|
218
|
-
});
|
|
219
|
-
// Return the server response.
|
|
220
|
-
if (ack) {
|
|
221
|
-
return ack.message || ack;
|
|
222
109
|
}
|
|
223
|
-
|
|
224
|
-
|
|
110
|
+
catch(error) {
|
|
111
|
+
console.log(`ERROR: Directory watching failed (${error.message})`);
|
|
225
112
|
}
|
|
226
113
|
};
|
|
227
|
-
//
|
|
228
|
-
const
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
//
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
114
|
+
// Checks servers for a network job.
|
|
115
|
+
const checkNetJob = async (servers, serverIndex, isForever, interval, noJobCount) => {
|
|
116
|
+
console.log('');
|
|
117
|
+
// If all servers are jobless:
|
|
118
|
+
if (noJobCount === servers.length) {
|
|
119
|
+
// Wait for the specified interval.
|
|
120
|
+
await wait(1000 * interval);
|
|
121
|
+
// Reset the count of jobless servers.
|
|
122
|
+
noJobCount = 0;
|
|
123
|
+
}
|
|
124
|
+
// Otherwise, i.e. if any server may still have a job:
|
|
125
|
+
else {
|
|
126
|
+
// Wait 2 seconds.
|
|
127
|
+
await wait(2000);
|
|
128
|
+
}
|
|
129
|
+
// Check the next server.
|
|
130
|
+
serverIndex = serverIndex % servers.length;
|
|
131
|
+
const server = servers[serverIndex];
|
|
132
|
+
const client = server.startsWith('https://') ? httpsClient : httpClient;
|
|
133
|
+
const fullURL = `${server}?agent=${agent}`;
|
|
134
|
+
const logStart = `Requested job from server ${server} and got `;
|
|
135
|
+
client.request(fullURL, response => {
|
|
136
|
+
const chunks = [];
|
|
137
|
+
response
|
|
138
|
+
// If the response to the job request threw an error:
|
|
139
|
+
.on('error', async error => {
|
|
140
|
+
// Report it.
|
|
141
|
+
console.log(`${logStart}error message ${error.message}`);
|
|
142
|
+
// Check the next server.
|
|
143
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
144
|
+
})
|
|
145
|
+
.on('data', chunk => {
|
|
146
|
+
chunks.push(chunk);
|
|
147
|
+
})
|
|
148
|
+
// When the response arrives:
|
|
149
|
+
.on('end', async () => {
|
|
150
|
+
const content = chunks.join('');
|
|
151
|
+
// If there was no job to do:
|
|
152
|
+
try {
|
|
153
|
+
const contentObj = JSON.parse(content);
|
|
154
|
+
if (! Object.keys(contentObj).length) {
|
|
155
|
+
// Report this.
|
|
156
|
+
console.log(`No job to do at ${server}`);
|
|
157
|
+
// Check the next server.
|
|
158
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
159
|
+
}
|
|
160
|
+
// Otherwise, i.e. if there was a job or a message:
|
|
161
|
+
else {
|
|
162
|
+
const {message, id, sources} = contentObj;
|
|
163
|
+
// If the server sent a message, not a job:
|
|
164
|
+
if (message) {
|
|
165
|
+
// Report it.
|
|
166
|
+
console.log(`${logStart}${message}`);
|
|
167
|
+
// Check the next server.
|
|
168
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
169
|
+
}
|
|
170
|
+
// Otherwise, if the server sent a valid job:
|
|
171
|
+
else if (id && sources) {
|
|
172
|
+
// Add the agent to it.
|
|
173
|
+
sources.agent = agent;
|
|
174
|
+
// If the job specifies a report destination:
|
|
175
|
+
const {sendReportTo} = sources;
|
|
176
|
+
if (sendReportTo) {
|
|
177
|
+
// Perform the job, adding result data to it.
|
|
178
|
+
console.log(`${logStart}job ${id} for ${sendReportTo} (${nowString()})`);
|
|
179
|
+
await doJob(contentObj);
|
|
180
|
+
const reportJSON = JSON.stringify(contentObj, null, 2);
|
|
181
|
+
console.log(`Job ${id} finished (${nowString()})`);
|
|
182
|
+
// Send the report to the specified server.
|
|
183
|
+
console.log(`Sending report ${id} to ${sendReportTo}`);
|
|
184
|
+
const reportClient = sendReportTo.startsWith('https://') ? httpsClient : httpClient;
|
|
185
|
+
const reportLogStart = `Sent report ${id} to ${sendReportTo} and got `;
|
|
186
|
+
reportClient.request(sendReportTo, {method: 'POST'}, repResponse => {
|
|
187
|
+
const chunks = [];
|
|
188
|
+
repResponse
|
|
189
|
+
// If the response to the report threw an error:
|
|
190
|
+
.on('error', async error => {
|
|
191
|
+
// Report this.
|
|
192
|
+
console.log(`${reportLogStart}error message ${error.message}`);
|
|
193
|
+
// Check the next server.
|
|
194
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
195
|
+
})
|
|
196
|
+
.on('data', chunk => {
|
|
197
|
+
chunks.push(chunk);
|
|
198
|
+
})
|
|
199
|
+
// When the response arrives:
|
|
200
|
+
.on('end', async () => {
|
|
201
|
+
const content = chunks.join('');
|
|
202
|
+
try {
|
|
203
|
+
// If the server sent a message, as expected:
|
|
204
|
+
const ackObj = JSON.parse(content);
|
|
205
|
+
const {message} = ackObj;
|
|
206
|
+
if (message) {
|
|
207
|
+
// Report it.
|
|
208
|
+
console.log(`${reportLogStart}${message}`);
|
|
209
|
+
// Archive the job.
|
|
210
|
+
await archiveJob(contentObj);
|
|
211
|
+
console.log(`Job ${id} archived (${nowString()})`);
|
|
212
|
+
// Check the next server.
|
|
213
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, 0);
|
|
214
|
+
}
|
|
215
|
+
// Otherwise, i.e. if the server sent anything else:
|
|
216
|
+
else {
|
|
217
|
+
// Report it.
|
|
218
|
+
console.log(
|
|
219
|
+
`ERROR: ${reportLogStart}status ${repResponse.statusCode} and error message ${JSON.stringify(ackObj, null, 2)}`
|
|
220
|
+
);
|
|
221
|
+
// Check the next server, disregarding the failed job.
|
|
222
|
+
await checkNetJob(
|
|
223
|
+
servers, serverIndex + 1, isForever, interval, noJobCount + 1
|
|
224
|
+
);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// If processing the report threw an error:
|
|
228
|
+
catch(error) {
|
|
229
|
+
// Report it.
|
|
230
|
+
console.log(
|
|
231
|
+
`ERROR: ${reportLogStart}status ${repResponse.statusCode} and response ${content.slice(0, 1000)}`
|
|
232
|
+
);
|
|
233
|
+
// Check the next server, disregarding the failed job.
|
|
234
|
+
await checkNetJob(
|
|
235
|
+
servers, serverIndex + 1, isForever, interval, noJobCount + 1
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
})
|
|
240
|
+
// If the report submission throws an error:
|
|
241
|
+
.on('error', async error => {
|
|
242
|
+
// Report this.
|
|
243
|
+
console.log(`ERROR: ${reportLogStart}error message ${error.message}`);
|
|
244
|
+
// Check the next server, disregarding the failed job.
|
|
245
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
246
|
+
})
|
|
247
|
+
// Finish submitting the report.
|
|
248
|
+
.end(reportJSON);
|
|
249
|
+
}
|
|
250
|
+
// Otherwise, i.e. if the job specifies no report destination:
|
|
251
|
+
else {
|
|
252
|
+
// Report this.
|
|
253
|
+
const message = `ERROR: ${logStart}job with no report destination`;
|
|
254
|
+
serveObject({message}, response);
|
|
255
|
+
console.log(message);
|
|
256
|
+
// Check the next server, disregarding the defective job.
|
|
257
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
// Otherwise, if the server sent an invalid job:
|
|
261
|
+
else {
|
|
262
|
+
// Report this.
|
|
263
|
+
const message
|
|
264
|
+
= `ERROR: ${logStart}invalid job:\n${JSON.stringify(contentObj, null, 2)}`;
|
|
265
|
+
console.log(message);
|
|
266
|
+
serveObject({message}, response);
|
|
267
|
+
// Check the next server, disregarding the defective job.
|
|
268
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
254
271
|
}
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
//
|
|
258
|
-
|
|
259
|
-
|
|
272
|
+
// If the response to the job request threw an error:
|
|
273
|
+
catch(error) {
|
|
274
|
+
// Report this.
|
|
275
|
+
console.log(
|
|
276
|
+
`ERROR: ${logStart}status ${response.statusCode}, error message ${error.message}, and response ${content.slice(0, 1000)}`
|
|
277
|
+
);
|
|
278
|
+
// Check the next server.
|
|
279
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
260
280
|
}
|
|
281
|
+
});
|
|
282
|
+
})
|
|
283
|
+
// If the job request throws an error:
|
|
284
|
+
.on('error', async error => {
|
|
285
|
+
// If it was a refusal to connect:
|
|
286
|
+
const {message} = error;
|
|
287
|
+
if (message.includes('ECONNREFUSED')) {
|
|
288
|
+
// Report this.
|
|
289
|
+
console.log(`${logStart}no connection`);
|
|
290
|
+
// Check the next server.
|
|
291
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
261
292
|
}
|
|
262
|
-
//
|
|
263
|
-
|
|
293
|
+
// Otherwise, if it was a DNS failure:
|
|
294
|
+
else if (message.includes('ENOTFOUND')) {
|
|
295
|
+
// Report this.
|
|
296
|
+
console.log(`${logStart}no domain name resolution`);
|
|
297
|
+
// Check the next server.
|
|
298
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
299
|
+
}
|
|
300
|
+
// Otherwise, i.e. if it was any other error:
|
|
301
|
+
else {
|
|
264
302
|
// Report this.
|
|
265
|
-
console.log(`ERROR: ${error
|
|
303
|
+
console.log(`ERROR: ${logStart}no response, but got error message ${error.message}`);
|
|
304
|
+
// Check the next server.
|
|
305
|
+
await checkNetJob(servers, serverIndex + 1, isForever, interval, noJobCount + 1);
|
|
266
306
|
}
|
|
307
|
+
})
|
|
308
|
+
// Finish sending the request.
|
|
309
|
+
.end();
|
|
310
|
+
};
|
|
311
|
+
// Composes an interval description.
|
|
312
|
+
const intervalSpec = interval => {
|
|
313
|
+
if (interval > -1) {
|
|
314
|
+
return `repeatedly, with ${interval}-second intervals `;
|
|
267
315
|
}
|
|
268
|
-
// Otherwise, i.e. if the job has no ID:
|
|
269
316
|
else {
|
|
270
|
-
|
|
271
|
-
console.log('ERROR: Job has no id');
|
|
317
|
+
return '';
|
|
272
318
|
}
|
|
273
319
|
};
|
|
274
|
-
// Checks for a job, performs it, and submits a report, once or repeatedly.
|
|
275
|
-
exports.
|
|
276
|
-
|
|
277
|
-
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
// If a directory was watched:
|
|
301
|
-
if (isDirWatch) {
|
|
302
|
-
// Archive the job.
|
|
303
|
-
await archiveJob(job, watchee);
|
|
304
|
-
console.log(`Job ${job.id} archived (${nowString()})`);
|
|
305
|
-
}
|
|
306
|
-
// If watching was specified for only 1 job, quit.
|
|
307
|
-
statusOK = isForever;
|
|
308
|
-
// Prevent a wait before the next iteration.
|
|
309
|
-
empty = false;
|
|
310
|
-
}
|
|
311
|
-
// Otherwise, i.e. if no job was found:
|
|
312
|
-
else {
|
|
313
|
-
// Cause a wait before the next check.
|
|
314
|
-
empty = true;
|
|
315
|
-
}
|
|
320
|
+
// Checks for a directory job, performs it, and submits a report, once or repeatedly.
|
|
321
|
+
exports.dirWatch = async (isForever, interval = 300) => {
|
|
322
|
+
console.log(`Directory watching started ${intervalSpec(interval)}(${nowString()})\n`);
|
|
323
|
+
// Start the checking.
|
|
324
|
+
await checkDirJob(isForever, interval);
|
|
325
|
+
};
|
|
326
|
+
// Checks for a network job, performs it, and submits a report, once or repeatedly.
|
|
327
|
+
exports.netWatch = async (isForever, interval = 300) => {
|
|
328
|
+
// If the servers to be checked are valid:
|
|
329
|
+
const servers = jobURLs
|
|
330
|
+
.split('+')
|
|
331
|
+
.map(url => [Math.random(), url])
|
|
332
|
+
.sort((a, b) => a[0] - b[0])
|
|
333
|
+
.map(pair => pair[1]);
|
|
334
|
+
if (
|
|
335
|
+
servers
|
|
336
|
+
&& servers.length
|
|
337
|
+
&& servers
|
|
338
|
+
.every(server => ['http://', 'https://'].some(prefix => server.startsWith(prefix)))
|
|
339
|
+
) {
|
|
340
|
+
console.log(`Network watching started ${intervalSpec(interval)}(${nowString()})\n`);
|
|
341
|
+
// Start checking.
|
|
342
|
+
await checkNetJob(servers, 0, isForever, interval, 0);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
console.log('ERROR: List of job URLs invalid');
|
|
316
346
|
}
|
|
317
|
-
console.log(`Watching ended (${nowString()})`);
|
|
318
347
|
};
|