MultipleInstances

From Request Tracker Wiki
Jump to navigation Jump to search

How to Run Multiple RT Instances

An /instance/ is a combination of:

  • Specific MySQL database, to hold a specific set of users, tickets, scrips, etc
  • RT core
  • Customizations, including config file, gui, code

This article shows various ways of running multiple instances on the same apache process, with each instance using a different url. Note that solution use FastCGI, because different FastCGI processes can have different code loaded at the same RT::* namespace. With RT-3.4.4 and mod_perl2.0 you can have several RT instances, but mod_perl1 is not suitable, it uses a single perl interpreter for the server instance.

Simple Answer

!FastCGI

The simplest solution is to install RT multiple times into completely separate directory trees. Then launch a FastCGI process for each install, and connect each FastCGI process to a unique url.

So, assume you have installed RT in /rt1 and /rt2 and you wish to connect these to these urls

http://myrt.com/rt1

and

http://myrt.com/rt2

You can set up apache this way:

AddHandler fastcgi-script fcgi

<Directory "/rt1" >
    Options FollowSymLinks ExecCGI
    AllowOverride None
</Directory>
FastCgiServer /rt1/bin/mason_handler.fcgi
ScriptAlias /rt1 /rt1/bin/mason_handler.fcgi

<Directory "/rt2" >
    Options FollowSymLinks ExecCGI
    AllowOverride None
</Directory>
FastCgiServer /rt2/bin/mason_handler.fcgi
ScriptAlias /rt2 /rt2/bin/mason_handler.fcgi

mod_perl 2.x

Use RT 3.4.4 or greater and install it as described in FastCGI section above, but in apache config use:

<VirtualHost your.ip.address>
  ServerName your.rt1.server.hostname
  DocumentRoot /rt1/share/html
  AddDefaultCharset UTF-8

  PerlOptions +Parent
  PerlRequire /rt1/bin/webmux.pl
  <Location />
      SetHandler perl-script
      PerlHandler RT::Mason
  </Location>
</VirtualHost>

<VirtualHost your.ip.address>
  ServerName your.rt2.server.hostname
  DocumentRoot /rt2/share/html
  AddDefaultCharset UTF-8

  PerlOptions +Parent
  PerlRequire /rt2/bin/webmux.pl
  <Location />
      SetHandler perl-script
      PerlHandler RT::Mason
  </Location>
</VirtualHost>

More Features, Better Maintenance

The simple answer works, but does nothing to ease the sharing of customizations between instances, nor the testing and installation of RT upgrades, nor the creation of new instances.

I have multiple departments, each of which needs a separate RT instance. Each instance needs a separate config file, and possibly separate gui or code customizations, as well as access to site-wide customizations. I want:

  • Single install of base RT code per RT version.
  • Single set of site-wide mods per RT version.
  • Separate directory tree per set of local customizations, containing the minimum number of files.
  • Trivial way to make an instance by combining any set of local customizations with any RT version.

Example

Suppose the English and Physics departments each use RT, version 3.0.8. Now the Math department wants an instance, with no mods.

  • Create new MySQL database, /rt_math/
  • Create new directory tree to hold math config, /rt-math
  • Create needed mail aliases
  • Configure apache to connect = with a new fastcgi process that runs the existing RT-3.0.8 code but uses [=/rt-math/etc/RT_SiteConfig.pm]

Now Math wants some customizations, maybe change a logo on their page, something that requires a change to a Mason or RT component.

  • Add the new file to /rt-math/local/html/... or /rt-math/local/lib/RT/... in the usual way. Use ..._Local.pm for RT overlays, or Mason callbacks as needed.
  • Restart apache.

Now RT 3.0.9 comes out. I need to upgrade, test, install.

  • Install into /rt-3.0.9 and apply site-wide mods as needed.
  • Configure apache to use = and connect this to a new fastcgi process that combines the existing /rt-physics instance and the new /rt-3.0.9 code. (Note this uses the existing live MySQL data. The truly paranoid would copy the data to a test MySQL database and copy the instance code to /rt-physics-test first.)
  • Restart apache.
  • When satisfied with testing, simply change apache config so that the production url, = refers to a fastcgi process that uses /rt-3.0.9 and /rt-physics
  • Test /math/ and /english/ separately, if their customizations need this. Otherwise just configure apache/fastcgi to use 3.0.9 instead of 3.0.8.

