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

Release/0.5.0 #102

Closed
wants to merge 4 commits into from
Closed
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "factotum"
version = "0.4.1"
version = "0.5.0-rc1"
authors = ["Ed Lewis <support@snowplowanalytics.com>", "Josh Beemster <support@snowplowanalytics.com>"]

[dependencies]
Expand Down
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