예제 #1
0
        private async Task WriteResponse(
            CloudFormationResourceRequest <TProperties> rawRequest,
            CloudFormationResourceResponse <TAttributes> rawResponse
            )
        {
            Exception exception = null;
            var       backoff   = TimeSpan.FromMilliseconds(100);

            // write response to pre-signed S3 URL
            for (var i = 0; i < MAX_SEND_ATTEMPTS; ++i)
            {
                try {
                    if (rawRequest.ResponseURL == null)
                    {
                        throw new InvalidOperationException("ResponseURL is missing");
                    }
                    var httpResponse = await HttpClient.SendAsync(new HttpRequestMessage {
                        RequestUri = new Uri(rawRequest.ResponseURL),
                        Method     = HttpMethod.Put,
                        Content    = new ByteArrayContent(Encoding.UTF8.GetBytes(LambdaSerializer.Serialize(rawResponse)))
                    });

                    if (httpResponse.StatusCode != HttpStatusCode.OK)
                    {
                        throw new LambdaCustomResourceException(
                                  "PUT operation to pre-signed S3 URL failed with status code: {0} [{1} {2}] = {3}",
                                  httpResponse.StatusCode,
                                  rawRequest.RequestType,
                                  rawRequest.ResourceType ?? "<MISSING>",
                                  await httpResponse.Content.ReadAsStringAsync()
                                  );
                    }
                    return;
                } catch (InvalidOperationException e) {
                    exception = e;
                    break;
                } catch (Exception e) {
                    exception = e;
                    LogErrorAsWarning(e, "writing response to pre-signed S3 URL failed");
                    await Task.Delay(backoff);

                    backoff = TimeSpan.FromSeconds(backoff.TotalSeconds * 2);
                }
            }
            if (exception == null)
            {
                exception = new ShouldNeverHappenException($"ALambdaCustomResourceFunction.WriteResponse failed w/o an explicit");
            }

            // max attempts have been reached; fail permanently and record the failed request for playback
            LogError(exception);
            await RecordFailedMessageAsync(LambdaLogLevel.ERROR, FailedMessageOrigin.CloudFormation, LambdaSerializer.Serialize(rawRequest), exception);
        }
예제 #2
0
 /// <summary>
 /// The <see cref="HandleFailedInitializationAsync(Stream)"/> method is only invoked when an error occurs during the
 /// Lambda function initialization. This method can be overridden to provide custom behavior for how to handle such
 /// failures more gracefully.
 /// </summary>
 /// <remarks>
 /// Regardless of what this method does. Once completed, the Lambda function exits by rethrowing the original exception
 /// that occurred during initialization.
 /// </remarks>
 /// <param name="stream">The stream with the request payload.</param>
 /// <returns>The task object representing the asynchronous operation.</returns>
 protected override async Task HandleFailedInitializationAsync(Stream stream)
 {
     // NOTE (2018-12-12, bjorg): function initialization failed; attempt to notify
     //  the CloudFormation service of the failure.
     try {
         var rawRequest  = DeserializeStream(stream);
         var rawResponse = new CloudFormationResourceResponse <TAttributes> {
             Status             = CloudFormationResourceResponseStatus.FAILED,
             Reason             = "custom resource initialization failed",
             StackId            = rawRequest.StackId,
             RequestId          = rawRequest.RequestId,
             LogicalResourceId  = rawRequest.LogicalResourceId,
             PhysicalResourceId = rawRequest.PhysicalResourceId ?? "no-physical-id"
         };
         await WriteResponse(rawRequest, rawResponse);
     } catch { }
 }
        private async Task WriteResponse(
            CloudFormationResourceRequest <TProperties> rawRequest,
            CloudFormationResourceResponse <TAttributes> rawResponse
            )
        {
            Exception exception = null;

            // write response to pre-signed S3 URL
            for (var i = 0; i < MAX_SEND_ATTEMPTS; ++i)
            {
                try {
                    var httpResponse = await HttpClient.SendAsync(new HttpRequestMessage {
                        RequestUri = new Uri(rawRequest.ResponseURL),
                        Method     = HttpMethod.Put,
                        Content    = new ByteArrayContent(Encoding.UTF8.GetBytes(SerializeJson(rawResponse)))
                    });

                    if (httpResponse.StatusCode != HttpStatusCode.OK)
                    {
                        throw new LambdaCustomResourceException(
                                  "PUT operation to pre-signed S3 URL failed with status code: {0} [{1} {2}] = {3}",
                                  httpResponse.StatusCode,
                                  rawRequest.RequestType,
                                  rawRequest.ResourceType,
                                  await httpResponse.Content.ReadAsStringAsync()
                                  );
                    }
                    return;
                } catch (Exception e) {
                    exception = e;
                    LogErrorAsWarning(e, "writing response to pre-signed S3 URL failed");
                    await Task.Delay(TimeSpan.FromMilliseconds(100));
                }
            }

            // max attempts have been reached; fail permanently and record the failed request for playback
            LogError(exception);
            await RecordFailedMessageAsync(LambdaLogLevel.ERROR, FailedMessageOrigin.CloudFormation, SerializeJson(rawRequest), exception);
        }
