Skip to content

Commit

Permalink
Fix Job collection on RDS
Browse files Browse the repository at this point in the history
Fix job collection issues on RDS caused by permissions.
  • Loading branch information
DavidWiseman committed May 17, 2024
1 parent ec7b2c7 commit 8a03295
Show file tree
Hide file tree
Showing 7 changed files with 166 additions and 130 deletions.
121 changes: 36 additions & 85 deletions DBADash/AgentJobs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ internal class AgentJobs : SMOBaseClass
public AgentJobs(DBADashConnection source, SchemaSnapshotDBOptions options) : base(source, options)
{
}

public AgentJobs(DBADashConnection source) : base(source)
{
}
Expand Down Expand Up @@ -71,20 +72,23 @@ private static DataTable JobStepTableSchema()
return jobStepDT;
}

public void CollectJobs(ref DataSet ds, bool scriptJobs)
public void CollectJobs(ref DataSet ds, bool scriptJobs, bool isRDS)
{
DataTable jobsDT;
DataTable jobStepsDT;
using (var op = Operation.At(Serilog.Events.LogEventLevel.Debug).Begin("Run Jobs query on instance {instance}", SourceConnection.ConnectionForPrint))
{
jobsDT = GetJobsDT();
op.Complete();
}
using (var op = Operation.At(Serilog.Events.LogEventLevel.Debug).Begin("Run JobsSteps query instance {instance}", SourceConnection.ConnectionForPrint))

DataTable jobStepsDT;
using (var op = Operation.At(Serilog.Events.LogEventLevel.Debug)
.Begin("Run JobsSteps query instance {instance}", SourceConnection.ConnectionForPrint))
{
jobStepsDT = GetJobStepsDT();
jobStepsDT = isRDS ? GetJobStepsDTForRDS() : GetJobStepsDT();
op.Complete();
}
ds.Tables.Add(jobStepsDT);

if (scriptJobs)
{
Expand All @@ -95,7 +99,6 @@ public void CollectJobs(ref DataSet ds, bool scriptJobs)
}
}
ds.Tables.Add(jobsDT);
ds.Tables.Add(jobStepsDT);
}

private void ScriptJobs(ref DataTable jobsDT)
Expand Down Expand Up @@ -131,7 +134,6 @@ private void ScriptJobs(ref DataTable jobsDT)
op.Complete();
}
}

}
}

Expand Down Expand Up @@ -161,90 +163,39 @@ private DataTable GetJobStepsDT()
}
}

