/*
Copyright (c) Microsoft Corporation
All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in
compliance with the License. You may obtain a copy of the License
at http://www.apache.org/licenses/LICENSE-2.0
THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER
EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF
TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT.
See the Apache Version 2.0 License for specific language governing permissions and
limitations under the License.
*/
//
// � Microsoft Corporation. All rights reserved.
//
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Security.Principal;
using System.Text;
using System.Xml;
using System.Text.RegularExpressions;
using Microsoft.Research.DryadLinq.Internal;
namespace Microsoft.Research.DryadLinq
{
///
/// Where is the JM process executed?
///
internal enum ExecutionKind
{
///
/// The JM has been submitted to the job scheduler.
///
JobScheduler,
///
/// The JM is run on the local machine.
///
// LocalJM,
///
/// The query is run using local debug.
///
LocalDebug
}
///
/// This class encapsulates the means to execute in the background a collection of queries.
///
internal class JobExecutor
{
///
/// The jobSubmission is only used if we use a job scheduler.
///
private IHpcLinqJobSubmission jobSubmission;
///
/// The Process is used only if we don't use a job scheduler.
///
// private Process dryadProc;
///
/// Where is the query being executed?
///
private ExecutionKind executionKind;
///
/// Where is the query being executed?
///
internal ExecutionKind ExecutionKind
{
get { return this.executionKind; }
}
///
/// Error message when job fails.
///
// private string errorMsg;
///
/// Keep status here when it no longer changes.
///
private JobStatus currentStatus;
private HpcLinqContext m_context;
///
/// Create a new job executor object.
///
public JobExecutor(HpcLinqContext context)
{
// use a new job submission object for each query
// this.errorMsg = "";
this.m_context = context;
this.currentStatus = JobStatus.NotSubmitted;
if (context.Runtime is HpcQueryRuntime)
{
YarnJobSubmission job = new YarnJobSubmission(context);
// job.LocalJM = false;
job.Initialize();
this.executionKind = ExecutionKind.JobScheduler;
this.jobSubmission = job;
}
else
{
throw new DryadLinqException(HpcLinqErrorCode.UnsupportedSchedulerType,
String.Format(SR.UnsupportedSchedulerType, context.Runtime));
}
#if REMOVE
case SchedulerKind.LocalJM:
{
HpcJobSubmission job = new HpcJobSubmission(runtime);
job.LocalJM = true;
job.Initialize();
this.executionKind = ExecutionKind.JobScheduler;
this.jobSubmission = job;
DryadLinq.SchedulerType = SchedulerKind.Hpc;
break;
}
#endif
}
///
/// Add a specified resource to the dryad computation.
///
/// Resource to add.
/// The pathname to the resource to use in the xml plan.
public string AddResource(string resource)
{
switch (this.executionKind)
{
case ExecutionKind.JobScheduler:
{
this.jobSubmission.AddLocalFile(resource);
FileInfo resourceInfo = new FileInfo(resource);
return resourceInfo.Name;
}
#if REMOVE
case ExecutionKind.LocalJM:
{
return resource;
}
#endif
default:
{
throw new DryadLinqException(HpcLinqErrorCode.UnsupportedExecutionKind,
SR.UnsupportedExecutionKind);
}
}
}
///
/// Add a resource to a job. Check whether there are clashes in resource names.
///
/// JobSubmission object which will hold the resources.
/// Pathname to file to add as a resource.
private void AddResource(IHpcLinqJobSubmission jobSubmission, string file)
{
// extract basename
string basename = Path.GetFileName(file);
this.jobSubmission.AddLocalFile(file);
}
///
/// Start executing the dryad job in the background.
///
/// Full path to query plan XML file.
public void ExecuteAsync(string queryPlanPath)
{
switch (this.executionKind)
{
case ExecutionKind.JobScheduler:
{
lock (this)
{
FileInfo xmlHostInfo = new FileInfo(StaticConfig.XmlHostPath);
// Consturct the Graph Manager cmd line.
//string queryPlanFileName = Path.GetFileName(queryPlanPath);
this.jobSubmission.AddJobOption("cmdline", xmlHostInfo.Name + " " + queryPlanPath);
AddResource(this.jobSubmission, StaticConfig.XmlHostPath);
AddResource(this.jobSubmission, queryPlanPath);
// Add black and white list file, additional resources if passed as a command line
string[] args = StaticConfig.XmlExecHostArgs.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
for (int i = 0; i < args.Length; ++i)
{
string arg = args[i];
if (arg.Equals("-bw") || arg.Equals("-r"))
{
AddResource(this.jobSubmission, args[++i]);
}
}
this.jobSubmission.SubmitJob();
this.currentStatus = JobStatus.Waiting;
}
break;
}
#if REMOVE
case ExecutionKind.LocalJM:
{
lock (this)
{
// Invoking Dryad as a separate process:
if (DryadLinq.APEnvironmentPath != null)
{
Environment.SetEnvironmentVariable("APENVIRONMENTPATH", DryadLinq.APEnvironmentPath);
}
if (DryadLinq.APConfigPath != null)
{
Environment.SetEnvironmentVariable("APCONFIGPATH", DryadLinq.APConfigPath);
}
ProcessStartInfo procStartInfo = new ProcessStartInfo();
procStartInfo.FileName = DryadLinq.XmlHostPath;
procStartInfo.Arguments = dryadProgram;
procStartInfo.RedirectStandardOutput = true;
procStartInfo.RedirectStandardError = false;
procStartInfo.UseShellExecute = false;
this.dryadProc = Process.Start(procStartInfo);
this.currentStatus = JobStatus.Running;
}
break;
}
#endif
default:
{
throw new NotImplementedException();
}
}
}
///
/// Wait for job completion.
///
/// The status of the job.
public JobStatus WaitForCompletion()
{
if (this.Terminated())
{
return this.currentStatus;
}
JobStatus status;
switch (this.executionKind)
{
case ExecutionKind.JobScheduler:
{
int sleep = 3000;
int maxSleep = 20000;
int retries = 0;
while (true)
{
try
{
string msg = null;
switch (status = this.GetStatus())
{
case JobStatus.Success:
case JobStatus.Failure:
case JobStatus.Cancelled:
return status;
case JobStatus.NotSubmitted:
msg = "The job to create this table has not been submitted yet. Waiting ...";
break;
case JobStatus.Running:
msg = "The job to create this table is still running. Waiting ...";
break;
case JobStatus.Waiting:
msg = "The job to create this table is still queued. Waiting ...";
break;
default:
throw new DryadLinqException(HpcLinqErrorCode.UnexpectedJobStatus,
String.Format(SR.UnexpectedJobStatus, status.ToString()));
}
retries = 0;
HpcClientSideLog.Add(msg);
}
catch (System.Net.WebException)
{
retries++;
sleep = maxSleep;
HpcClientSideLog.Add("Error contacting web server while querying job status. Waiting ...");
}
if (retries > 5)
{
throw new DryadLinqException(HpcLinqErrorCode.JobStatusQueryError,
SR.JobStatusQueryError);
}
if (sleep < maxSleep)
{
sleep = Math.Min(maxSleep, (int)(sleep * 1.1));
}
System.Threading.Thread.Sleep(sleep);
}
}
#if REMOVE
case ExecutionKind.LocalJM:
{
StreamReader dryadProcOut = this.dryadProc.StandardOutput;
StreamWriter stdout = null;
try
{
if (DryadLinq.LocalJMStdout != null)
{
stdout = new StreamWriter(DryadLinq.LocalJMStdout);
}
string outLine;
while ((outLine = dryadProcOut.ReadLine()) != null)
{
if (DryadLinq.Verbose)
{
Console.WriteLine(outLine);
}
if (stdout != null)
{
stdout.WriteLine(outLine);
}
}
}
finally
{
if (stdout != null) stdout.Close();
}
this.dryadProc.WaitForExit();
status = this.GetStatus();
this.dryadProc.Close();
return status;
}
#endif
default:
{
throw new NotImplementedException();
}
}
}
///
/// True if the background execution has terminated.
///
///
public bool Terminated()
{
// First check whether the status is finalized
switch (this.currentStatus)
{
case JobStatus.Cancelled:
case JobStatus.Failure:
case JobStatus.Success:
case JobStatus.NotSubmitted:
// this status can't change any more
return true;
case JobStatus.Running:
case JobStatus.Waiting:
// re-evaluate status
return false;
default:
throw new DryadLinqException(HpcLinqErrorCode.UnexpectedJobStatus,
String.Format(SR.UnexpectedJobStatus, this.currentStatus.ToString()));
}
}
///
/// Find out the status of the job.
///
/// The job status.
internal JobStatus GetStatus()
{
if (this.Terminated())
{
return this.currentStatus;
}
lock (this)
{
if (this.executionKind == ExecutionKind.JobScheduler)
{
this.currentStatus = this.jobSubmission.GetStatus();
}
#if REMOVE
else if (this.executionKind == ExecutionKind.LocalJM)
{
if (!dryadProc.HasExited)
{
this.currentStatus = JobStatus.Running;
}
else
{
if (this.dryadProc.ExitCode != 0)
{
this.currentStatus = JobStatus.Failure;
this.errorMsg = "HpcLinq graph manager process failed with exit code " + dryadProc.ExitCode.ToString();
}
else
this.currentStatus = JobStatus.Success;
}
}
#endif
else
{
throw new DryadLinqException(HpcLinqErrorCode.UnsupportedExecutionKind,
SR.UnsupportedExecutionKind);
}
return currentStatus;
}
}
internal void SetStatus(JobStatus js)
{
this.currentStatus = js;
}
///
/// Cancel the job computation.
///
/// The actual status of the job. This may be 'Cancelled', but if the job has completed it may be 'Success' as well.
internal JobStatus Cancel()
{
if (this.Terminated())
{
return currentStatus;
}
lock (this)
{
switch (this.executionKind)
{
case ExecutionKind.JobScheduler:
{
this.currentStatus = this.jobSubmission.TerminateJob();
return this.currentStatus;
}
#if REMOVE
case ExecutionKind.LocalJM:
{
if (this.dryadProc == null)
{
return JobStatus.NotSubmitted;
}
if (!this.dryadProc.HasExited)
{
this.dryadProc.Kill();
this.errorMsg = "Job Manager was cancelled";
this.currentStatus = JobStatus.Cancelled;
}
return this.GetStatus();
}
#endif
default:
{
throw new DryadLinqException(HpcLinqErrorCode.UnsupportedExecutionKind,
SR.UnsupportedExecutionKind);
}
}
}
}
///
/// For failed jobs this contains an error message.
///
internal string ErrorMsg
{
get
{
switch (this.executionKind)
{
case ExecutionKind.JobScheduler:
{
return this.jobSubmission.ErrorMsg;
}
#if REMOVE
case ExecutionKind.LocalJM:
{
return this.errorMsg;
}
#endif
default:
{
throw new DryadLinqException(HpcLinqErrorCode.UnsupportedExecutionKind,
SR.UnsupportedExecutionKind);
}
}
}
}
internal IHpcLinqJobSubmission JobSubmission
{
get { return this.jobSubmission; }
}
}
}