Friday, December 19, 2008

Zenoss 2.3.2 LDAP authentication with Ubuntu 8.04 and the stack installer

I was able to get the Active Directory authentication module loaded for our Ubuntu Server 8.04 stack installer-based Zenoss 2.3.2 installation. There is a bit of confusion about how to do this, as the wiki instructions for setup assume you are using the RPM-based installer or have installed from source. This turned out to not be too difficult given that the Ubuntu 8.04 distribution comes with the python-ldap package. In summary, you need to link in the distribution's installed python-ldap components into the site packages path for Zenoss's local Python 2.4 runtime and compile them. Here are the steps (these assume you have already downloaded and placed the LDAPUserFolder and LDAPMultiPlugins packages in the path identified in the wiki instructions):

Install python-ldap
(As root)
aptitude install python-ldap
Link python-ldap components to Zenoss's site packages path
We need the _ldap.so binary compiled against Python 2.4 and the source files. As the zenoss user:
#The Zenoss local Python site package path is $ZENHOME/lib/python!
cd $ZENHOME/lib/python
mkdir ldap
mkdir ldap/schema
ln -s /usr/share/pyshared/ldif.py
ln -s /usr/share/pyshared/ldapurl.py
ln -s /usr/lib/python2.4/site-packages/_ldap.so
cd ldap
ln -s /usr/share/pyshared/ldap/async.py
ln -s /usr/share/pyshared/ldap/controls.py
ln -s /usr/share/pyshared/ldap/filter.py
ln -s /usr/share/pyshared/ldap/__init__.py
ln -s /usr/share/pyshared/ldap/modlist.py
ln -s /usr/share/pyshared/ldap/cidict.py
ln -s /usr/share/pyshared/ldap/dn.py
ln -s /usr/share/pyshared/ldap/functions.py
ln -s /usr/share/pyshared/ldap/ldapobject.py
ln -s /usr/share/pyshared/ldap/sasl.py
cd schema
ln -s /usr/share/pyshared/ldap/schema/__init__.py
ln -s /usr/share/pyshared/ldap/schema/models.py
ln -s /usr/share/pyshared/ldap/schema/subentry.py
ln -s /usr/share/pyshared/ldap/schema/tokenizer.py
Compile .py files
Now that we have the files linked in from the global shared Python path (where the python-ldap deb installer put them), we need to compile all of the .py files using Zenoss's local python 2.4 installation:
cd $ZENHOME/lib/python
python /usr/local/zenoss/python/lib/python2.4/py_compile.py ldif.py
python /usr/local/zenoss/python/lib/python2.4/py_compile.py ldapurl.py
cd ldap
python /usr/local/zenoss/python/lib/python2.4/py_compile.py *.py
cd schema
python /usr/local/zenoss/python/lib/python2.4/py_compile.py *.py
Now that everything is compiled, restart zope (as zenoss, zopectl restart) and you can proceed with the rest of the instructions in the above wiki article. You will now see the ActiveDirectory Multi Plugin in the plugin list on the http://zenoss-installation:8080/zport/acl_users/manage_workspace page.

Tuesday, December 16, 2008

Faster DFS recovery application

In trying to set up DFS replication, we had a number of files that were not present in both the primary DFS partner and the destination partner. In this case, DFS will move all of the files "missing" from the primary partner out of the tree and into a separate pre-existing path on each destination volume. Microsoft will provide you with a recovery script that calls xcopy to, based on the generated PreExistingManifest.xml file, move the files back into their original locations.

