%3 ::nx::Object ::nx::Object __default_accessor __default_method_call_protection __object_configureparameter __resolve_method_path contains copy delete object method delete object property delete object variable destroy_on_cleanup info info info lookup parameters info lookup slots info lookup syntax info lookup variables info object method args info object method body info object method callprotection info object method debug info object method definition info object method definitionhandle info object method deprecated info object method exists info object method handle info object method origin info object method parameters info object method registrationhandle info object method returns info object method submethods info object method syntax info object method type info object slots info object variables info variable definition info variable name info variable parameter move object alias object forward object method object property object variable private protected public qn require namespace require object method require private object method require protected object method require public object method serialize ::acs::Cluster ::acs::Cluster allowed_command broadcast current_server_is_canonical_server current_server_is_dynamic_cluster_peer current_server_locations execute incoming_request init is_canonical_server is_configured_server is_current_server join_request last_contact last_request log message decode message encode message sign message verify ns_http_send peer_nodes preauth preferred_location qualified_location reachable register_nodes secret secret_configured send send_join_request setup update_node_info ::acs::Cluster->::nx::Object

Class ::acs::Cluster

::acs::Cluster[i] create ... \
           [ -allowed_command (default " set "" unset "" nsv_set "" nsv_unset "" nsv_incr "" nsv_dict "" bgdelivery "" callback "" ns_cache "^ns_cache\s+eval" ns_cache_flush "" util_memoize_flush_regexp_local "" ns_urlspace "" acs::cache_flush_all "" acs::cache_flush_pattern "" ::acs::cluster "^::acs::cluster\s+join_request" ") ] \
           [ -allowed_host (default " "127.0.0.1" 1 ") ] \
           [ -currentServerLocation (default "") ] \
           [ -url (default "/acs-cluster-do") ]

Class Relations

  • class: ::nx::Class[i]
  • superclass: ::nx::Object[i]
::nx::Class create ::acs::Cluster \
     -superclass ::nx::Object

