From 0efed221ee29c4cebffc6da92579c0ab1616130b Mon Sep 17 00:00:00 2001 From: Aditya Bist Date: Tue, 16 Oct 2018 13:24:32 -0700 Subject: [PATCH] Agent/improve steps (#710) * added stepInfo for editing jobs/steps * added schedules to history requests * added alerts and schedules to history * formatting * code review comments * removed smo import * changed and optimized histories, steps, alerts and operators --- .../Agent/AgentService.cs | 84 ++++++++----- .../Agent/Common/AgentUtilities.cs | 110 ++++++++++-------- .../Agent/Contracts/AgentJobInfo.cs | 33 +++++- .../Agent/Contracts/AgentJobRequest.cs | 6 +- .../Agent/Jobs/JobFetcher.cs | 18 ++- .../Agent/Jobs/JobProperties.cs | 17 ++- .../Agent/AgentTestUtils.cs | 5 +- 7 files changed, 180 insertions(+), 93 deletions(-) diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/AgentService.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/AgentService.cs index 45920e82..66e05703 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/AgentService.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/AgentService.cs @@ -27,7 +27,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent /// public class AgentService { - private Dictionary jobs = null; private ConnectionService connectionService = null; private static readonly Lazy instance = new Lazy(() => new AgentService()); @@ -148,11 +147,11 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent var serverConnection = new ServerConnection(sqlConnection); var fetcher = new JobFetcher(serverConnection); var filter = new JobActivityFilter(); - this.jobs = fetcher.FetchJobs(filter); + var jobs = fetcher.FetchJobs(filter); var agentJobs = new List(); - if (this.jobs != null) + if (jobs != null) { - foreach (var job in this.jobs.Values) + foreach (var job in jobs.Values) { agentJobs.Add(AgentUtilities.ConvertToAgentJobInfo(job)); } @@ -186,42 +185,63 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent out connInfo); if (connInfo != null) { - ConnectionServiceInstance.TryFindConnection(parameters.OwnerUri, out connInfo); CDataContainer dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); var jobs = dataContainer.Server.JobServer.Jobs; Tuple tuple = CreateSqlConnection(connInfo, parameters.JobId); SqlConnectionInfo sqlConnInfo = tuple.Item1; DataTable dt = tuple.Item2; ServerConnection connection = tuple.Item3; + + // Send Steps, Alerts and Schedules with job history in background + // Add steps to the job if any + JobStepCollection steps = jobs[parameters.JobName].JobSteps; + var jobSteps = new List(); + foreach (JobStep step in steps) + { + jobSteps.Add(AgentUtilities.ConvertToAgentJobStepInfo(step, parameters.JobId, parameters.JobName)); + } + result.Steps = jobSteps.ToArray(); + + // Add schedules to the job if any + JobScheduleCollection schedules = jobs[parameters.JobName].JobSchedules; + var jobSchedules = new List(); + foreach (JobSchedule schedule in schedules) + { + jobSchedules.Add(AgentUtilities.ConvertToAgentScheduleInfo(schedule)); + } + result.Schedules = jobSchedules.ToArray(); + + // Alerts + AlertCollection alerts = dataContainer.Server.JobServer.Alerts; + var jobAlerts = new List(); + foreach (Alert alert in alerts) + { + if (alert.JobName == parameters.JobName) + { + jobAlerts.Add(alert); + } + } + result.Alerts = AgentUtilities.ConvertToAgentAlertInfo(jobAlerts); + + // Add histories int count = dt.Rows.Count; List jobHistories = new List(); if (count > 0) { var job = dt.Rows[0]; - string jobName = Convert.ToString(job[AgentUtilities.UrnJobName], System.Globalization.CultureInfo.InvariantCulture); - Guid jobId = (Guid) job[AgentUtilities.UrnJobId]; + Guid jobId = (Guid)job[AgentUtilities.UrnJobId]; int runStatus = Convert.ToInt32(job[AgentUtilities.UrnRunStatus], System.Globalization.CultureInfo.InvariantCulture); - var t = new LogSourceJobHistory(jobName, sqlConnInfo, null, runStatus, jobId, null); + var t = new LogSourceJobHistory(parameters.JobName, sqlConnInfo, null, runStatus, jobId, null); var tlog = t as ILogSource; tlog.Initialize(); var logEntries = t.LogEntries; - // Send Steps, Alerts and Schedules with job history in background - JobStepCollection steps = jobs[jobName].JobSteps; - JobScheduleCollection schedules = jobs[jobName].JobSchedules; - List alerts = new List(); - foreach (Alert alert in dataContainer.Server.JobServer.Alerts) - { - if (alert.JobID == jobId && alert.JobName == jobName) - { - alerts.Add(alert); - } - } - jobHistories = AgentUtilities.ConvertToAgentJobHistoryInfo(logEntries, job, steps, schedules, alerts); + // Finally add the job histories + jobHistories = AgentUtilities.ConvertToAgentJobHistoryInfo(logEntries, job, steps); + result.Histories = jobHistories.ToArray(); + result.Success = true; tlog.CloseReader(); } - result.Jobs = jobHistories.ToArray(); - result.Success = true; connection.Disconnect(); await requestContext.SendResult(result); } @@ -320,12 +340,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent internal async Task HandleDeleteAgentJobRequest(DeleteAgentJobParams parameters, RequestContext requestContext) { - var result = await ConfigureAgentJob( - parameters.OwnerUri, - parameters.Job.Name, - parameters.Job, - ConfigAction.Drop, - ManagementUtils.asRunType(parameters.TaskExecutionMode)); + var result = await ConfigureAgentJob( + parameters.OwnerUri, + parameters.Job.Name, + parameters.Job, + ConfigAction.Drop, + ManagementUtils.asRunType(parameters.TaskExecutionMode)); await requestContext.SendResult(new ResultStatus() { @@ -418,7 +438,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent #endregion // "Jobs Handlers" - #region "Alert Handlers" + #region "Alert Handlers" /// /// Handle request to get the alerts list @@ -1059,12 +1079,12 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent ConnectionInfo connInfo; ConnectionServiceInstance.TryFindConnection(ownerUri, out connInfo); dataContainer = CDataContainer.CreateDataContainer(connInfo, databaseExists: true); - + XmlDocument jobDoc = CreateJobXmlDocument(dataContainer.Server.Name.ToUpper(), jobName); dataContainer.Init(jobDoc.InnerXml); - + STParameters param = new STParameters(dataContainer.Document); - string originalName = jobInfo != null && !string.Equals(jobName, jobInfo.Name) ? jobName : string.Empty; + string originalName = jobInfo != null && !string.Equals(jobName, jobInfo.Name) ? jobName : string.Empty; param.SetParam("job", configAction == ConfigAction.Update ? jobName : string.Empty); param.SetParam("jobid", string.Empty); diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentUtilities.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentUtilities.cs index 528942d4..048632e5 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentUtilities.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Common/AgentUtilities.cs @@ -34,14 +34,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent public const string UrnServer = "Server"; internal const string UrnServerTime = "CurrentDate"; - public static AgentJobInfo ConvertToAgentJobInfo(JobProperties job) + public static AgentJobInfo ConvertToAgentJobInfo(JobProperties job) { return new AgentJobInfo { Name = job.Name, Description = job.Description, - CurrentExecutionStatus = job.CurrentExecutionStatus, - LastRunOutcome = job.LastRunOutcome, + CurrentExecutionStatus = (Contracts.JobExecutionStatus) job.CurrentExecutionStatus, + LastRunOutcome = (Contracts.CompletionResult) job.LastRunOutcome, CurrentExecutionStep = job.CurrentExecutionStep, Enabled = job.Enabled, HasTarget = job.HasTarget, @@ -57,7 +57,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent }; } - internal static AgentJobStep ConvertToAgentJobStepInfo(JobStep step, LogSourceJobHistory.LogEntryJobHistory logEntry, string jobId) + internal static AgentJobStep ConvertToAgentJobStep(JobStep step, LogSourceJobHistory.LogEntryJobHistory logEntry, string jobId) { AgentJobStepInfo stepInfo = new AgentJobStepInfo(); stepInfo.JobId = jobId; @@ -88,6 +88,30 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent return jobStep; } + internal static AgentJobStepInfo ConvertToAgentJobStepInfo(JobStep step, string jobId, string jobName) + { + AgentJobStepInfo stepInfo = new AgentJobStepInfo(); + stepInfo.JobId = jobId; + stepInfo.JobName = jobName; + stepInfo.StepName = step.Name; + stepInfo.SubSystem = step.SubSystem.ToString(); + stepInfo.Id = step.ID; + stepInfo.FailureAction = step.OnFailAction.ToString(); + stepInfo.SuccessAction = step.OnSuccessAction.ToString(); + stepInfo.FailStepId = step.OnFailStep; + stepInfo.SuccessStepId = step.OnSuccessStep; + stepInfo.Command = step.Command; + stepInfo.CommandExecutionSuccessCode = step.CommandExecutionSuccessCode; + stepInfo.DatabaseName = step.DatabaseName; + stepInfo.DatabaseUserName = step.DatabaseUserName; + stepInfo.Server = step.Server; + stepInfo.OutputFileName = step.OutputFileName; + stepInfo.RetryAttempts = step.RetryAttempts; + stepInfo.RetryInterval = step.RetryInterval; + stepInfo.ProxyName = step.ProxyName; + return stepInfo; + } + internal static AgentScheduleInfo ConvertToAgentScheduleInfo(JobSchedule schedule) { AgentScheduleInfo scheduleInfo = new AgentScheduleInfo(); @@ -113,37 +137,41 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent return scheduleInfo; } - internal static AgentAlertInfo ConvertToAgentAlertInfo(Alert alert) + internal static AgentAlertInfo[] ConvertToAgentAlertInfo(List alerts) { - AgentAlertInfo alertInfo = new AgentAlertInfo(); - alertInfo.Id = alert.ID; - alertInfo.Name = alert.Name; - alertInfo.DelayBetweenResponses = alert.DelayBetweenResponses; - alertInfo.EventDescriptionKeyword = alert.EventDescriptionKeyword; - alertInfo.EventSource = alert.EventSource; - alertInfo.HasNotification = alert.HasNotification; - alertInfo.IncludeEventDescription = (Contracts.NotifyMethods) alert.IncludeEventDescription; - alertInfo.IsEnabled = alert.IsEnabled; - alertInfo.JobId = alert.JobID.ToString(); - alertInfo.JobName = alert.JobName; - alertInfo.LastOccurrenceDate = alert.LastOccurrenceDate.ToString(); - alertInfo.LastResponseDate = alert.LastResponseDate.ToString(); - alertInfo.MessageId = alert.MessageID; - alertInfo.NotificationMessage = alert.NotificationMessage; - alertInfo.OccurrenceCount = alert.OccurrenceCount; - alertInfo.PerformanceCondition = alert.PerformanceCondition; - alertInfo.Severity = alert.Severity; - alertInfo.DatabaseName = alert.DatabaseName; - alertInfo.CountResetDate = alert.CountResetDate.ToString(); - alertInfo.CategoryName = alert.CategoryName; - alertInfo.AlertType = (Contracts.AlertType) alert.AlertType; - alertInfo.WmiEventNamespace = alert.WmiEventNamespace; - alertInfo.WmiEventQuery = alert.WmiEventQuery; - return alertInfo; + var result = new List(); + foreach(Alert alert in alerts) + { + AgentAlertInfo alertInfo = new AgentAlertInfo(); + alertInfo.Id = alert.ID; + alertInfo.Name = alert.Name; + alertInfo.DelayBetweenResponses = alert.DelayBetweenResponses; + alertInfo.EventDescriptionKeyword = alert.EventDescriptionKeyword; + alertInfo.EventSource = alert.EventSource; + alertInfo.HasNotification = alert.HasNotification; + alertInfo.IncludeEventDescription = (Contracts.NotifyMethods) alert.IncludeEventDescription; + alertInfo.IsEnabled = alert.IsEnabled; + alertInfo.JobId = alert.JobID.ToString(); + alertInfo.JobName = alert.JobName; + alertInfo.LastOccurrenceDate = alert.LastOccurrenceDate.ToString(); + alertInfo.LastResponseDate = alert.LastResponseDate.ToString(); + alertInfo.MessageId = alert.MessageID; + alertInfo.NotificationMessage = alert.NotificationMessage; + alertInfo.OccurrenceCount = alert.OccurrenceCount; + alertInfo.PerformanceCondition = alert.PerformanceCondition; + alertInfo.Severity = alert.Severity; + alertInfo.DatabaseName = alert.DatabaseName; + alertInfo.CountResetDate = alert.CountResetDate.ToString(); + alertInfo.CategoryName = alert.CategoryName; + alertInfo.AlertType = (Contracts.AlertType) alert.AlertType; + alertInfo.WmiEventNamespace = alert.WmiEventNamespace; + alertInfo.WmiEventQuery = alert.WmiEventQuery; + result.Add(alertInfo); + } + return result.ToArray(); } - public static List ConvertToAgentJobHistoryInfo(List logEntries, - DataRow jobRow, JobStepCollection steps, JobScheduleCollection schedules, List alerts) + public static List ConvertToAgentJobHistoryInfo(List logEntries, DataRow jobRow, JobStepCollection steps) { List jobs = new List(); // get all the values for a job history @@ -174,26 +202,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent foreach (JobStep step in steps) { var jobId = jobRow[UrnJobId].ToString(); - jobSteps.Add(AgentUtilities.ConvertToAgentJobStepInfo(step, logEntry, jobId)); + jobSteps.Add(AgentUtilities.ConvertToAgentJobStep(step, logEntry, jobId)); } jobHistoryInfo.Steps = jobSteps.ToArray(); - - // Add schedules to the job if any - var jobSchedules = new List(); - foreach (JobSchedule schedule in schedules) - { - jobSchedules.Add(AgentUtilities.ConvertToAgentScheduleInfo(schedule)); - } - jobHistoryInfo.Schedules = jobSchedules.ToArray(); - - // Add alerts to the job if any - var jobAlerts = new List(); - foreach (Alert alert in alerts) - { - jobAlerts.Add(AgentUtilities.ConvertToAgentAlertInfo(alert)); - } - jobHistoryInfo.Alerts = jobAlerts.ToArray(); jobs.Add(jobHistoryInfo); } return jobs; diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobInfo.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobInfo.cs index 3631e716..103c4949 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobInfo.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobInfo.cs @@ -8,6 +8,25 @@ using Microsoft.SqlTools.ServiceLayer.Agent; namespace Microsoft.SqlTools.ServiceLayer.Agent.Contracts { + public enum JobCompletionActionCondition + { + Never = 0, + OnSuccess = 1, + OnFailure = 2, + Always = 3 + } + + public enum JobExecutionStatus + { + Executing = 1, + WaitingForWorkerThread = 2, + BetweenRetries = 3, + Idle = 4, + Suspended = 5, + WaitingForStepToFinish = 6, + PerformingCompletionAction = 7 + } + /// /// a class for storing various properties of agent jobs, /// used by the Job Activity Monitor @@ -17,8 +36,8 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent.Contracts public string Name { get; set; } public string Owner { get; set; } public string Description { get; set; } - public int CurrentExecutionStatus { get; set; } - public int LastRunOutcome { get; set; } + public JobExecutionStatus CurrentExecutionStatus { get; set; } + public CompletionResult LastRunOutcome { get; set; } public string CurrentExecutionStep { get; set; } public bool Enabled { get; set; } public bool HasTarget { get; set; } @@ -31,5 +50,15 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent.Contracts public string LastRun { get; set; } public string NextRun { get; set; } public string JobId { get; set; } + public string OperatorToEmail { get; set; } + public string OperatorToPage { get; set; } + public int StartStepID { get; set; } + public JobCompletionActionCondition EmailLevel { get; set; } + public JobCompletionActionCondition PageLevel { get; set; } + public JobCompletionActionCondition EventLogLevel { get; set; } + public JobCompletionActionCondition DeleteLevel { get; set; } + public AgentJobStepInfo[] JobSteps { get; set; } + public AgentScheduleInfo[] JobSchedules { get; set; } + public AgentAlertInfo[] Alerts { get; set; } } } diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobRequest.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobRequest.cs index f5bee563..3caed1f1 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobRequest.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Contracts/AgentJobRequest.cs @@ -135,6 +135,7 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent.Contracts public string OwnerUri { get; set; } public string JobId { get; set; } + public string JobName { get; set; } } /// @@ -142,7 +143,10 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent.Contracts /// public class AgentJobHistoryResult : ResultStatus { - public AgentJobHistoryInfo[] Jobs { get; set; } + public AgentJobHistoryInfo[] Histories { get; set; } + public AgentJobStepInfo[] Steps { get; set; } + public AgentScheduleInfo[] Schedules { get; set; } + public AgentAlertInfo[] Alerts { get; set ;} } /// diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobFetcher.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobFetcher.cs index 47097758..8141f489 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobFetcher.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobFetcher.cs @@ -379,15 +379,24 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent "HasSchedule", "HasStep", "HasServer", - "LastRunDate", - "NextRunDate", "LastRunOutcome", "JobID", - "Description" + "Description", + "LastRunDate", + "NextRunDate", + "OperatorToEmail", + "OperatorToNetSend", + "OperatorToPage", + "OwnerLoginName", + "PageLevel", + "StartStepID", + "NetSendLevel", + "EventLogLevel", + "EmailLevel", + "DeleteLevel" }; DataTable dt = enumerator.Process(connection, request); - int numJobs = dt.Rows.Count; if (numJobs == 0) { @@ -395,7 +404,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent } Dictionary foundJobs = new Dictionary(numJobs); - for (int i = 0; i < numJobs; ++i) { JobProperties jobProperties = new JobProperties(dt.Rows[i]); diff --git a/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobProperties.cs b/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobProperties.cs index 5b6c17cc..b825716a 100644 --- a/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobProperties.cs +++ b/src/Microsoft.SqlTools.ServiceLayer/Agent/Jobs/JobProperties.cs @@ -16,7 +16,6 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent public class JobProperties { private string name; - private int currentExecutionStatus; private int lastRunOutcome; private string currentExecutionStep; @@ -32,6 +31,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent private DateTime nextRun; private Guid jobId; private string description; + private string owner; + private string operatorToEmail; + private string operatorToPage; + private int startStepID; + private int emailLevel; + private int pageLevel; + private int eventLogLevel; + private int deleteLevel; private JobProperties() { @@ -65,6 +72,14 @@ namespace Microsoft.SqlTools.ServiceLayer.Agent this.lastRunOutcome = Convert.ToInt32(row["LastRunOutcome"], CultureInfo.InvariantCulture); this.jobId = Guid.Parse(row["JobID"].ToString()); this.description = row["Description"].ToString(); + this.owner = row["OwnerLoginName"].ToString(); + this.operatorToEmail = row["OperatorToEmail"].ToString(); + this.operatorToPage = row["OperatorToPage"].ToString(); + this.startStepID = Convert.ToInt32(row["StartStepID"], CultureInfo.InvariantCulture); + this.emailLevel = Convert.ToInt32(row["EmailLevel"], CultureInfo.InvariantCulture); + this.pageLevel = Convert.ToInt32(row["PageLevel"], CultureInfo.InvariantCulture); + this.eventLogLevel = Convert.ToInt32(row["EventLogLevel"], CultureInfo.InvariantCulture); + this.deleteLevel = Convert.ToInt32(row["DeleteLevel"], CultureInfo.InvariantCulture); // for a job to be runnable, it must: // 1. have a target server diff --git a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs index 0a6835eb..0ad1f917 100644 --- a/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs +++ b/test/Microsoft.SqlTools.ServiceLayer.IntegrationTests/Agent/AgentTestUtils.cs @@ -43,10 +43,9 @@ namespace Microsoft.SqlTools.ServiceLayer.IntegrationTests.Agent return new AgentJobInfo() { Name = TestJobName, - Owner = "sa", Description = "Test job description", - CurrentExecutionStatus = 1, - LastRunOutcome = 1, + CurrentExecutionStatus = JobExecutionStatus.Executing, + LastRunOutcome = CompletionResult.InProgress, CurrentExecutionStep = "Step 1", Enabled = false, HasTarget = false,