Technical Details - Overview

The key to using the same RT code with instance-specific directory trees lies in modifying some internal paths in RT.pm, such as $SITE_CONFIG_FILE, [=$LocalPath], and so on. Normally these are set at compile time, but a small mod lets the compiled values be overridden by an environment variable, and this variable can be specificed in apache as an argument to the [=FastCgiServer] directive.

Thus the [=FastCgiServer] directive can contain the path to the mason_handler.fcgi file (which specifies which base RT install to use) and separately an instance-specific environment variable (which specifies which instance-specific configs and mods to use).


Question: I wonder if there is any way to use a pool of dynamic FastCGI. When I first read this I thought, oh, use mod_env to pass in the variable settings. But I bet RT loads and stores a lot of state based on the values of those variables, so I think it wouldn't work. -- MarkBucciarelli


The only files that need to know about this environment variable are lib/RT.pm.in and bin/webmux.pl.in. And it's convenient to patch sbin/rt-setup-database.in to make it easy to create new MySQL databases based on a new instance-specific config. The patches are small and do not affect anything unless the environment variables are set.

In practice, I use two environment variables.

  • RT_INSTANCE_PATH -- Exactly as described above, this would be set to, e.g. /rt-physics to use the physics instance values.
  • RT_VENDOR_PATH -- I use ..._Local.pm files for per-instance customizations, and ..._Vendor.pm files for site-wide customizations where I don't want to directly patch the original files. I could use this environment variable to create a test instance with new site-wide mods, without re-installing RT.

Technical Details -- Checklist for Multiple Instance Installation

  • To install:
    • Unpack RT source
    • Use the patch below
    • Compile and install RT in the usual way. Assume this is installed in /rt.
  • To create a new RT Instance (e.g. instance name math, at = whose files are in /rt-math):
    • Set up the instance files in a separate directory tree, mirroring the RT tree. Minimally you will need [=/rt-math/etc/RT_SiteConfig.pm], but you might also have Mason customizations in /rt-math/local/html/... or RT customizations in /rt-math/lib/RT/...
    • Create the MySQL database, something like:
sbin/rt-setup-database /rt-math --action=init --dba=root --prompt-for-dba-password
    • For fastcgi reasons, you must have a separately-named mason_handler.fcgi even though that file does not need to be patched. I do this with a symlink:
cd /rt/bin
ln -s mason_handler.fcgi mason_handler_math.fcgi
    • Now configure apache:
<Directory /rt/math >
Options FollowSymLinks ExecCGI
AllowOverride None
</Directory>

FastCgiServer /rt/bin/mason_handler_math.fcgi -initial-env RT_INSTANCE_PATH=/rt-math
ScriptAlias /math /rt/bin/mason_handler_math.fcgi
    • Set up any needed mail aliases in the normal way. Make sure they refer to =

Appendix -- Patch

Here is an updated patch for RT 3.4.2 It has been minimally tested, so I would love input on it. No major changes except that I incorporated the comment at the bottom. Also a few changes to the context so it applies cleanly to 3.4.2, and I changed it to unified diff format.

Apply this patch to the RT source before running configure. If you start in the top level RT directory, you should run:

patch -p1 < patchfile

Here is the patchfile:

diff -ru rt-3.4.2-pristine/bin/webmux.pl.in rt-3.4.2/bin/webmux.pl.in
--- rt-3.4.2-pristine/bin/webmux.pl.in  2005-05-11 20:36:48.064856616 -0400
+++ rt-3.4.2/bin/webmux.pl.in   2005-05-11 20:39:43.540180312 -0400
@@ -64,6 +64,12 @@
 }

 use lib ( "@LOCAL_LIB_PATH@", "@RT_LIB_PATH@" );
+if ($ENV{RT_INSTANCE_PATH}) {
+    lib->import("$ENV{RT_INSTANCE_PATH}/local/lib");
+}
+if ($ENV{RT_VENDOR_PATH}) {
+    lib->import($ENV{RT_VENDOR_PATH});
+}
 use RT;

 package RT::Mason;
