@@ -125,7 +125,15 @@ impl DataStore {
125
125
}
126
126
}
127
127
} ;
128
- Ok ( target_release. into_external ( release_source) )
128
+ // We choose to fetch the blueprint directly from the database rather
129
+ // than relying on the cached blueprint in Nexus because our APIs try to
130
+ // be strongly consistent. This shows up/will show up as a warning in
131
+ // the UI, and we don't want the warning to flicker in and out of
132
+ // existence based on which Nexus is getting hit.
133
+ let min_gen = self . blueprint_target_get_current_min_gen ( opctx) . await ?;
134
+ // The semantics of min_gen mean we use a > sign here, not >=.
135
+ let mupdate_override = min_gen > target_release. generation . 0 ;
136
+ Ok ( target_release. into_external ( release_source, mupdate_override) )
129
137
}
130
138
}
131
139
@@ -135,6 +143,12 @@ mod test {
135
143
use crate :: db:: model:: { Generation , TargetReleaseSource } ;
136
144
use crate :: db:: pub_test_utils:: TestDatabase ;
137
145
use chrono:: { TimeDelta , Utc } ;
146
+ use nexus_inventory:: now_db_precision;
147
+ use nexus_reconfigurator_planning:: blueprint_builder:: BlueprintBuilder ;
148
+ use nexus_reconfigurator_planning:: example:: {
149
+ ExampleSystemBuilder , SimRngState ,
150
+ } ;
151
+ use nexus_types:: deployment:: BlueprintTarget ;
138
152
use omicron_common:: api:: external:: {
139
153
TufArtifactMeta , TufRepoDescription , TufRepoMeta ,
140
154
} ;
@@ -145,7 +159,8 @@ mod test {
145
159
146
160
#[ tokio:: test]
147
161
async fn target_release_datastore ( ) {
148
- let logctx = dev:: test_setup_log ( "target_release_datastore" ) ;
162
+ const TEST_NAME : & str = "target_release_datastore" ;
163
+ let logctx = dev:: test_setup_log ( TEST_NAME ) ;
149
164
let db = TestDatabase :: new_with_datastore ( & logctx. log ) . await ;
150
165
let ( opctx, datastore) = ( db. opctx ( ) , db. datastore ( ) ) ;
151
166
@@ -163,6 +178,56 @@ mod test {
163
178
) ;
164
179
assert ! ( initial_target_release. tuf_repo_id. is_none( ) ) ;
165
180
181
+ // Set up an initial blueprint and make it the target. This models real
182
+ // systems which always have a target blueprint.
183
+ let mut rng = SimRngState :: from_seed ( TEST_NAME ) ;
184
+ let ( system, mut blueprint) = ExampleSystemBuilder :: new_with_rng (
185
+ & logctx. log ,
186
+ rng. next_system_rng ( ) ,
187
+ )
188
+ . build ( ) ;
189
+ assert_eq ! (
190
+ blueprint. target_release_minimum_generation,
191
+ 1 . into( ) ,
192
+ "initial blueprint should have minimum generation of 1" ,
193
+ ) ;
194
+ // Treat this blueprint as the initial one for the system.
195
+ blueprint. parent_blueprint_id = None ;
196
+
197
+ datastore
198
+ . blueprint_insert ( & opctx, & blueprint)
199
+ . await
200
+ . expect ( "inserted blueprint" ) ;
201
+ datastore
202
+ . blueprint_target_set_current (
203
+ opctx,
204
+ BlueprintTarget {
205
+ target_id : blueprint. id ,
206
+ // enabled = true or false shouldn't matter for this.
207
+ enabled : true ,
208
+ time_made_target : now_db_precision ( ) ,
209
+ } ,
210
+ )
211
+ . await
212
+ . expect ( "set blueprint target" ) ;
213
+
214
+ // We should always be able to get a view of the target release.
215
+ let initial_target_release_view = datastore
216
+ . target_release_view ( opctx, & initial_target_release)
217
+ . await
218
+ . expect ( "got target release" ) ;
219
+ eprintln ! (
220
+ "initial target release view: {:#?}" ,
221
+ initial_target_release_view
222
+ ) ;
223
+
224
+ // This target release should not have the mupdate override set, because
225
+ // the generation is <= the minimum generation in the target blueprint.
226
+ assert ! (
227
+ !initial_target_release_view. mupdate_override,
228
+ "mupdate_override should be false for initial target release"
229
+ ) ;
230
+
166
231
// We should be able to set a new generation just like the first.
167
232
// We allow some slack in the timestamp comparison because the
168
233
// database only stores timestamps with μsec precision.
@@ -256,6 +321,86 @@ mod test {
256
321
) ;
257
322
assert_eq ! ( target_release. tuf_repo_id, Some ( tuf_repo_id) ) ;
258
323
324
+ // Generate a new blueprint with a greater target release generation.
325
+ let mut builder = BlueprintBuilder :: new_based_on (
326
+ & logctx. log ,
327
+ & blueprint,
328
+ & system. input ,
329
+ & system. collection ,
330
+ TEST_NAME ,
331
+ )
332
+ . expect ( "created blueprint builder" ) ;
333
+ builder. set_rng ( rng. next_planner_rng ( ) ) ;
334
+ builder
335
+ . set_target_release_minimum_generation (
336
+ blueprint. target_release_minimum_generation ,
337
+ 5 . into ( ) ,
338
+ )
339
+ . expect ( "set target release minimum generation" ) ;
340
+ let bp2 = builder. build ( ) ;
341
+
342
+ datastore
343
+ . blueprint_insert ( & opctx, & bp2)
344
+ . await
345
+ . expect ( "inserted blueprint" ) ;
346
+ datastore
347
+ . blueprint_target_set_current (
348
+ opctx,
349
+ BlueprintTarget {
350
+ target_id : bp2. id ,
351
+ // enabled = true or false shouldn't matter for this.
352
+ enabled : true ,
353
+ time_made_target : now_db_precision ( ) ,
354
+ } ,
355
+ )
356
+ . await
357
+ . expect ( "set blueprint target" ) ;
358
+
359
+ // Fetch the target release again.
360
+ let target_release = datastore
361
+ . target_release_get_current ( opctx)
362
+ . await
363
+ . expect ( "got target release" ) ;
364
+ let target_release_view_2 = datastore
365
+ . target_release_view ( opctx, & target_release)
366
+ . await
367
+ . expect ( "got target release" ) ;
368
+
369
+ eprintln ! ( "target release view 2: {target_release_view_2:#?}" ) ;
370
+
371
+ assert ! (
372
+ target_release_view_2. mupdate_override,
373
+ "mupdate override is set" ,
374
+ ) ;
375
+
376
+ // Now set the target release again -- this should cause the mupdate
377
+ // override to disappear.
378
+ let before = Utc :: now ( ) ;
379
+ let target_release = datastore
380
+ . target_release_insert (
381
+ opctx,
382
+ TargetRelease :: new_system_version ( & target_release, tuf_repo_id) ,
383
+ )
384
+ . await
385
+ . unwrap ( ) ;
386
+ let after = Utc :: now ( ) ;
387
+
388
+ assert_eq ! ( target_release. generation, Generation ( 5 . into( ) ) ) ;
389
+ assert ! ( target_release. time_requested >= before) ;
390
+ assert ! ( target_release. time_requested <= after) ;
391
+
392
+ let target_release_view_3 = datastore
393
+ . target_release_view ( opctx, & target_release)
394
+ . await
395
+ . expect ( "got target release" ) ;
396
+
397
+ eprintln ! ( "target release view 3: {target_release_view_3:#?}" ) ;
398
+
399
+ assert ! (
400
+ !target_release_view_3. mupdate_override,
401
+ "mupdate override is not set" ,
402
+ ) ;
403
+
259
404
// Clean up.
260
405
db. terminate ( ) . await ;
261
406
logctx. cleanup_successful ( ) ;
0 commit comments