Skip to content

Commit 3789819

Browse files
authored
Merge pull request #19 from OceanNetworksCanada/issue-11-support-new-restart-cancel-and-status-methods-add-tests
issue-11: Implement restart, cancel, and status functions and add tests
2 parents cd76c88 + 94c9daf commit 3789819

File tree

6 files changed

+151
-40
lines changed

6 files changed

+151
-40
lines changed

.github/workflows/matlab.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,5 @@ jobs:
3737
source-folder: 'onc'
3838
env:
3939
TOKEN: ${{ secrets.TOKEN }}
40-
ONC_ENV: true
40+
ONC_ENV: 'prod'
4141

onc/+onc/OncDelivery.m

Lines changed: 72 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,9 @@
7676
fprintf('Requesting data product...\n');
7777
[r, info] = this.doRequest(url, filters);
7878
status = info.status;
79-
if not(util.is_failed_response(r, status))
80-
this.estimatePollPeriod(r);
81-
this.printProductRequest(r);
82-
else
83-
throw(util.prepare_exception(status));
84-
end
79+
this.estimatePollPeriod(r);
80+
this.printProductRequest(r);
81+
8582
end
8683

8784
function [r, status] = runDataProduct(this, dpRequestId, waitComplete)
@@ -106,16 +103,18 @@
106103

107104
% run timed run request
108105
tic
106+
cancelUrl = url + "?method=cancel&token="+string(this.token)+"&dpRequestId=" + string(dpRequestId);
107+
if waitComplete
108+
fprintf('\nTo cancel this data product, visit url:\n %s\n', cancelUrl);
109+
else
110+
fprintf('\nTo cancel this data product, please execute command ''onc.cancelDataProduct(%d)''\n', dpRequestId)
111+
end
109112
flag = 'queued';
110113
while ~strcmp(flag,'complete') && ~strcmp(flag,'cancelled')
111114
[response, info] = this.doRequest(url, filters);
112115
status = info.status;
113116
r.requestCount = r.requestCount + 1;
114117

115-
% guard against failed request
116-
if util.is_failed_response(response, status)
117-
throw(util.prepare_exception(status));
118-
end
119118
% repeat only if waitComplete
120119
if waitComplete
121120
log.printResponse(response);
@@ -140,6 +139,42 @@
140139
r.runIds = [r.runIds, run.dpRunId];
141140
end
142141
end
142+
143+
function response = checkDataProduct(this, dpRequestId)
144+
%% Check the status of a data product
145+
%
146+
%
147+
% checkDataProduct (dpRequestId)
148+
%
149+
% * dpRequestId (int) Request id obtained by requestDataProduct()
150+
%
151+
% Returns: response (struct): status of this data product
152+
%
153+
url = sprintf('%sapi/dataProductDelivery', this.baseUrl);
154+
filters = struct('method', 'status', 'token', this.token, 'dpRequestId', dpRequestId);
155+
response = this.doRequest(url, filters);
156+
end
157+
158+
function [response, info] = cancelDataProduct(this, dpRequestId)
159+
%% Cancel a running data product
160+
%
161+
%
162+
% cancelDataProduct (dpRequestId)
163+
%
164+
% * dpRequestId (int) Request id obtained by requestDataProduct()
165+
%
166+
% Returns: response (struct): cancel process status and message
167+
% info (struct): cancel process http code and status
168+
%
169+
url = sprintf('%sapi/dataProductDelivery', this.baseUrl);
170+
filters = struct('method', 'cancel', 'token', this.token, 'dpRequestId', dpRequestId);
171+
[response, info] = this.doRequest(url, filters);
172+
if isfield(response, 'status') && strcmp(response.status, 'cancelled') && info.status == 200
173+
fprintf("The data product with request id %d and run id %d has been successfully cancelled.\n", dpRequestId, response.dpRunId);
174+
else
175+
fprintf("Failed to cancel the data Product.\n");
176+
end
177+
end
143178

144179
function fileData = downloadDataProduct(this, runId, varargin)
145180
%% Download a data product manually with a runId
@@ -169,8 +204,34 @@
169204
fileData = this.downloadProductFiles(runId, metadata, maxRetries, overwrite);
170205
end
171206
end
207+
208+
function [response, info] = restartDataProduct(this, dpRequestId, waitComplete)
209+
%% Restart a cancelled data product
210+
%
211+
%
212+
% restartDataProduct (dpRequestId, waitComplete)
213+
%
214+
% * dpRequestId (int) Request id obtained by requestDataProduct()
215+
% - waitComplete (optional): wait until dp finish when set to true (default)
216+
%
217+
% Returns: response (struct): restart process status and message
218+
% info (struct): restart process http code and status
219+
%
220+
if ~exist('waitComplete','var'), waitComplete = true; end
221+
url = sprintf('%sapi/dataProductDelivery', this.baseUrl);
222+
filters = struct('method', 'restart', 'token', this.token, 'dpRequestId', dpRequestId);
223+
[response, info] = this.doRequest(url, filters);
224+
if isfield(response, 'status') && (strcmp(response.status, 'data product running') || strcmp(response.status, 'queued')) && info.status == 200
225+
fprintf("The data product with request id %d and run id %d has been successfully restarted.\n", dpRequestId, response.dpRunId);
226+
else
227+
fprintf("Failed to restart the data product.\n");
228+
end
229+
if waitComplete
230+
[response, info] = this.runDataProduct(dpRequestId, true);
231+
end
232+
end
172233
end
173-
234+
174235
methods (Access = private, Hidden = true)
175236
function fileList = downloadProductFiles(this, runId, varargin)
176237
%% Download all data product files for provided run id

