comparison ServerMonitor/Objects/ServerMonitor.cs @ 0:3e1a2131f897

Initial commit. Ping check, scheduling, UI working. SSH check mostly working.
author Brad Greco <brad@bgreco.net>
date Mon, 31 Dec 2018 18:32:14 -0500
parents
children 3142e52cbe69
comparison
equal deleted inserted replaced
-1:000000000000 0:3e1a2131f897
1 using System;
2 using System.Collections.Generic;
3 using System.Diagnostics;
4 using System.IO;
5 using System.Linq;
6 using System.Text;
7 using System.Threading;
8 using System.Threading.Tasks;
9 using System.Xml.Serialization;
10
11 namespace ServerMonitorApp
12 {
13 public class ServerMonitor
14 {
15 private readonly string configFileDir;
16 private readonly Logger logger;
17 private readonly Dictionary<int, CancellationTokenSource> tokens = new Dictionary<int, CancellationTokenSource>();
18 private bool running;
19 private Dictionary<Task<CheckResult>, int> tasks;
20 //private List<Task<CheckResult>> tasks;
21
22 public event EventHandler<CheckStatusChangedEventArgs> CheckStatusChanged;
23
24 public List<Server> Servers { get; private set; } = new List<Server>();
25
26 public IEnumerable<Check> Checks { get { return Servers.SelectMany(s => s.Checks); } }
27
28 public string ConfigFile { get; private set; }
29
30 public ServerMonitor()
31 {
32 configFileDir = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ServerMonitor");
33 ConfigFile = Path.Combine(configFileDir, "servers.xml");
34 logger = new Logger(Path.Combine(configFileDir, "monitor.log"));
35 }
36
37 public void AddServer(Server server)
38 {
39 Servers.Add(server);
40 }
41
42 public void LoadServers()
43 {
44 TextReader reader = null;
45 try
46 {
47 reader = new StreamReader(ConfigFile);
48 XmlSerializer serializer = CreateXmlSerializer();
49 Servers.Clear();
50 Servers.AddRange((List<Server>)serializer.Deserialize(reader));
51 // Update the Checks so they know what Server they belong to.
52 // Would rather do this in the Server object on deserialization, but
53 // that doesn't work when using the XML serializer for some reason.
54 foreach (Server server in Servers)
55 {
56 foreach (Check check in server.Checks)
57 {
58 check.Server = server;
59 }
60 server.CheckModified += Server_CheckModified;
61 }
62 }
63 // If the file doesn't exist, no special handling is needed. It will be created later.
64 catch (FileNotFoundException) { }
65 catch (DirectoryNotFoundException) { }
66 catch (InvalidOperationException)
67 {
68 //TODO log
69 throw;
70 }
71 finally
72 {
73 reader?.Close();
74 }
75 Run();
76 }
77
78 public void SaveServers()
79 {
80 GenerateIds();
81 TextWriter writer = null;
82 XmlSerializer serializer = null;
83 try
84 {
85 writer = new StreamWriter(ConfigFile);
86 serializer = CreateXmlSerializer();
87 serializer.Serialize(writer, Servers);
88 }
89 catch (DirectoryNotFoundException)
90 {
91 Directory.CreateDirectory(configFileDir);
92 writer = new StreamWriter(ConfigFile);
93 serializer = CreateXmlSerializer();
94 serializer.Serialize(writer, Servers);
95 }
96 catch (Exception)
97 {
98 //TODO log
99 throw;
100 }
101 finally
102 {
103 writer?.Close();
104 }
105 }
106
107 private async void Run()
108 {
109 if (running)
110 return;
111 running = true;
112 //TODO subscribe to power events. Find any check's NextExecutionTime is in the past. Cancel waiting task and run immediately (or after short delay).
113 //tasks = Checks.Select(c => ScheduleExecuteCheckAsync(c)).ToList();
114 tasks = Checks.ToDictionary(c => ScheduleExecuteCheckAsync(c), c => c.Id);
115 while (tasks.Count > 0)
116 {
117 Task<CheckResult> task = await Task.WhenAny(tasks.Keys);
118 tasks.Remove(task);
119 try
120 {
121 CheckResult result = await task;
122 // Result will be null if a scheduled check was disabled
123 if (result != null && result.CheckStatus != CheckStatus.Disabled)
124 tasks.Add(ScheduleExecuteCheckAsync(result.Check), result.Check.Id);
125 }
126 catch (OperationCanceledException)
127 {
128
129 }
130 }
131 running = false;
132 }
133
134 public async Task<CheckResult> ExecuteCheckAsync(Check check, CancellationToken token = default(CancellationToken))
135 {
136 check.Status = CheckStatus.Running;
137 OnCheckStatusChanged(check);
138 CheckResult result = await check.ExecuteAsync(token);
139 OnCheckStatusChanged(check, result);
140 logger.Log(result);
141 return result;
142 }
143
144 public IList<CheckResult> GetLog(Server server)
145 {
146 return logger.Read(server);
147 }
148
149 private void OnCheckStatusChanged(Check check, CheckResult result = null)
150 {
151 SaveServers();
152 CheckStatusChanged?.Invoke(check, new CheckStatusChangedEventArgs(check, result));
153 }
154
155 private void Server_CheckModified(object sender, EventArgs e)
156 {
157 Check check = (Check)sender;
158 Task<CheckResult> task = tasks.FirstOrDefault(kvp => kvp.Value == check.Id).Key;
159 if (running)
160 {
161 if (task == null)
162 {
163 // No tasks associated with the check, so schedule a new one
164 tasks.Add(ScheduleExecuteCheckAsync(check), check.Id);
165 }
166 else
167 {
168 // Check was modified or deleted, so remove any waiting tasks
169 tasks.Remove(task);
170 if (tokens.TryGetValue(check.Id, out CancellationTokenSource cts))
171 cts.Cancel();
172 if (check.Server != null)
173 {
174 // If the check was not deleted, schedule the new check.
175 // But only if it's still running, otherwise restarting the monitor below
176 // will create a duplicate run.
177 if (running)
178 tasks.Add(ScheduleExecuteCheckAsync(check), check.Id);
179 }
180 }
181 }
182 // Run again in case removing a task above caused it to stop
183 Run();
184 }
185
186 private async Task<CheckResult> ScheduleExecuteCheckAsync(Check check)
187 {
188 if (!check.Enabled)
189 return await Task.FromResult(new CheckResult(check, CheckStatus.Disabled, null));
190
191 CancellationTokenSource cts = new CancellationTokenSource();
192 tokens[check.Id] = cts;
193 check.NextRunTime = check.Schedule.GetNextTime(check.LastScheduledRunTime);
194 await Task.Delay(check.NextRunTime - DateTime.Now, cts.Token);
195 check.LastScheduledRunTime = check.NextRunTime;
196 return await ExecuteCheckAsync(check, cts.Token);
197 }
198
199 private void GenerateIds()
200 {
201 if (Servers.Any())
202 {
203 int id = Servers.Max(s => s.Id);
204 foreach (Server server in Servers)
205 {
206 if (server.Id == 0)
207 server.Id = ++id;
208 }
209 }
210
211 //TODO if a check is deleted, there might be old results in the log file that share an ID with a new one
212 if (Checks.Any())
213 {
214 int id = Checks.Max(c => c.Id);
215 foreach (Check check in Checks)
216 {
217 if (check.Id == 0)
218 check.Id = ++id;
219 }
220 }
221 }
222
223 private XmlSerializer CreateXmlSerializer()
224 {
225 return new XmlSerializer(typeof(List<Server>), Check.CheckTypes);
226 }
227 }
228
229 public class CheckStatusChangedEventArgs : EventArgs
230 {
231 public Check Check { get; private set; }
232
233 public CheckResult CheckResult { get; private set; }
234
235 public CheckStatusChangedEventArgs(Check check, CheckResult result)
236 {
237 Check = check;
238 CheckResult = result;
239 }
240
241 }
242 }