private Int32 OpenChannel_1_0( Int32 ConnectionHandle, Hashtable InParameters, ref Hashtable OutParameters ) { StartTiming( "OpenChannel_1_0-Internal", true ); Int32 ErrorCode = Constants.INVALID; // First validate the connection handle Connection ConnectionThatOwnsChannel; if( Connections.TryGetValue( ConnectionHandle, out ConnectionThatOwnsChannel ) ) { // Unpack the input parameters string ChannelName = InParameters["ChannelName"] as string; EChannelFlags ChannelFlags = ( EChannelFlags )InParameters["ChannelFlags"]; Log( EVerbosityLevel.ExtraVerbose, ELogColour.Green, "[OpenChannel] Opening Channel: " + ChannelName ); // Whether the cache request is a hit bool CacheIsHit = true; // Determine the proper path for the file and do any necessary preparing // depending on the type of channel that's being requested try { ErrorCode = EnsureFolderExists( AgentApplication.Options.CacheFolder ); if( ErrorCode >= 0 ) { if( ( ChannelFlags & EChannelFlags.TYPE_PERSISTENT ) != 0 ) { string AgentStagingArea = Path.Combine( AgentApplication.Options.CacheFolder, "AgentStagingArea" ); string StagingAreaChannelName = Path.Combine( AgentStagingArea, ChannelName ); string FullyCachedChannelName = Path.Combine( AgentApplication.Options.CacheFolder, ChannelName ); // A channel is opened for writing in the staging area and moved to the cache on Close if( ( ChannelFlags & EChannelFlags.ACCESS_WRITE ) != 0 ) { // Ensure the necessary folder exists ErrorCode = EnsureFolderExists( AgentStagingArea ); if( ErrorCode >= 0 ) { // Delete the file if it exists in the staging area FileInfo StagingAreaChannel = new FileInfo( StagingAreaChannelName ); if( StagingAreaChannel.Exists ) { StagingAreaChannel.IsReadOnly = false; StagingAreaChannel.Delete(); } // Delete the file if it exists in the main cache area FileInfo FullyCachedChannel = new FileInfo( FullyCachedChannelName ); if( FullyCachedChannel.Exists ) { FullyCachedChannel.IsReadOnly = false; FullyCachedChannel.Delete(); } // Remove the channel's entry from the hash table, if one is there if( ChannelHashValues.Remove( ChannelName ) ) { Log( EVerbosityLevel.Verbose, ELogColour.Green, " ......... removed hash for " + ChannelName ); } // This channel is now safe to open // Create a new channel object and add it to the set of open channels Channel NewChannel = new Channel( ChannelName, StagingAreaChannelName, ChannelFlags ); ErrorCode = SetChannelHandleAndAdd( ConnectionThatOwnsChannel, NewChannel ); } } // A channel opened for reading is opened directly in the cache else if( ( ChannelFlags & EChannelFlags.ACCESS_READ ) != 0 ) { // If the file doesn't exist, see if we can recover if( !File.Exists( FullyCachedChannelName ) ) { if( !File.Exists( StagingAreaChannelName ) ) { // If the file doesn't exist, there are a couple cases we need to // check before we call this a failure. If this connection is a Job // running for a remote Agent, the channel is an implicit dependency // and needs to be requested from the remote Agent. if( ( ConnectionThatOwnsChannel.Parent != null ) && ( ConnectionThatOwnsChannel.Parent is RemoteConnection ) ) { // Request the missing channel via the remote parent connection CacheIsHit = false; RemoteConnection RemoteParentConnection = ConnectionThatOwnsChannel.Parent as RemoteConnection; if( !PullChannel( RemoteParentConnection, ChannelName, null ) ) { // If the pull failed, set the correct error based on the state of the remote if( RemoteParentConnection.Interface.IsAlive() ) { ErrorCode = Constants.ERROR_CHANNEL_NOT_FOUND; } else { ErrorCode = Constants.ERROR_CONNECTION_DISCONNECTED; } } } } else { // This suggests that the file is still being transferred // and the caller should wait and try the request again ErrorCode = Constants.ERROR_CHANNEL_NOT_READY; } } // If the file does exist, we still may need to validate it with the // remote Instigator using the hash value we have for the channel else if( ( ConnectionThatOwnsChannel.Parent != null ) && ( ConnectionThatOwnsChannel.Parent is RemoteConnection ) ) { RemoteConnection RemoteParentConnection = ConnectionThatOwnsChannel.Parent as RemoteConnection; byte[] LocalHashValue; bool ChannelHashIsValid = false; if( !ChannelHashValues.TryGetValue( ChannelName, out LocalHashValue ) ) { LocalHashValue = ComputeHash( File.ReadAllBytes( FullyCachedChannelName ) ); if( ChannelHashValues.Add( ChannelName, LocalHashValue ) ) { Log( EVerbosityLevel.Verbose, ELogColour.Green, " ......... (3) created hash for " + ChannelName ); } } if( LocalHashValue.Length > 0 ) { Int32 ParentConnectionHandle = RemoteParentConnection.Handle; ChannelHashIsValid = RemoteParentConnection.Interface.ValidateChannel( ParentConnectionHandle, ChannelName, LocalHashValue ); } else { Log( EVerbosityLevel.Informative, ELogColour.Orange, "[OpenChannel] Error: missing hash table entry for existing channel, pulling " + ChannelName ); } // If we fail the hash comparison test, we need to pull the file if( !ChannelHashIsValid ) { Log( EVerbosityLevel.Informative, ELogColour.Orange, "[OpenChannel] Remote hash comparison failed, re-pulling channel " + ChannelName ); CacheIsHit = false; // Before pulling the channel, delete what we've got on disk, in case the pull fails FileInfo FullyCachedChannel = new FileInfo( FullyCachedChannelName ); FullyCachedChannel.IsReadOnly = false; FullyCachedChannel.Delete(); // Remove the channel's entry from the hash table, if one is there if( ChannelHashValues.Remove( ChannelName ) ) { Log( EVerbosityLevel.Verbose, ELogColour.Green, " ......... removed hash for " + ChannelName ); } if( !PullChannel( RemoteParentConnection, ChannelName, null ) ) { // If the pull failed, set the correct error based on the state of the remote if( RemoteParentConnection.Interface.IsAlive() ) { ErrorCode = Constants.ERROR_CHANNEL_NOT_FOUND; } else { ErrorCode = Constants.ERROR_CONNECTION_DISCONNECTED; } } } } // Open the file if it exists if( File.Exists( FullyCachedChannelName ) ) { // Ensure we have a valid hash entry for this channel if( ChannelHashValues.Add( ChannelName, ComputeHash( File.ReadAllBytes( FullyCachedChannelName ) ) ) ) { // Add the entry into the channel hash table Log( EVerbosityLevel.Verbose, ELogColour.Green, " ......... (4) created hash for " + ChannelName ); } else { // TODO: Add an option for fully validated cache usage and, when // set, add code here to regenerate the hash and compare it here } // This channel is now safe to open // Create a new channel object and add it to the set of open channels Channel NewChannel = new Channel( ChannelName, FullyCachedChannelName, ChannelFlags ); ErrorCode = SetChannelHandleAndAdd( ConnectionThatOwnsChannel, NewChannel ); } else { // If the file does not exist, make sure we don't have a stale hash for it if( ChannelHashValues.Remove( ChannelName ) ) { Log( EVerbosityLevel.Verbose, ELogColour.Green, " ......... removed hash for " + ChannelName ); } } } } else if( ( ChannelFlags & EChannelFlags.TYPE_JOB_ONLY ) != 0 ) { AgentGuid JobGuid; // If there's a Job associated with this connection, use its GUID if( ConnectionThatOwnsChannel.Job != null ) { JobGuid = ConnectionThatOwnsChannel.Job.JobGuid; } else { // If there's no Job associated with this connection at this point, provide // a default GUID for one for debugging access to the agent cache JobGuid = DebuggingJobGuid; } string AllJobsFolder = Path.Combine( AgentApplication.Options.CacheFolder, "Jobs" ); string ThisJobFolder = Path.Combine( AllJobsFolder, "Job-" + JobGuid.ToString() ); // Be sure that the folders we need are there (should have all been created in OpenJob) if( Directory.Exists( ThisJobFolder ) ) { string FullJobChannelName = Path.Combine( ThisJobFolder, ChannelName ); // A channel opened for writing is opened directly in the Jobs folder if( ( ChannelFlags & EChannelFlags.ACCESS_WRITE ) != 0 ) { // Delete the file if it already exists FileInfo FullChannel = new FileInfo( FullJobChannelName ); if( FullChannel.Exists ) { FullChannel.IsReadOnly = false; FullChannel.Delete(); } // This channel is now safe to open // Create a new channel object and add it to the set of open channels Channel NewJobChannel = new JobChannel( JobGuid, ChannelName, FullJobChannelName, ChannelFlags ); ErrorCode = SetChannelHandleAndAdd( ConnectionThatOwnsChannel, NewJobChannel ); } // A channel opened for reading is opened directly from the Jobs folder else if( ( ChannelFlags & EChannelFlags.ACCESS_READ ) != 0 ) { // If the file doesn't exist, see if we can recover if( !File.Exists( FullJobChannelName ) ) { // If the file doesn't exist, there are a couple cases we need to // check before we call this a failure. If this connection is a Job // running for a remote Agent, the channel is an implicit dependency // and needs to be requested from the remote Agent. if( ( ConnectionThatOwnsChannel.Parent != null ) && ( ConnectionThatOwnsChannel.Parent is RemoteConnection ) ) { // Request the missing channel via the remote parent connection CacheIsHit = false; RemoteConnection RemoteParentConnection = ConnectionThatOwnsChannel.Parent as RemoteConnection; if( !PullChannel( RemoteParentConnection, ChannelName, JobGuid ) ) { // If the pull failed, set the correct error based on the state of the remote if( RemoteParentConnection.Interface.IsAlive() ) { ErrorCode = Constants.ERROR_CHANNEL_NOT_FOUND; } else { ErrorCode = Constants.ERROR_CONNECTION_DISCONNECTED; } } } } // Open the file if it exists if( File.Exists( FullJobChannelName ) ) { // This channel is now safe to open // Create a new channel object and add it to the set of open channels Channel NewJobChannel = new JobChannel( JobGuid, ChannelName, FullJobChannelName, ChannelFlags ); ErrorCode = SetChannelHandleAndAdd( ConnectionThatOwnsChannel, NewJobChannel ); } else { // File doesn't exist, error ErrorCode = Constants.ERROR_CHANNEL_NOT_FOUND; } } } } } } catch( Exception Ex ) { Log( EVerbosityLevel.Informative, ELogColour.Red, "OpenChannel failed: " + Ex.ToString() ); ErrorCode = Constants.ERROR_EXCEPTION; } // Register the cache request stats if the channel is being opened for READ if( ( ChannelFlags & EChannelFlags.ACCESS_READ ) != 0 ) { ConnectionThatOwnsChannel.CacheRequests++; // Only register the hit/miss data if the channel is opened if( ErrorCode > 0 ) { ConnectionThatOwnsChannel.CacheHits += CacheIsHit ? 1 : 0; ConnectionThatOwnsChannel.CacheMisses += CacheIsHit ? 0 : 1; } if( ConnectionThatOwnsChannel.Job != null ) { ConnectionThatOwnsChannel.Job.CacheRequests++; // Only register the hit/miss data if the channel is opened if( ErrorCode > 0 ) { ConnectionThatOwnsChannel.Job.CacheHits += CacheIsHit ? 1 : 0; ConnectionThatOwnsChannel.Job.CacheMisses += CacheIsHit ? 0 : 1; } } if( ConnectionThatOwnsChannel.Parent != null ) { ConnectionThatOwnsChannel.Parent.CacheRequests++; // Only register the hit/miss data if the channel is opened if( ErrorCode > 0 ) { ConnectionThatOwnsChannel.Parent.CacheHits += CacheIsHit ? 1 : 0; ConnectionThatOwnsChannel.Parent.CacheMisses += CacheIsHit ? 0 : 1; } } } } else { // Not a valid connection, return error ErrorCode = Constants.ERROR_CONNECTION_NOT_FOUND; } StopTiming(); return ErrorCode; }
private Int32 SetChannelHandleAndAdd( Connection ConnectionThatOwnsChannel, Channel NewChannel ) { Int32 NewHandleValue; lock( SetChannelHandleAndAddLock ) { // Generate a new random value for the handle do { NewHandleValue = SetChannelHandleAndAddGenerator.Next(); } // Keep generating new values until we find one not already in use while( ConnectionThatOwnsChannel.OpenChannels.ContainsKey( NewHandleValue ) ); // Now set the new handle value and add to the set NewChannel.SetHandle( NewHandleValue ); ConnectionThatOwnsChannel.OpenChannels.Add( NewHandleValue, NewChannel ); } return NewHandleValue; }