onc/+util/is_failed_response.m

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,23 +2,22 @@
22
%% Checks if a server response describes a failure
33
%
44
% * response: (struct) Response as returned by do_request()
5-
% - status: @TODO
5+
% - status: (double) http status code
66
%
77
% Returns: (Logical) true when the response is a failure
88
isFailed = false;
9-
9+
1010
% Fail if HTTP status code is not a 2xx
1111
if exist('status', 'var')
1212
if status < 200 || status > 226
1313
isFailed = true;
1414
return
1515
end
1616
end
17-
17+
1818
% Fail if the response is an error description
1919
if isfield(response, "errors")
2020
names = fieldnames(response.errors(1));
2121
isFailed = ismember("errorCode", names) && ismember("errorMessage", names);
2222
end
23-
end
24-
23+
end

onc/tests/globals.m

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
cd(fileparts(which(mfilename)));
1212
end
1313

14-
% grab token from "TOKEN" file
14+
% grab token from "TOKEN" file or get from env
1515
f = fopen('TOKEN','r');
1616
if f > 0
1717
token = fgetl(f);
@@ -20,8 +20,14 @@
2020
token = getenv('TOKEN');
2121
end
2222

23+
% get environment from ONC_ENV or use QA as default
24+
config.production = getenv('ONC_ENV');
25+
if strcmp(config.production, 'prod')
26+
config.production = true;
27+
else
28+
config.production = false;
29+
end
2330
% Set and save config
24-
config.production = true;
2531
config.showInfo = false;
2632
config.outPath = 'output';
2733
config.timeout = 60;

onc/tests/suites/Test07_DataProductDelivery.m

Lines changed: 53 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,40 @@ function testValidResultsOnly(this)
113113

114114

115115
end
116+
117+
%% Testing run method
118+
function testInvalidRequestId(this)
119+
verifyError(this, @() this.onc.runDataProduct(1234567890), 'onc:http400:error127');
120+
end
121+
122+
%% Testing download method
123+
function testInvalidRunId(this)
124+
verifyError(this, @() this.onc.downloadDataProduct(1234567890), 'onc:http400:error127');
125+
end
126+
127+
%% Testing cancel method
128+
function testCancelWithInvalidRequestId(this)
129+
verifyError(this, @() this.onc.cancelDataProduct(1234567890), 'onc:http400:error127');
130+
end
131+
132+
%% Testing status method
133+
function testStatusWithInvalidRequestId(this)
134+
verifyError(this, @() this.onc.checkDataProduct(1234567890), 'onc:http400:error127');
135+
end
136+
137+
%% Testing restart method
138+
function testRestartWithInvalidRequestId(this)
139+
verifyError(this, @() this.onc.restartDataProduct(1234567890), 'onc:http400:error127');
140+
end
116141

