@@ -206,6 +206,8 @@ pub struct ContainerSpec {
206
206
/// Key-Map of environment variables
207
207
/// Starts with RUST_LOG=debug,h2=info
208
208
env : HashMap < String , String > ,
209
+ /// Volume bind dst/source
210
+ binds : HashMap < String , String > ,
209
211
}
210
212
211
213
impl ContainerSpec {
@@ -259,6 +261,20 @@ impl ContainerSpec {
259
261
}
260
262
self
261
263
}
264
+ /// use a volume binds between host path and container container
265
+ pub fn with_bind ( mut self , host : & str , container : & str ) -> Self {
266
+ self . binds . insert ( container. to_string ( ) , host. to_string ( ) ) ;
267
+ self
268
+ }
269
+
270
+ /// List of volume binds with each element as host:container
271
+ fn binds ( & self ) -> Vec < String > {
272
+ let mut vec = vec ! [ ] ;
273
+ self . binds . iter ( ) . for_each ( |( container, host) | {
274
+ vec. push ( format ! ( "{}:{}" , host, container) ) ;
275
+ } ) ;
276
+ vec
277
+ }
262
278
263
279
/// Environment variables as a vector with each element as:
264
280
/// "{key}={value}"
@@ -312,6 +328,14 @@ impl Default for Builder {
312
328
}
313
329
}
314
330
331
+ /// trait to allow extensibility using the Builder pattern
332
+ pub trait BuilderConfigure {
333
+ fn configure (
334
+ & self ,
335
+ cfg : Builder ,
336
+ ) -> Result < Builder , Box < dyn std:: error:: Error > > ;
337
+ }
338
+
315
339
impl Builder {
316
340
/// construct a new builder for `[ComposeTest']
317
341
pub fn new ( ) -> Self {
@@ -327,6 +351,41 @@ impl Builder {
327
351
}
328
352
}
329
353
354
+ /// get the name of the experiment
355
+ pub fn get_name ( & self ) -> String {
356
+ self . name . clone ( )
357
+ }
358
+
359
+ /// configure the `Builder` using the `BuilderConfigure` trait
360
+ pub fn configure (
361
+ self ,
362
+ cfg : impl BuilderConfigure ,
363
+ ) -> Result < Builder , Box < dyn std:: error:: Error > > {
364
+ cfg. configure ( self )
365
+ }
366
+
367
+ /// next ordinal container ip
368
+ pub fn next_container_ip ( & self ) -> Result < String , Error > {
369
+ let net: Ipv4Network = self . network . parse ( ) . map_err ( |error| {
370
+ bollard:: errors:: Error :: IOError {
371
+ err : std:: io:: Error :: new (
372
+ std:: io:: ErrorKind :: InvalidInput ,
373
+ format ! ( "Invalid network format: {}" , error) ,
374
+ ) ,
375
+ }
376
+ } ) ?;
377
+ let ip = net. nth ( ( self . containers . len ( ) + 2 ) as u32 ) ;
378
+ match ip {
379
+ None => Err ( bollard:: errors:: Error :: IOError {
380
+ err : std:: io:: Error :: new (
381
+ std:: io:: ErrorKind :: AddrNotAvailable ,
382
+ "No available ip" ,
383
+ ) ,
384
+ } ) ,
385
+ Some ( ip) => Ok ( ip. to_string ( ) ) ,
386
+ }
387
+ }
388
+
330
389
/// run all containers on build
331
390
pub fn autorun ( mut self , run : bool ) -> Builder {
332
391
self . autorun = run;
@@ -512,7 +571,8 @@ pub struct ComposeTest {
512
571
label_prefix : String ,
513
572
/// automatically clean up the things we have created for this test
514
573
clean : bool ,
515
- pub prune : bool ,
574
+ /// remove existing containers upon creation
575
+ prune : bool ,
516
576
/// base image for image-less containers
517
577
image : Option < String > ,
518
578
/// output container logs on panic
@@ -557,14 +617,15 @@ impl ComposeTest {
557
617
/// networking IP and/or subnets
558
618
async fn network_create ( & mut self ) -> Result < NetworkId , Error > {
559
619
let mut net = self . network_list_labeled ( ) . await ?;
560
-
561
620
if !net. is_empty ( ) {
562
621
let first = net. pop ( ) . unwrap ( ) ;
563
622
if Some ( self . name . clone ( ) ) == first. name {
564
623
// reuse the same network
565
624
self . network_id = first. id . unwrap ( ) ;
566
- // but clean up the existing containers
567
- self . remove_network_containers ( & self . name ) . await ?;
625
+ if self . prune {
626
+ // but clean up the existing containers
627
+ self . remove_network_containers ( & self . name ) . await ?;
628
+ }
568
629
return Ok ( self . network_id . clone ( ) ) ;
569
630
} else {
570
631
self . network_remove_labeled ( ) . await ?;
@@ -607,7 +668,10 @@ impl ComposeTest {
607
668
}
608
669
609
670
/// remove all containers from the network
610
- async fn remove_network_containers ( & self , name : & str ) -> Result < ( ) , Error > {
671
+ pub async fn remove_network_containers (
672
+ & self ,
673
+ name : & str ,
674
+ ) -> Result < ( ) , Error > {
611
675
let containers = self . list_network_containers ( name) . await ?;
612
676
for k in & containers {
613
677
let name = k. id . clone ( ) . unwrap ( ) ;
@@ -741,13 +805,14 @@ impl ComposeTest {
741
805
)
742
806
. await ;
743
807
}
744
-
808
+ let mut binds = vec ! [
809
+ format!( "{}:{}" , self . srcdir, self . srcdir) ,
810
+ "/nix:/nix:ro" . into( ) ,
811
+ "/dev/hugepages:/dev/hugepages:rw" . into( ) ,
812
+ ] ;
813
+ binds. extend ( spec. binds ( ) ) ;
745
814
let host_config = HostConfig {
746
- binds : Some ( vec ! [
747
- format!( "{}:{}" , self . srcdir, self . srcdir) ,
748
- "/nix:/nix:ro" . into( ) ,
749
- "/dev/hugepages:/dev/hugepages:rw" . into( ) ,
750
- ] ) ,
815
+ binds : Some ( binds) ,
751
816
mounts : Some ( vec ! [
752
817
// DPDK needs to have a /tmp
753
818
Mount {
@@ -855,8 +920,13 @@ impl ComposeTest {
855
920
/// Pulls the docker image, if one is specified and is not present locally
856
921
async fn pull_missing_image ( & self , image : & Option < String > ) {
857
922
if let Some ( image) = image {
858
- if !self . image_exists ( image) . await {
859
- self . pull_image ( image) . await ;
923
+ let image = if !image. contains ( ':' ) {
924
+ format ! ( "{}:latest" , image)
925
+ } else {
926
+ image. clone ( )
927
+ } ;
928
+ if !self . image_exists ( & image) . await {
929
+ self . pull_image ( & image) . await ;
860
930
}
861
931
}
862
932
}
@@ -893,7 +963,17 @@ impl ComposeTest {
893
963
894
964
/// start the container
895
965
pub async fn start ( & self , name : & str ) -> Result < ( ) , Error > {
896
- let id = self . containers . get ( name) . unwrap ( ) ;
966
+ let id = self . containers . get ( name) . ok_or (
967
+ bollard:: errors:: Error :: IOError {
968
+ err : std:: io:: Error :: new (
969
+ std:: io:: ErrorKind :: NotFound ,
970
+ format ! (
971
+ "Can't start container {} as it was not configured" ,
972
+ name
973
+ ) ,
974
+ ) ,
975
+ } ,
976
+ ) ?;
897
977
self . docker
898
978
. start_container :: < & str > ( id. 0 . as_str ( ) , None )
899
979
. await ?;
@@ -904,10 +984,15 @@ impl ComposeTest {
904
984
/// stop the container
905
985
pub async fn stop ( & self , name : & str ) -> Result < ( ) , Error > {
906
986
let id = self . containers . get ( name) . unwrap ( ) ;
987
+ self . stop_id ( id. 0 . as_str ( ) ) . await
988
+ }
989
+
990
+ /// stop the container by its id
991
+ pub async fn stop_id ( & self , id : & str ) -> Result < ( ) , Error > {
907
992
if let Err ( e) = self
908
993
. docker
909
994
. stop_container (
910
- id. 0 . as_str ( ) ,
995
+ id,
911
996
Some ( StopContainerOptions {
912
997
t : 3 ,
913
998
} ) ,
@@ -1014,6 +1099,22 @@ impl ComposeTest {
1014
1099
Ok ( ( ) )
1015
1100
}
1016
1101
1102
+ /// stop all the containers part of the network
1103
+ /// returns the last error, if any or Ok
1104
+ pub async fn stop_network_containers ( & self ) -> Result < ( ) , Error > {
1105
+ let mut result = Ok ( ( ) ) ;
1106
+ let containers = self . list_network_containers ( & self . name ) . await ?;
1107
+ for container in containers {
1108
+ if let Some ( id) = container. id {
1109
+ if let Err ( e) = self . stop_id ( & id) . await {
1110
+ println ! ( "Failed to stop container id {:?}" , id) ;
1111
+ result = Err ( e) ;
1112
+ }
1113
+ }
1114
+ }
1115
+ result
1116
+ }
1117
+
1017
1118
/// inspect the given container
1018
1119
pub async fn inspect (
1019
1120
& self ,
0 commit comments