コード例 #1
0
        public void returns_empty_meter_if_not_meter_metric()
        {
            var meter = new CustomMeter();
            var value = meter.GetValueOrDefault();

            value.Should().NotBeNull();
        }
コード例 #2
0
        /// <summary>
        /// Updates a customMeter.
        /// </summary>
        /// <param name="meterName">The name of the customMeter to update.</param>
        /// <param name="customMeter">The updated customMeter.</param>
        /// <returns>The updated customMeter.</returns>
        public async Task <CustomMeter> UpdateAsync(string meterName, CustomMeter customMeter)
        {
            if (customMeter is null)
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(CustomMeter).Name),
                                                      UserErrorCode.PayloadNotProvided);
            }
            _logger.LogInformation(LoggingUtils.ComposeUpdateResourceMessage(typeof(CustomMeter).Name, customMeter.MeterName, payload: JsonSerializer.Serialize(customMeter)));

            // Get the customMeter that matches the meterName provided
            var customMeterDb = await GetAsync(meterName);

            // Check if (the meterName has been updated) &&
            //          (a customMeter with the same new name does not already exist)
            if ((meterName != customMeter.MeterName) && (await ExistsAsync(customMeter.MeterName)))
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(CustomMeter).Name),
                                                      UserErrorCode.NameMismatch);
            }

            // Copy over the changes
            customMeterDb.Copy(customMeter);

            // Update customMeterDb values and save changes in db
            _context.CustomMeters.Update(customMeterDb);
            await _context._SaveChangesAsync();

            _logger.LogInformation(LoggingUtils.ComposeResourceUpdatedMessage(typeof(CustomMeter).Name, customMeter.MeterName));

            return(customMeterDb);
        }
コード例 #3
0
        public async Task <ActionResult> CreateOrUpdateAsync(string meterName, [FromBody] CustomMeter customMeter)
        {
            if (customMeter == null)
            {
                throw new ArgumentNullException(nameof(customMeter));
            }

            if (!meterName.Equals(customMeter.MeterName))
            {
                throw new ArgumentException("The meter name in url doesn't match meter name in request body.");
            }

            if (await _customMeterService.ExistsAsync(meterName))
            {
                await _customMeterService.UpdateAsync(meterName, customMeter);

                return(Ok(customMeter));
            }
            else
            {
                await _customMeterService.CreateAsync(customMeter);

                return(CreatedAtAction(nameof(GetAsync), new { meterName = customMeter.MeterName }, customMeter));
            }
        }
コード例 #4
0
        /// <summary>
        /// Creates a customMeter.
        /// </summary>
        /// <param name="customMeter">The customMeter to create.</param>
        /// <returns>The created customMeter.</returns>
        public async Task <CustomMeter> CreateAsync(CustomMeter customMeter)
        {
            if (customMeter is null)
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(CustomMeter).Name),
                                                      UserErrorCode.PayloadNotProvided);
            }
            _logger.LogInformation(LoggingUtils.ComposeCreateResourceMessage(typeof(CustomMeter).Name, customMeter.MeterName, payload: JsonSerializer.Serialize(customMeter)));

            // Check that an customMeter with the same name does not already exist
            if (await ExistsAsync(customMeter.MeterName))
            {
                throw new LunaConflictUserException(LoggingUtils.ComposeAlreadyExistsErrorMessage(typeof(CustomMeter).Name,
                                                                                                  customMeter.MeterName));
            }

            // Add customMeter to db
            _context.CustomMeters.Add(customMeter);
            await _context._SaveChangesAsync();

            _logger.LogInformation(LoggingUtils.ComposeResourceCreatedMessage(typeof(CustomMeter).Name, customMeter.MeterName));

            return(customMeter);
        }