public void SnapshotJobs(ref DataSet ds)
public DataTable GetJobStepsDTForRDS()
{
var jobDT = JobDataTableSchema();
var jobStepDT = JobStepTableSchema();
List<Exception> errors = new();
using (var cn = new SqlConnection(ConnectionString))
using var cn = new SqlConnection(ConnectionString);
var instance = new Server(new Microsoft.SqlServer.Management.Common.ServerConnection(cn));

foreach (Job job in instance.JobServer.Jobs)
{
var instance = new Server(new Microsoft.SqlServer.Management.Common.ServerConnection(cn));
foreach (Job job in instance.JobServer.Jobs)
foreach (JobStep step in job.JobSteps)
{
DataRow r = jobDT.NewRow();
string sDDL;
try
{
sDDL = SchemaSnapshotDB.StringCollectionToString(job.Script(ScriptingOptions));
}
catch (Exception ex)
{
string message = String.Format("Error scripting agent job `{0}` on {1}", job.Name, instance.Name);
sDDL = "/*\n Error scripting job: \n" + ex.Message + "\n*/";
errors.Add(new Exception(message, ex));
Log.Error(ex, message);
}

var bDDL = Zip(sDDL);
r["name"] = job.Name;
r["job_id"] = job.JobID;
r["enabled"] = job.IsEnabled;
r["DDL"] = bDDL;
r["DDLHash"] = ComputeHash(bDDL);
r["date_created"] = job.DateCreated;
r["date_modified"] = job.DateLastModified;
r["category"] = job.Category;
r["category_id"] = job.CategoryID;
r["description"] = job.Description;
r["version_number"] = job.VersionNumber;
r["delete_level"] = job.DeleteLevel;
r["notify_level_page"] = job.DeleteLevel;
r["notify_level_netsend"] = job.NetSendLevel;
r["notify_level_email"] = job.EmailLevel;
r["notify_level_eventlog"] = job.EventLogLevel;
r["notify_email_operator"] = job.OperatorToEmail;
r["notify_netsend_operator"] = job.OperatorToNetSend;
r["notify_page_operator"] = job.OperatorToPage;
r["owner"] = job.OwnerLoginName;
r["start_step_id"] = job.StartStepID;
r["has_schedule"] = job.HasSchedule;
r["has_server"] = job.HasServer;
r["has_step"] = job.HasStep;
jobDT.Rows.Add(r);

foreach (Microsoft.SqlServer.Management.Smo.Agent.JobStep step in job.JobSteps)
{
var stepR = jobStepDT.NewRow();
stepR["job_id"] = job.JobID;
stepR["step_name"] = step.Name;
stepR["database_name"] = step.DatabaseName;
stepR["step_id"] = step.ID;
stepR["subsystem"] = step.SubSystem;
stepR["command"] = step.Command;
stepR["cmdexec_success_code"] = step.CommandExecutionSuccessCode;
stepR["on_success_action"] = step.OnSuccessAction;
stepR["on_success_step_id"] = step.OnSuccessStep;
stepR["on_fail_action"] = step.OnFailAction;
stepR["on_fail_step_id"] = step.OnFailStep;
stepR["database_user_name"] = step.DatabaseUserName;
stepR["retry_attempts"] = step.RetryAttempts;
stepR["retry_interval"] = step.RetryInterval;
stepR["output_file_name"] = step.OutputFileName;
stepR["proxy_name"] = step.ProxyName;

jobStepDT.Rows.Add(stepR);

}
var stepR = jobStepDT.NewRow();
stepR["job_id"] = job.JobID;
stepR["step_name"] = step.Name;
stepR["database_name"] = step.DatabaseName;
stepR["step_id"] = step.ID;
stepR["subsystem"] = step.SubSystem;
stepR["command"] = step.Command;
stepR["cmdexec_success_code"] = step.CommandExecutionSuccessCode;
stepR["on_success_action"] = step.OnSuccessAction;
stepR["on_success_step_id"] = step.OnSuccessStep;
stepR["on_fail_action"] = step.OnFailAction;
stepR["on_fail_step_id"] = step.OnFailStep;
stepR["database_user_name"] = step.DatabaseUserName;
stepR["retry_attempts"] = step.RetryAttempts;
stepR["retry_interval"] = step.RetryInterval;
stepR["output_file_name"] = step.OutputFileName;
stepR["proxy_name"] = step.ProxyName;

jobStepDT.Rows.Add(stepR);
}
}
ds.Tables.Add(jobDT);
ds.Tables.Add(jobStepDT);
if (errors.Count > 0)
{
throw new AggregateException(errors);
}
}


return jobStepDT;
}
}
}
}
67 changes: 58 additions & 9 deletions DBADash/DBCollector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.SqlServer.Management.SqlParser.Metadata;
using Microsoft.SqlServer.Management.SqlScriptPublish;
using Octokit;

namespace DBADash
Expand Down Expand Up @@ -104,6 +105,7 @@ public class DBCollector
private bool noWMI;
private bool IsAzureDB;
private bool isAzureMasterDB;
private bool IsRDS;
private string instanceName;
private string dbName;
private string productVersion;
Expand Down Expand Up @@ -382,6 +384,7 @@ public void GetInstance()
}
if (!noWMI && computerName.StartsWith("EC2AMAZ-")) // Disable WMI collection for RDS
{
IsRDS = true;
noWMI = true;
Log.Debug("WMI Disabled for RDS Instance: {0}", instanceName);
}
Expand Down Expand Up @@ -530,7 +533,7 @@ private bool CollectionTypeIsApplicable(CollectionType collectionType)
// Don't need to collect these types for Azure MI
return false;
}
else if ((new[] { CollectionType.JobHistory, CollectionType.AgentJobs, CollectionType.Jobs }).Contains(collectionType) && engineEdition == DatabaseEngineEdition.Express)
else if ((new[] { CollectionType.JobHistory, CollectionType.AgentJobs, CollectionType.Jobs, CollectionType.RunningJobs }).Contains(collectionType) && engineEdition == DatabaseEngineEdition.Express)
{
// SQL Agent not supported on express
return false;
Expand Down Expand Up @@ -987,7 +990,7 @@ private void ExecuteCollection(CollectionType collectionType)
try
{
//ss.SnapshotJobs(ref Data);
ss.CollectJobs(ref Data, Source.ScriptAgentJobs);
ss.CollectJobs(ref Data, Source.ScriptAgentJobs,IsRDS);
JobLastModified = currentJobModified;
}
catch (Microsoft.SqlServer.Management.Smo.UnsupportedFeatureException ex)
Expand Down Expand Up @@ -1022,20 +1025,66 @@ private void ExecuteCollection(CollectionType collectionType)
}
}

