Skip to content

Commit 1767074

Browse files
feat(deployer): new tool to test the control plane
Deploys all (configurable) control plane components in a local docker cluster. For more details please use the help argument and have a look at the README.md.
1 parent 211cc12 commit 1767074

File tree

16 files changed

+1076
-27
lines changed

16 files changed

+1076
-27
lines changed

Cargo.lock

+9
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

composer/src/lib.rs

+116-15
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,8 @@ pub struct ContainerSpec {
206206
/// Key-Map of environment variables
207207
/// Starts with RUST_LOG=debug,h2=info
208208
env: HashMap<String, String>,
209+
/// Volume bind dst/source
210+
binds: HashMap<String, String>,
209211
}
210212

211213
impl ContainerSpec {
@@ -259,6 +261,20 @@ impl ContainerSpec {
259261
}
260262
self
261263
}
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+
}
262278

263279
/// Environment variables as a vector with each element as:
264280
/// "{key}={value}"
@@ -312,6 +328,14 @@ impl Default for Builder {
312328
}
313329
}
314330

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+
315339
impl Builder {
316340
/// construct a new builder for `[ComposeTest']
317341
pub fn new() -> Self {
@@ -327,6 +351,41 @@ impl Builder {
327351
}
328352
}
329353

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+
330389
/// run all containers on build
331390
pub fn autorun(mut self, run: bool) -> Builder {
332391
self.autorun = run;
@@ -512,7 +571,8 @@ pub struct ComposeTest {
512571
label_prefix: String,
513572
/// automatically clean up the things we have created for this test
514573
clean: bool,
515-
pub prune: bool,
574+
/// remove existing containers upon creation
575+
prune: bool,
516576
/// base image for image-less containers
517577
image: Option<String>,
518578
/// output container logs on panic
@@ -557,14 +617,15 @@ impl ComposeTest {
557617
/// networking IP and/or subnets
558618
async fn network_create(&mut self) -> Result<NetworkId, Error> {
559619
let mut net = self.network_list_labeled().await?;
560-
561620
if !net.is_empty() {
562621
let first = net.pop().unwrap();
563622
if Some(self.name.clone()) == first.name {
564623
// reuse the same network
565624
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+
}
568629
return Ok(self.network_id.clone());
569630
} else {
570631
self.network_remove_labeled().await?;
@@ -607,7 +668,10 @@ impl ComposeTest {
607668
}
608669

609670
/// 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> {
611675
let containers = self.list_network_containers(name).await?;
612676
for k in &containers {
613677
let name = k.id.clone().unwrap();
@@ -741,13 +805,14 @@ impl ComposeTest {
741805
)
742806
.await;
743807
}
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());
745814
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),
751816
mounts: Some(vec![
752817
// DPDK needs to have a /tmp
753818
Mount {
@@ -855,8 +920,13 @@ impl ComposeTest {
855920
/// Pulls the docker image, if one is specified and is not present locally
856921
async fn pull_missing_image(&self, image: &Option<String>) {
857922
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;
860930
}
861931
}
862932
}
@@ -893,7 +963,17 @@ impl ComposeTest {
893963

894964
/// start the container
895965
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+
)?;
897977
self.docker
898978
.start_container::<&str>(id.0.as_str(), None)
899979
.await?;
@@ -904,10 +984,15 @@ impl ComposeTest {
904984
/// stop the container
905985
pub async fn stop(&self, name: &str) -> Result<(), Error> {
906986
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> {
907992
if let Err(e) = self
908993
.docker
909994
.stop_container(
910-
id.0.as_str(),
995+
id,
911996
Some(StopContainerOptions {
912997
t: 3,
913998
}),
@@ -1014,6 +1099,22 @@ impl ComposeTest {
10141099
Ok(())
10151100
}
10161101

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+
10171118
/// inspect the given container
10181119
pub async fn inspect(
10191120
&self,

nix/pkgs/mayastor/default.nix

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ let
3939
buildProps = rec {
4040
name = "mayastor";
4141
#cargoSha256 = "0000000000000000000000000000000000000000000000000000";
42-
cargoSha256 = "001a92rjffm1jc6pffmq3ci4a7ac3wxz6sbmrps67ir3chh2lv4g";
42+
cargoSha256 = "0jxi2z78kc0knr3bscyk622rg7b5ynjiw205xl6g4v8saychxpbd";
4343
inherit version;
4444
src = whitelistSource ../../../. [
4545
"Cargo.lock"

operators/node/src/main.rs

+4-6
Original file line numberDiff line numberDiff line change
@@ -8,9 +8,8 @@ use tracing::{debug, error, info, instrument};
88

99
#[derive(Debug, StructOpt)]
1010
struct CliArgs {
11-
/// The Rest Server hostname to connect to
12-
/// Default: localhost:8080
13-
#[structopt(long, short, default_value = "localhost:8080")]
11+
/// The Rest Server URL to connect to
12+
#[structopt(long, short, default_value = "https://localhost:8080")]
1413
rest: String,
1514

1615
/// Polling period
@@ -85,9 +84,8 @@ async fn main() -> anyhow::Result<()> {
8584

8685
let polling_period = CliArgs::from_args().period.into();
8786

88-
let rest_url = format!("https://{}", CliArgs::from_args().rest);
8987
let rest_cli = rest_client::ActixRestClient::new(
90-
&rest_url,
88+
&CliArgs::from_args().rest,
9189
CliArgs::from_args().jaeger.is_some(),
9290
)?;
9391

@@ -122,7 +120,7 @@ async fn polling_work(
122120
) -> anyhow::Result<()> {
123121
// Fetch all nodes as seen by the control plane via REST
124122
let rest_nodes = rest_cli.get_nodes().await?;
125-
println!("Retrieved rest nodes: {:?}", rest_nodes);
123+
debug!("Retrieved rest nodes: {:?}", rest_nodes);
126124

127125
// Fetch all node CRD's from k8s
128126
let kube_nodes = nodes_get_all(&nodes_api).await?;

rest/src/lib.rs

+23-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,21 @@ pub struct ActixRestClient {
2929

3030
impl ActixRestClient {
3131
/// creates a new client which uses the specified `url`
32+
/// uses the rustls connector if the url has the https scheme
3233
pub fn new(url: &str, trace: bool) -> anyhow::Result<Self> {
34+
let url: url::Url = url.parse()?;
35+
36+
match url.scheme() {
37+
"https" => Self::new_https(&url, trace),
38+
"http" => Self::new_http(&url, trace),
39+
invalid => {
40+
let msg = format!("Invalid url scheme: {}", invalid);
41+
Err(anyhow::Error::msg(msg))
42+
}
43+
}
44+
}
45+
/// creates a new secure client
46+
fn new_https(url: &url::Url, trace: bool) -> anyhow::Result<Self> {
3347
let cert_file = &mut BufReader::new(
3448
&std::include_bytes!("../certs/rsa/ca.cert")[..],
3549
);
@@ -46,7 +60,15 @@ impl ActixRestClient {
4660

4761
Ok(Self {
4862
client: rest_client,
49-
url: url.to_string(),
63+
url: url.to_string().trim_end_matches('/').into(),
64+
trace,
65+
})
66+
}
67+
/// creates a new client
68+
fn new_http(url: &url::Url, trace: bool) -> anyhow::Result<Self> {
69+
Ok(Self {
70+
client: Client::new(),
71+
url: url.to_string().trim_end_matches('/').into(),
5072
trace,
5173
})
5274
}

rest/tests/v0_test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ async fn client() {
8484
.autorun(false)
8585
// uncomment to leave containers running allowing us access the jaeger
8686
// traces at localhost:16686
87-
//.with_clean(false)
87+
.with_clean(false)
8888
.build()
8989
.await
9090
.unwrap();

services/Cargo.toml

+8-3
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,17 @@ path = "pool/src/server.rs"
2020
name = "volume"
2121
path = "volume/src/server.rs"
2222

23+
[[bin]]
24+
name = "deployer"
25+
path = "deployer/src/bin.rs"
26+
2327
[lib]
2428
name = "common"
2529
path = "common/src/lib.rs"
2630

2731
[dependencies]
2832
mbus_api = { path = "../mbus-api" }
33+
composer = { path = "../composer" }
2934
nats = "0.8"
3035
structopt = "0.3.15"
3136
tokio = { version = "0.2", features = ["full"] }
@@ -45,9 +50,9 @@ tracing-futures = "0.2.4"
4550
rpc = { path = "../rpc" }
4651
url = "2.2.0"
4752
http = "0.2.1"
48-
49-
[dev-dependencies]
50-
composer = { path = "../composer" }
53+
strum = "0.19"
54+
strum_macros = "0.19"
55+
paste = "1.0.4"
5156

5257
[dependencies.serde]
5358
features = ["derive"]

0 commit comments

Comments
 (0)