예제 #4
0
        //--- Methods ---

        /// <summary>
        /// The <see cref="ProcessMessageStreamAsync(Stream)"/> method handles the communication protocol with the AWS CloudFormation service.
        /// </summary>
        /// <remarks>
        /// This method cannot be overridden.
        /// </remarks>
        /// <param name="stream">The stream with the request payload.</param>
        /// <returns>The task object representing the asynchronous operation.</returns>
        public override sealed async Task <Stream> ProcessMessageStreamAsync(Stream stream)
        {
            var rawRequest = DeserializeStream(stream);

            // process message
            CloudFormationResourceResponse <TAttributes> rawResponse;

            LogInfo("processing request");
            try {
                LogInfo($"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation received");
                var request = new Request <TProperties> {
                    RequestType           = rawRequest.RequestType,
                    ResourceType          = rawRequest.ResourceType,
                    StackId               = rawRequest.StackId,
                    LogicalResourceId     = rawRequest.LogicalResourceId,
                    PhysicalResourceId    = rawRequest.PhysicalResourceId,
                    ResourceProperties    = rawRequest.ResourceProperties,
                    OldResourceProperties = rawRequest.OldResourceProperties
                };

                // handle slack request
                Task <Response <TAttributes> > responseTask;
                switch (request.RequestType)
                {
                case RequestType.Create:
                    responseTask = ProcessCreateResourceAsync(request);
                    break;

                case RequestType.Update:
                    responseTask = ProcessUpdateResourceAsync(request);
                    break;

                case RequestType.Delete:
                    responseTask = ProcessDeleteResourceAsync(request);
                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(request.RequestType), request.RequestType, "unexpected request value");
                }

                // check if the custom resource logic completes before function times out
                var timeout = CurrentContext.RemainingTime - TimeSpan.FromSeconds(0.5);
                if (await Task.WhenAny(Task.Delay(timeout), responseTask) != responseTask)
                {
                    // TODO (2020-07-31, bjorg): set cancellation token when available on ProcessXYSResourceAsync()
                    throw new TimeoutException($"custom resource operation timed out");
                }

                // await completed task to trigger any contained exceptions
                await responseTask;
                LogInfo($"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation was successful");
                rawResponse = new CloudFormationResourceResponse <TAttributes> {
                    Status             = CloudFormationResourceResponseStatus.SUCCESS,
                    Reason             = "",
                    StackId            = rawRequest.StackId,
                    RequestId          = rawRequest.RequestId,
                    LogicalResourceId  = rawRequest.LogicalResourceId,
                    PhysicalResourceId = responseTask.Result.PhysicalResourceId ?? rawRequest.PhysicalResourceId,
                    NoEcho             = responseTask.Result.NoEcho,
                    Data = responseTask.Result.Attributes
                };
            } catch (AggregateException aggregateException) when(
                (aggregateException.InnerExceptions.Count() == 1) &&
                (aggregateException.InnerExceptions[0] is CustomResourceAbortException e)
                )
            {
                LogInfo($"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation ABORTED [{{0}}]", e.Message);
                rawResponse = new CloudFormationResourceResponse <TAttributes> {
                    Status             = CloudFormationResourceResponseStatus.FAILED,
                    Reason             = e.Message,
                    StackId            = rawRequest.StackId,
                    RequestId          = rawRequest.RequestId,
                    LogicalResourceId  = rawRequest.LogicalResourceId,
                    PhysicalResourceId = rawRequest.PhysicalResourceId ?? "no-physical-id"
                };
            } catch (CustomResourceAbortException e) {
                LogInfo($"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation ABORTED [{{0}}]", e.Message);
                rawResponse = new CloudFormationResourceResponse <TAttributes> {
                    Status             = CloudFormationResourceResponseStatus.FAILED,
                    Reason             = e.Message,
                    StackId            = rawRequest.StackId,
                    RequestId          = rawRequest.RequestId,
                    LogicalResourceId  = rawRequest.LogicalResourceId,
                    PhysicalResourceId = rawRequest.PhysicalResourceId ?? "no-physical-id"
                };
            } catch (Exception e) {
                LogError(e, $"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation FAILED [{{0}}]", e.Message);
                rawResponse = new CloudFormationResourceResponse <TAttributes> {
                    Status             = CloudFormationResourceResponseStatus.FAILED,
                    Reason             = "internal error",
                    StackId            = rawRequest.StackId,
                    RequestId          = rawRequest.RequestId,
                    LogicalResourceId  = rawRequest.LogicalResourceId,
                    PhysicalResourceId = rawRequest.PhysicalResourceId ?? "no-physical-id"
                };
            }

            // write response
            await WriteResponse(rawRequest, rawResponse);

            return(rawResponse.Status.ToString().ToStream());
        }
