-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathUtilityMethods.cs
171 lines (151 loc) · 8.45 KB
/
UtilityMethods.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
using Azure.Core;
using Azure.ResourceManager;
using Azure.ResourceManager.ComputeSchedule;
using Azure.ResourceManager.ComputeSchedule.Models;
using Azure.ResourceManager.Resources;
namespace ComputeScheduleSampleProject
{
public static class UtilityMethods
{
// Amount of time to wait between each polling request
private static readonly int PollingIntervalInSeconds = 15;
// Amount of time to wait before polling requests start, this is because the p50 for compute operations is approximately 2 minutes
private static readonly int InitialWaitTimeBeforePollingInSeconds = 30;
// Timeout for polling operation status
private static readonly int OperationTimeoutInMinutes = 125;
/// <summary>
/// Generates a resource identifier for the subscriptionId
/// </summary>
/// <param name="client"></param>
/// <param name="subscriptionId"></param>
/// <returns></returns>
public static SubscriptionResource GetSubscriptionResource(ArmClient client, string subscriptionId)
{
ResourceIdentifier subscriptionResourceId = SubscriptionResource.CreateResourceIdentifier(subscriptionId);
return client.GetSubscriptionResource(subscriptionResourceId);
}
/// <summary>
/// Determine if the operation state is complete
/// </summary>
/// <param name="state"></param>
/// <returns></returns>
public static bool IsOperationStateComplete(ScheduledActionOperationState? state)
{
return state != null &&
(state == ScheduledActionOperationState.Succeeded |
state == ScheduledActionOperationState.Failed ||
state == ScheduledActionOperationState.Cancelled);
}
/// <summary>
/// Determine if polling for operation status should continue based on the response from GetOperationsRequest
/// </summary>
/// <param name="response"> Response from GetOperationsRequest that is used to determine if polling should continue </param>
/// <param name="totalVmsCount">Total number of virtual machines in the initial Start/Hibernate/Deallocate operation </param>
/// <param name="completedOps"> Dictionary of completed operations, that is, operations where state is either Succeeded, Failed, Cancelled </param>
/// <returns></returns>
public static bool ShouldRetryPolling(GetOperationStatusResult response, int totalVmsCount, Dictionary<string, ResourceOperationDetails> completedOps)
{
var shouldRetry = true;
foreach (var operationResult in response.Results)
{
var operation = operationResult.Operation;
var operationId = operation.OperationId;
var operationState = operation.State;
var operationError = operation.ResourceOperationError;
if (IsOperationStateComplete(operationState))
{
completedOps.TryAdd(operationId, operation);
Console.WriteLine($"Operation {operationId} completed with state {operationState}");
if (operationError != null)
{
Console.WriteLine($"Operation {operationId} encountered the following error: errorCode {operationError.ErrorCode}, errorDetails: {operationError.ErrorDetails}");
}
}
}
// CompletedOps.Count == TotalVmsCount means that all the operations have completed and there would be no need to retry polling
if (completedOps.Count == totalVmsCount)
{
shouldRetry = false;
}
return shouldRetry;
}
/// <summary>
/// Removes the operations that have completed from the list of operations to poll
/// </summary>
/// <param name="completedOps"> Dictionary of completed operations, that is, operations where state is either Succeeded, Failed, Cancelled </param>
/// <param name="allOps"></param>
/// <returns></returns>
private static HashSet<string?> ExcludeCompletedOperations(Dictionary<string, ResourceOperationDetails> completedOps, HashSet<string?> allOps)
{
var incompleteOps = new HashSet<string?>(allOps);
foreach (var op in allOps)
{
if (op != null && completedOps.ContainsKey(op))
{
incompleteOps.Remove(op);
}
}
Console.WriteLine(string.Join(", ", incompleteOps));
return incompleteOps;
}
/// <summary>
/// This method excludes resources not processed in Scheduledactions due to a number of reasons like operation conflicts,
/// operations in a blocked state due to scenarios like outages in downstream services, internal outages etc.
/// </summary>
/// <param name="results"></param>
/// <returns></returns>
public static HashSet<string?> ExcludeResourcesNotProcessed(IEnumerable<ResourceOperationResult> results)
{
var validOperationIds = new HashSet<string?>();
foreach (var result in results)
{
if (result.ErrorCode != null)
{
Console.WriteLine($"VM with resourceId: {result.ResourceId} encountered the following error: errorCode {result.ErrorCode}, errorDetails: {result.ErrorDetails}");
}
else if(result.Operation.State == ScheduledActionOperationState.Blocked)
{
/// Operations on virtual machines are set to blocked state in Computeschedule when there is an ongoing outage internally or in downstream services.
/// These operations could still be processed later as long as their due time for execution is not past deadline time + retrywindowinminutes
Console.WriteLine($"Operation on VM with resourceId: {result.ResourceId} is currently blocked, operation may still complete");
}
else
{
validOperationIds.Add(result.Operation.OperationId);
}
}
return validOperationIds;
}
/// <summary>
/// Polls the operation status for the operations that are in not yet in completed state
/// </summary>
/// <param name="cts"></param>
/// <param name="opIdsFromOperationReq"> OperationIds from execute type operations </param>
/// <param name="completedOps"> OperationIds of completed operations </param>
/// <param name="location"> Location of the virtual machines from execute type operations </param>
/// <param name="resource"> ARM subscription resource </param>
/// <returns></returns>
public static async Task PollOperationStatus(HashSet<string?> opIdsFromOperationReq, Dictionary<string, ResourceOperationDetails> completedOps, string location, SubscriptionResource resource)
{
await Task.Delay(InitialWaitTimeBeforePollingInSeconds);
GetOperationStatusContent getOpsStatusRequest = new(opIdsFromOperationReq, Guid.NewGuid().ToString());
GetOperationStatusResult? response = await resource.GetVirtualMachineOperationStatusAsync(location, getOpsStatusRequest);
// Cancellation token source is used in this case to cancel the polling operation after a certain time
using CancellationTokenSource cts = new(TimeSpan.FromMinutes(OperationTimeoutInMinutes));
while (!cts.Token.IsCancellationRequested)
{
if (!ShouldRetryPolling(response, opIdsFromOperationReq.Count, completedOps))
{
break;
}
else
{
var incompleteOperations = ExcludeCompletedOperations(completedOps, opIdsFromOperationReq);
GetOperationStatusContent pendingOpIds = new(incompleteOperations, Guid.NewGuid().ToString());
response = await resource.GetVirtualMachineOperationStatusAsync(location, pendingOpIds);
}
await Task.Delay(TimeSpan.FromSeconds(PollingIntervalInSeconds), cts.Token);
}
}
}
}