private void CollectRunningJobs()

private DataTable GetRunningJobsSchema()
{
using var cn = new SqlConnection(ConnectionString);
using var cmd = new SqlCommand(SqlStrings.RunningJobs, cn);
using var da = new SqlDataAdapter(cmd);
cn.Open();
var rdr = cmd.ExecuteReader();
var dtRunningJobs = new DataTable("RunningJobs");
dtRunningJobs.Load(rdr);
dtRunningJobs.Columns.Add("job_id", typeof(Guid));
dtRunningJobs.Columns.Add("run_requested_date_utc", typeof(DateTime));
dtRunningJobs.Columns.Add("run_requested_source", typeof(string));
dtRunningJobs.Columns.Add("queued_date_utc", typeof(DateTime));
dtRunningJobs.Columns.Add("start_execution_date_utc", typeof(DateTime));
dtRunningJobs.Columns.Add("last_executed_step_id", typeof(int));
dtRunningJobs.Columns.Add("last_executed_step_date_utc", typeof(DateTime));
dtRunningJobs.Columns.Add("SnapshotDate", typeof(DateTime));

dtRunningJobs.Columns.Add("current_execution_step_id", typeof(int));
dtRunningJobs.Columns.Add("current_execution_step_name", typeof(string));
dtRunningJobs.Columns.Add("current_retry_attempt", typeof(int));
dtRunningJobs.Columns.Add("current_execution_status", typeof(int));
return dtRunningJobs;
}

private void CollectRunningJobsRDS() //Permissions Issue with msdb.dbo.syssessions prevents usual collection. Get the data we can from sp_help_job
{
using var cn = new SqlConnection(ConnectionString);
using var cmd = new SqlCommand("msdb.dbo.sp_help_job", cn) { CommandType = CommandType.StoredProcedure};
cmd.Parameters.AddWithValue("@execution_status", 0);
cn.Open();
using var rdr = cmd.ExecuteReader();
var dtRunningJobs = GetRunningJobsSchema();
var snapshotDate = DateTime.UtcNow;
while (rdr.Read())
{
var row = dtRunningJobs.NewRow();
row["job_id"] = rdr["job_id"];
var step = rdr["current_execution_step"] as string;
ParseJobStep(step, out int stepId, out string stepName);
row["current_execution_step_id"] = stepId;
row["current_execution_step_name"] = stepName;
row["current_retry_attempt"] = rdr["current_retry_attempt"];
row["current_execution_status"] = rdr["current_execution_status"];
row["SnapshotDate"] = snapshotDate;
dtRunningJobs.Rows.Add(row);
}
Data.Tables.Add(dtRunningJobs);
}