Methods (to be applied on instances)

  • broadcast (scripted)

    if {[ns_ictl epoch] > 0} {
        catch {::throttle do incr ::count(cluster:broadcast)}
    }
    
    # Small optimization for cachingmode "none": no need to
    # send cache flushing requests to nodes, when there is no
    # caching in place.
    #
    if {[ns_config "ns/parameters" cachingmode "per-node"] eq "none"
        && [lindex $args 0] in {
            acs::cache_flush_pattern
            acs::cache_flush_all
            ns_cache}
    } {
        #
        # If caching mode is none, it is expected that all
        # nodes have this parameter set. Therefore, there is no
        # need to communicate cache flushing commands.
        #
        return
    }
    
    if {[nsv_get cluster cluster_peer_nodes locations]} {
        #
        # During startup the throttle thread might not be started,
        # so omit these statistic values
        #
        if {[ns_ictl epoch] > 0} {
            foreach location $locations {
                catch {::throttle do incr ::count(cluster:sent)}
                set t0 [clock clicks -microseconds]
                :send $location {*}$args
                set ms [expr {([clock clicks -microseconds] - $t0)/1000}]
                catch {::throttle do incr ::agg_time(cluster:sent) $ms}
            }
        } else {
            foreach location $locations {
                :send $location {*}$args
            }
        }
    }
  • current_server_is_canonical_server (scripted)

    if { ![info exists :canonicalServer] || ${:canonicalServer} eq "" } {
        ns_log Error "Your configuration is not correct for server clustering."  "Please ensure that you have the CanonicalServer parameter set correctly."
        return 1
    }
    set result 0
    foreach location ${:currentServerLocations} {
        if {[:is_canonical_server $location]} {
            set result 1
            break
        }
    }
    #:log "current_server_is_canonical_server $result"
    return $result
  • incoming_request (scripted)

    catch {::throttle do incr ::count(cluster:received)}
    
    ad_try {
        #ns_logctl severity Debug(connchan) on
        #ns_logctl severity Debug(request) on
        #ns_logctl severity Debug(ns:driver) on
        #ns_logctl severity Debug on
        set r [:message decode]
        set receive_timestamp [clock clicks -milliseconds]
        dict with r {
            #
            # We could check here the provided timepstamp and
            # honor only recent requests (protection against
            # replay attacks). However, the allowed requests
            # are non-destructive.
            #
            nsv_set cluster $peer-last-contact $receive_timestamp
            nsv_set cluster $peer-last-request $receive_timestamp
            nsv_incr cluster $peer-count
            ns_log notice "--cluster got cmd='$cmd' from $peer after [expr {$receive_timestamp - $timestamp}]ms"
    
            set result [:execute $r]
        }
    } on error {errorMsg} {
        ns_log notice "--cluster error: $errorMsg"
        ns_return 417 text/plain $errorMsg
    } on ok {r} {
        #ns_log notice "--cluster success $result"
        ns_return 200 text/plain $result
    }
  • is_canonical_server (scripted)

    if { ![info exists :canonicalServer] || ${:canonicalServer} eq "" } {
        ns_log Error "Your configuration is not correct for server clustering."  "Please ensure that you have the CanonicalServer parameter set correctly."
        return 1
    }
    
    set result [expr {$location in ${:canonicalServerLocation}}]
    #ns_log notice "is_canonical_server $location -> $result"
    return $result
  • join_request (scripted)

    ns_log notice "Cluster join_request from '$peerLocation'"
    set success 1
    #
    # Was the join request received by a canonical server?
    #
    if {![:current_server_is_canonical_server]} {
        ns_log warning "Cluster join_request 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 that the request is trustworthy.
        #
        ns_log notice "Cluster join_request $peerLocation accepted from $peerLocation"
        set dynamicClusterNodes [parameter::get -package_id $::acs::kernel_id -parameter DynamicClusterPeers]
        set dynamicClusterNodes [lsort -unique [concat $dynamicClusterNodes $peerLocation]]
        #
        # The parameter::set_value operation causes a
        # clusterwide cache-flush for the parameters
        #
        parameter::set_value -package_id $::acs::kernel_id -parameter DynamicClusterPeers -value $dynamicClusterNodes
        ns_log notice "Cluster join_request leads to DynamicClusterPeers $dynamicClusterNodes"
    }
    return $success
  • last_contact (scripted)

    if {[nsv_get cluster $location-last-contact clicksms]} {
        return $clicksms
    }
  • last_request (scripted)

    if {[nsv_get cluster $location-last-request clicksms]} {
        return $clicksms
    }
  • preauth (scripted)

    return filter_break
  • register_nodes (scripted)

    :log ":register_nodes startup $startup"
    
    #
    # Configure base configuration values
    #
    #
    set dynamic_peers [parameter::get -package_id $::acs::kernel_id -parameter DynamicClusterPeers]
    
    # At startup, when we are running on the canonical server,
    # check, whether the existing DynamicClusterPeers are
    # still reachable. When the canonical server is started
    # before the other cluster nodes, this parameter should be
    # empty. However, when the canonical server is restarted,
    # there might be some of the peer nodes already active.
    #
    if {$startup
        && ${:current_server_is_canonical_server}
        && $dynamic_peers ne ""
    } {
        #
        # When we are starting the canonical server, it resets
        # the potentially pre-existing dynamic nodes unless
        # these are reachable.
        #
        set old_peer_locations $dynamic_peers
        :log "canonical server starts with existing DynamicClusterPeers nodes: $old_peer_locations"
        #
        # Keep the reachable cluster nodes in
        # "DynamicClusterPeers".
        #
        set new_peer_locations {}
        foreach location $old_peer_locations {
            if {[:reachable $location]} {
                lappend new_peer_locations $location
            }
        }
        if {$new_peer_locations ne $old_peer_locations} {
            #
            # Update the DynamicClusterPeers in the database
            # such that the other nodes will pick it up as
            # well.
            #
            :log "updating DynamicClusterPeers to $new_peer_locations"
            parameter::set_value -package_id $::acs::kernel_id -parameter DynamicClusterPeers  -value $new_peer_locations
            set dynamic_peers $new_peer_locations
        }
    }
    
    #
    # Determine the peer nodes.
    #
    set cluster_peer_nodes [:peer_nodes $dynamic_peers]
    nsv_set cluster cluster_peer_nodes $cluster_peer_nodes
    
    if {![:is_configured_server ${:currentServerLocations}]} {
        #
        # Current node is not pre-registered.
        #
        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}
        }
    } else {
        #ns_log notice "Current host ${:currentServerLocation} is included in ${:configured_cluster_hosts}"
    }
  • secret_configured (scripted)

    set secret [:secret]
    return [expr {$secret ne ""}]
  • send (scripted)

    :log "outgoing request to $location // $args"
    set t0 [clock clicks -microseconds]
    switch $delivery {
        #connchan -
        #udp      -
        ns_http   {set result [:${delivery}_send $location {*}$args]}
        default {error "unknown delivery method '$delivery'"}
    }
    ns_log notice "-cluster: $location $args sent"  "total [expr {([clock clicks -microseconds] - $t0)/1000.0}]ms"
    return $result
  • send_join_request (scripted)

    :log "send_join_request to $location"
    set r [:send $location [self] join_request ${:currentServerLocation}]
    #:log "... join_request returned $r"
    
    if {[dict exists $r body]} {
        #
        # During startup/separation caches might not be in
        # 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"
        acs::cache_flush_all
    }
  • setup (scripted)

    set :currentServerLocations [:current_server_locations]
    set :currentServerLocation [:preferred_location ${:currentServerLocations}]
    
    set :canonicalServer [parameter::get -package_id $::acs::kernel_id -parameter CanonicalServer]
    set :canonicalServerLocation [:preferred_location [:qualified_location ${:canonicalServer}]]
    
    set :current_server_is_canonical_server [:current_server_is_canonical_server]
    set :staticServerLocations  [lmap entry [parameter::get -package_id $::acs::kernel_id -parameter ClusterPeerIP] {
            :preferred_location [:qualified_location $entry]
        }]
  • update_node_info (scripted)

    set dynamic_peers [parameter::get  -package_id $::acs::kernel_id  -parameter DynamicClusterPeers]
    
    if {!${:current_server_is_canonical_server}} {
        #
        # The current node might be a static or a dynamic
        # peer.  Do we have contact to the canonical_server?
        #
        if {![:reachable ${:canonicalServerLocation}]} {
            #
            # We lost contact to the canonical server. This is
            # for our server not a big problem, since all
            # other peer-to-peer updates will continue to
            # work.
            #
            # During downtime of the canonical server,
            # scheduled procedures (e.g. mail delivery) will
            # be interrupted, and no new servers can register.
            #
            ns_log warning "cluster node lost contact to "  "canonical server: ${:canonicalServerLocation}"
        }
        #
        # Are we an dynamic peer and not listed in
        # DynamicClusterPeers? This might happen in
        # situations, where the canonical server was
        # restarted (or separated for a while).
        #
        if {[:current_server_is_dynamic_cluster_peer]
            && ${:currentServerLocation} ni $dynamic_peers
        } {
            ns_log warning "cluster node is not listed in dynamic peers."  "Must re-join canonical server: ${:canonicalServerLocation}"
            :send_join_request ${:canonicalServerLocation}
        }
    }
    
    #
    # Update cluster_peer_nodes if necessary
    #
    set oldConfig [lsort [nsv_get cluster cluster_peer_nodes]]
    set newConfig [lsort [:peer_nodes $dynamic_peers]]
    if {$newConfig ne $oldConfig} {
        #
        # The cluster configuration has changed
        #
        ns_log notice "cluster config changed:\nOLD $oldConfig\nNEW $newConfig"
        nsv_set cluster cluster_peer_nodes $newConfig
    }