コード例 #5
0
        private async Task ReportBatchMeterEventsInternal(Offer offer, CustomMeter meter, DateTime effectiveStartTime)
        {
            _logger.LogInformation($"Query and report meter event {meter.MeterName} starting {effectiveStartTime}.");

            DateTime effectiveEndTime = effectiveStartTime.AddHours(1);

            var telemetryDataConnector = await _telemetryDataConnectorService.GetAsync(meter.TelemetryDataConnectorName);

            ITelemetryDataConnector connector = _telemetryConnectionManager.CreateTelemetryDataConnector(
                telemetryDataConnector.Type,
                telemetryDataConnector.Configuration);

            IEnumerable <Usage> meterEvents = await connector.GetMeterEventsByHour(effectiveStartTime, meter.TelemetryQuery);

            // Get the billable meter events
            List <Usage> billableMeterEvents = new List <Usage>();

            foreach (var meterEvent in meterEvents)
            {
                // Send the meter event only if:
                // 1. Subscription exists
                // 2. Subscription is in a plan using the current custom meter
                // 3. Meter usage is enabled
                // 4. The meter usage is not reported
                // 5. There's no error or error happened after the effective start time (we don't skip error).
                Guid subscriptionId;
                if (!Guid.TryParse(meterEvent.ResourceId, out subscriptionId))
                {
                    _logger.LogWarning($"ResourceId {meterEvent.ResourceId} is not a valid subscription. The data type should be GUID.");
                    continue;
                }

                if (!await _subscriptionService.ExistsAsync(subscriptionId))
                {
                    _logger.LogWarning($"The subscription {subscriptionId} doesn't exist. Will not report the meter event {meterEvent.Dimension}.");
                    continue;
                }

                if (!await _subscriptionCustomMeterUsageService.ExistsAsync(subscriptionId, meter.MeterName))
                {
                    _logger.LogWarning($"The subscription usage record with {subscriptionId} and meter name {meter.MeterName} doesn't exist. Will not report the meter event {meterEvent.Dimension}.");
                    continue;
                }

                var subscription = await _subscriptionService.GetAsync(subscriptionId);

                var meterUsage = await _subscriptionCustomMeterUsageService.GetAsync(subscriptionId, meter.MeterName);

                if (await _customMeterDimensionService.ExistsAsync(offer.OfferName, subscription.PlanName, meter.MeterName) &&
                    meterUsage.IsEnabled &&
                    meterUsage.LastUpdatedTime < effectiveEndTime &&
                    (meterUsage.LastErrorReportedTime == DateTime.MinValue || meterUsage.LastErrorReportedTime >= effectiveStartTime))
                {
                    meterEvent.Dimension = meter.MeterName;
                    meterEvent.PlanId    = subscription.PlanName;
                    billableMeterEvents.Add(meterEvent);
                }
            }

            CustomMeteringRequestResult requestResult = new CustomMeteringBatchSuccessResult();

            if (billableMeterEvents.Count > 0)
            {
                requestResult = await _customMeteringClient.RecordBatchUsageAsync(
                    Guid.NewGuid(),
                    Guid.NewGuid(),
                    billableMeterEvents,
                    default);
            }
            else
            {
                // Create an empty result
                ((CustomMeteringBatchSuccessResult)requestResult).Success = true;
                ((CustomMeteringBatchSuccessResult)requestResult).Result  = new List <CustomMeteringSuccessResult>();
            }

            if (requestResult.Success)
            {
                CustomMeteringBatchSuccessResult batchResult = (CustomMeteringBatchSuccessResult)requestResult;

                foreach (var result in batchResult.Result)
                {
                    var subscriptionId         = Guid.Parse(result.ResourceId);
                    var subscriptionMeterUsage = await _subscriptionCustomMeterUsageService.GetAsync(subscriptionId, meter.MeterName);

                    if (result.Status.Equals(nameof(CustomMeterEventStatus.Accepted), StringComparison.InvariantCultureIgnoreCase) ||
                        result.Status.Equals(nameof(CustomMeterEventStatus.Duplicate), StringComparison.InvariantCultureIgnoreCase))
                    {
                        _logger.LogWarning($"Meter event {result.Dimension} for subscription {result.ResourceId} at {result.EffectiveStartTime} reported at {DateTime.Now}, with status {result.Status}.");
                        subscriptionMeterUsage.LastUpdatedTime       = effectiveEndTime;
                        subscriptionMeterUsage.LastErrorReportedTime = DateTime.MinValue;

                        // Always record the reported meter event to Azure table if it is duplicate.
                        var tableEntity = new CustomMeteringAzureTableEntity(
                            result.Status.Equals(nameof(CustomMeterEventStatus.Accepted), StringComparison.InvariantCultureIgnoreCase) ?
                            result : result.Error.AdditionalInfo.AcceptedMessage);

                        await _storageUtility.InsertTableEntity(REPORTED_METER_EVENT_TABLE_NAME, tableEntity);

                        if (effectiveEndTime > subscriptionMeterUsage.UnsubscribedTime)
                        {
                            _logger.LogInformation($"Disabled meter usage for meter {meter.MeterName} subscription {subscriptionId} at {effectiveEndTime}.");
                            subscriptionMeterUsage.IsEnabled    = false;
                            subscriptionMeterUsage.DisabledTime = effectiveEndTime;
                        }
                    }
                    else if (result.Status.Equals(nameof(CustomMeterEventStatus.Expired), StringComparison.InvariantCultureIgnoreCase))
                    {
                        // If the meter event is expired, record a warning and move on
                        _logger.LogWarning($"Meter event {result.Dimension} for subscription {result.ResourceId} at {result.EffectiveStartTime} expired at {DateTime.Now}.");
                        subscriptionMeterUsage.LastUpdatedTime       = effectiveEndTime;
                        subscriptionMeterUsage.LastErrorReportedTime = DateTime.MinValue;

                        await _storageUtility.InsertTableEntity(EXPIRED_METER_EVENT_TABLE_NAME,
                                                                new CustomMeteringAzureTableEntity(result));

                        if (effectiveEndTime > subscriptionMeterUsage.UnsubscribedTime)
                        {
                            _logger.LogInformation($"Disabled meter usage for meter {meter.MeterName} subscription {subscriptionId} at {effectiveEndTime}.");
                            subscriptionMeterUsage.IsEnabled    = false;
                            subscriptionMeterUsage.DisabledTime = effectiveEndTime;
                        }
                    }
                    else
                    {
                        _logger.LogError($"Meter event {result.Dimension} for subscription {result.ResourceId} at {result.EffectiveStartTime} failed to report at {DateTime.Now}.");
                        subscriptionMeterUsage.LastErrorReportedTime = effectiveEndTime;
                        string errorMessage = ComposeErrorMessage(result.Error);
                        subscriptionMeterUsage.LastError = $"Meter event failed with error {result.Status}. Details: {errorMessage}.";

                        await _storageUtility.InsertTableEntity(FAILED_METER_EVENT_TABLE_NAME,
                                                                new FailedCustomMeteringAzureTableEntity(result, errorMessage));

                        // If the meter event request failed with ResourceNotFound error, it could be caused by the time different between
                        // end user cancel the subscription and SaaS service send the cancel request by calling the notification webhook
                        if (effectiveEndTime > subscriptionMeterUsage.UnsubscribedTime && result.Status.Equals("ResourceNotFound"))
                        {
                            _logger.LogInformation($"Disabled meter usage for meter {meter.MeterName} subscription {subscriptionId} at {effectiveEndTime}.");
                            subscriptionMeterUsage.IsEnabled    = false;
                            subscriptionMeterUsage.DisabledTime = effectiveEndTime;
                        }
                    }
                    await _subscriptionCustomMeterUsageService.UpdateAsync(subscriptionId, meter.MeterName, subscriptionMeterUsage);
                }

                await _subscriptionCustomMeterUsageService.UpdateLastUpdatedTimeForUnreportedSubscriptions(offer.OfferName, meter.MeterName, effectiveEndTime);

                _logger.LogInformation($"Completed reporting custom meter events {meter.MeterName} starting {effectiveStartTime}.");
            }
            else
            {
                _logger.LogWarning($"Failed to send the batch meter event. Response code: {requestResult.Code}");
            }
        }