142+
%% Integration tests
117143
function testValidManual(this)
118144
this.updateOncOutPath('output/testValidManual');
119145
requestId = this.onc.requestDataProduct(this.Params).dpRequestId;
146+
statusBeforeDownload = this.onc.checkDataProduct(requestId);
147+
148+
assertEqual(this, statusBeforeDownload.searchHdrStatus, 'OPEN');
149+
120150
runId = this.onc.runDataProduct(requestId).runIds(1);
121151
data = this.onc.downloadDataProduct(runId);
122152
verifyTrue(this, length(data) == 3, ...
@@ -133,16 +163,30 @@ function testValidManual(this)
133163
verify_files_in_path(this, this.onc.outPath, 3);
134164
verify_field_value_type(this, data(1), this.expectedFields);
135165

166+
statusAfterDownload = this.onc.checkDataProduct(requestId);
167+
assertEqual(this, statusAfterDownload.searchHdrStatus, 'COMPLETED');
136168
end
137-
138-
%% Testing run method
139-
function testInvalidRequestId(this)
140-
verifyError(this, @() this.onc.runDataProduct(1234567890), 'onc:http400:error127');
141-
end
142-
143-
%% Testing download method
144-
function testInvalidRunId(this)
145-
verifyError(this, @() this.onc.downloadDataProduct(1234567890), 'onc:http400:error127');
169+
170+
function testValidCancelRestart(this)
171+
this.updateOncOutPath('output/testValidCancelRestart');
172+
requestId = this.onc.requestDataProduct(this.Params).dpRequestId;
173+
runId = this.onc.runDataProduct(requestId, false).runIds(1);
174+
responseCancel = this.onc.cancelDataProduct(requestId);
175+
176+
verify_field_value(this, responseCancel, 'dpRunId', runId);
177+
verify_field_value(this, responseCancel, 'status', 'cancelled');
178+
179+
%update MATLAB:nonExistentField error to actual http400 error for this test
180+
%after api service fixes the issue that this 400 error does not contain "errors" field
181+
assertError(this, @() this.onc.downloadDataProduct(runId), 'MATLAB:nonExistentField')
182+
183+
runIdAfterRestart = this.onc.restartDataProduct(requestId).runIds(1);
184+
assertEqual(this, runIdAfterRestart, runId);
185+
186+
responseDownload = this.onc.downloadDataProduct(runId);
187+
assertEqual(this, length(responseDownload), 3, "The first two are png files, and the third one is the metadata");
188+
verify_files_in_path(this, this.onc.outPath, 3);
189+
verify_field_value_type(this, responseDownload(1), this.expectedFields);
146190
end
147191
end
148192

onc/tests/suites/Test08_RealTime.m

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ function testDeviceValidParamsOnePage(this)
163163
result = this.onc.getDirectByDevice(this.paramsDevice);
164164
resultAllPages = this.onc.getDirectByDevice(this.paramsDeviceMultiPages, 'allPages', true);
165165
assertTrue(this, length(result.sensorData(1).data.values) > this.paramsDeviceMultiPages.rowLimit, ...
166-
'Test should return at least `rowLimit` rows for each sensor.');
166+
'Test should return at least `rowLimit` rows.');
167167
assertEmpty(this, result.next, 'Test should return only one page.');
168168
assertEqual(this, resultAllPages.sensorData(1).data, result.sensorData(1).data, ...
169169
'Test should concatenate rows for all pages.');
@@ -178,8 +178,7 @@ function testDeviceValidParamsMultiplePages(this)
178178
end
179179

180180
%% Testing rawdata device
181-
%{
182-
% waiting for python updates
181+
183182
function testRawDeviceInvalidParamValue(this)
184183
filters = this.paramsDevice;
185184
filters.deviceCode = 'XYZ123';
@@ -196,27 +195,29 @@ function testRawDeviceNoData(this)
196195
filters = this.paramsDevice;
197196
filters.dateFrom = '2000-01-01';
198197
filters.dateTo = '2000-01-02';
199-
result = this.onc.getDirectByDevice(filters);
200-
assertEmpty(this, result.sensorData);
198+
result = this.onc.getDirectRawByDevice(filters);
199+
assertEmpty(this, result.data.lineTypes);
200+
assertEmpty(this, result.data.readings);
201+
assertEmpty(this, result.data.times);
201202
end
202203

203204
function testRawDeviceValidParamsOnePage(this)
204-
result = this.onc.getDirectByDevice(this.paramsDevice);
205-
resultAllPages = this.onc.getDirectByDevice(this.paramsDeviceMultiPages, 'allPages', true);
206-
assertTrue(this, length(result.sensorData(1).data.values) > this.paramsDeviceMultiPages.rowLimit, ...
205+
result = this.onc.getDirectRawByDevice(this.paramsDevice);
206+
resultAllPages = this.onc.getDirectRawByDevice(this.paramsDeviceMultiPages, 'allPages', true);
207+
assertTrue(this, length(result.data.readings) > this.paramsDeviceMultiPages.rowLimit, ...
207208
'Test should return at least `rowLimit` rows for each sensor.');
208209
assertEmpty(this, result.next, 'Test should return only one page.');
209-
assertEqual(this, resultAllPages.sensorData(1).data, result.sensorData(1).data, ...
210+
assertEqual(this, resultAllPages.data, result.data, ...
210211
'Test should concatenate rows for all pages.');
211212
assertEmpty(this, resultAllPages.next, 'Test should return only one page.');
212213
end
213214

214215
function testRawDeviceValidParamsMultiplePages(this)
215-
result = this.onc.getDirectByDevice(this.paramsDeviceMultiPages);
216-
assertEqual(this, length(result.sensorData(1).data.values), this.paramsDeviceMultiPages.rowLimit, ...
216+
result = this.onc.getDirectRawByDevice(this.paramsDeviceMultiPages);
217+
assertEqual(this, length(result.data.readings), this.paramsDeviceMultiPages.rowLimit, ...
217218
'Test should only return `rowLimit` rows for each sensor.');
218219
assertTrue(this, ~isempty(result.next), 'Test should return multiple pages.');
219220
end
220-
%}
221+
221222
end
222223
end

0 commit comments

Comments
 (0)