/*
* This file or a portion of this file is licensed under the terms of
* the Globus Toolkit Public License, found in file ../GTPL, or at
* http://www.globus.org/toolkit/download/license.html. This notice must
* appear in redistributions of this file, with or without modification.
*
* Redistributions of this Software, with or without modification, must
* reproduce the GTPL in: (1) the Software, or (2) the Documentation or
* some other similar material which is provided with the Software (if
* any).
*
* Copyright 1999-2004 University of Chicago and The University of
* Southern California. All rights reserved.
*/
package org.griphyn.vdl.dbschema;
import edu.isi.pegasus.planner.invocation.InvocationRecord;
import edu.isi.pegasus.planner.invocation.HasText;
import edu.isi.pegasus.planner.invocation.JobStatus;
import edu.isi.pegasus.planner.invocation.Arguments;
import edu.isi.pegasus.planner.invocation.Architecture;
import edu.isi.pegasus.planner.invocation.StatCall;
import edu.isi.pegasus.planner.invocation.HasDescriptor;
import edu.isi.pegasus.planner.invocation.Usage;
import edu.isi.pegasus.planner.invocation.StatInfo;
import edu.isi.pegasus.planner.invocation.Job;
import edu.isi.pegasus.planner.invocation.Status;
import edu.isi.pegasus.planner.invocation.HasFilename;
import java.sql.*;
import java.util.*;
import java.io.*;
import java.lang.reflect.*;
import java.net.InetAddress;
import org.griphyn.vdl.util.ChimeraProperties;
import edu.isi.pegasus.common.util.Separator;
import org.griphyn.vdl.util.Logging;
/**
* This class provides basic functionalities to interact with the
* backend database for invocation records, such as insertion, deletion,
* and search.
*
* @author Jens-S. Vöckler
* @author Yong Zhao
* @version $Revision$
*/
public class InvocationSchema extends DatabaseSchema
implements PTC
{
/**
* Default constructor for the provenance tracking.
*
* @param dbDriverName is the database driver name
*/
public InvocationSchema( String dbDriverName )
throws ClassNotFoundException,
NoSuchMethodException, InstantiationException,
IllegalAccessException, InvocationTargetException,
SQLException, IOException
{
// load the driver from the properties
super( dbDriverName, PROPERTY_PREFIX );
Logging.instance().log( "dbschema", 3, "done with parent schema c'tor" );
// Note: Does not rely on optional JDBC3 features
this.m_dbdriver.insertPreparedStatement( "stmt.save.uname",
"INSERT INTO ptc_uname(id,archmode,sysname,os_release,machine) " +
"VALUES (?,?,?,?,?)" );
this.m_dbdriver.insertPreparedStatement( "stmt.save.rusage",
"INSERT INTO ptc_rusage(id,utime,stime,minflt,majflt,nswaps," +
"nsignals,nvcsw,nivcsw) VALUES (?,?,?,?,?,?,?,?,?)" );
this.m_dbdriver.insertPreparedStatement( "stmt.save.stat",
"INSERT INTO ptc_stat(id,errno,fname,fdesc,size,mode,inode,atime," +
"ctime,mtime,uid,gid) VALUES (?,?,?,?,?,?,?,?,?,?,?,?)" );
this.m_dbdriver.insertPreparedStatement( "stmt.save.ivr",
"INSERT INTO ptc_invocation(id,creator,creationtime,wf_label," +
"wf_time,version,start,duration,tr_namespace,tr_name,tr_version," +
"dv_namespace,dv_name,dv_version,resource,host,pid," +
"uid,gid,cwd,arch,total)" +
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" );
this.m_dbdriver.insertPreparedStatement( "stmt.save.job",
"INSERT INTO ptc_job(id,type,start,duration,pid,rusage,stat," +
"exitcode,exit_msg,args) VALUES (?,?,?,?,?,?,?,?,?,?)" );
this.m_dbdriver.insertPreparedStatement( "stmt.save.lfn",
"INSERT INTO ptc_lfn(id,stat,initial,lfn) VALUES (?,?,?,?)" );
this.m_dbdriver.insertPreparedStatement( "stmt.select.ivr.sk",
"SELECT id FROM ptc_invocation WHERE start=? AND host=? AND pid=?" );
this.m_dbdriver.insertPreparedStatement( "stmt.select.uname.sk",
"SELECT id FROM ptc_uname WHERE archmode=? AND sysname=? " +
"AND os_release=? AND machine=?" );
}
/**
* Converts a regular datum into an SQL timestamp.
* @param date is a regular Java date
* @return a SQL timestamp obtained from the Date.
*/
protected java.sql.Timestamp toStamp( java.util.Date date )
{
return new java.sql.Timestamp( date.getTime() );
}
/**
* Checks the existence of an invocation record in the database.
* The information is based on the (start,host,pid) tuple, although
* with private networks, cases may arise that have this tuple
* identical, yet are different.
*
* @param start is the start time of the grid launcher
* @param host is the address of the host it ran upon
* @param pid is the process id of the grid launcher itself.
* @return the id of the existing record, or -1
*/
public long
getInvocationID( java.util.Date start, InetAddress host, int pid )
throws SQLException
{
long result = -1;
Logging.instance().log("xaction", 1, "START select invocation id" );
PreparedStatement ps =
m_dbdriver.getPreparedStatement("stmt.select.ivr.sk");
int i=1;
ps.setTimestamp( i++, toStamp(start) );
ps.setString( i++, host.getHostAddress() );
ps.setInt( i++, pid );
Logging.instance().log( "chunk", 2, "SELECT id FROM invocation" );
ResultSet rs = ps.executeQuery();
if ( rs.next() ) result = rs.getLong(1);
rs.close();
Logging.instance().log("xaction", 1, "FINAL select invocation id" );
return result;
}
/**
* Determines the id of an existing identical architecture, or creates
* a new entry.
*
* @param arch is the architecture description
* @return the id of the architecture, either new or existing.
*/
public long
saveArchitecture( Architecture arch )
throws SQLException
{
long result = -1;
Logging.instance().log("xaction", 1, "START select uname id" );
int i=1;
PreparedStatement ps =
m_dbdriver.getPreparedStatement("stmt.select.uname.sk");
stringOrNull( ps, i++, arch.getArchMode() );
stringOrNull( ps, i++, arch.getSystemName() );
stringOrNull( ps, i++, arch.getRelease() );
stringOrNull( ps, i++, arch.getMachine() );
Logging.instance().log( "chunk", 2, "SELECT id FROM uname" );
ResultSet rs = ps.executeQuery();
if ( rs.next() ) result = rs.getLong(1);
rs.close();
Logging.instance().log("xaction", 1, "FINAL select uname id" );
if ( result == -1 ) {
// nothing found, need to really insert things
Logging.instance().log("xaction", 1, "START save uname" );
try {
result = m_dbdriver.sequence1( "uname_id_seq" );
} catch ( SQLException e ) {
Logging.instance().log( "app", 0, "During rusage sequence number: " +
e.toString().trim() );
Logging.instance().log("xaction", 1, "START rollback" );
m_dbdriver.rollback();
Logging.instance().log("xaction", 1, "FINAL rollback" );
throw e; // re-throw
}
// add ID explicitely from sequence to insertion
ps = m_dbdriver.getPreparedStatement("stmt.save.uname");
i=1;
longOrNull( ps, i++, result );
stringOrNull( ps, i++, arch.getArchMode() );
stringOrNull( ps, i++, arch.getSystemName() );
stringOrNull( ps, i++, arch.getRelease() );
stringOrNull( ps, i++, arch.getMachine() );
// save prepared values
Logging.instance().log( "chunk", 2, "INSERT INTO uname" );
try {
int rc = ps.executeUpdate();
if ( result == -1 )
result = m_dbdriver.sequence2( ps, "uname_id_seq", 1 );
} catch ( SQLException e ) {
// race condition possibility: try once more to find info
result = -1;
Logging.instance().log("xaction", 1, "START select uname id" );
i = 1;
ps = m_dbdriver.getPreparedStatement("stmt.select.uname.sk");
stringOrNull( ps, i++, arch.getArchMode() );
stringOrNull( ps, i++, arch.getSystemName() );
stringOrNull( ps, i++, arch.getRelease() );
stringOrNull( ps, i++, arch.getMachine() );
Logging.instance().log( "chunk", 2, "SELECT id FROM uname" );
rs = ps.executeQuery();
if ( rs.next() ) result = rs.getLong(1);
rs.close();
Logging.instance().log("xaction", 1, "FINAL select uname id" );
if ( result == -1 ) {
Logging.instance().log( "app", 0, "While inserting into rusage: " +
e.toString().trim() );
// rollback in saveInvocation()
m_dbdriver.cancelPreparedStatement( "stmt.save.uname" );
throw e; // re-throw
}
}
Logging.instance().log("xaction", 1, "FINAL save uname: ID=" + result );
}
// done
return result;
}
/**
* Inserts an invocation record into the database.
*
* @param ivr is the invocation record to store.
* @return true, if insertion was successful, false otherwise.
*/
public boolean
saveInvocation( InvocationRecord ivr )
throws SQLException
{
// big outer try
try {
long id = -1;
try {
id = m_dbdriver.sequence1( "invocation_id_seq" );
} catch ( SQLException e ) {
Logging.instance().log( "app", 0, "During IVR sequence number: " +
e.toString().trim() );
throw e; // re-throw
}
// add ID explicitely from sequence to insertion
Logging.instance().log("xaction", 1, "START save invocation" );
PreparedStatement ps = m_dbdriver.getPreparedStatement( "stmt.save.ivr" );
int i = 1;
longOrNull( ps, i++, id );
// current_user()
stringOrNull( ps, i++, System.getProperty("user.name") );
// now()
ps.setTimestamp( i++, toStamp(new java.util.Date()) );
// wf_label, wf_time: not available at the moment...
if ( ivr.getWorkflowLabel() == null )
ps.setNull( i++, Types.VARCHAR );
else
ps.setString( i++, ivr.getWorkflowLabel() );
if ( ivr.getWorkflowTimestamp() == null )
ps.setNull( i++, Types.TIMESTAMP );
else
ps.setTimestamp( i++, toStamp(ivr.getWorkflowTimestamp()) );
// version
ps.setString( i++, ivr.getVersion() );
// start, duration
ps.setTimestamp( i++, toStamp(ivr.getStart()) );
ps.setDouble( i++,ivr.getDuration() );
// TR
i = splitDefinition( ps, ivr.getTransformation(), i );
// DV: not available at the moment
i = splitDefinition( ps, ivr.getDerivation(), i );
// resource (site handle)
if ( ivr.getResource() == null ) ps.setNull( i++, Types.VARCHAR );
else ps.setString( i++, ivr.getResource() );
// host
ps.setString( i++, ivr.getHostAddress().getHostAddress() );
// [pug]id
ps.setInt( i++, ivr.getPID() );
ps.setInt( i++, ivr.getUID() );
ps.setInt( i++, ivr.getGID() );
// cwd
stringOrNull( ps, i++, ivr.getWorkingDirectory().getValue() );
// uname
ps.setLong( i++, saveArchitecture( ivr.getArchitecture() ) );
// save usage and remember id
ps.setLong( i++, saveUsage( ivr.getUsage() ) );
// save prepared values
Logging.instance().log( "chunk", 2, "INSERT INTO invocation" );
int rc = ps.executeUpdate();
if ( id == -1 ) id = m_dbdriver.sequence2( ps, "invocation_id_seq", 1 );
Logging.instance().log("xaction", 1, "FINAL save invocation: ID=" + id );
// save jobs belonging to invocation
for ( Iterator j=ivr.iterateJob(); j.hasNext(); ) {
saveJob( id, ((Job) j.next()) );
}
// jsv 20050815: more stat info for Prophesy
for ( Iterator j=ivr.iterateStatCall(); j.hasNext(); ) {
StatCall s = (StatCall) j.next();
String sch = s.getHandle().toLowerCase();
if ( sch.equals("initial") || sch.equals("final") ) {
saveLFN( id, s );
}
}
// done
m_dbdriver.commit();
return true;
} catch ( SQLException e ) {
// show complete exception chain
for ( SQLException walk=e; walk != null; walk=walk.getNextException() ) {
Logging.instance().log( "app", 0, walk.getSQLState() + ": " +
walk.getErrorCode() + ": " +
walk.getMessage().trim() );
StackTraceElement[] ste = walk.getStackTrace();
for ( int n=0; n<5 && n<ste.length; ++n ) {
Logging.instance().log( "app", 0, ste[n].toString() );
}
}
Logging.instance().log("xaction", 1, "START rollback" );
m_dbdriver.cancelPreparedStatement( "stmt.save.ivr" );
m_dbdriver.rollback();
Logging.instance().log("xaction", 1, "FINAL rollback" );
throw e; // re-throw
}
}
/**
* Splits the canonical FQDN of a definition into its components,
* and save each component into the database.
*
* @param in is the canonical form.
* @param i is the current position in the invocation prepared stmt.
* @return the updated position (usually i+3)
*/
private int splitDefinition( PreparedStatement ps, String in, int i )
throws SQLException
{
if ( in == null ) {
// no input, insert all NULL values
ps.setNull( i++, Types.VARCHAR );
ps.setNull( i++, Types.VARCHAR );
ps.setNull( i++, Types.VARCHAR );
} else {
// there is input after all, insert appropriate values
String ns = null;
String vs = null;
// separate namespace
int p1 = in.indexOf( Separator.NAMESPACE );
if ( p1 == -1 ) {
// no namespace
p1 = 0;
} else {
// there is a namespace
ns = in.substring( 0, p1 );
p1 += Separator.NAMESPACE.length();
}
// separate version
int p2 = in.indexOf( Separator.NAME, p1 );
if ( p2 == -1 ) {
// no version attached
p2 = in.length();
} else {
vs = in.substring( p2 + Separator.NAME.length() );
}
// separate identifier -- this is a must-have
String nm = in.substring( p1, p2 );
stringOrNull( ps, i++, ns );
stringOrNull( ps, i++, nm );
stringOrNull( ps, i++, vs );
}
// done
return i;
}
/**
* Helper function to insert a chunk of the invocation record. This piece
* deals with the rusage information.
*
* @param u is the usage record to insert into the database
* @return the sequence number under which it was inserted.
* @exception SQLException if something goes awry during insertion.
*/
protected long saveUsage( Usage u )
throws SQLException
{
if ( u == null )
throw new RuntimeException( "usage record is null" );
long id = -1;
try {
id = m_dbdriver.sequence1( "rusage_id_seq" );
} catch ( SQLException e ) {
Logging.instance().log( "app", 0, "During rusage sequence number: " +
e.toString().trim() );
Logging.instance().log("xaction", 1, "START rollback" );
m_dbdriver.rollback();
Logging.instance().log("xaction", 1, "FINAL rollback" );
throw e; // re-throw
}
// add ID explicitely from sequence to insertion
Logging.instance().log("xaction", 1, "START save rusage" );
PreparedStatement ps = m_dbdriver.getPreparedStatement("stmt.save.rusage");
int i = 1;
longOrNull( ps, i++, id );
// add rest of rusage record
ps.setDouble( i++, u.getUserTime() );
ps.setDouble( i++, u.getSystemTime() );
ps.setInt( i++, u.getMinorFaults() );
ps.setInt( i++, u.getMajorFaults() );
ps.setInt( i++, u.getSwaps() );
ps.setInt( i++, u.getSignals() );
ps.setInt( i++, u.getVoluntarySwitches() );
ps.setInt( i++, u.getInvoluntarySwitches() );
// save prepared values
Logging.instance().log( "chunk", 2, "INSERT INTO rusage" );
try {
ps.executeUpdate();
if ( id==-1 ) id = m_dbdriver.sequence2( ps, "rusage_id_seq", 1 );
} catch ( SQLException e ) {
Logging.instance().log( "app", 0, "While inserting into rusage: " +
e.toString().trim() );
// rollback in saveInvocation()
this.m_dbdriver.cancelPreparedStatement( "stmt.save.rusage" );
throw e; // re-throw
}
// done
Logging.instance().log("xaction", 1, "FINAL save rusage: ID=" + id );
return id;
}
/**
* Helper function to insert a chunk of the invocation record. This piece
* deals with the stat and fstat information.
*
* @param s is the stat record to insert into the database
* @return the sequence number under which it was inserted.
* @exception SQLException if something goes awry during insertion.
*/
protected long saveStat( StatCall s )
throws SQLException
{
long id = -1;
try {
id = m_dbdriver.sequence1( "stat_id_seq" );
} catch ( SQLException e ) {
Logging.instance().log( "app", 0, "During stat sequence number: " +
e.toString().trim() );
Logging.instance().log("xaction", 1, "START rollback" );
m_dbdriver.rollback();
Logging.instance().log("xaction", 1, "FINAL rollback" );
throw e; // re-throw
}
// add ID explicitely from sequence to insertion
Logging.instance().log("xaction", 1, "START save stat" );
PreparedStatement ps = this.m_dbdriver.getPreparedStatement("stmt.save.stat");
int i = 1;
longOrNull( ps, i++, id );
// add rest of stat record, as appropriate. Many things will stay
// NULL during the fill-in of incomplete (failed) stat info
ps.setInt( i++, s.getError() );
edu.isi.pegasus.planner.invocation.File f = s.getFile();
if ( f != null ) {
if ( f instanceof HasFilename && ((HasFilename) f).getFilename() != null )
ps.setString( i++, ((HasFilename) f).getFilename() );
else
ps.setNull( i++, Types.VARCHAR );
if ( f instanceof HasDescriptor && ((HasDescriptor) f).getDescriptor() != -1 )
ps.setInt( i++, ((HasDescriptor) f).getDescriptor() );
else
ps.setNull( i++, Types.INTEGER );
} else {
i += 2;
}
StatInfo si = s.getStatInfo();
if ( si != null ) {
ps.setLong( i++, si.getSize() );
ps.setInt( i++, si.getMode() );
ps.setLong( i++, si.getINode() );
ps.setTimestamp( i++, toStamp( si.getAccessTime() ) );
ps.setTimestamp( i++, toStamp( si.getCreationTime() ) );
ps.setTimestamp( i++, toStamp( si.getModificationTime() ) );
ps.setInt( i++, si.getUID() );
ps.setInt( i++, si.getGID() );
} else {
// bug fixed 20040908 jsv:
// we don't know anything about those, so fill in NULL for rDBMS
// that don't automagically default empty columns to NULL (sigh).
ps.setNull( i++, Types.BIGINT );
ps.setNull( i++, Types.INTEGER );
ps.setNull( i++, Types.BIGINT );
ps.setNull( i++, Types.TIMESTAMP );
ps.setNull( i++, Types.TIMESTAMP );
ps.setNull( i++, Types.TIMESTAMP );
ps.setNull( i++, Types.INTEGER );
ps.setNull( i++, Types.INTEGER );
}
// save prepared values
Logging.instance().log( "chunk", 2, "INSERT INTO stat" );
try {
ps.executeUpdate();
if ( id==-1 ) id = m_dbdriver.sequence2( ps, "stat_id_seq", 1 );
} catch ( SQLException e ) {
Logging.instance().log( "app", 0, "While inserting into stat: " +
e.toString().trim() );
// rollback in safeInvocation()
this.m_dbdriver.cancelPreparedStatement( "stmt.save.stat" );
throw e; // re-throw
}
// done
Logging.instance().log("xaction", 1, "FINAL save stat: ID=" + id );
return id;
}
/**
* Helper function to insert a LFN PFN mapping stat call into the
* stat information records.
*
* @param iid is the invocation record id to which this job belongs.
* @param s is an instance of a stat call from the initial or final list
* @exception SQLException if something goes awry during insertion.
*/
protected void saveLFN( long iid, StatCall s )
throws SQLException
{
Logging.instance().log("xaction", 1, "START save lfn" );
PreparedStatement ps = m_dbdriver.getPreparedStatement( "stmt.save.lfn" );
int i=1;
// add foreign ID explicitely
ps.setLong( i++, iid );
// stat (foreign key)
ps.setLong( i++, saveStat(s) );
// set the modifier before or after
String id = s.getHandle().toLowerCase();
if ( id.equals("initial") || id.equals("final") ) {
ps.setString( i++, id.substring(0,1) );
} else {
ps.setNull( i++, Types.CHAR );
}
// set the LFN
stringOrNull( ps, i++, s.getLFN() );
// save prepared values
Logging.instance().log( "chunk", 2, "INSERT INTO lfn" );
try {
ps.executeUpdate();
} catch ( SQLException e ) {
Logging.instance().log( "app", 0, "While inserting into lfn: " +
e.toString().trim() );
// rollback in safeInvocation()
m_dbdriver.cancelPreparedStatement( "stmt.save.lfn" );
throw e; // re-throw
}
// done
Logging.instance().log("xaction", 1, "FINAL save lfn" );
}
/**
* Helper function to insert a chunk of the invocation record. This piece
* deals with the jobs themselves
*
* @param iid is the invocation record id to which this job belongs.
* @param job is the job to insert.
* @exception SQLException if something goes awry during insertion.
*/
protected void saveJob( long iid, Job job )
throws SQLException
{
Logging.instance().log("xaction", 1, "START save job" );
PreparedStatement ps = m_dbdriver.getPreparedStatement( "stmt.save.job" );
int i=1;
// add foreign ID explicitely
ps.setLong( i++, iid );
// type
String tag = job.getTag();
if ( tag.equals("mainjob") ) {
ps.setString( i++, "M" );
} else if ( tag.equals("prejob") ) {
ps.setString( i++, "P" );
} else if ( tag.equals("postjob") ) {
ps.setString( i++, "p" );
} else if ( tag.equals("cleanup") ) {
ps.setString( i++, "c" );
} else if ( tag.equals("setup") ) {
ps.setString( i++, "S" );
} else {
throw new SQLException( "illegal job type \"" + tag + "\"" );
}
// start, duration
ps.setTimestamp( i++, toStamp( job.getStart() ) );
ps.setDouble( i++, job.getDuration() );
// pid
ps.setInt( i++, job.getPID() );
// usage (foreign key)
ps.setLong( i++, saveUsage( job.getUsage() ) );
// stat (foreign key)
ps.setLong( i++, saveStat( job.getExecutable() ) );
// exitcode, exit_msg
Status status = job.getStatus();
ps.setInt( i++, status.getStatus() );
JobStatus js = status.getJobStatus();
String msg = null;
if ( js instanceof HasText ) msg = ((HasText) js).getValue();
stringOrNull( ps, i++, msg );
// args
Arguments args = job.getArguments();
stringOrNull( ps, i++, args.getValue() );
// save prepared values
Logging.instance().log( "chunk", 2, "INSERT INTO job" );
try {
ps.executeUpdate();
} catch ( SQLException e ) {
Logging.instance().log( "app", 0, "While inserting into job: " +
e.toString().trim() );
// rollback in safeInvocation()
m_dbdriver.cancelPreparedStatement( "stmt.save.job" );
throw e; // re-throw
}
// done
Logging.instance().log("xaction", 1, "FINAL save job" );
}
}