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

Fix TrideNode wildcard and regexp management #1002

Merged
merged 1 commit into from
Oct 20, 2023
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
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
);
}
144 changes: 132 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,126 @@ mod tests {
);
}

/// [io]
/// \
/// [sozu]
/// \
/// [*] <- this wildcard has multiple children
/// / \
/// (base) (api)
#[test]
fn multiple_children_on_a_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()))
);
}

/// [io]
/// \
/// [sozu] <- this node has multiple children including a wildcard
/// / \
/// (api) [*] <- this wildcard has multiple children
/// \
/// (base)
#[test]
fn multiple_children_including_one_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
Loading