diff -ru rt-3.4.2-pristine/lib/RT.pm.in rt-3.4.2/lib/RT.pm.in
--- rt-3.4.2-pristine/lib/RT.pm.in      2005-05-11 20:36:48.098851448 -0400
+++ rt-3.4.2/lib/RT.pm.in       2005-05-11 20:41:52.026647400 -0400
@@ -98,6 +98,60 @@
 # via the web interface)
 $MasonSessionDir = '@MASON_SESSION_PATH@';

+=item import
+
+Allow override of various internal paths.
+
+    RT->import (
+            RT_INSTANCE_PATH => '/usr/local/rt/stuff',
+            SITE_CONFIG_FILE => '/etc/stuff.pm',
+              ...
+    );
+
+If RT_INSTANCE_PATH is set in the arguments (or in %ENV)
+then it replaces the old value of $BasePath in the following
+variables:
+  $SITE_CONFIG_FILE
+  $LocalPath
+  $LocalEtcPath
+  $LocalLexiconPath
+  $MasonLocalComponentRoot
+  $MasonDataDir
+  $MasonSessionDir
+
+Beyond that, those individual values can be set explicitly
+by arguments.
+
+=cut
+
+sub import {
+no strict 'refs';
+    shift;
+    my %args = @_;
+    return unless %args || $ENV{RT_INSTANCE_PATH};
+
+    my @variables = qw (
+        SITE_CONFIG_FILE
+        LocalPath
+        LocalEtcPath
+        LocalLexiconPath
+        MasonLocalComponentRoot
+        MasonDataDir
+        MasonSessionDir
+    );
+
+    my $RT_INSTANCE_PATH = $args{RT_INSTANCE_PATH} || $ENV{RT_INSTANCE_PAT

+ if ($RT_INSTANCE_PATH) {
+ foreach my $vref (@variables) {
+ $$vref =~ s/^\Q$BasePath\E/$RT_INSTANCE_PATH/;
+ }
+ }
+ foreach my $vref (@variables) {
+ $$vref = $args{$vref} if defined ( $args{$vref} );
+ }
+
+use strict 'refs';
+}
diff -ru rt-3.4.2-pristine/sbin/rt-setup-database.in rt-3.4.2/sbin/rt-setup-database.in
--- rt-3.4.2-pristine/sbin/rt-setup-database.in 2005-05-11 20:36:48.128846888 -0400
+++ rt-3.4.2/sbin/rt-setup-database.in 2005-05-11 20:45:35.800628584 -0400
@@ -50,11 +50,17 @@
qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
use lib "@RT_LIB_PATH@";
 +## RT_INSTANCE_PATH can be passed on command line.
 +## Can change where RT_SiteConfig is read from.
 +my $RT_INSTANCE_PATH;
 +BEGIN {
 + $RT_INSTANCE_PATH = shift || $ENV{RT_INSTANCE_PATH};
 +}
 
 #This drags in RT's config.pm
 # We do it in a begin block because RT::Handle needs to know the type to do its
 # inheritance
 -use RT;
 +use RT (RT_INSTANCE_PATH => $RT_INSTANCE_PATH);
 use Carp;
 use RT::User;
 use RT::CurrentUser;
 
 
 

Comments

I would love people to test this out. I have only minimally tested it. For comparison to the older version, please look in the revisions.


      • Who are you? Identify yourself so that we can give feedback. *



The patch works great for me. The only caveat I found was that I had to create separate copies of rt-crontool for each instance. For rt-crontool, I had to modify the "use lib" line (50) to be instance-specific:


  1. diff rt-crontool rt-crontool-testinstance1 50c50 < use lib ("/var/rt3/local/lib", "/var/rt3/lib"); --- > use lib ("/var/rt3_testinstance1/local/lib", "/var/rt3_testinstance1/lib");


Your paths will vary depending on how you configured your queues. Presumably this would be necessary for standalone_httpd and webmux.pl as well, if you use those scripts. -SJ


I got the patch to work with on a Debian Stable (Sarge) install (RT v3.4.4) but with some twists:

- Instance configs are stored in /usr/local/share/instance1 - I don't pass the instance name to fcgi with a "/" - I also set the instance config paths in RT.pm differently - Segmented location of Mason Cache files in /var/cache/request-tracker3.4 into instance relevant dirs.

The change in webmux.pl comes after this line (about line 65)

#
 # START Multi-Instance Patch Segment
 #
 if ($ENV{RT_INSTANCE_PATH}) {
     lib->import("/usr/local/share/rtinst/" . $ENV{RT_INSTANCE_PATH} . "/lib");
 }
 if ($ENV{RT_VENDOR_PATH}) {
     lib->import($ENV{RT_VENDOR_PATH});
 }
 #
 # END Multi-Instance Patch Segment
 #
 
 

The changes in RT.pm are after the following lines (around line 97):

# RT needs to put session data (for preserving state between connections
 # via the web interface)
 $MasonSessionDir = '/var/cache/request-tracker3.4/session_data';
 
 
 #
 # START Multi-Instance Patch
 #
 =item import
 
 Allow override of various internal paths.
 
     RT->import (
             RT_INSTANCE_PATH => '/usr/share/request-tracker3.4',
             SITE_CONFIG_FILE => '/etc/stuff.pm',
               ...
     );
 
 If RT_INSTANCE_PATH is set in the arguments (or in %ENV)
 then it replaces the old value of $BasePath in the following
 variables:
   $SITE_CONFIG_FILE
   $LocalPath
   $LocalEtcPath
   $LocalLexiconPath
   $MasonLocalComponentRoot
   $MasonDataDir
   $MasonSessionDir
 
 Beyond that, those individual values can be set explicitly
 by arguments.
 
 =cut
 
 sub import {
 no strict 'refs';
     shift;
     my $InstancesLocalBasePath = '/usr/local/share/rtinst';
     my %args = @_;
     return unless %args || $ENV{RT_INSTANCE_PATH};
 
     my @variables = qw (
         SITE_CONFIG_FILE
         LocalPath
         LocalEtcPath
         LocalLexiconPath
         MasonLocalComponentRoot
         MasonDataDir
         MasonSessionDir
     );
 
     my $RT_INSTANCE_PATH = $args{RT_INSTANCE_PATH} || $ENV{RT_INSTANCE_PATH};
 
  if ($RT_INSTANCE_PATH) {
     $CORE_CONFIG_FILE = $InstancesLocalBasePath . "/" . $RT_INSTANCE_PATH . "/etc/RT_Config.pm";
     $SITE_CONFIG_FILE = $InstancesLocalBasePath . "/" . $RT_INSTANCE_PATH . "/etc/RT_SiteConfig.pm";
 
     $EtcPath = $InstancesLocalBasePath . "/" . $RT_INSTANCE_PATH . "etc";
     $LocalPath = $InstancesLocalBasePath . "/" . $RT_INSTANCE_PATH;
     $LocalEtcPath = $InstancesLocalBasePath . "/" . $RT_INSTANCE_PATH . "/etc";
     $LocalLexiconPath = $InstancesLocalBasePath . "/" . $RT_INSTANCE_PATH . "/po";
 
     # $MasonLocalComponentRoot is where your rt instance keeps its site-local
     # mason html files.
 
     $MasonLocalComponentRoot = $InstancesLocalBasePath . "/" . $RT_INSTANCE_PATH . "/html";
 
     # $MasonDataDir Where mason keeps its datafiles
 
     $MasonDataDir = "/var/cache/request-tracker3.4/" . $RT_INSTANCE_PATH . "/mason_data";
 
     # RT needs to put session data (for preserving state between connections
     # via the web interface)
     $MasonSessionDir = "/var/cache/request-tracker3.4/" . $RT_INSTANCE_PATH . "/session_data";
  }
 
 use strict 'refs';
 }
 #
 # END Multi-Instance Patch
 #
 
 

Hope that's helpful to someone...

MichaelErana g33k (at) efamilynj.org

I've done a patch for 3.6 that is actually applicable (I can't copy-paste the original patch above) and detailed procedures for FreeBSD in our wiki (http://wiki.koumbit.net/RequestTracker/InstallAndConfig#head-multiple-instance-setup). -- TheAnarcat


I needed this to work in version 3.8.1 and believe I have made the proper edits. It works for me anyway. -- jhoov

--- rt-3.8.1/sbin/rt-setup-database.in	2008-08-18 11:13:58.000000000 -0400
 +++ rt-3.8.1-multiple/sbin/rt-setup-database.in	2008-10-22 16:30:50.000000000 -0400
 @@ -76,11 +76,18 @@
 
  }
 
 +## RT_INSTANCE_PATH can be passed in environment.
 +## Can change where RT_SiteConfig is read from.
 +my $RT_INSTANCE_PATH;
 +BEGIN {
 + $RT_INSTANCE_PATH = $ENV{RT_INSTANCE_PATH};
 +}
 +
  #This drags in  RT's config.pm
  # We do it in a begin block because RT::Handle needs to know the type to do its
  # inheritance
  BEGIN {
 -    use RT;
 +    use RT (RT_INSTANCE_PATH => $RT_INSTANCE_PATH);
      RT::LoadConfig();
      RT::InitClasses();
  }
 
 --- rt-3.8.1/bin/webmux.pl.in	2008-08-18 11:14:11.000000000 -0400
 +++ rt-3.8.1-multiple/bin/webmux.pl.in	2008-10-22 16:42:09.000000000 -0400
 @@ -70,6 +70,10 @@
      my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
      my $bin_path;
 
 +    if ($ENV{RT_INSTANCE_PATH}) {
 +        push(@libs, "$ENV{RT_INSTANCE_PATH}/local/lib");
 +    }
 +
      for my $lib (@libs) {
          unless ( File::Spec->file_name_is_absolute($lib) ) {
              unless ($bin_path) {
 
 --- rt-3.8.1/lib/RT/Config.pm	2008-08-18 11:14:11.000000000 -0400
 +++ rt-3.8.1-multiple/lib/RT/Config.pm	2008-10-22 16:30:29.000000000 -0400
 @@ -368,11 +368,16 @@
  sub LoadConfig {
      my $self = shift;
      my %args = ( File => '', @_ );
 +    my $RT_INSTANCE_PATH = $args{RT_INSTANCE_PATH} || $ENV{RT_INSTANCE_PATH};
      $args{'File'} =~ s/(?<!Site)(?=Config\.pm$)/Site/;
      if ( $args{'File'} eq 'RT_SiteConfig.pm'
          and my $site_config = $ENV{RT_SITE_CONFIG} )
      {
          $self->_LoadConfig( %args, File => $site_config );
 +    } elsif ( $args{'File'} eq 'RT_SiteConfig.pm'
 +        and $RT_INSTANCE_PATH )
 +    {
 +        $self->_LoadConfig( %args, File => $RT_INSTANCE_PATH."/etc/RT_SiteConfig.pm" );
      } else {
          $self->_LoadConfig(%args);
      }
 
 --- rt-3.8.1/lib/RT.pm.in	2008-08-18 11:14:11.000000000 -0400
 +++ rt-3.8.1-multiple/lib/RT.pm.in	2008-10-22 16:30:40.000000000 -0400
 @@ -117,6 +117,57 @@
      }
  }
 
 +=item import
 +
 +Allow override of various internal paths.
 +
 +    RT->import (
 +            RT_INSTANCE_PATH => '/usr/local/rt/stuff',
 +              ...
 +    );
 +
 +If RT_INSTANCE_PATH is set in the arguments (or in %ENV)
 +then it replaces the old value of $BasePath in the following
 +variables:
 +  $LocalPath
 +  $LocalEtcPath
 +  $LocalLexiconPath
 +  $MasonLocalComponentRoot
 +  $MasonDataDir
 +  $MasonSessionDir
 +
 +Beyond that, those individual values can be set explicitly
 +by arguments.
 +
 +=cut
 +
 +sub import {
 +no strict 'refs';
 +    shift;
 +    my %args = @_;
 +    return unless %args || $ENV{RT_INSTANCE_PATH};
 +
 +    my @variables = qw (
 +        LocalPath
 +        LocalEtcPath
 +        LocalLexiconPath
 +        MasonLocalComponentRoot
 +        MasonDataDir
 +        MasonSessionDir
 +    );
 +
 +    my $RT_INSTANCE_PATH = $args{RT_INSTANCE_PATH} || $ENV{RT_INSTANCE_PATH};
 +    if ($RT_INSTANCE_PATH) {
 +        foreach my $vref (@variables) {
 +            $$vref =~ s/^\Q$BasePath\E/$RT_INSTANCE_PATH/;
 +        }
 +    }
 +    foreach my $vref (@variables) {
 +        $$vref = $args{$vref} if defined ( $args{$vref} );
 +    }
 +
 +use strict 'refs';
 +}
 
 =head1 NAME