NAME
   Perlbal::Plugin::SessionAffinity - Sane session affinity (sticky
   sessions) for Perlbal

VERSION
   version 0.010

SYNOPSIS
       LOAD SessionAffinity

       CREATE POOL backends
         POOL backends ADD 10.20.20.100
         POOL backends ADD 10.20.20.101
         POOL backends ADD 10.20.20.102

       CREATE SERVICE balancer
         SET listen          = 0.0.0.0:80
         SET role            = reverse_proxy
         SET pool            = backends
         SET persist_client  = on
         SET persist_backend = on
         SET verify_backend  = on
         SET plugins         = sessionaffinity
       ENABLE balancer

DESCRIPTION
   Perlbal doesn't support session affinity (or otherwise known as "sticky
   sessions") out of the box. There is a plugin on CPAN called
   Perlbal::Plugin::StickySessions but there are a few problems with it.

   This plugin should be do a much better job. Go ahead and read why you
   should use this one and how it works.

WHY YOU SHOULD USE IT
   Here are things that are unique in this plugin. I am comparing this with
   the current available session affinity implementation available on CPAN
   (Perlbal::Plugin::StickySessions).

   *   It supports session affinity for all requests

       Unlike the other plugin, this one uses a proper hook that supports
       not just file fetching, but for each and every request.

   *   No patches required

       Unlike the other plugin, that comes with two patches (which were not
       integrated into Perlbal core), this one requires no patches
       whatsoever.

   *   It's up-to-date

       Unlike the other plugin, that still requires a patch that includes a
       hook that was already introduced (which shows it's clearly
       outdated), this plugin is very much up to speed with things.

   *   It's thin and sane

       Unlike the other plugin, which is mostly copy-pasted from some
       handling code in Perlbal itself (seriously!), this module contains
       no copy-pasted code, is much smaller and leaner, and is much less
       likely to break between new versions of Perlbal.

   *   No breakage

       Unlike the other plugin, which - after close inspection - seemed
       breakable (to say the least, since connect-aheads don't seem to get
       cleaned up), this plugin uses a completely different method which
       emphasizes correctness and the least intervention with Perlbal
       itself, and keeps Perlbal in charge of the critical operations.

       Small note here: this does not mean it will definitely play nice
       with everything you already have. Specifically any hooks that rely
       on the name of the service might be affected.

       Please read further under Incompatibilities to understand the issue
       better.

   *   Much less security risk

       Unlike the other plugin, which sets a cookie with the backend ID
       correlating to the backend order in the pool, this plugin uses SHA1
       checksum IDs (with an optionally randomly-created salt) for each
       server, and allows you to change the header name and add a checksum
       salt (whether randomly-created or your own) for the cookie.

       This makes it harder for an attacker to understand what the header
       represents and how many backends exist (since there is no counter).

   *   Features

       Unlike the other plugin, that simply has things hardcoded, this
       plugin allows to change both the header name and the salt used to
       create the ID. By default the salt is off but you can turn it on and
       then either use a randomly-created one or set your own.

