@@ -11,10 +11,12 @@ use crate::opte::{Port, PortTicket};
11
11
use crate :: svc:: wait_for_service;
12
12
use crate :: zone:: { AddressRequest , IPADM , ZONE_PREFIX } ;
13
13
use camino:: { Utf8Path , Utf8PathBuf } ;
14
+ use camino_tempfile:: Utf8TempDir ;
14
15
use ipnetwork:: IpNetwork ;
15
16
use omicron_common:: backoff;
16
17
use slog:: { error, info, o, warn, Logger } ;
17
18
use std:: net:: { IpAddr , Ipv4Addr , Ipv6Addr } ;
19
+ use std:: sync:: Arc ;
18
20
#[ cfg( target_os = "illumos" ) ]
19
21
use std:: sync:: OnceLock ;
20
22
#[ cfg( target_os = "illumos" ) ]
@@ -1042,7 +1044,7 @@ pub struct ServiceProcess {
1042
1044
pub log_file : Utf8PathBuf ,
1043
1045
}
1044
1046
1045
- /// Errors returned from [`InstalledZone ::install`].
1047
+ /// Errors returned from [`ZoneBuilder ::install`].
1046
1048
#[ derive( thiserror:: Error , Debug ) ]
1047
1049
pub enum InstallZoneError {
1048
1050
#[ error( "Cannot create '{zone}': failed to create control VNIC: {err}" ) ]
@@ -1062,6 +1064,9 @@ pub enum InstallZoneError {
1062
1064
1063
1065
#[ error( "Failed to find zone image '{image}' from {paths:?}" ) ]
1064
1066
ImageNotFound { image : String , paths : Vec < Utf8PathBuf > } ,
1067
+
1068
+ #[ error( "Attempted to call install() on underspecified ZoneBuilder" ) ]
1069
+ IncompleteBuilder ,
1065
1070
}
1066
1071
1067
1072
pub struct InstalledZone {
@@ -1118,24 +1123,208 @@ impl InstalledZone {
1118
1123
& self . zonepath
1119
1124
}
1120
1125
1121
- // TODO: This would benefit from a "builder-pattern" interface.
1122
- #[ allow( clippy:: too_many_arguments) ]
1123
- pub async fn install (
1124
- log : & Logger ,
1125
- underlay_vnic_allocator : & VnicAllocator < Etherstub > ,
1126
- zone_root_path : & Utf8Path ,
1127
- zone_image_paths : & [ Utf8PathBuf ] ,
1128
- zone_type : & str ,
1129
- unique_name : Option < Uuid > ,
1130
- datasets : & [ zone:: Dataset ] ,
1131
- filesystems : & [ zone:: Fs ] ,
1132
- data_links : & [ String ] ,
1133
- devices : & [ zone:: Device ] ,
1134
- opte_ports : Vec < ( Port , PortTicket ) > ,
1135
- bootstrap_vnic : Option < Link > ,
1136
- links : Vec < Link > ,
1137
- limit_priv : Vec < String > ,
1138
- ) -> Result < InstalledZone , InstallZoneError > {
1126
+ pub fn site_profile_xml_path ( & self ) -> Utf8PathBuf {
1127
+ let mut path: Utf8PathBuf = self . zonepath ( ) . into ( ) ;
1128
+ path. push ( "root/var/svc/profile/site.xml" ) ;
1129
+ path
1130
+ }
1131
+ }
1132
+
1133
+ #[ derive( Clone ) ]
1134
+ pub struct FakeZoneBuilderConfig {
1135
+ temp_dir : Arc < Utf8TempDir > ,
1136
+ }
1137
+
1138
+ #[ derive( Clone , Default ) ]
1139
+ pub struct ZoneBuilderFactory {
1140
+ // Why this is part of this builder/factory and not some separate builder
1141
+ // type: At time of writing, to the best of my knowledge:
1142
+ // - If we want builder pattern, we need to return some type of `Self`.
1143
+ // - If we have a trait that returns `Self` type, we can't turn it into a
1144
+ // trait object (i.e. Box<dyn ZoneBuilderFactoryInterface>).
1145
+ // - Plumbing concrete types as generics through every other type that
1146
+ // needs to construct zones (and anything else with a lot of parameters)
1147
+ // seems like a worse idea.
1148
+ fake_cfg : Option < FakeZoneBuilderConfig > ,
1149
+ }
1150
+
1151
+ impl ZoneBuilderFactory {
1152
+ /// For use in unit tests that don't require actual zone creation to occur.
1153
+ pub fn fake ( ) -> Self {
1154
+ Self {
1155
+ fake_cfg : Some ( FakeZoneBuilderConfig {
1156
+ temp_dir : Arc :: new ( Utf8TempDir :: new ( ) . unwrap ( ) ) ,
1157
+ } ) ,
1158
+ }
1159
+ }
1160
+
1161
+ /// Create a [ZoneBuilder] that inherits this factory's fakeness.
1162
+ pub fn builder < ' a > ( & self ) -> ZoneBuilder < ' a > {
1163
+ ZoneBuilder { fake_cfg : self . fake_cfg . clone ( ) , ..Default :: default ( ) }
1164
+ }
1165
+ }
1166
+
1167
+ /// Builder-pattern construct for creating an [InstalledZone].
1168
+ /// Created by [ZoneBuilderFactory].
1169
+ #[ derive( Default ) ]
1170
+ pub struct ZoneBuilder < ' a > {
1171
+ log : Option < Logger > ,
1172
+ underlay_vnic_allocator : Option < & ' a VnicAllocator < Etherstub > > ,
1173
+ zone_root_path : Option < & ' a Utf8Path > ,
1174
+ zone_image_paths : Option < & ' a [ Utf8PathBuf ] > ,
1175
+ zone_type : Option < & ' a str > ,
1176
+ unique_name : Option < Uuid > , // actually optional
1177
+ datasets : Option < & ' a [ zone:: Dataset ] > ,
1178
+ filesystems : Option < & ' a [ zone:: Fs ] > ,
1179
+ data_links : Option < & ' a [ String ] > ,
1180
+ devices : Option < & ' a [ zone:: Device ] > ,
1181
+ opte_ports : Option < Vec < ( Port , PortTicket ) > > ,
1182
+ bootstrap_vnic : Option < Link > , // actually optional
1183
+ links : Option < Vec < Link > > ,
1184
+ limit_priv : Option < Vec < String > > ,
1185
+ fake_cfg : Option < FakeZoneBuilderConfig > ,
1186
+ }
1187
+
1188
+ impl < ' a > ZoneBuilder < ' a > {
1189
+ pub fn with_log ( mut self , log : Logger ) -> Self {
1190
+ self . log = Some ( log) ;
1191
+ self
1192
+ }
1193
+
1194
+ pub fn with_underlay_vnic_allocator (
1195
+ mut self ,
1196
+ vnic_allocator : & ' a VnicAllocator < Etherstub > ,
1197
+ ) -> Self {
1198
+ self . underlay_vnic_allocator = Some ( vnic_allocator) ;
1199
+ self
1200
+ }
1201
+
1202
+ pub fn with_zone_root_path ( mut self , root_path : & ' a Utf8Path ) -> Self {
1203
+ self . zone_root_path = Some ( root_path) ;
1204
+ self
1205
+ }
1206
+
1207
+ pub fn with_zone_image_paths (
1208
+ mut self ,
1209
+ image_paths : & ' a [ Utf8PathBuf ] ,
1210
+ ) -> Self {
1211
+ self . zone_image_paths = Some ( image_paths) ;
1212
+ self
1213
+ }
1214
+
1215
+ pub fn with_zone_type ( mut self , zone_type : & ' a str ) -> Self {
1216
+ self . zone_type = Some ( zone_type) ;
1217
+ self
1218
+ }
1219
+
1220
+ pub fn with_unique_name ( mut self , uuid : Uuid ) -> Self {
1221
+ self . unique_name = Some ( uuid) ;
1222
+ self
1223
+ }
1224
+
1225
+ pub fn with_datasets ( mut self , datasets : & ' a [ zone:: Dataset ] ) -> Self {
1226
+ self . datasets = Some ( datasets) ;
1227
+ self
1228
+ }
1229
+
1230
+ pub fn with_filesystems ( mut self , filesystems : & ' a [ zone:: Fs ] ) -> Self {
1231
+ self . filesystems = Some ( filesystems) ;
1232
+ self
1233
+ }
1234
+
1235
+ pub fn with_data_links ( mut self , links : & ' a [ String ] ) -> Self {
1236
+ self . data_links = Some ( links) ;
1237
+ self
1238
+ }
1239
+
1240
+ pub fn with_devices ( mut self , devices : & ' a [ zone:: Device ] ) -> Self {
1241
+ self . devices = Some ( devices) ;
1242
+ self
1243
+ }
1244
+
1245
+ pub fn with_opte_ports ( mut self , ports : Vec < ( Port , PortTicket ) > ) -> Self {
1246
+ self . opte_ports = Some ( ports) ;
1247
+ self
1248
+ }
1249
+
1250
+ pub fn with_bootstrap_vnic ( mut self , vnic : Link ) -> Self {
1251
+ self . bootstrap_vnic = Some ( vnic) ;
1252
+ self
1253
+ }
1254
+
1255
+ pub fn with_links ( mut self , links : Vec < Link > ) -> Self {
1256
+ self . links = Some ( links) ;
1257
+ self
1258
+ }
1259
+
1260
+ pub fn with_limit_priv ( mut self , limit_priv : Vec < String > ) -> Self {
1261
+ self . limit_priv = Some ( limit_priv) ;
1262
+ self
1263
+ }
1264
+
1265
+ fn fake_install ( self ) -> Result < InstalledZone , InstallZoneError > {
1266
+ let zone = self
1267
+ . zone_type
1268
+ . ok_or ( InstallZoneError :: IncompleteBuilder ) ?
1269
+ . to_string ( ) ;
1270
+ let control_vnic = self
1271
+ . underlay_vnic_allocator
1272
+ . ok_or ( InstallZoneError :: IncompleteBuilder ) ?
1273
+ . new_control ( None )
1274
+ . map_err ( move |err| InstallZoneError :: CreateVnic { zone, err } ) ?;
1275
+ let fake_cfg = self . fake_cfg . unwrap ( ) ;
1276
+ let temp_dir = fake_cfg. temp_dir . path ( ) . to_path_buf ( ) ;
1277
+ ( || {
1278
+ let full_zone_name = InstalledZone :: get_zone_name (
1279
+ self . zone_type ?,
1280
+ self . unique_name ,
1281
+ ) ;
1282
+ let zonepath = temp_dir
1283
+ . join ( self . zone_root_path ?. strip_prefix ( "/" ) . unwrap ( ) )
1284
+ . join ( & full_zone_name) ;
1285
+ let iz = InstalledZone {
1286
+ log : self . log ?,
1287
+ zonepath,
1288
+ name : full_zone_name,
1289
+ control_vnic,
1290
+ bootstrap_vnic : self . bootstrap_vnic ,
1291
+ opte_ports : self . opte_ports ?,
1292
+ links : self . links ?,
1293
+ } ;
1294
+ let xml_path = iz. site_profile_xml_path ( ) . parent ( ) ?. to_path_buf ( ) ;
1295
+ std:: fs:: create_dir_all ( & xml_path)
1296
+ . unwrap_or_else ( |_| panic ! ( "ZoneBuilder::fake_install couldn't create site profile xml path {:?}" , xml_path) ) ;
1297
+ Some ( iz)
1298
+ } ) ( )
1299
+ . ok_or ( InstallZoneError :: IncompleteBuilder )
1300
+ }
1301
+
1302
+ pub async fn install ( self ) -> Result < InstalledZone , InstallZoneError > {
1303
+ if self . fake_cfg . is_some ( ) {
1304
+ return self . fake_install ( ) ;
1305
+ }
1306
+
1307
+ let Self {
1308
+ log : Some ( log) ,
1309
+ underlay_vnic_allocator : Some ( underlay_vnic_allocator) ,
1310
+ zone_root_path : Some ( zone_root_path) ,
1311
+ zone_image_paths : Some ( zone_image_paths) ,
1312
+ zone_type : Some ( zone_type) ,
1313
+ unique_name,
1314
+ datasets : Some ( datasets) ,
1315
+ filesystems : Some ( filesystems) ,
1316
+ data_links : Some ( data_links) ,
1317
+ devices : Some ( devices) ,
1318
+ opte_ports : Some ( opte_ports) ,
1319
+ bootstrap_vnic,
1320
+ links : Some ( links) ,
1321
+ limit_priv : Some ( limit_priv) ,
1322
+ ..
1323
+ } = self
1324
+ else {
1325
+ return Err ( InstallZoneError :: IncompleteBuilder ) ;
1326
+ } ;
1327
+
1139
1328
let control_vnic =
1140
1329
underlay_vnic_allocator. new_control ( None ) . map_err ( |err| {
1141
1330
InstallZoneError :: CreateVnic {
@@ -1144,7 +1333,8 @@ impl InstalledZone {
1144
1333
}
1145
1334
} ) ?;
1146
1335
1147
- let full_zone_name = Self :: get_zone_name ( zone_type, unique_name) ;
1336
+ let full_zone_name =
1337
+ InstalledZone :: get_zone_name ( zone_type, unique_name) ;
1148
1338
1149
1339
// Looks for the image within `zone_image_path`, in order.
1150
1340
let image = format ! ( "{}.tar.gz" , zone_type) ;
@@ -1182,7 +1372,7 @@ impl InstalledZone {
1182
1372
net_device_names. dedup ( ) ;
1183
1373
1184
1374
Zones :: install_omicron_zone (
1185
- log,
1375
+ & log,
1186
1376
& zone_root_path,
1187
1377
& full_zone_name,
1188
1378
& zone_image_path,
@@ -1209,12 +1399,6 @@ impl InstalledZone {
1209
1399
links,
1210
1400
} )
1211
1401
}
1212
-
1213
- pub fn site_profile_xml_path ( & self ) -> Utf8PathBuf {
1214
- let mut path: Utf8PathBuf = self . zonepath ( ) . into ( ) ;
1215
- path. push ( "root/var/svc/profile/site.xml" ) ;
1216
- path
1217
- }
1218
1402
}
1219
1403
1220
1404
/// Return true if the service with the given FMRI appears to be an
0 commit comments