@@ -305,28 +305,67 @@ pub async fn timeseries_query(
305
305
cptestctx : & ControlPlaneTestContext < omicron_nexus:: Server > ,
306
306
query : impl ToString ,
307
307
) -> Vec < oxql_types:: Table > {
308
- execute_timeseries_query ( cptestctx, "/v1/system/timeseries/query" , query)
309
- . await
308
+ timeseries_query_until_success (
309
+ cptestctx,
310
+ "/v1/system/timeseries/query" ,
311
+ query,
312
+ )
313
+ . await
310
314
}
311
315
312
316
pub async fn project_timeseries_query (
313
317
cptestctx : & ControlPlaneTestContext < omicron_nexus:: Server > ,
314
318
project : & str ,
315
319
query : impl ToString ,
316
320
) -> Vec < oxql_types:: Table > {
317
- execute_timeseries_query (
321
+ timeseries_query_until_success (
318
322
cptestctx,
319
323
& format ! ( "/v1/timeseries/query?project={}" , project) ,
320
324
query,
321
325
)
322
326
. await
323
327
}
324
328
325
- async fn execute_timeseries_query (
329
+ /// Run an OxQL query until it succeeds or panics.
330
+ async fn timeseries_query_until_success (
326
331
cptestctx : & ControlPlaneTestContext < omicron_nexus:: Server > ,
327
332
endpoint : & str ,
328
333
query : impl ToString ,
329
334
) -> Vec < oxql_types:: Table > {
335
+ const POLL_INTERVAL : Duration = Duration :: from_secs ( 1 ) ;
336
+ const POLL_MAX : Duration = Duration :: from_secs ( 30 ) ;
337
+ let query_ = query. to_string ( ) ;
338
+ wait_for_condition (
339
+ || async {
340
+ match execute_timeseries_query ( cptestctx, endpoint, & query_) . await {
341
+ Some ( r) => Ok ( r) ,
342
+ None => Err ( CondCheckError :: < ( ) > :: NotYet ) ,
343
+ }
344
+ } ,
345
+ & POLL_INTERVAL ,
346
+ & POLL_MAX ,
347
+ )
348
+ . await
349
+ . unwrap_or_else ( |_| {
350
+ panic ! (
351
+ "Timeseries named in query are not available \
352
+ after {:?}, query: '{}'",
353
+ POLL_MAX ,
354
+ query. to_string( ) ,
355
+ )
356
+ } )
357
+ }
358
+
359
+ /// Run an OxQL query.
360
+ ///
361
+ /// This returns `None` if the query resulted in client error and the body
362
+ /// indicates that a timeseries named in the query could not be found. In all
363
+ /// other cases, it either succeeds or panics.
364
+ pub async fn execute_timeseries_query (
365
+ cptestctx : & ControlPlaneTestContext < omicron_nexus:: Server > ,
366
+ endpoint : & str ,
367
+ query : impl ToString ,
368
+ ) -> Option < Vec < oxql_types:: Table > > {
330
369
// first, make sure the latest timeseries have been collected.
331
370
cptestctx
332
371
. oximeter
@@ -353,14 +392,29 @@ async fn execute_timeseries_query(
353
392
. unwrap_or_else ( |e| {
354
393
panic ! ( "timeseries query failed: {e:?}\n query: {query}" )
355
394
} ) ;
356
- rsp. parsed_body :: < OxqlQueryResult > ( )
357
- . unwrap_or_else ( |e| {
358
- panic ! (
359
- "could not parse timeseries query response: {e:?}\n \
360
- query: {query}\n response: {rsp:#?}"
361
- ) ;
362
- } )
363
- . tables
395
+
396
+ // Check for a timeseries-not-found error specifically.
397
+ if rsp. status . is_client_error ( ) {
398
+ let text = std:: str:: from_utf8 ( & rsp. body )
399
+ . expect ( "Timeseries query response body should be UTF-8" ) ;
400
+ if text. contains ( "Schema for timeseries" ) && text. contains ( "not found" )
401
+ {
402
+ return None ;
403
+ }
404
+ }
405
+
406
+ // Try to parse the query as usual, which will fail on other kinds of
407
+ // errors.
408
+ Some (
409
+ rsp. parsed_body :: < OxqlQueryResult > ( )
410
+ . unwrap_or_else ( |e| {
411
+ panic ! (
412
+ "could not parse timeseries query response: {e:?}\n \
413
+ query: {query}\n response: {rsp:#?}"
414
+ ) ;
415
+ } )
416
+ . tables ,
417
+ )
364
418
}
365
419
366
420
#[ nexus_test]
0 commit comments