Skip to content

Commit

Permalink
Fix TrieNode wildcard and regexp management
Browse files Browse the repository at this point in the history
- add unit test and e2e test for wildcard behavior
- fix the TrieNode (is_empty, insert, remove, lookup_mut)
- documenting comments and variable renaming in the router module

Signed-off-by: Eloi DEMOLIS <[email protected]>
  • Loading branch information
Wonshtrum committed Oct 16, 2023
1 parent 9301048 commit 80bac33
Show file tree
Hide file tree
Showing 3 changed files with 361 additions and 37 deletions.
133 changes: 133 additions & 0 deletions e2e/src/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1409,6 +1409,131 @@ pub fn try_head() -> State {
State::Success
}

fn try_wildcard() -> State {
use sozu_command_lib::proto::command::{PathRule, RulePosition};
let front_address = create_local_address();

let (config, listeners, state) = Worker::empty_config();
let mut worker = Worker::start_new_worker("WLD_CRD", config, &listeners, state);
worker.send_proxy_request(
RequestType::AddHttpListener(
ListenerBuilder::new_http(front_address)
.to_http(None)
.unwrap(),
)
.into(),
);
worker.send_proxy_request(
RequestType::ActivateListener(ActivateListener {
address: front_address.to_string(),
proxy: ListenerType::Http.into(),
from_scm: false,
})
.into(),
);

worker.send_proxy_request(
RequestType::AddCluster(Worker::default_cluster("cluster_0", false)).into(),
);
worker.send_proxy_request(
RequestType::AddCluster(Worker::default_cluster("cluster_1", false)).into(),
);

worker.send_proxy_request(
RequestType::AddHttpFrontend(RequestHttpFrontend {
cluster_id: Some("cluster_0".to_string()),
address: front_address.to_string(),
hostname: String::from("*.sozu.io"),
path: PathRule::prefix(String::from("")),
position: RulePosition::Tree.into(),
..Default::default()
})
.into(),
);

let back_address: SocketAddr = create_local_address();
worker.send_proxy_request(
RequestType::AddBackend(Worker::default_backend(
"cluster_0",
"cluster_0-0",
back_address.to_string(),
None,
))
.into(),
);
worker.read_to_last();

let mut backend0 = SyncBackend::new(
"BACKEND_0",
back_address,
http_ok_response(format!("pong0")),
);

let mut client = Client::new(
"client",
front_address,
http_request(
"POST",
"/api",
format!("ping"),
"www.sozu.io",
),
);

backend0.connect();
client.connect();
client.send();
let accepted = backend0.accept(0);
assert!(accepted);
let request = backend0.receive(0);
println!("request: {request:?}");
backend0.send(0);
let response = client.receive();
println!("response: {response:?}");

worker.send_proxy_request(
RequestType::AddHttpFrontend(RequestHttpFrontend {
cluster_id: Some("cluster_1".to_string()),
address: front_address.to_string(),
hostname: String::from("*.sozu.io"),
path: PathRule::prefix(String::from("/api")),
position: RulePosition::Tree.into(),
..Default::default()
})
.into(),
);
let back_address: SocketAddr = create_local_address();
worker.send_proxy_request(
RequestType::AddBackend(Worker::default_backend(
"cluster_1",
"cluster_1-0",
back_address.to_string(),
None,
))
.into(),
);

let mut backend1 = SyncBackend::new(
"BACKEND_1",
back_address,
http_ok_response(format!("pong1")),
);

worker.read_to_last();

backend1.connect();

client.send();
let accepted = backend1.accept(0);
assert!(accepted);
let request = backend1.receive(0);
println!("request: {request:?}");
backend1.send(0);
let response = client.receive();
println!("response: {response:?}");

State::Success
}

#[test]
fn test_sync() {
Expand Down Expand Up @@ -1580,3 +1705,11 @@ fn test_head() {
State::Success
);
}

