1// DTM submission automation program 2// Author: Michael Goldish <mgoldish@redhat.com> 3// Based on sample code by Microsoft. 4 5// Note: this program has only been tested with DTM version 1.5. 6// It might fail to work with other versions, specifically because it uses 7// a few undocumented methods/attributes. 8 9using System; 10using System.Collections.Generic; 11using System.Text.RegularExpressions; 12using Microsoft.DistributedAutomation.DeviceSelection; 13using Microsoft.DistributedAutomation.SqlDataStore; 14 15namespace automate0 16{ 17 class AutoJob 18 { 19 // Wait for a machine to show up in the data store 20 static void FindMachine(IResourcePool rootPool, string machineName) 21 { 22 Console.WriteLine("Looking for machine '{0}'", machineName); 23 IResource machine = null; 24 while (true) 25 { 26 try 27 { 28 machine = rootPool.GetResourceByName(machineName); 29 } 30 catch (Exception e) 31 { 32 Console.WriteLine("Warning: " + e.Message); 33 } 34 // Make sure the machine is valid 35 if (machine != null && 36 machine.OperatingSystem != null && 37 machine.OperatingSystem.Length > 0 && 38 machine.ProcessorArchitecture != null && 39 machine.ProcessorArchitecture.Length > 0 && 40 machine.GetDevices().Length > 0) 41 break; 42 System.Threading.Thread.Sleep(1000); 43 } 44 Console.WriteLine("Client machine '{0}' found ({1}, {2})", 45 machineName, machine.OperatingSystem, machine.ProcessorArchitecture); 46 } 47 48 // Delete a machine pool if it exists 49 static void DeleteResourcePool(IDeviceScript script, string poolName) 50 { 51 while (true) 52 { 53 try 54 { 55 IResourcePool pool = script.GetResourcePoolByName(poolName); 56 if (pool != null) 57 script.DeleteResourcePool(pool); 58 break; 59 } 60 catch (Exception e) 61 { 62 Console.WriteLine("Warning: " + e.Message); 63 System.Threading.Thread.Sleep(1000); 64 } 65 } 66 } 67 68 // Set the machine's status to 'Reset' and optionally wait for it to become ready 69 static void ResetMachine(IResourcePool rootPool, string machineName, bool wait) 70 { 71 Console.WriteLine("Resetting machine '{0}'", machineName); 72 IResource machine; 73 while (true) 74 { 75 try 76 { 77 machine = rootPool.GetResourceByName(machineName); 78 machine.ChangeResourceStatus("Reset"); 79 break; 80 } 81 catch (Exception e) 82 { 83 Console.WriteLine("Warning: " + e.Message); 84 System.Threading.Thread.Sleep(5000); 85 } 86 } 87 if (wait) 88 { 89 Console.WriteLine("Waiting for machine '{0}' to be ready", machineName); 90 while (machine.Status != "Ready") 91 { 92 try 93 { 94 machine = rootPool.GetResourceByName(machineName); 95 } 96 catch (Exception e) 97 { 98 Console.WriteLine("Warning: " + e.Message); 99 } 100 System.Threading.Thread.Sleep(1000); 101 } 102 Console.WriteLine("Machine '{0}' is ready", machineName); 103 } 104 } 105 106 // Look for a device in a machine, and if not found, keep trying for 3 minutes 107 static IDevice GetDevice(IResourcePool rootPool, string machineName, string regexStr) 108 { 109 Regex deviceRegex = new Regex(regexStr, RegexOptions.IgnoreCase); 110 int numAttempts = 1; 111 DateTime endTime = DateTime.Now.AddSeconds(180); 112 while (DateTime.Now < endTime) 113 { 114 IResource machine = rootPool.GetResourceByName(machineName); 115 Console.WriteLine("Looking for device '{0}' in machine '{1}' (machine has {2} devices)", 116 regexStr, machineName, machine.GetDevices().Length); 117 foreach (IDevice d in machine.GetDevices()) 118 { 119 if (deviceRegex.IsMatch(d.FriendlyName)) 120 { 121 Console.WriteLine("Found device '{0}'", d.FriendlyName); 122 return d; 123 } 124 } 125 Console.WriteLine("Device not found"); 126 if (numAttempts % 5 == 0) 127 ResetMachine(rootPool, machineName, true); 128 else 129 System.Threading.Thread.Sleep(5000); 130 numAttempts++; 131 } 132 Console.WriteLine("Error: device '{0}' not found", deviceRegex); 133 return null; 134 } 135 136 static int Main(string[] args) 137 { 138 if (args.Length < 5) 139 { 140 Console.WriteLine("Error: incorrect number of command line arguments"); 141 Console.WriteLine("Usage: {0} serverName machinePoolName submissionName timeout machineName0 machineName1 ...", 142 System.Environment.GetCommandLineArgs()[0]); 143 return 1; 144 } 145 string serverName = args[0]; 146 string machinePoolName = args[1]; 147 string submissionName = args[2]; 148 double timeout = Convert.ToDouble(args[3]); 149 150 List<string> machines = new List<string>(); 151 for (int i = 4; i < args.Length; i++) 152 machines.Add(args[i]); 153 154 try 155 { 156 // Initialize DeviceScript and connect to data store 157 Console.WriteLine("Initializing DeviceScript object"); 158 DeviceScript script = new DeviceScript(); 159 Console.WriteLine("Connecting to data store"); 160 script.ConnectToNamedDataStore(serverName); 161 162 // Wait for client machines to become available 163 IResourcePool rootPool = script.GetResourcePoolByName("$"); 164 foreach (string machineName in machines) 165 FindMachine(rootPool, machineName); 166 167 // Delete the machine pool if it already exists 168 DeleteResourcePool(script, machinePoolName); 169 170 // Create the machine pool and add the client machines to it 171 // (this must be done because jobs cannot be scheduled for machines in the 172 // default pool) 173 try 174 { 175 script.CreateResourcePool(machinePoolName, rootPool.ResourcePoolId); 176 } 177 catch (Exception e) 178 { 179 Console.WriteLine("Warning: " + e.Message); 180 } 181 IResourcePool newPool = script.GetResourcePoolByName(machinePoolName); 182 foreach (string machineName in machines) 183 { 184 Console.WriteLine("Moving machine '{0}' to pool '{1}'", machineName, machinePoolName); 185 rootPool.GetResourceByName(machineName).ChangeResourcePool(newPool); 186 } 187 188 // Reset client machine 189 foreach (string machineName in machines) 190 ResetMachine(rootPool, machineName, true); 191 192 // Get requested device regex and look for a matching device in the first machine 193 Console.WriteLine("Device to test:"); 194 IDevice device = GetDevice(rootPool, machines[0], Console.ReadLine()); 195 if (device == null) 196 return 1; 197 198 // Get requested jobs regex 199 Console.WriteLine("Jobs to run:"); 200 Regex jobRegex = new Regex(Console.ReadLine(), RegexOptions.IgnoreCase); 201 202 // Create a submission 203 Object[] existingSubmissions = script.GetSubmissionByName(submissionName); 204 if (existingSubmissions.Length > 0) 205 { 206 Console.WriteLine("Submission '{0}' already exists -- removing it", 207 submissionName); 208 script.DeleteSubmission(((ISubmission)existingSubmissions[0]).Id); 209 } 210 string hardwareId = device.InstanceId.Remove(device.InstanceId.LastIndexOf("\\")); 211 Console.WriteLine("Creating submission '{0}' (hardware ID: {1})", submissionName, hardwareId); 212 ISubmission submission = script.CreateHardwareSubmission(submissionName, newPool.ResourcePoolId, hardwareId); 213 214 // Set submission DeviceData 215 List<Object> deviceDataList = new List<Object>(); 216 while (true) 217 { 218 ISubmissionDeviceData dd = script.CreateNewSubmissionDeviceData(); 219 Console.WriteLine("DeviceData name:"); 220 dd.Name = Console.ReadLine(); 221 if (dd.Name.Length == 0) 222 break; 223 Console.WriteLine("DeviceData data:"); 224 dd.Data = Console.ReadLine(); 225 deviceDataList.Add(dd); 226 } 227 submission.SetDeviceData(deviceDataList.ToArray()); 228 229 // Set submission descriptors 230 List<Object> descriptorList = new List<Object>(); 231 while (true) 232 { 233 Console.WriteLine("Descriptor path:"); 234 string descriptorPath = Console.ReadLine(); 235 if (descriptorPath.Length == 0) 236 break; 237 descriptorList.Add(script.GetDescriptorByPath(descriptorPath)); 238 } 239 submission.SetLogoDescriptors(descriptorList.ToArray()); 240 241 // Set machine dimensions 242 foreach (string machineName in machines) 243 { 244 IResource machine = rootPool.GetResourceByName(machineName); 245 while (true) 246 { 247 Console.WriteLine("Dimension name ({0}):", machineName); 248 string dimName = Console.ReadLine(); 249 if (dimName.Length == 0) 250 break; 251 Console.WriteLine("Dimension value ({0}):", machineName); 252 machine.SetDimension(dimName, Console.ReadLine()); 253 } 254 // Set the WDKSubmissionId dimension for all machines 255 machine.SetDimension("WDKSubmissionId", submission.Id.ToString() + "_" + submission.Name); 256 } 257 258 // Get job parameters 259 List<string> paramNames = new List<string>(); 260 List<string> paramValues = new List<string>(); 261 foreach (string machineName in machines) 262 { 263 while (true) 264 { 265 Console.WriteLine("Parameter name ({0}):", machineName); 266 string paramName = Console.ReadLine(); 267 if (paramName.Length == 0) 268 break; 269 Console.WriteLine("Device regex ({0}):", machineName); 270 IDevice d = GetDevice(rootPool, machineName, Console.ReadLine()); 271 if (d == null) 272 return 1; 273 string deviceName = d.GetAttribute("name")[0].ToString(); 274 Console.WriteLine("Setting parameter value to '{0}'", deviceName); 275 paramNames.Add(paramName); 276 paramValues.Add(deviceName); 277 } 278 } 279 280 // Find jobs that match the requested pattern 281 Console.WriteLine("Scheduling jobs:"); 282 List<IJob> jobs = new List<IJob>(); 283 foreach (IJob j in submission.GetJobs()) 284 { 285 if (jobRegex.IsMatch(j.Name)) 286 { 287 Console.WriteLine(" " + j.Name); 288 // Set job parameters 289 for (int i = 0; i < paramNames.Count; i++) 290 { 291 IParameter p = j.GetParameterByName(paramNames[i]); 292 if (p != null) 293 p.ScheduleValue = paramValues[i]; 294 } 295 jobs.Add(j); 296 } 297 } 298 if (jobs.Count == 0) 299 { 300 Console.WriteLine("Error: no submission jobs match pattern '{0}'", jobRegex); 301 return 1; 302 } 303 304 // Create a schedule, add jobs to it and run it 305 ISchedule schedule = script.CreateNewSchedule(); 306 foreach (IScheduleItem item in submission.ProcessJobs(jobs.ToArray())) 307 { 308 item.Device = device; 309 schedule.AddScheduleItem(item); 310 } 311 schedule.AddSubmission(submission); 312 schedule.SetResourcePool(newPool); 313 script.RunSchedule(schedule); 314 315 // Wait for jobs to complete 316 Console.WriteLine("Waiting for all jobs to complete (timeout={0}s)", timeout); 317 DateTime endTime = DateTime.Now.AddSeconds(timeout); 318 int numCompleted, numFailed; 319 do 320 { 321 System.Threading.Thread.Sleep(30000); 322 // Report results in a Python readable format and count completed and failed schedule jobs 323 numCompleted = numFailed = 0; 324 Console.WriteLine(); 325 Console.WriteLine("---- ["); 326 foreach (IResult r in schedule.GetResults()) 327 { 328 if (r.ResultStatus != "InProgress") numCompleted++; 329 if (r.ResultStatus == "Investigate") numFailed++; 330 Console.WriteLine(" {"); 331 Console.WriteLine(" 'id': {0}, 'job': r'''{1}''',", r.Job.Id, r.Job.Name); 332 Console.WriteLine(" 'logs': r'''{0}''',", r.LogLocation); 333 if (r.ResultStatus != "InProgress") 334 Console.WriteLine(" 'report': r'''{0}''',", 335 submission.GetSubmissionResultReport(r)); 336 Console.WriteLine(" 'status': '{0}',", r.ResultStatus); 337 Console.WriteLine(" 'pass': {0}, 'fail': {1}, 'notrun': {2}, 'notapplicable': {3}", 338 r.Pass, r.Fail, r.NotRun, r.NotApplicable); 339 Console.WriteLine(" },"); 340 } 341 Console.WriteLine("] ----"); 342 } while (numCompleted < schedule.GetResults().Length && DateTime.Now < endTime); 343 344 Console.WriteLine(); 345 346 // Cancel incomplete jobs 347 foreach (IResult r in schedule.GetResults()) 348 if (r.ResultStatus == "InProgress") 349 r.Cancel(); 350 351 // Reset the machines 352 foreach (string machineName in machines) 353 ResetMachine(rootPool, machineName, false); 354 355 // Report failures 356 if (numCompleted < schedule.GetResults().Length) 357 Console.WriteLine("Some jobs did not complete on time."); 358 if (numFailed > 0) 359 Console.WriteLine("Some jobs failed."); 360 if (numFailed > 0 || numCompleted < schedule.GetResults().Length) 361 return 1; 362 363 Console.WriteLine("All jobs completed."); 364 return 0; 365 } 366 catch (Exception e) 367 { 368 Console.WriteLine("Error: " + e.Message); 369 return 1; 370 } 371 } 372 } 373} 374