コード例 #6
0
ファイル: CustomMeterService.cs プロジェクト: Azure/ace-luna
        /// <summary>
        /// Updates a customMeter.
        /// </summary>
        /// <param name="offerName">The offer name of the customMeter.</param>
        /// <param name="meterName">The name of the customMeter to update.</param>
        /// <param name="customMeter">The updated customMeter.</param>
        /// <returns>The updated customMeter.</returns>
        public async Task <CustomMeter> UpdateAsync(string offerName, string meterName, CustomMeter customMeter)
        {
            if (customMeter is null)
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(CustomMeter).Name),
                                                      UserErrorCode.PayloadNotProvided);
            }
            _logger.LogInformation(LoggingUtils.ComposeUpdateResourceMessage(typeof(CustomMeter).Name, customMeter.MeterName, payload: JsonSerializer.Serialize(customMeter)));

            // Get the customMeter that matches the meterName provided
            var customMeterDb = await GetAsync(offerName, meterName);

            if ((meterName != customMeter.MeterName) || (offerName != customMeter.OfferName))
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(typeof(CustomMeter).Name),
                                                      UserErrorCode.NameMismatch);
            }

            // Copy over the changes
            customMeterDb.Copy(customMeter);

            var offer = await _offerService.GetAsync(offerName);

            var connector = await _telemetryDataconnectorService.GetAsync(customMeter.TelemetryDataConnectorName);

            customMeterDb.TelemetryDataConnectorId = connector.Id;
            customMeterDb.OfferId = offer.Id;

            // Update customMeterDb values and save changes in db
            _context.CustomMeters.Update(customMeterDb);
            await _context._SaveChangesAsync();

            _logger.LogInformation(LoggingUtils.ComposeResourceUpdatedMessage(typeof(CustomMeter).Name, customMeter.MeterName));

            return(customMeterDb);
        }
