From 2f9ac2daa9ec3ac75b1d986d26109d5e6d45ec63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Thu, 12 Mar 2026 09:20:39 +0100 Subject: [PATCH 01/13] test-perl: Add a new set of MCMP tests --- test-perl/t/mcmp/app.t | 405 +++++++++++++++++++++++++++++++++++++ test-perl/t/mcmp/config.t | 280 +++++++++++++++++++++++++ test-perl/t/mcmp/dump.t | 100 +++++++++ test-perl/t/mcmp/info.t | 89 ++++++++ test-perl/t/mcmp/ping.t | 110 ++++++++++ test-perl/t/mcmp/status.t | 118 +++++++++++ test-perl/t/mcmp/version.t | 38 ++++ 7 files changed, 1140 insertions(+) create mode 100644 test-perl/t/mcmp/app.t create mode 100644 test-perl/t/mcmp/config.t create mode 100644 test-perl/t/mcmp/dump.t create mode 100644 test-perl/t/mcmp/info.t create mode 100644 test-perl/t/mcmp/ping.t create mode 100644 test-perl/t/mcmp/status.t create mode 100644 test-perl/t/mcmp/version.t diff --git a/test-perl/t/mcmp/app.t b/test-perl/t/mcmp/app.t new file mode 100644 index 00000000..17a5c208 --- /dev/null +++ b/test-perl/t/mcmp/app.t @@ -0,0 +1,405 @@ +# Before 'make install' is performed this script should be runnable with +# 'make test'. After 'make install' it should work as 'perl Apache-ModProxyCluster.t' +######################### + +use strict; +use warnings; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestConfig; +use Apache::TestServer; +use Apache::TestRequest 'GET'; + +use ModProxyCluster; + +# get the fake cgi app host and port +Apache::TestRequest::module("fake_cgi_app"); +my ($apphost, $appport) = split ':', Apache::TestRequest::hostport(); +Apache::TestRequest::module("mpc_test_host"); + +plan tests => 366, need_mpc; + + +foreach my $cmd ('ENABLE-APP', 'STOP-APP', 'DISABLE-APP', 'REMOVE-APP') { + # missing context and alias + my $resp = CMD $cmd, { JVMRoute => 'no-app' }; + ok $resp->is_error; + # TODO: Check the error message + + # missing only context + $resp = CMD $cmd, { JVMRoute => 'no-app', Context => "mycontext" }; + ok $resp->is_error; + # TODO: Check the error message + + + # missing only alias + $resp = CMD $cmd, { JVMRoute => 'no-app', Alias => "myalias" }; + ok $resp->is_error; + # TODO: Check the error message + + + # try the command without an existing node + $resp = CMD $cmd, { JVMRoute => 'no-app', Context => "mycontext", Alias => "myalias" }; + ok $resp->is_error; + # TODO: Check the error message +} + +# Add a node with the fake cgi app +my $resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport }; +ok $resp->is_success; + +# Check that the app is present +$resp = GET "http://$apphost:$appport/"; +ok $resp->is_success; +ok t_cmp($resp->content, qr/Fake App!/, "Reach the app directly"); + +# Check that the context (pointing to the app) is not present +$resp = GET "/news"; +ok $resp->is_error; + +# Now enable the app +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/ENABLE-APP-RSP/); + +# Check that the app is present & accessible through the proxy +$resp = GET "/news"; +ok $resp->is_success; +ok t_cmp($resp->content, qr/Fake App!/, "Reach the app through the proxy"); + +# Check the app is reported & its state is ENABLED +$resp = CMD 'INFO'; +ok $resp->is_success; +my %p = parse_response 'INFO', $resp->content; + +ok t_cmp($p{Contexts}->[0]{Context}, "/news"); +ok t_cmp($p{Contexts}->[0]{Status}, "ENABLED"); + +# Now DISABLE the app & check it's unreachable but present +$resp = CMD 'DISABLE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/DISABLE-APP-RSP/); + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp($p{Contexts}->[0]{Context}, "/news"); +ok t_cmp($p{Contexts}->[0]{Status}, "DISABLED"); + +# Now STOP, so basically the same, but the Status is different +$resp = CMD 'STOP-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/STOP-APP-RSP/); + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp($p{Contexts}->[0]{Context}, "/news"); +ok t_cmp($p{Contexts}->[0]{Status}, "STOPPED"); + +# Let's ENABLE it again +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; + +# Check that the app is present +$resp = GET "/news"; +ok $resp->is_success; +ok t_cmp($resp->content, qr/Fake App!/, "The app is reachable again"); + +# Check the app is reported & its state is ENABLED +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp($p{Contexts}->[0]{Context}, "/news"); +ok t_cmp($p{Contexts}->[0]{Status}, "ENABLED"); + +# Now we REMOVE the app +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/REMOVE-APP-RSP/); + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Contexts}}, 0, "There are no longer any contexts"); +ok t_cmp(@{$p{Hosts}}, 0, "There are no longer any aliases"); +# but the node is still there +ok t_cmp(@{$p{Nodes}}, 1, "The node is still present but with no Contexts or Aliases"); + +# Now check that DISABLE and STOP add the app back (with the right status) but REMOVE doesn't +# 1) DISABLE +$resp = CMD 'DISABLE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp($p{Contexts}->[0]{Context}, "/news"); +ok t_cmp($p{Contexts}->[0]{Status}, "DISABLED"); + +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Contexts}}, 0, "There are no longer any contexts"); +ok t_cmp(@{$p{Hosts}}, 0, "There are no longer any aliases"); +# but the node is still there +ok t_cmp(@{$p{Nodes}}, 1, "The node is still present but with no Contexts or Aliases"); + +# 2) STOP +$resp = CMD 'STOP-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp($p{Contexts}->[0]{Context}, "/news"); +ok t_cmp($p{Contexts}->[0]{Status}, "STOPPED"); + +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; + +# 3) REMOVE +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "localhost" }; +ok $resp->is_success; + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Contexts}}, 0, "There are no longer any contexts"); +ok t_cmp(@{$p{Hosts}}, 0, "There are no aliases"); + +# ######################################### # +# Now let's test wildcarded/global commands # +# ######################################### # +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => "/one,/two", Alias => "localhost,example.com" }; +ok t_cmp($resp->is_success, 1, "ENABLE-APP with 2 contexts in 2 aliases should be ok"); + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 1, "There's a single node"); +ok t_cmp(@{$p{Hosts}}, 2, "There are two aliases"); +ok t_cmp(@{$p{Contexts}}, 4, "There are four contexts (2 per alias)"); + +foreach my $context (@{$p{Contexts}}) { + ok t_cmp($context->{Status}, 'ENABLED', "All contexts are enabled"); +} + +$resp = CMD 'DISABLE-APP', { JVMRoute => "fake-app" }, "/*"; +ok t_cmp($resp->is_success, 1, "DISABLE-APP with 2 contexts in 2 aliases should be ok"); + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +ok t_cmp(@{$p{Contexts}}, 4, "There are still four contexts (2 per alias)"); + +foreach my $context (@{$p{Contexts}}) { + ok t_cmp($context->{Status}, 'DISABLED', "All contexts are disabled"); +} + +$resp = CMD 'STOP-APP', { JVMRoute => "fake-app" }, "/*"; +ok t_cmp($resp->is_success, 1, "STOP-APP with 2 contexts in 2 aliases should be ok"); + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +ok t_cmp(@{$p{Contexts}}, 4, "There are still four contexts (2 per alias)"); + +foreach my $context (@{$p{Contexts}}) { + ok t_cmp($context->{Status}, 'STOPPED', "All contexts are stopped"); +} + +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app" }, "/*"; +ok t_cmp($resp->is_success, 1, "ENABLE-APP should bring us back to where we started"); + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +ok t_cmp(@{$p{Contexts}}, 4, "There are four contexts (2 per alias)"); +foreach my $context (@{$p{Contexts}}) { + ok t_cmp($context->{Status}, 'ENABLED', "All contexts are enabled again"); +} + +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app" }, "/*"; +ok t_cmp($resp->is_success, 1, "REMOVE-APP should clean everything now with the wildcard"); + +sleep 11; # necessary to see the removal + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +ok t_cmp(@{$p{Contexts}}, 0, "There are no longer any contexts"); +ok t_cmp(@{$p{Hosts}}, 0, "There are no aliases"); +ok t_cmp(@{$p{Nodes}}[0]->{Name}, 'REMOVED', "Even the node was removed"); + +# ############################################################### # +# Now let's try bunch of aliases and contexts in a single command # +# ############################################################### # +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport }; +ok $resp->is_success; + +# Make sure the node gets routed, no 503 +CMD 'STATUS', { JVMRoute => "fake-app", Load => 100 }; + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 1, "There is a single node"); +ok t_cmp(@{$p{Hosts}}, 0, "The alias got removed"); +ok t_cmp(@{$p{Contexts}}, 0, "The context got removed"); + +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => "/first,/news,/last", Alias => "news.example.com,myhost,example.com" }; +ok t_cmp($resp->is_success, 1, "ENABLE-APP with 3 contexts and 3 aliases should be ok"); + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 1, "There is a single node"); +ok t_cmp(@{$p{Hosts}}, 3, "There should be 3 aliases"); +ok t_cmp(@{$p{Contexts}}, 9, "There should be 9 contexts (3 per each of the 3 aliases)"); + +$resp = GET "/news"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /news"); +$resp = GET "/first"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /first"); +$resp = GET "/last"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /last"); + +# Now do the same, but we give the list as repeated entries for a given parameter +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $resp->is_success; + +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport }; +ok $resp->is_success; + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'INFO'; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 1, "The node remains present"); +ok t_cmp(@{$p{Hosts}}, 0, "The alias got removed"); +ok t_cmp(@{$p{Contexts}}, 0, "The context got removed"); + +$resp = CMD_internal 'ENABLE-APP', '/', 'JVMRoute=fake-app&Context=/first&Context=/news&Alias=news.example.com&Alias=myhost&Alias=example.com&Context=/last'; +ok t_cmp($resp->is_success, 1, "ENABLE-APP with 3 contexts and aliases as repeated parameters should be ok too"); + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 1, "The node remains present"); +ok t_cmp(@{$p{Hosts}}, 3, "There should be 3 aliases"); +ok t_cmp(@{$p{Contexts}}, 9, "There should be 9 contexts (3 per each of the 3 aliases)"); + +$resp = GET "/news"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /news"); +$resp = GET "/first"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /first"); +$resp = GET "/last"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /last"); + +# Now do the same again, but this time we'll send one command per each context+alias +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $resp->is_success; + +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport }; +ok $resp->is_success; + +$resp = GET "/news"; +ok $resp->is_error; + +foreach my $alias ("news.example.com", "myhost", "example.com") { + foreach my $context ("/first", "/news", "/last") { + $resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => $context, Alias => $alias }; + ok t_cmp($resp->is_success, 1, "ENABLE-APP with context $context and alias $alias should be ok"); + + $resp = GET $context; + ok t_cmp($resp->is_success, 1, "The app should be available again for $context"); + } +} + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 1, "There is the node still present"); +ok t_cmp(@{$p{Hosts}}, 3, "There should be 3 aliases"); +ok t_cmp(@{$p{Contexts}}, 9, "There should be 9 contexts (3 per each of the 3 aliases)"); + + +# We now add the node again and add more contexts that is the capacity. We check that the command +# does not fail, it adds all but the last context and these are reachable. +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $resp->is_success; + +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport }; +ok $resp->is_success; + +my @contexts = map { "/context$_" } (0..100); + +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => join(',', @contexts), Alias => "localhost" }; +ok t_cmp($resp->is_success, 1, "ENABLE-APP ok for context a set of contexts"); + +# check that the missing context is missing & not reachable +my $missing_context = pop @contexts; +ok t_cmp($resp->content, qr/(?!$missing_context)/, "Context $missing_context is missing"); +my $rsp = GET $missing_context; +ok t_cmp($rsp->is_error, 1, "$missing_context is missing and thus not reachable: " . $rsp->code); + +# check that the rest is there & reachable +foreach my $present_context (@contexts) { + ok t_cmp($resp->content, qr/$present_context(,|\n)/, "Checking $present_context is present"); + $rsp = GET $present_context; + ok t_cmp($rsp->is_success, 1, "$present_context is reachable: " . $rsp->code); +} + +# Clean after yourself by a simple restart of the server +END { + my $ret = $?; + my $cfg = Apache::Test::config(); + my $server = $cfg->server; + $server->stop(); + $server->start(); + $? = $ret; +} + diff --git a/test-perl/t/mcmp/config.t b/test-perl/t/mcmp/config.t new file mode 100644 index 00000000..2fd20020 --- /dev/null +++ b/test-perl/t/mcmp/config.t @@ -0,0 +1,280 @@ +# Before 'make install' is performed this script should be runnable with +# 'make test'. After 'make install' it should work as 'perl Apache-ModProxyCluster.t' +######################### + +use strict; +use warnings; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestConfig; +use Apache::TestRequest 'GET'; + +use ModProxyCluster; + +# get the fake cgi app host and port +Apache::TestRequest::module("fake_cgi_app"); +my ($apphost, $appport) = split ':', Apache::TestRequest::hostport(); +Apache::TestRequest::module("mpc_test_host"); + + +plan tests => 310, need_mpc; + + +# CONFIG wihtout JVMRoute should fail +my $resp = CMD 'CONFIG'; +ok $resp->is_error; +# TODO: Check the error message + +$resp = CMD 'INFO'; +ok $resp->is_success; +my %p = parse_response 'INFO', $resp->content; +ok t_cmp(@{$p{Nodes}}, 0, "There are no nodes after failed CONFIG (1)"); + +# CONFIG with Alias without Context should fail and vice-versa +$resp = CMD 'CONFIG', { JVMRoute => "route", Context => "/mycontext" }; +ok $resp->is_error; +# TODO: Check the error message +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +ok t_cmp(@{$p{Nodes}}, 0, "There are no nodes after failed CONFIG (2)"); + +$resp = CMD 'CONFIG', { JVMRoute => "route", Alias => "myalias" }; +ok $resp->is_error; +# TODO: Check the error message +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +ok t_cmp(@{$p{Nodes}}, 0, "There are no nodes after failed CONFIG (3)"); + + +# Add CONFIG with route only +$resp = CMD 'CONFIG', { JVMRoute => "route" }; +ok $resp->is_success; + +$resp = CMD 'INFO'; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 1, "There is a single node"); +ok t_cmp(@{$p{Contexts}}, 0, "There are no contexts"); +ok t_cmp(@{$p{Hosts}}, 0, "There are no Aliases"); + + +# Now we'll add a node with an app and check what a repeated CONFIG does in case of changed: +# - i) node config parameters: only those parameters that can change, app remains working +# - ii) other parameteres: the current node conflicts with the CONFIG, so the CONFIG is denied +# +# TODO: What about following? - ii) other parameteres: the current node with the app gets removed and a new one gets added +# +# + if there's a combination of i) and ii), it behaves as ii). +# + if the JVMRoute differs but ii) overlaps with an existing node, that's a BAD request (and should be denied) +my @node_config = ( { opt => "Flushpackets", value => "on" } + , { opt => "Flushwait", value => "30000" } + , { opt => "Ping", value => "15" } + , { opt => "Timeout", value => "10" } + , { opt => "StickySession", value => "no" } + , { opt => "StickySessionCookie", value => "MYCOOKIE" } + , { opt => "StickySessionPath", value => "mycookie" } + , { opt => "StickySessionRemove", value => "yes" } + , { opt => "StickySessionForce", value => "no" } + , { opt => "WaitWorker", value => "5" } + , { opt => "MaxAttempts", value => "3" } + , { opt => "Domain", value => "example.com" } + ); + +# we'll skip JVMRoute here because that's the special case +# as well as Context + Alias that must go together +my @other_config = ( { opt => "Balancer", value => "myotherbalancer" } + , { opt => "Host", value => "myhost" } + , { opt => "Port", value => "8888" } + , { opt => "Type", value => "ajp" } + , { opt => "Reversed", value => "yes" } + , { opt => "Smax", value => "5" } + , { opt => "Ttl", value => "72" } + ); + +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport }; +ok $resp->is_success; + +$resp = GET "/news"; +ok t_cmp($resp->is_error, 1, "The /news context is not available through proxy without ENABLE-APP"); + +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "myalias" }; +ok $resp->is_success; + +$resp = GET "/news"; +ok t_cmp($resp->is_success, 1, "The /news context became available through proxy after ENABLE-APP"); + +$resp = CMD 'INFO'; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 2, "There are two nodes now"); +ok t_cmp(@{$p{Contexts}}, 1, "There is the /news context"); +ok t_cmp(@{$p{Hosts}}, 1, "There is the myalias alias"); + +# i) +foreach my $param (@node_config) { + $resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, + Port => $appport, $param->{opt} => $param->{value} }; + ok $resp->is_success; + $resp = GET "/news"; + ok t_cmp($resp->is_success, 1, "Successful response after CONFIG with " . %$param{opt}); +} + +# ii) +foreach my $param (@other_config) { + # to prevent multiple records of the same parameter, let's do it this way + my %default = ( JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport ); + # this will override the default if present + $default{$param->{opt}} = $param->{value}; + + $resp = CMD 'CONFIG', \%default; + ok $resp->is_error; + + $resp = GET "/news"; + ok t_cmp($resp->is_success, 1, "The /news endpoint should be still accessible after config with $param->{opt}=$param->{value}"); + + # TODO: Reconsider whether this does not make more sense (see the ii) alternative above) + # ok $resp->is_success; + # $resp = GET "/news"; + # ok t_cmp($resp->is_error, 1, "CONFIG with " . %$param{opt} . " should remove/add node, thus removing the app"); + # + # The following is not tested as the new parameters would lead to an error (modified node is not reachable) + # $resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => "/news", Alias => "myalias" }; + # ok $resp->is_success; + # $resp = GET "/news"; + # ok $resp->is_success; +} + + +# TODO: Isn't this about APP commands here??? Try CONFIG with Context and ALIAS instead! + +# Remove the app and add it with multiple entries for context and alias +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $resp->is_success; + +$resp = GET "/news"; +ok $resp->is_error; + +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport + , Context => "/first,/news,/last", Alias => "news.example.com,myhost,example.com" }; +ok $resp->is_success; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 2, "There are two nodes now"); +ok t_cmp(@{$p{Hosts}}, 3, "There should be 3 aliases"); +ok t_cmp(@{$p{Contexts}}, 9, "There should be 9 contexts (3 per each of the 3 aliases)"); + +# The following should fail, because all contexts are STOPPED by default +$resp = GET "/news"; +ok t_cmp($resp->is_error, 1, "The app should NOT be available again for /news"); +$resp = GET "/first"; +ok t_cmp($resp->is_error, 1, "The app should NOT be available again for /first"); +$resp = GET "/last"; +ok t_cmp($resp->is_error, 1, "The app should NOT be available again for /last"); + +foreach my $context (@{$p{Contexts}}) { + ok t_cmp($context->{Status}, 'STOPPED', "All contexts are enabled"); +} + +# Enable all the contexts +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $resp->is_success; + +$resp = GET "/news"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /news"); +$resp = GET "/first"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /first"); +$resp = GET "/last"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /last"); + +# Now do the same, but we give the list as repeated entries for a given parameter +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $resp->is_success; + +$resp = CMD_internal 'CONFIG', '/', "JVMRoute=fake-app&Type=http&Host=$apphost&Port=$appport&Context=/first&Context=/news&Alias=news.example.com&Alias=myhost&Alias=example.com&Context=/last"; +ok $resp->is_success; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 2, "There are two nodes now"); +ok t_cmp(@{$p{Hosts}}, 3, "There should be 3 aliases"); +ok t_cmp(@{$p{Contexts}}, 9, "There should be 9 contexts (3 per each of the 3 aliases)"); + +# The following should fail, because all contexts are STOPPED by default +$resp = GET "/news"; +ok t_cmp($resp->is_error, 1, "The app should NOT be available again for /news"); +$resp = GET "/first"; +ok t_cmp($resp->is_error, 1, "The app should NOT be available again for /first"); +$resp = GET "/last"; +ok t_cmp($resp->is_error, 1, "The app should NOT be available again for /last"); + +foreach my $context (@{$p{Contexts}}) { + ok t_cmp($context->{Status}, 'STOPPED', "All contexts are enabled"); +} + +# Enable all the contexts +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $resp->is_success; + +$resp = GET "/news"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /news"); +$resp = GET "/first"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /first"); +$resp = GET "/last"; +ok t_cmp($resp->is_success, 1, "The app should be available again for /last"); + +# Check that we get an error with CONFIG when number of contexts exceeds the limit +# BUT also that the successfully added contexts are working. +## First clean everything +$resp = CMD 'REMOVE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $resp->is_success; + +my @contexts = map { "/context$_" } (0..100); + +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Host => $apphost, Port => $appport + , Context => join(',', @contexts), Alias => "localhost" }; +ok t_cmp($resp->is_error, 1, "CONFIG gives an error because the last Context couldn't be added"); + +# check that the failed context is reported and is not reachable +my $failed_context = pop @contexts; +ok t_cmp($resp->header('Mess'), qr/\($failed_context and alias:/, "Context $failed_context is reported as failed"); + +# before trying to reach it, we must ENABLE everything (the default state is STOPPED!) +my $rsp = CMD 'ENABLE-APP', { JVMRoute => "fake-app" }, "/*"; +ok $rsp->is_success; + +$rsp = CMD 'INFO'; +ok $rsp->is_success; +%p = parse_response 'INFO', $rsp->content; +ok t_cmp(@{$p{Contexts}}, 100, "There should be 100 contexts"); + +$rsp = GET $failed_context; +ok t_cmp($rsp->is_error, 1, "$failed_context is not reachable: " . $rsp->code); + +# check that the rest is there & reachable +foreach my $present_context (@contexts) { + ok t_cmp($resp->header('Mess'), qr/(?!\($present_context and alias:)/, "Check that $present_context was not reported in CONFIG error message"); + $rsp = GET $present_context; + ok t_cmp($rsp->is_success, 1, "$present_context is reachable: " . $rsp->code); +} + + +# TODO: Check the individual parameters ranges + +# Clean after yourself by a simple restart of the server +END { + my $ret = $?; + my $cfg = Apache::Test::config(); + my $server = $cfg->server; + $server->stop(); + $server->start(); + $? = $ret; +} + diff --git a/test-perl/t/mcmp/dump.t b/test-perl/t/mcmp/dump.t new file mode 100644 index 00000000..32b4040d --- /dev/null +++ b/test-perl/t/mcmp/dump.t @@ -0,0 +1,100 @@ +# Before 'make install' is performed this script should be runnable with +# 'make test'. After 'make install' it should work as 'perl Apache-ModProxyCluster.t' +######################### + +use strict; +use warnings; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestConfig; +use Apache::TestRequest 'GET'; + +use ModProxyCluster; + +Apache::TestRequest::module("fake_cgi_app"); +my ($apphost, $appport) = split ':', Apache::TestRequest::hostport(); +Apache::TestRequest::module("mpc_test_host"); + +plan tests => 47, need_mpc; + + +# DUMP with no nodes +my $resp = CMD 'DUMP'; + +ok $resp->is_success; +ok t_cmp($resp->content, ""); +my %p = parse_response 'DUMP', $resp->content; + +# There might be a balancer in case of previously run tests (removal of all nodes keep balancers in, currently) +ok t_cmp(@{$p{Balancers}}, 0, "No balancer in DUMP output for empty cluster"); +ok t_cmp(@{$p{Nodes}}, 0, "No nodes in DUMP output for empty cluster"); +ok t_cmp(@{$p{Contexts}}, 0, "No contexts in DUMP output for empty cluster"); +ok t_cmp(@{$p{Hosts}}, 0, "No hosts in DUMP output for empty cluster"); + +# Now let's add a node +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Scheme => "http", Port => $appport, Host => $apphost }; +ok $resp->is_success; + +$resp = CMD 'DUMP'; +ok $resp->is_success; +%p = parse_response 'DUMP', $resp->content; + +ok t_cmp(@{$p{Balancers}}, 1, "The default balancer in DUMP output"); +ok t_cmp(@{$p{Nodes}}, 1, "Single node in DUMP output for empty cluster"); +ok t_cmp(@{$p{Contexts}}, 0, "No contexts in DUMP output for empty cluster"); +ok t_cmp(@{$p{Hosts}}, 0, "No hosts in DUMP output for empty cluster"); + +ok t_cmp($p{Nodes}->[0]{JVMRoute}, "fake-app"); + +# Add Context + Alias +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => "/context", Alias => "myalias" }; +ok $resp->is_success; + +$resp = CMD 'DUMP'; +ok $resp->is_success; +%p = parse_response 'DUMP', $resp->content; + +ok t_cmp(@{$p{Balancers}}, 1, "The default balancer in DUMP output"); +ok t_cmp(@{$p{Nodes}}, 1, "Single nodes in DUMP output for empty cluster"); +ok t_cmp(@{$p{Contexts}}, 1, "Single context in DUMP output for empty cluster"); +ok t_cmp(@{$p{Hosts}}, 1, "Single host in DUMP output for empty cluster"); + +ok t_cmp($p{Contexts}->[0]{path}, "/context"); +ok t_cmp($p{Hosts}->[0]{alias}, "myalias"); + +# Check that we are not missing any parameters +# 1) balancers +my @balancer_dump = qw( balancer Name Sticky remove Timeout maxAttempts ); +foreach my $opt (@balancer_dump) { + ok (exists $p{Balancers}->[0]{$opt}); +} + +# 2) nodes +my @node_dump = qw( node Balancer JVMRoute LBGroup Host Port Type flushpackets flushwait ping smax ttl timeout ); +foreach my $opt (@node_dump) { + ok (exists $p{Nodes}->[0]{$opt}); +} + +# 3) hosts +my @host_dump = qw( host vhost node ); +foreach my $opt (@host_dump) { + ok (exists $p{Hosts}->[0]{$opt}); +} + +# 4) contexts +my @context_dump = qw( context vhost node status ); +foreach my $opt (@context_dump) { + ok (exists $p{Contexts}->[0]{$opt}); +} + +# Clean after yourself by a simple restart of the server +END { + my $ret = $?; + my $cfg = Apache::Test::config(); + my $server = $cfg->server; + $server->stop(); + $server->start(); + $? = $ret; +} + diff --git a/test-perl/t/mcmp/info.t b/test-perl/t/mcmp/info.t new file mode 100644 index 00000000..c709a4a7 --- /dev/null +++ b/test-perl/t/mcmp/info.t @@ -0,0 +1,89 @@ +# Before 'make install' is performed this script should be runnable with +# 'make test'. After 'make install' it should work as 'perl Apache-ModProxyCluster.t' +######################### + +use strict; +use warnings; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestConfig; +use Apache::TestRequest 'GET'; + +use ModProxyCluster; + +# get the fake cgi app host and port +Apache::TestRequest::module("fake_cgi_app"); +my ($apphost, $appport) = split ':', Apache::TestRequest::hostport(); +Apache::TestRequest::module("mpc_test_host"); + +plan tests => 40, need_mpc; + + +my $resp = CMD 'INFO'; +ok $resp->is_success; +ok t_cmp($resp->content, "", "INFO for an empty cluster should be empty"); + +# check that the parse_response is empty too +my %p = parse_response 'INFO', $resp->content; +ok t_cmp(@{$p{Nodes}}, 0, "Empty cluster has no Nodes"); +ok t_cmp(@{$p{Contexts}}, 0, "Empty cluster has no Contexts"); +ok t_cmp(@{$p{Hosts}}, 0, "Empty cluster has no Aliases"); + +# Now let's add a node +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Scheme => "http", Port => $appport, Host => $apphost }; +ok $resp->is_success; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok t_cmp(@{$p{Nodes}}, 1, "CONFIG added one node"); +ok t_cmp(@{$p{Contexts}}, 0, "No Contexts were added"); +ok t_cmp(@{$p{Hosts}}, 0, "No Aliases were added"); + +ok t_cmp($p{Nodes}->[0]{Name}, "fake-app"); + +# Check only that the values are present, we should check defaults elsewhere +my @node_info = qw( Node Name Balancer LBGroup Host Port Type Flushpackets Flushwait Ping Smax Ttl Elected Read Transfered Connected Load ); +foreach my $opt (@node_info) { + ok (exists $p{Nodes}->[0]{$opt}); +} + +# Add Context + Alias +$resp = CMD 'ENABLE-APP', { JVMRoute => "fake-app", Context => "/context", Alias => "myalias" }; +ok $resp->is_success; + +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; + +ok (@{$p{Nodes}} == 1); +ok (@{$p{Contexts}} == 1); +ok (@{$p{Hosts}} == 1); +ok t_cmp($p{Nodes}->[0]{Name}, "fake-app"); +ok t_cmp($p{Contexts}->[0]{Context}, "/context"); +ok t_cmp($p{Hosts}->[0]{Alias}, "myalias"); + +# Check again Context's and Alias' properties +my @alias_info = qw( Vhost Alias ); +foreach my $opt (@alias_info) { + ok (exists $p{Hosts}->[0]{$opt}); +} + +# TODO: In reality, INFO sends 3, but Context is there TWICE! +my @context_info = qw( Context Status ); +foreach my $opt (@context_info) { + ok (exists $p{Contexts}->[0]{$opt}); +} + +# Clean after yourself by a simple restart of the server +END { + my $ret = $?; + my $cfg = Apache::Test::config(); + my $server = $cfg->server; + $server->stop(); + $server->start(); + $? = $ret; +} + diff --git a/test-perl/t/mcmp/ping.t b/test-perl/t/mcmp/ping.t new file mode 100644 index 00000000..23b5e3d7 --- /dev/null +++ b/test-perl/t/mcmp/ping.t @@ -0,0 +1,110 @@ +# Before 'make install' is performed this script should be runnable with +# 'make test'. After 'make install' it should work as 'perl Apache-ModProxyCluster.t' +######################### + +use strict; +use warnings; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestConfig; +use Apache::TestRequest 'GET'; + +use ModProxyCluster; + +# get the fake cgi app host and port +Apache::TestRequest::module("fake_cgi_app"); +my ($apphost, $appport) = split ':', Apache::TestRequest::hostport(); +Apache::TestRequest::module("mpc_test_host"); + +plan tests => 25, need_mpc; + +# first, install two nodes, we'll use them later +my $resp = CMD 'CONFIG', { JVMRoute => "route" }; +ok t_cmp($resp->is_success, 1, "Adding route node"); + +$resp = CMD 'CONFIG', { JVMRoute => "route2", Type => "http", Port => $appport, Host => $apphost }; +ok t_cmp($resp->is_success, 1, "Adding route2 node"); + +# first wait for sync +sleep 2; + +# the default without any params +$resp = CMD 'PING'; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=PING-RSP&State=OK&id=\d+$/); + +# with JVMRoute +$resp = CMD 'PING', { JVMRoute => "route" }; +ok $resp->is_success; +# The node `route` does not exist, so it will be NOTOK +ok t_cmp($resp->content, qr/^Type=PING-RSP&JVMRoute=route&State=NOTOK&id=\d+$/, "PING response for 'route'"); + +$resp = CMD 'PING', { JVMRoute => "route2" }; +ok $resp->is_success; +# The node `route2` corresponds to the fake app, so it should be OK +ok t_cmp($resp->content, qr/^Type=PING-RSP&JVMRoute=route2&State=OK&id=\d+$/, "PING response for 'route2'"); + +# with Host + Port + Scheme +$resp = CMD 'PING', { Scheme => "http", Port => $appport, Host => $apphost }; +ok t_cmp($resp->is_success, 1); +# Same as previously +ok t_cmp($resp->content, qr/^Type=PING-RSP&State=OK&id=\d+$/, "PING response for 'http://$apphost:$appport'"); + +# with Host + Port + Scheme, but now with upgrade (On by default) +$resp = CMD 'PING', { Scheme => "ws", Port => $appport, Host => $apphost }; +ok t_cmp($resp->is_success, 1); +# Same as previously +ok t_cmp($resp->content, qr/^Type=PING-RSP&State=OK&id=\d+$/, "PING response for 'ws://$apphost:$appport'"); + +# only JVMRoute is used +$resp = CMD 'PING', { JVMRoute => "route", Scheme => "http", Port => $appport, Host => $apphost }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=PING-RSP&JVMRoute=route&State=NOTOK&id=\d+$/, "PING response with all arguments behaves as with JVMRoute only"); + + +# incorrect commands -> nonexisting JVMRoute +$resp = CMD 'PING', { JVMroute => "nonexsiting-route" }; +ok $resp->is_error; +# TODO: Check the error message (cannot read the given node) + +# incorrect commands -> something is missing (won't get an error but State=NOTOK) +$resp = CMD 'PING', { Scheme => "http", Port => $appport }; +ok $resp->is_error; +# ok t_cmp($resp->content, qr/^Type=PING-RSP&State=NOTOK&id=\d+$/); + +$resp = CMD 'PING', { Scheme => "http", Host => $apphost }; +ok $resp->is_error; +# ok t_cmp($resp->content, qr/^Type=PING-RSP&State=NOTOK&id=\d+$/); + +$resp = CMD 'PING', { Host => $apphost, Port => $appport }; +ok $resp->is_error; +# ok t_cmp($resp->content, qr/^Type=PING-RSP&State=NOTOK&id=\d+$/); + +# Scheme is not supported +$resp = CMD 'PING', { Scheme => "udp", Host => $apphost, Port => $appport }; +ok $resp->is_error; + +# incorrect commands -> nonexisting hots/ports/schemes +# Scheme does not exist; TODO: Is this the right approach? +$resp = CMD 'PING', { Scheme => "ajp", Host => $apphost, Port => $appport }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=PING-RSP&State=NOTOK&id=\d+$/, "Scheme does not match the node"); + +# Host does not exist; TODO: Is this the right approach? +$resp = CMD 'PING', { Scheme => "http", Host => "nonexisting.host", Port => $appport }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=PING-RSP&State=NOTOK&id=\d+$/, "Host does not match the node"); + +# Port does not exist; TODO: Is this the right approach? +$resp = CMD 'PING', { Scheme => "http", Host => $apphost, Port => 1234 }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=PING-RSP&State=NOTOK&id=\d+$/, "Port does not match the node"); + + +# Clean after yourself by a simple restart of the server +END { + remove_nodes "route", "route2"; + sleep 25; +} + diff --git a/test-perl/t/mcmp/status.t b/test-perl/t/mcmp/status.t new file mode 100644 index 00000000..9025a411 --- /dev/null +++ b/test-perl/t/mcmp/status.t @@ -0,0 +1,118 @@ +# Before 'make install' is performed this script should be runnable with +# 'make test'. After 'make install' it should work as 'perl Apache-ModProxyCluster.t' +######################### + +use strict; +use warnings; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestConfig; +use Apache::TestRequest 'GET'; + +use ModProxyCluster; + +# get the fake cgi app host and port +Apache::TestRequest::module("fake_cgi_app"); +my ($apphost, $appport) = split ':', Apache::TestRequest::hostport(); +Apache::TestRequest::module("mpc_test_host"); + +plan tests => 30, need_mpc; + + +my $resp = CMD 'CONFIG', { JVMRoute => "host" }; +ok $resp->is_success; + +$resp = CMD 'CONFIG', { JVMRoute => "fake-app", Type => "http", Port => $appport, Host => $apphost }; +ok $resp->is_success; + +# Status with no node specified +$resp = CMD 'STATUS'; +ok t_cmp($resp->is_error, 1, "STATUS should fail when missing JVMRoute parameter"); + +# Status with non-existing node +$resp = CMD 'STATUS', { JVMRoute => "nonexsiting" }; +ok t_cmp($resp->is_error, 1, "STATUS should fail for nonexisting node"); + +# Ask about the STATUS of the two nodes +$resp = CMD 'STATUS', { JVMRoute => "host" }; +ok $resp->is_success; +# Might be NOTOK because we created the host by CONFIG without anything being there +ok t_cmp($resp->content, qr/^Type=STATUS-RSP&JVMRoute=host&State=(NOTOK|OK)&id=\d+$/, "Check for STATUS-RSP content (host)"); + +$resp = CMD 'STATUS', { JVMRoute => "fake-app" }; +ok $resp->is_success; +# This is going to be ok, since there is the fake app behing it +ok t_cmp($resp->content, qr/^Type=STATUS-RSP&JVMRoute=fake-app&State=OK&id=\d+$/, "Check for STATUS-RSP content (fake-app)"); + +# Now with Load set +$resp = CMD 'STATUS', { JVMRoute => "fake-app", Load => 0 }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=STATUS-RSP&JVMRoute=fake-app&State=OK&id=\d+$/, "After Load=0"); +# Check it was set +$resp = CMD 'INFO'; +ok $resp->is_success; +my %p = parse_response 'INFO', $resp->content; +# TODO: Nodes should be probably addressable by their route +ok t_cmp($p{Nodes}->[0]{Load}, 0, "Load should be 0 for node $p{Nodes}->[0]{Name}"); + +$resp = CMD 'STATUS', { JVMRoute => "fake-app", Load => 100 }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=STATUS-RSP&JVMRoute=fake-app&State=OK&id=\d+$/, "After Load=100"); +# Check it was set +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +# TODO: Nodes should be probably addressable by their route +ok t_cmp($p{Nodes}->[0]{Load}, 100, "Load should be 100 for node $p{Nodes}->[0]{Name}"); + +$resp = CMD 'STATUS', { JVMRoute => "fake-app", Load => -1 }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=STATUS-RSP&JVMRoute=fake-app&State=OK&id=\d+$/, "After Load=-1"); +# Check it was set +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +# TODO: Nodes should be probably addressable by their route +ok t_cmp($p{Nodes}->[0]{Load}, -1, "Load should be -1 for node $p{Nodes}->[0]{Name}"); + +$resp = CMD 'STATUS', { JVMRoute => "fake-app", Load => 50 }; +ok $resp->is_success; +ok t_cmp($resp->content, qr/^Type=STATUS-RSP&JVMRoute=fake-app&State=OK&id=\d+$/, "After Load=50"); +# Check it was set +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +# TODO: Nodes should be probably addressable by their route +ok t_cmp($p{Nodes}->[0]{Load}, 50, "Load should be 50 for node $p{Nodes}->[0]{Name}"); + +# What about Load outside of the allowed range? +$resp = CMD 'STATUS', { JVMRoute => "fake-app", Load => 101 }; +ok $resp->is_error; +# Check it was set +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +# TODO: Nodes should be probably addressable by their route +ok t_cmp($p{Nodes}->[0]{Load}, 50, "Load remains, Load=101 was ignored for $p{Nodes}->[0]{Name}"); + +$resp = CMD 'STATUS', { JVMRoute => "fake-app", Load => -2 }; +ok $resp->is_error; +# Check it was set +$resp = CMD 'INFO'; +ok $resp->is_success; +%p = parse_response 'INFO', $resp->content; +# TODO: Nodes should be probably addressable by their route +ok t_cmp($p{Nodes}->[0]{Load}, 50, "Load remains, Load=-2 was ignored for node $p{Nodes}->[0]{Name}"); + + +# Clean after yourself by a simple restart of the server +END { + my $ret = $?; + my $cfg = Apache::Test::config(); + my $server = $cfg->server; + $server->stop(); + $server->start(); + $? = $ret; +} + diff --git a/test-perl/t/mcmp/version.t b/test-perl/t/mcmp/version.t new file mode 100644 index 00000000..c241e903 --- /dev/null +++ b/test-perl/t/mcmp/version.t @@ -0,0 +1,38 @@ +# Before 'make install' is performed this script should be runnable with +# 'make test'. After 'make install' it should work as 'perl Apache-ModProxyCluster.t' +######################### + +use strict; +use warnings; + +use Apache::Test; +use Apache::TestUtil; +use Apache::TestConfig; +use Apache::TestRequest 'GET'; + +use ModProxyCluster; +Apache::TestRequest::module("mpc_test_host"); + +plan tests => 7, need_mpc; + +my $resp = CMD 'VERSION'; +ok $resp->is_success; + +my $sw_info_pattern = '(mod_(?:proxy_)?cluster)\/(\d+\.\d+\.\d+\.(Dev|Alpha\d+|Beta\d+|CR\d+|Final))'; + +my ($sw_name, $sw_version, $proto_version) = $resp->content =~ /release: $sw_info_pattern, protocol: (\d+\.\d+\.\d+)/; + +ok (defined $sw_name); +ok (defined $sw_version); +ok (defined $proto_version); + +$resp = GET "/mod_cluster_manager"; +# resp should contain sw name and version too, check it for consistency: mod_cluster/2.0.0.Alpha1-SNAPSHOT +my ($manager_sw_name, $manager_sw_version) = $resp->content =~ /$sw_info_pattern/; + +ok t_cmp($sw_name, $manager_sw_name); +ok t_cmp($sw_version, $manager_sw_version); + +my ($reported_major, $reported_minor, $reported_patch, $reported_string) = mpc_version(); +ok t_cmp("$reported_major.$reported_minor.$reported_patch.$reported_string", $sw_version); + From 67c011ad8c1140b9b1c73da4a2e87d6aa7088222 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Fri, 27 Mar 2026 18:18:43 +0100 Subject: [PATCH 02/13] test-perl: Fix missing VERSION method among MCMP commands --- test-perl/lib/ModProxyCluster.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-perl/lib/ModProxyCluster.pm b/test-perl/lib/ModProxyCluster.pm index e940a3f6..642d23a2 100644 --- a/test-perl/lib/ModProxyCluster.pm +++ b/test-perl/lib/ModProxyCluster.pm @@ -179,7 +179,7 @@ sub parse_DUMP { sub CMD { my ($cmd, $params, $path) = @_; my @mpc_commands = qw(CONFIG ENABLE-APP DISABLE-APP STOP-APP REMOVE-APP STOP-APP-RSP - STATUS STATUS-RSP INFO INFO-RSP DUMP DUMP-RSP PING PING-RSP); + STATUS STATUS-RSP INFO INFO-RSP DUMP DUMP-RSP PING PING-RSP VERSION); $path = '' if not defined $path; $params = {} if not defined $params; From b5bade35cb21ad468a6e985673212fb4ee24cce8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Tue, 21 Apr 2026 17:29:48 +0200 Subject: [PATCH 03/13] test-perl: Fix current tests, tweak logging --- test-perl/t/conf/cgi.conf.in | 6 ++-- test-perl/t/fake_cgi_app.t | 11 +++---- test-perl/t/issues/MODCLUSTER-824.t | 12 ++++---- .../t/mod_proxy_cluster/config_time_params.t | 1 + test-perl/t/mod_proxy_cluster/messages_ok.t | 30 ++++++++++++------- 5 files changed, 36 insertions(+), 24 deletions(-) diff --git a/test-perl/t/conf/cgi.conf.in b/test-perl/t/conf/cgi.conf.in index 3cdb8e51..63b545b1 100644 --- a/test-perl/t/conf/cgi.conf.in +++ b/test-perl/t/conf/cgi.conf.in @@ -10,11 +10,13 @@ LoadModule rewrite_module modules/mod_rewrite.so - + AllowOverride None Require all granted RewriteEngine On - RewriteRule ^(.*)$ /cgi-bin/test.pl?url=$1 [R] + # to prevent infinite loops/redirects + RewriteCond %{REQUEST_URI} !^/cgi-bin/test\.pl + RewriteRule ^(.*)$ /cgi-bin/test.pl?url=$1 [L,PT] diff --git a/test-perl/t/fake_cgi_app.t b/test-perl/t/fake_cgi_app.t index 2736396c..d45ea60c 100644 --- a/test-perl/t/fake_cgi_app.t +++ b/test-perl/t/fake_cgi_app.t @@ -16,10 +16,11 @@ plan tests => 3; Apache::TestRequest::module("fake_cgi_app"); my $hostport = Apache::TestRequest::hostport(); -my $url = "http://$hostport/news"; -my $data = GET $url; +my $url = "http://$hostport/"; +my $resp = GET $url; -ok $data->is_success; +ok $resp->is_success; + +ok t_cmp($resp->content, qr/REDIRECT_STATUS --> 200/); +ok t_cmp($resp->content, qr/Fake App!/); -ok (index($data->as_string, "REDIRECT_") == -1); -ok (index($data->as_string, "Fake App!") != -1); diff --git a/test-perl/t/issues/MODCLUSTER-824.t b/test-perl/t/issues/MODCLUSTER-824.t index f754ac11..679cba88 100644 --- a/test-perl/t/issues/MODCLUSTER-824.t +++ b/test-perl/t/issues/MODCLUSTER-824.t @@ -21,8 +21,6 @@ my $resp = CMD 'INFO'; ok $resp->is_success; my %p = parse_response 'INFO', $resp->content; -my $host_count = scalar @{$p{Hosts}}; - $resp = CMD 'CONFIG', { JVMRoute => 'modcluster824' }; ok $resp->is_success; @@ -33,7 +31,7 @@ $resp = CMD 'INFO'; ok $resp->is_success; %p = parse_response 'INFO', $resp->content; -ok (@{$p{Hosts}} == $host_count + 2); +ok t_cmp(@{$p{Hosts}}, 2, "Two aliases are present"); $resp = CMD 'ENABLE-APP', { JVMRoute => 'modcluster824', Context => '/news', Alias => "beta,testalias" }; ok $resp->is_success; @@ -43,7 +41,8 @@ ok $resp->is_success; %p = parse_response 'INFO', $resp->content; # count should increase only by one -ok (@{$p{Hosts}} == $host_count + 3); +my @ls = map { $_->{Alias} } @{$p{Hosts}}; +ok t_cmp(@{$p{Hosts}}, 3, "A third alias was added (merged with the previous configuration: @ls)"); $resp = CMD 'ENABLE-APP', { JVMRoute => 'modcluster824', Context => '/news', Alias => "completely,unrelated" }; ok $resp->is_success; @@ -53,7 +52,8 @@ ok $resp->is_success; %p = parse_response 'INFO', $resp->content; # two new vhosts should be created -ok (@{$p{Hosts}} == $host_count + 5); +@ls = map { $_->{Alias} } @{$p{Hosts}}; +ok t_cmp(@{$p{Hosts}}, 5, "Another two aliases were added (@ls)"); # Clean after yourself remove_nodes 'modcluster824'; @@ -62,7 +62,7 @@ sleep 25; # just to make sure we'll have enough time to get it removed $resp = CMD 'INFO'; ok $resp->is_success; %p = parse_response 'INFO', $resp->content; -ok (@{$p{Hosts}} == $host_count); +ok t_cmp(@{$p{Hosts}}, 0, "Everything is gone"); END { remove_nodes 'modcluster824'; diff --git a/test-perl/t/mod_proxy_cluster/config_time_params.t b/test-perl/t/mod_proxy_cluster/config_time_params.t index 96308254..5ff7e695 100644 --- a/test-perl/t/mod_proxy_cluster/config_time_params.t +++ b/test-perl/t/mod_proxy_cluster/config_time_params.t @@ -39,6 +39,7 @@ ok($node->{timeout} == 0); # Clean after yourself remove_nodes 'test-time'; +sleep 25; # to have enough time for the removal ################################## ### Check custom valid values ### diff --git a/test-perl/t/mod_proxy_cluster/messages_ok.t b/test-perl/t/mod_proxy_cluster/messages_ok.t index d71ed51c..8dd75197 100644 --- a/test-perl/t/mod_proxy_cluster/messages_ok.t +++ b/test-perl/t/mod_proxy_cluster/messages_ok.t @@ -14,7 +14,12 @@ use Apache::TestRequest 'GET'; use ModProxyCluster; Apache::TestRequest::module("mpc_test_host"); -plan tests => 214, need_mpc; + +my $extra_tests = 0; +my $version1 = (mpc_version())[0] == 1; +$extra_tests = 9 if ($version1); + +plan tests => 203 + $extra_tests, need_mpc; my $resp = GET "/"; ok $resp->is_success; @@ -69,8 +74,8 @@ ok (@{$p{Contexts}} == 0); ok (@{$p{Hosts}} == 0); ## TODO: Make this implementation independent, i.e., we should not care whether indexing starts by 0 or 1... -ok ($p{Nodes}->[0]{Name} eq 'next'); -ok ($p{Nodes}->[1]{Name} eq 'spare'); +my @names = map { $_->{Name} } @{$p{Nodes}}; +ok t_cmp([sort @names], ['next', 'spare']); # All returned by INFO (+ Node: [%d]) my @info_opts = qw( Name Balancer LBGroup Host Port Type Flushpackets Flushwait Ping Smax Ttl Elected Read Transfered Connected Load ); @@ -198,7 +203,7 @@ ok $resp->is_success; $resp = CMD 'ENABLE-APP', { JVMRoute => 'app', Context => '/news', Alias => $apphost }; ok $resp->is_success; -ok ($resp->content eq ""); +ok ($resp->content eq "") if $version1; $resp = GET $app; ok $resp->is_success; @@ -244,7 +249,7 @@ ok ($p{Hosts}->[0]{vhost} == $p{Contexts}->[0]{vhost}); $resp = CMD 'DISABLE-APP', { JVMRoute => 'app', Context => '/news', Alias => $apphost }; ok $resp->is_success; -ok ($resp->content eq ""); +ok ($resp->content eq "") if $version1; $resp = GET $app; ok $resp->is_error; @@ -283,16 +288,19 @@ ok ($p{Hosts}->[0]{vhost} == $p{Contexts}->[0]{vhost}); ###################### CMD 'ENABLE-APP', { JVMRoute => 'app', Context => '/news', Alias => $apphost }; -my @stop_opts = qw( Type JvmRoute Alias Context Requests ); $resp = CMD 'STOP-APP', { JVMRoute => 'app', Context => '/news', Alias => $apphost }; ok $resp->is_success; -%p = parse_response 'STOP-APP', $resp->content; -ok ($p{Type} eq 'STOP-APP-RSP'); +if ((mpc_version())[0] == 1) { + my @stop_opts = qw( Type JvmRoute Alias Context Requests ); + %p = parse_response 'STOP-APP', $resp->content; + + ok ($p{Type} eq 'STOP-APP-RSP'); -for my $opt (@stop_opts) { - ok (exists $p{$opt}); + for my $opt (@stop_opts) { + ok (exists $p{$opt}); + } } $resp = GET $app; @@ -337,7 +345,7 @@ $resp = CMD 'REMOVE-APP', { JVMRoute => 'app', Context => '/news', Alias => $app ok $resp->is_success; -ok ($resp->content eq ""); +ok ($resp->content eq "") if $version1; $resp = GET $app; ok $resp->is_error; From ad9f18cb463815032ade8159a58839d635e1b98e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Tue, 21 Apr 2026 17:30:37 +0200 Subject: [PATCH 04/13] test-perl: Add support for version detection for ModProxyCluster.pm --- test-perl/lib/ModProxyCluster.pm | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test-perl/lib/ModProxyCluster.pm b/test-perl/lib/ModProxyCluster.pm index 642d23a2..4118e8fc 100644 --- a/test-perl/lib/ModProxyCluster.pm +++ b/test-perl/lib/ModProxyCluster.pm @@ -12,6 +12,7 @@ our @ISA = qw(Exporter); our @EXPORT = qw( need_mpc + mpc_version CMD CMD_internal parse_params @@ -23,6 +24,11 @@ our @EXPORT = qw( our $VERSION = '0.0.1'; our $ROOT = Apache::TestRequest::module2url('mpc_test_host', { path => '' }); +our $MPC_VERSION_MAJOR = 0; +our $MPC_VERSION_MINOR = 0; +our $MPC_VERSION_PATCH = 0; +our $MPC_VERSION_STRING = ""; + # You have to call `Apache::TestRequest::module()` before # running this function. # `need` or `need_module` for 'proxy_cluster' won't work because it's @@ -30,12 +36,18 @@ our $ROOT = Apache::TestRequest::module2url('mpc_test_host', { path => '' }); sub need_mpc { my $res = GET '/mod_cluster_manager'; if ($res->code != 404) { + my $sw_info_pattern = '\/(\d+)\.(\d+)\.(\d+)\.(Dev|Alpha\d+|Beta\d+|CR\d+|Final)'; + ($MPC_VERSION_MAJOR, $MPC_VERSION_MINOR, $MPC_VERSION_PATCH, $MPC_VERSION_STRING) = $res->content =~ /$sw_info_pattern/; return 1; # the module is alive } Apache::Test::skip_reason("/mod_cluster_manager endpoint returned 404"); return 0; } +sub mpc_version { + return ($MPC_VERSION_MAJOR, $MPC_VERSION_MINOR, $MPC_VERSION_PATCH, $MPC_VERSION_STRING); +} + sub CMD_internal { my ($cmd, $path, $params) = @_; my $header = []; From 2f65f724529e335fdea20d85754d0ecd6818a827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Tue, 12 May 2026 18:42:42 +0200 Subject: [PATCH 05/13] test-perl: Make #329 test working with both MCMP implementations --- test-perl/t/issues/GH-issue-329.t | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test-perl/t/issues/GH-issue-329.t b/test-perl/t/issues/GH-issue-329.t index b4f6862b..2039ddc4 100644 --- a/test-perl/t/issues/GH-issue-329.t +++ b/test-perl/t/issues/GH-issue-329.t @@ -29,7 +29,8 @@ my %p = parse_response 'INFO', $resp->content; ok(@{$p{Nodes}} == 1); -ok (@{$p{Contexts}} == 2); +# For 1.3.x we have 2 aliases and 2 contexts, with 2.x we have 2 aliases and 2 context per each +ok (@{$p{Contexts}} == ((mpc_version())[0] == 1 ? 2 : 4)); ok ($p{Contexts}->[0]{Context} eq '/news'); ok ($p{Contexts}->[1]{Context} eq '/test'); @@ -54,7 +55,7 @@ ok $resp->is_success; ok(@{$p{Nodes}} == 1); -ok (@{$p{Contexts}} == 2); +ok (@{$p{Contexts}} == ((mpc_version())[0] == 1 ? 2 : 4)); ok ($p{Contexts}->[0]{Context} eq '/news'); ok ($p{Contexts}->[1]{Context} eq '/test'); @@ -79,7 +80,7 @@ ok $resp->is_success; ok(@{$p{Nodes}} == 1); -ok (@{$p{Contexts}} == 2); +ok (@{$p{Contexts}} == ((mpc_version())[0] == 1 ? 2 : 4)); ok ($p{Contexts}->[0]{Context} eq '/news'); ok ($p{Contexts}->[1]{Context} eq '/test'); From bc179245eea706db4ed51e24737889f62e4fa1f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Thu, 14 May 2026 10:34:36 +0200 Subject: [PATCH 06/13] test-perl: Fix concat_params function handling falsy values In case a parameter's value was defined but evaluated false, it was ignored. --- test-perl/lib/ModProxyCluster.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-perl/lib/ModProxyCluster.pm b/test-perl/lib/ModProxyCluster.pm index 4118e8fc..d289a617 100644 --- a/test-perl/lib/ModProxyCluster.pm +++ b/test-perl/lib/ModProxyCluster.pm @@ -64,7 +64,7 @@ sub concat_params { my $d = ""; foreach my $k (sort(keys %$params)) { - if ($params->{$k}) { + if (defined $params->{$k}) { $p .= $d . $k . '=' . $params->{$k}; $d = "&"; } From e83888842284d2dbb0ad6f9b82f68704d0cb37c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Mon, 30 Mar 2026 10:50:39 +0200 Subject: [PATCH 07/13] test-perl: Drop hardcoded version in the old tests --- test-perl/t/mod_proxy_cluster/manager.t | 3 +-- test-perl/t/mod_proxy_cluster/messages_config_nok.t | 3 +-- test-perl/t/mod_proxy_cluster/messages_nok.t | 3 +-- test-perl/t/mod_proxy_cluster/messages_ok.t | 2 -- 4 files changed, 3 insertions(+), 8 deletions(-) diff --git a/test-perl/t/mod_proxy_cluster/manager.t b/test-perl/t/mod_proxy_cluster/manager.t index 9a7f1715..6f7d29c6 100644 --- a/test-perl/t/mod_proxy_cluster/manager.t +++ b/test-perl/t/mod_proxy_cluster/manager.t @@ -14,12 +14,11 @@ use Apache::TestRequest 'GET_BODY'; use ModProxyCluster; Apache::TestRequest::module("mpc_test_host"); -plan tests => 4, need_mpc; +plan tests => 3, need_mpc; my $url = "/mod_cluster_manager"; my $data = GET_BODY $url; -ok (index($data, "mod_cluster/2.0.0.Alpha1-SNAPSHOT") != -1); ok (index($data, "Node") == -1); my %h = ( JVMRoute => 'next', Host => '127.0.0.2', Port => '8082', Type => 'http' ); diff --git a/test-perl/t/mod_proxy_cluster/messages_config_nok.t b/test-perl/t/mod_proxy_cluster/messages_config_nok.t index 5cc5dfcb..36b17f4d 100644 --- a/test-perl/t/mod_proxy_cluster/messages_config_nok.t +++ b/test-perl/t/mod_proxy_cluster/messages_config_nok.t @@ -13,13 +13,12 @@ use Apache::TestRequest 'GET'; use ModProxyCluster; Apache::TestRequest::module("mpc_test_host"); -plan tests => 70, need_mpc; +plan tests => 69, need_mpc; my $hostport = Apache::TestRequest::hostport(); my $resp = GET "/"; ok $resp->is_success; -ok (index($resp->as_string, "mod_cluster/2.0.0.Alpha1-SNAPSHOT") != -1); my $is_httpd2_4 = $resp->header('Server') =~ m/Apache\/2\.4/; diff --git a/test-perl/t/mod_proxy_cluster/messages_nok.t b/test-perl/t/mod_proxy_cluster/messages_nok.t index 7bb5a96d..c17ef766 100644 --- a/test-perl/t/mod_proxy_cluster/messages_nok.t +++ b/test-perl/t/mod_proxy_cluster/messages_nok.t @@ -14,11 +14,10 @@ use Apache::TestRequest 'GET'; use ModProxyCluster; Apache::TestRequest::module("mpc_test_host"); -plan tests => 8, need_mpc; +plan tests => 7, need_mpc; my $resp = GET "/"; ok $resp->is_success; -ok (index($resp->as_string, "mod_cluster/2.0.0.Alpha1-SNAPSHOT") != -1); ################## ##### STATUS ##### diff --git a/test-perl/t/mod_proxy_cluster/messages_ok.t b/test-perl/t/mod_proxy_cluster/messages_ok.t index 8dd75197..1be6c429 100644 --- a/test-perl/t/mod_proxy_cluster/messages_ok.t +++ b/test-perl/t/mod_proxy_cluster/messages_ok.t @@ -14,7 +14,6 @@ use Apache::TestRequest 'GET'; use ModProxyCluster; Apache::TestRequest::module("mpc_test_host"); - my $extra_tests = 0; my $version1 = (mpc_version())[0] == 1; $extra_tests = 9 if ($version1); @@ -23,7 +22,6 @@ plan tests => 203 + $extra_tests, need_mpc; my $resp = GET "/"; ok $resp->is_success; -ok (index($resp->as_string, "mod_cluster/2.0.0.Alpha1-SNAPSHOT") != -1); ################## From 9a3761c85a29f11ba5056bc4667a4b64c7b6071d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Fri, 20 Mar 2026 15:20:55 +0100 Subject: [PATCH 08/13] Fix id for PING and STATUS responses --- native/mod_manager/mod_manager.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/native/mod_manager/mod_manager.c b/native/mod_manager/mod_manager.c index 6eb93910..afa8c283 100644 --- a/native/mod_manager/mod_manager.c +++ b/native/mod_manager/mod_manager.c @@ -2442,7 +2442,7 @@ static char *process_status(request_rec *r, const char *const *ptr, int *errtype ap_rprintf(r, isnode_up(r, node->mess.id, Load) != OK ? "&State=NOTOK" : "&State=OK"); - ap_rprintf(r, "&id=%d", (int)ap_scoreboard_image->global->restart_time); + ap_rprintf(r, "&id=%ld", ap_scoreboard_image->global->restart_time); ap_rprintf(r, "\n"); return NULL; @@ -2544,7 +2544,7 @@ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) ap_rprintf(r, isnode_up(r, node->mess.id, -2) != OK ? "&State=NOTOK" : "&State=OK"); } - ap_rprintf(r, "&id=%d", (int)ap_scoreboard_image->global->restart_time); + ap_rprintf(r, "&id=%ld", ap_scoreboard_image->global->restart_time); ap_rprintf(r, "\n"); return NULL; From bec2d719c176c73d22b4b55e470428d0eea6cccc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Thu, 2 Apr 2026 12:35:42 +0200 Subject: [PATCH 09/13] Rework MCMP processing and handling, tweak format and spacing --- native/common/common.c | 2 + native/mod_manager/mod_manager.c | 435 +++++++++---------- native/mod_proxy_cluster/mod_proxy_cluster.c | 7 +- 3 files changed, 212 insertions(+), 232 deletions(-) diff --git a/native/common/common.c b/native/common/common.c index f9210fd7..dcbf7c6b 100644 --- a/native/common/common.c +++ b/native/common/common.c @@ -432,6 +432,7 @@ node_context *find_node_context_host(request_rec *r, const proxy_balancer *balan } } } + if (max == 0) { return NULL; } @@ -470,6 +471,7 @@ node_context *find_node_context_host(request_rec *r, const proxy_balancer *balan } } } + if (nbest == 0) { return NULL; } diff --git a/native/mod_manager/mod_manager.c b/native/mod_manager/mod_manager.c index afa8c283..e1e23cc6 100644 --- a/native/mod_manager/mod_manager.c +++ b/native/mod_manager/mod_manager.c @@ -733,35 +733,22 @@ static apr_status_t insert_update_host_helper(server_rec *s, mem_t *mem, hostinf /** * Insert the hosts from Alias information */ -static apr_status_t insert_update_hosts(server_rec *s, mem_t *mem, char *str, int node, int vhost) +static apr_status_t insert_update_hosts(server_rec *s, mem_t *mem, apr_array_header_t *aliases, int node, int vhost) { hostinfo_t info; - apr_status_t status; - char *ptr = str; - char *previous = str; - - char empty[] = {'\0'}; - if (str == NULL) { - ptr = empty; - previous = empty; - } + int i; info.node = node; info.vhost = vhost; - while (*ptr) { - if (*ptr == ',') { - *ptr = '\0'; - status = insert_update_host_helper(s, mem, &info, previous); - if (status != APR_SUCCESS) { - return status; - } - previous = ptr + 1; + for (i = 0; i < aliases->nelts; i++) { + apr_status_t status = insert_update_host_helper(s, mem, &info, APR_ARRAY_IDX(aliases, i, char*)); + if (status != APR_SUCCESS) { + return status; } - ptr++; } - return insert_update_host_helper(s, mem, &info, previous); + return APR_SUCCESS; } /** @@ -777,8 +764,7 @@ static void read_remove_context(mem_t *mem, contextinfo_t *context) } } -static apr_status_t insert_update_context_helper(server_rec *s, mem_t *mem, contextinfo_t *info, char *context, - int status) +static apr_status_t insert_update_context_helper(server_rec *s, mem_t *mem, contextinfo_t *info, char *context, int status) { (void)s; info->id = 0; @@ -798,37 +784,23 @@ static apr_status_t insert_update_context_helper(server_rec *s, mem_t *mem, cont * 1 - if status is REMOVE remove_context will be called * 2 - return codes of REMOVE are ignored (always success) */ -static apr_status_t insert_update_contexts(server_rec *s, mem_t *mem, char *str, int node, int vhost, int status) +static apr_status_t insert_update_contexts(server_rec *s, mem_t *mem, apr_array_header_t *contexts, int node, int vhost, int cmd) { - char *ptr = str; - char *previous = str; - apr_status_t ret = APR_SUCCESS; contextinfo_t info; - - char root[] = "/"; - if (ptr == NULL) { - ptr = root; - previous = root; - } + int i; info.node = node; info.vhost = vhost; - info.status = status; - - while (*ptr) { - if (*ptr == ',') { - *ptr = '\0'; - ret = insert_update_context_helper(s, mem, &info, previous, status); - if (ret != APR_SUCCESS) { - return ret; - } + info.status = cmd; - previous = ptr + 1; + for (i = 0; i < contexts->nelts; i++) { + apr_status_t status = insert_update_context_helper(s, mem, &info, APR_ARRAY_IDX(contexts, i, char*), cmd); + if (status != APR_SUCCESS) { + return status; } - ptr++; } - return insert_update_context_helper(s, mem, &info, previous, status); + return APR_SUCCESS; } /** @@ -940,7 +912,7 @@ static apr_status_t mod_manager_manage_worker(request_rec *r, const nodeinfo_t * /* set the health check (requires mod_proxy_hcheck) */ /* CPING for AJP and OPTIONS for HTTP/1.1 */ - apr_table_set(params, "w_hm", strcmp(node->mess.Type, "ajp") ? "OPTIONS" : "CPING"); + apr_table_set(params, "w_hm", strcmp(node->mess.Type, "ajp") != 0 ? "OPTIONS" : "CPING"); /* Use 10 sec for the moment, the idea is to adjust it with the STATUS frequency */ apr_table_set(params, "w_hi", "10000"); @@ -1255,74 +1227,70 @@ static const proxy_worker_shared *read_shared_by_node(request_rec *r, nodeinfo_t return NULL; } -/* 0 if limit is OK (not exceeded), 1 otherwise */ -static int check_context_alias_length(const char *str, int limit) -{ - int i = 0, len = 0; - while (str[i]) { - if (str[i] == ',') { +/* Helper function. + * Returns: + * -1 when an empty value gets processed + * 0 when everything went ok + * 1 when the given limit got exceeded +*/ +static int parse_list_to_array_with_checks(char *val, apr_array_header_t *arr, int len_limit) { + int len = 0; + char *start = val; + char *current = val; + + while (*current) { + len++; + if (*current == ',') { + *current = '\0'; + *(char **)apr_array_push(arr) = start; + start = ++current; len = 0; } - if (len >= limit) { + + if (len >= len_limit) { return 1; } - len++; - i++; + current++; + } + + if (len >= len_limit) { + return 1; + } else if (len == 0) { + return -1; } + *(char **)apr_array_push(arr) = start; return 0; } /** * Process Alias and Context if present. - * We process both in two different context: - * 1) during CONFIG command - * 2) during APP command - * to differenciate between the two use the last argument (true -> CONFIG, false -> APP) + * + * Check that their lengths are in limit. + * Transform aliases to lowercase if needed. */ -static char *process_context_alias(char *key, char *val, apr_pool_t *p, char **contexts, char **aliases, int *errtype, - int in_config) +static char *process_context_alias(char *key, char *val, apr_array_header_t *contexts, apr_array_header_t *aliases, int *errtype) { + int res = 0; if (strcasecmp(key, "Alias") == 0) { - char *tmp; - - if (*aliases && !in_config) { - *errtype = TYPESYNTAX; - return in_config ? SALIBAD : SMULALB; + /* val can be a list separated by `,` so we have to process it and add sequentially */ + char *current = val; + while (*current) { + /* Aliases to lower case for further case-insensitive treatment, IETF RFC 1035 Section 2.3.3. */ + *current = apr_tolower(*current); + current++; } - if (check_context_alias_length(val, HOSTALIASZ)) { - *errtype = TYPESYNTAX; - return SALIBIG; - } - - /* Aliases to lower case for further case-insensitive treatment, IETF RFC 1035 Section 2.3.3. */ - tmp = val; - while (*tmp) { - *tmp = apr_tolower(*tmp); - tmp++; - } - - if (*aliases) { - *aliases = apr_pstrcat(p, *aliases, ",", val, NULL); - } else { - *aliases = val; - } - } - if (strcasecmp(key, "Context") == 0) { - if (*contexts && !in_config) { + res = parse_list_to_array_with_checks(val, aliases, HOSTALIASZ); + if (res != 0) { *errtype = TYPESYNTAX; - return SMULCTB; + return res == 1 ? SALIBIG : "TODO: ALIAS EMPTY"; } - if (check_context_alias_length(val, CONTEXTSZ)) { + } else if (strcasecmp(key, "Context") == 0) { + res = parse_list_to_array_with_checks(val, contexts, CONTEXTSZ); + if (res != 0) { *errtype = TYPESYNTAX; - return SCONBIG; - } - - if (*contexts) { - *contexts = apr_pstrcat(p, *contexts, ",", val, NULL); - } else { - *contexts = val; + return res == 1 ? SCONBIG : "TODO: CONTEXT EMPTY"; } } @@ -1355,8 +1323,8 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) nodeinfo_t *node; balancerinfo_t balancerinfo; - char *contexts = NULL; - char *aliases = NULL; + apr_array_header_t *contexts = apr_array_make(r->pool, 4, sizeof(char*)); + apr_array_header_t *aliases = apr_array_make(r->pool, 4, sizeof(char*)); int i = 0; int id = -1; @@ -1394,7 +1362,7 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) return err_msg; } /* Optional parameters */ - err_msg = process_context_alias(ptr[i], ptr[i + 1], r->pool, &contexts, &aliases, errtype, 1); + err_msg = process_context_alias(ptr[i], ptr[i + 1], contexts, aliases, errtype); if (err_msg != NULL) { return err_msg; } @@ -1407,12 +1375,24 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) *errtype = TYPESYNTAX; return SROUBAD; } + /* Check that either both, context and alias, are present or none of them */ + if (!apr_is_empty_array(aliases) && apr_is_empty_array(contexts)) { + *errtype = TYPESYNTAX; + return SALIBAD; + } else if (apr_is_empty_array(aliases) && !apr_is_empty_array(contexts)) { + *errtype = TYPESYNTAX; + return SCONBAD; + } - if (mconf->enable_ws_tunnel && strcmp(nodeinfo.mess.Type, "ajp")) { - if (!strcmp(nodeinfo.mess.Type, "http")) { - strcpy(nodeinfo.mess.Type, "ws"); + if (strcmp(nodeinfo.mess.Type, "ajp") == 0) { + if (mconf->ajp_secret) { + strncpy(nodeinfo.mess.AJPSecret, mconf->ajp_secret, sizeof(nodeinfo.mess.AJPSecret)); + nodeinfo.mess.AJPSecret[sizeof(nodeinfo.mess.AJPSecret) - 1] = '\0'; } - if (!strcmp(nodeinfo.mess.Type, "https")) { + } else if (mconf->enable_ws_tunnel) { + if (strcmp(nodeinfo.mess.Type, "http") == 0) { + strcpy(nodeinfo.mess.Type, "ws"); + } else if (strcmp(nodeinfo.mess.Type, "https") == 0) { strcpy(nodeinfo.mess.Type, "wss"); } if (mconf->ws_upgrade_header) { @@ -1423,19 +1403,11 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) } } - if (strcmp(nodeinfo.mess.Type, "ajp") == 0) { - if (mconf->ajp_secret) { - strncpy(nodeinfo.mess.AJPSecret, mconf->ajp_secret, sizeof(nodeinfo.mess.AJPSecret)); - nodeinfo.mess.AJPSecret[sizeof(nodeinfo.mess.AJPSecret) - 1] = '\0'; - } - } - - if (mconf->response_field_size && strcmp(nodeinfo.mess.Type, "ajp")) { + if (strcmp(nodeinfo.mess.Type, "ajp") != 0 && mconf->response_field_size) { nodeinfo.mess.ResponseFieldSize = mconf->response_field_size; } /* Insert or update balancer description */ - rv = loc_lock_nodes(); - ap_assert(rv == APR_SUCCESS); + ap_assert(loc_lock_nodes() == APR_SUCCESS); if (insert_update_balancer(balancerstatsmem, &balancerinfo) != APR_SUCCESS) { loc_unlock_nodes(); *errtype = TYPEMEM; @@ -1460,6 +1432,9 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) return mess; } } + + // NOTE: So here we have either node == NULL || node & nodeinfo are the same node + /* check if a node corresponding to the same worker already exists */ if (is_same_worker_existing(r, &nodeinfo)) { loc_unlock_nodes(); @@ -1570,7 +1545,7 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) /* so the scheme, hostname and port correspond to worker which was removed and readded */ reenable_proxy_worker(r, workernode, worker, &nodeinfo, the_conf); ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, - "process_config: reenable_proxy_worker... scheme %s hostname %s port %d route %s name %s id: %d", + "process_config: reenable_proxy_worker scheme %s hostname %s port %d route %s name %s id: %d", worker->s->scheme, worker->s->hostname_ex, worker->s->port, worker->s->route, #ifdef PROXY_WORKER_EXT_NAME_SIZE worker->s->name_ex, @@ -1585,7 +1560,7 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) inc_version_node(); /* Insert the Alias and corresponding Context */ - if (aliases == NULL && contexts == NULL) { + if (apr_is_empty_array(aliases) && apr_is_empty_array(contexts)) { /* if using mod_balancer create or update the worker */ if (balancer_manage) { apr_status_t rv = mod_manager_manage_worker(r, &nodeinfo, &balancerinfo); @@ -2099,17 +2074,18 @@ static char *process_node_cmd(request_rec *r, int status, int *errtype, nodeinfo /** * Process an enable/disable/stop/remove application message */ -static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errtype, int global, int fromnode) +static char *process_appl_cmd(request_rec *r, char **ptr, int cmd, int *errtype, int fromnode) { nodeinfo_t nodeinfo; nodeinfo_t *node; - char *contexts = NULL; - char *aliases = NULL; + apr_array_header_t *contexts = apr_array_make(r->pool, 4, sizeof(char*)); + apr_array_header_t *aliases = apr_array_make(r->pool, 4, sizeof(char*)); int i = 0; hostinfo_t hostinfo; hostinfo_t *host = NULL; + int global = strcmp(r->filename, NODE_COMMAND) == 0; char *err_msg; memset(&nodeinfo.mess, '\0', sizeof(nodeinfo.mess)); @@ -2123,7 +2099,7 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty strcpy(nodeinfo.mess.JVMRoute, ptr[i + 1]); nodeinfo.mess.id = -1; } - err_msg = process_context_alias(ptr[i], ptr[i + 1], r->pool, &contexts, &aliases, errtype, 0); + err_msg = process_context_alias(ptr[i], ptr[i + 1], contexts, aliases, errtype); if (err_msg) { return err_msg; } @@ -2137,50 +2113,39 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty return SROUBAD; } - /* Note: This applies only for non-wildcarded requests for which Alias and Context are required */ - if (contexts == NULL && aliases == NULL && strcmp(r->uri, "/*") != 0) { - *errtype = TYPESYNTAX; - return NOCONAL; - } - - if (contexts == NULL && aliases != NULL) { - *errtype = TYPESYNTAX; - return SALIBAD; - } - if (aliases == NULL && contexts != NULL) { - *errtype = TYPESYNTAX; - return SCONBAD; + /* Note: Only non-wildcarded requests require Alias and Context */ + if (!global) { + if (apr_is_empty_array(contexts) && apr_is_empty_array(aliases)) { + *errtype = TYPESYNTAX; + return NOCONAL; + } else if (apr_is_empty_array(contexts) && !apr_is_empty_array(aliases)) { + *errtype = TYPESYNTAX; + return SALIBAD; + } else if (!apr_is_empty_array(contexts) && apr_is_empty_array(aliases)) { + *errtype = TYPESYNTAX; + return SCONBAD; + } } /* Read the node */ loc_lock_nodes(); node = read_node(nodestatsmem, &nodeinfo); - if (node == NULL) { + if (node == NULL || node->mess.remove) { loc_unlock_nodes(); - if (status == REMOVE) { + if (cmd == REMOVE) { return NULL; /* Already done */ } + /* Even for a removed node act has if the node wasn't found */ *errtype = TYPEMEM; return apr_psprintf(r->pool, MNODERD, nodeinfo.mess.JVMRoute); } - /* If the node is marked removed check what to do */ - if (node->mess.remove) { - loc_unlock_nodes(); - if (status == REMOVE) { - return NULL; /* Already done */ - } - /* Act has if the node wasn't found */ - *errtype = TYPEMEM; - return apr_psprintf(r->pool, MNODERD, node->mess.JVMRoute); - } - inc_version_node(); /* Process the * APP commands */ if (global) { char *ret; - ret = process_node_cmd(r, status, errtype, node); + ret = process_node_cmd(r, cmd, errtype, node); loc_unlock_nodes(); return ret; } @@ -2190,33 +2155,28 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty */ hostinfo.node = node->mess.id; hostinfo.id = 0; - if (aliases != NULL) { - int start = 0; - i = 0; - while (host == NULL && (unsigned)(i + start) < strlen(aliases)) { - while (aliases[start + i] != ',' && aliases[start + i] != '\0') { - i++; - } - - strncpy(hostinfo.host, aliases + start, i); - hostinfo.host[i] = '\0'; + if (!apr_is_empty_array(aliases)) { + for (i = 0; i < aliases->nelts; i++) { + strncpy(hostinfo.host, APR_ARRAY_IDX(aliases, i, char*), HOSTALIASZ); + hostinfo.host[HOSTALIASZ] = '\0'; host = read_host(hoststatsmem, &hostinfo); - start = start + i + 1; - i = 0; + if (host != NULL) { + break; + } } } else { hostinfo.host[0] = '\0'; } + /// + /* The host does not exists. If the command is not REMOVE, try to create it */ if (host == NULL) { int vid, size, *id; - /* If REMOVE ignores it */ - if (status == REMOVE) { + if (cmd == REMOVE) { loc_unlock_nodes(); return NULL; } - /* Find the first available vhost id */ vid = 0; size = loc_get_max_size_host(); @@ -2243,8 +2203,9 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty hostinfo.id = 0; hostinfo.node = node->mess.id; hostinfo.host[0] = '\0'; - if (aliases != NULL) { - strncpy(hostinfo.host, aliases, sizeof(hostinfo.host)); + // TODO: This is incorrect, there can be more aliases! + if (!apr_is_empty_array(aliases)) { + strncpy(hostinfo.host, APR_ARRAY_IDX(aliases, 0, char*), sizeof(hostinfo.host)); hostinfo.host[sizeof(hostinfo.host) - 1] = '\0'; } @@ -2255,8 +2216,9 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty return apr_psprintf(r->pool, MHOSTRD, node->mess.JVMRoute); } } + /// - if (status == ENABLED) { + if (cmd == ENABLED) { /* There is no load balancing between balancers */ int size = loc_get_max_size_context(); int *id = apr_palloc(r->pool, sizeof(int) * size); @@ -2266,7 +2228,8 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty if (get_context(contextstatsmem, &ou, id[i]) != APR_SUCCESS) { continue; } - if (strcmp(ou->context, contexts) == 0) { + // TODO: Again incorrect, there might be more than 1 context + if (strcmp(ou->context, APR_ARRAY_IDX(contexts, 0, char*)) == 0) { /* There is the same context somewhere else */ nodeinfo_t *hisnode; if (get_node(nodestatsmem, &hisnode, ou->node) != APR_SUCCESS) { @@ -2274,8 +2237,9 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty } if (strcmp(hisnode->mess.balancer, node->mess.balancer)) { /* the same context would be on 2 different balancer */ + // TODO: Incorrect, there might be more contexts! ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, - "process_appl_cmd: ENABLE: context %s is in balancer %s and %s", contexts, + "process_appl_cmd: ENABLE: context %s is in balancer %s and %s", APR_ARRAY_IDX(contexts, 0, char*), node->mess.balancer, hisnode->mess.balancer); } } @@ -2283,7 +2247,7 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty } /* Now update each context from Context: part */ - if (insert_update_contexts(r->server, contextstatsmem, contexts, node->mess.id, host->vhost, status) != + if (insert_update_contexts(r->server, contextstatsmem, contexts, node->mess.id, host->vhost, cmd) != APR_SUCCESS) { loc_unlock_nodes(); *errtype = TYPEMEM; @@ -2297,7 +2261,7 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty } /* Remove the host if all the contextes have been removed */ - if (status == REMOVE) { + if (cmd == REMOVE) { int size = loc_get_max_size_context(); int *id = apr_palloc(r->pool, sizeof(int) * size); size = get_ids_used_context(contextstatsmem, id); @@ -2325,12 +2289,13 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty } } } - } else if (status == STOPPED) { + } else if (cmd == STOPPED) { /* insert_update_contexts in fact makes that contexts corresponds only to the first context... */ contextinfo_t in; contextinfo_t *ou; in.id = 0; - strncpy(in.context, contexts, CONTEXTSZ); + // TODO: Incorrect, there might be more contexts + strncpy(in.context, APR_ARRAY_IDX(contexts, 0, char*), CONTEXTSZ); in.context[CONTEXTSZ] = '\0'; in.vhost = host->vhost; in.node = node->mess.id; @@ -2340,9 +2305,10 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty ou->nbrequests); if (fromnode) { ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); + // TODO: Incorrect, there might be more contexts and aliases!! ap_rprintf(r, "Type=STOP-APP-RSP&JvmRoute=%.*s&Alias=%.*s&Context=%.*s&Requests=%d", - (int)sizeof(nodeinfo.mess.JVMRoute), nodeinfo.mess.JVMRoute, (int)sizeof(aliases), aliases, - (int)sizeof(contexts), contexts, ou->nbrequests); + (int)sizeof(nodeinfo.mess.JVMRoute), nodeinfo.mess.JVMRoute, (int)sizeof(aliases), aliases->elts, + CONTEXTSZ, APR_ARRAY_IDX(contexts, 0, char*), ou->nbrequests); ap_rprintf(r, "\n"); } } else { @@ -2353,24 +2319,24 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int status, int *errty return NULL; } -static char *process_enable(request_rec *r, char **ptr, int *errtype, int global) +static char *process_enable(request_rec *r, char **ptr, int *errtype) { - return process_appl_cmd(r, ptr, ENABLED, errtype, global, 0); + return process_appl_cmd(r, ptr, ENABLED, errtype, 0); } -static char *process_disable(request_rec *r, char **ptr, int *errtype, int global) +static char *process_disable(request_rec *r, char **ptr, int *errtype) { - return process_appl_cmd(r, ptr, DISABLED, errtype, global, 0); + return process_appl_cmd(r, ptr, DISABLED, errtype, 0); } -static char *process_stop(request_rec *r, char **ptr, int *errtype, int global, int fromnode) +static char *process_stop(request_rec *r, char **ptr, int *errtype, int fromnode) { - return process_appl_cmd(r, ptr, STOPPED, errtype, global, fromnode); + return process_appl_cmd(r, ptr, STOPPED, errtype, fromnode); } -static char *process_remove(request_rec *r, char **ptr, int *errtype, int global) +static char *process_remove(request_rec *r, char **ptr, int *errtype) { - return process_appl_cmd(r, ptr, REMOVE, errtype, global, 0); + return process_appl_cmd(r, ptr, REMOVE, errtype, 0); } /* @@ -2391,6 +2357,15 @@ static int ishost_up(request_rec *r, char *scheme, char *host, char *port) return balancerhandler != NULL ? balancerhandler->proxy_host_isup(r, scheme, host, port) : OK; } +static void print_status_response(request_rec *r, const char *jvmroute, int node_up) { + // TODO: Add other content types + ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); + ap_rprintf(r, "Type=STATUS-RSP&JVMRoute=%.*s", JVMROUTESZ, jvmroute); + ap_rprintf(r, node_up != OK ? "&State=NOTOK" : "&State=OK"); + ap_rprintf(r, "&id=%ld", ap_scoreboard_image->global->restart_time); + ap_rprintf(r, "\n"); +} + /* * Process the STATUS command * Load -1 : Broken @@ -2399,7 +2374,7 @@ static int ishost_up(request_rec *r, char *scheme, char *host, char *port) */ static char *process_status(request_rec *r, const char *const *ptr, int *errtype) { - int Load = -1; + int load = -1; nodeinfo_t nodeinfo; nodeinfo_t *node; @@ -2415,7 +2390,11 @@ static char *process_status(request_rec *r, const char *const *ptr, int *errtype strcpy(nodeinfo.mess.JVMRoute, ptr[i + 1]); nodeinfo.mess.id = -1; } else if (strcasecmp(ptr[i], "Load") == 0) { - Load = atoi(ptr[i + 1]); + sscanf(ptr[i + 1], "%d", &load); + if (load < -1 || load > 100) { + *errtype = TYPESYNTAX; + return "TODO: Bad value for Load"; + } } else { *errtype = TYPESYNTAX; return apr_psprintf(r->pool, SBADFLD, ptr[i]); @@ -2437,14 +2416,7 @@ static char *process_status(request_rec *r, const char *const *ptr, int *errtype * If the node is usualable do a ping/pong to prevent Split-Brain Syndrome * and update the worker status and load factor acccording to the test result. */ - ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); - ap_rprintf(r, "Type=STATUS-RSP&JVMRoute=%.*s", (int)sizeof(nodeinfo.mess.JVMRoute), nodeinfo.mess.JVMRoute); - - ap_rprintf(r, isnode_up(r, node->mess.id, Load) != OK ? "&State=NOTOK" : "&State=OK"); - - ap_rprintf(r, "&id=%ld", ap_scoreboard_image->global->restart_time); - - ap_rprintf(r, "\n"); + print_status_response(r, nodeinfo.mess.JVMRoute, isnode_up(r, node->mess.id, load)); return NULL; } @@ -2470,6 +2442,24 @@ static char *process_version(request_rec *r, const char *const *const ptr, int * return NULL; } + +static void print_ping_response(request_rec *r, const char *jvmroute, int is_up) { + ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); + + if (jvmroute != NULL && jvmroute[0] != '\0') { + ap_rprintf(r, "Type=PING-RSP&JVMRoute=%.*s", JVMROUTESZ, jvmroute); + ap_rprintf(r, is_up ? "&State=OK" : "&State=NOTOK"); + } else if (is_up != -1) { + ap_rprintf(r, "Type=PING-RSP"); + ap_rprintf(r, is_up ? "&State=OK" : "&State=NOTOK"); + } else { + ap_rprintf(r, "Type=PING-RSP&State=OK"); + } + + ap_rprintf(r, "&id=%ld", ap_scoreboard_image->global->restart_time); + ap_rprintf(r, "\n"); +} + /* * Process the PING command * With a JVMRoute does a cping/cpong in the node. @@ -2486,9 +2476,11 @@ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) char *port = NULL; int i = 0; + int node_up = -1; + nodeinfo.mess.JVMRoute[0] = '\0'; + nodeinfo.mess.id = -1; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "Processing PING"); - nodeinfo.mess.id = -2; while (ptr[i] && ptr[i][0] != '\0') { if (strcasecmp(ptr[i], "JVMRoute") == 0) { if (strlen(ptr[i + 1]) >= sizeof(nodeinfo.mess.JVMRoute)) { @@ -2496,9 +2488,13 @@ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) return SROUBIG; } strcpy(nodeinfo.mess.JVMRoute, ptr[i + 1]); - nodeinfo.mess.id = -1; } else if (strcasecmp(ptr[i], "Scheme") == 0) { scheme = apr_pstrdup(r->pool, ptr[i + 1]); + // TODO: ideally drop this and fix proxy_host_isup support in mod_proxy_cluster.c + if (strcasecmp(scheme, "ajp") != 0 && strcasecmp(scheme, "http") != 0 && strcasecmp(scheme, "https") != 0) { + *errtype = TYPESYNTAX; + return apr_psprintf(r->pool, "PING Unsupported scheme: %s", scheme); + } } else if (strcasecmp(ptr[i], "Host") == 0) { host = apr_pstrdup(r->pool, ptr[i + 1]); } else if (strcasecmp(ptr[i], "Port") == 0) { @@ -2510,22 +2506,8 @@ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) i++; i++; } - if (nodeinfo.mess.id == -2) { - /* PING scheme, host, port or just httpd */ - if (scheme == NULL && host == NULL && port == NULL) { - ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); - ap_rprintf(r, "Type=PING-RSP&State=OK"); - } else { - if (scheme == NULL || host == NULL || port == NULL) { - *errtype = TYPESYNTAX; - return apr_psprintf(r->pool, SMISFLD); - } - ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); - ap_rprintf(r, "Type=PING-RSP"); - ap_rprintf(r, ishost_up(r, scheme, host, port) != OK ? "&State=NOTOK" : "&State=OK"); - } - } else { + if (nodeinfo.mess.JVMRoute[0] != '\0') { /* Read the node */ loc_lock_nodes(); node = read_node(nodestatsmem, &nodeinfo); @@ -2534,19 +2516,17 @@ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) *errtype = TYPEMEM; return apr_psprintf(r->pool, MNODERD, nodeinfo.mess.JVMRoute); } - - /* - * If the node is usualable do a ping/pong to prevent Split-Brain Syndrome - * and update the worker status and load factor acccording to the test result. - */ - ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); - ap_rprintf(r, "Type=PING-RSP&JVMRoute=%.*s", (int)sizeof(nodeinfo.mess.JVMRoute), nodeinfo.mess.JVMRoute); - - ap_rprintf(r, isnode_up(r, node->mess.id, -2) != OK ? "&State=NOTOK" : "&State=OK"); + node_up = isnode_up(r, node->mess.id, -2) == OK; + } else if (scheme != NULL || host != NULL || port != NULL) { + // first check, that none of the three is NULL + if (scheme == NULL || host == NULL || port == NULL) { + *errtype = TYPESYNTAX; + return apr_psprintf(r->pool, SMISFLD); + } + node_up = ishost_up(r, scheme, host, port) == OK; } - ap_rprintf(r, "&id=%ld", ap_scoreboard_image->global->restart_time); - ap_rprintf(r, "\n"); + print_ping_response(r, nodeinfo.mess.JVMRoute, node_up); return NULL; } @@ -3049,17 +3029,16 @@ static void sort_nodes(nodeinfo_t *nodes, int nbnodes) /* * Helper function, returns 1 in case of the application command. Otherwise returns 0 */ -static int process_appl(const char *cmd, request_rec *r, char **ptr, int *errtype, int global, char **errstring, - int fromnode) +static int process_appl(const char *cmd, request_rec *r, char **ptr, int *errtype, char **errstring, int fromnode) { if (strcasecmp(cmd, "ENABLE-APP") == 0) { - *errstring = process_enable(r, ptr, errtype, global); + *errstring = process_enable(r, ptr, errtype); } else if (strcasecmp(cmd, "DISABLE-APP") == 0) { - *errstring = process_disable(r, ptr, errtype, global); + *errstring = process_disable(r, ptr, errtype); } else if (strcasecmp(cmd, "STOP-APP") == 0) { - *errstring = process_stop(r, ptr, errtype, global, fromnode); + *errstring = process_stop(r, ptr, errtype, fromnode); } else if (strcasecmp(cmd, "REMOVE-APP") == 0) { - *errstring = process_remove(r, ptr, errtype, global); + *errstring = process_remove(r, ptr, errtype); } else { return 0; } @@ -3097,7 +3076,7 @@ static char *process_domain(request_rec *r, char **ptr, int *errtype, const char } /* add the JVMRoute */ ptr[pos + 1] = apr_pstrdup(r->pool, ou->mess.JVMRoute); - process_appl(cmd, r, ptr, errtype, RANGENODE, &errstring, 0); + process_appl(cmd, r, ptr, errtype, &errstring, 0); } return errstring; } @@ -3254,6 +3233,7 @@ static const char *process_params(request_rec *r, apr_table_t *params, int allow if (errstring) { process_error(r, errstring, errtype); } + /* Process other command if any */ if (range != NULL && allow_cmd && errstring == NULL) { int global = RANGECONTEXT; @@ -3283,7 +3263,7 @@ static const char *process_params(request_rec *r, apr_table_t *params, int allow if (global == RANGEDOMAIN) { errstring = process_domain(r, ptr, &errtype, cmd, domain); - } else if (!process_appl(cmd, r, ptr, &errtype, global, errstr, 0)) { + } else if (!process_appl(cmd, r, ptr, &errtype, errstr, 0)) { errstring = SCMDUNS; errtype = TYPESYNTAX; } @@ -3440,7 +3420,7 @@ static int manager_handler(request_rec *r) { apr_bucket_brigade *input_brigade; char *buff, *errstring = NULL; - int errtype = 0, global = 0; + int errtype = 0; apr_size_t bufsiz = 0, maxbufsiz, len; apr_status_t status; char **ptr; @@ -3504,9 +3484,6 @@ static int manager_handler(request_rec *r) process_error(r, SMESPAR, TYPESYNTAX); return HTTP_INTERNAL_SERVER_ERROR; } - if (strstr(r->filename, NODE_COMMAND)) { - global = 1; - } if (strcasecmp(r->method, "CONFIG") == 0) { errstring = process_config(r, ptr, &errtype); @@ -3522,7 +3499,7 @@ static int manager_handler(request_rec *r) } else if (strcasecmp(r->method, "VERSION") == 0) { errstring = process_version(r, (const char *const *)ptr, &errtype); /* Application handling */ - } else if (!process_appl(r->method, r, ptr, &errtype, global, &errstring, 1)) { + } else if (!process_appl(r->method, r, ptr, &errtype, &errstring, 1)) { errstring = SCMDUNS; errtype = TYPESYNTAX; } diff --git a/native/mod_proxy_cluster/mod_proxy_cluster.c b/native/mod_proxy_cluster/mod_proxy_cluster.c index 446eba65..f0a00252 100644 --- a/native/mod_proxy_cluster/mod_proxy_cluster.c +++ b/native/mod_proxy_cluster/mod_proxy_cluster.c @@ -1879,6 +1879,7 @@ static int proxy_node_isup(request_rec *r, int id, int load) } s = s->next; } + if (worker == NULL) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_cluster_isup: Can't find worker for %d. Check balancer names.", id); @@ -2678,7 +2679,7 @@ static int proxy_cluster_trans(request_rec *r) rv = ap_proxy_trans_match(r, dconf->alias, dconf); if (rv != HTTP_CONTINUE) { ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, - "proxy_cluster_trans: ap_proxy_trans_match(dconf) matches or reject %s to %s %d", + "proxy_cluster_trans: ap_proxy_trans_match(dconf) matches or reject %s to %s %d", r->uri, r->filename, rv); return rv; /* Done */ } @@ -2692,7 +2693,7 @@ static int proxy_cluster_trans(request_rec *r) rv = ap_proxy_trans_match(r, ent, dconf); if (rv != HTTP_CONTINUE) { ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, - "proxy_cluster_trans: ap_proxy_trans_match(conf) matches or reject %s to %s %d", + "proxy_cluster_trans: ap_proxy_trans_match(conf) matches or reject %s to %s %d", r->uri, r->filename, rv); return rv; /* Done */ } @@ -2700,7 +2701,7 @@ static int proxy_cluster_trans(request_rec *r) } /* Here the ProxyPass or ProxyPassMatch have been checked and have NOT returned ERRROR nor OK */ - ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "proxy_cluster_trans: no match for ap_proxy_trans_match on:%s", + ap_log_rerror(APLOG_MARK, APLOG_TRACE3, 0, r, "proxy_cluster_trans: no match for ap_proxy_trans_match on: %s", r->uri); /* Use proxy-nocanon if needed */ From 75084527af38c917c9297a4894517cfe83e54493 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Thu, 30 Apr 2026 19:02:32 +0200 Subject: [PATCH 10/13] test: Fix possible hanging in Main tests, tweak print --- test/maintests.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/maintests.sh b/test/maintests.sh index 2ffa2496..b909bbd4 100644 --- a/test/maintests.sh +++ b/test/maintests.sh @@ -32,7 +32,8 @@ echotestlabel "sticky one app" SESSIONCO=$(curl -v http://localhost:8090/testapp/test.jsp -m 20 -o /dev/null 2>&1 | grep Set-Cookie | awk '{ print $3 } ' | sed 's:;::') if [ "${SESSIONCO}" = "" ];then echo "Failed no sessionid in curl output..." - curl -v http://localhost:8090/testapp/test.jsp + curl -v -m 20 http://localhost:8090/testapp/test.jsp + exit 1 fi echo ${SESSIONCO} NEWCO=$(curl -v --cookie "${SESSIONCO}" http://localhost:8090/testapp/test.jsp -m 20 -o /dev/null 2>&1 | grep Set-Cookie | awk '{ print $3 } ' | sed 's:;::') @@ -68,7 +69,7 @@ do echo "Can't find node in request" exit 1 fi - echo "trying other webapp try: ${i}" + echo "$(date) trying other webapp try: ${i}" done echo "${i} try gives: ${NEWCO} node: ${NEWNODE}" From f2df8f857fb13583eeb5481bc0e57b5d34cc6995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Mon, 11 May 2026 09:23:00 +0200 Subject: [PATCH 11/13] Rework read_node function to read the whole node at once --- native/mod_manager/node.c | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/native/mod_manager/node.c b/native/mod_manager/node.c index 561ba09d..a08d055f 100644 --- a/native/mod_manager/node.c +++ b/native/mod_manager/node.c @@ -144,19 +144,19 @@ static apr_status_t loc_read_node(void *mem, void *data, apr_pool_t *pool) nodeinfo_t *read_node(mem_t *s, nodeinfo_t *node) { apr_status_t rv; - nodeinfo_t *ou; if (node->mess.id == -1) { rv = s->storage->doall(s->slotmem, loc_read_node, node, s->p); - if (rv == APR_EEXIST) { - return node; - } - } else { - rv = s->storage->dptr(s->slotmem, node->mess.id, (void **)&ou); - if (rv == APR_SUCCESS) { - return ou; + if (rv != APR_EEXIST) { + return NULL; } } + + rv = s->storage->dptr(s->slotmem, node->mess.id, (void **)&node); + if (rv == APR_SUCCESS) { + return node; + } + return NULL; } From 754ce745661591413753ce3ec38020100677eadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Mon, 11 May 2026 09:25:34 +0200 Subject: [PATCH 12/13] Fix the wrong exposed version format See https://github.com/modcluster/mod_proxy_cluster/blob/main/RELEASING.md --- native/include/mod_proxy_cluster.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/include/mod_proxy_cluster.h b/native/include/mod_proxy_cluster.h index fdf197dc..debed7cd 100644 --- a/native/include/mod_proxy_cluster.h +++ b/native/include/mod_proxy_cluster.h @@ -21,7 +21,7 @@ #include "context.h" #include "host.h" -#define MOD_CLUSTER_EXPOSED_VERSION "mod_cluster/2.0.0.Alpha1-SNAPSHOT" +#define MOD_CLUSTER_EXPOSED_VERSION "mod_proxy_cluster/2.0.0.Dev" /* define HAVE_CLUSTER_EX_DEBUG to have extented debug in mod_cluster */ #define HAVE_CLUSTER_EX_DEBUG 0 From db91ff2ae9e03c58636fb8251326362b7e408291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Chlup?= Date: Tue, 12 May 2026 13:52:01 +0200 Subject: [PATCH 13/13] Rework MCMP message handling and processing, refactor * tweak logging * rework the ping routine * simplify code a little bit * improve variable naming, drop unused parameters --- native/mod_manager/mod_manager.c | 569 ++++++++++--------- native/mod_proxy_cluster/mod_proxy_cluster.c | 242 +++++--- 2 files changed, 454 insertions(+), 357 deletions(-) diff --git a/native/mod_manager/mod_manager.c b/native/mod_manager/mod_manager.c index e1e23cc6..30273667 100644 --- a/native/mod_manager/mod_manager.c +++ b/native/mod_manager/mod_manager.c @@ -730,27 +730,6 @@ static apr_status_t insert_update_host_helper(server_rec *s, mem_t *mem, hostinf return insert_update_host(mem, info); } -/** - * Insert the hosts from Alias information - */ -static apr_status_t insert_update_hosts(server_rec *s, mem_t *mem, apr_array_header_t *aliases, int node, int vhost) -{ - hostinfo_t info; - int i; - - info.node = node; - info.vhost = vhost; - - for (i = 0; i < aliases->nelts; i++) { - apr_status_t status = insert_update_host_helper(s, mem, &info, APR_ARRAY_IDX(aliases, i, char*)); - if (status != APR_SUCCESS) { - return status; - } - } - - return APR_SUCCESS; -} - /** * Remove the context using the contextinfo_t information * we read it first then remove it @@ -764,7 +743,8 @@ static void read_remove_context(mem_t *mem, contextinfo_t *context) } } -static apr_status_t insert_update_context_helper(server_rec *s, mem_t *mem, contextinfo_t *info, char *context, int status) +static apr_status_t insert_update_context_helper(server_rec *s, mem_t *mem, contextinfo_t *info, char *context, + int status) { (void)s; info->id = 0; @@ -778,30 +758,6 @@ static apr_status_t insert_update_context_helper(server_rec *s, mem_t *mem, cont return insert_update_context(mem, info); } -/** - * Insert the context from Context information - * Note: - * 1 - if status is REMOVE remove_context will be called - * 2 - return codes of REMOVE are ignored (always success) - */ -static apr_status_t insert_update_contexts(server_rec *s, mem_t *mem, apr_array_header_t *contexts, int node, int vhost, int cmd) -{ - contextinfo_t info; - int i; - - info.node = node; - info.vhost = vhost; - info.status = cmd; - - for (i = 0; i < contexts->nelts; i++) { - apr_status_t status = insert_update_context_helper(s, mem, &info, APR_ARRAY_IDX(contexts, i, char*), cmd); - if (status != APR_SUCCESS) { - return status; - } - } - - return APR_SUCCESS; -} /** * Check that the node could be handle as is there were the same @@ -1215,8 +1171,8 @@ static const proxy_worker_shared *read_shared_by_node(request_rec *r, nodeinfo_t for (j = 0; j < balancer->workers->nelts; j++, workers++) { proxy_worker *worker = *workers; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, - "read_shared_by_node: Balancer %s worker (%d) %s, %s, %d", balancer->s->name, worker->s->index, - worker->s->route, worker->s->hostname, worker->s->port); + "read_shared_by_node: Balancer %s worker (%d) route (%s), %s://%s:%d", balancer->s->name, + worker->s->index, worker->s->route, worker->s->scheme, worker->s->hostname, worker->s->port); if (worker->s->port == port && strcmp(worker->s->hostname, node->mess.Host) == 0 && strcmp(worker->s->route, node->mess.JVMRoute) == 0) { return worker->s; @@ -1232,8 +1188,9 @@ static const proxy_worker_shared *read_shared_by_node(request_rec *r, nodeinfo_t * -1 when an empty value gets processed * 0 when everything went ok * 1 when the given limit got exceeded -*/ -static int parse_list_to_array_with_checks(char *val, apr_array_header_t *arr, int len_limit) { + */ +static int parse_list_to_array_with_checks(char *val, apr_array_header_t *arr, int len_limit) +{ int len = 0; char *start = val; char *current = val; @@ -1265,11 +1222,12 @@ static int parse_list_to_array_with_checks(char *val, apr_array_header_t *arr, i /** * Process Alias and Context if present. - * + * * Check that their lengths are in limit. * Transform aliases to lowercase if needed. */ -static char *process_context_alias(char *key, char *val, apr_array_header_t *contexts, apr_array_header_t *aliases, int *errtype) +static char *process_context_alias(char *key, char *val, apr_array_header_t *contexts, apr_array_header_t *aliases, + int *errtype) { int res = 0; if (strcasecmp(key, "Alias") == 0) { @@ -1284,13 +1242,13 @@ static char *process_context_alias(char *key, char *val, apr_array_header_t *con res = parse_list_to_array_with_checks(val, aliases, HOSTALIASZ); if (res != 0) { *errtype = TYPESYNTAX; - return res == 1 ? SALIBIG : "TODO: ALIAS EMPTY"; + return res == 1 ? SALIBIG : "TODO: Empty Alias is not allowed"; } } else if (strcasecmp(key, "Context") == 0) { res = parse_list_to_array_with_checks(val, contexts, CONTEXTSZ); if (res != 0) { *errtype = TYPESYNTAX; - return res == 1 ? SCONBIG : "TODO: CONTEXT EMPTY"; + return res == 1 ? SCONBIG : "TODO: Empty Context is not allowed"; } } @@ -1321,14 +1279,15 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) /* Process the node/balancer description */ nodeinfo_t nodeinfo; nodeinfo_t *node; - balancerinfo_t balancerinfo; + balancerinfo_t balancerinfo, oldbalancerinfo, *balancerinfo_ptr; - apr_array_header_t *contexts = apr_array_make(r->pool, 4, sizeof(char*)); - apr_array_header_t *aliases = apr_array_make(r->pool, 4, sizeof(char*)); + apr_array_header_t *contexts = apr_array_make(r->pool, 4, sizeof(char *)); + apr_array_header_t *aliases = apr_array_make(r->pool, 4, sizeof(char *)); + char *err_msg = NULL; int i = 0; int id = -1; - int vid = 1; /* zero and "" is empty */ + int vid = 1; int removed = -1; void *sconf = r->server->module_config; mod_manager_config *mconf = ap_get_module_config(sconf, &manager_module); @@ -1345,7 +1304,6 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) process_config_balancer_defaults(r, &balancerinfo, mconf); while (ptr[i]) { - char *err_msg = NULL; if (!ptr[i + 1] || *ptr[i + 1] == '\0') { *errtype = TYPESYNTAX; return SMESPAR; @@ -1406,35 +1364,22 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) if (strcmp(nodeinfo.mess.Type, "ajp") != 0 && mconf->response_field_size) { nodeinfo.mess.ResponseFieldSize = mconf->response_field_size; } - /* Insert or update balancer description */ - ap_assert(loc_lock_nodes() == APR_SUCCESS); - if (insert_update_balancer(balancerstatsmem, &balancerinfo) != APR_SUCCESS) { - loc_unlock_nodes(); - *errtype = TYPEMEM; - return apr_psprintf(r->pool, MBALAUI, nodeinfo.mess.JVMRoute); - } /* check for removed node */ node = read_node(nodestatsmem, &nodeinfo); if (node != NULL) { /* If the node is removed (or kill and restarted) and recreated unchanged that is ok: network problems */ if (!is_same_node(node, &nodeinfo)) { - /* Here we can't update it because the old one is still in */ - char *mess = apr_psprintf(r->pool, MNODERM, node->mess.JVMRoute); - ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, - "process_config: node %s %d %s : %s %s already exists, removing...", node->mess.JVMRoute, - node->mess.id, node->mess.Port, nodeinfo.mess.JVMRoute, nodeinfo.mess.Port); - mark_node_removed(node); - loc_remove_host_context(node->mess.id, r->pool); - inc_version_node(); + /* Here we can't update it because another one is still in */ + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "process_config: node %s %d %s : %s %s already exists", + node->mess.JVMRoute, node->mess.id, node->mess.Port, nodeinfo.mess.JVMRoute, + nodeinfo.mess.Port); loc_unlock_nodes(); *errtype = TYPEMEM; - return mess; + return apr_psprintf(r->pool, "MEM: Node with \"%s\" JVMRoute already exists", node->mess.JVMRoute); } } - // NOTE: So here we have either node == NULL || node & nodeinfo are the same node - /* check if a node corresponding to the same worker already exists */ if (is_same_worker_existing(r, &nodeinfo)) { loc_unlock_nodes(); @@ -1512,11 +1457,26 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) "process_config: NEW (%s) %s %s will not be added (Maxnode reached)", nodeinfo.mess.JVMRoute, nodeinfo.mess.Host, nodeinfo.mess.Port); } else if (id != -1) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_config: NEW (%s) %s %s in %d", - nodeinfo.mess.JVMRoute, nodeinfo.mess.Host, nodeinfo.mess.Port, id); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_config: NEW (%s) %s://%s:%s in %d", + nodeinfo.mess.JVMRoute, nodeinfo.mess.Type, nodeinfo.mess.Host, nodeinfo.mess.Port, id); } } + /* Now we'll start inserting. First the balancer part, then the node. */ + /* Insert or update balancer description */ + ap_assert(loc_lock_nodes() == APR_SUCCESS); + balancerinfo_ptr = read_balancer(balancerstatsmem, &balancerinfo); + if (balancerinfo_ptr != NULL) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_config: backing up the existing balancerinfo"); + oldbalancerinfo = *balancerinfo_ptr; + } + + if (insert_update_balancer(balancerstatsmem, &balancerinfo) != APR_SUCCESS) { + loc_unlock_nodes(); + *errtype = TYPEMEM; + return apr_psprintf(r->pool, MBALAUI, nodeinfo.mess.JVMRoute); + } + /* Insert or update node description */ if (insert_update_node(nodestatsmem, &nodeinfo, &id, clean) != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, @@ -1525,6 +1485,12 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) nodeinfo_t *workernode = read_node_by_id(nodestatsmem, removed); mark_node_removed(workernode); } + /* Revert back balancer changes */ + if (balancerinfo_ptr != NULL) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "process_config: insert/update node failed, restoring the old balancerinfo"); + insert_update_balancer(balancerstatsmem, &oldbalancerinfo); + } loc_unlock_nodes(); *errtype = TYPEMEM; return apr_psprintf(r->pool, MNODEUI, nodeinfo.mess.JVMRoute); @@ -1554,8 +1520,9 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) #endif worker->s->index); } else { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_config: (%s) %s inserted/updated in worker %d", - nodeinfo.mess.JVMRoute, nodeinfo.mess.Port, id); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "process_config: (%s) (%s://%s:%s) inserted/updated in worker %d", nodeinfo.mess.JVMRoute, + nodeinfo.mess.Type, nodeinfo.mess.Host, nodeinfo.mess.Port, id); } inc_version_node(); @@ -1572,19 +1539,43 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) return NULL; /* Alias and Context missing */ } + for (i = 0; i < aliases->nelts; i++) { + int j = 0; + hostinfo_t hinfo; + hinfo.node = id; + hinfo.vhost = vid; + hinfo.id = 0; + rv = insert_update_host_helper(r->server, hoststatsmem, &hinfo, APR_ARRAY_IDX(aliases, i, char *)); + if (rv != APR_SUCCESS) { + /* TODO: Clean this as part of #330 */ + char *tmp = + apr_psprintf(r->pool, MHOSTUI " (%s)", nodeinfo.mess.JVMRoute, APR_ARRAY_IDX(aliases, i, char *)); + err_msg = err_msg == NULL ? tmp : apr_pstrcat(r->pool, err_msg, ", ", tmp, NULL); + continue; + } - if (insert_update_hosts(r->server, hoststatsmem, aliases, id, vid) != APR_SUCCESS) { - loc_unlock_nodes(); - return apr_psprintf(r->pool, MHOSTUI, nodeinfo.mess.JVMRoute); - } + for (j = 0; j < contexts->nelts; j++) { + contextinfo_t cinfo; + cinfo.node = hinfo.node; + cinfo.vhost = hinfo.vhost; + cinfo.status = STOPPED; + rv = insert_update_context_helper(r->server, contextstatsmem, &cinfo, APR_ARRAY_IDX(contexts, j, char *), + STOPPED); + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_config: ADDING context: %s for alias: %s", + APR_ARRAY_IDX(contexts, j, char *), APR_ARRAY_IDX(aliases, i, char *)); + + if (rv != APR_SUCCESS) { + /* TODO: Clean this as part of #330 */ + char *tmp = apr_psprintf(r->pool, MCONTUI " (%s and alias: %s)", nodeinfo.mess.JVMRoute, + APR_ARRAY_IDX(contexts, j, char *), APR_ARRAY_IDX(aliases, i, char *)); + err_msg = err_msg == NULL ? tmp : apr_pstrcat(r->pool, err_msg, ", ", tmp, NULL); + } + } - if (insert_update_contexts(r->server, contextstatsmem, contexts, id, vid, STOPPED) != APR_SUCCESS) { - loc_unlock_nodes(); - return apr_psprintf(r->pool, MCONTUI, nodeinfo.mess.JVMRoute); + vid++; } - vid++; - /* if using mod_balancer create or update the worker */ if (balancer_manage) { apr_status_t rv = mod_manager_manage_worker(r, &nodeinfo, &balancerinfo); @@ -1596,7 +1587,7 @@ static char *process_config(request_rec *r, char **ptr, int *errtype) ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_config: Done"); - return NULL; + return err_msg; } /* @@ -2071,20 +2062,63 @@ static char *process_node_cmd(request_rec *r, int status, int *errtype, nodeinfo } +static void print_app_cmd_response(request_rec *r, int cmd, const char *jvmroute, const apr_array_header_t *updated, + const apr_array_header_t *aliases, const apr_array_header_t *contexts) +{ + int i, j; + ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); + switch (cmd) { + case ENABLED: + ap_rprintf(r, "Type=ENABLE-APP-RSP"); + break; + case STOPPED: + ap_rprintf(r, "Type=STOP-APP-RSP"); + break; + case DISABLED: + ap_rprintf(r, "Type=DISABLE-APP-RSP"); + break; + case REMOVE: /* fall through */ + default: + ap_rprintf(r, "Type=REMOVE-APP-RSP"); + break; + } + + ap_rprintf(r, "\nJvmRoute=%.*s", JVMROUTESZ, jvmroute); + + for (j = 0; j < aliases->nelts; j++) { + char *str = apr_psprintf(r->pool, "\nAlias=%.*s Contexts=", HOSTALIASZ, APR_ARRAY_IDX(aliases, j, char *)); + for (i = 0; i < contexts->nelts; i++) { + int idx = j * contexts->nelts + i; + /* print only contexts and aliases that were touched */ + if (APR_ARRAY_IDX(updated, idx, int) == 1) { + ap_rprintf(r, "%s%s", str, APR_ARRAY_IDX(contexts, i, char *)); + str = ","; + } + } + + if (str[0] == ',') { + /* this means at least one context was updated/inserted */ + ap_rprintf(r, "\n"); + } + } + + ap_rprintf(r, "\n"); +} + + /** * Process an enable/disable/stop/remove application message */ -static char *process_appl_cmd(request_rec *r, char **ptr, int cmd, int *errtype, int fromnode) +static char *process_app_cmd(request_rec *r, char **ptr, int cmd, int *errtype) { - nodeinfo_t nodeinfo; - nodeinfo_t *node; + nodeinfo_t nodeinfo, *node = NULL; - apr_array_header_t *contexts = apr_array_make(r->pool, 4, sizeof(char*)); - apr_array_header_t *aliases = apr_array_make(r->pool, 4, sizeof(char*)); + apr_array_header_t *aliases = apr_array_make(r->pool, 4, sizeof(char *)); + apr_array_header_t *contexts = apr_array_make(r->pool, 4, sizeof(char *)); + apr_array_header_t *updated_aliases_contexts = NULL; - int i = 0; - hostinfo_t hostinfo; - hostinfo_t *host = NULL; + int i = 0, j = 0, vid = 0; + hostinfo_t hostinfo, *host = NULL; int global = strcmp(r->filename, NODE_COMMAND) == 0; char *err_msg; @@ -2113,7 +2147,7 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int cmd, int *errtype, return SROUBAD; } - /* Note: Only non-wildcarded requests require Alias and Context */ + /* Note: Non-wildcarded requests require Alias and Context */ if (!global) { if (apr_is_empty_array(contexts) && apr_is_empty_array(aliases)) { *errtype = TYPESYNTAX; @@ -2132,9 +2166,7 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int cmd, int *errtype, node = read_node(nodestatsmem, &nodeinfo); if (node == NULL || node->mess.remove) { loc_unlock_nodes(); - if (cmd == REMOVE) { - return NULL; /* Already done */ - } + /* TODO: Let's consider returning NULL for an already removed node */ /* Even for a removed node act has if the node wasn't found */ *errtype = TYPEMEM; return apr_psprintf(r->pool, MNODERD, nodeinfo.mess.JVMRoute); @@ -2150,202 +2182,187 @@ static char *process_appl_cmd(request_rec *r, char **ptr, int cmd, int *errtype, return ret; } - /* Go through the provided Aliases, the first Alias that matches an existing host gets used - * otherwise, a new host will be created - */ - hostinfo.node = node->mess.id; - hostinfo.id = 0; - if (!apr_is_empty_array(aliases)) { - for (i = 0; i < aliases->nelts; i++) { - strncpy(hostinfo.host, APR_ARRAY_IDX(aliases, i, char*), HOSTALIASZ); - hostinfo.host[HOSTALIASZ] = '\0'; - host = read_host(hoststatsmem, &hostinfo); - if (host != NULL) { - break; - } - } - } else { - hostinfo.host[0] = '\0'; + /* We'll track which contexts for which aliases were processed successfully */ + updated_aliases_contexts = apr_array_make(r->pool, aliases->nelts * contexts->nelts, sizeof(int)); + for (j = 0; j < aliases->nelts * contexts->nelts; j++) { + /* Indices are alias.idx * contexts.count + context.idx (in aliases and contexts respectively). */ + int *el = (int *)apr_array_push(updated_aliases_contexts); + /* The default 0 indicates this was not processed successfully */ + *el = 0; } - /// - /* The host does not exists. If the command is not REMOVE, try to create it */ - if (host == NULL) { - int vid, size, *id; - - if (cmd == REMOVE) { - loc_unlock_nodes(); - return NULL; - } - /* Find the first available vhost id */ - vid = 0; - size = loc_get_max_size_host(); - id = apr_palloc(r->pool, sizeof(int) * size); - size = get_ids_used_host(hoststatsmem, id); - for (i = 0; i < size; i++) { - hostinfo_t *ou; - if (get_host(hoststatsmem, &ou, id[i]) != APR_SUCCESS) { - continue; - } - if (ou->node == node->mess.id && ou->vhost > vid) { - vid = ou->vhost; - } - } - vid++; /* Use next one. */ - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_appl_cmd: adding vhost: %d node: %d route: %s", - vid, node->mess.id, nodeinfo.mess.JVMRoute); - /* If the Host doesn't exist yet create it */ - if (insert_update_hosts(r->server, hoststatsmem, aliases, node->mess.id, vid) != APR_SUCCESS) { - loc_unlock_nodes(); - *errtype = TYPEMEM; - return apr_psprintf(r->pool, MHOSTUI, nodeinfo.mess.JVMRoute); - } - hostinfo.id = 0; + /* Go through the provided Aliases. If an alias matches an existing host, it gets updated. + * Otherwise a new host will be created for the alias. + */ + for (j = 0; j < aliases->nelts; j++) { + char *current_alias = APR_ARRAY_IDX(aliases, j, char *); hostinfo.node = node->mess.id; - hostinfo.host[0] = '\0'; - // TODO: This is incorrect, there can be more aliases! - if (!apr_is_empty_array(aliases)) { - strncpy(hostinfo.host, APR_ARRAY_IDX(aliases, 0, char*), sizeof(hostinfo.host)); - hostinfo.host[sizeof(hostinfo.host) - 1] = '\0'; - } - + hostinfo.id = 0; + strncpy(hostinfo.host, current_alias, HOSTALIASZ); + hostinfo.host[HOSTALIASZ] = '\0'; host = read_host(hoststatsmem, &hostinfo); if (host == NULL) { - loc_unlock_nodes(); - *errtype = TYPEMEM; - return apr_psprintf(r->pool, MHOSTRD, node->mess.JVMRoute); - } - } - /// - - if (cmd == ENABLED) { - /* There is no load balancing between balancers */ - int size = loc_get_max_size_context(); - int *id = apr_palloc(r->pool, sizeof(int) * size); - size = get_ids_used_context(contextstatsmem, id); - for (i = 0; i < size; i++) { - contextinfo_t *ou; - if (get_context(contextstatsmem, &ou, id[i]) != APR_SUCCESS) { + int size, *id; + if (cmd == REMOVE) { + /* We don't have to do anything on REMOVE, host does not exist */ continue; } - // TODO: Again incorrect, there might be more than 1 context - if (strcmp(ou->context, APR_ARRAY_IDX(contexts, 0, char*)) == 0) { - /* There is the same context somewhere else */ - nodeinfo_t *hisnode; - if (get_node(nodestatsmem, &hisnode, ou->node) != APR_SUCCESS) { + /* Otherwise we have to create a new host */ + size = loc_get_max_size_host(); + id = apr_palloc(r->pool, sizeof(int) * size); + size = get_ids_used_host(hoststatsmem, id); + for (i = 0; i < size; i++) { + hostinfo_t *ou; + if (get_host(hoststatsmem, &ou, id[i]) != APR_SUCCESS) { continue; } - if (strcmp(hisnode->mess.balancer, node->mess.balancer)) { - /* the same context would be on 2 different balancer */ - // TODO: Incorrect, there might be more contexts! - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, - "process_appl_cmd: ENABLE: context %s is in balancer %s and %s", APR_ARRAY_IDX(contexts, 0, char*), - node->mess.balancer, hisnode->mess.balancer); + if (ou->node == node->mess.id && ou->vhost > vid) { + vid = ou->vhost; } } + hostinfo.vhost = ++vid; /* Use next one. */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "process_app_cmd: adding vhost: %d node: %d route: %s, alias: %s", vid, node->mess.id, + nodeinfo.mess.JVMRoute, current_alias); + /* If the Host doesn't exist yet create it */ + if (insert_update_host(hoststatsmem, &hostinfo) != APR_SUCCESS) { + /* creation failed, but we'll continue with the rest of aliases/hosts */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, "Failed to insert/update host (%d) for %s", vid, + current_alias); + continue; + } + + host = read_host(hoststatsmem, &hostinfo); + if (host == NULL) { + /* read failed, but we'll continue with the rest of aliases/hosts */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, "Failed to read host (%d) for %s", vid, + current_alias); + continue; + } } - } - /* Now update each context from Context: part */ - if (insert_update_contexts(r->server, contextstatsmem, contexts, node->mess.id, host->vhost, cmd) != - APR_SUCCESS) { - loc_unlock_nodes(); - *errtype = TYPEMEM; - return apr_psprintf(r->pool, MCONTUI, node->mess.JVMRoute); - } + hostinfo.vhost = host->vhost; - if (insert_update_hosts(r->server, hoststatsmem, aliases, node->mess.id, host->vhost) != APR_SUCCESS) { - loc_unlock_nodes(); - *errtype = TYPEMEM; - return apr_psprintf(r->pool, MHOSTUI, node->mess.JVMRoute); - } + /* Now we have a host */ + if (cmd == ENABLED) { + /* There is no load balancing between balancers */ + int size = loc_get_max_size_context(); + int *id = apr_palloc(r->pool, sizeof(int) * size); + size = get_ids_used_context(contextstatsmem, id); + for (i = 0; i < size; i++) { + int c; + contextinfo_t *ou; + if (get_context(contextstatsmem, &ou, id[i]) != APR_SUCCESS) { + continue; + } + for (c = 0; c < contexts->nelts; c++) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, + "process_app_cmd: ENABLE: context %s alias %s", APR_ARRAY_IDX(contexts, c, char *), + current_alias); + if (strcmp(ou->context, APR_ARRAY_IDX(contexts, c, char *)) == 0) { + /* There is the same context somewhere else */ + nodeinfo_t *hisnode; + if (get_node(nodestatsmem, &hisnode, ou->node) != APR_SUCCESS) { + continue; + } + if (strcmp(hisnode->mess.balancer, node->mess.balancer)) { + /* the same context would be on 2 different balancer */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, + "process_app_cmd: ENABLE: context %s is in balancer %s and %s", + APR_ARRAY_IDX(contexts, c, char *), node->mess.balancer, + hisnode->mess.balancer); + } + } + } + } + } - /* Remove the host if all the contextes have been removed */ - if (cmd == REMOVE) { - int size = loc_get_max_size_context(); - int *id = apr_palloc(r->pool, sizeof(int) * size); - size = get_ids_used_context(contextstatsmem, id); - for (i = 0; i < size; i++) { - contextinfo_t *ou; - if (get_context(contextstatsmem, &ou, id[i]) != APR_SUCCESS) { + /* Now update each context from Context: part */ + for (i = 0; i < contexts->nelts; i++) { + apr_status_t status; + contextinfo_t info; + info.node = node->mess.id; + info.vhost = host->vhost; + info.status = cmd; + status = insert_update_context_helper(r->server, contextstatsmem, &info, APR_ARRAY_IDX(contexts, i, char *), + cmd); + if (status != APR_SUCCESS) { + /* insert/update failed, but we'll continue with the rest of contexts */ + ap_log_error(APLOG_MARK, APLOG_WARNING, status, r->server, + "Failed to insert/update context (%s) for %s", APR_ARRAY_IDX(contexts, i, char *), + current_alias); continue; } - if (ou->vhost == host->vhost && ou->node == node->mess.id) { - break; - } + /* the insert/update was successfull, so we note it in our array */ + APR_ARRAY_IDX(updated_aliases_contexts, j * contexts->nelts + i, int) = 1; } - if (i == size) { - int size = loc_get_max_size_host(); + + /* Remove the host if all the contextes have been removed */ + if (cmd == REMOVE) { + int size = loc_get_max_size_context(); int *id = apr_palloc(r->pool, sizeof(int) * size); - size = get_ids_used_host(hoststatsmem, id); + size = get_ids_used_context(contextstatsmem, id); for (i = 0; i < size; i++) { - hostinfo_t *ou; - - if (get_host(hoststatsmem, &ou, id[i]) != APR_SUCCESS) { + contextinfo_t *ou; + if (get_context(contextstatsmem, &ou, id[i]) != APR_SUCCESS) { continue; } if (ou->vhost == host->vhost && ou->node == node->mess.id) { - remove_host(hoststatsmem, ou->id); + break; } } - } - } else if (cmd == STOPPED) { - /* insert_update_contexts in fact makes that contexts corresponds only to the first context... */ - contextinfo_t in; - contextinfo_t *ou; - in.id = 0; - // TODO: Incorrect, there might be more contexts - strncpy(in.context, APR_ARRAY_IDX(contexts, 0, char*), CONTEXTSZ); - in.context[CONTEXTSZ] = '\0'; - in.vhost = host->vhost; - in.node = node->mess.id; - ou = read_context(contextstatsmem, &in); - if (ou != NULL) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_appl_cmd: STOP-APP nbrequests %d", - ou->nbrequests); - if (fromnode) { - ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); - // TODO: Incorrect, there might be more contexts and aliases!! - ap_rprintf(r, "Type=STOP-APP-RSP&JvmRoute=%.*s&Alias=%.*s&Context=%.*s&Requests=%d", - (int)sizeof(nodeinfo.mess.JVMRoute), nodeinfo.mess.JVMRoute, (int)sizeof(aliases), aliases->elts, - CONTEXTSZ, APR_ARRAY_IDX(contexts, 0, char*), ou->nbrequests); - ap_rprintf(r, "\n"); + if (i == size) { + int size = loc_get_max_size_host(); + int *id = apr_palloc(r->pool, sizeof(int) * size); + size = get_ids_used_host(hoststatsmem, id); + for (i = 0; i < size; i++) { + hostinfo_t *ou; + + if (get_host(hoststatsmem, &ou, id[i]) != APR_SUCCESS) { + continue; + } + if (ou->vhost == host->vhost && ou->node == node->mess.id) { + remove_host(hoststatsmem, ou->id); + } + } } - } else { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "process_appl_cmd: STOP-APP can't read_context"); } } + + /* This is always !global (see the global return above). */ + print_app_cmd_response(r, cmd, nodeinfo.mess.JVMRoute, updated_aliases_contexts, aliases, contexts); + loc_unlock_nodes(); return NULL; } static char *process_enable(request_rec *r, char **ptr, int *errtype) { - return process_appl_cmd(r, ptr, ENABLED, errtype, 0); + return process_app_cmd(r, ptr, ENABLED, errtype); } static char *process_disable(request_rec *r, char **ptr, int *errtype) { - return process_appl_cmd(r, ptr, DISABLED, errtype, 0); + return process_app_cmd(r, ptr, DISABLED, errtype); } -static char *process_stop(request_rec *r, char **ptr, int *errtype, int fromnode) +static char *process_stop(request_rec *r, char **ptr, int *errtype) { - return process_appl_cmd(r, ptr, STOPPED, errtype, fromnode); + return process_app_cmd(r, ptr, STOPPED, errtype); } static char *process_remove(request_rec *r, char **ptr, int *errtype) { - return process_appl_cmd(r, ptr, REMOVE, errtype, 0); + return process_app_cmd(r, ptr, REMOVE, errtype); } /* * Call the ping/pong logic * Do a ping/png request to the node and set the load factor. */ -static int isnode_up(request_rec *r, int id, int Load) +static int isnode_up(request_rec *r, int id, int load) { - return balancerhandler != NULL ? balancerhandler->proxy_node_isup(r, id, Load) : OK; + return balancerhandler != NULL ? balancerhandler->proxy_node_isup(r, id, load) : OK; } /* @@ -2357,11 +2374,11 @@ static int ishost_up(request_rec *r, char *scheme, char *host, char *port) return balancerhandler != NULL ? balancerhandler->proxy_host_isup(r, scheme, host, port) : OK; } -static void print_status_response(request_rec *r, const char *jvmroute, int node_up) { - // TODO: Add other content types +static void print_status_response(request_rec *r, const char *jvmroute, int node_up) +{ ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); ap_rprintf(r, "Type=STATUS-RSP&JVMRoute=%.*s", JVMROUTESZ, jvmroute); - ap_rprintf(r, node_up != OK ? "&State=NOTOK" : "&State=OK"); + ap_rprintf(r, node_up ? "&State=OK" : "&State=NOTOK"); ap_rprintf(r, "&id=%ld", ap_scoreboard_image->global->restart_time); ap_rprintf(r, "\n"); } @@ -2374,7 +2391,7 @@ static void print_status_response(request_rec *r, const char *jvmroute, int node */ static char *process_status(request_rec *r, const char *const *ptr, int *errtype) { - int load = -1; + int load = -2; nodeinfo_t nodeinfo; nodeinfo_t *node; @@ -2399,8 +2416,12 @@ static char *process_status(request_rec *r, const char *const *ptr, int *errtype *errtype = TYPESYNTAX; return apr_psprintf(r->pool, SBADFLD, ptr[i]); } - i++; - i++; + i += 2; + } + + if (nodeinfo.mess.JVMRoute[0] == '\0') { + *errtype = TYPESYNTAX; + return "TODO: No JVMRoute given!"; } /* Read the node */ @@ -2416,7 +2437,8 @@ static char *process_status(request_rec *r, const char *const *ptr, int *errtype * If the node is usualable do a ping/pong to prevent Split-Brain Syndrome * and update the worker status and load factor acccording to the test result. */ - print_status_response(r, nodeinfo.mess.JVMRoute, isnode_up(r, node->mess.id, load)); + /* TODO: Do we need isnode_up? Shouldn't we just check (or potentially change) just the status? */ + print_status_response(r, nodeinfo.mess.JVMRoute, isnode_up(r, node->mess.id, load) == OK); return NULL; } @@ -2443,17 +2465,16 @@ static char *process_version(request_rec *r, const char *const *const ptr, int * } -static void print_ping_response(request_rec *r, const char *jvmroute, int is_up) { +static void print_ping_response(request_rec *r, const char *jvmroute, int is_up) +{ ap_set_content_type(r, PLAINTEXT_CONTENT_TYPE); if (jvmroute != NULL && jvmroute[0] != '\0') { ap_rprintf(r, "Type=PING-RSP&JVMRoute=%.*s", JVMROUTESZ, jvmroute); ap_rprintf(r, is_up ? "&State=OK" : "&State=NOTOK"); - } else if (is_up != -1) { + } else { ap_rprintf(r, "Type=PING-RSP"); ap_rprintf(r, is_up ? "&State=OK" : "&State=NOTOK"); - } else { - ap_rprintf(r, "Type=PING-RSP&State=OK"); } ap_rprintf(r, "&id=%ld", ap_scoreboard_image->global->restart_time); @@ -2462,10 +2483,6 @@ static void print_ping_response(request_rec *r, const char *jvmroute, int is_up) /* * Process the PING command - * With a JVMRoute does a cping/cpong in the node. - * Without just answers ok. - * NOTE: It is hard to cping/cpong a host + port but CONFIG + PING + REMOVE_APP * - * would do the same. */ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) { @@ -2490,8 +2507,8 @@ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) strcpy(nodeinfo.mess.JVMRoute, ptr[i + 1]); } else if (strcasecmp(ptr[i], "Scheme") == 0) { scheme = apr_pstrdup(r->pool, ptr[i + 1]); - // TODO: ideally drop this and fix proxy_host_isup support in mod_proxy_cluster.c - if (strcasecmp(scheme, "ajp") != 0 && strcasecmp(scheme, "http") != 0 && strcasecmp(scheme, "https") != 0) { + if (strcasecmp(scheme, "ajp") != 0 && strcasecmp(scheme, "http") != 0 && strcasecmp(scheme, "https") != 0 && + strcasecmp(scheme, "ws") != 0 && strcasecmp(scheme, "wss") != 0) { *errtype = TYPESYNTAX; return apr_psprintf(r->pool, "PING Unsupported scheme: %s", scheme); } @@ -2503,8 +2520,8 @@ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) *errtype = TYPESYNTAX; return apr_psprintf(r->pool, SBADFLD, ptr[i]); } - i++; - i++; + + i += 2; } if (nodeinfo.mess.JVMRoute[0] != '\0') { @@ -2516,9 +2533,13 @@ static char *process_ping(request_rec *r, const char *const *ptr, int *errtype) *errtype = TYPEMEM; return apr_psprintf(r->pool, MNODERD, nodeinfo.mess.JVMRoute); } - node_up = isnode_up(r, node->mess.id, -2) == OK; - } else if (scheme != NULL || host != NULL || port != NULL) { - // first check, that none of the three is NULL + + scheme = node->mess.Type; + host = node->mess.Host; + port = node->mess.Port; + } + + if (scheme != NULL || host != NULL || port != NULL) { if (scheme == NULL || host == NULL || port == NULL) { *errtype = TYPESYNTAX; return apr_psprintf(r->pool, SMISFLD); @@ -3029,14 +3050,14 @@ static void sort_nodes(nodeinfo_t *nodes, int nbnodes) /* * Helper function, returns 1 in case of the application command. Otherwise returns 0 */ -static int process_appl(const char *cmd, request_rec *r, char **ptr, int *errtype, char **errstring, int fromnode) +static int process_appl(const char *cmd, request_rec *r, char **ptr, int *errtype, char **errstring) { if (strcasecmp(cmd, "ENABLE-APP") == 0) { *errstring = process_enable(r, ptr, errtype); } else if (strcasecmp(cmd, "DISABLE-APP") == 0) { *errstring = process_disable(r, ptr, errtype); } else if (strcasecmp(cmd, "STOP-APP") == 0) { - *errstring = process_stop(r, ptr, errtype, fromnode); + *errstring = process_stop(r, ptr, errtype); } else if (strcasecmp(cmd, "REMOVE-APP") == 0) { *errstring = process_remove(r, ptr, errtype); } else { @@ -3076,7 +3097,7 @@ static char *process_domain(request_rec *r, char **ptr, int *errtype, const char } /* add the JVMRoute */ ptr[pos + 1] = apr_pstrdup(r->pool, ou->mess.JVMRoute); - process_appl(cmd, r, ptr, errtype, &errstring, 0); + process_appl(cmd, r, ptr, errtype, &errstring); } return errstring; } @@ -3263,7 +3284,7 @@ static const char *process_params(request_rec *r, apr_table_t *params, int allow if (global == RANGEDOMAIN) { errstring = process_domain(r, ptr, &errtype, cmd, domain); - } else if (!process_appl(cmd, r, ptr, &errtype, errstr, 0)) { + } else if (!process_appl(cmd, r, ptr, &errtype, errstr)) { errstring = SCMDUNS; errtype = TYPESYNTAX; } @@ -3499,7 +3520,7 @@ static int manager_handler(request_rec *r) } else if (strcasecmp(r->method, "VERSION") == 0) { errstring = process_version(r, (const char *const *)ptr, &errtype); /* Application handling */ - } else if (!process_appl(r->method, r, ptr, &errtype, &errstring, 1)) { + } else if (!process_appl(r->method, r, ptr, &errtype, &errstring)) { errstring = SCMDUNS; errtype = TYPESYNTAX; } diff --git a/native/mod_proxy_cluster/mod_proxy_cluster.c b/native/mod_proxy_cluster/mod_proxy_cluster.c index f0a00252..3439d827 100644 --- a/native/mod_proxy_cluster/mod_proxy_cluster.c +++ b/native/mod_proxy_cluster/mod_proxy_cluster.c @@ -298,8 +298,9 @@ static apr_status_t create_worker_reuse(proxy_server_conf *conf, const char *ptr /* Check if the shared memory goes to the right place */ ptr = ptr_node + NODEOFFSET; - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "create_worker: reusing worker (id %d) for %s", node->mess.id, - url); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, + "create_worker: reusing worker (id: %d) for %s (worker->s->status: %d, lbstatus: %d lbfactor: %d)", + node->mess.id, url, worker->s->status, worker->s->lbstatus, worker->s->lbfactor); if (helper->index == node->mess.id && worker->s == (proxy_worker_shared *)ptr) { /* the shared memory may have been removed and recreated */ if (!worker->s->status) { @@ -339,16 +340,15 @@ static apr_status_t create_worker_reuse(proxy_server_conf *conf, const char *ptr ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, server, "create_worker: can't reuse worker as it is for %s (scheme: %s hostname %s port %d route %s " - "name %s) cleaning...", + "name %s) cleaning... (helper->index: %d node->mess.id: %d worker->s: %pp ptr: %pp)", url, worker->s->scheme, worker->s->hostname_ex, worker->s->port, worker->s->route, #ifdef PROXY_WORKER_EXT_NAME_SIZE - worker->s->name_ex); + worker->s->name_ex, helper->index, node->mess.id, (void *)worker->s, (void *)ptr); #else - worker->s->name); + worker->s->name, helper->index, node->mess.id, (void *)worker->s, (void *)ptr); #endif - ptr = ptr_node + NODEOFFSET; - *shared = worker->s; worker->s = (proxy_worker_shared *)ptr; + *shared = worker->s; worker->s->was_malloced = 0; /* Prevent mod_proxy to free it */ helper->isinnodes = 1; helper->index = node->mess.id; @@ -784,32 +784,23 @@ static void add_balancers_workers(nodeinfo_t *node, const char *ptr_node, apr_po static proxy_worker *get_worker_from_id_stat(const proxy_server_conf *conf, int id, const proxy_worker_shared *stat, nodeinfo_t *node) { - int i; - char *ptr = conf->balancers->elts; - int sizeb = conf->balancers->elt_size; - int sizew = sizeof(proxy_worker *); - (void)node; + int i, j; + for (i = 0; i < conf->balancers->nelts; i++) { + proxy_balancer *balancer = &(APR_ARRAY_IDX(conf->balancers, i, proxy_balancer)); + for (j = 0; j < balancer->workers->nelts; j++) { + proxy_worker *worker = APR_ARRAY_IDX(balancer->workers, j, proxy_worker *); + proxy_cluster_helper *helper = (proxy_cluster_helper *)worker->context; - for (i = 0; i < conf->balancers->nelts; i++, ptr = ptr + sizeb) { - int j; - char *ptrw; - proxy_balancer *balancer = (proxy_balancer *)ptr; - ptrw = balancer->workers->elts; - for (j = 0; j < balancer->workers->nelts; j++, ptrw = ptrw + sizew) { - proxy_worker **worker = (proxy_worker **)ptrw; - proxy_cluster_helper *helper = (proxy_cluster_helper *)(*worker)->context; - - if ((*worker)->s == stat && helper->index == id) { - if (is_worker_empty(*worker)) { - return NULL; - } + ap_log_error(APLOG_MARK, APLOG_TRACE3, 0, main_server, + "get_worker_from_id_stat: Processing id: %d helper->index: %d ptr: %pp stat: %pp", id, + helper->index, (void *)worker->s, (void *)stat); - return *worker; + if (worker->s == stat && helper->index == id) { + return is_worker_empty(worker) ? NULL : worker; } if (helper->index == id) { - unpair_worker_node((*worker)->s, node); - helper->shared->index = -1; + unpair_worker_node(worker->s, node); } } } @@ -996,8 +987,10 @@ static void copy_and_trim_crlf(char *buffer, const char *ptr, apr_size_t len) } } -/* Do a ping/pong to the node */ -static apr_status_t http_handle_cping_cpong(proxy_conn_rec *p_conn, request_rec *r, apr_interval_time_t timeout) +/* * Replicates AJP CPING/CPONG for HTTP backends. + * Returns APR_SUCCESS if the backend answers, or an error code if it's dead. + */ +static apr_status_t http_handle_ping_pong(proxy_conn_rec *p_conn, request_rec *r, apr_interval_time_t timeout) { char *srequest; apr_size_t len; @@ -1080,8 +1073,8 @@ static apr_status_t http_handle_cping_cpong(proxy_conn_rec *p_conn, request_rec return APR_SUCCESS; } -static apr_status_t proxy_cluster_try_pingpong(request_rec *r, proxy_worker *worker, char *url, proxy_server_conf *conf, - apr_interval_time_t ping, apr_interval_time_t workertimeout) + +static apr_status_t proxy_cluster_try_pingpong(request_rec *r, proxy_worker *worker, char *url, proxy_server_conf *conf) { apr_status_t status; apr_interval_time_t timeout; @@ -1091,19 +1084,20 @@ static apr_status_t proxy_cluster_try_pingpong(request_rec *r, proxy_worker *wor apr_uri_t *uri; char *scheme = worker->s->scheme; int is_ssl = 0; - (void)ping; - (void)workertimeout; if ((strcasecmp(scheme, "HTTPS") == 0 || strcasecmp(scheme, "WSS") == 0 || strcasecmp(scheme, "WS") == 0 || strcasecmp(scheme, "HTTP") == 0) && !enable_options) { - /* we cant' do CPING/CPONG so we just return OK */ + /* we cant' do PING/PONG so we just return OK */ + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "proxy_cluster_try_pingpong: can't do pingpong for: %s enable_options: %d", scheme, + enable_options); return APR_SUCCESS; } if (strcasecmp(scheme, "HTTPS") == 0 || strcasecmp(scheme, "WSS") == 0) { if (!ap_proxy_ssl_enable(NULL)) { ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, - "proxy_cluster_try_pingpong: cping_cpong failed (mod_ssl not configured?)"); + "proxy_cluster_try_pingpong: pingpong failed (mod_ssl not configured?)"); return APR_EGENERAL; } is_ssl = 1; @@ -1178,11 +1172,11 @@ static apr_status_t proxy_cluster_try_pingpong(request_rec *r, proxy_worker *wor } ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_cluster_try_pingpong: trying %s", backend->connection->client_ip); - status = http_handle_cping_cpong(backend, r, timeout); + status = http_handle_ping_pong(backend, r, timeout); } if (status != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_cluster_try_pingpong: cping_cpong failed"); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_cluster_try_pingpong: pingpong failed"); backend->close = 1; } @@ -1256,7 +1250,7 @@ static void *APR_THREAD_FUNC check_proxy_worker(apr_thread_t *thread, void *data rnew->method = "PING"; rnew->uri = "/"; rnew->headers_in = apr_table_make(rnew->pool, 1); - rv = proxy_cluster_try_pingpong(rnew, worker, url, conf, ou->mess.ping, ou->mess.timeout); + rv = proxy_cluster_try_pingpong(rnew, worker, url, conf); apr_pool_destroy(rrp); /* We have checked the worker... check if we were told to stop */ @@ -1615,11 +1609,21 @@ static proxy_worker *internal_process_worker(proxy_worker *worker, int checking_ } if (helper->index != worker->s->index) { /* something is very bad */ - ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, - "find_session_route: byrequests balancer skipping BAD worker"); + ap_log_error( + APLOG_MARK, APLOG_ERR, 0, r->server, + "find_session_route: byrequests balancer skipping BAD worker (helper->index: %d, worker->s->index: %d)", + helper->index, worker->s->index); return NULL; /* probably used by different worker */ } + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "find_session_route: worker %d name: %s lbfactor: %d checking_standby: %d", worker->s->index, +#ifdef PROXY_WORKER_EXT_NAME_SIZE + worker->s->name_ex, worker->s->lbfactor, checking_standby); +#else + worker->s->name, worker->s->lbfactor, checking_standby); +#endif + /* standby logic * lbfactor: -1 broken node. * 0 standby. @@ -1829,6 +1833,27 @@ static proxy_worker *internal_find_best_byrequests(const proxy_balancer *balance return mycandidate; } +static void set_worker_load(proxy_worker *worker, int load) +{ + if (worker == NULL || load < -1 || load > 100) { + return; + } + + if (load == -1) { + worker->s->status |= PROXY_WORKER_IN_ERROR; + } else if (load == 0) { + worker->s->status &= ~PROXY_WORKER_IN_ERROR; + worker->s->status |= PROXY_WORKER_HOT_STANDBY; + } else { + worker->s->status &= ~PROXY_WORKER_IN_ERROR; + worker->s->status &= ~PROXY_WORKER_STOPPED; + worker->s->status &= ~PROXY_WORKER_DISABLED; + worker->s->status &= ~PROXY_WORKER_HOT_STANDBY; + } + + worker->s->lbfactor = load; +} + /* * Check that we could connect to the node and create corresponding balancers and workers. * id : worker id @@ -1886,7 +1911,7 @@ static int proxy_node_isup(request_rec *r, int id, int load) return HTTP_INTERNAL_SERVER_ERROR; } - /* Try a ping/pong to check the node */ + /* Try a ping/pong to check the node */ if (load >= 0 || load == -2) { /* Only try usuable nodes */ if (!apr_is_empty_table(proxyhctemplate)) { @@ -1909,28 +1934,19 @@ static int proxy_node_isup(request_rec *r, int id, int load) url = apr_pstrcat(r->pool, worker->s->scheme, "://", worker->s->hostname, ":", sport, "/", NULL); } worker->s->error_time = 0; /* Force retry now */ - if (proxy_cluster_try_pingpong(r, worker, url, conf, node->mess.ping, node->mess.timeout) != APR_SUCCESS) { + if (proxy_cluster_try_pingpong(r, worker, url, conf) != APR_SUCCESS) { worker->s->status |= PROXY_WORKER_IN_ERROR; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_cluster_isup: pingpong %s failed", url); return HTTP_INTERNAL_SERVER_ERROR; } } } - if (load == -2) { - return 0; - } else if (load == -1) { - worker->s->status |= PROXY_WORKER_IN_ERROR; - worker->s->lbfactor = -1; - } else if (load == 0) { - worker->s->status |= PROXY_WORKER_HOT_STANDBY; - worker->s->lbfactor = 0; - } else { - worker->s->status &= ~PROXY_WORKER_IN_ERROR; - worker->s->status &= ~PROXY_WORKER_STOPPED; - worker->s->status &= ~PROXY_WORKER_DISABLED; - worker->s->status &= ~PROXY_WORKER_HOT_STANDBY; - worker->s->lbfactor = load; - } + + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_cluster_isup: Found worker %d, setting load: %d", id, + load); + + set_worker_load(worker, load); + return 0; } @@ -1941,40 +1957,100 @@ static int proxy_host_isup(request_rec *r, const char *scheme, const char *host, apr_status_t rv; int nport = atoi(port); - rv = apr_socket_create(&sock, APR_INET, SOCK_STREAM, 0, r->pool); - if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, "proxy_host_isup: pingpong (apr_socket_create) failed"); - return HTTP_INTERNAL_SERVER_ERROR; - } - rv = apr_sockaddr_info_get(&to, host, APR_INET, nport, 0, r->pool); - if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, - "proxy_host_isup: pingpong (apr_sockaddr_info_get(%s, %d)) failed", host, nport); - return HTTP_INTERNAL_SERVER_ERROR; - } - - rv = apr_socket_connect(sock, to); - if (rv != APR_SUCCESS) { - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_host_isup: pingpong (apr_socket_connect) failed"); - return HTTP_INTERNAL_SERVER_ERROR; - } - /* XXX: For the moment we support only AJP */ if (strcasecmp(scheme, "AJP") == 0) { + rv = apr_socket_create(&sock, APR_INET, SOCK_STREAM, 0, r->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, + "proxy_host_isup: pingpong (apr_socket_create) failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } + rv = apr_sockaddr_info_get(&to, host, APR_INET, nport, 0, r->pool); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, r->server, + "proxy_host_isup: pingpong (apr_sockaddr_info_get(%s, %d)) failed", host, nport); + apr_socket_close(sock); + return HTTP_INTERNAL_SERVER_ERROR; + } + + rv = apr_socket_connect(sock, to); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "proxy_host_isup: pingpong (apr_socket_connect) failed"); + apr_socket_close(sock); + return HTTP_INTERNAL_SERVER_ERROR; + } rv = ajp_handle_cping_cpong(sock, r, apr_time_from_sec(10)); + apr_socket_close(sock); if (rv != APR_SUCCESS) { ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_host_isup: cping_cpong failed"); return HTTP_INTERNAL_SERVER_ERROR; } + } else if (strncasecmp(scheme, "HTTP", 4) == 0 || strncasecmp(scheme, "WS", 2) == 0) { + char *url; + proxy_server_conf *conf; + proxy_worker *worker = NULL; + server_rec *s = main_server; + int nport = atoi(port); + + while (s && worker == NULL) { + int i, j; + void *sconf = s->module_config; + conf = (proxy_server_conf *)ap_get_module_config(sconf, &proxy_module); + + for (i = 0; i < conf->balancers->nelts; i++) { + proxy_balancer *balancer = &(APR_ARRAY_IDX(conf->balancers, i, proxy_balancer)); + if (balancer == NULL) { + continue; + } + + /* we could use worker = ap_proxy_get_worker(r->pool, balancer, conf, url); instead, but + * that would require us to guess the scheme correctly and there are many options with upgrades */ + for (j = 0; j < balancer->workers->nelts; j++) { + proxy_worker *w = APR_ARRAY_IDX(balancer->workers, j, proxy_worker *); + if (w->s->port == nport && + (strncasecmp(w->s->scheme, "HTTP", 4) == 0 || strncasecmp(w->s->scheme, "WS", 2) == 0) && + compare_hostname(w->s->hostname, host) == 0) { + /* we have it! */ + worker = w; + break; + } + } + + if (worker != NULL) { + break; + } + } + + s = s->next; + } + + if (worker == NULL) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, + "proxy_host_isup: worker not found for %s://%s:%s, no pingpong possible", scheme, host, port); + return HTTP_INTERNAL_SERVER_ERROR; + } + /* Using worker's scheme will allow us to not care about whether http was upgraded or not... */ + url = apr_pstrcat(r->pool, worker->s->scheme, "://", host, ":", port, NULL); + url = normalize_workername(r->pool, url); + if (url == NULL) { + ap_log_error(APLOG_MARK, APLOG_ERR, 0, r->server, "proxy_host_isup: normalize_workername returns NULL"); + return HTTP_INTERNAL_SERVER_ERROR; + } + + rv = proxy_cluster_try_pingpong(r, worker, url, conf); + if (rv != APR_SUCCESS) { + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "proxy_host_isup: pingpong failed"); + return HTTP_INTERNAL_SERVER_ERROR; + } } else { ap_log_error(APLOG_MARK, APLOG_NOTICE, 0, r->server, "proxy_host_isup: %s no yet supported", scheme); } - apr_socket_close(sock); return 0; } -static proxy_worker *searchworker(request_rec *r, const char *bal, const char *ptr, int *id, +static proxy_worker *searchworker(request_rec *r, const char *bal, const char *url, int *id, const proxy_server_conf **the_conf) { /* search for the worker in the VirtualHosts */ @@ -1987,13 +2063,13 @@ static proxy_worker *searchworker(request_rec *r, const char *bal, const char *p balancer = ap_proxy_get_balancer(r->pool, conf, bal, 0); if (balancer != NULL) { - worker = ap_proxy_get_worker(r->pool, balancer, conf, ptr); + worker = ap_proxy_get_worker(r->pool, balancer, conf, url); if (worker != NULL) { proxy_cluster_helper *helper; if (worker->s->index != -1) { *id = worker->s->index; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, - "searchworker: %s worker->s->index: %d the_conf %ld", ptr, *id, (uintptr_t)conf); + "searchworker: %s worker->s->index: %d the_conf %ld", url, *id, (uintptr_t)conf); *the_conf = conf; return worker; /* Done current index */ } @@ -2001,18 +2077,18 @@ static proxy_worker *searchworker(request_rec *r, const char *bal, const char *p if (helper && helper->index != -1) { *id = helper->index; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, - "searchworker: %s helper->index %d the_conf %ld", ptr, *id, (uintptr_t)conf); + "searchworker: %s helper->index %d the_conf %ld", url, *id, (uintptr_t)conf); *the_conf = conf; return worker; /* Done previous index */ } if (helper && helper->shared) { *id = helper->shared->index; ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, - "searchworker: %s helper->shared->index %d the_conf %ld", ptr, *id, (uintptr_t)conf); + "searchworker: %s helper->shared->index %d the_conf %ld", url, *id, (uintptr_t)conf); *the_conf = conf; return worker; /* our index was saved when we remove... */ } - ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "searchworker: %s FAILED", ptr); + ap_log_error(APLOG_MARK, APLOG_DEBUG, 0, r->server, "searchworker: %s FAILED", url); return NULL; } }