@@ -37,6 +37,9 @@ pub enum TomlToSpecError {
37
37
#[ error( "failed to parse PCI path string {0:?}" ) ]
38
38
PciPathParseFailed ( String , #[ source] std:: io:: Error ) ,
39
39
40
+ #[ error( "spec key {0:?} defined multiple times" ) ]
41
+ DuplicateSpecKey ( SpecKey ) ,
42
+
40
43
#[ error( "invalid storage device kind {kind:?} for device {name:?}" ) ]
41
44
InvalidStorageDeviceType { kind : String , name : String } ,
42
45
@@ -71,6 +74,27 @@ pub struct SpecConfig {
71
74
pub components : BTreeMap < SpecKey , ComponentV0 > ,
72
75
}
73
76
77
+ // Inspired by `api_spec_v0.rs`'s `insert_component` and
78
+ // `propolis-cli/src/main.rs`'s `add_component_to_spec`. Same purpose as both of
79
+ // them.
80
+ //
81
+ // Before either of those are transforming one kind of spec to another, TOML
82
+ // configurations are parsed to a SpecConfig in this file, where there is *also*
83
+ // an opportunity for duplicate keys to clobber spec items.
84
+ #[ track_caller]
85
+ fn spec_component_add (
86
+ spec : & mut SpecConfig ,
87
+ key : SpecKey ,
88
+ component : ComponentV0 ,
89
+ ) -> Result < ( ) , TomlToSpecError > {
90
+ if spec. components . contains_key ( & key) {
91
+ return Err ( TomlToSpecError :: DuplicateSpecKey ( key) ) ;
92
+ }
93
+
94
+ spec. components . insert ( key, component) ;
95
+ Ok ( ( ) )
96
+ }
97
+
74
98
impl TryFrom < & super :: Config > for SpecConfig {
75
99
type Error = TomlToSpecError ;
76
100
@@ -109,12 +133,13 @@ impl TryFrom<&super::Config> for SpecConfig {
109
133
. unwrap_or ( 0 )
110
134
. max ( 0 ) as u32 ;
111
135
112
- spec. components . insert (
136
+ spec_component_add (
137
+ & mut spec,
113
138
SpecKey :: Name ( MIGRATION_FAILURE_DEVICE_NAME . to_owned ( ) ) ,
114
139
ComponentV0 :: MigrationFailureInjector (
115
140
MigrationFailureInjector { fail_exports, fail_imports } ,
116
141
) ,
117
- ) ;
142
+ ) ? ;
118
143
119
144
continue ;
120
145
}
@@ -140,20 +165,24 @@ impl TryFrom<&super::Config> for SpecConfig {
140
165
backend_config,
141
166
) ?;
142
167
143
- spec . components . insert ( device_id, device_spec) ;
144
- spec . components . insert ( backend_id, backend_spec) ;
168
+ spec_component_add ( & mut spec , device_id, device_spec) ? ;
169
+ spec_component_add ( & mut spec , backend_id, backend_spec) ? ;
145
170
}
146
171
"pci-virtio-viona" => {
147
172
let ParsedNic { device_spec, backend_spec, backend_id } =
148
173
parse_network_device_from_config ( device_name, device) ?;
149
174
150
- spec. components
151
- . insert ( device_id, ComponentV0 :: VirtioNic ( device_spec) ) ;
175
+ spec_component_add (
176
+ & mut spec,
177
+ device_id,
178
+ ComponentV0 :: VirtioNic ( device_spec) ,
179
+ ) ?;
152
180
153
- spec. components . insert (
181
+ spec_component_add (
182
+ & mut spec,
154
183
backend_id,
155
184
ComponentV0 :: VirtioNetworkBackend ( backend_spec) ,
156
- ) ;
185
+ ) ? ;
157
186
}
158
187
"softnpu-pci-port" => {
159
188
let pci_path: PciPath =
@@ -163,12 +192,13 @@ impl TryFrom<&super::Config> for SpecConfig {
163
192
)
164
193
} ) ?;
165
194
166
- spec. components . insert (
195
+ spec_component_add (
196
+ & mut spec,
167
197
device_id,
168
198
ComponentV0 :: SoftNpuPciPort ( SoftNpuPciPort {
169
199
pci_path,
170
200
} ) ,
171
- ) ;
201
+ ) ? ;
172
202
}
173
203
"softnpu-port" => {
174
204
let vnic_name =
@@ -179,20 +209,22 @@ impl TryFrom<&super::Config> for SpecConfig {
179
209
let backend_name =
180
210
SpecKey :: Name ( format ! ( "{device_id}:backend" ) ) ;
181
211
182
- spec. components . insert (
212
+ spec_component_add (
213
+ & mut spec,
183
214
device_id,
184
215
ComponentV0 :: SoftNpuPort ( SoftNpuPort {
185
216
link_name : device_name. to_string ( ) ,
186
217
backend_id : backend_name. clone ( ) ,
187
218
} ) ,
188
- ) ;
219
+ ) ? ;
189
220
190
- spec. components . insert (
221
+ spec_component_add (
222
+ & mut spec,
191
223
backend_name,
192
224
ComponentV0 :: DlpiNetworkBackend ( DlpiNetworkBackend {
193
225
vnic_name : vnic_name. to_owned ( ) ,
194
226
} ) ,
195
- ) ;
227
+ ) ? ;
196
228
}
197
229
"softnpu-p9" => {
198
230
let pci_path: PciPath =
@@ -202,19 +234,21 @@ impl TryFrom<&super::Config> for SpecConfig {
202
234
)
203
235
} ) ?;
204
236
205
- spec. components . insert (
237
+ spec_component_add (
238
+ & mut spec,
206
239
device_id,
207
240
ComponentV0 :: SoftNpuP9 ( SoftNpuP9 { pci_path } ) ,
208
- ) ;
241
+ ) ? ;
209
242
}
210
243
"pci-virtio-9p" => {
211
- spec. components . insert (
244
+ spec_component_add (
245
+ & mut spec,
212
246
device_id,
213
247
ComponentV0 :: P9fs ( parse_p9fs_from_config (
214
248
device_name,
215
249
device,
216
250
) ?) ,
217
- ) ;
251
+ ) ? ;
218
252
}
219
253
_ => {
220
254
return Err ( TomlToSpecError :: UnrecognizedDeviceType (
@@ -233,13 +267,14 @@ impl TryFrom<&super::Config> for SpecConfig {
233
267
)
234
268
} ) ?;
235
269
236
- spec. components . insert (
270
+ spec_component_add (
271
+ & mut spec,
237
272
SpecKey :: Name ( format ! ( "pci-bridge-{}" , bridge. pci_path) ) ,
238
273
ComponentV0 :: PciPciBridge ( PciPciBridge {
239
274
downstream_bus : bridge. downstream_bus ,
240
275
pci_path,
241
276
} ) ,
242
- ) ;
277
+ ) ? ;
243
278
}
244
279
245
280
Ok ( spec)
0 commit comments