/// <summary> /// Run the job /// </summary> /// <param name="jobDetails"> /// Job Details /// </param> /// <returns> /// Task Wrapper /// </returns> public async Task RunJobAsync(ScheduledJobDetails jobDetails) { await ExecuteJob(JobFactory(jobDetails.JobType), jobDetails); // Mark as done await Scheduler.CompleteJobIterationAsync(jobDetails); }
/// <summary> /// Initializes a new instance of the JobOrchestrator class. /// </summary> /// <param name="job"> /// The job being executed. /// </param> /// <param name="jobDetails"> /// Details about the job being executed. /// </param> /// <param name="log"> /// The CommerceLog object through which log entries can be made. /// </param> public JobOrchestrator(IOrchestratedJob job, ScheduledJobDetails jobDetails, CommerceLog log) { Job = job; JobDetails = jobDetails; Log = log; }
/// <summary> /// Execute the file sync job /// </summary> /// <param name="details"> /// Details of the job we are executing here. /// </param> /// <param name="logger"> /// Handle to the logger /// </param> /// <remarks> /// This job: /// 1. If response is pending, it waits for response to be available /// 2. When response becomes available, the job processes it and puts flag that response is processed /// 3. If response is not pending, it checks to see if there are records to upload and uploads them /// 4. After file is sent to Amex, job puts flag that response is being waited on /// </remarks> public async Task Execute(ScheduledJobDetails details, CommerceLog logger) { Logger = logger; JobDetails = details; Init(); Logger.Exhaustive("Starting execution of job \r\n Details {0}", details); ////if (IsWaitingForResponse()) ////{ //// // check the response //// string[] responseFileNames = await RetrieveResponseFileNamesAsync().ConfigureAwait(false); //// if (responseFileNames == null) //// { //// // we have to wait for response. //// return; //// } //// await UploadResponseFilesToBlobStoreAsync(responseFileNames).ConfigureAwait(false); //// // update job to mark, no response is expected now //// UpdateResponsePendingIndicatorInJob("false"); ////} ////else ////{ //// // process response files if present in blob store //// await ProcessResponseFilesAsync().ConfigureAwait(false); //// // if we reach here, we have either processed the response or no response is being waited on //// // so create new request file if needed //// string requestFile = await CreateNewRequestFileIfNeededAsync().ConfigureAwait(false); //// if (requestFile != null) //// { //// await SendRequestFileAsync(requestFile).ConfigureAwait(false); //// // successfully sent file -> update job to wait for response //// UpdateResponsePendingIndicatorInJob("true"); //// } ////} // Process if any response files are pending string[] responseFileNames = await RetrieveResponseFileNamesAsync().ConfigureAwait(false); if (responseFileNames != null && responseFileNames.Length > 0) { await UploadResponseFilesToBlobStoreAsync(responseFileNames).ConfigureAwait(false); } // Process if any requests files are pending string requestFile = await CreateNewRequestFileIfNeededAsync().ConfigureAwait(false); if (requestFile != null) { await SendRequestFileAsync(requestFile).ConfigureAwait(false); } }
/// <summary> /// Get a Job Runner <see cref="IJobRunner"/> /// </summary> /// <param name="jobDetails"> /// Job Details /// </param> /// <param name="scheduler"> /// Scheduler to use /// </param> /// <param name="commerceConfig"> /// Configuration /// </param> /// <param name="log"> /// Logger /// </param> /// <returns> /// Instance of a runner /// </returns> public static IJobRunner Runner(ScheduledJobDetails jobDetails, IScheduler scheduler, CommerceConfig commerceConfig, CommerceLog log) { if (jobDetails.Orchestrated) { return(new OrchestratedJobRunner(OrchestratedJobFactory.Create, scheduler, commerceConfig, log)); } return(new SimpleJobRunner(ScheduledJobFactory.GetJobByType, log, scheduler)); }
/// <summary> /// Creates and initializes an instance of IOrchestratedJob of the specified type. /// </summary> /// <param name="jobDetails"> /// Details describing the IOrchestratedJob type to create. /// </param> /// <param name="scheduler"> /// The scheduler managing the job. /// </param> /// <param name="log"> /// The object through which log entries can be made. /// </param> /// <returns> /// An instance of IOrchestratedJob of the specified type. /// </returns> /// <exception cref="ArgumentNullException"> /// * Parameter jobDetails cannot be null. /// -OR- /// * Parameter log cannot be null. /// </exception> /// <exception cref="ArgumentException"> /// Parameter JobDetails does not specify a valid IOrchestratedJob type. /// </exception> public static IOrchestratedJob Create(ScheduledJobDetails jobDetails, IScheduler scheduler, CommerceLog log) { if (jobDetails == null) { throw new ArgumentNullException("jobDetails", "Parameter jobDetails cannot be null."); } if (log == null) { throw new ArgumentNullException("log", "Parameter log cannot be null."); } IOrchestratedJob result = null; if (jobDetails.Payload != null) { switch (jobDetails.JobType) { case ScheduledJobType.ApplyReferralReward: result = new ApplyReferralRewardJob(log); break; case ScheduledJobType.ApplyRedemptionReward: result = new ApplyRedemptionRewardJob(log); break; case ScheduledJobType.ClaimDiscountsForNewCard: result = new ClaimDiscountsForNewCardJob(log); break; case ScheduledJobType.ClaimDiscountForExistingCards: result = new ClaimDiscountForExistingCardsJob(log); break; case ScheduledJobType.AmexOfferRegistration: result = new AmexOfferRegistrationJob(log); break; default: throw new ArgumentException("Parameter JobDetails does not specify a valid IOrchestratedJob type.", "jobDetails"); } result.Initialize(jobDetails, scheduler); } else { log.Error("{0} orchestrated job contains no Payload.", null, jobDetails.JobType, ResultCode.JobContainsNoPayload); } return(result); }
/// <summary> /// Process MasterCard rebate confirmation file job execution. /// </summary> /// <param name="details"> /// Details of the job to be executed. /// </param> /// <param name="log"> /// Log within which to log status of job processing. /// </param> /// <returns> /// A task to execute the job. /// </returns> /// <remarks> /// Once complete, this job will schedule a corresponding MasterCardProcessRebateConfirmationJob. /// </remarks> public async Task Execute(ScheduledJobDetails details, CommerceLog log) { Log = log; Log.Verbose("Starting execution of job.\r\nDetails {0}", details); MasterCardRebateConfirmationBlobClient blobClient = MasterCardBlobClientFactory.MasterCardRebateConfirmationBlobClient(log); // Download files from MasterCard and upload them to the blob store. IFtpClient ftpClient = MasterCardFtpClientFactory.RebateConfirmationFtpClient(Log); string[] files = await ftpClient.DirectoryListAsync(); if (files != null) { foreach (string fileName in files) { using (MemoryStream memStream = new MemoryStream()) { // Download the file from MasterCard. await ftpClient.DownloadFileAsync(fileName, memStream).ConfigureAwait(false); // Upload the file to the blob store. memStream.Position = 0; await blobClient.UploadAsync(memStream, fileName).ConfigureAwait(false); } } } // Process all pending rebate confirmation files in the blob store. ICollection <string> fileNames = blobClient.RetrieveNamesOfPendingFiles(); if (fileNames != null) { foreach (string fileName in fileNames) { using (MemoryStream memoryStream = new MemoryStream()) { // Download the file from the blob store. memoryStream.Position = 0; await blobClient.DownloadAsync(memoryStream, fileName).ConfigureAwait(false); // Process the file. memoryStream.Position = 0; ISettlementFileProcessor rebateConfirmationProcessor = MasterCardFileProcessorFactory.MasterCardRebateConfirmationProcessor(memoryStream, fileName); await rebateConfirmationProcessor.Process().ConfigureAwait(false); } // Mark the file as having been processed. await blobClient.MarkAsCompleteAsync(fileName).ConfigureAwait(false); } } Log.Verbose("Execution of job {0} complete ", details.JobId); }
/// <summary> /// Adds a redemption reward for the transaction in the context. /// </summary> internal void AddRedemptionRewards() { RedeemedDealInfo redeemedDealInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; if (Context.Config.EnableRedemptionRewards == true && (ReimbursementTender)redeemedDealInfo.ReimbursementTenderId == ReimbursementTender.MicrosoftEarn) { IRewardOperations rewardOperations = CommerceOperationsFactory.RewardOperations(Context); Context[Key.RewardId] = Context.Config.FirstEarnRewardId; Context[Key.FirstEarnRewardAmount] = Context.Config.FirstEarnRewardAmount; Context[Key.FirstEarnRewardExplanation] = Context.Config.FirstEarnRewardExplanation; ConcurrentDictionary <string, string> payload = new ConcurrentDictionary <string, string>(); IScheduler scheduler = PartnerFactory.Scheduler(Context.Config.SchedulerQueueName, Context.Config.SchedulerTableName, Context.Config); if (rewardOperations.AddRedemptionReward() == ResultCode.Success) { // Add a job to potentially reward user for their first Earn. payload[Key.RewardPayoutId.ToString()] = ((Guid)Context[Key.RewardPayoutId]).ToString(); payload[Key.PartnerCardId.ToString()] = (string)Context[Key.PartnerCardId]; payload[Key.PartnerRedeemedDealId.ToString()] = redeemedDealInfo.PartnerRedeemedDealId; payload[Key.RewardId.ToString()] = Context.Config.FirstEarnRewardId.ToString(); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ApplyRedemptionReward, JobDescription = redeemedDealInfo.GlobalUserId.ToString(), Orchestrated = true, Payload = payload }; scheduler.ScheduleJobAsync(scheduledJobDetails).Wait(); } // Add a job to potentially reward the person who referred this user for this user's first Earn. Context[Key.RedeemedDealId] = ((RedeemedDeal)Context[Key.RedeemedDeal]).Id; Guid globalUserId = ((RedeemedDealInfo)Context[Key.RedeemedDealInfo]).GlobalUserId; Context[Key.GlobalUserId] = globalUserId; string userId = globalUserId.ToString(); if (rewardOperations.AddReferredRedemptionReward() == ResultCode.Success) { payload[Key.GlobalUserId.ToString()] = userId; payload[Key.ReferralEvent.ToString()] = ReferralEvent.Signup.ToString(); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ApplyReferralReward, JobDescription = userId, Orchestrated = true, StartTime = DateTime.UtcNow, Payload = payload }; scheduler.ScheduleJobAsync(scheduledJobDetails).Wait(); } } }
public async Task Execute(ScheduledJobDetails details, CommerceLog log) { Log = log; Log.Verbose("Starting execution of job.\r\nDetails {0}", details); // Process rebate job for Visa. var visaProcessor = VisaSettlementProcessorFactory.VisaRebateProcessor(); await visaProcessor.Process().ConfigureAwait(false); Log.Verbose("Exeuction of job {0} complete ", details.JobId); }
/// <summary> /// Async wrapper for Commerce Worker Role /// </summary> /// <returns> /// A Task to be waited on /// </returns> private async Task RunAsync() { Log.Verbose("Running Commerce Worker role."); Log.Verbose("Checking if we can start processing jobs ..."); while (!ProcessJobs) { // is it fine ? too fast or slow? // we will finetune this after few tries on prod. Thread.Sleep(CommerceWorkerConfig.Instance.ProcessingLoopPollingInterval); } Log.Verbose("Entering processing loop."); do { try { Thread.Sleep(CommerceWorkerConfig.Instance.ProcessingLoopPollingInterval); ScheduledJobDetails jobDetails = await Scheduler.GetJobToProcessAsync(); if (jobDetails != null) { IJobRunner runner = JobRunnerFactory.Runner( jobDetails, Scheduler, CommerceWorkerConfig.Instance, Log); Log.Information("Running {0} job.", jobDetails.JobType); Tuple <IScheduler, ScheduledJobDetails> timerState = new Tuple <IScheduler, ScheduledJobDetails>(Scheduler, jobDetails); using (Timer tmr = new Timer(ExtendTimeout, timerState, WhenToExtendTimeout, WhenToExtendTimeout)) { await runner.RunJobAsync(jobDetails); } } } catch (Exception ex) { Log.Critical("An unknown error occurred during processing.", ex); } }while (ProcessJobs); Log.Information("Processing loop has exited."); // We are no longer processing jobs new now...just waiting to be killed when processing ends. while (!ExitRole) { #if !IntDebug && !IntRelease Log.Information("Wating for role to exit ..."); #endif Thread.Sleep(TimeSpan.FromSeconds(1)); } Log.Information("Role is shutting down ..."); }
/// <summary> /// Process MasterCard rebate file job execution. /// </summary> /// <param name="details"> /// Details of the job to be executed. /// </param> /// <param name="log"> /// Log within which to log status of job processing. /// </param> /// <returns> /// A task to execute the job. /// </returns> public async Task Execute(ScheduledJobDetails details, CommerceLog log) { Log = log; Log.Verbose("Starting execution of job.\r\nDetails {0}", details); // Process rebate job for MasterCard. ISettlementFileProcessor masterCardProcessor = MasterCardFileProcessorFactory.MasterCardRebateProcessor(UploadRebateFile); await masterCardProcessor.Process().ConfigureAwait(false); Log.Verbose("Exeuction of job {0} complete ", details.JobId); }
/// <summary> /// Process Pts File Job Execution /// </summary> /// <param name="details"> /// Details of the job we are executing here. /// </param> /// <param name="logger"> /// Handle to the logger /// </param> public async Task Execute(ScheduledJobDetails details, CommerceLog logger) { Logger = logger; Logger.Verbose("Starting execution of job \r\n " + "Details {0}", details); // Process PTS Job for FDC ISettlementFileProcessor ptsProcessor = FirstDataFileProcessorFactory.FirstDataPtsProcessor(OnPtsBuild); await ptsProcessor.Process().ConfigureAwait(false); Logger.Verbose("Exeuction of job {0} complete ", details.JobId); }
/// <summary> /// Process Amex Statement Credi File Job Execution /// </summary> /// <param name="details"> /// Details of the job we are executing here. /// </param> /// <param name="logger"> /// Handle to the logger /// </param> public async Task Execute(ScheduledJobDetails details, CommerceLog logger) { Logger = logger; Logger.Verbose("Starting execution of job \r\n Details {0}", details); // Process Statement Credit Acknowledgment files await ProcessStatementCreditResponse(); // Process Statement Credit files StatementCreditFileBuilder builder = new StatementCreditFileBuilder(); await builder.Build(OnStmtCreditFileBuild).ConfigureAwait(false); Logger.Verbose("Execution of job {0} complete ", details.JobId); }
/// <summary> /// Excute the job /// </summary> /// <param name="job"> /// The job to execute /// </param> /// <param name="details"> /// Job Details /// </param> /// <returns> /// Async Task Wrapper /// </returns> internal async Task ExecuteJob(IScheduledJob job, ScheduledJobDetails details) { try { await job.Execute(details, Log); } catch (Exception exception) { Log.Error(" Error in execution job \r\n" + " Details : {0}", exception, (int)ResultCode.JobExecutionError, details); } }
/// <summary> /// Process Amex Transaction Log File Job Execution /// </summary> /// <param name="details"> /// Details of the job we are executing here. /// </param> /// <param name="logger"> /// Handle to the logger /// </param> public async Task Execute(ScheduledJobDetails details, CommerceLog logger) { logger.Verbose("Starting execution of job \r\n Details {0}", details); string connectionString = CloudConfigurationManager.GetSetting("Lomo.Commerce.Fdc.Blob.ConnectionString"); AmexTransactionLogSftpClient ftpClient = new AmexTransactionLogSftpClient(logger); AmexTransactionLogFileBlobClient blobClient = AmexBlobFactory.TransactionLogBlobClient(connectionString, logger); string[] files = await ftpClient.DirectoryListAsync("AXP_MSF_TLOG", "outbox"); if (files != null) { foreach (string fileName in files) { MemoryStream memStream = new MemoryStream(); await ftpClient.DownloadFileAsync(fileName, memStream, "outbox").ConfigureAwait(false); // lets upload it to blob memStream.Position = 0; await blobClient.UploadAsync(memStream, fileName).ConfigureAwait(false); } } ICollection <string> listOfFiles = blobClient.RetrieveFilesToProcess(); if (listOfFiles != null) { foreach (string fileName in listOfFiles) { MemoryStream memStream = new MemoryStream(); memStream.Position = 0; await blobClient.DownloadAsync(memStream, fileName).ConfigureAwait(false); memStream.Position = 0; TransactionLogFileProcessor transactionLogFileProcessor = new TransactionLogFileProcessor() { TransactionLogFileName = fileName, TransactionLogFileStream = memStream }; await transactionLogFileProcessor.Process().ConfigureAwait(false); await blobClient.MarkAsProcessedAsync(fileName).ConfigureAwait(false); } } logger.Verbose("Execution of job {0} complete ", details.JobId); }
/// <summary> /// 1. Process the FDC Extract file. /// 2. Schedule the Process Pts Job /// </summary> /// <param name="details"> /// Details of the job we are executing here. /// </param> /// <param name="logger"> /// Handle to the logger /// </param> public async Task Execute(ScheduledJobDetails details, CommerceLog logger) { logger.Verbose("Starting execution of job \r\n " + "Details {0}", details); string connectionString = CloudConfigurationManager.GetSetting("Lomo.Commerce.Fdc.Blob.ConnectionString"); IFtpClient ftpClient = FirstDataFtpClientFactory.FirstDataExtractFtpClient(logger); FirstDataExtractBlobClient blobClient = FirstDataBlobClientFactory.FirstDataExtractBlobClient(connectionString, logger); string[] files = await ftpClient.DirectoryListAsync(); if (files != null) { foreach (string fileName in files) { MemoryStream memStream = new MemoryStream(); await ftpClient.DownloadFileAsync(fileName, memStream).ConfigureAwait(false); // lets upload it to blob memStream.Position = 0; await blobClient.UploadAsync(memStream, fileName).ConfigureAwait(false); } } // Now try to run all the pending files in the blob ICollection <string> listOfFiles = blobClient.RetrieveFilesToProcess(); if (listOfFiles != null) { foreach (string fileName in listOfFiles) { MemoryStream memStream = new MemoryStream(); memStream.Position = 0; await blobClient.DownloadAsync(memStream, fileName).ConfigureAwait(false); memStream.Position = 0; ISettlementFileProcessor extractProcessor = FirstDataFileProcessorFactory.FirstDataExtractProcessor(fileName, memStream); await extractProcessor.Process().ConfigureAwait(false); await blobClient.MarkAsProcessedAsync(fileName).ConfigureAwait(false); } } logger.Verbose("Execution of job {0} complete ", details.JobId); }
public Task Execute(ScheduledJobDetails details, CommerceLog logger) { this.log = logger; DateTime startDate; DateTime endDate = DateTime.UtcNow.Date; //Check if we have the last successful report run date in the payload if (details.Payload != null && details.Payload.ContainsKey(RewardNetworkReportLastRunDate)) { string strLastRunDate = details.Payload[RewardNetworkReportLastRunDate]; if (!DateTime.TryParse(strLastRunDate, out startDate)) { log.Error( "LastRunDate specified for the RewardNetworksReportJob in the payload is invalid. Invalid value is {0}", null, (int)ResultCode.JobExecutionError, strLastRunDate); } //If we have the last successful run date, add a day to it to set as the start date for the next run startDate = startDate.AddDays(1).Date; } else { startDate = DateTime.UtcNow.AddDays(-1).Date; } RewardNetworkReportProcessor rewardNetworkReportProcessor = new RewardNetworkReportProcessor(Context); rewardNetworkReportProcessor.GenerateReportForDays(startDate, endDate); DateTime lastSuccessfulRun = (DateTime)Context[Key.RewardNetworkReportLastRunDate]; if (details.Payload == null) { details.Payload = new Dictionary <string, string>(); } if (details.Payload.ContainsKey(RewardNetworkReportLastRunDate)) { details.Payload[RewardNetworkReportLastRunDate] = lastSuccessfulRun.ToString("yyyy-MM-dd"); } this.log.Verbose("Execution of job {0} complete ", details.JobId); return(Task.FromResult(0)); }
/// <summary> /// Process the rewards if applicable /// </summary> /// <param name="settlementDetail"> /// Settlement Details /// </param> /// <returns> /// Async Task Wrapper /// </returns> internal async Task ProcessRewardPayoutAsync(SettlementDetail settlementDetail) { if (settlementDetail.TransactionType == TransactionType.SettlementRedemption) { // First add a redemption reward to the redeeming user if they're enabled. if (EnableRedemptionRewards == true && WorkerActions.RewardRedemption(RewardOperations, Context) == ResultCode.Success) { // Add job to process the reward payout. Note that this job will be scheduled 30 minutes in the // future to guard against applying a reward for a transaction that was reversed in a later // record. ConcurrentDictionary <string, string> payload = new ConcurrentDictionary <string, string>(); payload[Key.RewardPayoutId.ToString()] = ((Guid)Context[Key.RewardPayoutId]).ToString(); payload[Key.PartnerCardId.ToString()] = (string)Context[Key.PartnerCardId]; payload[Key.PartnerRedeemedDealId.ToString()] = settlementDetail.TransactionId; IScheduler scheduler = PartnerFactory.Scheduler(CommerceWorkerConfig.Instance.SchedulerQueueName, CommerceWorkerConfig.Instance.SchedulerTableName, CommerceWorkerConfig.Instance); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ApplyRedemptionReward, JobDescription = settlementDetail.ConsumerId, Orchestrated = true, StartTime = DateTime.UtcNow.AddMinutes(30), Payload = payload }; await scheduler.ScheduleJobAsync(scheduledJobDetails).ConfigureAwait(false); } // Then add a referred redemption reward to the user who referred the redeeming user. WorkerActions.RewardReferredRedemption(RewardOperations, Context); } else { Context.Log.Verbose("No Bing Reward can be given for a reversed transaction."); } }
/// <summary> /// Queues claiming already claimed deals for the new card. /// </summary> /// <param name="response"> /// The AddCardResponse being built. /// </param> private void QueueClaimingDeals(AddCardResponse response) { Context.Log.Verbose("Queueing claiming user's existing claimed deals for the new card."); string userId = ((User)Context[Key.User]).GlobalId.ToString(); ConcurrentDictionary <string, string> payload = new ConcurrentDictionary <string, string>(); payload[Key.GlobalUserId.ToString()] = userId; payload[Key.CardId.ToString()] = General.IntegerFromGuid(response.NewCardId).ToString(); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ClaimDiscountsForNewCard, JobDescription = userId, Orchestrated = true, StartTime = DateTime.UtcNow, Payload = payload }; IScheduler scheduler = PartnerFactory.Scheduler(CommerceServiceConfig.Instance.SchedulerQueueName, CommerceServiceConfig.Instance.SchedulerTableName, CommerceServiceConfig.Instance); scheduler.ScheduleJobAsync(scheduledJobDetails).Wait(); }
/// <summary> /// Runs the orchestrated job described in the specified ScheduleJobDetails object. /// </summary> /// <param name="jobDetails"> /// The details of the orchestrated job to run. /// </param> /// <remarks> /// A job is limited to MaxJobRetries runs, even for successful runs, before being placed back in the queue to ensure /// that a job in an endless loop does not take down the entire worker role (pre-emptive multi-tasking.) But, if the job /// times out, another worker instance may attempt to run the job while it's still being run in another instance. Going /// forward, some mechanism to halt execution at time out should be added. /// </remarks> public async Task RunJobAsync(ScheduledJobDetails jobDetails) { OrchestratedExecutionResult executionResult; int maxRetries = CommerceConfig.MaxJobRetries; int retryLatency = CommerceConfig.InitialJobRetryLatency; int tryCount = 0; int tasksPerformed = 0; IOrchestratedJob job = JobFactory(jobDetails, Scheduler, Log); JobOrchestrator jobOrchestrator = null; if (job != null) { jobOrchestrator = new JobOrchestrator(job, jobDetails, Log); } if (jobOrchestrator != null) { do { try { Task <OrchestratedExecutionResult> executionResultTask = Task.Factory.StartNew(() => jobOrchestrator.Execute(out tasksPerformed)); executionResult = await executionResultTask; } catch (InvalidOperationException ex) { ResultCode resultCode; if (Enum.TryParse <ResultCode>(ex.Message, out resultCode) == true) { executionResult = OrchestratedExecutionResult.NonTerminalError; } else { throw; } } Log.Verbose("{0} orchestrated job completed {1} steps with result {2}.", jobDetails.JobType, tasksPerformed, executionResult); if (executionResult == OrchestratedExecutionResult.NonTerminalError && tryCount <= maxRetries) { Log.Verbose("Waiting {0} milliseconds before retrying job execution.", retryLatency); Thread.Sleep(retryLatency); retryLatency *= 2; } tryCount++; }while (executionResult != OrchestratedExecutionResult.TerminalError && tasksPerformed > 0 && tryCount <= maxRetries); // tear down the job here. executionResult = jobOrchestrator.Cleanup(executionResult); } else { executionResult = OrchestratedExecutionResult.TerminalError; } StringBuilder stringBuilder = new StringBuilder("{0} orchestrated job completed with result {1}."); if (executionResult == OrchestratedExecutionResult.NonTerminalError) { stringBuilder.Append(" Job will be sent to the back of the queue for reprocessing."); } Log.Information(stringBuilder.ToString(), jobDetails.JobType, executionResult); // Update Scheduler with result of running the job. switch (executionResult) { case OrchestratedExecutionResult.Success: await Scheduler.CompleteJobIterationAsync(jobDetails).ConfigureAwait(false); break; case OrchestratedExecutionResult.TerminalError: jobDetails.JobState = ScheduledJobState.Canceled; jobDetails.Payload = null; await Scheduler.UpdateJobAsync(jobDetails).ConfigureAwait(false); break; case OrchestratedExecutionResult.NonTerminalError: await Scheduler.ExponentiallyBackoffAsync(jobDetails, Log).ConfigureAwait(false); break; } }
/// <summary> /// Process the transaction log file /// </summary> /// <returns> /// Async Task Wrapper /// </returns> public async Task Process() { TransactionLogParser transactionLogParser = new TransactionLogParser(Context.Log); TransactionLogFile transactionLogFile = transactionLogParser.Parse(TransactionLogFileName, TransactionLogFileStream); if (transactionLogFile != null) { foreach (TransactionLogDetail detail in transactionLogFile.TransactionLogRecords) { // block the reversed transactions if (TransactionIdSet.Contains(detail.TransactionId) || detail.TransactionAmount <= 0) { continue; } TransactionIdSet.Add(detail.TransactionId); // 1. process the detail record here -> Insert as redeemed deal RedeemedDeal redeemedDeal = new RedeemedDeal() { AnalyticsEventId = Guid.NewGuid() }; Context[Key.RedeemedDeal] = redeemedDeal; MarshalRedeemDeal(detail); ResultCode result = RedeemedDealOperations.AddRedeemedDeal(); //2. If the record was processed successfully, attempt to add a redemption reward if applicable and analytics if (result == ResultCode.Created) { RedeemedDealInfo redemptionInfo = (RedeemedDealInfo)Context[Key.RedeemedDealInfo]; // First add a redemption reward to the redeeming user if they're enabled. if (EnableRedemptionRewards) { if (WorkerActions.RewardRedemption(RewardOperations, Context) == ResultCode.Success) { // Add job to process the reward payout. ConcurrentDictionary <string, string> payload = new ConcurrentDictionary <string, string>(); payload[Key.RewardPayoutId.ToString()] = ((Guid)Context[Key.RewardPayoutId]).ToString(); payload[Key.PartnerCardId.ToString()] = (string)Context[Key.PartnerCardId]; payload[Key.PartnerRedeemedDealId.ToString()] = redemptionInfo.PartnerRedeemedDealId; IScheduler scheduler = PartnerFactory.Scheduler(CommerceWorkerConfig.Instance.SchedulerQueueName, CommerceWorkerConfig.Instance.SchedulerTableName, CommerceWorkerConfig.Instance); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ApplyRedemptionReward, JobDescription = redemptionInfo.GlobalUserId.ToString(), Orchestrated = true, Payload = payload }; await scheduler.ScheduleJobAsync(scheduledJobDetails).ConfigureAwait(false); } } // Then add a referred redemption reward to the user who referred the redeeming user. Context[Key.RedeemedDealId] = ((RedeemedDeal)Context[Key.RedeemedDeal]).Id; Context[Key.GlobalUserId] = ((RedeemedDealInfo)Context[Key.RedeemedDealInfo]).GlobalUserId; WorkerActions.RewardReferredRedemption(RewardOperations, Context); // Update analytics. // For FDC this happens at AUTH time // Butfor Amex flow, we put analytics at the time of Transaction File Processing SharedUserLogic sharedUserLogic = new SharedUserLogic(Context, CommerceOperationsFactory.UserOperations(Context)); Context[Key.GlobalUserId] = redemptionInfo.GlobalUserId; User user = sharedUserLogic.RetrieveUser(); Analytics.AddRedemptionEvent(redemptionInfo.GlobalUserId, redeemedDeal.AnalyticsEventId, user.AnalyticsEventId, redemptionInfo.ParentDealId, redemptionInfo.Currency, redeemedDeal.AuthorizationAmount, redemptionInfo.DiscountAmount, redemptionInfo.GlobalId, (string)Context[Key.PartnerMerchantId], CommerceWorkerConfig.Instance); } } } }
/// <summary> /// Ping Job - Just logs the message. /// </summary> /// <param name="details"> /// Details of the job we are executing here. /// </param> /// <param name="logger"> /// Handle to the logger /// </param> public Task Execute(ScheduledJobDetails details, CommerceLog logger) { return(Task.Factory.StartNew(() => logger.Information("Executing ping job \r\n " + "Details {0}", details))); }
public Task Execute(ScheduledJobDetails details, CommerceLog logger) { return(Task.Factory.StartNew(() => { })); }
/// <summary> /// Exponentially backoff the next run time of a job after every failure (Non-Terminal) upto a defined MAX. /// Currently, only one time jobs are exponentially backed off, and recurring jobs are just scheduled for next occurence. /// </summary> /// <param name="scheduler"> /// Instance of the Scheduler /// </param> /// <param name="jobDetails"> /// Scheduled Job Details. /// </param> /// <returns> /// Async Task Wrapper /// </returns> public static async Task ExponentiallyBackoffAsync(this IScheduler scheduler, ScheduledJobDetails jobDetails, CommerceLog log) { // this branch should not happen once we schedule the job once. if (jobDetails != null && jobDetails.Recurrence != null) { // if job is scheduled to run only once if (jobDetails.Recurrence.Count == 1) { // initialize retry count if it does not exist if (jobDetails.Payload == null) { jobDetails.Payload = new Dictionary <string, string>(); jobDetails.Payload["RetryCount"] = "0"; } else if (!jobDetails.Payload.ContainsKey("RetryCount")) { jobDetails.Payload["RetryCount"] = "0"; } int retryCount; if (!int.TryParse(jobDetails.Payload["RetryCount"], out retryCount)) { retryCount = 0; } //Important: Since the job is a run once job, so recurrence for the next retry is solely // dependent on the retry interval. Past recurrence is immaterial. jobDetails.Recurrence = new Recurrence() { Frequency = RecurrenceFrequency.Second, Count = 1, Interval = GetWaitTimeInSeconds(retryCount) }; log.Verbose("Job Id {0} has been retried {1} times, back off to try the next time after {2} seconds", jobDetails.JobId, retryCount, jobDetails.Recurrence.Interval); // increment retry count in payload jobDetails.Payload["RetryCount"] = (retryCount + 1).ToString(CultureInfo.InvariantCulture); // schedule it to run later await scheduler.UpdateJobAsync(jobDetails).ConfigureAwait(false); } else // recurring job { // just mark current iteration as done. We will try again next time await scheduler.CompleteJobIterationAsync(jobDetails).ConfigureAwait(false); } } else { log.Warning("After first run of job, job or recurrence should not be null."); await Task.Factory.StartNew(() => { }).ConfigureAwait(false); } }
/// <summary> /// Initializes the IOrchestratedJob instance /// </summary> /// <param name="jobDetails"> /// The details of the job being run. /// </param> /// <param name="scheduler"> /// The scheduler managing the jobs. /// </param> public void Initialize(ScheduledJobDetails jobDetails, IScheduler scheduler) { JobDetails = jobDetails; Scheduler = scheduler; }
/// <summary> /// Process the response file /// </summary> /// <returns> /// Async Task Wrapper /// </returns> public virtual async Task <bool> ProcessAsync() { OfferRegistrationResponseFileParser parser = new OfferRegistrationResponseFileParser(Context.Log); OfferRegistrationResponseFile responseFile = parser.Parse(ResponseFileName, ResponseFileStream); bool submissionValid = true; if (responseFile != null) { if (responseFile.Header.ResponseCode == "A") { foreach (OfferRegistrationResponseDetail record in responseFile.ResponseRecords) { Context[Key.PartnerDealId] = record.OfferId; Context[Key.Partner] = Partner.Amex; IDealOperations dealOperations = CommerceOperationsFactory.DealOperations(Context); Guid? discountId = dealOperations.RetrieveDiscountIdFromPartnerDealId(); Context[Key.GlobalDealId] = discountId.Value; SharedDealLogic dealLogic = new SharedDealLogic(Context, CommerceOperationsFactory.DealOperations(Context)); Deal deal = dealLogic.RetrieveDeal(); // for each record - check the status and process accordingly if (record.ResponseCode == "A") { if (record.ActionCode == OfferRegistrationActionCodeType.Add) { // Possible Race condition in this part of the code // By time time we check whether all partners are registered, things could change in DB // this is not a concern right now but we need to figure it out. bool allOtherPartnersRegistered = true; foreach (PartnerDealInfo partnerDealInfo in deal.PartnerDealInfoList) { if (partnerDealInfo.PartnerId != Partner.Amex) { if (partnerDealInfo.PartnerDealRegistrationStatusId != PartnerDealRegistrationStatus.Complete) { allOtherPartnersRegistered = false; break; } } } // now update deal status deal.DealStatusId = DealStatus.PendingAutoLinking; foreach (PartnerDealInfo partnerDealInfo in deal.PartnerDealInfoList) { if (partnerDealInfo.PartnerId == Partner.Amex) { partnerDealInfo.PartnerDealRegistrationStatusId = PartnerDealRegistrationStatus.Complete; } } Context[Key.Deal] = deal; dealOperations.RegisterDeal(); if (allOtherPartnersRegistered) { // schedule autolinking ConcurrentDictionary <string, string> payload = new ConcurrentDictionary <string, string>(); payload[Key.GlobalDealId.ToString()] = deal.GlobalId.ToString(); ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails { JobId = Guid.NewGuid(), JobType = ScheduledJobType.ClaimDiscountForExistingCards, Orchestrated = true, StartTime = DateTime.UtcNow, Payload = payload }; await Scheduler.ScheduleJobAsync(scheduledJobDetails).ConfigureAwait(false); } } else if (record.ActionCode == OfferRegistrationActionCodeType.Update) { // previously registered, and update was successful. foreach (PartnerDealInfo partnerDealInfo in deal.PartnerDealInfoList) { if (partnerDealInfo.PartnerId == Partner.Amex) { partnerDealInfo.PartnerDealRegistrationStatusId = PartnerDealRegistrationStatus.Complete; } } Context[Key.Deal] = deal; dealOperations.RegisterDeal(); // TODO:Tell Deal Server we are done. // ConcurrentDictionary<string, string> payload = new ConcurrentDictionary<string, string>(); // payload[Key.DealId.ToString()] = deal.Id.ToString(); // ScheduledJobDetails scheduledJobDetails = new ScheduledJobDetails // { // JobId = Guid.NewGuid(), // JobType = ScheduledJobType.DiscountActivationJob, // Orchestrated = false, // StartTime = DateTime.UtcNow, // Payload = payload // }; // await Scheduler.ScheduleJobAsync(scheduledJobDetails).ConfigureAwait(false); } } else { Context.Log.Warning("Attempt to register a deal with Amex failed\r\nOffer Id {0}\r\n Reason {1}", (int)ResultCode.SubmissionRejected, record.OfferId, record.ResponseCodeMessage); // update the deal to reflect error foreach (PartnerDealInfo partnerDealInfo in deal.PartnerDealInfoList) { if (partnerDealInfo.PartnerId == Partner.Amex) { partnerDealInfo.PartnerDealRegistrationStatusId = PartnerDealRegistrationStatus.Error; } } Context[Key.Deal] = deal; dealOperations.RegisterDeal(); } } } else { // file submission was rejected. submissionValid = false; } } return(submissionValid); }