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