< Summary

Class:Azure.Core.ArmOperationHelpers`1
Assembly:Azure.ResourceManager.Compute
File(s):C:\Git\azure-sdk-for-net\sdk\core\Azure.Core\src\Shared\AutoRest\ArmOperationHelpers.cs
Covered lines:126
Uncovered lines:29
Coverable lines:155
Total lines:359
Line coverage:81.2% (126 of 155)
Covered branches:76
Total branches:98
Branch coverage:77.5% (76 of 98)

Metrics

MethodCyclomatic complexity Line coverage Branch coverage
get_DefaultPollingInterval()-0%100%
.cctor()-100%100%
.ctor(...)-100%100%
GetRawResponse()-100%100%
WaitForCompletionAsync(...)-0%100%
WaitForCompletionAsync()-100%100%
UpdateStatusAsync()-90.32%60.71%
UpdateStatusAsync()-100%100%
UpdateStatus(...)-0%100%
get_Id()-0%100%
get_Value()-66.67%50%
get_HasCompleted()-100%100%
get_HasValue()-100%100%
CreateRequest(...)-100%100%
GetResponseAsync()-63.64%50%
GetResponse(...)-0%0%
IsTerminalState(...)-95.83%85.71%
InitializeScenarioInfo()-84.62%83.33%
UpdatePollUri()-78.57%83.33%
GetFinalUri()-100%100%

File(s)

C:\Git\azure-sdk-for-net\sdk\core\Azure.Core\src\Shared\AutoRest\ArmOperationHelpers.cs

#LineLine coverage
 1// Copyright (c) Microsoft Corporation. All rights reserved.
 2// Licensed under the MIT License.
 3
 4#nullable enable
 5
 6using System;
 7using System.Linq;
 8using System.Text.Json;
 9using System.Threading;
 10using System.Threading.Tasks;
 11using Azure.Core.Pipeline;
 12
 13namespace Azure.Core
 14{
 15    /// <summary>
 16    /// This implements the ARM scenarios for LROs. It is highly recommended to read the ARM spec prior to modifying thi
 17    /// https://github.com/Azure/azure-resource-manager-rpc/blob/master/v1.0/Addendum.md#asynchronous-operations
 18    /// Other reference documents include:
 19    /// https://github.com/Azure/autorest/blob/master/docs/extensions/readme.md#x-ms-long-running-operation
 20    /// https://github.com/Azure/adx-documentation-pr/blob/master/sdks/LRO/LRO_AzureSDK.md
 21    /// </summary>
 22    /// <typeparam name="T">The final result of the LRO.</typeparam>
 23    internal class ArmOperationHelpers<T>
 24    {
 025        public static TimeSpan DefaultPollingInterval { get; } = TimeSpan.FromSeconds(1);
 26
 4627        private static readonly string[] s_failureStates = {"failed", "canceled"};
 4628        private static readonly string[] s_terminalStates = {"succeeded", "failed", "canceled"};
 29
 30        private readonly HttpPipeline _pipeline;
 31        private readonly ClientDiagnostics _clientDiagnostics;
 32        private readonly string _scopeName;
 33        private readonly RequestMethod _requestMethod;
 34        private readonly string _originalUri;
 35        private readonly OperationFinalStateVia _finalStateVia;
 36        private HeaderFrom _headerFrom;
 37        private string _pollUri = default!;
 38        private bool _originalHasLocation;
 39        private string? _lastKnownLocation;
 40
 41        private readonly IOperationSource<T> _source;
 42        private Response _rawResponse;
 43        private T _value = default!;
 44        private bool _hasValue;
 45        private bool _hasCompleted;
 46        private bool _shouldPoll;
 47
 216048        public ArmOperationHelpers(
 216049            IOperationSource<T> source,
 216050            ClientDiagnostics clientDiagnostics,
 216051            HttpPipeline pipeline,
 216052            Request originalRequest,
 216053            Response originalResponse,
 216054            OperationFinalStateVia finalStateVia,
 216055            string scopeName)
 56        {
 216057            _source = source;
 216058            _rawResponse = originalResponse;
 216059            _requestMethod = originalRequest.Method;
 216060            _originalUri = originalRequest.Uri.ToString();
 216061            _finalStateVia = finalStateVia;
 216062            InitializeScenarioInfo();
 63
 216064            _pipeline = pipeline;
 216065            _clientDiagnostics = clientDiagnostics;
 216066            _scopeName = scopeName;
 67            // When the original response has no headers, we do not start polling immediately.
 216068            _shouldPoll = _headerFrom != HeaderFrom.None;
 216069        }
 70
 41218471        public Response GetRawResponse() => _rawResponse;
 72
 73        public ValueTask<Response<T>> WaitForCompletionAsync(CancellationToken cancellationToken = default)
 74        {
 075            return WaitForCompletionAsync(DefaultPollingInterval, cancellationToken);
 76        }
 77
 78        public async ValueTask<Response<T>> WaitForCompletionAsync(TimeSpan pollingInterval, CancellationToken cancellat
 79        {
 20178080            while (true)
 81            {
 20393682                await UpdateStatusAsync(cancellationToken).ConfigureAwait(false);
 20393683                if (HasCompleted)
 84                {
 215685                    return Response.FromValue(Value, GetRawResponse());
 86                }
 87
 20178088                await Task.Delay(pollingInterval, cancellationToken).ConfigureAwait(false);
 89            }
 215690        }
 91
 92        private async ValueTask<Response> UpdateStatusAsync(bool async, CancellationToken cancellationToken)
 93        {
 20393694            if (_hasCompleted)
 95            {
 096                return GetRawResponse();
 97            }
 98
 20393699            if (_shouldPoll)
 100            {
 203836101                UpdatePollUri();
 203836102                _rawResponse = async
 203836103                    ? await GetResponseAsync(_pollUri, cancellationToken).ConfigureAwait(false)
 203836104                    : GetResponse(_pollUri, cancellationToken);
 105            }
 106
 203936107            _shouldPoll = true;
 203936108            _hasCompleted = IsTerminalState(out string state);
 203936109            if (_hasCompleted)
 110            {
 2156111                Response finalResponse = GetRawResponse();
 2156112                if (s_failureStates.Contains(state))
 113                {
 0114                    throw _clientDiagnostics.CreateRequestFailedException(finalResponse);
 115                }
 116
 2156117                string? finalUri = GetFinalUri();
 2156118                if (finalUri != null)
 119                {
 1588120                    finalResponse = async
 1588121                        ? await GetResponseAsync(finalUri, cancellationToken).ConfigureAwait(false)
 1588122                        : GetResponse(finalUri, cancellationToken);
 123                }
 124
 2156125                switch (finalResponse.Status)
 126                {
 127                    case 200:
 4128                    case 201 when _requestMethod == RequestMethod.Put:
 80129                    case 204 when !(_requestMethod == RequestMethod.Put || _requestMethod == RequestMethod.Patch):
 130                    {
 2156131                        _value = async
 2156132                            ? await _source.CreateResultAsync(finalResponse, cancellationToken).ConfigureAwait(false)
 2156133                            : _source.CreateResult(finalResponse, cancellationToken);
 2156134                        _rawResponse = finalResponse;
 2156135                        _hasValue = true;
 2156136                        break;
 137                    }
 138                    default:
 0139                        throw _clientDiagnostics.CreateRequestFailedException(finalResponse);
 140                }
 2156141            }
 142
 203936143            return GetRawResponse();
 203936144        }
 145
 203936146        public async ValueTask<Response> UpdateStatusAsync(CancellationToken cancellationToken = default) => await Updat
 147
 0148        public Response UpdateStatus(CancellationToken cancellationToken = default) => UpdateStatusAsync(async: false, c
 149
 150#pragma warning disable CA1822
 151        //TODO: This is currently unused.
 0152        public string Id => throw new NotImplementedException();
 153#pragma warning restore CA1822
 154
 155        public T Value
 156        {
 157            get
 158            {
 2156159                if (!HasValue)
 160                {
 0161                    throw new InvalidOperationException("The operation has not completed yet.");
 162                }
 163
 2156164                return _value;
 165            }
 166        }
 167
 203936168        public bool HasCompleted => _hasCompleted;
 2156169        public bool HasValue => _hasValue;
 170
 171        private HttpMessage CreateRequest(string link)
 172        {
 205424173            HttpMessage message = _pipeline.CreateMessage();
 205424174            Request request = message.Request;
 205424175            request.Method = RequestMethod.Get;
 205424176            request.Uri.Reset(new Uri(link));
 205424177            return message;
 178        }
 179
 180        private async ValueTask<Response> GetResponseAsync(string link, CancellationToken cancellationToken = default)
 181        {
 205424182            if (link == null)
 183            {
 0184                throw new ArgumentNullException(nameof(link));
 185            }
 186
 205424187            using DiagnosticScope scope = _clientDiagnostics.CreateScope(_scopeName);
 205424188            scope.Start();
 189            try
 190            {
 205424191                using HttpMessage message = CreateRequest(link);
 205424192                await _pipeline.SendAsync(message, cancellationToken).ConfigureAwait(false);
 205424193                return message.Response;
 194            }
 0195            catch (Exception e)
 196            {
 0197                scope.Failed(e);
 0198                throw;
 199            }
 205424200        }
 201
 202        private Response GetResponse(string link, CancellationToken cancellationToken = default)
 203        {
 0204            if (link == null)
 205            {
 0206                throw new ArgumentNullException(nameof(link));
 207            }
 208
 0209            using DiagnosticScope scope = _clientDiagnostics.CreateScope(_scopeName);
 0210            scope.Start();
 211            try
 212            {
 0213                using HttpMessage message = CreateRequest(link);
 0214                _pipeline.Send(message, cancellationToken);
 0215                return message.Response;
 216            }
 0217            catch (Exception e)
 218            {
 0219                scope.Failed(e);
 0220                throw;
 221            }
 0222        }
 223
 224        private bool IsTerminalState(out string state)
 225        {
 203936226            Response response = GetRawResponse();
 203936227            state = string.Empty;
 203936228            if (_headerFrom == HeaderFrom.Location)
 229            {
 10582230                return response.Status != 202;
 231            }
 232
 193354233            if (response.Status >= 200 && response.Status <= 204)
 234            {
 193354235                if (response.ContentStream?.Length > 0)
 236                {
 237                    try
 238                    {
 193270239                        using JsonDocument document = JsonDocument.Parse(response.ContentStream);
 957638240                        foreach (JsonProperty property in document.RootElement.EnumerateObject())
 241                        {
 382180242                            if ((_headerFrom == HeaderFrom.OperationLocation ||
 382180243                                 _headerFrom == HeaderFrom.AzureAsyncOperation) &&
 382180244                                property.NameEquals("status"))
 245                            {
 193250246                                state = property.Value.GetString().ToLowerInvariant();
 193250247                                return s_terminalStates.Contains(state);
 248                            }
 249
 188930250                            if (_headerFrom == HeaderFrom.None && property.NameEquals("properties"))
 251                            {
 260252                                foreach (JsonProperty innerProperty in property.Value.EnumerateObject())
 253                                {
 116254                                    if (innerProperty.NameEquals("provisioningState"))
 255                                    {
 12256                                        state = innerProperty.Value.GetString().ToLowerInvariant();
 12257                                        return s_terminalStates.Contains(state);
 258                                    }
 259                                }
 260                            }
 261                        }
 262                    }
 263                    finally
 264                    {
 265                        // It is required to reset the position of the content after reading as this response may be use
 193270266                        response.ContentStream.Position = 0;
 193270267                    }
 268                }
 269
 270                // If provisioningState was not found, it defaults to Succeeded.
 92271                if (_headerFrom == HeaderFrom.None)
 272                {
 92273                    return true;
 274                }
 275            }
 276
 0277            throw _clientDiagnostics.CreateRequestFailedException(response);
 193262278        }
 279
 280        private enum HeaderFrom
 281        {
 282            None,
 283            OperationLocation,
 284            AzureAsyncOperation,
 285            Location
 286        }
 287
 288        private void InitializeScenarioInfo()
 289        {
 2160290            _originalHasLocation = _rawResponse.Headers.Contains("Location");
 291
 2160292            if (_rawResponse.Headers.Contains("Operation-Location"))
 293            {
 0294                _headerFrom = HeaderFrom.OperationLocation;
 0295                return;
 296            }
 297
 2160298            if (_rawResponse.Headers.Contains("Azure-AsyncOperation"))
 299            {
 1808300                _headerFrom = HeaderFrom.AzureAsyncOperation;
 1808301                return;
 302            }
 303
 352304            if (_originalHasLocation)
 305            {
 252306                _headerFrom = HeaderFrom.Location;
 252307                return;
 308            }
 309
 100310            _pollUri = _originalUri;
 100311            _headerFrom = HeaderFrom.None;
 100312        }
 313
 314        private void UpdatePollUri()
 315        {
 203836316            var hasLocation = _rawResponse.Headers.TryGetValue("Location", out string? location);
 203836317            if (hasLocation)
 318            {
 11154319                _lastKnownLocation = location;
 320            }
 321
 203836322            switch (_headerFrom)
 323            {
 0324                case HeaderFrom.OperationLocation when _rawResponse.Headers.TryGetValue("Operation-Location", out string
 0325                    _pollUri = operationLocation;
 0326                    return;
 193250327                case HeaderFrom.AzureAsyncOperation when _rawResponse.Headers.TryGetValue("Azure-AsyncOperation", out st
 1804328                    _pollUri = azureAsyncOperation;
 1804329                    return;
 10582330                case HeaderFrom.Location when hasLocation:
 10582331                    _pollUri = location!;
 10582332                    return;
 333            }
 191446334        }
 335
 336        private string? GetFinalUri()
 337        {
 2156338            if (_headerFrom == HeaderFrom.OperationLocation || _headerFrom == HeaderFrom.AzureAsyncOperation)
 339            {
 1804340                if (_requestMethod == RequestMethod.Delete)
 341                {
 212342                    return null;
 343                }
 344
 1592345                if (_requestMethod == RequestMethod.Put || (_originalHasLocation && _finalStateVia == OperationFinalStat
 346                {
 1340347                    return _originalUri;
 348                }
 349
 252350                if (_originalHasLocation && _finalStateVia == OperationFinalStateVia.Location)
 351                {
 248352                    return _lastKnownLocation;
 353                }
 354            }
 355
 356356            return null;
 357        }
 358    }
 359}