private void CollectRunningJobs()
{
if (IsRDS)
{
CollectRunningJobsRDS();
return;
}
using var cn = new SqlConnection(ConnectionString);
using var cmd = new SqlCommand(SqlStrings.RunningJobs, cn);
using var da = new SqlDataAdapter(cmd);
cn.Open();
using var rdr = cmd.ExecuteReader();
var dtRunningJobs = GetRunningJobsSchema();
dtRunningJobs.Load(rdr);

var spHelpJobInfo = new Dictionary<Guid, (int ExecutionStepID, string ExecutionStep, int RetryAttempts, int ExecutionStatus)>();

while (rdr.Read())
Expand Down
97 changes: 66 additions & 31 deletions DBADash/SQL/SQLJobs.sql
Original file line number Diff line number Diff line change
@@ -1,31 +1,66 @@
SELECT J.job_id,
S.name AS originating_server,
J.name,
J.enabled,
J.description,
J.start_step_id,
J.category_id,
C.name AS category,
SUSER_SNAME(J.owner_sid) AS owner,
J.notify_level_eventlog,
J.notify_level_email,
J.notify_level_netsend,
J.notify_level_page,
ISNULL(EmailOp.name,'') AS notify_email_operator,
ISNULL(NetOp.name,'') AS notify_netsend_operator,
ISNULL(PageOp.name,'') AS notify_page_operator,
J.delete_level,
J.date_created,
J.date_modified,
J.version_number,
CASE WHEN EXISTS(SELECT * FROM msdb.dbo.sysjobschedules JS WHERE JS.job_id = J.job_id) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END AS has_schedule,
CASE WHEN EXISTS(SELECT * FROM msdb.dbo.sysjobservers JS WHERE JS.job_id = J.job_id) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END AS has_server,
CASE WHEN EXISTS(SELECT 1 FROM msdb.dbo.sysjobsteps JS WHERE JS.job_id = J.job_id) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END has_step,
CAST(NULL AS BINARY(32)) AS DDLHash, /* Populated using SMO */
CAST(NULL AS VARBINARY(MAX)) AS DDL /* Populated using SMO */
FROM msdb.dbo.sysjobs J
LEFT JOIN [msdb].[sys].[servers] AS S ON J.originating_server_id = S.server_id AND J.originating_server_id<>0 /* Return NULL for this server like SMO */
LEFT JOIN msdb.dbo.syscategories C ON C.category_id = J.category_id
LEFT JOIN msdb.dbo.sysoperators EmailOp ON J.notify_email_operator_id = EmailOp.id
LEFT JOIN msdb.dbo.sysoperators NetOp ON J.notify_netsend_operator_id = NetOp.id
LEFT JOIN msdb.dbo.sysoperators PageOp ON J.notify_page_operator_id = PageOp.id
IF CAST(SERVERPROPERTY('ComputerNamePhysicalNetBIOS') AS NVARCHAR(128)) LIKE 'EC2AMAZ-%' /* RDS */
BEGIN
SELECT J.job_id,
S.name AS originating_server,
J.name,
J.enabled,
J.description,
J.start_step_id,
J.category_id,
C.name AS category,
SUSER_SNAME(J.owner_sid) AS owner,
J.notify_level_eventlog,
J.notify_level_email,
J.notify_level_netsend,
J.notify_level_page,
'' AS notify_email_operator,
'' AS notify_netsend_operator,
'' AS notify_page_operator,
J.delete_level,
J.date_created,
J.date_modified,
J.version_number,
CAST(NULL AS BIT) AS has_schedule,
CAST(NULL AS BIT) AS has_server,
CAST(NULL AS BIT) AS has_step,
CAST(NULL AS BINARY(32)) AS DDLHash, /* Populated using SMO */
CAST(NULL AS VARBINARY(MAX)) AS DDL /* Populated using SMO */
FROM msdb.dbo.sysjobs J
LEFT JOIN [msdb].[sys].[servers] AS S ON J.originating_server_id = S.server_id AND J.originating_server_id<>0 /* Return NULL for this server like SMO */
LEFT JOIN msdb.dbo.syscategories C ON C.category_id = J.category_id
WHERE SUSER_SNAME(J.owner_sid) <> 'rdsa'
END
ELSE
BEGIN
SELECT J.job_id,
S.name AS originating_server,
J.name,
J.enabled,
J.description,
J.start_step_id,
J.category_id,
C.name AS category,
SUSER_SNAME(J.owner_sid) AS owner,
J.notify_level_eventlog,
J.notify_level_email,
J.notify_level_netsend,
J.notify_level_page,
ISNULL(EmailOp.name,'') AS notify_email_operator,
ISNULL(NetOp.name,'') AS notify_netsend_operator,
ISNULL(PageOp.name,'') AS notify_page_operator,
J.delete_level,
J.date_created,
J.date_modified,
J.version_number,
CASE WHEN EXISTS(SELECT * FROM msdb.dbo.sysjobschedules JS WHERE JS.job_id = J.job_id) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END AS has_schedule,
CASE WHEN EXISTS(SELECT * FROM msdb.dbo.sysjobservers JS WHERE JS.job_id = J.job_id) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END AS has_server,
CASE WHEN EXISTS(SELECT 1 FROM msdb.dbo.sysjobsteps JS WHERE JS.job_id = J.job_id) THEN CAST(1 AS BIT) ELSE CAST(0 AS BIT) END has_step,
CAST(NULL AS BINARY(32)) AS DDLHash, /* Populated using SMO */
CAST(NULL AS VARBINARY(MAX)) AS DDL /* Populated using SMO */
FROM msdb.dbo.sysjobs J
LEFT JOIN [msdb].[sys].[servers] AS S ON J.originating_server_id = S.server_id AND J.originating_server_id<>0 /* Return NULL for this server like SMO */
LEFT JOIN msdb.dbo.syscategories C ON C.category_id = J.category_id
LEFT JOIN msdb.dbo.sysoperators EmailOp ON J.notify_email_operator_id = EmailOp.id
LEFT JOIN msdb.dbo.sysoperators NetOp ON J.notify_netsend_operator_id = NetOp.id
LEFT JOIN msdb.dbo.sysoperators PageOp ON J.notify_page_operator_id = PageOp.id
END
Loading

0 comments on commit 8a03295

Please sign in to comment.