コード例 #7
0
ファイル: CustomMeterService.cs プロジェクト: Azure/ace-luna
        /// <summary>
        /// Creates a customMeter.
        /// </summary>
        /// <param name="offerName">The offer name of the customMeter.</param>
        /// <param name="meterName">The name of the customMeter.</param>
        /// <param name="customMeter">The customMeter to create.</param>
        /// <returns>The created customMeter.</returns>
        public async Task <CustomMeter> CreateAsync(string offerName, string meterName, CustomMeter customMeter)
        {
            if (customMeter is null)
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(typeof(CustomMeter).Name),
                                                      UserErrorCode.PayloadNotProvided);
            }
            _logger.LogInformation(LoggingUtils.ComposeCreateResourceMessage(typeof(CustomMeter).Name, customMeter.MeterName, offerName: offerName, payload: JsonSerializer.Serialize(customMeter)));

            // Check that an customMeter with the same name does not already exist
            if (await ExistsAsync(offerName, meterName))
            {
                throw new LunaConflictUserException(LoggingUtils.ComposeAlreadyExistsErrorMessage(typeof(CustomMeter).Name,
                                                                                                  customMeter.MeterName, offerName: offerName));
            }

            var offer = await _offerService.GetAsync(offerName);

            var connector = await _telemetryDataconnectorService.GetAsync(customMeter.TelemetryDataConnectorName);

            customMeter.TelemetryDataConnectorId = connector.Id;
            customMeter.OfferId = offer.Id;


            using (var transaction = await _context.BeginTransactionAsync())
            {
                // Not using subscriptionService here to avoid circular reference
                List <Subscription> subscriptionList = _context.Subscriptions.Where(s => s.OfferId == offer.Id &&
                                                                                    (s.Status == FulfillmentState.Subscribed.ToString() ||
                                                                                     s.Status == FulfillmentState.Suspended.ToString() ||
                                                                                     s.Status == FulfillmentState.PendingFulfillmentStart.ToString())).ToList();

                _context.CustomMeters.Add(customMeter);

                await _context._SaveChangesAsync();

                // Add customMeter to db
                foreach (var sub in subscriptionList)
                {
                    bool isEnabled = sub.Status == FulfillmentState.Subscribed.ToString() || sub.Status == FulfillmentState.Suspended.ToString();
                    _context.SubscriptionCustomMeterUsages.Add(new SubscriptionCustomMeterUsage(customMeter.Id, sub.SubscriptionId, isEnabled));
                }

                await _context._SaveChangesAsync();

                transaction.Commit();
            }


            _logger.LogInformation(LoggingUtils.ComposeResourceCreatedMessage(typeof(CustomMeter).Name, customMeter.MeterName, offerName: offerName));

            return(customMeter);
        }
コード例 #8
0
        public async Task <ActionResult> CreateOrUpdateAsync(string offerName, string meterName, [FromBody] CustomMeter customMeter)
        {
            AADAuthHelper.VerifyUserAccess(this.HttpContext, _logger, true);

            if (customMeter == null)
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposePayloadNotProvidedErrorMessage(nameof(customMeter)), UserErrorCode.PayloadNotProvided);
            }

            if (!offerName.Equals(customMeter.OfferName))
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(nameof(offerName)), UserErrorCode.NameMismatch);
            }

            if (!meterName.Equals(customMeter.MeterName))
            {
                throw new LunaBadRequestUserException(LoggingUtils.ComposeNameMismatchErrorMessage(nameof(meterName)), UserErrorCode.NameMismatch);
            }

            if (await _customMeterService.ExistsAsync(offerName, meterName))
            {
                await _customMeterService.UpdateAsync(offerName, meterName, customMeter);

                return(Ok(customMeter));
            }
            else
            {
                await _customMeterService.CreateAsync(offerName, meterName, customMeter);

                return(CreatedAtRoute(nameof(GetAsync) + nameof(CustomMeter), new { offerName, meterName }, customMeter));
            }
        }