HOW DOES IT WORK
 Basic stuff
   Basically, the module creates a SHA1 checksum for each backend node, and
   provides the user with a cookie request. If the user provides that
   cookie in return, it will try and find and provide the user with that
   specific node.

   If the node is no longer in the service's pool, or the cookie matches a
   node that doesn't exist, it will provide the user with a cookie again.

 Advanced stuff
   The plugin sets up dedicated pools and services for each service's node.
   This is required since Perlbal has no way of actually allowing you to
   specify the node a user will go to, only the service. Not to worry, this
   creation is done lazily so it saves as much memory as it can.

   When a user comes in with a cookie of a node that exist in the service's
   pool it will create a pool for it (if one doesn't exist), and a matching
   service for it (if one doesn't exist) and then direct to user to it.

   The check against nodes and pools is done live and not against the
   static configuration file. This means that if you're playing with the
   pools (changing them live, for example), it will still work just fine.

   A new service is created using configurations from the existing service.
   The more interesting details is that reuse is emphasized so no new
   sockets are created and instead this new service uses the already
   existing sockets (along with existing connections) instead of firing new
   ones. It doesn't open a new socket for listening or anything like that.
   This also means your SSL connections work seamlessly. Yes, it's insanely
   cool, I know! :)

 Incompatibilities
   If you've read the Advanced stuff section above, you might have guessed
   a possible problem with anything that relies on the name of the service.

   If you're using a plugin that relies on the name of the service, you
   might notice it stops working properly. This is because the new service
   that is generated by SessionAffinity is no longer the previous service,
   and doesn't contain its name. Instead it has its own name, which is not
   known to your plugin.

   If you're using the "header" command to add headers to the backend, fear
   not. We copy over the headers from the original service to the new one.
   That still works just fine.

   One possible way to fix it (implemented and later removed) is to include
   the previous name in a new unofficial (and unauthorized) key in the
   service hash.

ATTRIBUTES
 affinity_cookie_header
   The name of the cookie header for the session.

   Default: X-SERVERID.

 affinity_use_salt
   Whether to use a salt or not when calculating SHA1 IDs.

       # both are equal
       affinity_use_salt = 1
       affinity_use_salt = yes

       # opposite meaning
       affinity_use_salt = 0
       affinity_use_salt = no

   Default: no.

 affinity_salt
   The salt that is used to create the backend's SHA1 IDs.

   Default: the following code is run when you load
   Perlbal::Plugin::SessionAffinity to create the salt on start up:

       join q{}, map { $_ = rand 999; s/\.//; $_ } 1 .. 10;

   If you want predictability with salt, you can override it as such:

       affinity_salt = helloworld

       # now the calculation will be:
       my $sha1 = sha1hex( $salt . $ip . $port );

 affinity_use_domain
   Uses domain-mode for finding the backend. This is an alternate way of
   deciding the backend, which enables backends to persist per domain,
   allowing you to avoid a fragmented cache. If you have a lot of cache
   misses because of jumping between backends, try turning this feature on.

   This feature ignores the cookie provided (and does not provide its own
   cookie) since backends are decided by the domain name alone.

       # both are equal
       affinity_use_domain = 1
       affinity_use_domain = yes

       # opposite meaning
       affinity_use_domain = 0
       affinity_use_domain = no

   Default: no.

SUBROUTINES/METHODS
 register
   Registers our events.

 unregister
   Unregister our hooks and setters events.

 get_ip_port
   Parses a request's cookies and finds the specific cookie relating to
   session affinity and get the backend details via the ID in the cookie.

 find_backend_by_id
   Given a SHA1 ID, find the correct backend to which it belongs.

 find_backend_by_domain_id
   Given a SHA1 ID for a domain, find the correct backend to which it
   belongs.

 create_id
   Creates a SHA1 checksum ID using Digest::SHA. The checksum is composed
   of the IP, port and salt. If you want to have more predictability, you
   can provide a salt of 0 or "string" and then the checksum would be
   predictable.

   This should make it clear on how it's created:

       if ( $has_salt ) {
           $checksum = sha1sum( $salt . "$ip:$port" );
       } else {
           $checksum = sha1sum( "$ip:$port" );
       }

 create_domain_id
   Same concept as the above "create_id" function, except for the following
   changes:

   Accepts a domain and a list of nodes (which is assumed to be ordered),
   uses the "domain_index" function to get the index in the nodes of a
   domain and picks the correct node from the list it receives by index.

 domain_index
   This function tries to fetch an index number for a given domain name. It
   accepts a domain name and the maximum index number.

   It translates the domain name to a long number, and uses mod ("%") on
   it.

DEPENDENCIES
 Perlbal
   Obviously.

 CGI::Cookies
   To parse and create cookies.

 Digest::SHA
   To provide a SHA1 checksum.

SEE ALSO
 Perlbal::Plugin::StickySessions
AUTHOR
   Sawyer X <[email protected]>

COPYRIGHT AND LICENSE
   This software is copyright (c) 2013 by Sawyer X.

   This is free software; you can redistribute it and/or modify it under
   the same terms as the Perl 5 programming language system itself.