4
4
5
5
//! Helpers common to Reconfigurator tests
6
6
7
- use anyhow:: { Context , ensure} ;
8
- use nexus_client:: types:: BlueprintTargetSet ;
7
+ use anyhow:: { Context , anyhow, bail, ensure} ;
8
+ use nexus_client:: types:: { BackgroundTasksActivateRequest , BlueprintTargetSet } ;
9
+ use nexus_db_queries:: context:: OpContext ;
10
+ use nexus_db_queries:: db:: DataStore ;
9
11
use nexus_reconfigurator_planning:: blueprint_builder:: BlueprintBuilder ;
10
12
use nexus_reconfigurator_planning:: planner:: PlannerRng ;
11
13
use nexus_types:: deployment:: { Blueprint , PlanningInput } ;
14
+ use nexus_types:: external_api:: views:: SledState ;
12
15
use nexus_types:: inventory:: Collection ;
16
+ use omicron_test_utils:: dev:: poll:: { CondCheckError , wait_for_condition} ;
13
17
use omicron_uuid_kinds:: GenericUuid ;
14
18
use slog:: { debug, info} ;
19
+ use slog_error_chain:: InlineErrorChain ;
20
+ use std:: time:: Duration ;
21
+
22
+ /// Return the current target blueprint
23
+ ///
24
+ /// Also validates that it's enabled. If an operator has disabled execution, we
25
+ /// don't want to proceed with tests.
26
+ pub async fn blueprint_load_target_enabled (
27
+ log : & slog:: Logger ,
28
+ nexus : & nexus_client:: Client ,
29
+ ) -> Result < Blueprint , anyhow:: Error > {
30
+ // Fetch the current target configuration.
31
+ info ! ( log, "editing current target blueprint" ) ;
32
+ let target_blueprint = nexus
33
+ . blueprint_target_view ( )
34
+ . await
35
+ . context ( "fetch current target config" ) ?
36
+ . into_inner ( ) ;
37
+
38
+ debug ! ( log, "found current target blueprint" ;
39
+ "blueprint_id" => %target_blueprint. target_id
40
+ ) ;
41
+ ensure ! (
42
+ target_blueprint. enabled,
43
+ "refusing to operate on a system with target blueprint disabled"
44
+ ) ;
45
+
46
+ let blueprint = nexus
47
+ . blueprint_view ( target_blueprint. target_id . as_untyped_uuid ( ) )
48
+ . await
49
+ . context ( "fetch current target blueprint" ) ?
50
+ . into_inner ( ) ;
51
+ debug ! ( log, "fetched current target blueprint" ;
52
+ "blueprint_id" => %target_blueprint. target_id
53
+ ) ;
54
+ Ok ( blueprint)
55
+ }
15
56
16
57
/// Modify the system by editing the current target blueprint
17
58
///
@@ -44,28 +85,7 @@ pub async fn blueprint_edit_current_target(
44
85
) -> Result < ( Blueprint , Blueprint ) , anyhow:: Error > {
45
86
// Fetch the current target configuration.
46
87
info ! ( log, "editing current target blueprint" ) ;
47
- let target_blueprint = nexus
48
- . blueprint_target_view ( )
49
- . await
50
- . context ( "fetch current target config" ) ?
51
- . into_inner ( ) ;
52
- debug ! ( log, "found current target blueprint" ;
53
- "blueprint_id" => %target_blueprint. target_id
54
- ) ;
55
- ensure ! (
56
- target_blueprint. enabled,
57
- "refusing to modify a system with target blueprint disabled"
58
- ) ;
59
-
60
- // Fetch the actual blueprint.
61
- let blueprint1 = nexus
62
- . blueprint_view ( target_blueprint. target_id . as_untyped_uuid ( ) )
63
- . await
64
- . context ( "fetch current target blueprint" ) ?
65
- . into_inner ( ) ;
66
- debug ! ( log, "fetched current target blueprint" ;
67
- "blueprint_id" => %target_blueprint. target_id
68
- ) ;
88
+ let blueprint1 = blueprint_load_target_enabled ( log, nexus) . await ?;
69
89
70
90
// Make a new builder based on that blueprint and use `edit_fn` to edit it.
71
91
let mut builder = BlueprintBuilder :: new_based_on (
@@ -83,7 +103,7 @@ pub async fn blueprint_edit_current_target(
83
103
// Assemble the new blueprint, import it, and make it the new target.
84
104
let blueprint2 = builder. build ( ) ;
85
105
info ! ( log, "assembled new blueprint based on target" ;
86
- "current_target_id" => %target_blueprint . target_id ,
106
+ "current_target_id" => %blueprint1 . id ,
87
107
"new_blueprint_id" => %blueprint2. id,
88
108
) ;
89
109
nexus
@@ -107,3 +127,105 @@ pub async fn blueprint_edit_current_target(
107
127
108
128
Ok ( ( blueprint1, blueprint2) )
109
129
}
130
+
131
+ /// Checks whether the given blueprint's sled configurations appear to be
132
+ /// propagated to all sleds.
133
+ ///
134
+ /// If so, returns the inventory collection so that the caller can check
135
+ /// additional details if wanted. If not or if we failed to determine the
136
+ /// answer, returns an error.
137
+ pub async fn blueprint_sled_configs_propagated (
138
+ opctx : & OpContext ,
139
+ datastore : & DataStore ,
140
+ blueprint : & Blueprint ,
141
+ ) -> Result < Collection , anyhow:: Error > {
142
+ let log = & opctx. log ;
143
+ let latest_collection = datastore
144
+ . inventory_get_latest_collection ( opctx)
145
+ . await
146
+ . context ( "fetching latest collection" ) ?
147
+ . ok_or_else ( || anyhow ! ( "have no inventory collections" ) ) ?;
148
+ debug ! ( log, "got inventory" ; "id" => %latest_collection. id) ;
149
+ for ( sled_id, sled_config) in & blueprint. sleds {
150
+ if sled_config. state != SledState :: Active {
151
+ continue ;
152
+ }
153
+
154
+ let agent = latest_collection
155
+ . sled_agents
156
+ . get ( sled_id)
157
+ . ok_or_else ( || anyhow ! ( "sled {sled_id}: missing inventory" ) ) ?;
158
+ let reconciled_config = & agent
159
+ . last_reconciliation
160
+ . as_ref ( )
161
+ . ok_or_else ( || {
162
+ anyhow ! ( "sled {sled_id}: missing last_reconciliation" )
163
+ } ) ?
164
+ . last_reconciled_config ;
165
+ if reconciled_config. generation < sled_config. sled_agent_generation {
166
+ bail ! (
167
+ "sled {sled_id}: last reconciled generation {}, waiting for {}" ,
168
+ reconciled_config. generation,
169
+ sled_config. sled_agent_generation
170
+ ) ;
171
+ }
172
+ }
173
+
174
+ Ok ( latest_collection)
175
+ }
176
+
177
+ /// Waits for the given blueprint's sled configurations to appear to be
178
+ /// propagated to all sleds.
179
+ ///
180
+ /// Returns the inventory collection so that the caller can check additional
181
+ /// details if wanted.
182
+ pub async fn blueprint_wait_sled_configs_propagated (
183
+ opctx : & OpContext ,
184
+ datastore : & DataStore ,
185
+ blueprint : & Blueprint ,
186
+ nexus : & nexus_client:: Client ,
187
+ timeout : Duration ,
188
+ ) -> Result < Collection , anyhow:: Error > {
189
+ wait_for_condition (
190
+ || async {
191
+ match blueprint_sled_configs_propagated ( opctx, datastore, blueprint)
192
+ . await
193
+ {
194
+ Ok ( collection) => Ok ( collection) ,
195
+ Err ( error) => {
196
+ debug ! (
197
+ opctx. log,
198
+ "blueprint_wait_sled_configs_propagated" ;
199
+ InlineErrorChain :: new( & * error)
200
+ ) ;
201
+
202
+ // Activate the inventory collector.
203
+ info ! ( opctx. log, "activating inventory collector" ) ;
204
+ nexus
205
+ . bgtask_activate ( & BackgroundTasksActivateRequest {
206
+ bgtask_names : vec ! [ String :: from(
207
+ "inventory_collection" ,
208
+ ) ] ,
209
+ } )
210
+ . await
211
+ . expect ( "activating inventory background task" ) ;
212
+
213
+ // We don't use the variant of `CondCheckError` that carries
214
+ // a permanent error, so we need to put a type here. We
215
+ // want the resulting error to impl `ToString`, so we need a
216
+ // type that impls that. We pick `String`.
217
+ Err ( CondCheckError :: < String > :: NotYet )
218
+ }
219
+ }
220
+ } ,
221
+ & Duration :: from_millis ( 3000 ) ,
222
+ & timeout,
223
+ )
224
+ . await
225
+ . map_err ( |error| {
226
+ anyhow ! (
227
+ "waiting for blueprint {}'s sled configs to be propagated: {error}" ,
228
+ blueprint. id
229
+ )
230
+ } )
231
+ }
0 commit comments