mirror of
https://github.com/ckaczor/sqltoolsservice.git
synced 2026-02-16 18:47:57 -05:00
SQL Agent configuration for Operators, Alerts and Proxies (WIP) (#621)
* Initial non-refactored SQL Agent alert classes (WIP) * Move agent classes into subdirectories * Refactor the agent config code a bit more * Add more implementation for handlers * Add more code to the create alert handler * Clean up agent alert class * Clean up alert methods a bit * Initial Operator contracts * Additonal SQL Agent config changes * More Proxy config cleanup * Cleanup AgentProxy class * Additional cleanups * Run SRGen * Add security service to create credential objects
This commit is contained in:
@@ -0,0 +1,704 @@
|
||||
//
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
using Microsoft.SqlServer.Management.Sdk.Sfc;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.SqlServer.Management.Diagnostics;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Microsoft.SqlTools.ServiceLayer.Agent
|
||||
{
|
||||
|
||||
#region LogSourceAggregation - ILogSource info built from multiple other sources
|
||||
internal class LogSourceAggregation : ILogSource, ITypedColumns, IDisposable
|
||||
{
|
||||
#region Constants
|
||||
private const int cMaximumNotificationChunkSize = 128; // 16384 high no: faster aggregation, low no: responsive ui
|
||||
#endregion
|
||||
|
||||
#region Variables
|
||||
private string m_logName = null;
|
||||
private bool m_logInitialized = false;
|
||||
private string[] m_fieldNames = null;
|
||||
private TypedColumnCollection m_columnTypes = null;
|
||||
|
||||
List<ILogSource> m_originalSources = null;
|
||||
List<ILogSource> m_sources = null;
|
||||
ILogConstraints m_filter = null;
|
||||
private LogAggregator m_owner = null;
|
||||
private ILogEntry m_currentEntry = null;
|
||||
private List<ILogEntry> m_currentEntrySources = null;
|
||||
private List<Exception> m_exceptionList = null;
|
||||
|
||||
#endregion
|
||||
|
||||
#region Reverse order Property
|
||||
private bool ReverseOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="owner"></param>
|
||||
/// <param name="name"></param>
|
||||
/// <param name="sources"></param>
|
||||
/// <param name="filter">if null no filter, else use it to filter every ILogEntry</param>
|
||||
public LogSourceAggregation (LogAggregator owner, string name, ILogSource[] sources, ILogConstraints filterTemplate)
|
||||
{
|
||||
m_owner = owner;
|
||||
|
||||
m_logName = name;
|
||||
m_originalSources = new List<ILogSource>(sources);
|
||||
|
||||
m_fieldNames = AggregateFieldNames(sources);
|
||||
|
||||
AggregateColumnTypes(sources);
|
||||
|
||||
// if (filterTemplate != null)
|
||||
// {
|
||||
// m_filter = new LogConstraints(this, filterTemplate as LogConstraints);
|
||||
// }
|
||||
// else
|
||||
{
|
||||
m_filter = null;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
for (int i = 0; i < m_originalSources.Count; ++i)
|
||||
{
|
||||
if (m_originalSources[i] is IDisposable)
|
||||
{
|
||||
(m_originalSources[i] as IDisposable).Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
m_currentEntry = null;
|
||||
m_sources = null;
|
||||
m_exceptionList = null;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ILogSource interface implementation
|
||||
|
||||
bool ILogSource.OrderedByDateDescending
|
||||
{
|
||||
get {return this.ReverseOrder;}
|
||||
}
|
||||
|
||||
ILogEntry ILogSource.CurrentEntry
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_currentEntry;
|
||||
}
|
||||
}
|
||||
|
||||
bool ILogSource.ReadEntry()
|
||||
{
|
||||
//the m_currentEntrySources list contains the list of currentEntries for each logSource
|
||||
//when the readentry is called for the first time the list is null so we need to initialize it
|
||||
if (m_currentEntrySources == null)
|
||||
{
|
||||
m_sources = new List<ILogSource>(m_originalSources);
|
||||
m_currentEntrySources = new List<ILogEntry>(m_sources.Count);
|
||||
for (int i = 0; i < m_sources.Count; i++)
|
||||
{
|
||||
//the null value acts as a guard that indicates whether we have read all the entries from the current source
|
||||
//or if an error happened. That is why I initialize all the sources with null
|
||||
m_currentEntrySources.Add(null);
|
||||
try
|
||||
{
|
||||
|
||||
if (m_sources[i].CurrentEntry != null || (m_sources[i].ReadEntry()))
|
||||
{
|
||||
m_currentEntrySources[i] = m_sources[i].CurrentEntry;
|
||||
if (m_filter != null)
|
||||
{
|
||||
while (!m_filter.MatchLogEntry(m_sources[i].CurrentEntry))
|
||||
{
|
||||
//check if cancel
|
||||
if (IsCanceled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_sources[i].ReadEntry())
|
||||
{
|
||||
m_currentEntrySources[i] = m_sources[i].CurrentEntry;
|
||||
|
||||
if (m_filter != null && !m_filter.IsEntryWithinFilterTimeWindow(m_currentEntrySources[i]))
|
||||
{
|
||||
m_currentEntrySources[i] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentEntrySources[i] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) //whenever a source issued an exception, the exception is stored in the exception list and the source is removed from the list
|
||||
{
|
||||
AddExceptionToExceptionList(e, m_sources[i].Name);
|
||||
m_currentEntrySources[i] = null;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
//we check the currentEntrySources again to see if there are any entries read from the source
|
||||
//if not it means that either the source has no entries (that satisfy the filter if a filter is defined)
|
||||
//or an error happened so we need to close the reader and remove the source.
|
||||
for (int i = 0; i < m_currentEntrySources.Count; i++)
|
||||
{
|
||||
if (m_currentEntrySources[i] == null)
|
||||
{
|
||||
m_sources[i].CloseReader();
|
||||
m_sources.RemoveAt(i);
|
||||
m_currentEntrySources.RemoveAt(i);
|
||||
i--; //we need this to make the indexer point at the previous log source
|
||||
}
|
||||
}
|
||||
|
||||
int sourceindex = -1;
|
||||
|
||||
if (m_sources.Count == 1 && m_currentEntrySources[0] != null)
|
||||
{
|
||||
sourceindex = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
DateTime maxtime = DateTime.MinValue;
|
||||
for (int i = 0; i < m_sources.Count; i++)
|
||||
{
|
||||
if (maxtime.CompareTo(m_currentEntrySources[i].PointInTime) <= 0)
|
||||
{
|
||||
maxtime = m_currentEntrySources[i].PointInTime;
|
||||
sourceindex = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (sourceindex > -1)
|
||||
{
|
||||
m_currentEntry = m_sources[sourceindex].CurrentEntry;
|
||||
try
|
||||
{
|
||||
do
|
||||
{
|
||||
//check if cancel
|
||||
if (IsCanceled())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_sources[sourceindex].ReadEntry())
|
||||
{
|
||||
m_currentEntrySources[sourceindex] = m_sources[sourceindex].CurrentEntry;
|
||||
|
||||
if (m_filter != null && !m_filter.IsEntryWithinFilterTimeWindow(m_currentEntrySources[sourceindex]))
|
||||
{
|
||||
m_currentEntrySources[sourceindex] = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_currentEntrySources[sourceindex] = null;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
while (m_filter != null && !m_filter.MatchLogEntry(m_sources[sourceindex].CurrentEntry));
|
||||
}
|
||||
catch (Exception e) //whenever a source issued an exception, the exception is stored in the exception list and the source is removed from the list
|
||||
{
|
||||
AddExceptionToExceptionList(e, m_sources[sourceindex].Name);
|
||||
m_currentEntrySources[sourceindex] = null;
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ILogSource.CloseReader()
|
||||
{
|
||||
foreach (ILogSource source in m_originalSources)
|
||||
{
|
||||
source.CloseReader();
|
||||
}
|
||||
|
||||
m_currentEntrySources = null;
|
||||
m_currentEntry = null;
|
||||
m_exceptionList = null;
|
||||
|
||||
}
|
||||
|
||||
string ILogSource.Name
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_logName;
|
||||
}
|
||||
}
|
||||
|
||||
void ILogSource.Initialize()
|
||||
{
|
||||
if (m_logInitialized == true)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// initialize original sources
|
||||
int n = m_originalSources.Count;
|
||||
int i = 0;
|
||||
for (i = 0; i < m_originalSources.Count; i++)
|
||||
{
|
||||
|
||||
ILogSource s = m_originalSources[i];
|
||||
|
||||
try
|
||||
{
|
||||
// format notification message
|
||||
m_owner.Raise_AggregationProgress("Raise_AggregationProgress", //LogViewerSR.AggregationProgress_Initialize(i + 1, n, (s.Name != null) ? s.Name.Trim() : String.Empty),
|
||||
0,
|
||||
null);
|
||||
|
||||
// initialize (load) inner source
|
||||
s.Initialize();
|
||||
}
|
||||
catch (Exception e) //whenever a source issued an exception, the exception is stored in the exception list and the source is removed from the list
|
||||
{
|
||||
AddExceptionToExceptionList(e, s.Name);
|
||||
m_originalSources.RemoveAt(i);
|
||||
s.CloseReader();
|
||||
i--;
|
||||
}
|
||||
|
||||
// check for cancel
|
||||
if (IsCanceled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// report all inner source loaded
|
||||
m_owner.Raise_AggregationProgress("LogViewerSR.AggregationProgress_InitializationDone",
|
||||
LogAggregator.cProgressLoaded,
|
||||
null);
|
||||
|
||||
|
||||
m_logInitialized = true;
|
||||
}
|
||||
|
||||
string[] ILogSource.FieldNames
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_fieldNames;
|
||||
}
|
||||
}
|
||||
|
||||
TypedColumnCollection ITypedColumns.ColumnTypes
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_columnTypes;
|
||||
}
|
||||
}
|
||||
|
||||
ILogSource[] ILogSource.SubSources
|
||||
{
|
||||
get { return null;}
|
||||
}
|
||||
|
||||
ILogSource ILogSource.GetRefreshedClone()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Implementation
|
||||
/// <summary>
|
||||
/// computes the available fields for the aggregated log source
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
internal static string[] AggregateFieldNames(ILogSource[] sources)
|
||||
{
|
||||
List<string> ar = new List<string>();
|
||||
|
||||
foreach(ILogSource s in sources)
|
||||
{
|
||||
if ((s != null) && (s.FieldNames != null))
|
||||
{
|
||||
foreach(string fieldName in s.FieldNames)
|
||||
{
|
||||
if (ar.Contains(fieldName))
|
||||
{
|
||||
continue; // do not add it again
|
||||
}
|
||||
ar.Add(fieldName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ar.ToArray();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// computes the available column types for the aggregated log source
|
||||
/// </summary>
|
||||
/// <param name="sources"></param>
|
||||
private void AggregateColumnTypes(ILogSource[] sources)
|
||||
{
|
||||
m_columnTypes = new TypedColumnCollection();
|
||||
|
||||
foreach (ILogSource s in sources)
|
||||
{
|
||||
if (s is ITypedColumns)
|
||||
{
|
||||
ITypedColumns cs = (ITypedColumns)s;
|
||||
if ((cs != null) && (cs.ColumnTypes != null))
|
||||
{
|
||||
foreach (string fieldName in s.FieldNames)
|
||||
{
|
||||
m_columnTypes.AddColumnType(fieldName, cs.ColumnTypes.GetColumnType(fieldName));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// checks to see if somebody decided to cancel or stop the operation
|
||||
/// </summary>
|
||||
private bool IsCanceled()
|
||||
{
|
||||
return m_owner.CancelInternal || m_owner.StopInternal;
|
||||
}
|
||||
|
||||
|
||||
public IList<Exception> ExceptionList
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_exceptionList;
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearExceptionList()
|
||||
{
|
||||
m_exceptionList = null;
|
||||
}
|
||||
|
||||
private void AddExceptionToExceptionList(Exception e, string sourceName)
|
||||
{
|
||||
e.Source = sourceName;
|
||||
|
||||
if (m_exceptionList == null)
|
||||
{
|
||||
m_exceptionList = new List<Exception>();
|
||||
}
|
||||
m_exceptionList.Add(e);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region [Conditional("DEBUG")] validate correctness of a log source
|
||||
/// <summary>
|
||||
/// validate if entries are in correct order
|
||||
///
|
||||
/// costly operation so we compile this only if "DEBUG" is defined
|
||||
/// iterates through all the entries and if their datetime is different the
|
||||
/// DateTime.MinValue or DateTime.MaxValue compares it with adjacent entries
|
||||
///
|
||||
/// we do not compare subentries as aggregation is performed
|
||||
/// only at entries level (subentries are always linked to thier parent entry)
|
||||
///
|
||||
/// the order should be ascending (newer logs are after older logs) if reverseOrder = false
|
||||
/// the order should be descending (newer logs are before older logs) if reverseOrder = true
|
||||
/// </summary>
|
||||
/// <param name="entries"></param>
|
||||
/// <param name="reverseOrder"></param>
|
||||
[System.Diagnostics.Conditional("DEBUG")]
|
||||
private static void ConditionalDEBUG_ValidateLogEntriesOrder(List<ILogEntry> entries,
|
||||
bool reverseOrder)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine("LogSourceAggregation.ConditionalDEBUG_ValidateLogEntriesOrder ------- reverseOrder=" + reverseOrder.ToString());
|
||||
|
||||
if ((entries == null) || (entries.Count < 2))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < (entries.Count - 1); ++i)
|
||||
{
|
||||
int j = i + 1;
|
||||
|
||||
DateTime dti = entries[i].PointInTime;
|
||||
DateTime dtj = entries[j].PointInTime;
|
||||
|
||||
if (
|
||||
(dti != DateTime.MinValue) && (dti != DateTime.MaxValue) &&
|
||||
(dtj != DateTime.MinValue) && (dtj != DateTime.MaxValue)
|
||||
)
|
||||
{
|
||||
// if logs are comming from same source then we dont Assert since it is not
|
||||
// the aggregator algoritm to blame but the log source provider who broke
|
||||
// the assumption that log sources are coming already pre-sorted
|
||||
if ((entries[i].OriginalSourceTypeName == entries[j].OriginalSourceTypeName) &&
|
||||
(entries[i].OriginalSourceName == entries[j].OriginalSourceName))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
|
||||
#region ITypedColumns Members
|
||||
|
||||
|
||||
public void HyperLinkClicked(string sourcename, string columnname, string hyperlink, long row, int column)
|
||||
{
|
||||
foreach (ILogSource s in m_originalSources)
|
||||
{
|
||||
if ((s is ITypedColumns) &&
|
||||
(s.Name == sourcename)) // The original source for the row containing the hyperlink
|
||||
{
|
||||
((ITypedColumns)s).HyperLinkClicked(sourcename, columnname, hyperlink, row, column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region TypedColumnCollection
|
||||
|
||||
internal class TypedColumnCollection
|
||||
{
|
||||
private Dictionary<string, int> m_typedColumns = null;
|
||||
|
||||
internal TypedColumnCollection()
|
||||
{
|
||||
m_typedColumns = new Dictionary<string, int>();
|
||||
}
|
||||
|
||||
internal void AddColumnType(string columnName, int columnType)
|
||||
{
|
||||
if (!m_typedColumns.ContainsKey(columnName))
|
||||
{
|
||||
m_typedColumns.Add(columnName, columnType);
|
||||
}
|
||||
}
|
||||
|
||||
internal int GetColumnType(string columnName)
|
||||
{
|
||||
int returnType;
|
||||
if (m_typedColumns.TryGetValue(columnName, out returnType))
|
||||
{
|
||||
return returnType;
|
||||
}
|
||||
return GridColumnType.Text;
|
||||
}
|
||||
|
||||
internal bool IsEmpty
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_typedColumns.Count == 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#region LogAggregator class - ILogAggregator algorithm
|
||||
/// <summary>
|
||||
/// Summary description for LogAggregator.
|
||||
/// </summary>
|
||||
internal class LogAggregator : ILogAggregator
|
||||
{
|
||||
#region Constants
|
||||
internal const int cProgressLogCreated = 1;
|
||||
internal const int cProgressLoaded = 15;
|
||||
internal const int cProgressAlmostDone = 95;
|
||||
internal const int cProgressDone = 100;
|
||||
#endregion
|
||||
|
||||
#region Properties - CancelInternal (lock-ed access)
|
||||
private volatile bool m_boolCancelInternal = false;
|
||||
internal bool CancelInternal
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
return m_boolCancelInternal;
|
||||
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
m_boolCancelInternal = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private volatile bool m_boolStopInternal = false;
|
||||
internal bool StopInternal
|
||||
{
|
||||
get
|
||||
{
|
||||
|
||||
return m_boolStopInternal;
|
||||
|
||||
}
|
||||
set
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
m_boolStopInternal = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private bool m_reverseOrder = true;
|
||||
internal bool ReverseOrder
|
||||
{
|
||||
get
|
||||
{
|
||||
return m_reverseOrder;
|
||||
}
|
||||
set
|
||||
{
|
||||
m_reverseOrder = value;
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region Variables
|
||||
private LogSourceAggregation m_currentSource = null;
|
||||
#endregion
|
||||
|
||||
#region Constructor
|
||||
/// <summary>
|
||||
/// create an log aggregator using a default empty cache
|
||||
/// </summary>
|
||||
public LogAggregator()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region ILogAggregator interface implementation
|
||||
ILogSource ILogAggregator.PrepareAggregation(string outputLogSourceName, ILogSource[] sources, ILogConstraints filterTemplate)
|
||||
{
|
||||
ILogSource outputSource = CreateUninitializedAggregation(outputLogSourceName, sources, filterTemplate);
|
||||
|
||||
m_currentSource = outputSource as LogSourceAggregation;
|
||||
|
||||
return outputSource;
|
||||
}
|
||||
|
||||
void ILogAggregator.CancelAsyncWork()
|
||||
{
|
||||
CancelInternal = true;
|
||||
}
|
||||
|
||||
void ILogAggregator.StopAsyncWork()
|
||||
{
|
||||
StopInternal = true;
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region CreateUninitializedAggregation algorithm
|
||||
/// <summary>
|
||||
/// agregates one or more sources -> creates a new (uninitialized) aggregation
|
||||
///
|
||||
/// NOTE:
|
||||
/// we also 'aggregate' only 1 source to gain the advantage offered by this algoritm
|
||||
/// of being able to pump entry-s to ui thread in chucks instead of sending all source
|
||||
/// in one shoot -> more responsive ui
|
||||
/// </summary>
|
||||
/// <param name="outputLogSourceName"></param>
|
||||
/// <param name="sources"></param>
|
||||
/// <param name="constraints">null if no filter</param>
|
||||
/// <returns></returns>
|
||||
private ILogSource CreateUninitializedAggregation(string outputLogSourceName, ILogSource[] sources, ILogConstraints filterTemplate)
|
||||
{
|
||||
// zero sources - nothing we can do
|
||||
if ((sources == null) || (sources.Length==0))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
ILogSource newAggregation = null;
|
||||
try
|
||||
{
|
||||
// not in cache, so build it, add it to cache (if caching ok) and return it
|
||||
newAggregation = new LogSourceAggregation(this, outputLogSourceName, sources, filterTemplate);
|
||||
|
||||
return newAggregation;
|
||||
|
||||
}
|
||||
finally
|
||||
{
|
||||
Raise_AggregationProgress( "LogViewerSR.AggregationProgress_BeginInitialize",
|
||||
cProgressLogCreated,
|
||||
null);
|
||||
}
|
||||
}
|
||||
#endregion
|
||||
|
||||
#region DelegateAggregationWorkImplementation - entry for - asynchronous invocation with callback ***** via delegate
|
||||
private List<Exception> m_exceptionsList = new List<Exception>();
|
||||
#endregion
|
||||
|
||||
#region Report Progress
|
||||
/// <summary>
|
||||
/// if job not null and callbackProgress available -> invoke progress delegate in ui thread
|
||||
/// </summary>
|
||||
/// <param name="job"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="percent"></param>
|
||||
internal void Raise_AggregationProgress(string message,
|
||||
int percent,
|
||||
IList<Exception> exceptionList)
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
#endregion
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user