Skip to content
This repository has been archived by the owner on Aug 13, 2024. It is now read-only.

Commit

Permalink
Add "server mode" to Factotum (closes #98) - WIP
Browse files Browse the repository at this point in the history
  • Loading branch information
ungn committed Apr 10, 2017
1 parent af443fa commit b25f147
Show file tree
Hide file tree
Showing 16 changed files with 2,020 additions and 0 deletions.
18 changes: 18 additions & 0 deletions factotum-server/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Compiled files
*.o
*.so
*.rlib
*.dll

# Executables
*.exe

# Generated by Cargo
/target/

# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
# More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
Cargo.lock
.factotum
.vagrant

27 changes: 27 additions & 0 deletions factotum-server/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
[package]
name = "factotum-server"
version = "0.1.0"
authors = ["Nicholas Ung <support@snowplowanalytics.com>"]

[dependencies]
log = "0.3"
log4rs = "0.6"
docopt = "0.7"
getopts = "0.2"
chrono = { version = "0.3", features = ["serde"] }
lazy_static = "0.2"
regex = "0.2"
url = "1.4"
rust-crypto = "^0.2"
threadpool = "1.3"
iron = "0.5"
router = "0.5"
bodyparser = "0.6"
persistent = "0.3"
logger = "0.3"
rustc-serialize = "0.3"
serde = "0.9"
serde_derive = "0.9"
serde_json = "0.9"
consul = "0.0.6"
base64 = "0.4"
75 changes: 75 additions & 0 deletions factotum-server/src/factotum_server/command/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
// Copyright (c) 2016-2017 Snowplow Analytics Ltd. All rights reserved.
//
// This program is licensed to you under the Apache License Version 2.0, and
// you may not use this file except in compliance with the Apache License
// Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
// http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License Version 2.0 is distributed on an "AS
// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the Apache License Version 2.0 for the specific language
// governing permissions and limitations there under.
//

use std::collections::HashMap;
use std::process::Command;

macro_rules! commands {
($( $key: expr => $val: expr ),*) => {{
let mut map = ::std::collections::HashMap::new();
$( map.insert($key, $val); )*
::factotum_server::command::CommandStore::new(map)
}}
}

#[cfg(test)]
mod tests;

pub trait Execution {
fn get_command(&self, command: &str) -> Result<String, String>;
fn execute(&self, cmd_path: String, cmd_args: Vec<String>) -> Result<String, String>;
}

#[derive(Clone, Debug)]
pub struct CommandStore {
pub command_map: HashMap<String, String>
}

impl CommandStore {
pub fn new(commands: HashMap<String, String>) -> CommandStore {
CommandStore {
command_map: commands
}
}
}

impl Execution for CommandStore {
fn get_command(&self, command: &str) -> Result<String, String> {
match self.command_map.get(command) {
Some(command) => Ok(command.to_owned()),
None => Err(format!("Command <{}> not found in map.", command))
}
}

fn execute(&self, cmd_path: String, cmd_args: Vec<String>) -> Result<String, String> {
let command_str = format!("{} {}", cmd_path, cmd_args.join(" "));
debug!("Executing: [{}]", command_str);
let failed_command_msg = format!("Failed to execute command: [{}]", command_str);
match Command::new(cmd_path)
.args(&cmd_args)
.output()
{
Ok(output) => {
if output.status.success() {
let stdout = String::from_utf8_lossy(&output.stdout);
Ok(stdout.into_owned())
} else {
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("{} - {}", failed_command_msg, stderr.into_owned()))
}
}
Err(e) => Err(format!("{} - {}", failed_command_msg, e))
}
}
}
48 changes: 48 additions & 0 deletions factotum-server/src/factotum_server/command/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Copyright (c) 2016-2017 Snowplow Analytics Ltd. All rights reserved.
//
// This program is licensed to you under the Apache License Version 2.0, and
// you may not use this file except in compliance with the Apache License
// Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
// http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License Version 2.0 is distributed on an "AS
// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the Apache License Version 2.0 for the specific language
// governing permissions and limitations there under.
//

use super::*;

#[test]
fn create_command_store_macro() {
let command_store = commands!["dummy".to_string() => "/tmp/fake_command".to_string()];
assert_eq!(command_store.command_map.contains_key("dummy"), true);
assert_eq!(command_store.command_map.contains_key("other_dummy"), false);
}