The problem we had was that shelling out to xcopy when you have millions of relatively small files was going to take, well, months to complete. I built the following .NET (3.5, C#) console application which proved to do this at hundreds of times the rate of the Microsoft script. The only issue is that it does not replicate permissions; since we did not need that for our recovery, it fit the bill.

Please use at your own risk. I make no warranties. I recommend specifying an alternate recovery path when calling the application so you can validate output first.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace DFSRecovery
{
/// <summary>
/// Handles copying DFS files back into the original folder structure.
/// </summary>
class Program
{
/// <summary>
/// The main application loop.
/// </summary>
/// <param name="args">The args. See usage text.</param>
static void Main(string[] args)
{
if (args.Length < 3)
{
Console.WriteLine("\nDFSRecovery version " + System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString() + " [Arthur Penn, http://devarthur.blogspot.com]");
Console.WriteLine("Usage: DFSRecovery.exe \"\\\\path\\to\\PreExistingManifest.xml\" \"\\\\path\\to\\pre-existing\\folder\" \"\\\\path\\to\\output\\folder\" [print only=true|false]");
Environment.Exit(1);
}

// Load the PreExistingManifest.xml document and select the values we need
var doc = XDocument.Load(args[0]);
string preExistingFolder = args[1];
string outputFolder = args[2];
bool printOnly = false;
if (args.Length > 3)
{
printOnly = bool.Parse(args[3]);
}

int rc = 0;

var actions = from n in doc.Descendants("Resource")
select new {
FileOrFolder = ((string)n.Descendants("Attributes").First()),
Source = Path.Combine(preExistingFolder, (string)n.Descendants("NewName").First()),
Destination = Path.Combine(outputFolder, ((string)n.Descendants("Path").First()).Substring(7,
((string)n.Descendants("Path").First()).Length - 7))
};

foreach (var item in actions)
{
try
{
if (File.Exists(item.Source))
{
if (File.Exists(item.Destination))
{
if (printOnly)
{
Console.WriteLine("Target file exists: \"" + item.Destination + "\"");
}
}
else
{
CopyFile(item.Source, item.Destination, printOnly);
}
}
else
{
// It's a directory
CopyDirectory(item.Source, item.Destination, printOnly);
//break;
}
}
catch (Exception x)
{
rc = 1;
Console.WriteLine("Exception copying \"" + item.Source + "\" to \"" + item.Destination +
"\": " + x.ToString());
}
}

Environment.Exit(rc);
}

/// <summary>
/// Ensures the directory is present.
/// </summary>
/// <param name="path">The path.</param>
/// <param name="isDirectory">if set to <c>true</c> [is directory].</param>
/// <param name="printOnly">if set to <c>true</c> [print only].</param>
static void EnsureDirectory(string path, bool isDirectory, bool printOnly)
{
string targetFolder = (isDirectory ? path : path.Substring(0, path.LastIndexOf("\\")));
if (Directory.Exists(targetFolder))
{
if (printOnly)
{
Console.WriteLine("Target folder exists: \"" + targetFolder + "\"");
}
}
else
{
if (printOnly)
{
Console.WriteLine("Creating target folder: \"" + targetFolder + "\"");
}
else
{
Directory.CreateDirectory(targetFolder);
}
}
}

/// <summary>
/// Copies the directory.
/// </summary>
/// <param name="sourcePath">The source path.</param>
/// <param name="destinationPath">The destination path.</param>
/// <param name="printOnly">if set to <c>true</c> [print only].</param>
static void CopyDirectory(string sourcePath, string destinationPath, bool printOnly)
{
EnsureDirectory(destinationPath, true, printOnly);
foreach (string file in Directory.GetFiles(sourcePath))
{
//#if DEBUG
// Console.Write("From CopyDirectory: ");
//#endif
string fileName = file.Substring(file.LastIndexOf("\\") + 1);
CopyFile(file, Path.Combine(destinationPath, fileName), printOnly);
}

// Recursively process directories
foreach (string directory in Directory.GetDirectories(sourcePath))
{
string sourceSubDirectory = directory.Substring(directory.LastIndexOf("\\") + 1);
string destinationSubDirectory = Path.Combine(destinationPath, sourceSubDirectory);

CopyDirectory(directory, destinationSubDirectory, printOnly);
}
}

/// <summary>
/// Copies the file.
/// </summary>
/// <param name="sourcePath">The source path.</param>
/// <param name="destinationPath">The destination path.</param>
/// <param name="printOnly">if set to <c>true</c> [print only].</param>
static void CopyFile(string sourcePath, string destinationPath, bool printOnly)
{
if (printOnly)
{
Console.WriteLine("Copying \"" + sourcePath + "\" to \"" + destinationPath + "\"");
}
else
{
EnsureDirectory(destinationPath, false, printOnly);
File.Copy(sourcePath, destinationPath);
}
}
}
}