/// <summary> /// Resolves a subscription with Azure Marketplace given a token /// </summary> /// <param name="authCode">The authentication code for the subscription</param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <MarketplaceSubscription> ResolveSubscriptionAsync( string authCode, CancellationToken cancellationToken = default) { try { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.ResolveSubscriptionAsync))); Guid requestId = Guid.NewGuid(); Guid correlationId = Guid.NewGuid(); ResolvedSubscriptionResult subscription = await _fulfillmentClient.ResolveSubscriptionAsync( authCode, requestId, correlationId, cancellationToken); _logger.LogInformation( LoggingUtils.ComposeSubscriptionActionMessage( "Resolved", subscription.SubscriptionId, subscription.PlanId, subscription.Quantity.ToString())); return(MarketplaceSubscription.From(subscription, StatusEnum.Provisioning)); } catch (Exception e) { var errorMessage = "The token is invalid."; throw new LunaFulfillmentException(errorMessage, e); } }
/// <summary> /// Cancel a subscription /// </summary> /// <param name="subscriptionId">The subscription id</param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <FulfillmentManagerOperationResult> RequestCancelSubscriptionAsync( Guid subscriptionId, CancellationToken cancellationToken = default) { try { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.DeleteSubscriptionAsync), subscriptionId)); Guid requestId = Guid.NewGuid(); Guid correlationId = Guid.NewGuid(); UpdateOrDeleteSubscriptionRequestResult response = await _fulfillmentClient.DeleteSubscriptionAsync( subscriptionId, requestId, correlationId, cancellationToken); _logger.LogInformation($"Cancelled subscription {subscriptionId}."); return(FulfillmentManagerOperationResult.Success); } catch (Exception e) { var errorMessage = $"Cannot cancel subscription {subscriptionId}."; throw new LunaFulfillmentException(errorMessage, e); } }
/// <summary> /// Get all subscriptions /// </summary> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <IEnumerable <MarketplaceSubscription> > GetSubscriptionsAsync( CancellationToken cancellationToken = default) { try { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.GetSubscriptionsAsync))); Guid requestId = Guid.NewGuid(); Guid correlationId = Guid.NewGuid(); var response = await _fulfillmentClient.GetSubscriptionsAsync( requestId, correlationId, cancellationToken); return(response.Select(s => MarketplaceSubscription.From(s))); } catch (Exception e) { var errorMessage = $"Cannot get subscriptions."; throw new LunaFulfillmentException(errorMessage, e); } }
/// <summary> /// Get all available plans for a subscription /// </summary> /// <param name="subscriptionId">The subscription id</param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <SubscriptionPlans> GetSubscriptionPlansAsync( Guid subscriptionId, CancellationToken cancellationToken = default) { try { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.GetSubscriptionPlansAsync), subscriptionId)); Guid requestId = Guid.NewGuid(); Guid correlationId = Guid.NewGuid(); return(await _fulfillmentClient.GetSubscriptionPlansAsync( subscriptionId, requestId, correlationId, cancellationToken)); } catch (Exception e) { var errorMessage = $"Cannot get subscription plans for subscription {subscriptionId}."; throw new LunaFulfillmentException(errorMessage, e); } }
/// <summary> /// Get subscription information for the given subscription /// </summary> /// <param name="subscriptionId">The subscription id</param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <MarketplaceSubscription> GetSubscriptionAsync( Guid subscriptionId, CancellationToken cancellationToken) { try { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.GetSubscriptionAsync), subscriptionId)); Guid requestId = Guid.NewGuid(); Guid correlationId = Guid.NewGuid(); var subscription = await _fulfillmentClient.GetSubscriptionAsync( subscriptionId, requestId, correlationId, cancellationToken); return(MarketplaceSubscription.From(subscription)); } catch (Exception e) { var errorMessage = $"Cannot get subscription {subscriptionId} from Azure Marketplace."; throw new LunaFulfillmentException(errorMessage, e); } }
/// <summary> /// Get the operation status of the given operation /// </summary> /// <param name="receivedSubscriptionId">The subscription id</param> /// <param name="operationId">The operation id</param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <FulfillmentManagerOperationResult> GetOperationResultAsync( Guid receivedSubscriptionId, Guid operationId, CancellationToken cancellationToken = default) { try { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.GetSubscriptionOperationAsync), receivedSubscriptionId)); Guid requestId = Guid.NewGuid(); Guid correlationId = Guid.NewGuid(); var operationResult = await _fulfillmentClient.GetSubscriptionOperationAsync( receivedSubscriptionId, operationId, requestId, correlationId, cancellationToken); return(FulfillmentManagerOperationResult.Success); } catch (Exception e) { var errorMessage = $"Operation {operationId} failed for subscription {receivedSubscriptionId}."; throw new LunaFulfillmentException(errorMessage, e); } }
public async Task <Subscription> ActivateSubscriptionAsync(Guid subscriptionId, string activatedBy = "system") { Subscription subscription = await _context.Subscriptions.FindAsync(subscriptionId); ValidateSubscriptionAndInputState(subscription); try { Offer offer = await FindOfferById(subscription.OfferId); if (subscription.ProvisioningStatus.Equals(ProvisioningState.NotificationPending.ToString(), StringComparison.InvariantCultureIgnoreCase) && offer.ManualActivation) { _logger.LogInformation($"ManualActivation of offer {offer.OfferName} is set to true. Will not activate the subscription."); return(await TransitToNextState(subscription, ProvisioningState.ManualActivationPending)); } Plan plan = await FindPlanById(subscription.PlanId); ActivatedSubscriptionResult activatedResult = new ActivatedSubscriptionResult { PlanId = plan.PlanName, Quantity = subscription.Quantity.ToString() }; _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.ActivateSubscriptionAsync), subscriptionId)); var result = await _fulfillmentClient.ActivateSubscriptionAsync( subscriptionId, activatedResult, Guid.NewGuid(), Guid.NewGuid(), default); _logger.LogInformation( LoggingUtils.ComposeSubscriptionActionMessage( "Activated", subscriptionId, activatedResult.PlanId, activatedResult.Quantity, activatedBy)); await _subscriptionCustomMeterUsageService.EnableSubscriptionCustomMeterUsageBySubscriptionId(subscriptionId); subscription.Status = nameof(FulfillmentState.Subscribed); subscription.ActivatedTime = DateTime.UtcNow; subscription.ActivatedBy = activatedBy; return(await TransitToNextState(subscription, ProvisioningState.Succeeded)); } catch (Exception e) { return(await HandleExceptions(subscription, e)); } }
/// <summary> /// Update a subscription /// </summary> /// <param name="subscriptionId">The subscription id</param> /// <param name="planId">The plan id</param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <FulfillmentManagerOperationResult> RequestUpdateSubscriptionAsync( Guid subscriptionId, string planId, CancellationToken cancellationToken = default) { try { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.UpdateSubscriptionAsync), subscriptionId)); Guid requestId = Guid.NewGuid(); Guid correlationId = Guid.NewGuid(); ActivatedSubscriptionResult activatedSubscription = new ActivatedSubscriptionResult { PlanId = planId }; UpdateOrDeleteSubscriptionRequestResult response = await _fulfillmentClient.UpdateSubscriptionAsync( subscriptionId, activatedSubscription, requestId, correlationId, cancellationToken); _logger.LogInformation( LoggingUtils.ComposeSubscriptionActionMessage( "Updated", subscriptionId, activatedSubscription.PlanId, activatedSubscription.Quantity)); SubscriptionOperation operation = await _fulfillmentClient.GetSubscriptionOperationAsync( subscriptionId, response.OperationId, requestId, correlationId, cancellationToken); FulfillmentManagerOperationResult returnResult = FulfillmentManagerOperationResult.Success; returnResult.Operation = operation; return(returnResult); } catch (Exception e) { var errorMessage = $"Cannot update subscription {subscriptionId}."; throw new LunaFulfillmentException(errorMessage, e); } }
public async Task <Subscription> CheckArmDeploymentStatusAsync(Guid subscriptionId) { Subscription subscription = await _context.Subscriptions.FindAsync(subscriptionId); ValidateSubscriptionAndInputState(subscription); try { Offer offer = await FindOfferById(subscription.OfferId); _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _provisioningClient.GetType().Name, nameof(_provisioningClient.GetDeploymentAsync), subscriptionId)); var result = await _provisioningClient.GetDeploymentAsync( Guid.NewGuid(), Guid.NewGuid(), offer.HostSubscription.ToString(), subscription.ResourceGroup, subscription.DeploymentName, default); if (result.Properties.ProvisioningState.Equals(nameof(ArmProvisioningState.Succeeded))) { _logger.LogInformation($"ARM deployment {subscription.DeploymentName} succeeded."); return(await TransitToNextState(subscription, ProvisioningState.WebhookPending)); } if (result.Properties.ProvisioningState.Equals(nameof(ArmProvisioningState.Failed)) || result.Properties.ProvisioningState.Equals(nameof(ArmProvisioningState.Canceled))) { throw new LunaProvisioningException( $"ARM deployment {subscription.DeploymentName} failed or canceled with ProvisioningState {result.Properties.ProvisioningState}.", ExceptionUtils.IsHttpErrorCodeRetryable(result.StatusCode), ProvisioningState.ArmTemplatePending); } _logger.LogInformation($"ARM deployment {subscription.DeploymentName} in progress. The current provisioning state is {result.Properties.ProvisioningState}."); return(await TransitToNextState(subscription, ProvisioningState.ArmTemplateRunning)); } catch (Exception e) { return(await HandleExceptions(subscription, e)); } }
public async Task <Subscription> CheckResourceGroupDeploymentStatusAsync(Guid subscriptionId) { Subscription subscription = await _context.Subscriptions.FindAsync(subscriptionId); ValidateSubscriptionAndInputState(subscription); try { Offer offer = await FindOfferById(subscription.OfferId); _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _provisioningClient.GetType().Name, nameof(_provisioningClient.ResourceGroupExistsAsync), subscriptionId)); //TODO: how to check if it is being deployed or deployment failed? bool resourceGroupExists = await _provisioningClient.ResourceGroupExistsAsync( Guid.NewGuid(), Guid.NewGuid(), offer.HostSubscription.ToString(), subscription.ResourceGroup, default); if (resourceGroupExists) { _logger.LogInformation($"Resource group deployment for {subscription.ResourceGroup} succeeded."); return(await TransitToNextState(subscription, ProvisioningState.ArmTemplatePending)); } _logger.LogInformation($"Resource group deployment for {subscription.ResourceGroup} in progress."); return(await TransitToNextState(subscription, ProvisioningState.DeployResourceGroupRunning)); } catch (Exception e) { return(await HandleExceptions(subscription, e)); } }
public async Task <Subscription> UpdateOperationCompletedAsync(Guid subscriptionId, string activatedBy = "system") { Subscription subscription = await _context.Subscriptions.FindAsync(subscriptionId); ValidateSubscriptionAndInputState(subscription); try { Offer offer = await FindOfferById(subscription.OfferId); if (subscription.ProvisioningStatus.Equals(ProvisioningState.NotificationPending.ToString(), StringComparison.InvariantCultureIgnoreCase) && offer.ManualCompleteOperation) { _logger.LogInformation($"ManualCompleteOperation of offer {offer.OfferName} is set to true. Will not complete the operation automatically."); return(await TransitToNextState(subscription, ProvisioningState.ManualCompleteOperationPending)); } // Don't need to update marketplace operation for delete data if (!subscription.ProvisioningType.Equals(nameof(ProvisioningType.DeleteData))) { Plan plan = await FindPlanById(subscription.PlanId); OperationUpdate update = new OperationUpdate { PlanId = plan.PlanName, Quantity = subscription.Quantity, Status = OperationUpdateStatusEnum.Success }; _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.UpdateSubscriptionOperationAsync), subscriptionId)); var result = await _fulfillmentClient.UpdateSubscriptionOperationAsync( subscriptionId, subscription.OperationId ?? Guid.Empty, update, Guid.NewGuid(), Guid.NewGuid(), default); } switch (subscription.ProvisioningType) { case nameof(ProvisioningType.Update): case nameof(ProvisioningType.Reinstate): subscription.Status = nameof(FulfillmentState.Subscribed); break; case nameof(ProvisioningType.DeleteData): subscription.Status = nameof(FulfillmentState.Purged); break; case nameof(ProvisioningType.Suspend): subscription.LastSuspendedTime = DateTime.UtcNow; subscription.Status = nameof(FulfillmentState.Suspended); break; case nameof(ProvisioningType.Unsubscribe): subscription.UnsubscribedTime = DateTime.UtcNow; subscription.Status = nameof(FulfillmentState.Unsubscribed); break; default: throw new ArgumentException($"Provisioning type {subscription.ProvisioningType} is not supported."); } subscription.ActivatedBy = activatedBy; return(await TransitToNextState(subscription, ProvisioningState.Succeeded)); } catch (Exception e) { return(await HandleExceptions(subscription, e)); } }
public async Task <Subscription> ExecuteWebhookAsync(Guid subscriptionId) { Subscription subscription = await _context.Subscriptions.FindAsync(subscriptionId); ValidateSubscriptionAndInputState(subscription); try { Offer offer = await FindOfferById(subscription.OfferId); Plan plan = await FindPlanById(subscription.PlanId); string urlString = await GetWebhookUrl(plan, subscription.ProvisioningType); // Only run the webhook if it is specified. if (urlString != null) { Context context = await SetContext(offer.OfferName, subscription.Owner, subscriptionId, plan.PlanName, subscription.ProvisioningType); UriBuilder webhookUri = new UriBuilder(urlString); var query = HttpUtility.ParseQueryString(webhookUri.Query); foreach (var key in query.AllKeys) { if (query[key].StartsWith("{", StringComparison.InvariantCultureIgnoreCase) && query[key].EndsWith("}", StringComparison.InvariantCultureIgnoreCase)) { string parameterName = query[key].Substring(1, query[key].Length - 2); if (context.Parameters.ContainsKey(parameterName)) { query[key] = context.Parameters[parameterName].ToString(); } else { //re-evaluate the parameter and reset the context await EvaluateParameters(offer, plan, subscription); context = await SetContext(offer.OfferName, subscription.Owner, subscriptionId, plan.PlanName, subscription.ProvisioningType); if (context.Parameters.ContainsKey(parameterName)) { query[key] = context.Parameters[parameterName].ToString(); } else { // There's a bug if this happens. throw new LunaServerException($"Webhook parameter {parameterName} doesn't exist."); } } } } webhookUri.Query = query.ToString(); _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _provisioningClient.GetType().Name, nameof(_provisioningClient.ExecuteWebhook), subscriptionId)); await _provisioningClient.ExecuteWebhook(webhookUri.Uri); _logger.LogInformation($"Running webhook {webhookUri.Uri} for subscription {subscriptionId}."); } return(await TransitToNextState(subscription, ProvisioningState.NotificationPending)); } catch (Exception e) { return(await HandleExceptions(subscription, e)); } }
public async Task <Subscription> DeployArmTemplateAsync(Guid subscriptionId) { Subscription subscription = await _context.Subscriptions.FindAsync(subscriptionId); ValidateSubscriptionAndInputState(subscription); try { if (subscription.ResourceGroup == null) { // If resource group is not created, transit to ProvisioningPending state to create resource group return(await TransitToNextState(subscription, ProvisioningState.ProvisioningPending)); } Offer offer = await FindOfferById(subscription.OfferId); Plan plan = await FindPlanById(subscription.PlanId); string deploymentName = $"{plan.PlanName}{offer.OfferName}{new Random().Next(0, 9999).ToString("D4")}"; // deployment name cannot exceed 64 characters, otherwise returns 400 string isvSubscriptionId = offer.HostSubscription.ToString(); string templatePath = await GetTemplatePath(plan, subscription.ProvisioningType); if (templatePath == null) { // If template is not specified, do nothing and transit to WebhookPending state return(await TransitToNextState(subscription, ProvisioningState.WebhookPending)); } // Reevaluate parameters if not subscribe. If it is subscribe, the parameters are evaluated when creating resource group if (!subscription.ProvisioningType.Equals(nameof(ProvisioningType.Subscribe))) { await EvaluateParameters(offer, plan, subscription); } templatePath = await _storageUtility.GetFileReferenceWithSasKeyAsync(templatePath); JObject parameters = new JObject(); using (WebClient client = new WebClient()) { string content = client.DownloadString(templatePath); Context context = await SetContext(offer.OfferName, subscription.Owner, subscriptionId, plan.PlanName, subscription.ProvisioningType); var paramList = ARMTemplateHelper.GetArmTemplateParameters(content); foreach (var param in paramList) { JProperty value = new JProperty("value", context.Parameters[param.Key]); parameters.Add(param.Key, new JObject(value)); } } _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _provisioningClient.GetType().Name, nameof(_provisioningClient.PutDeploymentAsync), subscriptionId)); var result = await _provisioningClient.PutDeploymentAsync( Guid.NewGuid(), Guid.NewGuid(), isvSubscriptionId, subscription.ResourceGroup, deploymentName, parameters : parameters, templatePath : templatePath); subscription.DeploymentName = result.Name; _logger.LogInformation($"Running ARM deployment {deploymentName} for subscription {isvSubscriptionId} in resource group {subscription.ResourceGroup}."); return(await TransitToNextState(subscription, ProvisioningState.ArmTemplateRunning)); } catch (Exception e) { return(await HandleExceptions(subscription, e)); } }
public async Task <Subscription> CreateResourceGroupAsync(Guid subscriptionId) { Subscription subscription = await _context.Subscriptions.FindAsync(subscriptionId); ValidateSubscriptionAndInputState(subscription); try { Offer offer = await FindOfferById(subscription.OfferId); Plan plan = await FindPlanById(subscription.PlanId); string isvSubscriptionId = offer.HostSubscription.ToString(); var parameters = await EvaluateParameters(offer, plan, subscription); subscription.EntryPointUrl = parameters.ContainsKey("entryPointUrl") ? parameters["entryPointUrl"].ToString() : ""; if (plan.SubscribeArmTemplateId == null) { // Don't need to deploy anything, transit to WebhookPending state return(await TransitToNextState(subscription, ProvisioningState.WebhookPending)); } string azureLocation = null; string resourceGroupName = $"{offer.OfferName}-{subscription.SubscriptionId}"; if (parameters.ContainsKey("resourceGroupLocation")) { azureLocation = parameters["resourceGroupLocation"].ToString(); } else { throw new LunaBadRequestUserException("The ResourceGroupLocation parameter is not specified.", UserErrorCode.ParameterNotProvided); } _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _provisioningClient.GetType().Name, nameof(_provisioningClient.ResourceGroupExistsAsync), subscriptionId)); bool resourceGroupExists = await _provisioningClient.ResourceGroupExistsAsync( Guid.NewGuid(), Guid.NewGuid(), isvSubscriptionId, resourceGroupName, default); if (!resourceGroupExists) { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _provisioningClient.GetType().Name, nameof(_provisioningClient.CreateOrUpdateResourceGroupAsync), subscriptionId)); var result = await _provisioningClient.CreateOrUpdateResourceGroupAsync( Guid.NewGuid(), Guid.NewGuid(), isvSubscriptionId, resourceGroupName, azureLocation, default ); _logger.LogInformation($"Deploying resource group {resourceGroupName} in location {azureLocation}."); } else { throw new LunaConflictUserException($"Resource group with name {resourceGroupName} already exist."); } subscription.ResourceGroup = resourceGroupName; return(await TransitToNextState(subscription, ProvisioningState.DeployResourceGroupRunning)); } catch (Exception e) { return(await HandleExceptions(subscription, e)); } }
/// <summary> /// Activate a subscription /// </summary> /// <param name="subscriptionId">The subscription id</param> /// <param name="planId">The plan id</param> /// <param name="quantity">The quantity</param> /// <param name="cancellationToken"></param> /// <returns></returns> public async Task <MarketplaceSubscription> ActivateSubscriptionAsync( Guid subscriptionId, string planId, int?quantity, CancellationToken cancellationToken = default) { Guid requestId = Guid.NewGuid(); Guid correlationId = Guid.NewGuid(); var subscriptionToBeActivated = new ActivatedSubscriptionResult { PlanId = planId }; if (quantity.HasValue) { subscriptionToBeActivated.Quantity = quantity.Value.ToString(); } try { _logger.LogInformation( LoggingUtils.ComposeHttpClientLogMessage( _fulfillmentClient.GetType().Name, nameof(_fulfillmentClient.ActivateSubscriptionAsync), subscriptionId)); var result = await _fulfillmentClient.ActivateSubscriptionAsync( subscriptionId, subscriptionToBeActivated, requestId, correlationId, cancellationToken); _logger.LogInformation( LoggingUtils.ComposeSubscriptionActionMessage( "Activated", subscriptionId, planId, quantity.ToString())); var returnValue = new MarketplaceSubscription { PlanId = planId, State = StatusEnum.Subscribed, SubscriptionId = subscriptionId }; if (quantity.HasValue) { returnValue.Quantity = quantity.Value; } return(returnValue); } catch (Exception e) { var errorMessage = LoggingUtils.ComposeSubscriptionActionMessage( "Failed to activate", subscriptionId, planId, quantity.ToString()); throw new LunaFulfillmentException(errorMessage, e); } }