#[test]
fn command_store_get_command_success() {
let command_store = commands!["dummy".to_string() => "/tmp/fake_command".to_string()];
assert_eq!(command_store.get_command("dummy"), Ok("/tmp/fake_command".to_string()));
}

#[test]
fn command_store_get_command_error() {
let command_store = CommandStore::new(HashMap::new());
assert_eq!(command_store.get_command("dummy"), Err("Command <dummy> not found in map.".to_string()));
}

#[test]
fn command_store_execute_fail() {
let command_store = commands!["dummy".to_string() => "/tmp/fake_command".to_string()];
let output = command_store.execute("/tmp/fake_command".to_string(), vec!["--random_arg".to_string()]).unwrap_err();
assert_eq!(output, "Failed to execute command: [/tmp/fake_command --random_arg] - No such file or directory (os error 2)");
}

#[test]
fn command_store_execute_illegal_option() {
let command_store = commands!["dummy".to_string() => "/tmp/fake_command".to_string()];
let output = command_store.execute("pwd".to_string(), vec!["--random_arg".to_string()]).unwrap_err();
assert_eq!(output, "Failed to execute command: [pwd --random_arg] - pwd: unrecognized option \'--random_arg\'\nTry \'pwd --help\' for more information.\n");
}
70 changes: 70 additions & 0 deletions factotum-server/src/factotum_server/dispatcher/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) 2016-2017 Snowplow Analytics Ltd. All rights reserved.
//
// This program is licensed to you under the Apache License Version 2.0, and
// you may not use this file except in compliance with the Apache License
// Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
// http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License Version 2.0 is distributed on an "AS
// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the Apache License Version 2.0 for the specific language
// governing permissions and limitations there under.
//

#[cfg(test)]
mod tests;

use std::collections::VecDeque;
use std::sync::mpsc::Sender;
use factotum_server::server::JobRequest;
use factotum_server::responder::DispatcherStatus;

#[derive(Debug, PartialEq)]
pub enum Dispatch {
StatusUpdate(Query<DispatcherStatus>),
CheckQueue(Query<bool>),
NewRequest(JobRequest),
ProcessRequest,
RequestComplete(JobRequest),
RequestFailure(JobRequest),
StopProcessing,
}

#[derive(Debug)]
pub struct Dispatcher {
pub max_jobs: usize,
pub max_workers: usize,
pub requests_queue: VecDeque<JobRequest>,
}

impl Dispatcher {
pub fn new(queue_size: usize, workers_size: usize) -> Dispatcher {
Dispatcher {
max_jobs: if queue_size > 0 { queue_size } else { ::MAX_JOBS_DEFAULT },
max_workers: if workers_size > 0 { workers_size } else { ::MAX_WORKERS_DEFAULT },
requests_queue: VecDeque::with_capacity(queue_size),
}
}
}

#[derive(Debug)]
pub struct Query<T> {
pub name: String,
pub status_tx: Sender<T>,
}

impl<T> Query<T> {
pub fn new(name: String, status_tx: Sender<T>) -> Query<T> {
Query {
name: name,
status_tx: status_tx,
}
}
}

impl<T> PartialEq for Query<T> {
fn eq(&self, other: &Query<T>) -> bool {
self.name == other.name
}
}
24 changes: 24 additions & 0 deletions factotum-server/src/factotum_server/dispatcher/tests.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
// Copyright (c) 2016-2017 Snowplow Analytics Ltd. All rights reserved.
//
// This program is licensed to you under the Apache License Version 2.0, and
// you may not use this file except in compliance with the Apache License
// Version 2.0. You may obtain a copy of the Apache License Version 2.0 at
// http://www.apache.org/licenses/LICENSE-2.0.
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the Apache License Version 2.0 is distributed on an "AS
// IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied. See the Apache License Version 2.0 for the specific language
// governing permissions and limitations there under.
//

use super::*;

#[test]
fn create_new_dispatcher() {
let dispatcher = Dispatcher::new(10, 2);

assert_eq!(dispatcher.max_jobs, 10);
assert_eq!(dispatcher.max_workers, 2);
assert!(dispatcher.requests_queue.is_empty());
}
Loading

0 comments on commit b25f147

Please sign in to comment.