public void returns_empty_meter_if_not_meter_metric() { var meter = new CustomMeter(); var value = meter.GetValueOrDefault(); value.Should().NotBeNull(); }
/// <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); }
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)); } }
/// <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); }
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}"); } }
/// <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); }
/// <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); }
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)); } }