#[test]
fn test_wildcard() {
assert_eq!(
repeat_until_error_or(1, "Hostname with wildcard", try_wildcard),
State::Success
);
}
130 changes: 118 additions & 12 deletions lib/src/router/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ impl Router {

if let Some((_, path_rules)) = self.tree.lookup(hostname, true) {
let mut prefix_length = 0;
let mut res = None;
let mut route = None;

for (rule, method_rule, cluster_id) in path_rules {
match rule.matches(path) {
Expand All @@ -68,7 +68,7 @@ impl Router {
MethodRuleResult::Equals => return Some(cluster_id.clone()),
MethodRuleResult::All => {
prefix_length = path.len();
res = Some(cluster_id);
route = Some(cluster_id);
}
MethodRuleResult::None => {}
}
Expand All @@ -79,11 +79,11 @@ impl Router {
// FIXME: the rule order will be important here
MethodRuleResult::Equals => {
prefix_length = size;
res = Some(cluster_id);
route = Some(cluster_id);
}
MethodRuleResult::All => {
prefix_length = size;
res = Some(cluster_id);
route = Some(cluster_id);
}
MethodRuleResult::None => {}
}
Expand All @@ -93,7 +93,7 @@ impl Router {
}
}

if let Some(cluster_id) = res {
if let Some(cluster_id) = route {
return Some(cluster_id.clone());
}
}
Expand Down Expand Up @@ -493,16 +493,16 @@ pub enum PathRuleResult {
impl PathRule {
pub fn matches(&self, path: &[u8]) -> PathRuleResult {
match self {
PathRule::Prefix(s) => {
if path.starts_with(s.as_bytes()) {
PathRuleResult::Prefix(s.len())
PathRule::Prefix(prefix) => {
if path.starts_with(prefix.as_bytes()) {
PathRuleResult::Prefix(prefix.len())
} else {
PathRuleResult::None
}
}
PathRule::Regex(r) => {
PathRule::Regex(regex) => {
let start = Instant::now();
let is_a_match = r.is_match(path);
let is_a_match = regex.is_match(path);
let now = Instant::now();
time!("regex_matching_time", (now - start).whole_milliseconds());

Expand All @@ -512,8 +512,8 @@ impl PathRule {
PathRuleResult::None
}
}
PathRule::Equals(s) => {
if path == s.as_bytes() {
PathRule::Equals(pattern) => {
if path == pattern.as_bytes() {
PathRuleResult::Equals
} else {
PathRuleResult::None
Expand Down Expand Up @@ -685,6 +685,112 @@ mod tests {
);
}

#[test]
fn multiple_children_on_wildcard() {
let mut router = Router::new();

assert!(router.add_tree_rule(
b"*.sozu.io",
&PathRule::Prefix("".to_string()),
&MethodRule::new(Some("GET".to_string())),
&Route::ClusterId("base".to_string())
));
println!("{:#?}", router.tree);
assert_eq!(
router.lookup("www.sozu.io".as_bytes(), "/api".as_bytes(), &Method::Get),
Some(Route::ClusterId("base".to_string()))
);
assert!(router.add_tree_rule(
b"*.sozu.io",
&PathRule::Prefix("/api".to_string()),
&MethodRule::new(Some("GET".to_string())),
&Route::ClusterId("api".to_string())
));
println!("{:#?}", router.tree);
assert_eq!(
router.lookup("www.sozu.io".as_bytes(), "/ap".as_bytes(), &Method::Get),
Some(Route::ClusterId("base".to_string()))
);
assert_eq!(
router.lookup("www.sozu.io".as_bytes(), "/api".as_bytes(), &Method::Get),
Some(Route::ClusterId("api".to_string()))
);
}

#[test]
fn multiple_children_with_wildcard() {
let mut router = Router::new();

assert!(router.add_tree_rule(
b"*.sozu.io",
&PathRule::Prefix("".to_string()),
&MethodRule::new(Some("GET".to_string())),
&Route::ClusterId("base".to_string())
));
println!("{:#?}", router.tree);
assert_eq!(
router.lookup("www.sozu.io".as_bytes(), "/api".as_bytes(), &Method::Get),
Some(Route::ClusterId("base".to_string()))
);
assert!(router.add_tree_rule(
b"api.sozu.io",
&PathRule::Prefix("".to_string()),
&MethodRule::new(Some("GET".to_string())),
&Route::ClusterId("api".to_string())
));
println!("{:#?}", router.tree);
assert_eq!(
router.lookup("www.sozu.io".as_bytes(), "/api".as_bytes(), &Method::Get),
Some(Route::ClusterId("base".to_string()))
);
assert_eq!(
router.lookup("api.sozu.io".as_bytes(), "/api".as_bytes(), &Method::Get),
Some(Route::ClusterId("api".to_string()))
);
}

#[test]
fn router_insert_remove_through_regex() {
let mut router = Router::new();

assert!(router.add_tree_rule(
b"www./.*/.io",
&PathRule::Prefix("".to_string()),
&MethodRule::new(Some("GET".to_string())),
&Route::ClusterId("base".to_string())
));
println!("{:#?}", router.tree);
assert!(router.add_tree_rule(
b"www.doc./.*/.io",
&PathRule::Prefix("".to_string()),
&MethodRule::new(Some("GET".to_string())),
&Route::ClusterId("doc".to_string())
));
println!("{:#?}", router.tree);
assert_eq!(
router.lookup("www.sozu.io".as_bytes(), "/".as_bytes(), &Method::Get),
Some(Route::ClusterId("base".to_string()))
);
assert_eq!(
router.lookup("www.doc.sozu.io".as_bytes(), "/".as_bytes(), &Method::Get),
Some(Route::ClusterId("doc".to_string()))
);
assert!(router.remove_tree_rule(
b"www./.*/.io",
&PathRule::Prefix("".to_string()),
&MethodRule::new(Some("GET".to_string()))
));
println!("{:#?}", router.tree);
assert_eq!(
router.lookup("www.sozu.io".as_bytes(), "/".as_bytes(), &Method::Get),
None
);
assert_eq!(
router.lookup("www.doc.sozu.io".as_bytes(), "/".as_bytes(), &Method::Get),
Some(Route::ClusterId("doc".to_string()))
);
}

#[test]
fn match_router() {
let mut router = Router::new();
Expand Down
Loading

0 comments on commit 80bac33

Please sign in to comment.