Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

revise: Create Unique Random Alphanumeric Generator #3

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ clap-verbosity-flag = "2.2.1"
color-eyre = "0.6.3"
dirs = "5.0.1"
eyre = "0.6.12"
fastbloom = "0.7.1"
futures = "0.3.30"
indexmap = { version = "2.5.0", features = ["serde"] }
indicatif = "0.17.8"
Expand Down
2 changes: 1 addition & 1 deletion crankshaft-config/src/backend/generic/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

use std::collections::HashMap;

use crate::backend::generic::driver;
use crate::backend::generic::Config;
use crate::backend::generic::driver;

/// An error related to a [`Builder`].
#[derive(Debug)]
Expand Down
4 changes: 2 additions & 2 deletions crankshaft-config/src/backend/generic/driver/builder.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
//! Builders for [command driver configuration](Config).

use crate::backend::generic::driver::ssh;
use crate::backend::generic::driver::Config;
use crate::backend::generic::driver::DEFAULT_MAX_ATTEMPTS;
use crate::backend::generic::driver::Locale;
use crate::backend::generic::driver::Shell;
use crate::backend::generic::driver::DEFAULT_MAX_ATTEMPTS;
use crate::backend::generic::driver::ssh;

/// A builder for a [command driver configuration object](Config).
pub struct Builder {
Expand Down
2 changes: 1 addition & 1 deletion crankshaft-config/src/backend/tes/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

use url::Url;

use crate::backend::tes::http;
use crate::backend::tes::Config;
use crate::backend::tes::http;

/// An error related to a [`Builder`].
#[derive(Debug)]
Expand Down
2 changes: 1 addition & 1 deletion crankshaft-config/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! Builders for a [global configuration object for Crankshaft](Config).

use crate::backend;
use crate::Config;
use crate::backend;

/// A builder for a [global configuration object for Crankshaft](Config).
#[derive(Default)]
Expand Down
2 changes: 1 addition & 1 deletion crankshaft-config/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@

use std::path::Path;

use config::builder::DefaultState;
use config::Config as ConfigCrate;
use config::ConfigBuilder;
use config::ConfigError as Error;
use config::Environment;
use config::File;
use config::builder::DefaultState;
use serde::Deserialize;
use serde::Serialize;

Expand Down
18 changes: 6 additions & 12 deletions crankshaft-docker/src/bin/docker-driver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,18 +113,12 @@ async fn run(args: &Args) -> Result<()> {

match &args.command {
Command::CreateContainer { image, name, tag } => {
create_container(
docker,
image,
tag,
name,
[
String::from("/usr/bin/env"),
String::from("bash"),
String::from("-c"),
String::from("echo 'hello, world!'"),
],
)
create_container(docker, image, tag, name, [
String::from("/usr/bin/env"),
String::from("bash"),
String::from("-c"),
String::from("echo 'hello, world!'"),
])
.await?;
}
Command::RunContainer {
Expand Down
4 changes: 2 additions & 2 deletions crankshaft-docker/src/container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,20 @@ use std::os::windows::process::ExitStatusExt as _;
use std::process::ExitStatus;
use std::process::Output;

use bollard::Docker;
use bollard::container::AttachContainerOptions;
use bollard::container::LogOutput;
use bollard::container::RemoveContainerOptions;
use bollard::container::StartContainerOptions;
use bollard::container::UploadToContainerOptions;
use bollard::container::WaitContainerOptions;
use bollard::Docker;
pub use builder::Builder;
use futures::TryStreamExt as _;
use tokio_stream::StreamExt as _;
use tracing::Level;
use tracing::debug;
use tracing::enabled;
use tracing::trace;
use tracing::Level;

use crate::Error;
use crate::Result;
Expand Down
2 changes: 1 addition & 1 deletion crankshaft-docker/src/container/builder.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! Builders for containers.

use bollard::Docker;
use bollard::container::Config;
use bollard::container::CreateContainerOptions;
use bollard::secret::HostConfig;
use bollard::Docker;
use tracing::warn;

use crate::Container;
Expand Down
2 changes: 1 addition & 1 deletion crankshaft-docker/src/images.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ use bollard::secret::ImageDeleteResponseItem;
use bollard::secret::ImageSummary;
use futures::stream::FuturesUnordered;
use tokio_stream::StreamExt as _;
use tracing::Level;
use tracing::debug;
use tracing::enabled;
use tracing::trace;
use tracing::Level;

use crate::Docker;
use crate::Error;
Expand Down
1 change: 1 addition & 0 deletions crankshaft-engine/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ bollard.workspace = true
crankshaft-config = { path = "../crankshaft-config", version = "0.1.0" }
crankshaft-docker = { path = "../crankshaft-docker", version = "0.1.0" }
eyre.workspace = true
fastbloom.workspace = true
futures.workspace = true
indexmap.workspace = true
indicatif.workspace = true
Expand Down
4 changes: 2 additions & 2 deletions crankshaft-engine/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
use std::time::Duration;

use crankshaft_config::backend::Config;
use futures::stream::FuturesUnordered;
use futures::StreamExt;
use futures::stream::FuturesUnordered;
use indexmap::IndexMap;
use indicatif::ProgressBar;
use indicatif::ProgressStyle;
Expand All @@ -15,9 +15,9 @@ pub mod task;

pub use task::Task;

use crate::service::Runner;
use crate::service::runner::Backend;
use crate::service::runner::TaskHandle;
use crate::service::Runner;

/// The top-level result returned within the engine.
///
Expand Down
117 changes: 102 additions & 15 deletions crankshaft-engine/src/service/name.rs
Original file line number Diff line number Diff line change
@@ -1,36 +1,123 @@
//! Name generation services.

use fastbloom::BloomFilter;
use rand::Rng;
use rand::rngs::ThreadRng;
use rand::Rng as _;

/// A name generator.
pub trait Generator {
/// Generates a new name.
fn generate(&self) -> String;
fn generate(&mut self, rng: &mut impl Rng) -> String;
}

/// An alphanumeric name generator.
pub struct Alphanumeric {
/// A unique alphanumeric name generator.
#[derive(Debug)]
pub struct UniqueAlphanumeric {
/// The length of the randomized portion of the name.
length: usize,
/// Bloom filter responsible for ensuring uniqueness of these names
bloom_filter: BloomFilter,
}

impl Default for Alphanumeric {
fn default() -> Self {
Self { length: 12 }
impl Generator for UniqueAlphanumeric {
fn generate(&mut self, rng: &mut impl Rng) -> String {
loop {
let random: String = rng
.sample_iter(&rand::distributions::Alphanumeric)
.take(self.length)
.map(char::from)
.collect();

if !self.bloom_filter.contains(&random) {
self.bloom_filter.insert(&random);
return random;
}
}
}
}

impl UniqueAlphanumeric {
/// Default construction for a UniqueAlphanumeric generator with a given
/// estimated amount of generations it will need to complete.
pub fn default_with_expected_generations(expected: usize) -> Self {
Self {
length: 12,
bloom_filter: BloomFilter::with_false_pos(0.001).expected_items(expected),
}
}
}

/// An iterator over some generic generator
#[derive(Debug)]
pub struct GeneratorIterator<G: Generator> {
/// The underlying generator
generator: G,

/// The buffer holding generated data
buffer: Vec<String>,
}

impl<G: Generator> Iterator for GeneratorIterator<G> {
type Item = String;

fn next(&mut self) -> Option<Self::Item> {
if let Some(pregenerated) = self.buffer.pop() {
Some(pregenerated)
} else {
self.rehydrate();
self.buffer.pop()
}
}
}

impl Generator for Alphanumeric {
fn generate(&self) -> String {
impl<G: Generator> GeneratorIterator<G> {
/// Creates a new [`GeneratorIterator`] with the provided capacity.
pub fn new(generator: G, capacity: usize) -> Self {
Self {
generator,
buffer: Vec::with_capacity(capacity),
}
}

/// Rehydrates the underlying buffer by repeatedly generating unique
/// alphanumeric strings.
fn rehydrate(&mut self) {
let mut rng = ThreadRng::default();
for _ in 0..self.buffer.capacity() {
let generated = self.generator.generate(&mut rng);
self.buffer.push(generated);
}
}
}

#[cfg(test)]
mod tests {
use std::collections::HashSet;

use super::GeneratorIterator;
use super::UniqueAlphanumeric;

#[test]
fn unique_alphanumeric_generator_is_truly_unique() {
let alphanumeric: UniqueAlphanumeric =
UniqueAlphanumeric::default_with_expected_generations(100_000);
let mut generator: GeneratorIterator<_> = GeneratorIterator::new(alphanumeric, 100_000);
let _ = generator.next();
let unique = HashSet::<&String>::from_iter(generator.buffer.iter());

assert_eq!(generator.buffer.len(), unique.len())
}

#[test]
fn unique_alphanumeric_generator_rehydrates_when_empty() {
let alphanumeric: UniqueAlphanumeric =
UniqueAlphanumeric::default_with_expected_generations(10);
let mut generator: GeneratorIterator<_> = GeneratorIterator::new(alphanumeric, 10);

let random: String = (&mut rng)
.sample_iter(&rand::distributions::Alphanumeric)
.take(self.length) // Generate 12 alphanumeric characters
.map(char::from)
.collect();
for _ in 0..10 {
assert!(generator.next().is_some())
}

format!("job-{}", random)
assert!(generator.next().is_some())
}
}
Loading