Index: /trunk/ippdor/README
===================================================================
--- /trunk/ippdor/README	(revision 32874)
+++ /trunk/ippdor/README	(revision 32874)
@@ -0,0 +1,10 @@
+Installation:
+	./deployment/deploy.py -h
+
+Documentation:
+	cd ./doc && make
+
+Code Quality Metrics (pylint 0.25 required):
+	./quality_metrics.sh
+
+
Index: /trunk/ippdor/conf/pylintrc
===================================================================
--- /trunk/ippdor/conf/pylintrc	(revision 32874)
+++ /trunk/ippdor/conf/pylintrc	(revision 32874)
@@ -0,0 +1,310 @@
+# lint Python modules using external checkers.
+# 
+# This is the main checker controlling the other ones and the reports
+# generation. It is itself both a raw checker and an astng checker in order
+# to:
+# * handle message activation / deactivation at the module level
+# * handle some basic but necessary stats'data (number of classes, methods...)
+# 
+[MASTER]
+
+# Specify a configuration file.
+#rcfile=
+
+# Python code to execute, usually for sys.path manipulation such as
+# pygtk.require().
+#init-hook=
+
+# Profiled execution.
+profile=no
+
+# Add <file or directory> to the black list. It should be a base name, not a
+# path. You may set this option multiple times.
+ignore=CVS
+
+# Pickle collected data for later comparisons.
+persistent=yes
+
+# Set the cache size for astng objects.
+cache-size=500
+
+# List of plugins (as comma separated values of python modules names) to load,
+# usually to register additional checkers.
+load-plugins=
+
+
+[MESSAGES CONTROL]
+
+# Enable only checker(s) with the given id(s). This option conflicts with the
+# disable-checker option
+#enable-checker=
+
+# Enable all checker(s) except those with the given id(s). This option
+# conflicts with the enable-checker option
+#disable-checker=
+
+# Enable all messages in the listed categories (IRCWEF).
+#enable-msg-cat=
+
+# Disable all messages in the listed categories (IRCWEF).
+disable-msg-cat=I
+
+# Enable the message(s) with the given id(s).
+#enable-msg=
+
+# Disable the message(s) with the given id(s).
+disable-msg=W0704
+
+
+[REPORTS]
+
+# Set the output format. Available formats are text, parseable, colorized, msvs
+# (visual studio) and html
+output-format=text
+
+# Include message's id in output
+include-ids=no
+
+# Put messages in a separate file for each module / package specified on the
+# command line instead of printing them on stdout. Reports (if any) will be
+# written in a file name "pylint_global.[txt|html]".
+files-output=no
+
+# Tells whether to display a full report or only the messages
+reports=yes
+
+# Python expression which should return a note less than 10 (10 is the highest
+# note). You have access to the variables errors warning, statement which
+# respectively contain the number of errors / warnings messages and the total
+# number of statements analyzed. This is used by the global evaluation report
+# (R0004).
+evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
+
+# Add a comment according to your evaluation note. This is used by the global
+# evaluation report (R0004).
+comment=no
+
+# Enable the report(s) with the given id(s).
+#enable-report=
+
+# Disable the report(s) with the given id(s).
+#disable-report=
+
+
+# checks for :
+# * doc strings
+# * modules / classes / functions / methods / arguments / variables name
+# * number of arguments, local variables, branches, returns and statements in
+# functions, methods
+# * required module attributes
+# * dangerous default values as arguments
+# * redefinition of function / method / class
+# * uses of the global statement
+# 
+[BASIC]
+
+# Required attributes for module, separated by a comma
+required-attributes=
+
+# Regular expression which should only match functions or classes name which do
+# not require a docstring
+no-docstring-rgx=__.*__
+
+# Regular expression which should only match correct module names
+module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+))$
+
+# Regular expression which should only match correct module level names
+const-rgx=(([A-Z_][A-Z0-9_]*)|(__.*__))$
+
+# Regular expression which should only match correct class names
+class-rgx=[A-Z_][a-zA-Z0-9]+$
+
+# Regular expression which should only match correct function names
+function-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct method names
+method-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct instance attribute names
+attr-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct argument names
+argument-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct variable names
+variable-rgx=[a-z_][a-z0-9_]{2,30}$
+
+# Regular expression which should only match correct list comprehension /
+# generator expression variable names
+inlinevar-rgx=[A-Za-z_][A-Za-z0-9_]*$
+
+# Good variable names which should always be accepted, separated by a comma
+good-names=i,j,k,ex,Run,_
+
+# Bad variable names which should always be refused, separated by a comma
+bad-names=foo,bar,baz,toto,tutu,tata
+
+# List of builtins function names that should not be used, separated by a comma
+bad-functions=map,filter,apply,input
+
+
+# try to find bugs in the code using type inference
+# 
+[TYPECHECK]
+
+# Tells whether missing members accessed in mixin class should be ignored. A
+# mixin class is detected if its name ends with "mixin" (case insensitive).
+ignore-mixin-members=yes
+
+# List of classes names for which member attributes should not be checked
+# (useful for classes with attributes dynamically set).
+ignored-classes=SQLObject
+
+# When zope mode is activated, add a predefined set of Zope acquired attributes
+# to generated-members.
+zope=no
+
+# List of members which are set dynamically and missed by pylint inference
+# system, and so shouldn't trigger E0201 when accessed.
+generated-members=REQUEST,acl_users,aq_parent
+
+
+# checks for
+# * unused variables / imports
+# * undefined variables
+# * redefinition of variable from builtins or from an outer scope
+# * use of variable before assignment
+# 
+[VARIABLES]
+
+# Tells whether we should check for unused import in __init__ files.
+init-import=no
+
+# A regular expression matching names used for dummy variables (i.e. not used).
+dummy-variables-rgx=_|dummy
+
+# List of additional names supposed to be defined in builtins. Remember that
+# you should avoid to define new builtins when possible.
+additional-builtins=
+
+
+# checks for :
+# * methods without self as first argument
+# * overridden methods signature
+# * access only to existent members via self
+# * attributes not defined in the __init__ method
+# * supported interfaces implementation
+# * unreachable code
+# 
+[CLASSES]
+
+# List of interface methods to ignore, separated by a comma. This is used for
+# instance to not check methods defines in Zope's Interface base class.
+ignore-iface-methods=isImplementedBy,deferred,extends,names,namesAndDescriptions,queryDescriptionFor,getBases,getDescriptionFor,getDoc,getName,getTaggedValue,getTaggedValueTags,isEqualOrExtendedBy,setTaggedValue,isImplementedByInstancesOf,adaptWith,is_implemented_by
+
+# List of method names used to declare (i.e. assign) instance attributes.
+defining-attr-methods=__init__,__new__,setUp
+
+
+# checks for
+# * external modules dependencies
+# * relative / wildcard imports
+# * cyclic imports
+# * uses of deprecated modules
+# 
+[IMPORTS]
+
+# Deprecated modules which should not be used, separated by a comma
+deprecated-modules=regsub,string,TERMIOS,Bastion,rexec
+
+# Create a graph of every (i.e. internal and external) dependencies in the
+# given file (report R0402 must not be disabled)
+import-graph=
+
+# Create a graph of external dependencies in the given file (report R0402 must
+# not be disabled)
+ext-import-graph=
+
+# Create a graph of internal dependencies in the given file (report R0402 must
+# not be disabled)
+int-import-graph=
+
+
+# checks for sign of poor/misdesign:
+# * number of methods, attributes, local variables...
+# * size, complexity of functions, methods
+# 
+[DESIGN]
+
+# Maximum number of arguments for function / method
+max-args=8
+
+# Maximum number of locals for function / method body
+max-locals=25
+
+# Maximum number of return / yield for function / method body
+max-returns=6
+
+# Maximum number of branch for function / method body
+max-branchs=15
+
+# Maximum number of statements in function / method body
+max-statements=150
+
+# Maximum number of parents for a class (see R0901).
+max-parents=7
+
+# Maximum number of attributes for a class (see R0902).
+max-attributes=7
+
+# Minimum number of public methods for a class (see R0903).
+min-public-methods=0
+
+# Maximum number of public methods for a class (see R0904).
+max-public-methods=30
+
+
+# checks for:
+# * warning notes in the code like FIXME, XXX
+# * PEP 263: source code with non ascii character but no encoding declaration
+# 
+[MISCELLANEOUS]
+
+# List of note tags to take in consideration, separated by a comma.
+notes=FIXME,XXX,TODO
+
+
+# checks for similarities and duplicated code. This computation may be
+# memory / CPU intensive, so you should disable it if you experiments some
+# problems.
+# 
+[SIMILARITIES]
+
+# Minimum lines number of a similarity.
+min-similarity-lines=10
+
+# Ignore comments when computing similarities.
+ignore-comments=yes
+
+# Ignore docstrings when computing similarities.
+ignore-docstrings=yes
+
+
+# checks for :
+# * unauthorized constructions
+# * strict indentation
+# * line length
+# * use of <> instead of !=
+# 
+[FORMAT]
+
+# Maximum number of characters on a single line.
+#max-line-length=80
+max-line-length=120
+
+# Maximum number of lines in a module
+max-module-lines=1000
+
+# String used as indentation unit. This is usually " " (4 spaces) or "\t" (1
+# tab).
+indent-string='    '
Index: /trunk/ippdor/deployment/Deployment.py
===================================================================
--- /trunk/ippdor/deployment/Deployment.py	(revision 32874)
+++ /trunk/ippdor/deployment/Deployment.py	(revision 32874)
@@ -0,0 +1,200 @@
+#!/usr/bin/env python
+
+"""Utility class for Condor IPP software deployent
+"""
+
+import os
+import subprocess
+import tempfile
+import shutil
+import logging
+
+class Deployment:
+    """Utility class for Condor IPP software deployent
+    """
+    logger = logging.getLogger('ippdor')
+
+    CLASSES_FILENAMES = [ '__init__.py',
+                          'ChipManager.py', 
+                          'CamManager.py', 
+                          'FakeManager.py', 
+                          'WarpManager.py',
+                          'StackManager.py',
+                          'Gpc1Manager.py', 
+                          'helpers.py', 
+                          'constants/__init__.py',
+                          'constants/Globals.py',
+                          'constants/Chip.py',
+                          'constants/Camera.py',
+                          'constants/Fake.py',
+                          'constants/Warp.py',
+                          'constants/Stack.py',
+                          'exceptions/__init__.py',
+                          'exceptions/IppException.py',
+                          'exceptions/UninstantiableIppException.py',
+                          ]
+    EXECUTABLES_FILENAMES = [ 'generate_chip_jobs.py', 
+                              'generate_cam_jobs.py', 
+                              'generate_fake_jobs.py',
+                              'generate_warp_jobs.py', 
+                              'generate_stack_jobs.py', 
+                              'generate_exposure_dag.py',
+                              'ippdor_chip_to_warp.py', 
+                              # 'ippdor_chip_to_stack.py', 
+                              'undo_dag_exposure.py',
+                              'send_email.py', 
+                              'encapsulate_chip.sh', 
+                              'encapsulate_camera.sh', 
+                              'encapsulate_fake.sh',
+                              'encapsulate_warp.sh', 
+                              'encapsulate_stack.sh',
+                              ]
+
+    def __init__(self, 
+                 svn_source_url, 
+                 target_directory, 
+                 target_binary_directory, 
+                 logger = None):
+        """
+        Constructor:
+        
+        svn_source_url: SVN Source URL
+        target_directory: Target Python Directory
+        target_binary_directory: Target Executables Directory
+        logger: optional argument (if None: logging to logging.getLogger('ippdor'))
+        """
+        self.svn_source_url = svn_source_url
+        self.target_directory  = target_directory
+        self.target_binary_directory = target_binary_directory
+        self.temporary_directory = None # Set when necessary
+        if logger is not None:
+            Deployment.logger = logger
+
+    def copy_to_targetdir(self, path_to_file, filename):
+        """
+        Copy a file from the SVN temporary directory to the target
+        directory.
+        """
+        Deployment.logger.info('...... Installing %s' % filename)
+        shutil.copyfile('%s/%s/%s' % (self.temporary_directory, 
+                                      path_to_file, 
+                                      filename), 
+                        '%s/%s' % (self.target_directory, filename))
+
+    def copy_to_bindir(self, path_to_file, filename):
+        """
+        Copy a file from the SVN temporary directory to the target
+        binary directory.
+        """
+        Deployment.logger.info('...... Installing %s' % filename)
+        shutil.copyfile('%s/%s/%s' % (self.temporary_directory, 
+                                      path_to_file, 
+                                      filename), 
+                        '%s/%s' % (self.target_binary_directory, filename))
+
+    def check(self):
+        """
+        Check different elements
+        """
+        Deployment.logger.info('Checking Condor-IPP Softwares Configuration')
+        # self.target_directory
+        Deployment.logger.info('... Checking write permission to %s' % self.target_directory)
+        extra = ''
+        if not os.path.exists(self.target_directory):
+            os.makedirs(self.target_directory)
+            extra = ' (directory was created)'
+        if not os.access(self.target_directory, os.W_OK):
+            raise IOError('Directory [%s] is not writable' % self.target_directory)
+        Deployment.logger.info('...... OK%s' % extra)
+        # self.target_binary_directory
+        Deployment.logger.info('... Checking write permission to %s' % self.target_binary_directory)
+        extra = ''
+        if not os.path.exists(self.target_binary_directory):
+            os.makedirs(self.target_binary_directory)
+            extra = ' (directory was created)'
+        if not os.access(self.target_binary_directory, os.W_OK):
+            raise IOError('Directory [%s] is not writable' % self.target_binary_directory)
+        Deployment.logger.info('...... OK%s' % extra)
+        # Check deployment software version
+        # Anything else?
+        Deployment.logger.info('... Check complete')
+
+    def svn_export(self):
+        """
+        Export the software under configuration into the SVN temporary directory
+        """
+        Deployment.logger.info('Updating Condor-IPP Softwares SVN temporary directory')
+        Deployment.logger.info('... Creating temporary directory:')
+        self.temporary_directory = tempfile.mkdtemp()
+        Deployment.logger.info('...... OK [%s]' % self.temporary_directory)
+        Deployment.logger.info('... Exporting Condor source code to temporary directory')
+        process = subprocess.Popen( ('svn export --force %s %s' % (self.svn_source_url, 
+                                                                   self.temporary_directory)).split(' '),
+                                    stdout = open('/dev/null', 'w'), 
+                                    stderr = open('/dev/null', 'w') )
+        status = process.wait()
+        if status != 0:
+            Deployment.logger.error('Error: svn export failed')
+            raise Exception('svn export failed')
+        Deployment.logger.info('...... OK')
+        Deployment.logger.info('... SVN Update complete')
+        
+    def generate(self):
+        """
+        Generate any file depending on the installation target
+        """
+        Deployment.logger.info('Generating Condor-IPP Softwares')
+        Deployment.logger.debug('--- Nothing to generate yet from %s' % self.svn_source_url)
+        Deployment.logger.info('... Generation complete')
+
+    def deploy(self):
+        """
+        Deploy files on the target
+        """
+        Deployment.logger.info('Deploying Condor-IPP Softwares')
+        Deployment.logger.info('... Installing Python classes to [%s]' % self.target_directory)
+        for filename in Deployment.CLASSES_FILENAMES:
+            path_elements = filename.split('/')
+            if len(path_elements) > 1:
+                try:
+                    os.makedirs('%s/%s' % (self.target_directory,
+                                           path_elements[0]))
+                except OSError, e:
+                    Deployment.logger.warn(e)
+                    Deployment.logger.info('Ignored')
+        for filename in Deployment.CLASSES_FILENAMES:
+            self.copy_to_targetdir('ipp', filename)
+        Deployment.logger.info('... Installing executables to [%s]' % self.target_binary_directory)
+        for filename in Deployment.EXECUTABLES_FILENAMES:
+            self.copy_to_bindir('executables', filename)
+        Deployment.logger.info('... Deployment complete')
+        
+    def clean(self, doit = False):
+        """
+        Clean the installation, i.e. remove files and subdirectories
+        if doit is True (default is False). Otherwise, doesn't do
+        anything...
+        """
+        Deployment.logger.info('Cleaning')
+        if not doit:
+            Deployment.logger.warn('!!! NO DELETION WILL BE PERFORMED SINCE doit IS False')
+        try:
+            Deployment.logger.info('... Deleting temporary directory [%s]' % self.temporary_directory)
+            if doit:
+                for root, dirs, files in os.walk(self.temporary_directory, topdown=False):
+                    Deployment.logger.debug('...... Deleting [%s]' % root)
+                    for name in files:
+                        Deployment.logger.debug('...... Deleting [%s] (in files)' % name)
+                        os.remove(os.path.join(root, name))
+                    for name in dirs:
+                        Deployment.logger.debug('...... Deleting [%s] (in dirs)' % name)
+                        os.rmdir(os.path.join(root, name))
+                os.rmdir(self.temporary_directory)
+        except AttributeError:
+            Deployment.logger.info('... Nothing to be cleaned')
+        Deployment.logger.info('... Cleaning complete')
+
+if __name__ == '__main__':
+    print "Running doctest"
+    import doctest
+    doctest.testmod()
Index: /trunk/ippdor/deployment/__init__.py
===================================================================
--- /trunk/ippdor/deployment/__init__.py	(revision 32874)
+++ /trunk/ippdor/deployment/__init__.py	(revision 32874)
@@ -0,0 +1,3 @@
+"""
+This is the deployment module: a module to deploy Condor-IPP software.
+"""
Index: /trunk/ippdor/deployment/deploy.py
===================================================================
--- /trunk/ippdor/deployment/deploy.py	(revision 32874)
+++ /trunk/ippdor/deployment/deploy.py	(revision 32874)
@@ -0,0 +1,85 @@
+#!/usr/bin/env python
+
+"""
+Utility executable for IPP Condor deployment
+"""
+
+# Default values
+class DefaultValues:
+    """
+    This class contains all default values for the IPP Condor deployment
+    """
+    svnSourceUrl          = 'file:///home/schastel/svnrepo/ippdor/trunk/src'
+    targetDirectory       = '/home/schastel/local/share/python/ipp'
+    targetBinaryDirectory = '/home/schastel/local/bin'
+
+    def __init__(self):
+        """Not to be used as a class
+        """
+        raise Exception('Not to be used as a class')
+
+import sys
+import logging
+#optparse Deprecated in python 2.7. Will be replaced by argparse
+from optparse import OptionParser 
+from Deployment import Deployment
+
+if __name__ == '__main__':
+    USAGE = """usage: %prog [options] arg1 arg2
+
+Deploy the Condor-IPP softwares
+"""
+    PARSER = OptionParser(usage = USAGE)
+    PARSER.set_defaults(verbose=True)
+    PARSER.add_option( '-v', action = 'store_true', 
+                      dest = 'verbose',
+                      help = 'Be verbose',
+                      default = False)
+    PARSER.add_option('-q', action='store_true', 
+                      dest = 'quiet',
+                      help = 'Be quiet',
+                      default = False)
+    PARSER.add_option( '-s', '--svnSourceUrl',
+                       dest = "svnSourceUrl",
+                       help = 'SVN Source URL [default: %default]',
+                       default = DefaultValues.svnSourceUrl )
+    PARSER.add_option( '-t', '--targetDirectory',
+                       dest = "targetDirectory",
+                       help = 'Target Python Directory for classes [default: %default]',
+                       default = DefaultValues.targetDirectory )
+    PARSER.add_option( '-b', '--targetBinaryDirectory',
+                       dest = "targetBinaryDirectory",
+                       help = 'Target Executables Directory [default: %default]',
+                       default = DefaultValues.targetBinaryDirectory )
+    (OPTIONS, ARGS) = PARSER.parse_args()    # ... the values are stored in options
+
+    LOGGER = logging.getLogger('ippdor')
+    FORMATTER = logging.Formatter('%(asctime)s | %(levelname)7s | %(message)s', 
+                                  '%Y-%m-%dT%H:%M:%S')
+    STDOUT = logging.StreamHandler(sys.stdout)
+    STDOUT.setFormatter(FORMATTER)
+    LOGGER.addHandler(STDOUT) 
+    if OPTIONS.verbose:
+        LOGGER.setLevel(logging.DEBUG)
+        LOGGER.debug('Logging level set to DEBUG')
+    elif OPTIONS.quiet:
+        LOGGER.setLevel(logging.ERROR)
+        LOGGER.info('Logging level set to ERROR')
+    else:
+        LOGGER.setLevel(logging.INFO)
+
+    try:
+        DEPLOYMENT = Deployment(OPTIONS.svnSourceUrl,
+                                OPTIONS.targetDirectory,
+                                OPTIONS.targetBinaryDirectory)
+        DEPLOYMENT.check()
+        DEPLOYMENT.svn_export()
+        DEPLOYMENT.generate()
+        DEPLOYMENT.deploy()
+        DEPLOYMENT.clean(doit = True)
+    except Exception, e:
+        LOGGER.info('Exception caught')
+        DEPLOYMENT.clean(doit = False)
+        LOGGER.error(e)
+        import traceback
+        traceback.print_exc()
Index: /trunk/ippdor/doc/Chip2Stack/stack.tex
===================================================================
--- /trunk/ippdor/doc/Chip2Stack/stack.tex	(revision 32874)
+++ /trunk/ippdor/doc/Chip2Stack/stack.tex	(revision 32874)
@@ -0,0 +1,97 @@
+\documentclass[10pt]{article}
+\usepackage[hmargin=0.5in,vmargin=0.5in]{geometry}
+
+\begin{document}
+
+\textbf{\Large FOR EACH EXPOSURE}
+
+\begin{description}
+\item[Prerequisites] The exposures to be processed are
+  \textbf{labelled} externally in the chipRun table.
+\item[A. Chip stage] Detail prerequisites \mbox{ }
+  \begin{description}
+  \item[PRE A010] (Script) Generate the Condor job file containing the
+    \verb+chip_imfile.pl+ commands to be run on the cluster
+    \hfill{}\mbox{Equivalent to chip.imfile.load}
+  \item[A010] (Condor) Queue that Condor job file (exactly 60
+    commands/jobs: one for each ota) \hfill{}\mbox{Equivalent to
+      chip.imfile.run}
+  \item[POST A010] (Script) Generate the Condor job file containing the
+    appropriate jobs for reverting \hfill{}\mbox{Equivalent to
+      chip.revert}
+  \item[A020] (Condor) Queue that Condor job file (various, possibly
+    nothing to do)
+  \item[POST A020] (Script) Generate the Condor job file containing the
+    appropriate jobs for reverting \hfill{}\mbox{Equivalent to
+      chip.revert}
+  \item[A030] (Condor) Queue that Condor job file (various, possibly
+    nothing to do)
+  \item[POST A030] (Script) Generate the Condor job file containing the
+    appropriate jobs for reverting \hfill{}\mbox{Equivalent to
+      chip.revert}
+  \item[A040] (Condor) Queue that Condor job file (various, possibly
+    nothing to do)
+  \item[POST A040] (Script) If there is failing otas, send e-mail to
+    operator and stop processing. Otherwise generate stage 050 jobs file.
+  \item[A050] (Script) Advance the exposure with
+    \verb+chiptool -advanceexp+ \hfill{}\mbox{Equivalent to
+      chip.advanceexp}
+  \end{description}
+\item[B. Camera stage] Detail prerequisites \mbox{ }
+  \begin{description}
+  \item[PRE B010] (Script) Generate the Condor job file containing the
+    \verb+camera_exp.pl+ command(s?) \hfill{}\mbox{Equivalent to
+      camera.exp.load}
+  \item[B010] (Condor) Queue that Condor job file
+    \hfill{}\mbox{Equivalent to camera.exp.run}
+  \item[PRE B020] (Script) Generate the Condor job file possibly
+    containing the revert command (possibly nothing to revert)
+    \hfill{}\mbox{Equivalent to camera.revert}
+  \item[B020] (Condor) Queue that Condor job file
+  \item[PRE B030] (Script) Generate the Condor job file possibly
+    containing the revert command (possibly nothing to revert)
+    \hfill{}\mbox{Equivalent to camera.revert}
+  \item[B030] (Condor) Queue that Condor job file
+  \item[PRE B040] (Script) Generate the Condor job file possibly
+    containing the revert command (possibly nothing to revert)
+    \hfill{}\mbox{Equivalent to camera.revert}
+  \item[B040] (Condor) Queue that Condor job file
+  \item[POST B040] (Script) If the camera entry could be reverted, send
+    e-mail to operator and stop processing
+  \end{description}
+\item[C. Fake stage] Detail prerequisites \mbox{ }
+  \begin{description}
+  \item[PRE C010] (Script) Generate the Condor job file containing the
+    \verb+fake_imfile.pl+ command(s?) \hfill{}\mbox{Equivalent to
+      fake.imfile.load}
+  \item[C010] (Condor) Queue that Condor job file
+    \hfill{}\mbox{Equivalent to fake.imfile.run}
+  \item[C020] (Script (or Script+Condor)) Run the
+    \verb+faketool -advanceexp+ command(s?) (or: PRE C020: Generate
+    the Condor job file containing the \verb+faketool -advanceexp+
+    command(s?); C020: Queue that Condor job file)
+    \hfill{}\mbox{Equivalent to fake.advanceexp}
+  \end{description}
+\item[D. Warp stage] Detail prerequisites \hrulefill{}\textbf{BARF}\mbox{ }
+  \begin{description}
+  \item[TODO] \verb+warptool -tooverlap+ \hfill{}\mbox{Equivalent to warp.exp.load}
+  \item[TODO] \verb+warp_overlap.pl+ \hfill{}\mbox{Equivalent to warp.exp.run}
+  \item[TODO] \hfill{}\mbox{Equivalent to warp.revert.overlap}
+  \item[TODO] \verb+warptool -towarp+ \hfill{}\mbox{Equivalent to warp.skycell.load}
+  \item[TODO] \verb+warp_skycell.pl+ \hfill{}\mbox{Equivalent to warp.skycell.run}
+  \item[TODO] \verb++ \hfill{}\mbox{Equivalent to warp.revert.warped}
+  \item[TODO] \verb+ warptool -advancerun+ \hfill{}\mbox{Equivalent to warp.advancerun}
+  \item[Check if needed?] \verb++ \hfill{}\mbox{Equivalent to
+      warp.summary.load and/or warp.summary.run}
+  \end{description}
+\item[E. Stack stage] Detail prerequisites? \mbox{ }
+  \begin{description}
+  \item[TODO] \verb+stacktool -tosum+ \hfill{}\mbox{Equivalent to stack.skycell.load}
+  \item[TODO] \verb+stack_skycell.pl + \hfill{}\mbox{Equivalent to stack.skycell.run}
+  \item[TODO] \verb++ \hfill{}\mbox{Equivalent to stack.revert}
+  \item[Check if needed?] \verb++ \hfill{}\mbox{Equivalent to
+      stack.summary.load and/or stack.summary.run}
+  \end{description}
+\end{description}
+
+\end{document}
Index: /trunk/ippdor/doc/Full/Makefile
===================================================================
--- /trunk/ippdor/doc/Full/Makefile	(revision 32874)
+++ /trunk/ippdor/doc/Full/Makefile	(revision 32874)
@@ -0,0 +1,10 @@
+MAIN	= ippdor
+
+all:
+	pdflatex $(MAIN).tex
+	makeindex $(MAIN)
+	pdflatex $(MAIN).tex
+
+clean:
+	/bin/rm -f $(MAIN).pdf
+	CleanTex.sh
Index: /trunk/ippdor/doc/Full/administration.tex
===================================================================
--- /trunk/ippdor/doc/Full/administration.tex	(revision 32874)
+++ /trunk/ippdor/doc/Full/administration.tex	(revision 32874)
@@ -0,0 +1,34 @@
+
+\chapter{Administration}
+\label{chap-adm}
+
+\section{The \texttt{ippdor} user}
+\label{sec-adm-ippdor}
+
+\texttt{ippdor} is the dedicated user for any processing happening in
+Condor. This user uses the \texttt{bash} shell. The ippdor has no
+particular privilege on the system.
+
+The \texttt{bashrc} file contains some definitions and expansions:
+\begin{itemize}
+\item \texttt{PATH} adds \texttt{\$HOME/local/bin};
+\item \texttt{LD\_LIBRARY\_PATH} adds \texttt{\$HOME/local/lib};
+\item \texttt{PYTHONPATH} adds \texttt{\$HOME/local/share/python} and
+  \texttt{\$HOME/local/lib/python2.6/site-packages};
+\item The \texttt{PSCONFIG} value is fixed to \texttt{current}. See
+  section~\ref{sec-link-ippTODO} for details. The
+  \texttt{psconfig.bash} utility is called using that value.
+\end{itemize}
+
+\texttt{cmake} was locally installed to allow Condor build and
+install. It is now the only external software. It was built using the
+\texttt{--prefix=\$HOME/local} option and can be found in the
+\texttt{\$HOME/softwares} directory.
+
+\section{Deploying Condor on a new host}
+\label{sec-adm-newnode}
+
+\section{Updating Condor}
+\label{sec-adm-update}
+
+To be written when it's happening...
Index: /trunk/ippdor/doc/Full/annex.tex
===================================================================
--- /trunk/ippdor/doc/Full/annex.tex	(revision 32874)
+++ /trunk/ippdor/doc/Full/annex.tex	(revision 32874)
@@ -0,0 +1,103 @@
+\chapter{Annex}
+
+\section{Held Jobs}
+\label{annex-heldjobs}
+
+\null\hfill{\parbox{5in}{\scriptsize This example is related to the
+    DAG shown in section~\ref{sec-oper-tasks}. The A.condor and
+    B.condor jobs are defined as "echo A" and "echo B" commands.}}
+
+If \texttt{condor\_submit\_dag} is executed from a local directory
+(e.g. \textbf{/export/ippc01.0/ippdor/simple\_dag}), the jobs will be
+indefinitely held by Condor. 
+
+\begin{verbatim}
+ippdor@ippc01 /export/ippc01.0/ippdor/simple_dag $ condor_submit_dag AB.dag
+-----------------------------------------------------------------------
+File for submitting this DAG to Condor           : AB.dag.condor.sub
+Log of DAGMan debugging messages                 : AB.dag.dagman.out
+Log of Condor library output                     : AB.dag.lib.out
+Log of Condor library error messages             : AB.dag.lib.err
+Log of the life of condor_dagman itself          : AB.dag.dagman.log
+
+Submitting job(s).
+1 job(s) submitted to cluster 71204.
+-----------------------------------------------------------------------
+\end{verbatim}
+
+This can be seen with the
+\texttt{condor\_q} command:
+
+\begin{verbatim}
+$ condor_q
+
+-- Submitter: ippc01.ifa.hawaii.edu : <10.10.20.80:46944> : ippc01.ifa.hawaii.edu
+ ID      OWNER            SUBMITTED     RUN_TIME ST PRI SIZE CMD               
+71204.0   ippdor         11/25 11:41   0+00:01:27 R  0   12.2 condor_dagman     
+71205.0   ippdor         11/25 11:42   0+00:00:01 H  0   0.0  echo A            
+
+2 jobs; 0 idle, 1 running, 1 held
+\end{verbatim}
+
+``In-depth'' analysis can be performed with \texttt{condor\_q -analyze} command:
+
+\begin{verbatim}
+$ condor_q -analyze 71205.0
+
+-- Submitter: ippc01.ifa.hawaii.edu : <10.10.20.80:46944> : ippc01.ifa.hawaii.edu
+---
+71205.000:  Request is held.
+
+Hold reason: Error from slot7@ipp029.ifa.hawaii.edu: Failed to execute 
+'/bin/echo' with arguments A: Cannot access specified iwd 
+"/export/ippc01.0/ippdor/simple_dag" (errno=2: 'No such file or directory')
+\end{verbatim}
+
+The hold reason makes things obvious as soon as you know that
+\texttt{iwd} stands for "Initial Working Directory". There is no way
+that the Execute node \texttt{ipp029} can access the contents of what is
+on the \texttt{ippc01} local file system.
+
+To fix the problem, stop processing with:
+\begin{verbatim}
+$ condor_rm 71204.0
+\end{verbatim}
+
+Note the creation of the \texttt{rescue} file in
+\texttt{/export/ippc01.0/ippdor/simple\_dag}
+
+Now submit the DAG from any NFS directory:
+\begin{verbatim}
+ippdor@ippc01 ~ $ condor_submit_dag /export/ippc01.0/ippdor/simple_dag/AB.dag 
+
+Running rescue DAG 1
+-----------------------------------------------------------------------
+File for submitting this DAG to Condor           : /export/ippc01.0/ippdor/simple_dag/AB.dag.condor.sub
+Log of DAGMan debugging messages                 : /export/ippc01.0/ippdor/simple_dag/AB.dag.dagman.out
+Log of Condor library output                     : /export/ippc01.0/ippdor/simple_dag/AB.dag.lib.out
+Log of Condor library error messages             : /export/ippc01.0/ippdor/simple_dag/AB.dag.lib.err
+Log of the life of condor_dagman itself          : /export/ippc01.0/ippdor/simple_dag/AB.dag.dagman.log
+
+Submitting job(s).
+1 job(s) submitted to cluster 71208.
+-----------------------------------------------------------------------
+\end{verbatim}
+
+Note that the "rescue DAG 1" is used.
+
+... and the DAG should terminate correctly from the \texttt{condor\_q}
+command and the contents of the \texttt{AB.dag.dagman.out} file:
+\begin{verbatim}
+[...]
+11/25/11 12:13:58  Done     Pre   Queued    Post   Ready   Un-Ready   Failed
+11/25/11 12:13:58   ===     ===      ===     ===     ===        ===      ===
+11/25/11 12:13:58     2       0        0       0       0          0        0
+11/25/11 12:13:58 0 job proc(s) currently held
+11/25/11 12:13:58 All jobs Completed!
+11/25/11 12:13:58 Note: 0 total job deferrals because of -MaxJobs limit (0)
+11/25/11 12:13:58 Note: 0 total job deferrals because of -MaxIdle limit (0)
+11/25/11 12:13:58 Note: 0 total job deferrals because of node category throttles
+11/25/11 12:13:58 Note: 0 total PRE script deferrals because of -MaxPre limit (0)
+11/25/11 12:13:58 Note: 0 total POST script deferrals because of -MaxPost limit (0)
+11/25/11 12:13:58 **** condor_scheduniv_exec.71208.0 (condor_DAGMAN) pid 18455 EXITING WITH STATUS 0
+\end{verbatim}
Index: /trunk/ippdor/doc/Full/design.tex
===================================================================
--- /trunk/ippdor/doc/Full/design.tex	(revision 32874)
+++ /trunk/ippdor/doc/Full/design.tex	(revision 32874)
@@ -0,0 +1,4 @@
+\chapter{Software Design}
+\label{chap-software}
+
+TODO
Index: /trunk/ippdor/doc/Full/heldjobs.tex
===================================================================
--- /trunk/ippdor/doc/Full/heldjobs.tex	(revision 32874)
+++ /trunk/ippdor/doc/Full/heldjobs.tex	(revision 32874)
@@ -0,0 +1,66 @@
+\chapter{Annex}
+
+\section{Held Jobs}
+\label{annex-heldjobs}
+
+\nul\hfill{\scriptsize This example is related to the DAG shown in
+  section~\ref{sec-oper-tasks}. The A.condor and B.condor jobs are
+  defined as "echo A" and "echo B" commands.}
+
+If \texttt{condor\_submit\_dag} is executed from a local directory
+(e.g. \textbf{/export/ippc01.0/ippdor/simple_dag}), the jobs will be
+indefinitely held by Condor. 
+
+\begin{verbatim}
+ippdor@ippc01 /export/ippc01.0/ippdor/simple_dag $ condor_submit_dag AB.dag
+-----------------------------------------------------------------------
+File for submitting this DAG to Condor           : AB.dag.condor.sub
+Log of DAGMan debugging messages                 : AB.dag.dagman.out
+Log of Condor library output                     : AB.dag.lib.out
+Log of Condor library error messages             : AB.dag.lib.err
+Log of the life of condor_dagman itself          : AB.dag.dagman.log
+
+Submitting job(s).
+1 job(s) submitted to cluster 71204.
+-----------------------------------------------------------------------
+\end{verbatim}
+
+This can be seen with the
+\texttt{condor\_q} command:
+
+\begin{verbatim}
+$ condor_q
+
+-- Submitter: ippc01.ifa.hawaii.edu : <10.10.20.80:46944> : ippc01.ifa.hawaii.edu
+ ID      OWNER            SUBMITTED     RUN_TIME ST PRI SIZE CMD               
+71204.0   ippdor         11/25 11:41   0+00:01:27 R  0   12.2 condor_dagman     
+71205.0   ippdor         11/25 11:42   0+00:00:01 H  0   0.0  echo A            
+
+2 jobs; 0 idle, 1 running, 1 held
+\end{verbatim}
+
+``In-depth'' analysis can be performed with \texttt{condor\_q -analyze} command:
+
+\begin{verbatim}
+$ condor_q -analyze 71205.0
+
+-- Submitter: ippc01.ifa.hawaii.edu : <10.10.20.80:46944> : ippc01.ifa.hawaii.edu
+---
+71205.000:  Request is held.
+
+Hold reason: Error from slot7@ipp029.ifa.hawaii.edu: Failed to execute 
+'/bin/echo' with arguments A: Cannot access specified iwd 
+"/export/ippc01.0/ippdor/simple_dag" (errno=2: 'No such file or directory')
+\end{verbatim}
+
+The hold reason makes things obvious (as soon as you know that
+\texttt{iwd} stands for "Initial Working Directory")... There is no
+way that the Execute node \texttt{ipp029} can get the contents of what
+is on \texttt{ippc01} local file system.
+
+To fix the problem, stop processing with:
+\begin{verbatim}
+$ condor_rm 71204.0
+\end{verbatim}
+
+Note the creation of the \texttt{rescue} file in \texttt{/export/ippc01.0/ippdor/simple\_dag}
Index: /trunk/ippdor/doc/Full/ippdor.tex
===================================================================
--- /trunk/ippdor/doc/Full/ippdor.tex	(revision 32874)
+++ /trunk/ippdor/doc/Full/ippdor.tex	(revision 32874)
@@ -0,0 +1,100 @@
+\documentclass[10pt]{report}
+
+\usepackage{vmargin}
+\setpapersize{USletter}
+\setmarginsrb{0.5in}{0.5in}{1.25in}{0.5in}{0pt}{0mm}{0pt}{0mm}
+\usepackage{longtable}
+\usepackage{hyperref}
+\usepackage{makeidx}
+\usepackage{enumitem}
+
+\title{Condor in the IPP}
+\author{S.~Chastel\\Version 0.1}
+\date{2011-11-23}
+
+\makeindex
+
+\newcommand{\CondorSubmit}{\texttt{condor\_submit}}
+\newcommand{\CondorSubmitDag}{\texttt{condor\_submit\_dag}}
+
+\newcommand{\IppPython}[1]{\texttt{\~{ }ippdor/local/share/python/ipp/{#1}}}
+
+\newcommand{\definition}[3]{%
+  %#1: What is displayed in the text
+  %#2: What is displayed in the margin
+  %#3: Entry in the index
+  \emph{#1}\marginpar{\scriptsize #2}%
+  \index{#3}%
+}
+
+\begin{document}
+\maketitle
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\chapter*{Introduction}
+
+This is the documentation detailing how the Condor HTC system is used
+to perform processing for the IPP. Note that it is (currently) not
+supposed to replace the \texttt{pantasks} scheduling system (mainly
+because we choose not to use Condor as a scheduler).
+
+This documentation consists of five parts:
+\begin{enumerate}
+\item This introduction;
+\item Operational Users Manual (page~\pageref{chap-operation}): its
+  targets are the operators of the IPP, that is, persons who want to
+  use the Condor framework to process data for the IPP;
+\item Administration Manual (page~\pageref{chap-adm}): for system
+  administrators. Mainly how to install and upgrade the Condor
+  framework;
+\item Link with the IPP (page~\pageref{chap-ipp}): TODO: Should be
+  part of Software Organization;
+\item Software Organization (page~\pageref{chap-software}): Design,
+  Classes. How to add new features.
+\end{enumerate}
+
+\emph{Note:} Be aware that this documentation is not a substitute to
+the Condor documentation\definition{}{Condor
+  documentation}{Condor!Documentation} which is available from the
+Condor Project Homepage\footnote{when this document was released, it
+  was:
+  \href{http://research.cs.wisc.edu/condor/}{http://research.cs.wisc.edu/condor/}}.
+
+\subsection*{Document History}
+\begin{tabular}{|c|c|p{5in}|}
+  \hline
+  \textbf{Version} & \textbf{Date} & \textbf{Comment} \\
+  \hline
+  0.1 & 2011-11-23 & First draft \\
+  \hline
+\end{tabular}
+
+\subsection*{Acronyms}
+\begin{longtable}{lp{5in}}
+  IPP & Image Processing Pipeline \\
+  IWD & Initial Working Directory \\
+  RTCM & Read the Condor Manual \\
+\end{longtable}
+
+\setcounter{tocdepth}{2}
+\tableofcontents
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\input{operations.tex}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\input{administration.tex}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\input{link_ipp.tex}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\input{design.tex}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\input{annex.tex}
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+\printindex
+
+\end{document}
Index: /trunk/ippdor/doc/Full/link_ipp.tex
===================================================================
--- /trunk/ippdor/doc/Full/link_ipp.tex	(revision 32874)
+++ /trunk/ippdor/doc/Full/link_ipp.tex	(revision 32874)
@@ -0,0 +1,4 @@
+\chapter{Link with the IPP}
+\label{chap-ipp}
+
+TODO
Index: /trunk/ippdor/doc/Full/operations.tex
===================================================================
--- /trunk/ippdor/doc/Full/operations.tex	(revision 32874)
+++ /trunk/ippdor/doc/Full/operations.tex	(revision 32874)
@@ -0,0 +1,622 @@
+\newcommand{\QueueExposuresWarping}{%
+  \texttt{queue\_exposures\_for\_warping.py}}
+\newcommand{\QueueExposuresStacking}{%
+  \texttt{queue\_exposures\_for\_stacking.py}}
+
+\chapter{Operators Manual}
+\label{chap-operation}
+
+\section{Condor Framework Availability on the IPP Production Cluster}
+
+The Condor framework is not available on all hosts of the IPP
+production cluster. The purpose of this section is to give some
+details about the different nodes which are used and their roles in
+Condor. The set of all machines on which Condor is installed is called
+the \definition{Condor pool}{Condor pool}{Condor!Pool}.
+
+The Condor \definition{central manager}{Central
+  manager}{Condor!Central manager} is \texttt{ippdb03}. From the
+Condor documentation (section~3.1), the role of the central manager is
+to "[collect] information, and [negotiate] between resources and
+resource requests". It "plays a very important part in the Condor pool
+and should be reliable" since "[if it] crashes, no further matchmaking
+can be performed within the Condor system". Moreover "[all] queries go
+to the central manager".
+
+The other nodes of the IPP production cluster where Condor is
+installed are all both Execute and Submit Condor nodes. The complete
+list of those nodes is available in the file
+\texttt{\~{ }ippdor/local/share/CondorNodes}. Note that a node on which
+Condor is installed is not necessary available for processing in
+Condor since it can be deactivated (see section~\ref{sec-oper-hosts}
+for details).
+
+An \definition{Execute}{Execute node}{Condor!Execute node} node is a
+node where processing can be performed. From the Condor manual
+section~3.1, "the only resource that might matter is disk
+space"... Until now, it has never been a matter.
+
+A \definition{Submit}{Submit node}{Condor!Submit node} node is a node
+where Condor jobs can be submitted (see
+section~\ref{sec-ippdor-run}). It is suggested (again this is from the
+Condor manual, section~3.1), that since "every job that you submit
+that is currently running on a remote machine generates another
+process on your submit machine [, ...]  if you have lots of jobs
+running, you will need a fair amount of swap space and/or real
+memory". Until now, jobs were generally submitted from one of the
+\texttt{ippc\emph{xy}} nodes.
+
+Note: For Condor gurus, there is no Checkpoint Server since all
+processing is performed in the \emph{vanilla} Condor universe.
+
+\section{Condor Dedicated User: \texttt{ippdor}}
+\label{sec-oper-login}
+
+To use the Condor framework you need to log into the IPP production
+cluster as the user \texttt{ippdor}. Note that since Condor is not
+installed on \texttt{ippc18}, you will have to login again into
+another node for the condor commands to be usable. \texttt{ipp004} or
+\texttt{ippdb03} are other "open" nodes where you can connect.
+
+Note: The \texttt{ippdor} account uses \texttt{bash}.
+
+Theoretically it should be possible to use the Condor framework with
+your own login but 1) It has not been tested; 2) No support will be
+provided for that; 3) So... RTCM.
+
+As detailed in section~\ref{sec-adm-ippdor}, the \texttt{ippdor}
+account uses files that may be localized on the Condor node. If you
+get a \texttt{command not found} error, make sure you are on one of
+the node where Condor is installed\footnote{that is, the command
+  \texttt{"grep \`{ }hostname\`{ } \~{
+    }ippdor/local/share/CondorNodes"} should display the host name. If
+  you don't see anything, read the first paragraph of this section.}.
+
+\section{Managing Condor Execution on the IPP Production Cluster
+  Nodes}
+\label{sec-oper-hosts}
+
+Note 1: To install Condor on a new node, see
+section~\ref{sec-adm-newnode}.
+
+Note 2: Except for a few home-grown commands, information was
+extracted from the Condor Manual section~3.10.
+
+\subsection{Node Availability}
+\label{sec-oper-hosts-availability}
+\definition{}{Node availability}{Node availability}%
+\definition{}{}{Condor!Node availability}%
+To check if a node can be used by Condor, check the result of the
+\texttt{ipp\_condor\_status <hostname>} command. You should get some
+information about the host from the Condor point of view. For
+instance, \texttt{ipp060} potentially has 12 potential processors for
+Condor execution\footnote{To get more information about what the
+  columns mean, RTCM}:
+\begin{verbatim}
+ippdor@ippc20 ~ $ ipp_condor_status ipp060
+Name               OpSys      Arch   State     Activity LoadAv Mem   ActvtyTime
+
+slot10@ipp060.ifa. LINUX      X86_64 Unclaimed Idle     0.000  4032  0+05:54:29
+slot11@ipp060.ifa. LINUX      X86_64 Owner     Idle     0.400  4032  0+00:00:06
+slot12@ipp060.ifa. LINUX      X86_64 Owner     Idle     1.000  4032  0+00:00:07
+slot1@ipp060.ifa.h LINUX      X86_64 Owner     Idle     0.840  4032  0+00:00:04
+slot2@ipp060.ifa.h LINUX      X86_64 Unclaimed Idle     0.000  4032  0+14:34:09
+slot3@ipp060.ifa.h LINUX      X86_64 Unclaimed Idle     0.000  4032  0+06:56:32
+slot4@ipp060.ifa.h LINUX      X86_64 Unclaimed Idle     0.000  4032  0+14:25:44
+slot5@ipp060.ifa.h LINUX      X86_64 Unclaimed Idle     0.000  4032  0+14:33:57
+slot6@ipp060.ifa.h LINUX      X86_64 Claimed   Idle     0.000  4032  3+11:22:07
+slot7@ipp060.ifa.h LINUX      X86_64 Owner     Idle     1.000  4032  0+00:00:29
+slot8@ipp060.ifa.h LINUX      X86_64 Owner     Idle     0.790  4032  0+00:00:03
+slot9@ipp060.ifa.h LINUX      X86_64 Unclaimed Idle     0.000  4032  0+06:55:39
+                     Total Owner Claimed Unclaimed Matched Preempting Backfill
+
+        X86_64/LINUX    12     5       1         6       0          0        0
+
+               Total    12     5       1         6       0          0        0
+\end{verbatim}
+
+If you don't get anything, the node has been deactivated for Condor
+processing). For instance (while the author writes these lines),
+\texttt{ipp065} will not execute any Condor jobs:
+\begin{verbatim}
+ippdor@ippc20 ~ $ ipp_condor_status ipp065
+ippdor@ippc20 ~ $ 
+\end{verbatim}
+
+\emph{Note:} \texttt{ipp\_condor\_status} is an alias of
+\texttt{condor\_status} defined in \texttt{\~{ }ippdor/.bashrc}.
+
+\subsection{Make a Node Unavailable}
+\label{sec-oper-hosts-remove}
+
+To remove a node from the list of execution nodes, execute the command
+\texttt{ipp\_condor\_off <hostname>} from any node where Condor is
+installed. No job will be started/executed and submitted from that
+host. \texttt{ipp\_condor\_off} is a function defined in \texttt{\~{
+  }ippdor/.bashrc}.
+
+To stop Condor execution and submission from \definition{all}{Stopping
+  Condor on stare nodes}{Stopping Condor on stare nodes}stare nodes,
+the \texttt{stare\_nodes\_off} and \texttt{stare\_nodes\_on} scripts
+are provided. As it can be expected, those scripts just call
+\texttt{ipp\_condor\_off} or \texttt{ipp\_condor\_on} for each of the
+stares nodes.
+
+\subsection{Make a Node Available}
+\label{sec-oper-hosts-available}
+
+To make a node available for Condor after it has been stopped, execute
+the command \texttt{ipp\_condor\_on <hostname>} from any node where
+Condor is installed.
+
+\emph{Note:} If the following message is displayed,
+\begin{verbatim}
+bash: condor_master: command not found
+\end{verbatim}
+it simply means that Condor is not installed on the node where you try
+to start it.
+
+\section{Condor Jobs and DAG}
+
+\subsection{Running Jobs in Condor}
+\label{sec-oper-jobs}
+We will call \definition{job}{job}{job} an elementary unit of
+processing. It consists of the execution of one instance of an
+executable (we will assume that an executable is a program that can be
+called directly from the command line). For instance, "/bin/echo One"
+and "/bin/echo Two" are two jobs, each job being an instance of the
+same executable "/bin/echo".
+
+In our case, defining a correct Condor job is as simple (not to say
+simplistic) as defining:
+\begin{itemize}
+\item The name of the executable;
+\item The Condor universe of execution (in the IPP case, to avoid
+  recompilation of programs we use the \emph{vanilla} universe);
+\item The file where the standard output of the executable will go (on
+  an NFS directory since we don't know where the executable will be
+  run);
+\item The file where the error output of the executable will go (on
+  an NFS directory since we don't know where the executable will be
+  run);
+\item The file where Condor will write the log of the job (on a local
+  directory);
+\item The requirements about the node where execution will take place;
+\item Some argument to tell that we want to keep the environment
+  variables;
+\item The arguments for each execution of the executable.
+\end{itemize}
+
+With the example given on \texttt{/bin/echo}, the contents of the
+condor job file (let's call this file \texttt{echo\_test.condor}) are:
+\begin{verbatim}
+Executable   = /bin/echo
+Universe     = vanilla
+Output       = /home/panstarrs/ippdor/tmp/tests/out.echo_test
+Error        = /home/panstarrs/ippdor/tmp/tests/err.echo_test
+Log          = /tmp/echo_test.log
+Requirements = LoadAvg < 0.3
+
+getenv       = True
+
+Arguments    = One
+Queue
+
+Arguments    = Two
+Queue
+\end{verbatim}
+
+To execute those jobs, they just need to be \definition{submitted}{Job
+  submission}{Job submission} from any Submit node to the Condor
+system with:
+
+\texttt{condor\_submit echo\_test.condor}
+
+The jobs are (usually) not executed immediately and there is no way of
+guessing the order on the jobs execution (if the order of jobs
+execution is a matter, look at paragraph~\ref{sec-oper-tasks}).
+
+Note: From what the author experienced, \texttt{condor\_submit} and
+the condor jobs definition file can be executed and placed on any kind
+of file system (local and/or NFS) which is different from
+\texttt{condor\_submit\_dag}.
+
+\subsection{Running tasks}
+\label{sec-oper-tasks}
+
+\subsubsection{DAGMan}
+
+We will call \definition{task}{task}{task} a set of jobs where jobs
+chaining depend on each other. Condor provides a mechanism of
+organizing jobs according to their dependencies:
+\definition{DAGMan}{DAGMan}{DAGMan} (which stands for Directed Acyclic
+Graph Manager). Exhaustive documentation about DAGMan can be found in
+the Condor documentation section 2.10. In this section I will just
+give details about what is used for the IPP.
+
+\subsubsection{DAG Description File}
+
+To make it simple, a DAG is a high-level description of how jobs are
+sequenced. If the job B has to be performed after the job A, the
+dependency is formalized this way:
+\begin{verbatim}
+JOB    A     /some/directory/A.condor
+JOB    B     /some/directory/B.condor
+PARENT A CHILD B
+\end{verbatim}
+
+Assuming the previous text is saved in the file named \texttt{AB.dag},
+the execution of the DAG can be \definition{submitted}{DAG
+  submission}{DAG submission} from any Submit node to the Condor
+system using:
+
+\texttt{condor\_submit\_dag AB.dag}
+
+The output of that command will look like:
+\begin{verbatim}
+-----------------------------------------------------------------------
+File for submitting this DAG to Condor           : AB.dag.condor.sub
+Log of DAGMan debugging messages                 : AB.dag.dagman.out
+Log of Condor library output                     : AB.dag.lib.out
+Log of Condor library error messages             : AB.dag.lib.err
+Log of the life of condor_dagman itself          : AB.dag.dagman.log
+
+Submitting job(s).
+1 job(s) submitted to cluster 71201.
+-----------------------------------------------------------------------
+\end{verbatim}
+
+All those files are created in the directory of the \texttt{AB.dag}
+file. The \texttt{AB.dag.dagman.out} file is the most interesting log
+file. 
+
+\subsubsection{Files and Directories Locations}
+
+The DAG file can be localized on any file system: local or
+NFS. However for performance concerns, I recommend to put it on the
+local file system of the submit node. Monitor to the log growth rate
+(the logs are not huge though... For instance, full processing of 2300
+MD06 exposures from Chip to Warp stages in the IPP created a log of 15
+MB for the \texttt{.out} file and of a total of about 50 kB for the 4
+other files).
+
+The \texttt{/some/directory} directory (where the condor jobs are
+defined) can be placed on any file system. Again, I suggest to put
+them on the local file system of the Submit node (mostly for
+performance concerns).
+
+\begin{center}
+  \fbox{\parbox{6in}{\textbf{Important note:} The
+      \texttt{condor\_submit\_dag} command \textbf{must} be executed
+      \textbf{from a NFS file system} or the jobs will be indefinitely
+      \texttt{Held} (shown with \texttt{H} status in
+      \texttt{condor\_q}. See section~\ref{annex-heldjobs} for more
+      details.}}
+\end{center}
+
+One restriction is that there cannot be cycles, i.e. circular
+dependencies. For instance, job Egg depends on job Chicken, which
+depend on job Egg. If you submit a DAG with a cycle, looking at the
+\texttt{dagman.out} file, you will see a message like:
+\begin{verbatim}
+[...]
+11/25/11 12:22:02 ERROR: DAG finished but not all nodes are complete -- 
+                  checking for a cycle...
+11/25/11 12:22:02 ... ERROR: a cycle exists in the dag, plese check input
+[...]
+11/25/11 12:22:03 **** condor_scheduniv_exec.71211.0 (condor_DAGMAN) 
+                  pid 20996 EXITING WITH STATUS 1
+\end{verbatim}
+
+\subsubsection{Advanced DAGMan Features}
+\label{sec-oper-dagman}
+
+DAGMan offers other convenient functions for a DAG description. As for
+the other Condor features the reader is invited to check the Condor
+documentation for in-depth details about them.
+
+\paragraph{SubDAG:} A graph node of a DAG can be itself a DAG making
+the development and the analysis of a processing chain easier. A DAG
+in a DAG is called a \definition{SubDAG}{SubDAG}{SubDAG} and can be
+added to a DAG using this syntax:
+\begin{verbatim}
+SUBDAG EXTERNAL <job name> <dag file name>
+\end{verbatim}
+
+\paragraph{Script:} Scripts canbe executed before or after a node
+execution. They can be added using the following syntax:
+\begin{verbatim}
+SCRIPT PRE <job name> <Executable name> [<arguments>]
+SCRIPT POST <job name> <Executable name> [<arguments>]
+\end{verbatim}
+
+From the Condor documentation, "the \texttt{<job name>} specifies the
+node to which the script is attached. The \texttt{<Executable name>}
+specifies the script to be executed, and it may be followed by any
+command line \texttt{<arguments>} to that script". In the IPP context
+we currently use only the \texttt{SCRIPT POST} feature.
+
+\subsection{Condor Interrupted}
+\label{sec-oper-condor-interrupted}
+
+If the execution of Condor DAG is interrupted, for instance, because
+of an operator \texttt{condor\_rm} or because of errors, Condor marks
+the jobs or subdags that were successfully completed by adding the
+\texttt{DONE} keyword to the DAG file. The resulting DAG file is saved
+in a \definition{rescue DAG file}{Rescue DAG file}{Rescue DAG file},
+which is simply a copy of the original DAG file with a
+\texttt{rescue<running ID>} file extension (\texttt{<running ID>} is
+an auto-incrementing integer coded on three digits, starting at
+\texttt{001}). 
+
+To \definition{rescue processing}{Rescuing processing}{Rescuing
+  processing} you just need to execute \textbf{exactly} the same
+submission command: Condor will use the last rescue file that was
+produced.
+
+Note: If you know what you are doing you can create you rown rescue
+file or add/remove \texttt{DONE} keywords to/from a rescue file. Some
+utility programs can do that for you for some IPP stages
+(see~\ref{TODO}).
+
+\section{IPP Tasks}
+\label{sec-oper-ipp}
+
+The IPP defines complex tasks. The purpose of this section is to
+provide a user manual about the programs that need to be run to
+prepare the set of Condor files which are needed to perform some of
+the IPP tasks. Developers should look at chapter~\ref{chap-software}
+for in-depth details.
+
+Each subsection of this section details: 1) The software role; 2) Its
+required inputs; 3) Its outputs; 4) Its IPP products.
+
+Currently the IPP parts that are implemented are:
+\begin{itemize}%
+  \setlength{\itemsep}{0pt}\setlength{\topsep}{0pt}\setlength{\partopsep}{0pt}\setlength{\parsep}{0pt}%
+\item Chip to Warp stages (paragraph~\ref{sec-oper-ipp-chip2warp});
+\item Chip to Stack stages (paragraph~\ref{sec-oper-ipp-chip2stack});
+\end{itemize}
+
+\subsection{Chip to Warp Processing: \QueueExposuresWarping{}}
+\label{sec-oper-ipp-chip2warp}
+
+Since this section is supposed to belong to the operators manual, no
+detail about the software will be provided. The developer will read
+section~\ref{sec-TODO} for such details.
+
+\subsubsection{Role}
+The role of \QueueExposuresWarping{} is to prepare the set of Condor
+and IPP elements which are required to process a set of exposures from
+the Chip to the Warp stage.
+
+The \QueueExposuresWarping{} script has four roles:
+\begin{enumerate}[topsep=0pt, partopsep=0pt, itemsep=0pt, parsep=0pt, itemindent=10pt]
+\item Assert that the label has not already been used and that the
+  input exposures exist;
+\item Create entries in the \texttt{chipRun} table;
+\item Create the DAG files which are necessary for the processing of
+  all exposures;
+\item Display the \texttt{submit\_condor\_dag} command that needs to
+  be run by the operator.
+\end{enumerate}
+
+Note that the operator has to run the \texttt{submit\_condor\_dag}
+command.
+
+\subsubsection{Submit Node}
+
+A node needs to be chosen as Submit node. TODO: add details.
+
+\subsubsection{Inputs}
+The inputs are:
+\begin{itemize}
+\item A \textbf{label} The label used for processing. If the label has
+  already been used, the program is aborted. As for the processing in
+  IPP, don't use spaces or non-ASCII characters for the label (for
+  instance, \texttt{My.New\_Label});
+\item An \textbf{e-mail address} who gets the e-mails (for instance,
+  \texttt{someone@mail.com});
+\item A \textbf{tessellation ID} (e.g. \texttt{MD04.V3});
+\item A \textbf{list of exposures} to be processed (for instance,
+  \texttt{o4926g0064o}, \texttt{o4926g0068o}, \texttt{o4926g0069o},
+  \texttt{o4926g0071o}, and \texttt{o4940g0237o}).
+\end{itemize}
+
+The \QueueExposuresWarping{} script can be called in two ways:
+\begin{itemize}
+\item Either they are provided in a file which has the format of a
+  Properties-style file. With the previous parameters:
+\begin{verbatim}
+label     = My.New_Label
+tessId    = MD04.V3
+recipient = someone@mail.com
+exposure  = o4926g0064o
+exposure  = o4926g0068o
+exposure  = o4926g0069o
+exposure  = o4926g0071o
+# Comment line
+exposure  = o4940g0237o  # Line followed by a comment
+\end{verbatim}
+  Assume that the file is stored under the name 
+  \texttt{~{ }ippdor/examples/ChipsToWarps.ippdor} 
+  the script is called with (note the \texttt{-f} option flag):
+\begin{verbatim}
+queue_exposures_for_warping.py -f ~ippdor/examples/01/ChipsToWarps.ippdor
+\end{verbatim}
+\item Or the input parameters are provided as arguments, that is (the
+  arguments have to be in this order):
+\begin{verbatim}
+queue_exposures_for_warping.py My.New_Label MD04.V3 someone@mail.com \
+      o4926g0064o o4926g0068o o4926g0069o o4926g0071o o4940g0237o
+\end{verbatim}
+\end{itemize}
+
+\subsubsection{Outputs. Entries on GPC1 Database}
+The script writes its outputs to the directory named
+\texttt{<LOCAL\_TMP\_DIR>/<label>} where
+\definition{\emph{\texttt{<LOCAL\_TMP\_DIR>}}}{\texttt{LOCAL\_TMP\_DIR}}{\texttt{LOCAL\_TMP\_DIR}}{}
+is defined in \IppPython{Constants.py} and \texttt{<label>} is the
+label which has been provided to the script. For instance, with the
+current configuration if executed on \texttt{ippc10} in the
+\texttt{ippdor} production environment, and for the label
+\texttt{My.New\_Label}, the various files will be written to the
+directory \texttt{/export/ippc10.0/ippdor/processing/My.New\_Label}.
+
+The main file, also called DAG of DAGs, is \texttt{ch2wp.dag}. Its
+name is defined by
+\texttt{ChipWarpBuilder.Constants.DAG\_OF\_DAGS\_FMT}. This DAG lists
+all the steps which are necessary to build the IPP products from the
+list of exposures.
+
+The script also creates one directory for each exposure in
+\texttt{<LOCAL\_TMP\_DIR>}. 
+
+
+\subsubsection{IPP Products}
+
+Upon successfull completion, the IPP Products are those which are
+produced by successfull chip, cam, fake, and warp stages. Look at the
+IPP documentation to find at what they are.
+
+If the execution of a SubDAG fails, an e-mail is sent to the
+\texttt{recipient} address telling about the nature of the failure. To
+avoid too many failures each processing stage is reverted up to 3
+times. The failure e-mail is therefore sent after 3 failed revert
+attempts. It is expected from the operator to fix the problem and
+\textbf{manually} revert \textbf{all} failures before restarting the
+Condor processing.
+
+%%%%%%%%%%%%%%%%%%% BARF
+
+\subsection{Chip to Stack}
+\label{sec-oper-ipp-chip2stack}
+
+
+\paragraph{Required Inputs}
+
+The name of the utility is \QueueExposuresStacking. Running it requires
+parameters. Those parameters can be provided through command line
+arguments or in a file (which filename is provided through the command
+line interface). Those parameters are:
+\begin{itemize}
+\item \texttt{label} The label used for processing. If the label has already
+  been used, the program is aborted. As for the processing in IPP,
+  don't use spaces or non-ASCII characters for the label.
+\item \texttt{tesselation ID} like MD08.V2
+\item \texttt{E-mail address} who gets the e-mails
+\item \texttt{List of exposures}
+\end{itemize}
+
+The file format is a Properties-style file, for instance:
+\begin{verbatim}
+label     = My.New_Label
+tessId    = MD06.V3
+recipient = someone@mail.com
+exposure  = o4926g0064o
+exposure  = o4926g0068o
+exposure  = o4926g0069o
+exposure  = o4926g0071o
+# Comment line
+exposure  = o4940g0237o  # Followed by a comment
+\end{verbatim}
+
+The command-line equivalent is:
+\begin{verbatim}
+queue\_exposures\_for\_stacking.py \
+    My.New_Label MD06.V3 someone@mail.com \
+    o4926g0064o o4926g0068o o4926g0069o o4926g0071o o4940g0237o
+\end{verbatim}
+
+\paragraph{What \QueueExposuresStacking{} does}
+\begin{enumerate}
+\item It checks that each exposure exists;
+\item It creates the appropriate entries in the \texttt{chipRun} table;
+\item It generates the various Condor DAGs: one global DAG and one DAG
+  for each exposure. The files are located
+\item It displays the \CondorSubmitDag{} command that has to be run
+\end{enumerate}
+
+\section{Running a task}
+\label{ippdor-run}
+The \CondorSubmitDag{} command has to be run on the host where
+\QueueExposuresStacking{} was run. But the \CondorSubmitDag{} has to
+be run from a NFS directory (e.g. the \texttt{ippdor} home directory)
+otherwise Condor will hold jobs.
+
+The output of the \CondorSubmitDag{} command looks like\footnote{Long lines were wrapped}: 
+\begin{verbatim}
+Can't open directory "/export/ippc20.0/ippdor/condor_local/config" as \
+   PRIV_UNKNOWN, errno: 2 (No such file or directory)
+-----------------------------------------------------------------------
+File for submitting this DAG to Condor           : \
+  /export/ippc20.0/ippdor/processing/condor_MD06.V3_01/ch2st.dag.condor.sub
+Log of DAGMan debugging messages                 : \
+  /export/ippc20.0/ippdor/processing/condor_MD06.V3_01/ch2st.dag.dagman.out
+Log of Condor library output                     : \
+  /export/ippc20.0/ippdor/processing/condor_MD06.V3_01/ch2st.dag.lib.out
+Log of Condor library error messages             : \
+  /export/ippc20.0/ippdor/processing/condor_MD06.V3_01/ch2st.dag.lib.err
+Log of the life of condor_dagman itself          : \
+  /export/ippc20.0/ippdor/processing/condor_MD06.V3_01/ch2st.dag.dagman.log
+
+Submitting job(s).
+1 job(s) submitted to cluster 62384.
+-----------------------------------------------------------------------
+\end{verbatim}
+
+Don't bother about the warning on the first line. You should have a
+look (or even \texttt{tail~-f}) the \texttt{ch2st.dag.dagman.out} file
+since it is the place where Condor dagman is logging. It tells how the
+jobs progress, what is happening in terms of successes and failures,
+if jobs are held...
+
+Note the \texttt{1 job(s) submitted to cluster 62384} last line. It
+tells the Condor cluster id which is the mother of all other Condor
+jobs in the DAG. More precisely, if you want to stop the Condor
+processing (see~\ref{ippdor-rm}), you can either stop each Condor job
+individually (somewhat annoying when you have 2000 jobs), or stop the
+mother.
+
+\section{Supervising a Task Execution}
+
+As mentioned in the previous paragraph, the
+\texttt{ch2st.dag.dagman.out} file is the file to look at to get
+information from Condor.
+
+The \texttt{condor\_q} command can be used to monitor the
+progress. The \texttt{long\_condor\_q} command is a home-grown command
+that displays more information about the jobs which are running (it
+doesn't display anything about idle or held jobs).
+
+\texttt{condor\_q -analyze <job\_id>} can be useful to get more
+information about a job.
+
+\section{Ending a task}
+
+\subsubsection{Regular}
+\label{ippdor-end}
+
+When \texttt{condor\_q} does not show the Condor cluster ID.
+
+\subsection{Forced Termination}
+\label{ippdor-rm}
+
+The command to stop a Condor job is \texttt{condor\_rm}. 
+
+An argument is necessary. You will generally use the Condor cluster ID
+that you got from the \CondorSubmitDag{} command. For instance, to
+stop all processing from example shown in paragraph~\ref{ippdor-run}:
+\begin{verbatim}
+condor_rm 62384.0
+\end{verbatim}
+
+The \texttt{-all} option also exists. Use at your own risk... You're
+maybe not the only one to run a Condor instance.
+
+\section{FAQ}
+
+\begin{description}
+\item[All jobs are held] 
+\end{description}
+
Index: /trunk/ippdor/quality_metrics.sh
===================================================================
--- /trunk/ippdor/quality_metrics.sh	(revision 32874)
+++ /trunk/ippdor/quality_metrics.sh	(revision 32874)
@@ -0,0 +1,18 @@
+#!/bin/bash
+
+if [ "$PYTHONPATH" != "" ]; then
+    echo "Unset your PYTHONPATH before running this"
+    exit 1
+fi
+
+export PYTHONPATH=`pwd`/deployment
+
+/bin/rm -rf ./test
+mkdir -p ./test/ipp ./test/bin
+./deployment/deploy.py -t ./test/ipp -b ./test/bin 
+
+echo "\"\"\"Stupido\n\"\"\"" > ./test/__init__.py
+cp ./test/__init__.py ./test/bin
+
+export PYTHONPATH=`pwd`/test
+pylint -f html --rcfile=conf/pylintrc test > report.html
Index: /trunk/ippdor/src/__init__.py
===================================================================
--- /trunk/ippdor/src/__init__.py	(revision 32874)
+++ /trunk/ippdor/src/__init__.py	(revision 32874)
@@ -0,0 +1,2 @@
+"""Added to suppress a warning
+"""
Index: /trunk/ippdor/src/executables/encapsulate_camera.sh
===================================================================
--- /trunk/ippdor/src/executables/encapsulate_camera.sh	(revision 32874)
+++ /trunk/ippdor/src/executables/encapsulate_camera.sh	(revision 32874)
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+#
+# Modified to be adapted to camera processing
+#
+TO_BE_RUN="$*"
+`exec $TO_BE_RUN`
+exit 0
+
Index: /trunk/ippdor/src/executables/encapsulate_chip.sh
===================================================================
--- /trunk/ippdor/src/executables/encapsulate_chip.sh	(revision 32874)
+++ /trunk/ippdor/src/executables/encapsulate_chip.sh	(revision 32874)
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+#
+# Modified to be adapted to chip processing
+#
+TO_BE_RUN="$*"
+`exec $TO_BE_RUN`
+exit 0
+
Index: /trunk/ippdor/src/executables/encapsulate_fake.sh
===================================================================
--- /trunk/ippdor/src/executables/encapsulate_fake.sh	(revision 32874)
+++ /trunk/ippdor/src/executables/encapsulate_fake.sh	(revision 32874)
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+#
+# Modified to be adapted to fake processing
+#
+TO_BE_RUN="$*"
+`exec $TO_BE_RUN`
+exit 0
+
Index: /trunk/ippdor/src/executables/encapsulate_stack.sh
===================================================================
--- /trunk/ippdor/src/executables/encapsulate_stack.sh	(revision 32874)
+++ /trunk/ippdor/src/executables/encapsulate_stack.sh	(revision 32874)
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+#
+# Modified to be adapted to stack processing
+#
+TO_BE_RUN="$*"
+`exec $TO_BE_RUN`
+exit 0
+
Index: /trunk/ippdor/src/executables/encapsulate_warp.sh
===================================================================
--- /trunk/ippdor/src/executables/encapsulate_warp.sh	(revision 32874)
+++ /trunk/ippdor/src/executables/encapsulate_warp.sh	(revision 32874)
@@ -0,0 +1,9 @@
+#!/bin/bash
+
+#
+# Modified to be adapted to warp processing
+#
+TO_BE_RUN="$*"
+`exec $TO_BE_RUN`
+exit 0
+
Index: /trunk/ippdor/src/executables/generate_cam_jobs.py
===================================================================
--- /trunk/ippdor/src/executables/generate_cam_jobs.py	(revision 32874)
+++ /trunk/ippdor/src/executables/generate_cam_jobs.py	(revision 32874)
@@ -0,0 +1,44 @@
+#!/usr/bin/env python
+
+"""
+Generates the complete Camera stage DAG for one exposure
+"""
+from ipp.CamManager import CamManager as CamManager
+
+import sys
+import os
+
+#################################################################
+if __name__ == '__main__':
+    if len(sys.argv) != 4 and len(sys.argv) != 5:
+        print 'generate_cam_jobs.py <stage> <label> <exposure_name> [<email recipient>]'
+        print '  e.g.: generate_cam_jobs.py B010 MD08.jtrp o5806g0078o me@nospam.com'
+        print '  Acceptable value for <stage>: DAG, B010, B020, B030, B040, B050'
+        sys.exit(1)
+    STAGE = sys.argv[1]
+    LABEL = sys.argv[2]
+    EXPOSURE_NAME = sys.argv[3]
+    if len(sys.argv) == 5:
+        RECIPIENT = sys.argv[4]
+    else:
+        RECIPIENT = 'schastel@ifa.hawaii.edu'
+    if os.getenv('USER') == 'ippdor':
+        CAMQUERIER = CamManager(LABEL, EXPOSURE_NAME, recipient=RECIPIENT)
+    else:
+        CAMQUERIER = CamManager(LABEL, EXPOSURE_NAME, recipient=RECIPIENT,
+                                host='ipp0012.ifa.hawaii.edu')
+    if STAGE == 'B010':
+        CAMQUERIER.build_new_camera_jobs()
+    elif STAGE == 'B020' or STAGE == 'B030' or STAGE == 'B040':
+        CAMQUERIER.build_camera_revert_jobs(STAGE)
+    elif STAGE == 'B050':
+        CAMQUERIER.advance_camera()
+    elif STAGE == 'DAG':
+        DAG_NAME = CAMQUERIER.build_camera_dag()
+        CAMQUERIER.build_new_camera_jobs()
+        print 'DAG file generated now run:'
+        print 'condor_submit_dag %s' % DAG_NAME
+    else:
+        print 'Stage [%s] not supported' % STAGE
+        sys.exit(2)
+    sys.exit(0)
Index: /trunk/ippdor/src/executables/generate_chip_jobs.py
===================================================================
--- /trunk/ippdor/src/executables/generate_chip_jobs.py	(revision 32874)
+++ /trunk/ippdor/src/executables/generate_chip_jobs.py	(revision 32874)
@@ -0,0 +1,53 @@
+#!/usr/bin/env python
+
+"""
+Generates the full chip DAG for one exposure
+"""
+from ipp.ChipManager import ChipManager as ChipManager
+from ipp.exceptions.IppException import IppException
+
+import sys
+import os
+
+#################################################################
+if __name__ == '__main__':
+    if len(sys.argv) != 4 and len(sys.argv) != 5:
+        print len(sys.argv)
+        print 'generate_chip_jobs.py <stage> <label> <exposure_name> [<email recipient>]'
+        print '  e.g.: generate_chip_jobs.py A010 MD08.jtrp o5806g0078o me@nospam.com'
+        print '  Acceptable value for <stage>: DAG, A020, A030, A040, or A050'
+        print '  Default for e-mail recipient is schastel@ifa.hawaii.edu'
+        sys.exit(1)
+    STAGE = sys.argv[1]
+    LABEL = sys.argv[2]
+    EXPOSURE_NAME = sys.argv[3]
+    if len(sys.argv) == 5:
+        RECIPIENT = sys.argv[4]
+    else:
+        RECIPIENT = 'schastel@ifa.hawaii.edu'
+    if os.getenv('USER') == 'ippdor':
+        CHIP_MANAGER = ChipManager(LABEL, EXPOSURE_NAME, 
+                                   RECIPIENT)
+    else:
+        CHIP_MANAGER = ChipManager(LABEL, EXPOSURE_NAME, 
+                                   RECIPIENT, 
+                                   host='ipp0012.ifa.hawaii.edu')
+    try:
+        if STAGE == 'A010':
+            CHIP_MANAGER.build_new_chips_jobs()
+        elif STAGE == 'A020' or STAGE == 'A030' or STAGE == 'A040':
+            CHIP_MANAGER.build_chip_revert_jobs(STAGE)
+        elif STAGE == 'A050':
+            CHIP_MANAGER.advance_chips()
+        elif STAGE == 'DAG':
+            DAG_NAME = CHIP_MANAGER.build_chip_dag()
+            CHIP_MANAGER.build_new_chips_jobs()
+            print 
+            print 'Now run: condor_submit_dag %s\n' % DAG_NAME
+        else:
+            raise IppException('Stage [%s] not supported' % STAGE)
+    except IppException, e:
+        print 'IppException caught: ', e
+        print 'Aborting'
+        sys.exit(1)
+    sys.exit(0)
Index: /trunk/ippdor/src/executables/generate_exposure_dag.py
===================================================================
--- /trunk/ippdor/src/executables/generate_exposure_dag.py	(revision 32874)
+++ /trunk/ippdor/src/executables/generate_exposure_dag.py	(revision 32874)
@@ -0,0 +1,224 @@
+#!/usr/bin/env python
+
+"""
+Generate the complete DAG for ONE exposure from chip to warp
+"""
+from ipp.helpers import mkdir
+from ipp.ChipManager import ChipManager as ChipManager
+from ipp.CamManager import CamManager as CamManager
+from ipp.FakeManager import FakeManager as FakeManager
+from ipp.WarpManager import WarpManager as WarpManager
+from ipp.constants.Globals import Globals as GlobalConstants
+
+import sys
+import os
+
+class DagGenerator(ChipManager, CamManager, FakeManager, WarpManager):
+    """
+    Generation of all DAGs needed for the processing of an exposure
+    """
+    def __init__(self, 
+                 label, 
+                 exposure_name, 
+                 recipient,
+                 host='ippdb01.ifa.hawaii.edu', 
+                 user='ipp', 
+                 passwd='ipp'):
+        """
+        Create a connector to the gpc1 database and a local directory
+        dedicated to the exposure
+        """
+        ChipManager.__init__(self, label, exposure_name, recipient, host, user, passwd)
+        CamManager.__init__(self, label, exposure_name, recipient, host, user, passwd)
+        FakeManager.__init__(self, label, exposure_name, recipient, host, user, passwd)
+        WarpManager.__init__(self, label, exposure_name, recipient, host, user, passwd)
+        self.exposure_directory = '%s/%s/%s' % (GlobalConstants.LOCAL_TMP_DIR, label, exposure_name)
+        mkdir(self.exposure_directory)
+        return
+
+    def build_dag(self, need_configuration_file = True):
+        """Build the DAG to generating all stages from chip to warp
+        """
+        dag_name = '%s/%s.dag' % (self.exposure_directory, self.exposure_name)
+        print "Generating Warp file [%s] for exposure %s (label %s)" % (dag_name,
+                                                                        self.exposure_name,
+                                                                        self.label)
+        output = open(dag_name, 'w')
+        output.write('# Generated DAG\n')
+        output.write('\n')
+        if need_configuration_file:
+            output.write('CONFIG %s/%s/configuration' % (GlobalConstants.LOCAL_TMP_DIR, self.label))
+            output.write('\n')
+        output.write('# Chip stages\n')
+        output.write('JOB\tA010\t%s/A010.jobs\n' % self.chip_directory)
+        output.write('SCRIPT POST A010 %s A020 %s %s %s\n' % (ChipManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tA020\t%s/A020.jobs\n' % self.chip_directory)
+        output.write('SCRIPT POST A020 %s A030 %s %s %s\n' % (ChipManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tA030\t%s/A030.jobs\n' % self.chip_directory)
+        output.write('SCRIPT POST A030 %s A040 %s %s %s\n' % (ChipManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tA040\t%s/A040.jobs\n' % self.chip_directory)
+        output.write('SCRIPT POST A040 %s A050 %s %s %s\n' % (ChipManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tA050\t%s/A050.jobs\n' % self.chip_directory)
+        output.write('\n')
+        output.write('# Chip-Cam link\n')
+        output.write('SCRIPT POST A050 %s B010 %s %s %s\n' % (CamManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('\n')
+        output.write('# Cam stages\n')
+        output.write('JOB\tB010\t%s/B010.jobs\n' % self.cam_directory)
+        output.write('SCRIPT POST B010 %s B020 %s %s %s\n' % (CamManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tB020\t%s/B020.jobs\n' % self.cam_directory)
+        output.write('SCRIPT POST B020 %s B030 %s %s %s\n' % (CamManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tB030\t%s/B030.jobs\n' % self.cam_directory)
+        output.write('SCRIPT POST B030 %s B040 %s %s %s\n' % (CamManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tB040\t%s/B040.jobs\n' % self.cam_directory)
+        output.write('SCRIPT POST B040 %s B050 %s %s %s\n' % (CamManager.SCRIPT_NAME,
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tB050\t%s/B050.jobs\n' % self.cam_directory)
+        output.write('\n')
+        output.write('# Cam-Fake link\n')
+        output.write('SCRIPT POST B050 %s C010 %s %s %s\n' % (FakeManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('\n')
+        output.write('# Fake stages\n')
+        output.write('JOB\tC010\t%s/C010.jobs\n' % self.fake_directory)
+        output.write('SCRIPT POST C010 %s C020 %s %s %s\n' % (FakeManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tC020\t%s/C020.jobs\n' % self.fake_directory)
+        output.write('SCRIPT POST C020 %s C030 %s %s %s\n' % (FakeManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tC030\t%s/C030.jobs\n' % self.fake_directory)
+        output.write('SCRIPT POST C030 %s C040 %s %s %s\n' % (FakeManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tC040\t%s/C040.jobs\n' % self.fake_directory)
+        output.write('SCRIPT POST C040 %s C050 %s %s %s\n' % (FakeManager.SCRIPT_NAME,
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tC050\t%s/C050.jobs\n' % self.fake_directory)
+        output.write('\n')
+        output.write('# Fake-Warp link\n')
+        output.write('SCRIPT POST C050 %s D010 %s %s %s\n' % (WarpManager.SCRIPT_NAME,
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('\n')
+        output.write('# Warp stages\n')
+        output.write('JOB\tD010\t%s/D010.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D010 %s D020 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tD020\t%s/D020.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D020 %s D030 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tD030\t%s/D030.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D030 %s D040 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tD040\t%s/D040.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D040 %s D110 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('\n')
+        output.write('# Skycell\n')
+        output.write('JOB\tD110\t%s/D110.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D110 %s D120 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tD120\t%s/D120.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D120 %s D130 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tD130\t%s/D130.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D130 %s D140 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('JOB\tD140\t%s/D140.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D140 %s D210 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('\n')
+        output.write('# Summary\n')
+        output.write('JOB\tD210\t%s/D210.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D210 %s D310 %s %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                              self.label, 
+                                                              self.exposure_name, self.recipient))
+        output.write('\n')
+        output.write('# Advance\n')
+        output.write('JOB\tD310\t%s/D310.jobs\n' % self.warp_directory)
+        output.write('\n')
+
+        output.write('\n')
+        output.write('# Chip dependencies\n')
+        output.write('PARENT A010 CHILD A020\n')
+        output.write('PARENT A020 CHILD A030\n')
+        output.write('PARENT A030 CHILD A040\n')
+        output.write('PARENT A040 CHILD A050\n')
+        output.write('# Chip-Cam dependencies\n')
+        output.write('PARENT A050 CHILD B010\n')
+        output.write('# Cam dependencies\n')
+        output.write('PARENT B010 CHILD B020\n')
+        output.write('PARENT B020 CHILD B030\n')
+        output.write('PARENT B030 CHILD B040\n')
+        output.write('PARENT B040 CHILD B050\n')
+        output.write('# Cam-Fake dependencies\n')
+        output.write('PARENT B050 CHILD C010\n')
+        output.write('# Fake dependencies\n')
+        output.write('PARENT C010 CHILD C020\n')
+        output.write('PARENT C020 CHILD C030\n')
+        output.write('PARENT C030 CHILD C040\n')
+        output.write('PARENT C040 CHILD C050\n')
+        output.write('# Fake-Warp dependencies\n')
+        output.write('PARENT C050 CHILD D010\n')
+        output.write('# Warp dependencies\n')
+        output.write('PARENT D010 CHILD D020\n')
+        output.write('PARENT D020 CHILD D030\n')
+        output.write('PARENT D030 CHILD D040\n')
+        output.write('PARENT D040 CHILD D110\n')
+        output.write('PARENT D110 CHILD D120\n')
+        output.write('PARENT D120 CHILD D130\n')
+        output.write('PARENT D130 CHILD D140\n')
+        output.write('PARENT D140 CHILD D210\n')
+        output.write('PARENT D210 CHILD D310\n')
+        output.write('\n')
+        output.close()
+        return dag_name
+
+if __name__ == '__main__':
+    if len(sys.argv) != 4:
+        print 'generate_exposure_dag.py <label> <recipient> <exposure_name>'
+        print '  e.g.: generate_exposure_dag.py MD08.jtrp schastel@ifa.hawaii.edu o5806g0078o'
+        sys.exit(1)
+    LABEL = sys.argv[1]
+    RECIPIENT = sys.argv[2]
+    EXPOSURE_NAME = sys.argv[3]
+    if os.getenv('USER') == 'ippdor':
+        GENERATOR = DagGenerator(LABEL, EXPOSURE_NAME, RECIPIENT)
+    else:
+        GENERATOR = DagGenerator(LABEL, EXPOSURE_NAME, RECIPIENT, 
+                                 host='ipp0012.ifa.hawaii.edu')
+    DAG_NAME = GENERATOR.build_dag()
+    GENERATOR.build_new_chips_jobs()
+    print 'Now run'
+    print 'condor_submit_dag %s' % DAG_NAME
+    print
+    sys.exit(0)
Index: /trunk/ippdor/src/executables/generate_fake_jobs.py
===================================================================
--- /trunk/ippdor/src/executables/generate_fake_jobs.py	(revision 32874)
+++ /trunk/ippdor/src/executables/generate_fake_jobs.py	(revision 32874)
@@ -0,0 +1,46 @@
+#!/usr/bin/env python
+
+"""
+Generates the fake DAG and Condor jobs for one exposure
+"""
+from ipp.FakeManager import FakeManager as FakeManager
+
+import sys
+import os
+
+#################################################################
+if __name__ == '__main__':
+    if len(sys.argv) != 4 and len(sys.argv) != 5:
+        print 'generate_fake_jobs.py <stage> <label> <exposure_name>'
+        print '  e.g.: generate_fake_jobs.py C010 MD08.jtrp o5806g0078o'
+        print '  Acceptable value for <stage>: DAG, C010, C020, C030, C040, C050'
+        sys.exit(1)
+    STAGE = sys.argv[1]
+    LABEL = sys.argv[2]
+    EXPOSURE_NAME = sys.argv[3]
+    if len(sys.argv) == 5:
+        RECIPIENT = sys.argv[4]
+    else:
+        RECIPIENT = 'schastel@ifa.hawaii.edu'
+    if os.getenv('USER') == 'ippdor':
+        FAKE_MANAGER = FakeManager(LABEL, EXPOSURE_NAME, 
+                                  recipient=RECIPIENT)
+    else:
+        FAKE_MANAGER = FakeManager(LABEL, EXPOSURE_NAME, 
+                                  recipient=RECIPIENT, 
+                                  host='ipp0012.ifa.hawaii.edu')
+    if STAGE == 'C010':
+        FAKE_MANAGER.build_new_fake_jobs()
+    elif STAGE == 'C020' or STAGE == 'C030' or STAGE == 'C040':
+        FAKE_MANAGER.build_fake_revert_jobs(STAGE)
+    elif STAGE == 'C050':
+        FAKE_MANAGER.advance_fake()
+    elif STAGE == 'DAG':
+        DAG_NAME = FAKE_MANAGER.build_fake_dag()
+        FAKE_MANAGER.build_new_fake_jobs()
+        print 'DAG file generated now run:'
+        print 'condor_submit_dag %s' % DAG_NAME
+    else:
+        print 'Stage [%s] not supported' % STAGE
+        sys.exit(2)
+    sys.exit(0)
Index: /trunk/ippdor/src/executables/generate_stack_jobs.py
===================================================================
--- /trunk/ippdor/src/executables/generate_stack_jobs.py	(revision 32874)
+++ /trunk/ippdor/src/executables/generate_stack_jobs.py	(revision 32874)
@@ -0,0 +1,43 @@
+#!/usr/bin/env python
+
+"""
+Generates the complete stack stage DAG for one label
+"""
+from ipp.StackManager import StackManager as StackManager
+
+import sys
+import os
+
+#################################################################
+if __name__ == '__main__':
+    if len(sys.argv) != 4:
+        print 'generate_stack_jobs.py <stage> <label> <recipient>'
+        print '  e.g.: generate_stack_jobs.py E010 MD08.jtrp me@nospam.com'
+        print '  Acceptable value for <stage>: DAG, E010-E060'
+        sys.exit(1)
+    STAGE = sys.argv[1]
+    LABEL = sys.argv[2]
+    RECIPIENT = sys.argv[3]
+    if os.getenv('USER') == 'ippdor':
+        STACK_QUERIER = StackManager(LABEL, RECIPIENT)
+    else:
+        STACK_QUERIER = StackManager(LABEL, 
+                                    RECIPIENT,
+                                    host='ipp0012.ifa.hawaii.edu')
+    if STAGE == 'E010':
+        STACK_QUERIER.build_create_stack_entries_job()
+    elif STAGE == 'E020':
+        STACK_QUERIER.build_new_stack_jobs()
+    elif STAGE == 'E030' or STAGE == 'E040' or STAGE == 'E050':
+        STACK_QUERIER.build_revert_stack_jobs(STAGE)
+    elif STAGE == 'E060':
+        STACK_QUERIER.build_stack_summary_jobs()
+    elif STAGE == 'DAG':
+        DAG_NAME = STACK_QUERIER.build_dag()
+        STACK_QUERIER.build_create_stack_entries_job()
+        print 'DAG file generated now run:'
+        print 'condor_submit_dag %s' % DAG_NAME
+    else:
+        print 'Stage [%s] not supported' % STAGE
+        sys.exit(2)
+    sys.exit(0)
Index: /trunk/ippdor/src/executables/generate_warp_jobs.py
===================================================================
--- /trunk/ippdor/src/executables/generate_warp_jobs.py	(revision 32874)
+++ /trunk/ippdor/src/executables/generate_warp_jobs.py	(revision 32874)
@@ -0,0 +1,52 @@
+#!/usr/bin/env python
+
+"""
+Generates the complete warp stage DAG for one exposure
+"""
+from ipp.WarpManager import WarpManager as WarpManager
+
+import sys
+import os
+
+#################################################################
+if __name__ == '__main__':
+    if len(sys.argv) != 4 and len(sys.argv) != 5:
+        print 'generate_warp_jobs.py <stage> <label> <exposure_name> [recipient]'
+        print '  e.g.: generate_warp_jobs.py D010 MD08.jtrp o5806g0078o'
+        print '  Acceptable value for <stage>: DAG, D010-D040, D110-D150, D210, D310'
+        sys.exit(1)
+    STAGE = sys.argv[1]
+    LABEL = sys.argv[2]
+    EXPOSURE_NAME = sys.argv[3]
+    if len(sys.argv) == 5:
+        RECIPIENT = sys.argv[4]
+    else:
+        RECIPIENT = 'schastel@ifa.hawaii.edu'
+    if os.getenv('USER') == 'ippdor':
+        WARP_QUERIER = WarpManager(LABEL, EXPOSURE_NAME, 
+                                  recipient=RECIPIENT)
+    else:
+        WARP_QUERIER = WarpManager(LABEL, EXPOSURE_NAME, 
+                                  recipient=RECIPIENT, 
+                                  host='ipp0012.ifa.hawaii.edu')
+    if STAGE == 'D010':
+        WARP_QUERIER.build_new_exp_warp_jobs()
+    elif STAGE == 'D020' or STAGE == 'D030' or STAGE == 'D040':
+        WARP_QUERIER.build_warp_exp_revert_jobs(STAGE)
+    elif STAGE == 'D110':
+        WARP_QUERIER.build_new_skycell_warp_jobs()
+    elif STAGE == 'D120' or STAGE == 'D130' or STAGE == 'D140':
+        WARP_QUERIER.build_warp_skycell_revert_jobs(STAGE)
+    elif STAGE == 'D210':
+        WARP_QUERIER.build_new_summary_warp_jobs()
+    elif STAGE == 'D310':
+        WARP_QUERIER.advance_warp()
+    elif STAGE == 'DAG':
+        DAG_NAME = WARP_QUERIER.build_warp_dag()
+        WARP_QUERIER.build_new_exp_warp_jobs()
+        print 'DAG file generated now run:'
+        print 'condor_submit_dag %s' % DAG_NAME
+    else:
+        print 'Stage [%s] not supported' % STAGE
+        sys.exit(2)
+    sys.exit(0)
Index: /trunk/ippdor/src/executables/ippdor_chip_to_stack.py
===================================================================
--- /trunk/ippdor/src/executables/ippdor_chip_to_stack.py	(revision 32874)
+++ /trunk/ippdor/src/executables/ippdor_chip_to_stack.py	(revision 32874)
@@ -0,0 +1,234 @@
+#!/usr/bin/env python
+
+import sys
+import os
+import subprocess
+from ipp.Gpc1Manager import Gpc1Manager as Gpc1Manager
+from ipp.StackManager import StackManager as StackManager
+from ipp.helpers import mkdir
+from ipp.IppException import IppException as IppException
+
+class StackBuilder(Gpc1Manager):
+    def __init__(self,
+                 label,
+                 recipient,
+                 host='ippdb01.ifa.hawaii.edu', 
+                 user='ipp', 
+                 passwd='ipp'):
+        """Create a connector to the gpc1 database
+        """
+        Gpc1Manager.__init__(self, recipient=recipient, host=host, user=user, passwd=passwd)
+        self.label = label
+        if self.isLabelUsed():
+            print 'Label [%s] already used' % label
+            print 'IF YOU KNOW WHAT YOU ARE DOING, delete all chips with label %s in chipRun:' % label
+            print "  DELETE FROM chipImfile USING chipImfile, chipRun WHERE chipImfile.chip_id=chipRun.chip_id AND label = '%s';" % label
+            print "  DELETE FROM chipRun WHERE label = '%s';" % label
+            print 'OR'
+            import datetime
+            print 'Use another label (e.g. %s_%s)' % (label, datetime.datetime.now().strftime("%Y%m%dT%H%M%S"))
+            raise IppException('Label [%s] already used' % label)
+        self.rootDirectory = '%s/%s' % (Constants.ippdorLocalTmp, label)
+        mkdir(self.rootDirectory)
+
+    def isLabelUsed(self):
+        query = """SELECT label from chipRun WHERE label = '%s' LIMIT 1""" % self.label
+        values = self.multipleQuery(query)
+        return values is not None and len(values) != 0
+
+    def isExposureNameValid(self, exposureName):
+        query = """SELECT exp_id FROM rawExp WHERE exp_name='%s'""" % exposureName
+        return int(self.simpleQuery(query)[0])
+
+    def buildCondorFiles(self, exposureNames):
+        # Create DAGs for each exposure
+        for exposureName in exposureNames:
+            print '\tCreating DAG for exposure [%s]' % (exposureName)
+            command = "/home/panstarrs/ippdor/local/bin/generate_exposure_dag.py %s %s %s" % (self.label, 
+                                                                                              self.recipient, 
+                                                                                              exposureName)
+            try:
+                subprocess.call(command.split(' '))
+            except OSError:
+                print 'Cannot find /home/panstarrs/ippdor/local/bin/generate_exposure_dag.py, Trying with anonymous generate_exposure_dag.py'
+                command = "generate_exposure_dag.py %s %s %s" % (self.label, 
+                                                                 self.recipient,
+                                                                 exposureName)
+                subprocess.call(command.split(' '))
+        # Generate stack generation job file
+        self.stackGenerationJob()
+        # Generate configuration file
+        self.generateConfiguration()
+        # Generate DAG of DAGs
+        dagOfdagsName = self.generateDAG(exposureNames)
+        return dagOfdagsName
+
+    def generateConfiguration(self):
+        # Generate configuration file
+        output = open('%s/configuration' % self.rootDirectory, 'w')
+        output.write('# DAGMAN_ABORT_ON_SCARY_SUBMIT = FALSE\n')
+        output.close()
+
+    def stackGenerationJob(self):
+        output = open('%s/stack_generation.job' % self.rootDirectory, 'w')
+        output.write('Executable = %s\n' % StackManager.scriptName)
+        output.write('Universe = vanilla\n')
+        output.write('Output = /home/panstarrs/ippdor/tmp/02test/out.%s.stack_generation\n' % (self.label))
+        output.write('Error = /home/panstarrs/ippdor/tmp/02test/err.%s.stack\n' % (self.label))
+        output.write('Log = /tmp/stack_generation.log\n')
+        output.write('Requirements = %s\n' % Constants.REQUIREMENTS)
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        output.write('Arguments = DAG %s %s\n' % (self.label, self.recipient))
+        output.write('Queue\n')
+        output.write('\n')
+        output.close()
+
+    def generateDAG(self, exposureNames):
+        dagName = '%s/ch2st.dag' % self.rootDirectory
+        output = open(dagName, 'w')
+        output.write('# Chip to warp DAGs\n')
+        chip2warpNodesList = ''
+        for exposureName in exposureNames:
+            output.write('SUBDAG EXTERNAL CHIP2WARP_%s %s/%s/%s.dag\n' % (exposureName, 
+                                                                          self.rootDirectory,
+                                                                          exposureName,
+                                                                          exposureName))
+            chip2warpNodesList = '%s CHIP2WARP_%s' % (chip2warpNodesList, exposureName)
+        output.write('\n')
+        output.write('# Generate Stack DAGs\n')
+        output.write('JOB GENERATE_STACK %s/stack_generation.job\n' % self.rootDirectory)
+        output.write('\n')
+        output.write('# Stack DAGs\n')
+        output.write('SUBDAG EXTERNAL STACK %s/stack/stack.dag\n' % (self.rootDirectory))
+        output.write('\n')
+        output.write('# Node Dependencies\n')
+        output.write('PARENT %s CHILD GENERATE_STACK\n' % chip2warpNodesList)
+        output.write('PARENT GENERATE_STACK CHILD STACK\n')
+        output.write('\n')
+        output.write('# Retries\n')
+        for exposureName in exposureNames:
+            output.write('# RETRY CHIP2WARP_%s 3\n' % exposureName)
+        output.write('# RETRY GENERATE_STACK 3\n')
+        output.write('# RETRY STACK 3\n')        
+        output.write('\n')
+        output.close()
+        return dagName
+
+def loadInputFile(fileName):
+    """File format is the following:
+label    = PreferredLabel
+tessId   = MD08.V2
+exposure = exposure1
+exposure = exposure2
+exposure = exposure3
+exposure = exposure4
+# Comment line
+exposure = exposure5    # Followed by a comment
+"""
+    import re
+    exposureNames = []
+    inFile = open(fileName)
+    labelNotDefined = True
+    tessIdNotDefined = True
+    recipientNotDefined = True
+    for originalLine in inFile:
+        line = originalLine[:-1]
+        line = re.sub('#.*', '', line)
+        line = re.sub('\s+', '', line)
+        #print 'Line after replacements: [%s]' % line
+        if line != '':
+            elements = line.split('=')
+            if len(elements) != 2:
+                print 'Cannot interpret line [%s]' % originalLine[:-1]
+                print 'Check your file format:'
+                print loadInputFile.__doc__
+                raise IppException('Error: Input file format [%s]' % fileName)
+            else:
+                if elements[0] == 'label':
+                    if labelNotDefined:
+                        label = elements[1]
+                        labelNotDefined = False
+                    else:
+                        raise IppException('label defined twice in [%s]' % fileName)
+                elif elements[0] == 'tessId':
+                    if tessIdNotDefined:
+                        tessId = elements[1]
+                        tessIdNotDefined = False
+                    else:
+                        raise IppException('tessId defined twice in [%s]' % fileName)
+                elif elements[0] == 'exposure':
+                    exposureNames.append(elements[1])
+                elif elements[0] == 'recipient':
+                    if recipientNotDefined:
+                        recipient = elements[1]
+                        recipientNotDefined = False
+                    else:
+                        raise IppException('recipient defined twice in [%s]' % fileName)
+                else:
+                    print 'Unknown key [%s] in [%s]' % (elements[0], fileName)
+                    print loadInputFile.__doc__
+                    raise IppException('Unknown key [%s] in [%s]' % (elements[0], fileName))
+    return (label, tessId, exposureNames, recipient)
+
+if __name__ == '__main__':
+    if len(sys.argv)<2 or sys.argv[1].startswith('-h') or sys.argv[1].startswith('--h'):
+        print '<Usage>: %s <label> <tessellation> <email recipient> [exposures list]' % sys.argv[0]
+        print '         %s -f <input_file>' % sys.argv[0]
+        print '   If <label> has already been used, the program will abort'
+        print '   [exposures list] must have more than 2 exposures'
+        print
+        print loadInputFile.__doc__
+        sys.exit(1)
+    #TODO: Implement proper arguments management
+    if sys.argv[1] == '-f':
+        (label, tessellation, exposureNames, recipient) = loadInputFile(sys.argv[2])
+    else:
+        argStart = 1
+        label = sys.argv[argStart]
+        argStart += 1
+        tessellation = sys.argv[argStart]
+        argStart += 1
+        recipient = sys.argv[argStart]
+        argStart += 1
+        exposureNames = sys.argv[argStart:]
+        argStart += 1
+    if len(exposureNames) < 2:
+        print 'You need more than 2 exposures to build a stack'
+        print 'Aborting'
+        sys.exit(4)
+    if os.getenv('USER') == 'ippdor':
+        builder = StackBuilder(label, recipient)
+    else:
+        builder = StackBuilder(label, recipient, host='ipp0012.ifa.hawaii.edu')
+    # Check exposures        
+    expIds = dict()
+    for exposureName in exposureNames:
+        print 'Checking exposure [%s]' % exposureName
+        try:
+            expIds[exposureName] = builder.isExposureNameValid(exposureName)
+        except IndexError:
+            print 'Exposure [%s] is not valid' % exposureName
+            print 'Aborting'
+            sys.exit(3)
+    # Create entries in chiptool
+    print 'Stacking %d exposures with label [%s]' % (len(exposureNames), label)
+    for exposureName in exposureNames:
+        expId = expIds[exposureName]
+        print '\tCreating chipRun entry for exposure [%s]/%d' % (exposureName, expId)
+        command = "/home/panstarrs/ippdor/psconfig/20110920/20110920.lin64/bin/chiptool -dbname gpc1 -definebyquery -exp_id %d" % expId
+        command = "%s -set_label %s" % (command, label)
+        command = "%s -set_workdir neb://\@HOST\@.0/gpc1/%s" % (command, label)
+        command = "%s -set_tess_id %s" % (command, tessellation)
+        command = "%s -set_data_group %s_dg" % (command, label)
+        command = "%s -set_note \'chip/stack_reprocessing\' -set_end_stage warp" % (command)
+        try:
+            subprocess.call(command.split(' '))
+        except OSError:
+            print 'System call to [%s] failed. Continuing though...' % command
+    # Generate Condor jobs, subdags and dag
+    dagOfDagsName = builder.buildCondorFiles(exposureNames)
+    # Show the command to be run
+    print 'Now run:'
+    print 'condor_submit_dag -maxidle 1000 -maxjobs 500 %s' % dagOfDagsName
Index: /trunk/ippdor/src/executables/ippdor_chip_to_warp.py
===================================================================
--- /trunk/ippdor/src/executables/ippdor_chip_to_warp.py	(revision 32874)
+++ /trunk/ippdor/src/executables/ippdor_chip_to_warp.py	(revision 32874)
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+
+"""
+This is the script that generates:
+- DAG for each stage from chip to warp for each exposure 
+- DAG of all these DAGs
+"""
+
+import sys
+import os
+import subprocess
+import datetime
+
+from ipp.Gpc1Manager import Gpc1Manager as Gpc1Manager
+from ipp.helpers import mkdir
+from ipp.constants.Globals import Globals as GlobalConstants
+from ipp.exceptions.IppException import IppException
+from ipp.exceptions.UninstantiableIppException import UninstantiableIppException
+
+class Constants:
+    """Constants which are used only in this script
+    """
+    def __init__(self):
+        raise UninstantiableIppException()
+
+    # In LABEL_ALREADY_USED, replace:
+    # - @LABEL@ with label value 
+    LABEL_ALREADY_USED = """
+Error: Label [@LABEL@] already used.
+
+To fix the problem:
+1) Either use another label (e.g. @LABEL_%s)
+2) Or __IF YOU KNOW WHAT YOU ARE DOING__, delete all chips with label [@LABEL@] in chipRun:
+  DELETE FROM chipImfile USING chipImfile, chipRun WHERE chipImfile.chip_id=chipRun.chip_id AND label = '@LABEL@';
+  DELETE FROM chipRun WHERE label = '@LABEL@';
+""" % (datetime.datetime.now().strftime("%Y%m%dT%H%M%S"))
+    # Format string for the DAG of DAGs:
+    # - #1: target directory
+    DAG_OF_DAGS_FMT = "%s/ch2wp.dag"
+
+class ChipWarpBuilder(Gpc1Manager):
+    """Main class for DAG/Condor job files building
+    """
+    def __init__(self,
+                 label,
+                 recipient,
+                 host='ippdb01.ifa.hawaii.edu', 
+                 user='ipp', 
+                 passwd='ipp'):
+        """
+        The constructor:
+        - creates a connector to the gpc1 database
+        - checks if the label has already been used
+        - creates a local directory
+        """
+        Gpc1Manager.__init__(self, recipient=recipient, host=host, user=user, passwd=passwd)
+        self.label = label
+        if self.is_label_used():
+            print Constants.LABEL_ALREADY_USED.replace('@LABEL@', self.label)
+            raise IppException('Label [%s] already used' % label)
+        self.root_directory = '%s/%s' % (GlobalConstants.LOCAL_TMP_DIR, label)
+        mkdir(self.root_directory)
+
+    def build_condor_files(self, exposures_names):
+        """Generate all condor subdag files
+        """
+        # Create DAGs for each exposure
+        for exposure_name in exposures_names:
+            print '\tCreating DAG for exposure [%s]' % (exposure_name)
+            command = GlobalConstants.GENERATE_EXPOSURE_DAG % (self.label, 
+                                                               self.recipient, 
+                                                               exposure_name)
+            try:
+                subprocess.call(command.split(' '))
+            except OSError:
+                print 'Cannot find %s' % GlobalConstants.GENERATE_EXPOSURE_DAG
+                command = "generate_exposure_dag.py %s %s %s" % (self.label, 
+                                                                 self.recipient,
+                                                                 exposure_name)
+                subprocess.call(command.split(' '))
+        # Generate completion job file
+        self.completion_job()
+        # Generate configuration file
+        self.generate_configuration_file()
+        # Generate DAG of DAGs
+        dag_of_dags_name = self.generate_dag(exposures_names)
+        return dag_of_dags_name
+
+    def generate_configuration_file(self):
+        """Method generating the condor configuration file (empty now)
+        """
+        output = open('%s/configuration' % self.root_directory, 'w')
+        output.write('# File empty for now\n')
+        output.close()
+
+    def completion_job(self):
+        """Method generating the condor job file for completion
+        """
+        output = open('%s/chip_to_warp_completion.job' % self.root_directory, 'w')
+        output.write('Executable = %s/send_email.py\n' % GlobalConstants.IPPDOR_BINARY_PATH)
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/%s.out.ch2wp.completion\n' % (GlobalConstants.NFS_TMP_DIR, 
+                                                                self.label))
+        output.write('Error = %s/%s.err.ch2wp.completion\n' % (GlobalConstants.NFS_TMP_DIR, 
+                                                               self.label))
+        output.write('Log = %s/%s_ch2wp.completion.log\n' % (GlobalConstants.LOCAL_TMP_DIR, 
+                                                             self.label))
+        output.write('Requirements = %s\n' % (GlobalConstants.REQUIREMENTS))
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        output.write('Arguments =  %s %s %s/%s_final_email.txt\n' % (self.label, 
+                                                                     self.recipient,
+                                                                     GlobalConstants.NFS_TMP_DIR,
+                                                                     self.label))
+        output.write('Queue\n')
+        output.write('\n')
+        output.close()
+        # E-mail content
+        output = open('%s/%s_final_email.txt' % (GlobalConstants.NFS_TMP_DIR,
+                                                 self.label), 'w')
+        output.write('Condor DAG complete: label [%s]\n' % self.label)
+        output.close()
+
+    def generate_dag(self, exposures_names):
+        """Method generating the DAG of DAGs
+        """
+        dag_name = Constants.DAG_OF_DAGS_FMT % self.root_directory
+        output = open(dag_name, 'w')
+        output.write('# Chip to warp DAGs\n')
+        chip2warp_nodes_list = ''
+        for exposure_name in exposures_names:
+            output.write('SUBDAG EXTERNAL CHIP2WARP_%s %s/%s/%s.dag\n' % (exposure_name, 
+                                                                          self.root_directory,
+                                                                          exposure_name,
+                                                                          exposure_name))
+            chip2warp_nodes_list = '%s CHIP2WARP_%s' % (chip2warp_nodes_list, exposure_name)
+        output.write('\n')
+        output.write('# Send an e-mail when done\n')
+        output.write('JOB COMPLETION_EMAIL %s/chip_to_warp_completion.job\n' % self.root_directory)
+        output.write('\n')
+        output.write('# Node Dependencies\n')
+        output.write('PARENT %s CHILD COMPLETION_EMAIL\n' % chip2warp_nodes_list)
+        output.write('\n')
+        output.close()
+        return dag_name
+
+def load_input_file(filename):
+    """File format is the following:
+label    = PreferredLabel
+tessId   = MD08.V2
+exposure = exposure1
+exposure = exposure2
+exposure = exposure3
+exposure = exposure4
+# Comment line
+exposure = exposure5    # Followed by a comment
+"""
+    import re
+    exposures_names = []
+    input_file = open(filename)
+    label_not_defined = True
+    tessellation_not_defined = True
+    recipient_not_defined = True
+    for original_line in input_file:
+        line = original_line[:-1]
+        line = re.sub('#.*', '', line)
+        line = re.sub('\s+', '', line)
+        #print 'Line after replacements: [%s]' % line
+        if line != '':
+            elements = line.split('=')
+            if len(elements) != 2:
+                print 'Cannot interpret line [%s]' % original_line[:-1]
+                print 'Check your file format:'
+                print load_input_file.__doc__
+                raise IppException('Error: Input file format [%s]' % filename)
+            else:
+                if elements[0] == 'label':
+                    if label_not_defined:
+                        label = elements[1]
+                        label_not_defined = False
+                    else:
+                        raise IppException('label defined twice in [%s]' % filename)
+                elif elements[0] == 'tessId':
+                    if tessellation_not_defined:
+                        tessellation_id = elements[1]
+                        tessellation_not_defined = False
+                    else:
+                        raise IppException('tessId defined twice in [%s]' % filename)
+                elif elements[0] == 'exposure':
+                    exposures_names.append(elements[1])
+                elif elements[0] == 'recipient':
+                    if recipient_not_defined:
+                        recipient = elements[1]
+                        recipient_not_defined = False
+                    else:
+                        raise IppException('recipient defined twice in [%s]' % filename)
+                else:
+                    print 'Unknown key [%s] in [%s]' % (elements[0], filename)
+                    print load_input_file.__doc__
+                    raise IppException('Unknown key [%s] in [%s]' % (elements[0], filename))
+    input_file.close()
+    return (label, tessellation_id, exposures_names, recipient)
+
+#####################################################################################################
+if __name__ == '__main__':
+    #TODO: Implement proper arguments management
+    if len(sys.argv)<2 or sys.argv[1].startswith('-h') or sys.argv[1].startswith('--h'):
+        print '<Usage>: %s <label> <tessellation> <email recipient> [exposures list]' % sys.argv[0]
+        print '         %s -f <input_file>' % sys.argv[0]
+        print '   If <label> has already been used, the program will abort'
+        print '   [exposures list] must have more than 2 exposures'
+        print
+        print load_input_file.__doc__
+        sys.exit(1)
+    if sys.argv[1] == '-f':
+        (LABEL, TESSELLATION_ID, EXPOSURES_NAMES, RECIPIENT) = load_input_file(sys.argv[2])
+    else:
+        ARG_START = 1
+        LABEL = sys.argv[ARG_START]
+        ARG_START += 1
+        TESSELLATION_ID = sys.argv[ARG_START]
+        ARG_START += 1
+        RECIPIENT = sys.argv[ARG_START]
+        ARG_START += 1
+        EXPOSURES_NAMES = sys.argv[ARG_START:]
+        ARG_START += 1
+    if len(EXPOSURES_NAMES) < 2:
+        print 'You need more than 2 exposures to build a stack'
+        print 'Aborting'
+        sys.exit(4)
+    if os.getenv('USER') == 'ippdor':
+        BUILDER = ChipWarpBuilder(LABEL, RECIPIENT)
+    else:
+        BUILDER = ChipWarpBuilder(LABEL, RECIPIENT, host='ipp0012.ifa.hawaii.edu')
+    # Check exposures        
+    EXPOSURES_IDS = dict()
+    for _exposure_name in EXPOSURES_NAMES:
+        print 'Checking exposure [%s]' % _exposure_name
+        try:
+            EXPOSURES_IDS[_exposure_name] = BUILDER.is_exposure_name_valid(_exposure_name)
+        except IndexError:
+            print 'Exposure [%s] is not valid' % _exposure_name
+            print 'Aborting'
+            sys.exit(3)
+    # Generate Condor jobs, subdags and dag
+    DAG_OF_DAGS_NAME = BUILDER.build_condor_files(EXPOSURES_NAMES)
+    # Create entries in chiptool
+    print 'Stacking %d exposures with label [%s]' % (len(EXPOSURES_NAMES), LABEL)
+    for _exposure_name in EXPOSURES_NAMES:
+        expId = EXPOSURES_IDS[_exposure_name]
+        print '\tCreating chipRun entry for exposure [%s]/%d' % (_exposure_name, expId)
+        COMMAND = "%s -definebyquery -exp_id %d" % (GlobalConstants.CHIPTOOL, expId)
+        COMMAND = "%s -set_label %s" % (COMMAND, LABEL)
+        COMMAND = "%s -set_workdir neb://\@HOST\@.0/gpc1/%s" % (COMMAND, LABEL)
+        COMMAND = "%s -set_tess_id %s" % (COMMAND, TESSELLATION_ID)
+        COMMAND = "%s -set_data_group %s_dg" % (COMMAND, LABEL)
+        COMMAND = "%s -set_note \'chip/stack_reprocessing\' -set_end_stage warp" % (COMMAND)
+        try:
+            subprocess.call(COMMAND.split(' '))
+        except OSError:
+            print 'System call to [%s] failed.' % COMMAND
+            print '... Continuing though (assuming you know what you are doing)'
+    # Show the command to be run
+    print 'Now run:'
+    print 'condor_submit_dag -maxidle 1000 -maxjobs 500 %s' % DAG_OF_DAGS_NAME
Index: /trunk/ippdor/src/executables/send_email.py
===================================================================
--- /trunk/ippdor/src/executables/send_email.py	(revision 32874)
+++ /trunk/ippdor/src/executables/send_email.py	(revision 32874)
@@ -0,0 +1,28 @@
+#!/usr/bin/env python
+
+"""
+A simple utility program to send an e-mail at the end of the condor
+processing.
+"""
+import sys
+
+LABEL = sys.argv[1]
+RECIPIENT = sys.argv[2]
+BODY = ""
+
+BODY_INPUT_FILE = open(sys.argv[3])
+for line in BODY_INPUT_FILE:
+    BODY = "%s%s" % (BODY, line)
+BODY_INPUT_FILE.close()
+
+import smtplib
+from email.mime.text import MIMEText
+MESSAGE = MIMEText(BODY)
+MESSAGE['Subject'] = 'End of Condor processing: [%s]' % LABEL
+_FROM = 'ippdor@ifa.hawaii.edu'
+MESSAGE['From'] = _FROM
+_TO = [ RECIPIENT, 'schastel@ifa.hawaii.edu' ]
+MESSAGE['To'] = _TO
+SMTP_CONNECTION = smtplib.SMTP('ippc18.ifa.hawaii.edu')
+SMTP_CONNECTION.sendmail(_FROM, _TO, MESSAGE.as_string())
+SMTP_CONNECTION.quit()
Index: /trunk/ippdor/src/executables/undo_dag_exposure.py
===================================================================
--- /trunk/ippdor/src/executables/undo_dag_exposure.py	(revision 32874)
+++ /trunk/ippdor/src/executables/undo_dag_exposure.py	(revision 32874)
@@ -0,0 +1,36 @@
+#!/usr/bin/env python
+
+"""
+Remove the DONE label from a dag file from a <stage> and after this <stage>
+"""
+
+
+if __name__ == '__main__':
+    import sys
+    FILENAME = sys.argv[1]
+    STAGE = sys.argv[2]
+    # Make a backup
+    BACKUP_FILE = '%s.backup' % FILENAME
+    try:
+        BACKUP = open(BACKUP_FILE)
+        BACKUP.close()
+        print 'Remove/Rename old backup first [%s] since I will not delete it' % BACKUP_FILE
+        sys.exit(1)
+    except IOError:
+        pass
+    print 'Backup file: %s' % BACKUP_FILE
+    import shutil
+    shutil.copyfile(FILENAME, BACKUP_FILE)
+    # Parse and filter
+    INPUT_FILE = open(BACKUP_FILE)
+    OUTPUT = open(FILENAME, 'w')
+    STATE = 0
+    for line in INPUT_FILE:
+        if STATE == 0:
+            if STAGE in line:
+                STATE = 1
+        if STATE == 1:
+            line = line.replace(' DONE', '')
+        OUTPUT.write(line)
+    INPUT_FILE.close()
+    OUTPUT.close()
Index: /trunk/ippdor/src/ipp/CamManager.py
===================================================================
--- /trunk/ippdor/src/ipp/CamManager.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/CamManager.py	(revision 32874)
@@ -0,0 +1,232 @@
+"""
+Generation of the full cam DAG for one exposure and the jobs
+associated to this stage
+"""
+from ipp.Gpc1Manager import Gpc1Manager as Gpc1Manager
+from ipp.helpers import mkdir
+from ipp.exceptions.IppException import IppException
+from ipp.constants.Globals import Globals as GlobalConstants
+from ipp.constants.Camera import Camera as CameraConstants
+
+import sys
+
+#################################################################
+class CamManager(Gpc1Manager):
+    """Derived from the abstract Gpc1Manager to have database access
+    """
+    SCRIPT_NAME = '/home/panstarrs/ippdor/local/bin/generate_cam_jobs.py'
+
+    def __init__(self,
+                 label,
+                 exposure_name,
+                 recipient,
+                 host='ippdb01.ifa.hawaii.edu', 
+                 user='ipp', 
+                 passwd='ipp'):
+        """
+        Creates a connector to the gpc1 database as well as local and
+        nfs directories (their name is cam)
+        """
+        Gpc1Manager.__init__(self, recipient=recipient, host=host, user=user, passwd=passwd)
+        self.label = label
+        self.exposure_name = exposure_name
+        self.cam_directory = '%s/%s/%s/cam' % (GlobalConstants.LOCAL_TMP_DIR,
+                                              self.label, 
+                                               self.exposure_name)
+        mkdir(self.cam_directory)
+        self.nfs_cam_directory = '%s/%s/%s/cam' % (GlobalConstants.NFS_TMP_DIR,
+                                                   self.label, 
+                                                   self.exposure_name)
+        mkdir(self.nfs_cam_directory)
+
+    def build_new_camera_jobs(self, stage_name = 'B010'):
+        """Build Condor jobs files for stages B010 (regular new), B020, B030, B040 (3 possible reverts)
+        """
+        query = """
+SELECT
+    rawExp.exp_tag,
+    camRun.cam_id,
+    rawExp.camera,
+    camRun.state,
+    camRun.workdir,
+    camRun.reduction,
+    camRun.dvodb
+FROM camRun
+JOIN chipRun
+    USING(chip_id)
+JOIN rawExp
+    USING(exp_id)
+LEFT JOIN camProcessedExp
+    USING(cam_id)
+LEFT JOIN camMask
+    ON camRun.label = camMask.label
+WHERE
+    chipRun.state = 'full'
+    AND ((camRun.state = 'new' AND camProcessedExp.cam_id IS NULL) OR camRun.state = 'update')
+    AND camMask.label IS NULL
+    AND chipRun.label = '%s'
+    AND rawExp.exp_name = '%s'
+""" % (self.label, self.exposure_name)
+        cameras = self.multiple_query(query)
+        jobs_filename = '%s/%s.jobs' % (self.cam_directory, stage_name)
+        message = 'Generating Condor jobs file [%s]' % (jobs_filename)
+        message = '%s for processing of %d cam (stage %s)' % ( message,
+                                                               len(cameras), 
+                                                               stage_name)
+        print message
+        output = open(jobs_filename, 'w')
+        output.write('Executable = %s/encapsulate_camera.sh\n' % GlobalConstants.IPPDOR_BINARY_PATH)
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/%s.%s.cam_%s.out\n' % (self.nfs_cam_directory,
+                                                         self.label, self.exposure_name, 
+                                                         stage_name))
+        output.write('Error = %s/%s.%s.cam_%s.err\n' % (self.nfs_cam_directory,
+                                                        self.label, self.exposure_name, 
+                                                        stage_name))
+        output.write('Log = %s/%s.%s.cam.log\n' % (self.cam_directory,
+                                                   self.exposure_name, stage_name))
+        output.write('Requirements = %s\n' % CameraConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        if len(cameras) > 0:
+            camera = cameras[0]
+            (exp_tag, cam_id, camera, state, workdir, reduction, dvodb) = camera
+            if len(cameras) > 1:
+                body = CameraConstants.email_body_multiple_cam_entries % (self.label, 
+                                                                          self.exposure_name, 
+                                                                          cam_id)
+                self.send_email('Multiple camRun entries for label %s / exposure %s' 
+                                % (self.label, 
+                                   self.exposure_name),
+                                body)
+            outroot = '%s/%s/%s.cm.%d' % (workdir, exp_tag, exp_tag, cam_id)
+            arguments = "Arguments = %s" % GlobalConstants.CAMEXP
+            arguments = "%s --exp_tag %s" % (arguments, exp_tag)
+            arguments = "%s --cam_id %d" % (arguments, cam_id)
+            arguments = "%s --camera %s" % (arguments, camera)
+            arguments = "%s --outroot %s" % (arguments, outroot)
+            arguments = "%s --run-state %s" % (arguments, state)
+            if reduction is not None and reduction != 'NULL':
+                arguments = '%s --reduction %s' % (arguments, reduction)
+            if dvodb is not None and dvodb != 'NULL':
+                arguments = '%s --dvodb %s' % (arguments, dvodb)
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        if len(cameras) == 0:
+            arguments = 'Arguments = echo Nothing to do for stage %s' % stage_name
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        output.close()
+
+    def build_camera_revert_jobs(self, stage_name):
+        """Build the jobs to revert camera
+        """
+        query = """
+DELETE FROM camProcessedExp
+USING camProcessedExp, camRun, chipRun, rawExp
+WHERE
+    camRun.cam_id = camProcessedExp.cam_id
+    AND camRun.chip_id = chipRun.chip_id
+    AND chipRun.exp_id = rawExp.exp_id
+    AND camProcessedExp.fault != 0
+    AND camRun.state = 'new'
+    AND camRun.label = '%s'
+    AND chipRun.exp_id = %d
+""" % (self.label, self.get_exposure_id(self.exposure_name))
+        self.multiple_query(query)
+        self.build_new_camera_jobs(stage_name)
+
+    def advance_camera(self):
+        """Last phase (B050) in camera: 
+        * If there is something to revert (we already reverted 3 times): send 
+          an e-mail to the operator and abort any further processing
+        * If it has quality != 0: send an e-mail to the operator and
+          abort any further processing
+        """
+        query = """
+SELECT camProcessedExp.fault, camProcessedExp.quality
+FROM camProcessedExp 
+  JOIN camRun USING(cam_id) 
+  JOIN chipRun USING(chip_id) 
+  JOIN rawExp USING(exp_id)
+WHERE
+  ( ( camProcessedExp.fault != 0
+      AND camRun.state = 'new' )
+    OR ( camProcessedExp.quality != 0) )
+  AND camRun.label = '%s'
+  AND chipRun.exp_id = %d
+""" % (self.label, self.get_exposure_id(self.exposure_name))
+        cam_status = self.multiple_query(query)
+        if len(cam_status) > 0:
+            # Send e-mail to recipient
+            print 'Default for exposure [%s] at cam level' % (self.exposure_name)
+            body = CameraConstants.email_body_revert_failure % (self.exposure_name,
+                                                                self.label, 
+                                                                self.exposure_name)
+            self.send_email('Camera stage failed for label %s / exposure %s)' % (self.label, 
+                                                                                 self.exposure_name), 
+                            body)
+            print 'EMail sent to [%s]' % self.recipient
+            raise IppException('Camera stage failed for label %s / exposure %s)' 
+                               % (self.label, 
+                                  self.exposure_name))
+        # Generate the B050 stage
+        jobs_filename = '%s/B050.jobs' % (self.cam_directory)
+        print "Generating Condor jobs file [%s] for processing of camera (stage B050)" % (jobs_filename)
+        output = open(jobs_filename, 'w')
+        output.write('Executable = %s/encapsulate_camera.sh\n' % GlobalConstants.IPPDOR_BINARY_PATH)
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/%s.%s.cam_B050.out\n' % (self.nfs_cam_directory,
+                                                           self.label, self.exposure_name))
+        output.write('Error = %s/%s.%s.cam_B050.err\n' % (self.nfs_cam_directory,
+                                                          self.label, self.exposure_name))
+        output.write('Log = %s/%s.%s.cam_B050.log\n' % (self.cam_directory,
+                                                        self.label, self.exposure_name))
+        output.write('Requirements = %s\n' % CameraConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        arguments = 'echo Nothing to do for camera exposure %s/%s (stage B050)' % (self.exposure_name, self.label)
+        output.write('Arguments = %s\n' % arguments)
+        output.write('Queue\n')
+        output.write('\n')
+        output.close()
+
+    def build_camera_dag(self):
+        """Generate the camera DAG
+        """
+        dag_name = '%s/camera.dag' % (self.cam_directory)
+        output = open(dag_name, 'w')
+        output.write('# Generated DAG\n')
+        output.write('JOB\tB010\t%s/B010.jobs\n' % self.cam_directory)
+        output.write('SCRIPT POST B010 %s B020 %s %s %s\n' % (CamManager.SCRIPT_NAME, 
+                                                              self.label, self.exposure_name, self.recipient))
+        output.write('JOB\tB020\t%s/B020.jobs\n' % self.cam_directory)
+        output.write('SCRIPT POST B020 %s B030 %s %s %s\n' % (CamManager.SCRIPT_NAME, 
+                                                              self.label, self.exposure_name, self.recipient))
+        output.write('JOB\tB030\t%s/B030.jobs\n' % self.cam_directory)
+        output.write('SCRIPT POST B030 %s B040 %s %s %s\n' % (CamManager.SCRIPT_NAME, 
+                                                              self.label, self.exposure_name, self.recipient))
+        output.write('JOB\tB040\t%s/B040.jobs\n' % self.cam_directory)
+        output.write('SCRIPT POST B040 %s B050 %s %s %s\n' % (CamManager.SCRIPT_NAME, 
+                                                              self.label, self.exposure_name, self.recipient))
+        output.write('JOB\tB050\t%s/B050.jobs\n' % self.cam_directory)
+        output.write('\n')
+        output.write('PARENT B010 CHILD B020\n')
+        output.write('PARENT B020 CHILD B030\n')
+        output.write('PARENT B030 CHILD B040\n')
+        output.write('PARENT B040 CHILD B050\n')
+        output.write('\n')
+        output.close()
+        return dag_name
+
+#################################################################
+if __name__ == '__main__':
+    print '<Error>: Not intended to be used as an independent program'
+    print '<Error>: Use %s' % CamManager.SCRIPT_NAME
+    sys.exit(1)
Index: /trunk/ippdor/src/ipp/ChipManager.py
===================================================================
--- /trunk/ippdor/src/ipp/ChipManager.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/ChipManager.py	(revision 32874)
@@ -0,0 +1,280 @@
+"""
+Generate the full chip DAG for one exposure
+Generate the Condor job files for chip stage
+"""
+from ipp.Gpc1Manager import Gpc1Manager as Gpc1Manager
+from ipp.helpers import mkdir
+from ipp.exceptions.IppException import IppException
+from ipp.constants.Globals import Globals as GlobalConstants
+from ipp.constants.Chip import Chip as ChipConstants
+
+#################################################################
+class ChipManager(Gpc1Manager):
+    """
+    Generate the full chip DAG for one exposure
+    Generate the Condor job files for chip stage
+    """
+    SCRIPT_NAME = '%s/generate_chip_jobs.py' % GlobalConstants.IPPDOR_BINARY_PATH
+
+    def __init__(self,
+                 label,
+                 exposure_name,
+                 recipient,
+                 host='ippdb01.ifa.hawaii.edu', 
+                 user='ipp', 
+                 passwd='ipp'):
+        """
+        Creates a connector to the gpc1 database as well as local and
+        nfs directories (their name is chip)
+        """
+        Gpc1Manager.__init__(self, recipient=recipient, host=host, user=user, passwd=passwd)
+        self.label = label
+        self.exposure_name = exposure_name
+        self.chip_directory = '%s/%s/%s/chip' % (GlobalConstants.LOCAL_TMP_DIR, 
+                                                 self.label, self.exposure_name)
+        mkdir(self.chip_directory)
+        self.nfs_chip_directory = '%s/%s/%s/chip' % (GlobalConstants.NFS_TMP_DIR, 
+                                                     self.label, self.exposure_name)
+        mkdir(self.nfs_chip_directory)
+
+    def build_new_chips_jobs(self, stage_name = 'A010'):
+        """Build the jobs file for the A010 node
+        """
+        query = """
+SELECT
+    chipRun.exp_id,
+    chipRun.chip_id,
+    chipImfile.chip_imfile_id,
+    rawImfile.class_id,
+    rawImfile.uri,
+    rawExp.camera,
+    chipRun.state,
+    rawImfile.magicked as raw_magicked,
+    (rawImfile.user_1 is not NULL and rawImfile.user_1 > 0.5) as deburned,
+    chipRun.workdir,
+    rawExp.exp_tag,
+    chipRun.reduction
+FROM chipRun
+JOIN rawExp
+    USING(exp_id)
+JOIN rawImfile
+    ON rawExp.exp_id = rawImfile.exp_id
+    AND rawImfile.ignored = 0
+JOIN chipImfile
+    USING(chip_id, class_id)
+LEFT JOIN chipProcessedImfile
+    ON chipRun.chip_id = chipProcessedImfile.chip_id
+    AND rawImfile.exp_id = chipProcessedImfile.exp_id
+    AND rawImfile.class_id = chipProcessedImfile.class_id
+LEFT JOIN chipMask
+    ON chipRun.label = chipMask.label
+LEFT JOIN Label ON chipRun.label = Label.label
+WHERE
+    chipRun.state = 'new'
+    AND chipProcessedImfile.chip_id IS NULL
+    AND chipProcessedImfile.exp_id IS NULL
+    AND chipProcessedImfile.class_id IS NULL
+    AND chipMask.label IS NULL
+    AND chipRun.exp_id = %d
+    AND chipRun.label = '%s'
+""" % (self.get_exposure_id(self.exposure_name), self.label)
+        chips = self.multiple_query(query)
+        if len(chips) != 60:
+            message = "(Stage %s) There are %d chips (instead of 60) " % (stage_name, 
+                                                                          len(chips))
+            message = "%s for exposure [%s] (label: [%s])" % (message,
+                                                              self.exposure_name, 
+                                                              self.label)
+            print message
+        jobs_filename = '%s/%s.jobs' % (self.chip_directory, stage_name)
+        message = 'Generating Condor jobs file [%s] ' % jobs_filename
+        message = '%s for processing of %d chips (stage %s)' % (message, 
+                                                                len(chips), 
+                                                                stage_name)
+        output = open(jobs_filename, 'w')
+        output.write('Executable = %s/encapsulate_chip.sh\n' % GlobalConstants.IPPDOR_BINARY_PATH)
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/%s.%s.chip_%s.out\n' % (self.nfs_chip_directory, 
+                                                          self.label, self.exposure_name, stage_name))
+        output.write('Error = %s/%s.%s.chip_%s.err\n' % (self.nfs_chip_directory, 
+                                                         self.label, self.exposure_name, stage_name))
+        output.write('Log = %s/%s.%s.chip_%s.log\n' % (self.chip_directory,
+                                                       self.label, self.exposure_name,
+                                                       stage_name))
+        output.write('Requirements = %s\n' % ChipConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        for chip in chips:
+            (exp_id, chip_id, chip_imfile_id, ota, uri, camera, state, raw_magicked, 
+             deburned, work_dir, exp_tag, reduction) = chip
+            outroot = '%s/%s/%s.ch.%d' % (work_dir, exp_tag, exp_tag, chip_id)
+            arguments = 'Arguments = %s' % GlobalConstants.CHIPIMFILE
+            arguments = '%s --exp_id %d' % (arguments, exp_id)
+            arguments = '%s --chip_id %d' % (arguments, chip_id)
+            arguments = '%s --chip_imfile_id %d' % (arguments, chip_imfile_id)
+            arguments = '%s --class_id %s' % (arguments, ota)
+            arguments = '%s --uri %s' % (arguments, uri)
+            arguments = '%s --camera %s' % (arguments, camera)
+            arguments = '%s --run-state %s' % (arguments, state)
+            arguments = '%s --deburned %d' % (arguments, deburned)
+            arguments = '%s --outroot %s' % (arguments, outroot)
+            if raw_magicked > 0:
+                arguments = '%s --magicked %d' % (arguments, raw_magicked)
+            if reduction is not None:
+                arguments = '%s --reduction %s' % (arguments, reduction)
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        if len(chips) == 0:
+            arguments = 'Arguments = echo Nothing to do for stage %s' % stage_name
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        output.close()
+
+    def build_chip_dag(self):
+        """Build the full DAG file for the chip (aka A) stage
+        """
+        dag_name = '%s/chip.dag' % (self.chip_directory)
+        output = open(dag_name, 'w')
+        output.write('# Generated DAG\n')
+        output.write('JOB\tA010\t%s/A010.jobs\n' % self.chip_directory)
+        output.write('SCRIPT POST A010 %s A020 %s %s %s\n' % (ChipManager.SCRIPT_NAME, 
+                                                              self.label, self.exposure_name,
+                                                              self.recipient))
+        output.write('JOB\tA020\t%s/A020.jobs\n' % self.chip_directory)
+        output.write('SCRIPT POST A020 %s A030 %s %s %s\n' % (ChipManager.SCRIPT_NAME, 
+                                                              self.label, self.exposure_name,
+                                                              self.recipient))
+        output.write('JOB\tA030\t%s/A030.jobs\n' % self.chip_directory)
+        output.write('SCRIPT POST A030 %s A040 %s %s %s\n' % (ChipManager.SCRIPT_NAME, 
+                                                              self.label, self.exposure_name,
+                                                              self.recipient))
+        output.write('JOB\tA040\t%s/A040.jobs\n' % self.chip_directory)
+        output.write('SCRIPT POST A040 %s A050 %s %s %s\n' % (ChipManager.SCRIPT_NAME, 
+                                                              self.label, self.exposure_name,
+                                                              self.recipient))
+        output.write('JOB\tA050\t%s/A050.jobs\n' % self.chip_directory)
+        output.write('\n')
+        output.write('PARENT A010 CHILD A020\n')
+        output.write('PARENT A020 CHILD A030\n')
+        output.write('PARENT A030 CHILD A040\n')
+        output.write('PARENT A040 CHILD A050\n')
+        output.write('\n')
+        output.close()
+        return dag_name
+
+    def build_chip_revert_jobs(self, stage_name):
+        """Build the jobs to revert chips
+        """
+        query = """
+DELETE FROM chipProcessedImfile
+USING chipProcessedImfile, chipRun, rawExp
+WHERE
+    chipRun.chip_id = chipProcessedImfile.chip_id
+    AND rawExp.exp_id = chipProcessedImfile.exp_id
+    AND chipRun.state = 'new'
+    AND chipProcessedImfile.fault != 0
+    AND chipRun.exp_id = %d
+    AND chipRun.label = '%s'
+""" % (self.get_exposure_id(self.exposure_name), self.label)
+        print "Reverting with %s" % query
+        self.multiple_query(query)
+        self.build_new_chips_jobs(stage_name)
+
+    def advance_chips(self):
+        """
+        Last phase in Chip:
+        * If *all* chips have failed: send an e-mail to the operator
+          and abort any further processing
+          (Matching example: MD08.photfest.20100923 o5331g0350o)
+        * Create the job that runs 'chiptool -advanceexp' for the exposure
+        """
+        query = """
+SELECT class_id
+FROM chipProcessedImfile JOIN chipRun USING(chip_id) JOIN rawExp ON rawExp.exp_id = chipRun.exp_id
+WHERE chipRun.state = 'new'
+    AND chipProcessedImfile.fault != 0
+    AND chipRun.exp_id = %d
+    AND chipRun.label = '%s'""" % (self.get_exposure_id(self.exposure_name), self.label)
+        chips = self.multiple_query(query)
+        if len(chips) != 0:
+            # Send e-mail to recipient
+            body = ChipConstants.email_body_failed_revert.replace('@COUNT_FAILED_CHIPS@',
+                                                                  len(chips))
+            body = body.replace('@LABEL@', self.label)
+            body = body.replace('@EXPOSURE_NAME@', self.exposure_name)
+            query = 'SELECT exp_name, chip_id, class_id, chipProcessedImfile.fault, path_base'
+            query = '%s FROM chipProcessedImfile JOIN chipRun USING(chip_id) JOIN rawExp' % query
+            query = "%s ON chipRun.exp_id = rawExp.exp_id " % query
+            query = "%s WHERE chipRun.label = '%s'" % (query, self.label)
+            query = "%s  AND exp_name = '%s' " % (query, self.exposure_name)
+            query = "%s AND chipProcessedImfile.fault!=0" % (query)
+            rows = self.multiple_query(query)
+            updateprocessedimfile = ''
+            for row in rows:
+                chip_id = row[1]
+                class_id = row[2]
+                updateprocessedimfile = "%s\tchiptool -dbname gpc1 -updateprocessedimfile" % updateprocessedimfile
+                updateprocessedimfile = "%s -fault 0 -set_quality 42" % updateprocessedimfile
+                updateprocessedimfile = "%s -chip_id %s -class_id %s\n" % (updateprocessedimfile, 
+                                                                           chip_id, 
+                                                                           class_id)
+            body = body.replace('@CHIPTOOL_UPDATEPROCESSEDIMFILE@', updateprocessedimfile)
+            self.send_email('Chip stage failed for label %s / exposure %s)' % (self.label, 
+                                                                               self.exposure_name), 
+                            body)
+            print 'EMail sent to [%s]' % self.recipient
+            raise IppException('Chip stage failed for label %s / exposure %s)' % (self.label, self.exposure_name))
+        # It seems that sometimes we create more than one entry in
+        # camRun. Maybe chiptool is not thread-safe
+        query = "SELECT count(*) FROM camRun JOIN chipRun USING(chip_id)"
+        query = "%s WHERE chipRun.label='%s' AND chipRun.chip_id = %d" % (query,
+                                                                          self.label, 
+                                                                          self.get_chip_id(self.exposure_name, 
+                                                                                           self.label))
+        cam_count = int(self.simple_query(query)[0])
+        if (cam_count != 0):
+            body = 'Error: More than one entry in camRun'
+            body = '%s for chip_id %d' % (body,
+                                          self.get_chip_id(self.exposure_name, 
+                                                           self.label))
+            self.send_email('Cam entries for %s: %d' % (self.exposure_name, cam_count), 
+                            body)
+            raise IppException('Invalid cam entries count for chip %d' 
+                               % self.get_chip_id(self.exposure_name, 
+                                                  self.label) )
+        # Resume to normal case...
+        jobs_filename = '%s/A050.jobs' % (self.chip_directory)
+        print "Generating Condor jobs file [%s] for processing of %d chips (stage A050)" % (jobs_filename, len(chips))
+        output = open(jobs_filename, 'w')
+        output.write('Executable = %s/encapsulate_chip.sh\n' % GlobalConstants.IPPDOR_BINARY_PATH)
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/%s.%s.chip_A050.out\n' % (self.nfs_chip_directory,
+                                                            self.label, self.exposure_name))
+        output.write('Error = %s/%s.%s.chip_A050.err\n' % (self.nfs_chip_directory,
+                                                           self.label, self.exposure_name))
+        output.write('Log = %s/%s.%s.chip_A050.log\n' % (self.chip_directory,
+                                                        self.label, self.exposure_name))
+        output.write('Requirements = %s"\n' % ChipConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        arguments = '%s -advanceexp -chip_id %d -label %s' % (GlobalConstants.CHIPTOOL,
+                                                              self.get_chip_id(self.exposure_name, 
+                                                                               self.label), 
+                                                              self.label)
+        output.write('Arguments = %s\n' % arguments)
+        output.write('Queue\n')
+        output.write('\n')
+        output.close()
+
+#################################################################
+if __name__ == '__main__':
+    import sys
+    print '<Error>: Not intended to be used as an independent program'
+    print '  Use %s' % ChipManager.SCRIPT_NAME
+    sys.exit(1)
Index: /trunk/ippdor/src/ipp/Constants.py
===================================================================
--- /trunk/ippdor/src/ipp/Constants.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/Constants.py	(revision 32874)
@@ -0,0 +1,130 @@
+"""
+This is the file in which all constants relative to the Conodr-IPP
+framework are defined. It defines the following data members
+(according to the hostname value):
+
+- LOCAL_TMP_DIR
+- NFS_TMP_DIR
+- IPP_BIN_PATH
+-
+"""
+
+from ipp.helpers import hostname
+from ipp.IppException import IppException
+
+class Constants:
+    """
+    This class is not intended to be used as a class instance.
+    All its members are static.
+    """
+    def __init__(self):
+        raise IppException('Not intended to be used as a class instance')
+
+    if hostname().startswith('ipp'): # Production cluster
+        ippdorBinaryPath = '/home/panstarrs/ippdor/local/bin'
+        LOCAL_TMP_DIR    = '/export/%s.0/ippdor/processing' % hostname()
+        NFS_TMP_DIR    = '/home/panstarrs/ippdor/processing'
+        IPP_BIN_PATH = '/home/panstarrs/ippdor/psconfig/20110920/current.lin64/bin'
+    else: # Development
+        ippdorBinaryPath = '~/local/bin'
+        LOCAL_TMP_DIR    = '/tmp/ippdor_processing'
+        NFS_TMP_DIR    = '~/ippdor_processing'
+        IPP_BIN_PATH = '~/ipp/default.linux'
+
+    # Global requirements
+    REQUIREMENTS     = 'LoadAvg < 0.3'
+
+    # Condor scripts
+    GENERATE_EXPOSURE_DAG = '%s/generate_exposure_dag.py %s %s %s' % (ippdorBinaryPath, '%s', '%s', '%s')
+
+    # IPP executables
+    CHIPTOOL = '%s/chiptool -dbname gpc1' % IPP_BIN_PATH
+    CHIPIMFILE = '%s/chip_imfile.pl --verbose --dbname gpc1 --redirect-output --threads 1' % IPP_BIN_PATH
+    CAMTOOL = '%s/camtool' % IPP_BIN_PATH
+    CAMEXP = '%s/camera_exp.pl --redirect-output --verbose --dbname gpc1' % IPP_BIN_PATH
+    FAKETOOL = '%s/faketool' % IPP_BIN_PATH
+    WARPTOOL = '%s/warptool' % IPP_BIN_PATH
+    STACKTOOL = '%s/stacktool' % IPP_BIN_PATH
+
+    class Chip:
+        """
+        Class where all chip stage constants are defined
+        """
+        def __init__(self):
+            raise IppException('Not intended to be used as a class instance')
+        REQUIREMENTS = 'LoadAvg < 0.3'
+        email_subject = ''
+        email_body = """Hi,
+
+this is an e-mail generated by the ippdor subsystem.
+
+@COUNT_FAILED_CHIPS@ chips have a fault after 3 revert attempts.
+
+You can identify them by running the query:
+SELECT exp_name, chip_id, class_id, chipProcessedImfile.fault, path_base FROM chipProcessedImfile JOIN chipRun USING(chip_id) JOIN rawExp ON chipRun.exp_id = rawExp.exp_id WHERE chipRun.label = '@LABEL@' AND exp_name = '@EXPOSURE_NAME@' AND chipProcessedImfile.fault!=0;
+
+You can have a look at the processing logs:
+@LIST_PROCESSING_LOGS@
+
+Please fix those faults.
+
+Note that some people will use:
+@CHIPTOOL_UPDATEPROCESSEDIMFILE@
+and then:
+@CHIPTOOL_REVERTPROCESSEDIMFILE@
+
+Once you have FIXED ALL PROBLEMS FOR ALL EXPOSURES, on the host where the Condor task was submitted, submit the DAG (the host name is shown in the following line) again:
+condor_submit_dag -maxidle 1000 -maxjobs 500 %s/%s/ch2st.dag
+
+Thanks,
+
+"""
+
+    class Camera:
+        """
+        Class where all cam stage constants are defined
+        """
+        def __init__(self):
+            raise IppException('Not intended to be used as a class instance')
+
+        REQUIREMENTS = 'LoadAvg < 0.3'
+        email_body_multiple_cam_entries = """Hi,
+
+this is an e-mail generated by the ippdor subsystem.
+
+There is more than one entry for the camera stage for 
+label %s / exposure %s.
+
+I will only process the entry with cam_id = %d and I strongly
+suggest to delete the other entries in the camRun table.
+
+Thanks,
+"""
+        email_body_revert_failure = """Hi,
+
+this is an e-mail generated by the ippdor subsystem.
+
+exposure %s has a fault at camera stage (after 3 revert attempts).
+
+You could identify where the problem comes from by running the query:
+
+SELECT exp_name, rawExp.exp_id, chipRun.chip_id, camRun.cam_id, camRun.state, camProcessedExp.fault, camProcessedExp.quality, camProcessedExp.path_base FROM camRun JOIN camProcessedExp USING(cam_id) JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) WHERE camRun.label = '%s' AND exp_name='%s';
+
+Thanks,
+"""
+
+    class Fake:
+        """
+        Class where all fake stage constants are defined
+        """
+        def __init__(self):
+            raise IppException('Not intended to be used as a class instance')
+        REQUIREMENTS = 'LoadAvg < 0.3'
+
+    class Warp:
+        """
+        Class where all cam warp constants are defined
+        """
+        def __init__(self):
+            raise IppException('Not intended to be used as a class instance')
+        REQUIREMENTS = 'LoadAvg < 0.3'
Index: /trunk/ippdor/src/ipp/FakeManager.py
===================================================================
--- /trunk/ippdor/src/ipp/FakeManager.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/FakeManager.py	(revision 32874)
@@ -0,0 +1,199 @@
+"""
+Generate the full fake DAG for one exposure
+Generate the jobs for fake stage
+"""
+from ipp.Gpc1Manager import Gpc1Manager as Gpc1Manager
+from ipp.helpers import mkdir
+from ipp.constants.Globals import Globals as GlobalConstants
+from ipp.constants.Fake import Fake as FakeConstants
+
+#################################################################
+class FakeManager(Gpc1Manager):
+    """
+    Fake stage implementation
+    """
+    SCRIPT_NAME = '/home/panstarrs/ippdor/local/bin/generate_fake_jobs.py'
+
+    def __init__(self,
+                 label,
+                 exposure_name,
+                 recipient,
+                 host='ippdb01.ifa.hawaii.edu', 
+                 user='ipp', 
+                 passwd='ipp'):
+        """
+        Creates a connector to the gpc1 database as well as local and
+        nfs directories (their name is fake)
+        """
+        Gpc1Manager.__init__(self, recipient=recipient, host=host, user=user, passwd=passwd)
+        self.label = label
+        self.exposure_name = exposure_name
+        self.fake_directory = '%s/%s/%s/fake' % (GlobalConstants.LOCAL_TMP_DIR, 
+                                                label, 
+                                                exposure_name)
+        mkdir(self.fake_directory)
+
+    def build_new_fake_jobs(self, stage_name = 'C010'):
+        """Build the jobs file for the C010 node
+        """
+        query = """
+SELECT
+    chipProcessedImfile.exp_id,
+    fakeRun.fake_id,
+    chipProcessedImfile.class_id,
+    chipProcessedImfile.path_base,
+    camProcessedExp.path_base,
+    rawExp.camera,
+    fakeRun.reduction,
+    fakeRun.dvodb,
+    fakeRun.workdir
+FROM fakeRun
+JOIN camRun USING(cam_id)
+JOIN camProcessedExp USING(cam_id)
+JOIN chipRun USING(chip_id)
+JOIN chipProcessedImfile USING(chip_id,exp_id)
+JOIN rawExp USING(exp_id)
+--    ON chipProcessedImfile.exp_id = rawExp.exp_id
+LEFT JOIN fakeProcessedImfile
+    ON fakeRun.fake_id = fakeProcessedImfile.fake_id
+    AND chipProcessedImfile.class_id = fakeProcessedImfile.class_id
+LEFT JOIN fakeMask
+    ON fakeRun.label = fakeMask.label
+WHERE
+    ((fakeRun.state = 'new'
+        AND fakeProcessedImfile.fake_id IS NULL
+        AND fakeProcessedImfile.class_id IS NULL
+    )
+    OR (fakeRun.state = 'update'
+        AND fakeProcessedImfile.data_state = 'cleaned')
+    )
+    AND fakeMask.label IS NULL
+    AND fakeRun.label='%s'
+    AND rawExp.exp_id = %d
+""" % (self.label, self.get_exposure_id(self.exposure_name))
+        fakes = self.multiple_query(query)
+        jobs_filename = '%s/%s.jobs' % (self.fake_directory, stage_name)
+        print "Generating Condor jobs file [%s] for processing of %d fake (stage %s)" % (jobs_filename, 
+                                                                                         len(fakes), 
+                                                                                         stage_name)
+        output = open(jobs_filename, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_fake.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = /home/panstarrs/ippdor/tmp/02test/out.%s.%s.fake_%s\n' % (self.label, 
+                                                                                         self.exposure_name, 
+                                                                                         stage_name))
+        output.write('Error = /home/panstarrs/ippdor/tmp/02test/err.%s.%s.fake_%s\n' % (self.label, 
+                                                                                        self.exposure_name, 
+                                                                                        stage_name))
+        output.write('Log = /tmp/%s_fake_%s.log\n' % (self.exposure_name, stage_name))
+        output.write('Requirements = %s\n' % FakeConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        for fake in fakes:
+            (exp_id, fake_id, class_id, chip_pathbase, cam_pathbase, camera, reduction, dvodb, workdir) = fake
+            outroot = '%s/%s/%s.fk.%d' % (workdir, exp_id, exp_id, fake_id)
+            arguments = 'Arguments = %s' % GlobalConstants.FAKE_IMFILE
+            arguments = '%s --exp_id %d' % (arguments, exp_id)
+            arguments = '%s --fake_id %d' % (arguments, fake_id)
+            arguments = '%s --class_id %s' % (arguments, class_id)
+            arguments = '%s --chiproot=%s' % (arguments, chip_pathbase)
+            arguments = '%s --camroot=%s' % (arguments, cam_pathbase)
+            arguments = '%s --camera %s' % (arguments, camera)
+            arguments = '%s --outroot %s' % (arguments, outroot)
+            if reduction is not None and reduction != 'NULL':
+                arguments = '%s --reduction %s' % (arguments, reduction)
+            if dvodb is not None and dvodb != 'NULL':
+                print 'dvodb is defined but I won\'t use it'
+            #     arguments = '%s --dvodb %s' % (arguments, dvodb)
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        if len(fakes) == 0:
+            arguments = "Arguments = echo Nothing to do for stage %s" % stage_name
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        output.close()
+
+    def build_fake_revert_jobs(self, stage_name):
+        """Revert faulty fakes if any
+        """
+        query = """
+DELETE FROM fakeProcessedImfile
+USING fakeProcessedImfile, fakeRun, camRun, chipRun, rawExp
+WHERE
+    fakeProcessedImfile.fake_id = fakeRun.fake_id
+    AND fakeRun.cam_id = camRun.cam_id
+    AND camRun.chip_id = chipRun.chip_id
+    AND chipRun.exp_id = rawExp.exp_id
+    AND fakeProcessedImfile.fault != 0
+    AND fakeRun.label = '%s'
+    AND chipRun.exp_id = %d
+""" % (self.label, self.get_exposure_id(self.exposure_name))
+        self.multiple_query(query)
+        self.build_new_fake_jobs(stage_name)
+
+    def advance_fake(self):
+        """Last phase (C050) in fake stage:
+        * 
+        """
+        jobs_filename = '%s/C050.jobs' % (self.fake_directory)
+        print "Generating Condor jobs file [%s] for processing of a fake (stage C050)" % (jobs_filename)
+        output = open(jobs_filename, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_fake.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = /home/panstarrs/ippdor/tmp/02test/out.%s.%s.fake_C050\n' % (self.label, 
+                                                                                           self.exposure_name))
+        output.write('Error = /home/panstarrs/ippdor/tmp/02test/err.%s.%s.fake_C050\n' % (self.label, 
+                                                                                          self.exposure_name))
+        output.write('Log = /tmp/%s_fake_C050.log\n' % self.exposure_name)
+        output.write('Requirements = %s\n' % FakeConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        arguments = '%s -advanceexp' % GlobalConstants.FAKETOOL
+        arguments = '%s -fake_id %d -label %s' % (arguments,
+                                                  self.get_fake_id(self.exposure_name, 
+                                                                   self.label), 
+                                                  self.label)
+        output.write('Arguments = %s\n' % arguments)
+        output.write('Queue\n')
+        output.write('\n')
+        output.close()
+
+    def build_fake_dag(self):
+        """Build the full DAG file for the fake (aka C) stage
+        """
+        dag_name = '%s/fake.dag' % (self.fake_directory)
+        output = open(dag_name, 'w')
+        output.write('# Generated DAG\n')
+        output.write('JOB\tC010\t%s/C010.jobs\n' % self.fake_directory)
+        output.write('SCRIPT POST C010 %s C020 %s %s\n' % (FakeManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tC020\t%s/C020.jobs\n' % self.fake_directory)
+        output.write('SCRIPT POST C020 %s C030 %s %s\n' % (FakeManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tC030\t%s/C030.jobs\n' % self.fake_directory)
+        output.write('SCRIPT POST C030 %s C040 %s %s\n' % (FakeManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tC040\t%s/C040.jobs\n' % self.fake_directory)
+        output.write('SCRIPT POST C040 %s C050 %s %s\n' % (FakeManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tC050\t%s/C050.jobs\n' % self.fake_directory)
+        output.write('\n')
+        output.write('PARENT C010 CHILD C020\n')
+        output.write('PARENT C020 CHILD C030\n')
+        output.write('PARENT C030 CHILD C040\n')
+        output.write('PARENT C040 CHILD C050\n')
+        output.write('\n')
+        output.close()
+        return dag_name
+
+#################################################################
+if __name__ == '__main__':
+    import sys
+    print '<Error>: Use %s' % FakeManager.SCRIPT_NAME
+    sys.exit(1)
Index: /trunk/ippdor/src/ipp/Gpc1Manager.py
===================================================================
--- /trunk/ippdor/src/ipp/Gpc1Manager.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/Gpc1Manager.py	(revision 32874)
@@ -0,0 +1,129 @@
+"""
+- Access to the Gpc1 database
+- Definition of high-level values (recipient, label...)
+"""
+import sys
+import MySQLdb
+
+#################################################################
+class Gpc1Manager:
+    """
+    Access to the Gpc1 database
+    """
+    def __init__(self,
+                 recipient = 'schastel@ifa.hawaii.edu',
+                 host = 'ippdb01.ifa.hawaii.edu', 
+                 user = 'ipp', 
+                 passwd = 'ipp'):
+        """Create a connector to the gpc1 database
+        """
+        self.recipient = recipient
+        self.connector = MySQLdb.connect(host, user, passwd, db = 'gpc1')
+        self.label = None # Set by derived classes
+
+    ##############################################################
+    def close(self):
+        """Close the connection
+        """
+        self.connector.close()
+
+    ##############################################################
+    def multiple_query(self, query):
+        """Perform a MySQL query ("multiple" possibly empty answers)
+        """
+        cursor = self.connector.cursor()
+        cursor.execute(query)
+        rows = cursor.fetchall()
+        cursor.close()
+        self.connector.commit()
+        return rows
+
+    ##############################################################
+    def simple_query(self, query):
+        """Perform a MySQL query (unique answer)
+        """
+        rows = self.multiple_query(query)
+        self.connector.commit()
+        if rows is None:
+            return None
+        if len(rows) > 1:
+            raise Exception("More than one answer to statement [%s]"
+                            % query)
+        return rows[0]
+
+    ##############################################################
+    def get_exposure_id(self, exposure_name):
+        """Get the exposure id associated to a named exposure
+        """
+        query = "SELECT exp_id FROM rawExp WHERE exp_name = '%s'" % exposure_name
+        return int(self.simple_query(query)[0])
+
+    ##############################################################
+    def get_chip_id(self, exposure_name, label = None):
+        """Get the chip id associated to a named exposure
+        """
+        query = "SELECT chip_id FROM chipRun JOIN rawExp USING(exp_id) "
+        query = "%s WHERE exp_name = '%s'" % (query, exposure_name)
+        if label is not None:
+            query = "%s AND label = '%s'" % (query, label)
+        return int(self.simple_query(query)[0])
+
+    ##############################################################
+    def get_fake_id(self, exposure_name, label = None):
+        """Get the fake id associated to a named exposure
+        """
+        query = "SELECT fake_id FROM fakeRun JOIN camRun USING(cam_id) JOIN chipRun USING(chip_id)"
+        query = "%s JOIN rawExp USING(exp_id) WHERE exp_name = '%s'" % exposure_name
+        if label is not None:
+            query = "%s AND fakeRun.label='%s'" % (query, label)
+        return int(self.simple_query(query)[0])
+
+    ##############################################################
+    def get_warp_id(self, exposure_name, label):
+        """Get the warp id associated to a named and labelled exposure
+        """
+        query = "SELECT warp_id FROM warpRun JOIN fakeRun USING(fake_id) JOIN camRun USING(cam_id)"
+        query = "%s JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id)" % query
+        query = "%s WHERE warpRun.label='%s' AND exp_name='%s'" % (query, label, exposure_name)
+        return int(self.simple_query(query)[0])
+
+    ##############################################################
+    def send_email(self, subject, body):
+        """
+        Send an e-mail to the recipient (which has been previously defined)
+        """
+        import smtplib
+        from email.mime.text import MIMEText
+        message = MIMEText(body)
+        message['Subject'] = subject
+        _from = 'ippdor@ifa.hawaii.edu'
+        message['From'] = _from
+        _to = [ self.recipient, 'schastel@ifa.hawaii.edu' ]
+        message['To'] = self.recipient
+        smtp_connection = smtplib.SMTP('ippc18.ifa.hawaii.edu')
+        smtp_connection.sendmail(_from, _to, message.as_string())
+        smtp_connection.quit()
+
+    ##############################################################
+    def is_label_used(self):
+        """
+        Check if the label is used in the chipRun table
+        """
+        query = """SELECT label from chipRun WHERE label = '%s' LIMIT 1""" % self.label
+        values = self.multiple_query(query)
+        return values is not None and len(values) != 0
+
+    ##############################################################
+    def is_exposure_name_valid(self, exposure_name):
+        """
+        Check if the exposure named exposure_name is in the rawExp table
+        """
+        query = """SELECT exp_id FROM rawExp WHERE exp_name='%s'""" % exposure_name
+        return int(self.simple_query(query)[0])
+
+#################################################################
+if __name__ == '__main__':
+    print 'Not intended to be used as an independent program'
+    GPC1 = Gpc1Manager(recipient = 'schastel@ifa.hawaii.edu')
+    GPC1.send_email("This is a test", "Condor e-mail test")
+    sys.exit(1)
Index: /trunk/ippdor/src/ipp/StackManager.py
===================================================================
--- /trunk/ippdor/src/ipp/StackManager.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/StackManager.py	(revision 32874)
@@ -0,0 +1,275 @@
+"""
+The purpose of this class is to:
+- Implement the stack.pro pantasks script tasks
+- Generate the associated DAG
+"""
+
+#
+# Generate the stack DAG for a label and (a set of exposures?)
+#
+from ipp.Gpc1Manager import Gpc1Manager as Gpc1Manager
+from ipp.helpers import mkdir
+from ipp.constants.Globals import Globals as GlobalConstants
+from ipp.constants.Stack import Stack as StackConstants
+
+#################################################################
+class StackManager(Gpc1Manager):
+    """
+    Implementation of the stack.pro pantasks script tasks
+    """
+    scriptName = '/home/panstarrs/ippdor/local/bin/generate_stack_jobs.py'
+
+    def __init__(self,
+                 label,
+                 recipient,
+                 host = 'ippdb01.ifa.hawaii.edu', 
+                 user = 'ipp', 
+                 passwd = 'ipp'):
+        """
+        Creates a connector to the gpc1 database as well as local and
+        nfs directories (their name is stack)
+        """
+        Gpc1Manager.__init__(self, 
+                             recipient=recipient, 
+                             host=host, 
+                             user=user, 
+                             passwd=passwd)
+        self.label = label
+        # Retrieve exposures names (even if they are known) and their warp_id
+        self.exposures_names = []
+        self.warp_ids = []
+        query = """
+SELECT rawExp.exp_name, warpRun.warp_id, warpRun.data_group
+FROM warpRun
+  JOIN fakeRun USING(fake_id)
+  JOIN camRun USING(cam_id)
+  JOIN chipRun USING(chip_id)
+  JOIN rawExp USING(exp_id)
+WHERE
+  warpRun.state = 'full'
+  AND warpRun.label = '%s'""" % (self.label)
+        print
+        print query
+        print
+        data = self.multiple_query(query)
+        for datum in data:
+            (exposure_name, warp_id, data_group) = datum
+            self.exposures_names.append(exposure_name)
+            self.warp_ids.append(str(warp_id))
+            self.data_group = data_group
+        self.stack_directory = '%s/%s/stack' % (GlobalConstants.LOCAL_TMP_DIR, self.label)
+        mkdir(self.stack_directory)
+
+    def build_create_stack_entries_job(self, stage_name = 'E010'):
+        """
+        Stage E010:
+        stacktool -dbname gpc1 -definebyquery -set_label ${label} 
+                  -set_data_group $data_group  -set_workdir $workdir
+                  -warp_id <w1> -warp_id <w2> [...]
+        """
+        argument_warp_ids =  '-warp_id %s' % ' -warp_id '.join(self.warp_ids)
+        jobs_filenames = '%s/%s.jobs' % (self.stack_directory, stage_name)
+        print "Generating Condor jobs file [%s] for stacking of %d exposures (stage %s)" % (jobs_filenames, 
+                                                                                            len(self.exposures_names), 
+                                                                                            stage_name)
+        output = open(jobs_filenames, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_stack.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = /home/panstarrs/ippdor/tmp/02test/out.%s.stack_%s\n' % (self.label, 
+                                                                                       stage_name))
+        output.write('Error = /home/panstarrs/ippdor/tmp/02test/err.%s.stack_%s\n' % (self.label,
+                                                                                      stage_name))
+        output.write('Log = /tmp/stack_%s.log\n' % stage_name)
+        output.write('Requirements = %s\n' % StackConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        arguments = "%s/stacktool -definebyquery -dbname gpc1" % GlobalConstants.IPP_BIN_PATH
+        arguments = "%s -set_dist_group NODIST -min_num 2" % arguments
+        arguments = "%s -set_label %s" % (arguments, self.label)
+        arguments = "%s -set_data_group %s" % (arguments, self.data_group)
+        arguments = "%s -set_workdir neb://@HOST.0@/gpc1/%s/stack" % (arguments, self.label)
+        arguments = "%s %s -min_num 2 " % (arguments, argument_warp_ids)
+        output.write('Arguments = %s\n' % arguments)
+        output.write('Queue\n')
+        output.write('\n')
+        output.close()
+
+    def build_new_stack_jobs(self, stage_name = 'E020'):
+        """Equivalent of stacktool -tosum
+        """
+        query = """
+SELECT
+    stackRun.stack_id,
+    stackRun.tess_id,
+    stackRun.skycell_id,
+    stackRun.workdir,
+    stackRun.reduction,
+    stackRun.state,
+    stackSumSkyfile.path_base
+FROM stackRun
+JOIN stackInputSkyfile USING(stack_id)
+JOIN warpRun USING(warp_id)
+LEFT JOIN stackSumSkyfile USING(stack_id)
+LEFT JOIN Label ON Label.label = stackRun.label
+WHERE
+    ((stackRun.state = 'new' AND stackSumSkyfile.stack_id IS NULL)
+    OR (stackRun.state = 'update' AND stackSumSkyfile.fault = 0 AND stackSumSkyfile.quality = 0))
+    AND (Label.active OR Label.active IS NULL)
+    AND warpRun.label='%s'
+GROUP BY stack_id
+HAVING SUM(IF(warpRun.state = 'full', 1, 0)) = COUNT(stackInputSkyfile.warp_id)
+""" % (self.label)
+        skycells_to_be_processed = self.multiple_query(query)
+        print 'Got %d jobs to generate' % len(skycells_to_be_processed)
+        jobs_filenames = '%s/%s.jobs' % (self.stack_directory, stage_name)
+        message = 'Generating Condor jobs file [%s] ' % (jobs_filenames)
+        message = '%s for stacking of %d skycells (stage %s)' % (message,
+                                                                 len(skycells_to_be_processed), 
+                                                                 stage_name)
+        print message
+        output = open(jobs_filenames, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_stack.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = /home/panstarrs/ippdor/tmp/02test/out.%s.stack_%s\n' % (self.label, stage_name))
+        output.write('Error = /home/panstarrs/ippdor/tmp/02test/err.%s.stack_%s\n' % (self.label, stage_name))
+        output.write('Log = /tmp/stack_%s.log\n' % stage_name)
+        output.write('Requirements = %s\n' % StackConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        for skycell_info in skycells_to_be_processed:
+            (stack_id, tess_id, skycell_id, workdir, reduction, state, path_base) = skycell_info
+            outroot = '%s/%s/%s/%s.%s.stk.%d' % (workdir, tess_id, skycell_id, tess_id, skycell_id, stack_id)
+            arguments = "Arguments = %s/stack_skycell.pl --dbname gpc1 --threads 1 " % GlobalConstants.IPP_BIN_PATH
+            arguments = "%s --stack_id %d" % (arguments, stack_id)
+            arguments = "%s --outroot %s --redirect-output " % (arguments, outroot)
+            arguments = "%s --run-state %s" % (arguments, state)
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+            if reduction is not None:
+                print 'reduction defined (%s) but not used' % reduction
+            if path_base is not None:
+                print 'path_base defined (%s) but not used' % path_base
+        if len(skycells_to_be_processed) == 0:
+            arguments = "Arguments = echo No skycell to be processed for label [%s]" % self.label
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        output.close()
+        return
+
+    def build_revert_stack_jobs(self, stage_name):
+        """-revertsumskyfile
+        """
+        query = """
+DELETE FROM stackSumSkyfile
+USING stackSumSkyfile, stackRun
+WHERE stackSumSkyfile.stack_id = stackRun.stack_id
+    AND stackRun.state = 'new'
+    AND fault != 0
+    AND stackRun.label = '%s'
+""" % (self.label)
+        self.multiple_query(query)
+        self.build_new_stack_jobs(stage_name)
+        return
+
+    def build_stack_summary_jobs(self):
+        """E060:
+        skycell_jpeg.pl --stage stack --stage_id $SASS_ID --camera $CAMERA --outroot $outroot
+        """
+        query = """
+SELECT DISTINCT sass_id,rawExp.camera,stackRun.workdir,stackRun.tess_id,stackRun.state
+       FROM stackRun
+       JOIN stackInputSkyfile ON stackRun.stack_id = stackInputSkyfile.stack_id
+       JOIN stackAssociationMap ON stackRun.stack_id = stackAssociationMap.stack_id
+       JOIN stackAssociation USING(sass_id)
+       JOIN warpRun USING(warp_id)
+       JOIN fakeRun USING(fake_id)
+       JOIN camRun USING(cam_id)
+       JOIN chipRun USING(chip_id)
+       JOIN rawExp USING(exp_id)
+       LEFT JOIN stackSummary USING(sass_id)
+WHERE stackRun.state = 'full' AND
+      stackSummary.projection_cell IS NULL AND
+      stackRun.label = '%s'
+""" % (self.label)
+        entries = self.multiple_query(query)
+        print 'Got %d jobs to generate' % len(entries)
+        stage_name = 'E060'
+        jobs_filenames = '%s/%s.jobs' % (self.stack_directory, stage_name)
+        print "Generating Condor jobs file [%s] for jpeging of %d skycells (stage %s)" % (jobs_filenames, 
+                                                                                          len(entries), 
+                                                                                          stage_name)
+        output = open(jobs_filenames, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_stack.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = /home/panstarrs/ippdor/tmp/02test/out.%s.stack_%s\n' % (self.label, stage_name))
+        output.write('Error = /home/panstarrs/ippdor/tmp/02test/err.%s.stack_%s\n' % (self.label, stage_name))
+        output.write('Log = /tmp/stack_%s.log\n' % stage_name)
+        output.write('Requirements = %s\n' % StackConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        for entry in entries:
+            (sass_id, camera, workdir, tess_id, state) = entry
+            outroot = '%s/%s/%s.stk.%d.summary' % (workdir, tess_id, tess_id, sass_id)
+            arguments = 'Arguments = skycell_jpeg.pl --stage stack '
+            arguments = '%s --stage_id %d --camera %s --outroot %s' % (arguments,
+                                                                       sass_id,
+                                                                       camera, 
+                                                                       outroot)
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+            if state is not None:
+                print 'state defined (%s) but not used' % state
+        if len(entries) == 0:
+            arguments = "Arguments = echo No skycell to be jpegged for label [%s]" % self.label
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        output.close()
+        return
+
+    def build_dag(self):
+        """
+        DAG construction
+        """
+        dag_name = '%s/stack.dag' % (self.stack_directory)
+        print "Generating Stack DAG file [%s] for label %s" % (dag_name,
+                                                               self.label)
+        output = open(dag_name, 'w')
+        output.write('# Generated DAG\n')
+        output.write('\n')
+        output.write('JOB\tE010\t%s/E010.jobs\n' % self.stack_directory)
+        output.write('SCRIPT POST E010 %s E020 %s %s\n' % (StackManager.scriptName, self.label, self.recipient))
+        output.write('JOB\tE020\t%s/E020.jobs\n' % self.stack_directory)
+        output.write('SCRIPT POST E020 %s E030 %s %s\n' % (StackManager.scriptName, self.label, self.recipient))
+        output.write('JOB\tE030\t%s/E030.jobs\n' % self.stack_directory)
+        output.write('SCRIPT POST E030 %s E040 %s %s\n' % (StackManager.scriptName, self.label, self.recipient))
+        output.write('JOB\tE040\t%s/E040.jobs\n' % self.stack_directory)
+        output.write('SCRIPT POST E040 %s E050 %s %s\n' % (StackManager.scriptName, self.label, self.recipient))
+        output.write('JOB\tE050\t%s/E050.jobs\n' % self.stack_directory)
+        output.write('SCRIPT POST E050 %s E060 %s %s\n' % (StackManager.scriptName, self.label, self.recipient))
+        output.write('JOB\tE060\t%s/E060.jobs\n' % self.stack_directory)
+        output.write('\n')
+        output.write('PARENT E010 CHILD E020\n')
+        output.write('PARENT E020 CHILD E030\n')
+        output.write('PARENT E030 CHILD E040\n')
+        output.write('PARENT E040 CHILD E050\n')
+        output.write('PARENT E050 CHILD E060\n')
+        output.write('\n')
+        output.close()
+        return dag_name
+
+#################################################################
+if __name__ == '__main__':
+    import sys
+    print '<Error>: Not intended to be used as an independent program'
+    print 'Use %s' % StackManager.scriptName
+    sys.exit(1)
Index: /trunk/ippdor/src/ipp/WarpManager.py
===================================================================
--- /trunk/ippdor/src/ipp/WarpManager.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/WarpManager.py	(revision 32874)
@@ -0,0 +1,391 @@
+"""
+Generate the full warp DAG for one exposure
+Generate the condor job files for the warp substages
+"""
+from ipp.Gpc1Manager import Gpc1Manager as Gpc1Manager
+from ipp.helpers import mkdir
+from ipp.constants.Globals import Globals as GlobalConstants
+from ipp.constants.Warp import Warp as WarpConstants
+
+#################################################################
+class WarpManager(Gpc1Manager):
+    """
+    Warp stage in Condor
+    """
+    SCRIPT_NAME = '/home/panstarrs/ippdor/local/bin/generate_warp_jobs.py'
+
+    def __init__(self,
+                 label,
+                 exposure_name,
+                 recipient,
+                 host = 'ippdb01.ifa.hawaii.edu', 
+                 user = 'ipp', 
+                 passwd = 'ipp'):
+        """
+        Creates a connector to the gpc1 database as well as local and
+        nfs directories (their name is warp)
+        """
+        Gpc1Manager.__init__(self, recipient=recipient, host=host, user=user, passwd=passwd)
+        self.label = label
+        self.exposure_name = exposure_name
+        self.nfs_warp_directory = '%s' % GlobalConstants.NFS_TMP_DIR
+        self.warp_directory = '%s/%s/%s/warp' % (GlobalConstants.LOCAL_TMP_DIR, 
+                                                label, exposure_name)
+        mkdir(self.warp_directory)
+
+    def build_new_exp_warp_jobs(self, stage_name = 'D010'):
+        """Equivalent of warptool -tooverlap and warptool -revert
+        """
+        query = """
+SELECT
+    warpRun.warp_id,
+    warpRun.workdir,
+    warpRun.tess_id,
+    rawExp.camera,
+    exp_tag
+FROM warpRun
+JOIN fakeRun
+    USING(fake_id)
+JOIN camRun
+    USING(cam_id)
+JOIN chipRun
+    USING(chip_id)
+JOIN rawExp
+    USING(exp_id)
+LEFT JOIN warpSkyCellMap
+    USING(warp_id)
+LEFT JOIN warpMask
+    ON warpRun.label = warpMask.label
+WHERE
+    warpRun.state = 'new'
+    AND fakeRun.state = 'full'
+    AND camRun.state = 'full'
+    AND chipRun.state = 'full'
+    AND warpSkyCellMap.warp_id IS NULL
+    AND warpMask.label IS NULL
+    AND warpRun.label = '%s'
+    AND exp_name = '%s'
+""" % (self.label, self.exposure_name)
+        warps = self.multiple_query(query)
+        jobs_filename = '%s/%s.jobs' % (self.warp_directory, stage_name)
+        message = 'Generating Condor jobs file [%s] for processing' % jobs_filename
+        message = "%s %d warps (stage %s)" % (message, len(warps), stage_name)
+        print message
+        output = open(jobs_filename, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_warp.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/out.%s.%s.warp_%s\n' % (self.nfs_warp_directory,
+                                                          self.label, 
+                                                          self.exposure_name, 
+                                                          stage_name))
+        output.write('Error = %s/err.%s.%s.warp_%s\n' % (self.nfs_warp_directory,
+                                                         self.label, 
+                                                         self.exposure_name, 
+                                                         stage_name))
+        output.write('Log = /tmp/%s_warp_%s.log\n' % (self.exposure_name, stage_name))
+        output.write('Requirements = %s\n' % WarpConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        for warp in warps:
+            (warp_id, workdir, tess_dir, camera, exp_tag) = warp
+            outlogfile = "%s/%s/%s.wrp.%s.log" % (workdir, exp_tag, exp_tag, warp_id)
+            arguments = "Arguments = %s --warp_id %d" % (GlobalConstants.WARP_OVERLAP, warp_id)
+            arguments = "%s --camera %s --tess_dir %s --logfile %s" % (arguments, camera, 
+                                                                       tess_dir, outlogfile)
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        if len(warps) == 0:
+            arguments = "Arguments = echo Nothing to do for stage %s" % stage_name
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        output.close()
+
+    def build_warp_exp_revert_jobs(self, stage_name):
+        """revert the -overlap exp stage
+        """
+        query = """
+DELETE FROM warpSkyCellMap
+USING warpSkyCellMap, warpRun, fakeRun, camRun, chipRun, rawExp
+WHERE warpSkyCellMap.warp_id = warpRun.warp_id
+    AND warpRun.fake_id = fakeRun.fake_id
+    AND fakeRun.cam_id = camRun.cam_id
+    AND camRun.chip_id = chipRun.chip_id
+    AND chipRun.exp_id = rawExp.exp_id
+    AND warpRun.state = 'new'
+    AND warpSkyCellMap.fault != 0
+    AND warpRun.label = '%s'
+    AND rawExp.exp_name = '%s'
+""" % (self.label, self.exposure_name)
+        self.multiple_query(query)
+        self.build_new_exp_warp_jobs(stage_name)
+
+    def build_new_skycell_warp_jobs(self, stage_name = 'D110'):
+        """Equivalent of warp.skycell.[load|run|revert] (warptool -towarp)
+        Dropped the label part. We are not in the general case
+        """
+        query = """
+SELECT DISTINCT
+        warpSkyCellMap.warp_id,
+        warpImfile.warp_skyfile_id,
+        warpSkyCellMap.skycell_id,
+        warpSkyCellMap.tess_id,
+        rawExp.camera,
+        warpRun.workdir,
+        rawExp.exp_tag,
+        warpRun.reduction,
+        chipRun.magicked,
+        warpRun.state
+    FROM warpRun
+    JOIN warpSkyCellMap
+        USING(warp_id)
+    JOIN warpImfile
+        ON warpSkyCellMap.warp_id = warpImfile.warp_id
+        AND warpSkyCellMap.skycell_id = warpImfile.skycell_id
+    JOIN fakeRun
+        USING(fake_id)
+    JOIN camRun
+        USING(cam_id)
+    JOIN chipRun
+        USING(chip_id)
+    JOIN chipProcessedImfile
+        USING(chip_id)
+    JOIN rawExp
+        ON chipRun.exp_id = rawExp.exp_id
+    LEFT JOIN warpSkyfile
+        ON warpRun.warp_id = warpSkyfile.warp_id
+        AND warpSkyCellMap.skycell_id = warpSkyfile.skycell_id
+        AND warpSkyCellMap.tess_id = warpSkyfile.tess_id
+    LEFT JOIN warpMask
+        ON warpRun.label = warpMask.label
+    LEFT JOIN Label ON warpRun.label = Label.label
+    WHERE
+        warpRun.state = 'new'
+        AND warpSkyfile.warp_id IS NULL
+        AND warpSkyfile.skycell_id IS NULL
+        AND warpSkyfile.tess_id IS NULL
+        AND fakeRun.state = 'full'
+        AND camRun.state = 'full'
+        AND chipRun.state = 'full'
+        AND warpMask.label IS NULL
+        AND warpSkyCellMap.fault = 0
+        AND (Label.active OR Label.active IS NULL)
+        AND warpRun.label = '%s'
+        AND rawExp.exp_name = '%s'
+""" % (self.label, self.exposure_name)
+        warps = self.multiple_query(query)
+        jobs_filename = '%s/%s.jobs' % (self.warp_directory, stage_name)
+        message ="Generating Condor jobs file [%s] for processing" % jobs_filename
+        message = "%s of %d warps (stage %s)" % (message, len(warps), stage_name)
+        print message
+        output = open(jobs_filename, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_warp.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/out.%s.%s.warp_%s\n' % (self.nfs_warp_directory,
+                                                          self.label, 
+                                                          self.exposure_name, 
+                                                          stage_name))
+        output.write('Error = %s/err.%s.%s.warp_%s\n' % (self.nfs_warp_directory,
+                                                         self.label, 
+                                                         self.exposure_name, 
+                                                         stage_name))
+        output.write('Log = %s/%s_warp_%s.log\n' % (self.warp_directory,
+                                                    self.exposure_name, stage_name))
+        output.write('Requirements = %s\n' % WarpConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        for warp in warps:
+            ( warp_id, warp_skyfile_id, skycell_id, tess_dir, camera, workdir, 
+              exp_tag, reduction, raw_magicked, state ) = warp
+            outroot = '%s/%s/%s.wrp.%d.%s' % (workdir, exp_tag, exp_tag, warp_id, skycell_id)
+            arguments = "Arguments = %s" % GlobalConstants.WARP_SKYCELL
+            arguments = "%s --warp_id %d" % (arguments, warp_id)
+            arguments = "%s --warp_skyfile_id %d" % (arguments, warp_skyfile_id) 
+            arguments = "%s --skycell_id %s" % (arguments, skycell_id)
+            arguments = "%s --tess_dir %s" % (arguments, tess_dir)
+            arguments = "%s --camera %s" % (arguments,  camera)
+            arguments = "%s --outroot %s" % (arguments, outroot)
+            arguments = "%s --run-state %s" % (arguments, state)
+            if raw_magicked is not None and raw_magicked > 0:
+                arguments = '%s --magicked %d' % (arguments, raw_magicked)
+            if reduction is not None and reduction != 'NULL':
+                arguments = '%s --reduction %s' % (arguments, reduction)
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        if len(warps) == 0:
+            arguments = 'Arguments = echo Nothing to do for stage %s' % stage_name
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        output.close()
+
+    def build_warp_skycell_revert_jobs(self, stage_name):
+        """Revert stage D110
+        """
+        query = """
+DELETE FROM warpSkyfile
+USING warpSkyfile, warpRun, fakeRun, camRun, chipRun, rawExp
+WHERE warpSkyfile.warp_id = warpRun.warp_id
+    AND warpRun.fake_id = fakeRun.fake_id
+    AND fakeRun.cam_id = camRun.cam_id
+    AND camRun.chip_id = chipRun.chip_id
+    AND chipRun.exp_id = rawExp.exp_id
+    AND warpRun.state = 'new'
+    AND warpSkyfile.fault != 0
+    AND warpRun.label = '%s'
+    AND rawExp.exp_name = '%s'
+""" % (self.label, self.exposure_name)
+        self.multiple_query(query)
+        self.build_new_skycell_warp_jobs(stage_name)
+
+    def build_new_summary_warp_jobs(self):
+        """Stage D210
+        """
+        query = """
+SELECT DISTINCT warp_id,rawExp.camera,warpRun.workdir,warpRun.tess_id,rawExp.exp_tag,warpRun.state
+       FROM warpRun 
+       JOIN fakeRun USING(fake_id) JOIN camRun USING(cam_id)
+       JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id)
+       LEFT JOIN warpSummary USING(warp_id)
+WHERE warpRun.state = 'full' 
+  AND warpSummary.projection_cell IS NULL
+  AND warpRun.label = '%s'
+  AND rawExp.exp_name = '%s'
+""" % (self.label, self.exposure_name)
+        summaries = self.multiple_query(query)
+        jobs_filename = '%s/%s.jobs' % (self.warp_directory, 'D210')
+        message = "Generating Condor jobs file [%s] for processing of" % jobs_filename
+        message = "%s %d warp summaries (stage D210)" % (message, len(summaries))
+        print message
+        output = open(jobs_filename, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_warp.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/out.%s.%s.warp_D210\n' % (self.nfs_warp_directory,
+                                                            self.label, self.exposure_name))
+        output.write('Error = %s/err.%s.%s.warp_D210\n' % (self.nfs_warp_directory,
+                                                           self.label, self.exposure_name))
+        output.write('Log = %s/%s_warp_D210.log\n' % (self.warp_directory,
+                                                      self.exposure_name))
+        output.write('Requirements = %s\n' % WarpConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        for summary in summaries:
+            (warp_id, camera, workdir, tess_id, exp_tag, state) = summary
+            outroot = '%s/%s/%s.wrp.%d.%s' % (workdir, exp_tag, exp_tag, warp_id, tess_id)
+            arguments = 'Arguments = %s -stage_id %d -camera %s -outroot %s' % (GlobalConstants.WARP_JPEG,
+                                                                                warp_id, camera, outroot)
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+            if state is not None:
+                print 'state is defined but not used: %s' % state
+        if len(summaries) == 0:
+            arguments = 'Arguments = echo Nothing to do for stage D210'
+            output.write('%s\n' % arguments)
+            output.write('Queue\n')
+            output.write('\n')
+        output.close()
+
+    def advance_warp(self):
+        """Stage 310 (last). Create the job to 'warptool -advancerun -warp_id -label'
+        """
+        jobs_filename = '%s/%s.jobs' % (self.warp_directory, 'D310')
+        print "Generating Condor jobs file [%s] for advancing of warp related to %s (stage D310)" % (jobs_filename, 
+                                                                                                     self.exposure_name)
+        output = open(jobs_filename, 'w')
+        output.write('Executable = /home/panstarrs/ippdor/local/bin/encapsulate_warp.sh\n')
+        output.write('Universe = vanilla\n')
+        output.write('Output = %s/out.%s.%s.warp_D210\n' % (self.nfs_warp_directory,
+                                                            self.label, self.exposure_name))
+        output.write('Error = %s/err.%s.%s.warp_D210\n' % (self.nfs_warp_directory,
+                                                           self.label, self.exposure_name))
+        output.write('Log = %s/%s_warp_D210.log\n' % (self.warp_directory,
+                                                      self.exposure_name))
+        output.write('Requirements = %s\n' % WarpConstants.REQUIREMENTS)
+        output.write('Notification = Error\n')
+        output.write('\n')
+        output.write('getenv = True\n')
+        output.write('\n')
+        arguments = '%s -advancerun -label %s -warp_id %d' % (GlobalConstants.WARPTOOL, 
+                                                              self.label, 
+                                                              self.get_warp_id(self.exposure_name, 
+                                                                               self.label))
+        output.write('Arguments = %s\n' % arguments)
+        output.write('Queue\n')
+        output.write('\n')
+        output.close()
+
+    def build_warp_dag(self):
+        """Build the warp stage DAG
+        """
+        dag_name = '%s/warp.dag' % (self.warp_directory)
+        print "Generating Warp DAG file [%s] for exposure %s (label %s)" % (dag_name,
+                                                                            self.exposure_name,
+                                                                            self.label)
+        output = open(dag_name, 'w')
+        output.write('# Generated DAG\n')
+        output.write('\n')
+        output.write('# Exp\n')
+        output.write('JOB\tD010\t%s/D010.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D010 %s D020 %s %s\n' % (WarpManager.SCRIPT_NAME,
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tD020\t%s/D020.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D020 %s D030 %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tD030\t%s/D030.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D030 %s D040 %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tD040\t%s/D040.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D040 %s D110 %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('\n')
+        output.write('# Skycell\n')
+        output.write('JOB\tD110\t%s/D110.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D110 %s D120 %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tD120\t%s/D120.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D120 %s D130 %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tD130\t%s/D130.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D130 %s D140 %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('JOB\tD140\t%s/D140.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D140 %s D210 %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('\n')
+        output.write('# Summary\n')
+        output.write('JOB\tD210\t%s/D210.jobs\n' % self.warp_directory)
+        output.write('SCRIPT POST D210 %s D310 %s %s\n' % (WarpManager.SCRIPT_NAME, 
+                                                           self.label, self.exposure_name))
+        output.write('\n')
+        output.write('# Advance\n')
+        output.write('JOB\tD310\t%s/D310.jobs\n' % self.warp_directory)
+        output.write('\n')
+        output.write('PARENT D010 CHILD D020\n')
+        output.write('PARENT D020 CHILD D030\n')
+        output.write('PARENT D030 CHILD D040\n')
+        output.write('PARENT D040 CHILD D110\n')
+        output.write('\n')
+        output.write('PARENT D110 CHILD D120\n')
+        output.write('PARENT D120 CHILD D130\n')
+        output.write('PARENT D130 CHILD D140\n')
+        output.write('PARENT D140 CHILD D210\n')
+        output.write('\n')
+        output.write('PARENT D210 CHILD D310\n')
+        output.write('\n')
+        output.close()
+        return dag_name
+
+#################################################################
+if __name__ == '__main__':
+    import sys
+    print 'Error: Use %s' % WarpManager.SCRIPT_NAME
+    sys.exit(0)
Index: /trunk/ippdor/src/ipp/__init__.py
===================================================================
--- /trunk/ippdor/src/ipp/__init__.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/__init__.py	(revision 32874)
@@ -0,0 +1,3 @@
+"""
+Classes dedicated for IPP Processing in the Condor framework
+"""
Index: /trunk/ippdor/src/ipp/constants/Camera.py
===================================================================
--- /trunk/ippdor/src/ipp/constants/Camera.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/constants/Camera.py	(revision 32874)
@@ -0,0 +1,44 @@
+"""
+This is the file in which all constants relative to the Condor-IPP
+framework are defined for the Camera stage. 
+
+It defines the following data members:
+TODO: list defined constants
+"""
+from ipp.exceptions.UninstantiableIppException import UninstantiableIppException
+from ipp.constants.Globals import Globals as GlobalConstants
+
+class Camera:
+    """
+    Class where all cam stage constants are defined
+    """
+    def __init__(self):
+        raise UninstantiableIppException()
+
+    # Change it according to new needs
+    REQUIREMENTS = GlobalConstants.REQUIREMENTS
+
+    email_body_multiple_cam_entries = """Hi,
+
+this is an e-mail generated by the ippdor subsystem.
+
+There is more than one entry for the camera stage for 
+label %s / exposure %s.
+
+I will only process the entry with cam_id = %d and I strongly
+suggest to delete the other entries in the camRun table.
+
+Thanks,
+"""
+    email_body_revert_failure = """Hi,
+
+this is an e-mail generated by the ippdor subsystem.
+
+exposure %s has a fault at camera stage (after 3 revert attempts).
+
+You could identify where the problem comes from by running the query:
+
+SELECT exp_name, rawExp.exp_id, chipRun.chip_id, camRun.cam_id, camRun.state, camProcessedExp.fault, camProcessedExp.quality, camProcessedExp.path_base FROM camRun JOIN camProcessedExp USING(cam_id) JOIN chipRun USING(chip_id) JOIN rawExp USING(exp_id) WHERE camRun.label = '%s' AND exp_name='%s';
+
+Thanks,
+"""
Index: /trunk/ippdor/src/ipp/constants/Chip.py
===================================================================
--- /trunk/ippdor/src/ipp/constants/Chip.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/constants/Chip.py	(revision 32874)
@@ -0,0 +1,44 @@
+"""
+This is the file in which all constants relative to the Condor-IPP
+framework are defined for the chip stage.
+
+It defines the following data members:
+TODO: list defined constants
+"""
+from ipp.exceptions.UninstantiableIppException import UninstantiableIppException
+
+class Chip:
+    """
+    This class is not intended to be used as a class instance.
+    All its members are static.
+    """
+    def __init__(self):
+        raise UninstantiableIppException()
+
+    REQUIREMENTS = 'LoadAvg < 0.3'
+    email_subject = ''
+    email_body_failed_revert = """Hi,
+
+this is an e-mail generated by the ippdor subsystem.
+
+@COUNT_FAILED_CHIPS@ chips have a fault after 3 revert attempts.
+
+You can identify them by running the query:
+SELECT exp_name, chip_id, class_id, chipProcessedImfile.fault, path_base FROM chipProcessedImfile JOIN chipRun USING(chip_id) JOIN rawExp ON chipRun.exp_id = rawExp.exp_id WHERE chipRun.label = '@LABEL@' AND exp_name = '@EXPOSURE_NAME@' AND chipProcessedImfile.fault!=0;
+
+You can have a look at the processing logs:
+@LIST_PROCESSING_LOGS@
+
+Please fix those faults.
+
+Note that some people will use:
+@CHIPTOOL_UPDATEPROCESSEDIMFILE@
+and then:
+@CHIPTOOL_REVERTPROCESSEDIMFILE@
+
+Once you have FIXED ALL PROBLEMS FOR ALL EXPOSURES, on the host where the Condor task was submitted, submit the DAG (the host name is shown in the following line) again:
+condor_submit_dag -maxidle 1000 -maxjobs 500 %s/%s/ch2st.dag
+
+Thanks,
+
+"""
Index: /trunk/ippdor/src/ipp/constants/ChipToWarp.py
===================================================================
--- /trunk/ippdor/src/ipp/constants/ChipToWarp.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/constants/ChipToWarp.py	(revision 32874)
@@ -0,0 +1,19 @@
+"""
+This is the file in which all constants relative to the Condor-IPP
+framework are defined for the ippdor_chip_to_warp script. 
+
+It defines the following data members:
+TODO
+"""
+from ipp.exceptions.UninstantiableIppException import UninstantiableIppException
+from ipp.constants.Globals import Globals as GlobalConstants
+
+class ChipToWarp:
+    """
+    Class where all fake stage constants are defined
+    """
+    def __init__(self):
+        raise UninstantiableIppException()
+
+    # Change it according to new needs
+    REQUIREMENTS = GlobalConstants.REQUIREMENTS
Index: /trunk/ippdor/src/ipp/constants/Fake.py
===================================================================
--- /trunk/ippdor/src/ipp/constants/Fake.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/constants/Fake.py	(revision 32874)
@@ -0,0 +1,19 @@
+"""
+This is the file in which all constants relative to the Condor-IPP
+framework are defined for the Camera stage. 
+
+It defines the following data members:
+TODO: list defined constants
+"""
+from ipp.exceptions.UninstantiableIppException import UninstantiableIppException
+from ipp.constants.Globals import Globals as GlobalConstants
+
+class Fake:
+    """
+    Class where all fake stage constants are defined
+    """
+    def __init__(self):
+        raise UninstantiableIppException()
+
+    # Change it according to new needs
+    REQUIREMENTS = GlobalConstants.REQUIREMENTS
Index: /trunk/ippdor/src/ipp/constants/Globals.py
===================================================================
--- /trunk/ippdor/src/ipp/constants/Globals.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/constants/Globals.py	(revision 32874)
@@ -0,0 +1,55 @@
+"""
+This is the file in which all constants relative to the Condor-IPP
+framework are defined. It defines the following data members
+(depending on the hostname value):
+
+- LOCAL_TMP_DIR
+- NFS_TMP_DIR
+- IPP_BIN_PATH
+-
+"""
+
+from ipp.helpers import hostname
+from ipp.exceptions.UninstantiableIppException import UninstantiableIppException
+
+class Globals:
+    """
+    This class is not intended to be used as a class instance.
+    All its members are static.
+    """
+    def __init__(self):
+        raise UninstantiableIppException()
+
+    if hostname().startswith('ipp'): # Production cluster
+        IPPDOR_BINARY_PATH = '/home/panstarrs/ippdor/local/bin'
+        LOCAL_TMP_DIR    = '/export/%s.0/ippdor/processing' % hostname()
+        NFS_TMP_DIR    = '/home/panstarrs/ippdor/processing'
+        IPP_BIN_PATH = '/home/panstarrs/ippdor/psconfig/20110920/current.lin64/bin'
+    else: # Development
+        IPPDOR_BINARY_PATH = '~/local/bin'
+        LOCAL_TMP_DIR    = '/tmp/ippdor_processing'
+        NFS_TMP_DIR    = '~/ippdor_processing'
+        IPP_BIN_PATH = '~/ipp/default.linux'
+
+    # Global requirements
+    REQUIREMENTS     = 'LoadAvg < 0.3'
+
+    # Condor scripts
+    GENERATE_EXPOSURE_DAG = '%s/generate_exposure_dag.py %s %s %s' % (IPPDOR_BINARY_PATH, '%s', '%s', '%s')
+
+    # IPP executables
+    CHIPTOOL = '%s/chiptool -dbname gpc1' % IPP_BIN_PATH
+    CHIPIMFILE = '%s/chip_imfile.pl --dbname gpc1 --redirect-output --verbose --threads 1' % IPP_BIN_PATH
+
+    CAMTOOL = '%s/camtool' % IPP_BIN_PATH
+    CAMEXP = '%s/camera_exp.pl --dbname gpc1 --redirect-output --verbose' % IPP_BIN_PATH
+
+    FAKETOOL = '%s/faketool -dbname gpc1' % IPP_BIN_PATH
+    FAKE_IMFILE = '%s/fake_imfile.pl -dbname gpc1 --redirect-output --verbose'
+
+    WARPTOOL = '%s/warptool -dbname gpc1' % IPP_BIN_PATH
+    WARP_OVERLAP = '%s/warp_overlap.pl --dbname gpc1' % IPP_BIN_PATH
+    WARP_SKYCELL = '%s/warp_skycell.pl --dbname gpc1 --threads 1 --redirect-output' % IPP_BIN_PATH
+    WARP_JPEG = '%s/skycell_jpeg.pl -dbname gpc1 -stage warp' % IPP_BIN_PATH
+
+    STACKTOOL = '%s/stacktool' % IPP_BIN_PATH
Index: /trunk/ippdor/src/ipp/constants/Stack.py
===================================================================
--- /trunk/ippdor/src/ipp/constants/Stack.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/constants/Stack.py	(revision 32874)
@@ -0,0 +1,19 @@
+"""
+This is the file in which all constants relative to the Condor-IPP
+framework are defined for the Stack stage. 
+
+It defines the following constants:
+TODO: list defined constants
+"""
+from ipp.exceptions.UninstantiableIppException import UninstantiableIppException
+from ipp.constants.Globals import Globals as GlobalConstants
+
+class Stack:
+    """
+    Class where all warp stack constants are defined
+    """
+    def __init__(self):
+        raise UninstantiableIppException()
+
+    # Change it according to new needs
+    REQUIREMENTS = GlobalConstants.REQUIREMENTS
Index: /trunk/ippdor/src/ipp/constants/Warp.py
===================================================================
--- /trunk/ippdor/src/ipp/constants/Warp.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/constants/Warp.py	(revision 32874)
@@ -0,0 +1,19 @@
+"""
+This is the file in which all constants relative to the Condor-IPP
+framework are defined for the Warp stage. 
+
+It defines the following data members:
+TODO: list defined constants
+"""
+from ipp.exceptions.UninstantiableIppException import UninstantiableIppException
+from ipp.constants.Globals import Globals as GlobalConstants
+
+class Warp:
+    """
+    Class where all warp stage constants are defined
+    """
+    def __init__(self):
+        raise UninstantiableIppException()
+
+    # Change it according to new needs
+    REQUIREMENTS = GlobalConstants.REQUIREMENTS
Index: /trunk/ippdor/src/ipp/constants/__init__.py
===================================================================
--- /trunk/ippdor/src/ipp/constants/__init__.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/constants/__init__.py	(revision 32874)
@@ -0,0 +1,5 @@
+"""
+This module is the place where all constants are defined
+
+TODO: anything else?
+"""
Index: /trunk/ippdor/src/ipp/exceptions/IppException.py
===================================================================
--- /trunk/ippdor/src/ipp/exceptions/IppException.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/exceptions/IppException.py	(revision 32874)
@@ -0,0 +1,12 @@
+#!/usr/bin/env python
+
+"""
+A class for the IPP-Condor framework
+"""
+class IppException(Exception):
+    """
+    A class for our own exception
+    """
+    def __init__(self, message = None):
+        Exception.__init__(self, message)
+
Index: /trunk/ippdor/src/ipp/exceptions/UninstantiableIppException.py
===================================================================
--- /trunk/ippdor/src/ipp/exceptions/UninstantiableIppException.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/exceptions/UninstantiableIppException.py	(revision 32874)
@@ -0,0 +1,15 @@
+"""
+A class for the IPP-Condor framework
+"""
+
+from ipp.exceptions.IppException import IppException
+
+class UninstantiableIppException(IppException):
+    """
+    An exception for classes that we don't want to be instantiated
+    (e.g. constants containers)
+    """
+    def __init__(self, message = None):
+        if message is None:
+            IppException.__init__(self, 
+                                  'Not intended to be instantiated')
Index: /trunk/ippdor/src/ipp/exceptions/__init__.py
===================================================================
--- /trunk/ippdor/src/ipp/exceptions/__init__.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/exceptions/__init__.py	(revision 32874)
@@ -0,0 +1,5 @@
+"""
+Module containing the exceptions classes for the Condor-IPP framework.
+
+All classes should derive from IppException.
+"""
Index: /trunk/ippdor/src/ipp/helpers.py
===================================================================
--- /trunk/ippdor/src/ipp/helpers.py	(revision 32874)
+++ /trunk/ippdor/src/ipp/helpers.py	(revision 32874)
@@ -0,0 +1,43 @@
+"""
+Various Condor-IPP helpers
+"""
+import os
+import socket
+
+#################################################################
+def mkdir(directory_name):
+    """From http://code.activestate.com/recipes/82465-a-friendly-mkdir/
+
+    Works the way a good mkdir should :)
+        - already exists, silently complete
+        - regular file in the way, raise an exception
+        - parent directory(ies) does not exist, make them as well
+    """
+    print 'Creating directory [%s]' % directory_name
+    if os.path.isdir(directory_name):
+        pass
+    elif os.path.isfile(directory_name):
+        raise OSError("a file with the same name as the desired " \
+                      "dir, '%s', already exists." % directory_name)
+    else:
+        head, tail = os.path.split(directory_name)
+        if head and not os.path.isdir(head):
+            mkdir(head)
+        if tail:
+            os.mkdir(directory_name)
+
+#################################################################
+def hostname():
+    """
+    Get the host name
+    >>> print hostname()
+    neverland
+    """
+    return socket.gethostbyaddr(socket.gethostname())[0].split('.')[0]
+
+#################################################################
+if __name__ == '__main__':
+    print 'Host is [%s]' % hostname()
+    print 'Not intended to be used independently'
+    import sys
+    sys.exit(1)