예제 #5
0
        //--- Methods ---
        public override async Task ProcessMessageAsync(CloudFormationResourceRequest <TRequestProperties> rawRequest, ILambdaContext context)
        {
            LogInfo(JsonConvert.SerializeObject(rawRequest, Formatting.Indented));
            CloudFormationResourceResponse <TResponseProperties> rawResponse;

            LogInfo($"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation received");
            try {
                var request = new Request <TRequestProperties> {
                    RequestType           = rawRequest.RequestType,
                    ResourceType          = rawRequest.ResourceType,
                    LogicalResourceId     = rawRequest.LogicalResourceId,
                    PhysicalResourceId    = rawRequest.PhysicalResourceId,
                    ResourceProperties    = rawRequest.ResourceProperties,
                    OldResourceProperties = rawRequest.OldResourceProperties
                };

                // handle slack request
                Response <TResponseProperties> response;
                switch (request.RequestType)
                {
                case RequestType.Create:
                    response = await HandleCreateResourceAsync(request);

                    break;

                case RequestType.Update:
                    response = await HandleUpdateResourceAsync(request);

                    break;

                case RequestType.Delete:
                    response = await HandleDeleteResourceAsync(request);

                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(request.RequestType), request.RequestType, "unexpected request value");
                }
                rawResponse = new CloudFormationResourceResponse <TResponseProperties> {
                    Status             = CloudFormationResourceResponseStatus.SUCCESS,
                    Reason             = "",
                    StackId            = rawRequest.StackId,
                    RequestId          = rawRequest.RequestId,
                    LogicalResourceId  = rawRequest.LogicalResourceId,
                    PhysicalResourceId = response.PhysicalResourceId ?? rawRequest.PhysicalResourceId,
                    NoEcho             = response.NoEcho,
                    Data = response.Properties
                };
                LogInfo($"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation was successful");
            } catch (Exception e) {
                LogError(e, $"{rawRequest.ResourceType}: {rawRequest.RequestType.ToString().ToUpperInvariant()} operation FAILED [{{0}}]", e.Message);
                rawResponse = new CloudFormationResourceResponse <TResponseProperties> {
                    Status            = CloudFormationResourceResponseStatus.FAILED,
                    Reason            = e.Message,
                    StackId           = rawRequest.StackId,
                    RequestId         = rawRequest.RequestId,
                    LogicalResourceId = rawRequest.LogicalResourceId
                };
            }

            // write response to pre-signed S3 URL
            try {
                var httpResponse = await HttpClient.SendAsync(new HttpRequestMessage {
                    RequestUri = new Uri(rawRequest.ResponseURL),
                    Method     = HttpMethod.Put,
                    Content    = new ByteArrayContent(Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(rawResponse)))
                });

                if (httpResponse.StatusCode != HttpStatusCode.OK)
                {
                    throw new CustomResourceException(
                              "PUT operation to pre-signed S3 URL failed with status code: {0} [{1} {2}] = {3}",
                              httpResponse.StatusCode,
                              rawRequest.RequestType,
                              rawRequest.ResourceType,
                              await httpResponse.Content.ReadAsStringAsync()
                              );
                }
            } catch (Exception e) {
                LogError(e, "writing response to pre-signed S3 URL failed");

                // TODO (2018-06-14, bjorg): how should we handle this? we may want the handler to re-attempt the
                // resource creation since the request was SNS based
                throw;
            }
        }