Skip to content

Commit

Permalink
Add: Prototype for Plugin Scheduler
Browse files Browse the repository at this point in the history
This prototoype contains a plugin scheduler, which automatically resolves dependencies and enables execution of plugins either sequential or parallel
  • Loading branch information
Kraemii committed Oct 25, 2023
1 parent d9529cf commit 809d75c
Show file tree
Hide file tree
Showing 8 changed files with 888 additions and 142 deletions.
342 changes: 201 additions & 141 deletions rust/Cargo.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ members = [
"scanconfig",
"infisto",
"smoketest",
"plugin-scheduler",
"dep-graph",
]

[workspace.package]
version = "0.1.0"
edition = "2021"
license = "GPL-2.0-or-later"

11 changes: 11 additions & 0 deletions rust/plugin-scheduler/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[package]
name = "plugin-scheduler"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
dep-graph = { path = "../dep-graph" }
generic-array = "1.0"
rayon = "1.8"
144 changes: 144 additions & 0 deletions rust/plugin-scheduler/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
# Plugin Scheduler

## Plugin

A plugin is a simple struct containing an identifier, a list of identifier representing the dependencies and a Category or Phase, in which the plugin should be launched.

## Scheduler

The Scheduler uses a Collection of Plugins and a list of requested plugin identifiers to create an execution order of the requested plugins. It automatically resolves dependencies and detects errors. Plugins or dependencies containing some kind of error are not added to the scheduler, but are collected as errors.


## Usage

Here is a simple example creating a scheduler and executing plugins in parallel:

```rust
use std::{marker::PhantomData, slice::Iter, thread, time};

use generic_array::typenum::U2;
use plugin_scheduler::{
plugin::{Phase, Plugin, PluginCollection},
scheduler::PluginScheduler,
};

#[derive(PartialEq, Clone, Debug)]
enum TestCategory {
Phase1,
Phase2,
}

impl Phase for TestCategory {
type LEN = U2;

fn get(&self) -> usize {
match self {
Self::Phase1 => 0,
Self::Phase2 => 1,
}
}
}

impl TestCategory {
pub fn iterator() -> Iter<'static, TestCategory> {
static CATEGORIES: [TestCategory; 2] = [TestCategory::Phase1, TestCategory::Phase2];
CATEGORIES.iter()
}
}

#[derive(Clone)]
struct TestPlugin<C>
where
C: Phase + Clone,
{
category: C,
dependencies: Vec<String>,
name: String,
}

impl<C> Plugin<C> for TestPlugin<C>
where
C: Phase + Clone,
{
fn get_category(&self) -> C {
self.category.clone()
}

fn get_dependencies(&self) -> Vec<String> {
self.dependencies.clone()
}

fn get_id(&self) -> String {
self.name.clone()
}
}

struct TestPluginCollection<P, C>
where
P: Plugin<C>,
C: Phase,
{
plugins: Vec<P>,
phantom: PhantomData<C>,
}

impl<P, C> TestPluginCollection<P, C>
where
P: Plugin<C>,
C: Phase,
{
fn new() -> Self {
TestPluginCollection {
plugins: Default::default(),
phantom: Default::default(),
}
}

fn add(&mut self, plugin: P) {
self.plugins.push(plugin);
}
}

impl<P, C> PluginCollection<P, C> for TestPluginCollection<P, C>
where
P: Plugin<C> + Clone,
C: Phase,
{
fn get_plugin(&self, id: &str) -> Option<P> {
for plugin in &self.plugins {
if id == plugin.get_id() {
return Some(plugin.to_owned());
}
}
None
}
}

fn main() {
let mut collection = TestPluginCollection::new();

collection.add(TestPlugin {
category: TestCategory::Phase2,
dependencies: vec!["1".to_string(), "2".to_string()],
name: "0".to_string(),
});

collection.add(TestPlugin {
category: TestCategory::Phase2,
dependencies: vec!["2".to_string()],
name: "1".to_string(),
});

collection.add(TestPlugin {
category: TestCategory::Phase1,
dependencies: vec![],
name: "2".to_string(),
});

let scheduler = PluginScheduler::create(&collection, vec!["0".to_string()]);

for phase in TestCategory::iterator() {
scheduler.execute_parallel(phase.to_owned(), |script| todo!());
}
}
```
57 changes: 57 additions & 0 deletions rust/plugin-scheduler/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-FileCopyrightText: 2023 Greenbone AG
//
// SPDX-License-Identifier: GPL-2.0-or-later

use std::fmt::Display;

use crate::plugin::Phase;

#[derive(Clone, Debug)]
/// Errors, that occur during creation of the PluginScheduler
pub enum SchedulerError<C>
where
C: Phase + Clone,
{
/// A dependency cycle within the dependency chain of a plugin.
DependencyCycle(Vec<String>),

/// A plugin is missing in the PluginCollection.
PluginNotFound(Vec<String>, String),

/// An error in the plugin execution error. Plugins corresponds to Categories. These categories
/// are ran in a specific order, like category 1 runs before category 2. When a plugin of
/// category 1 has a dependency to a plugin of category 2, it is impossible to run the
/// dependency plugin before its dependant.
DependencyOrder(Vec<String>, (String, C), (String, C)),
}

impl<C> Display for SchedulerError<C>
where
C: Phase + Display + Clone,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::DependencyCycle(deps) => {
write!(f, "dependency cycle in ({})", deps.join(","))
}
Self::PluginNotFound(deps, not_found) => {
if deps.is_empty() {
write!(f, "NVT not found")
} else {
write!(f, "dependency {not_found} not found ({})", deps.join(","))
}
}
Self::DependencyOrder(deps, dependant, dependency) => {
write!(
f,
"dependency {} of category {} would run after dependant {} of category {} ({})",
dependency.0,
dependency.1,
dependant.0,
dependant.1,
deps.join(",")
)
}
}
}
}
7 changes: 7 additions & 0 deletions rust/plugin-scheduler/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-FileCopyrightText: 2023 Greenbone AG
//
// SPDX-License-Identifier: GPL-2.0-or-later

pub mod error;
pub mod plugin;
pub mod scheduler;
37 changes: 37 additions & 0 deletions rust/plugin-scheduler/src/plugin.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2023 Greenbone AG
//
// SPDX-License-Identifier: GPL-2.0-or-later

use generic_array::ArrayLength;

/// The phase is used to differentiate execution phases of Plugins. Different phases might use
/// different setups for execution.
pub trait Phase {
/// Number of Phases
type LEN: ArrayLength;
/// Get the value of a phase for indexing
fn get(&self) -> usize;
}

/// This Trait defines a Plugin used for the Plugin Scheduler.
pub trait Plugin<C>
where
C: Phase,
{
/// Returns an identifier of a Plugin
fn get_id(&self) -> String;
/// Returns a list of identifiers corresponding to the plugins dependencies
fn get_dependencies(&self) -> Vec<String>;
/// Return the category of a Plugin
fn get_category(&self) -> C;
}

/// A PluginCollection is a collection of plugins, to look them up
pub trait PluginCollection<P, C>
where
P: Plugin<C>,
C: Phase,
{
/// Search for a Plugin by an identifier
fn get_plugin(&self, id: &str) -> Option<P>;
}
Loading

0 comments on commit 809d75c

Please sign in to comment.