diff --git a/packages/acs-admin/acs-admin.info b/packages/acs-admin/acs-admin.info
index 1f5193085e..f81870354c 100644
--- a/packages/acs-admin/acs-admin.info
+++ b/packages/acs-admin/acs-admin.info
@@ -9,7 +9,7 @@
f
t
-
+
Don Baccus
An interface for Site-wide administration of an OpenACS Installation.
2023-02-08
@@ -20,9 +20,9 @@
GPL
3
-
+
-
+
diff --git a/packages/acs-admin/www/cluster.tcl b/packages/acs-admin/www/cluster.tcl
index 5e7dba24ff..b20c08a841 100644
--- a/packages/acs-admin/www/cluster.tcl
+++ b/packages/acs-admin/www/cluster.tcl
@@ -3,7 +3,7 @@ ad_page_contract {
@creation-date Feb 8, 2023
} {
- {drop_node:nohtml,notnull ""}
+ {disconnect_node:nohtml,notnull ""}
{flush_node:nohtml,notnull ""}
}
@@ -13,11 +13,11 @@ set context [list $page_title]
set server_cluster_enabled_p [server_cluster_enabled_p]
set dynamic_cluster_nodes [::acs::cluster dynamic_cluster_nodes]
-if {$drop_node ne ""} {
+if {$disconnect_node ne ""} {
#
- # Drop the provided node from DynamicClusterPeers
+ # Disconnect the provided node from DynamicClusterPeers
#
- acs::cluster drop_dynamic_node $drop_node
+ acs::cluster dynamic_cluster_reconfigure disconnect $disconnect_node
set done 1
} elseif {$flush_node ne ""} {
#
@@ -100,7 +100,7 @@ if {$server_cluster_enabled_p} {
-
}
diff --git a/packages/acs-tcl/acs-tcl.info b/packages/acs-tcl/acs-tcl.info
index 466d6673b8..a4ffc593a2 100644
--- a/packages/acs-tcl/acs-tcl.info
+++ b/packages/acs-tcl/acs-tcl.info
@@ -9,7 +9,7 @@
f
t
-
+
OpenACS
The Kernel Tcl API library.
2023-05-15
@@ -18,7 +18,7 @@
GPL version 2
3
-
+
diff --git a/packages/acs-tcl/tcl/cluster-init.tcl b/packages/acs-tcl/tcl/cluster-init.tcl
index 9a5f628c99..2b0b267be0 100644
--- a/packages/acs-tcl/tcl/cluster-init.tcl
+++ b/packages/acs-tcl/tcl/cluster-init.tcl
@@ -77,18 +77,28 @@ if {[server_cluster_enabled_p]} {
::acs::cluster register_nodes -startup
ns_atstartup {
- #
- # We could add some code for testing actively keep-alive
- # status.
- #
- ns_log notice "CHECK ::throttle '[::info commands ::throttle]'"
- if {0 && [::info commands ::throttle] ne ""} {
- ns_log notice "CHECK calling ::acs::cluster check_nodes"
- throttle do ::acs::cluster check_nodes
+ ns_log notice "acs::cluster starting:" \
+ "running as canonical server [::acs::cluster current_server_is_canonical_server]," \
+ "cluster nodes: [nsv_get cluster cluster_peer_nodes]"
+ }
+
+ #
+ # Register callback for shutdown operations. When the shutdown is
+ # performed at a dynamic cluster node, disconnect the node from the
+ # cluster.
+ #
+ ns_atshutdown {
+ if {[::acs::cluster current_server_is_canonical_server]} {
+ ns_log notice "acs::cluster: shutdown canonical server"
+ } elseif {[::acs::cluster current_server_is_dynamic_cluster_peer]} {
+ ns_log notice "acs::cluster: shutdown dynamic cluster peer (perform disconnect operation)"
+ acs::cluster send_disconnect_request_to_canonical_server
+ } else {
+ ns_log notice "acs::cluster: shutdown static cluster peer"
}
}
}
-ns_log notice "cluster-init done"
+
#
# Local variables:
# mode: tcl
diff --git a/packages/acs-tcl/tcl/cluster-procs.tcl b/packages/acs-tcl/tcl/cluster-procs.tcl
index f3f33b92cb..913ff7ada6 100644
--- a/packages/acs-tcl/tcl/cluster-procs.tcl
+++ b/packages/acs-tcl/tcl/cluster-procs.tcl
@@ -103,6 +103,7 @@ namespace eval ::acs {
acs::cache_flush_all ""
acs::cache_flush_pattern ""
::acs::cluster "^::acs::cluster\s+join_request"
+ ::acs::cluster "^::acs::cluster\s+disconnect_request"
}
#
@@ -309,25 +310,7 @@ namespace eval ::acs {
-package_id $::acs::kernel_id \
-parameter DynamicClusterPeers]
}
-
- :public method drop_dynamic_node {node} {
- #
- # Drop the provided node from DynamicClusterPeers
- #
- set dynamic_cluster_nodes [:dynamic_cluster_nodes]
- set p [lsearch $dynamic_cluster_nodes $node]
- if {$p != -1} {
- set cluster_nodes [lreplace $dynamic_cluster_nodes $p $p]
- parameter::set_value \
- -package_id $::acs::kernel_id \
- -parameter DynamicClusterPeers \
- -value $cluster_nodes
- } else {
- ns_log warning "cluster: can't drop node '$node': not in the" \
- "dynamic cluster configuration: $dynamic_cluster_nodes"
- }
- }
-
+
:public method check_state {} {
#
# Check the livelyness of the dynamic cluster nodes. This
@@ -339,14 +322,14 @@ namespace eval ::acs {
-package_id $::acs::kernel_id \
-parameter ClusterAutodeleteInterval \
-default 2m]
-
+
foreach node [:dynamic_cluster_nodes] {
set last_contact [acs::cluster last_contact $node]
if {$last_contact ne ""} {
set seconds [expr {$last_contact/1000}]
if {[clock seconds]-($last_contact/1000) > [ns_baseunit -time $autodeleteInterval]} {
- ns_log notice "[self] drop dynamic node $node due to ClusterAutodeleteInterval"
- :drop_dynamic_node $node
+ ns_log notice "[self] disconnect dynamic node $node due to ClusterAutodeleteInterval"
+ :disconnect_dynamic_node $node
}
}
}
@@ -394,7 +377,7 @@ namespace eval ::acs {
} {
ns_log warning "cluster node is not listed in dynamic peers." \
"Must re-join canonical server: ${:canonicalServerLocation}"
- :send_join_request ${:canonicalServerLocation}
+ :send_join_request_to_canonical_server
}
}
@@ -534,7 +517,7 @@ namespace eval ::acs {
return $result
}
- :method current_server_is_dynamic_cluster_peer {} {
+ :public method current_server_is_dynamic_cluster_peer {} {
#
# We are a dynamic cluster peer, when we are not the
# canonical server neither isted in the static server
@@ -655,13 +638,14 @@ namespace eval ::acs {
return $result
}
- :public method send_join_request {location} {
+ :method send_dynamic_cluster_reconfigure_request {operation} {
#
- # Send a join request to the canonical server.
+ # Send a cluster reconfigure request to the canonical server.
#
- :log "send_join_request to $location"
- set r [:send $location [self] join_request ${:currentServerLocation}]
- #:log "... join_request returned $r"
+ set location ${:canonicalServerLocation}
+ :log "send $operation request to $location"
+ set r [:send $location [self] ${operation}_request ${:currentServerLocation}]
+ #:log "... $operation request returned $r"
if {[dict exists $r body]} {
#
@@ -669,32 +653,69 @@ namespace eval ::acs {
# sync. Therefore, we have lost confidence in our
# caches and clear these.
#
- :log "send_join_request returned [dict get $r body], flushing all my caches"
+ :log "$operation request returned [dict get $r body], flushing all my caches"
acs::cache_flush_all
}
}
- :public method join_request {peerLocation} -returns boolean {
+ :public method send_join_request_to_canonical_server {} {
+ #
+ # Send a join request to the canonical server.
+ #
+ :send_dynamic_cluster_reconfigure_request join
+ }
+
+ :public method send_disconnect_request_to_canonical_server {} {
#
- # A join request was received
+ # Send a disconnect request to the canonical server.
#
- ns_log notice "Cluster join_request from '$peerLocation'"
+ :send_dynamic_cluster_reconfigure_request disconnect
+ }
+
+ :public method dynamic_cluster_reconfigure {operation qualifiedLocation} -returns boolean {
+ #
+ # Reconfigure the cluster via "join" or "disconnect" operation,
+ # when running on the canonical server. The result of the
+ # reconfiguration is a changed list of
+ # DynamicClusterPeers. The method returns a boolean value
+ # indicating success.
+ #
+ ns_log notice "Cluster reconfigure $operation from '$qualifiedLocation'"
+
set success 1
#
- # Was the join request received by a canonical server?
+ # To be ultra-conservative, we could allow cluster
+ # reconfigure operations only on the canonical
+ # server. This would require also to alter the
+ # acs-admin/cluster page to show the trash icon only when
+ # the page is executed on the canonical server.
#
- if {![:current_server_is_canonical_server]} {
- ns_log warning "Cluster join_request rejected," \
+ if {0 && ![:current_server_is_canonical_server]} {
+ ns_log warning "Cluster reconfigure rejected," \
"since it was received by a non-canonical server"
set success 0
} else {
#
- # We know, we are running on the canonical server, an
+ # We know, we are running on the canonical server, and
# we know that the request is trustworthy.
#
- ns_log notice "Cluster join_request $peerLocation accepted from $peerLocation"
+ ns_log notice "Cluster reconfigure $qualifiedLocation accepted from $qualifiedLocation"
set dynamicClusterNodes [:dynamic_cluster_nodes]
- set dynamicClusterNodes [lsort -unique [concat $dynamicClusterNodes [:qualified_location $peerLocation]]]
+ switch $operation {
+ "join" {
+ set dynamicClusterNodes \
+ [lsort -unique [concat $dynamicClusterNodes $qualifiedLocation]]
+ }
+ "disconnect" {
+ set dynamicClusterNodes \
+ [lsearch -inline -all -not -exact $dynamicClusterNodes $qualifiedLocation]
+ }
+ default {
+ ns_log warning "Cluster reconfigure rejected," \
+ "received invalid operation '$operation'"
+ return 0
+ }
+ }
#
# The parameter::set_value operation causes a
# clusterwide cache-flush for the parameters
@@ -703,11 +724,24 @@ namespace eval ::acs {
-package_id $::acs::kernel_id \
-parameter DynamicClusterPeers \
-value $dynamicClusterNodes
- ns_log notice "[self] Cluster join_request leads to DynamicClusterPeers $dynamicClusterNodes"
+ ns_log notice "[self] reconfigure $operation leads to DynamicClusterPeers $dynamicClusterNodes"
}
return $success
}
+ :public method join_request {peerLocation} -returns boolean {
+ #
+ # Server received a request to join dynamic cluster nodes from $peerLocation.
+ #
+ return [:dynamic_cluster_reconfigure join [:qualified_location $peerLocation]]
+ }
+
+ :public method disconnect_request {peerLocation} -returns boolean {
+ #
+ # Server received a request to disconnect $peerLocation from dynamic cluster nodes.
+ #
+ return [:dynamic_cluster_reconfigure disconnect [:qualified_location $peerLocation]]
+ }
:method peer_nodes {dynamic_peers} {
#
@@ -820,7 +854,7 @@ namespace eval ::acs {
ns_log notice "Current host ${:currentServerLocation} is not included in ${:configured_cluster_hosts}"
if {![:current_server_is_canonical_server]} {
ns_log notice "... must join at canonical server ${:canonicalServerLocation}"
- :send_join_request ${:canonicalServerLocation}
+ :send_join_request_to_canonical_server
}
} else {
#ns_log notice "Current host ${:currentServerLocation} is included in ${:configured_cluster_hosts}"