Schedule Migration Integration

This commit is contained in:
Harsha Bacharaju 2022-11-10 17:46:15 -08:00
Родитель 14d92553f6
Коммит 6f3efe6895
7 изменённых файлов: 1142 добавлений и 40 удалений

Просмотреть файл

@ -32,7 +32,10 @@ internal class CustomerProvider : ICustomerProvider
var authenticationResult = await this.tokenProvider.GetTokenAsync();
Console.WriteLine("Token generated...");
var httpClient = new HttpClient();
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
@ -52,7 +55,7 @@ internal class CustomerProvider : ICustomerProvider
route = $"{Routes.GetCustomers}&seekOperation=next";
}
request.RequestUri = new Uri(route);
request.RequestUri = new Uri(route, UriKind.Relative);
var response = await httpClient.SendAsync(request).ConfigureAwait(false);
if (response.StatusCode == HttpStatusCode.BadRequest)

Просмотреть файл

@ -0,0 +1,36 @@
// -----------------------------------------------------------------------
// <copyright file="INewCommerceMigrationScheduleProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
namespace NCEBulkMigrationTool
{
internal interface INewCommerceMigrationScheduleProvider
{
/// <summary>
/// Exports legacy commerce subscriptions with their migration eligibility to an output CSV file so that
/// they can be scheduled as needed.
/// </summary>
/// <returns>Bool indicating success/ failure.</returns>
Task<bool> ValidateAndGetSubscriptionsToScheduleMigrationAsync();
/// <summary>
/// Uploads New Commerce Migration Schedules based on CSV files in the input folders and writes the schedule migration data to a new CSV file.
/// </summary>
/// <returns>Bool indicating success/ failure.</returns>
Task<bool> UploadNewCommerceMigrationSchedulesAsync();
/// <summary>
/// Exports all the New Commerce Migration Schedules for the given input list of customers.
/// </summary>
/// <returns>Bool indicating success/ failure.</returns>
Task<bool> ExportNewCommerceMigrationSchedulesAsync();
/// <summary>
/// Cancels the New Commerce Migration Schedules based on the CSV input files and writes the output with the updated Schedule Migration Status.
/// </summary>
/// <returns>Bool indicating success/ failure.</returns>
Task<bool> CancelNewCommerceMigrationSchedulesAsync();
}
}

Просмотреть файл

@ -85,6 +85,8 @@ internal record MigrationRequest
public int SeatCount { get; set; }
public DateTime? CustomTermEndDate { get; set; }
public bool AddOn { get; init; }
public string BaseSubscriptionId { get; init; } = string.Empty;
@ -158,6 +160,8 @@ public record NewCommerceMigration
public bool PurchaseFullTerm { get; set; }
public DateTime? CustomTermEndDate { get; set; }
public string ExternalReferenceId { get; set; } = string.Empty;
public IEnumerable<NewCommerceMigration> AddOnMigrations { get; set; } = Enumerable.Empty<NewCommerceMigration>();
@ -200,6 +204,8 @@ public record MigrationResult
public int NCESeatCount { get; set; }
public DateTime? CustomTermEndDate { get; set; }
public int? ErrorCode { get; set; } = null;
public string ErrorReason { get; init; } = string.Empty;
@ -223,6 +229,8 @@ public record NewCommerceEligibility
public string CatalogItemId { get; set; } = string.Empty;
public DateTime? CustomTermEndDate { get; set; }
public IEnumerable<NewCommerceEligibilityError> Errors { get; set; } = Enumerable.Empty<NewCommerceEligibilityError>();
public IEnumerable<NewCommerceEligibility> AddOnMigrations { get; set; } = Enumerable.Empty<NewCommerceEligibility>();
@ -242,6 +250,145 @@ public record CustomerErrorResponse
public string Message { get; set; } = string.Empty;
}
public record NewCommerceMigrationSchedule
{
public string Id { get; set; } = string.Empty;
public string CurrentSubscriptionId { get; set; } = string.Empty;
public string Status { get; set; } = string.Empty;
public string CustomerTenantId { get; set; } = string.Empty;
public string CatalogItemId { get; set; } = string.Empty;
public DateTime? SubscriptionEndDate { get; set; }
public int Quantity { get; set; }
public string TermDuration { get; set; } = string.Empty;
public string BillingCycle { get; set; } = string.Empty;
public bool PurchaseFullTerm { get; set; }
public DateTime? CustomTermEndDate { get; set; }
public DateTime? TargetDate { get; set; }
public bool? MigrateOnRenewal { get; set; }
public DateTimeOffset CreatedTime { get; set; }
public DateTimeOffset LastModifiedTime { get; set; }
public string ExternalReferenceId { get; set; } = string.Empty;
public string NewCommerceMigrationId { get; set; } = string.Empty;
public string ErrorDescription { get; set; } = string.Empty;
public int? ErrorCode { get; set; }
public IEnumerable<NewCommerceMigrationSchedule> AddOnMigrationSchedules { get; set; } = Enumerable.Empty<NewCommerceMigrationSchedule>();
}
internal record ScheduleMigrationRequest
{
public string PartnerTenantId { get; init; } = string.Empty;
public string IndirectResellerMpnId { get; init; } = string.Empty;
public string CustomerName { get; init; } = string.Empty;
public Guid CustomerTenantId { get; init; }
public string LegacySubscriptionId { get; init; } = string.Empty;
public string LegacySubscriptionName { get; init; } = string.Empty;
public string LegacyProductName { get; init; } = string.Empty;
public DateTime ExpirationDate { get; init; }
public bool AutoRenewEnabled { get; init; }
public bool AddOn { get; init; }
public string BaseSubscriptionId { get; init; } = string.Empty;
public bool MigrationEligible { get; set; }
public string NcePsa { get; set; } = string.Empty;
public string CurrentTerm { get; init; } = string.Empty;
public string CurrentBillingPlan { get; init; } = string.Empty;
public int CurrentSeatCount { get; set; }
public bool StartNewTermInNce { get; init; }
public string Term { get; init; } = string.Empty;
public string BillingPlan { get; init; } = string.Empty;
public int SeatCount { get; set; }
public DateTime? CustomTermEndDate { get; set; }
public DateTime? TargetDate { get; set; }
public bool? MigrateOnRenewal { get; set; }
public string MigrationIneligibilityReason { get; set; } = string.Empty;
}
public record ScheduleMigrationResult
{
public string PartnerTenantId { get; init; } = string.Empty;
public string IndirectResellerMpnId { get; init; } = string.Empty;
public string CustomerName { get; init; } = string.Empty;
public Guid CustomerTenantId { get; init; }
public string LegacySubscriptionId { get; init; } = string.Empty;
public string LegacySubscriptionName { get; init; } = string.Empty;
public string LegacyProductName { get; init; } = string.Empty;
public DateTime ExpirationDate { get; init; }
public bool AddOn { get; init; }
public bool StartedNewTermInNce { get; init; }
public string NCETermDuration { get; init; } = string.Empty;
public string NCEBillingPlan { get; init; } = string.Empty;
public int NCESeatCount { get; set; }
public DateTime? CustomTermEndDate { get; set; }
public DateTime? TargetDate { get; set; }
public bool? MigrateOnRenewal { get; set; }
public string MigrationScheduleStatus { get; init; } = string.Empty;
public string MigrationScheduleId { get; init; } = string.Empty;
public string BatchId { get; init; } = string.Empty;
public int? ErrorCode { get; set; } = null;
public string ErrorReason { get; init; } = string.Empty;
}
public enum NewCommerceEligibilityErrorCode
{
SubscriptionStatusNotActive = 0,
@ -261,6 +408,14 @@ public enum NewCommerceEligibilityErrorCode
SubscriptionActiveForLessThanOneMonth = 7,
TradeStatusNotAllowed = 8,
InvalidPartnerIdOnRecord = 9,
MigrationsWithinLast24hOfTermOnlyAllowedForFullTerm = 10,
InvalidCustomTermEndDate = 11,
CustomTermEndDateMustAlignWithExistingSubscriptionOrCalendarMonth = 12,
}
[JsonConverter(typeof(JsonStringEnumConverter))]
@ -277,6 +432,8 @@ internal enum SubscriptionStatus
Expired = 4,
Pending = 5,
Disabled = 6,
}
[JsonConverter(typeof(JsonStringEnumConverter))]
@ -310,28 +467,58 @@ internal record Constants
internal record Routes
{
/// <summary>
/// Base url for the partner center Apis.
/// </summary>
public const string BaseUrl = "https://api.partnercenter.microsoft.com";
/// <summary>
/// Get customers route.
/// </summary>
public const string GetCustomers = "https://api.partnercenter.microsoft.com/v1/customers?size=500";
public const string GetCustomers = "/v1/customers?size=500";
/// <summary>
/// Get subscriptions route, 0 represents customerId.
/// </summary>
public const string GetSubscriptions = "https://api.partnercenter.microsoft.com/v1/customers/{0}/subscriptions";
public const string GetSubscriptions = "/v1/customers/{0}/subscriptions";
/// <summary>
/// Get new commerce migrations route, 0 represents customerId, 1 represents newCommerceMigrationId.
/// </summary>
public const string GetNewCommerceMigration = "https://api.partnercenter.microsoft.com/v1/customers/{0}/migrations/newcommerce/{1}";
public const string GetNewCommerceMigration = "/v1/customers/{0}/migrations/newcommerce/{1}";
/// <summary>
/// Validate migration eligibility route, 0 represents customerId.
/// </summary>
public const string ValidateMigrationEligibility = "https://api.partnercenter.microsoft.com/v1/customers/{0}/migrations/newcommerce/validate";
public const string ValidateMigrationEligibility = "/v1/customers/{0}/migrations/newcommerce/validate";
/// <summary>
/// Post new commerce migration, 0 represents customerId.
/// </summary>
public const string PostNewCommerceMigration = "https://api.partnercenter.microsoft.com/v1/customers/{0}/migrations/newcommerce";
public static string PostNewCommerceMigration = "/v1/customers/{0}/migrations/newcommerce";
/// <summary>
/// Get new commerce migrations route, 0 represents customerId, 1 represents newCommerceMigrationScheduleId.
/// </summary>
public const string GetNewCommerceMigrationSchedule = "/v1/customers/{0}/migrations/newcommerce/schedules/{1}";
/// <summary>
/// Post new commerce migration schedule, 0 represents customerId.
/// </summary>
public const string PostNewCommerceMigrationSchedule = "/v1/customers/{0}/migrations/newcommerce/schedules";
/// <summary>
/// Update new commerce migration schedule, 0 represents customerId, 1 represents newCommerceMigrationScheduleId.
/// </summary>
public const string UpdateNewCommerceMigrationSchedule = "/v1/customers/{0}/migrations/newcommerce/schedules/{1}";
/// <summary>
/// Get new commerce migration schedules route.
/// </summary>
public const string GetNewCommerceMigrationSchedules = "/v1/migrations/newcommerce/schedules";
/// <summary>
/// Cancel new commerce migration schedule, 0 represents customerId, 1 represents newCommerceMigrationScheduleId.
/// </summary>
public const string CancelNewCommerceMigrationSchedule = "/v1/customers/{0}/migrations/newcommerce/schedules/{1}/cancel";
}

Просмотреть файл

@ -46,7 +46,10 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
var migrations = new ConcurrentBag<IEnumerable<MigrationResult>>();
var httpClient = new HttpClient();
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
@ -87,10 +90,10 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
Console.WriteLine("Exporting migrations");
await csvProvider.ExportCsv(migrations.SelectMany(m => m), $"{Constants.OutputFolderPath}/migrations/{processedFileName}_{batchId}.csv");
File.Move(fileName, $"{Constants.InputFolderPath}/subscriptions/processed/{processedFileName}", true);
await Task.Delay(1000 * 60);
await Task.Delay(1000);
Console.WriteLine($"Exported migrations at {Environment.CurrentDirectory}/{Constants.OutputFolderPath}/migrations/{processedFileName}_{batchId}.csv");
}
@ -115,7 +118,10 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
var migrations = new ConcurrentBag<MigrationResult>();
var httpClient = new HttpClient();
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
var options = new ParallelOptions()
@ -173,7 +179,7 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
File.Move(fileName, $"{Constants.InputFolderPath}/migrations/processed/{processedFileName}", true);
await Task.Delay(1000 * 60);
await Task.Delay(1000);
Console.WriteLine($"Exported migration status at {Environment.CurrentDirectory}/{Constants.OutputFolderPath}/migrationstatus/{processedFileName}.csv");
}
@ -226,7 +232,7 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
}
var result = this.PrepareMigrationResult(migrationResult, migrationResult.BatchId, migration, migrationError);
return (result!, migration?.AddOnMigrations ?? Enumerable.Empty<NewCommerceMigration>());
return (result, migration?.AddOnMigrations);
}
/// <summary>
@ -251,6 +257,7 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
BillingCycle = migrationRequest.BillingPlan,
TermDuration = migrationRequest.Term,
ExternalReferenceId = batchId,
CustomTermEndDate = migrationRequest.CustomTermEndDate,
};
// If they want to start a new term, then we should take the input from the file.
@ -317,6 +324,7 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
BillingCycle = request.BillingPlan,
TermDuration = request.Term,
PurchaseFullTerm = request.StartNewTermInNce,
CustomTermEndDate = request.CustomTermEndDate,
});
allAddOnMigrations.AddRange(addOnNewCommerceMigrations);
@ -363,18 +371,12 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
/// <returns>The add on migration results.</returns>
private List<MigrationResult> PrepareAddOnMigrationResult(IEnumerable<MigrationRequest> addOnMigrationRequests, string batchId, NewCommerceMigration? newCommerceMigration, NewCommerceMigrationError? newCommerceMigrationError, List<MigrationResult> migrationResults)
{
if(newCommerceMigration != null)
foreach (var addOnMigrationResponse in newCommerceMigration.AddOnMigrations)
{
foreach (var addOnMigrationResponse in newCommerceMigration.AddOnMigrations)
{
var addOnMigrationRequest = addOnMigrationRequests.SingleOrDefault(n => n.LegacySubscriptionId.Equals(addOnMigrationResponse.CurrentSubscriptionId, StringComparison.OrdinalIgnoreCase));
if(addOnMigrationRequest != null)
{
addOnMigrationResponse.Status = newCommerceMigration.Status;
addOnMigrationResponse.Id = newCommerceMigration.Id;
PrepareMigrationResult(addOnMigrationRequest, batchId, addOnMigrationResponse, newCommerceMigrationError, migrationResults);
}
}
var addOnMigrationRequest = addOnMigrationRequests.SingleOrDefault(n => n.LegacySubscriptionId.Equals(addOnMigrationResponse.CurrentSubscriptionId, StringComparison.OrdinalIgnoreCase));
addOnMigrationResponse.Status = newCommerceMigration.Status;
addOnMigrationResponse.Id = newCommerceMigration.Id;
PrepareMigrationResult(addOnMigrationRequest, batchId, addOnMigrationResponse, newCommerceMigrationError, migrationResults);
}
return migrationResults;
@ -407,6 +409,7 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
NCETermDuration = migrationRequest.Term,
NCEBillingPlan = migrationRequest.BillingPlan,
NCESeatCount = migrationRequest.SeatCount,
CustomTermEndDate = migrationRequest.CustomTermEndDate,
ErrorCode = newCommerceMigrationError.Code,
ErrorReason = newCommerceMigrationError.Description,
};
@ -432,6 +435,7 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
NCETermDuration = newCommerceMigration.TermDuration,
NCEBillingPlan = newCommerceMigration.BillingCycle,
NCESeatCount = newCommerceMigration.Quantity,
CustomTermEndDate = newCommerceMigration.CustomTermEndDate,
NCESubscriptionId = newCommerceMigration.NewCommerceSubscriptionId,
BatchId = batchId,
MigrationId = newCommerceMigration.Id,
@ -469,6 +473,7 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
NCETermDuration = migrationResult.NCETermDuration,
NCEBillingPlan = migrationResult.NCEBillingPlan,
NCESeatCount = migrationResult.NCESeatCount,
CustomTermEndDate = migrationResult.CustomTermEndDate,
ErrorCode = newCommerceMigrationError.Code,
ErrorReason = newCommerceMigrationError.Description,
};
@ -491,6 +496,7 @@ internal class NewCommerceMigrationProvider : INewCommerceMigrationProvider
NCETermDuration = newCommerceMigration.TermDuration,
NCEBillingPlan = newCommerceMigration.BillingCycle,
NCESeatCount = newCommerceMigration.Quantity,
CustomTermEndDate = newCommerceMigration.CustomTermEndDate,
NCESubscriptionId = newCommerceMigration.NewCommerceSubscriptionId,
BatchId = batchId,
MigrationId = newCommerceMigration.Id,

Просмотреть файл

@ -0,0 +1,849 @@
// -----------------------------------------------------------------------
// <copyright file="NewCommerceMigrationScheduleProvider.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
// -----------------------------------------------------------------------
namespace NCEBulkMigrationTool
{
internal class NewCommerceMigrationScheduleProvider : INewCommerceMigrationScheduleProvider
{
private static string partnerTenantId = string.Empty;
private readonly ITokenProvider tokenProvider;
private long subscriptionsCntr = 0;
/// <summary>
/// The NewCommerceMigrationScheduleProvider constructor.
/// </summary>
/// <param name="tokenProvider">The token provider.</param>
public NewCommerceMigrationScheduleProvider(ITokenProvider tokenProvider)
{
this.tokenProvider = tokenProvider;
}
/// <inheritdoc/>
public async Task<bool> ValidateAndGetSubscriptionsToScheduleMigrationAsync()
{
subscriptionsCntr = 0;
var csvProvider = new CsvProvider();
using TextReader fileReader = File.OpenText($"{Constants.InputFolderPath}/customers.csv");
using var csvReader = new CsvReader(fileReader, CultureInfo.InvariantCulture, leaveOpen: true);
var inputCustomers = csvReader.GetRecordsAsync<CompanyProfile>();
ConcurrentBag<IEnumerable<ScheduleMigrationRequest>> allMigrationRequests = new ConcurrentBag<IEnumerable<ScheduleMigrationRequest>>();
var failedCustomersBag = new ConcurrentBag<CompanyProfile>();
var authenticationResult = await this.tokenProvider.GetTokenAsync();
partnerTenantId = authenticationResult.TenantId;
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = 5
};
await Parallel.ForEachAsync(inputCustomers, options, async (customer, cancellationToken) =>
{
try
{
var subscriptions = await GetLegacySubscriptionsAsync(httpClient, customer, cancellationToken);
Console.WriteLine($"Validating subscriptions eligibility for customer {customer.CompanyName}");
var migrationEligibilities = await ValidateMigrationEligibility(customer, httpClient, options, subscriptions);
var migrationScheduleDetails = await GetMigrationScheduleAsync(customer, httpClient, options, migrationEligibilities);
allMigrationRequests.Add(migrationScheduleDetails.Select(a => a));
}
catch (Exception ex)
{
Console.WriteLine($"Failed to verify migration eligibility for customer {customer.CompanyName} {ex}");
failedCustomersBag.Add(customer);
}
});
csvReader.Dispose();
fileReader.Close();
Console.WriteLine("Exporting subscriptions");
await csvProvider.ExportCsv(allMigrationRequests.SelectMany(m => m), $"{Constants.OutputFolderPath}/subscriptionsforschedule.csv");
Console.WriteLine($"Exported subscriptions at {Environment.CurrentDirectory}/{Constants.OutputFolderPath}/subscriptionsforschedule.csv");
if (failedCustomersBag.Count > 0)
{
Console.WriteLine("Exporting failed customers");
await csvProvider.ExportCsv(failedCustomersBag, $"{Constants.OutputFolderPath}/failedCustomers_schedulemigration.csv");
Console.WriteLine($"Exported failed customers at {Environment.CurrentDirectory}/{Constants.OutputFolderPath}/failedCustomers_schedulemigration.csv");
}
return true;
}
/// <inheritdoc/>
public async Task<bool> UploadNewCommerceMigrationSchedulesAsync()
{
var csvProvider = new CsvProvider();
var inputFileNames = Directory.EnumerateFiles($"{Constants.InputFolderPath}/subscriptionsforschedule");
var authenticationResult = await this.tokenProvider.GetTokenAsync();
foreach (var fileName in inputFileNames)
{
Console.WriteLine($"Processing file {fileName}");
using TextReader fileReader = File.OpenText(fileName);
using var csvReader = new CsvReader(fileReader, CultureInfo.InvariantCulture, leaveOpen: true);
var inputMigrationRequests = csvReader.GetRecords<ScheduleMigrationRequest>().ToList();
if (inputMigrationRequests.Count > 200)
{
Console.WriteLine($"There are too many migration requests in the file: {fileName}. The maximum limit for migration uploads per file is 200. Please fix the input file to continue...");
continue;
}
var migrations = new ConcurrentBag<IEnumerable<ScheduleMigrationResult>>();
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = 5
};
long subscriptionsCntr = 0; //The counter to track
var batchId = Guid.NewGuid().ToString();
var inputBaseMigrationRequests = inputMigrationRequests.Where(m => !m.AddOn && m.MigrationEligible);
var inputAddOnMigrationRequests = inputMigrationRequests.Where(m => m.AddOn && m.MigrationEligible);
await Parallel.ForEachAsync(inputBaseMigrationRequests, options, async (migrationRequest, cancellationToken) =>
{
try
{
List<ScheduleMigrationResult> migrationResult;
migrationResult = await this.PostNewCommerceMigrationScheduleAsync(httpClient, migrationRequest, inputAddOnMigrationRequests, batchId, cancellationToken);
migrations.Add(migrationResult);
}
catch (Exception)
{
Console.WriteLine($"Migration for subscription: {migrationRequest.LegacySubscriptionId} failed.");
}
finally
{
Interlocked.Increment(ref subscriptionsCntr);
Console.WriteLine($"Processed {subscriptionsCntr} subscription migration requests.", subscriptionsCntr);
}
});
csvReader.Dispose();
fileReader.Close();
var index = fileName.LastIndexOf('\\');
var processedFileName = fileName[++index..];
Console.WriteLine("Exporting migrations");
await csvProvider.ExportCsv(migrations.SelectMany(m => m), $"{Constants.OutputFolderPath}/schedulemigrations/{processedFileName}_{batchId}.csv");
File.Move(fileName, $"{Constants.InputFolderPath}/subscriptionsforschedule/processed/{processedFileName}", true);
await Task.Delay(1000);
Console.WriteLine($"Exported migrations at {Environment.CurrentDirectory}/{Constants.OutputFolderPath}/schedulemigrations/{processedFileName}_{batchId}.csv");
}
return true;
}
/// <inheritdoc/>
public async Task<bool> ExportNewCommerceMigrationSchedulesAsync()
{
var csvProvider = new CsvProvider();
using TextReader fileReader = File.OpenText($"{Constants.InputFolderPath}/customers.csv");
using var csvReader = new CsvReader(fileReader, CultureInfo.InvariantCulture, leaveOpen: true);
var inputCustomers = csvReader.GetRecordsAsync<CompanyProfile>();
ConcurrentBag<IEnumerable<NewCommerceMigrationSchedule>> allMigrationSchedules = new();
var failedCustomersBag = new ConcurrentBag<CompanyProfile>();
var authenticationResult = await this.tokenProvider.GetTokenAsync();
partnerTenantId = authenticationResult.TenantId;
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = 5
};
await Parallel.ForEachAsync(inputCustomers, options, async (customer, cancellationToken) =>
{
try
{
var getScheduleMigrationsRequest = new HttpRequestMessage(HttpMethod.Get, $"{Routes.GetNewCommerceMigrationSchedules}/?CustomerTenantId={customer.TenantId}");
getScheduleMigrationsRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
var scheduleMigrationsResponse = await httpClient.SendAsync(getScheduleMigrationsRequest, cancellationToken).ConfigureAwait(false);
if (scheduleMigrationsResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authenticationResult = await this.tokenProvider.GetTokenAsync();
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
getScheduleMigrationsRequest = new HttpRequestMessage(HttpMethod.Get, $"{Routes.GetNewCommerceMigrationSchedules}/?CustomerTenantId={customer.TenantId}");
getScheduleMigrationsRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
scheduleMigrationsResponse = await httpClient.SendAsync(getScheduleMigrationsRequest).ConfigureAwait(false);
}
scheduleMigrationsResponse.EnsureSuccessStatusCode();
var newCommerceMigrationSchedules = await scheduleMigrationsResponse.Content.ReadFromJsonAsync<IEnumerable<NewCommerceMigrationSchedule>>().ConfigureAwait(false);
allMigrationSchedules.Add(newCommerceMigrationSchedules);
}
catch (Exception ex)
{
Console.WriteLine($"Failed to fetch migration schedules for customer {customer.CompanyName} {ex}");
failedCustomersBag.Add(customer);
}
});
csvReader.Dispose();
fileReader.Close();
Console.WriteLine("Exporting migration schedules");
await csvProvider.ExportCsv(allMigrationSchedules.SelectMany(m => m), $"{Constants.OutputFolderPath}/schedulemigrations.csv");
Console.WriteLine($"Exported schedule migrations at {Environment.CurrentDirectory}/{Constants.OutputFolderPath}/schedulemigrations.csv");
if (failedCustomersBag.Count > 0)
{
Console.WriteLine("Exporting failed customers");
await csvProvider.ExportCsv(failedCustomersBag, "failedCustomers.csv");
Console.WriteLine($"Exported failed customers at {Environment.CurrentDirectory}/failedCustomers.csv");
}
return true;
}
/// <inheritdoc/>
public async Task<bool> CancelNewCommerceMigrationSchedulesAsync()
{
var csvProvider = new CsvProvider();
var inputFileNames = Directory.EnumerateFiles($"{Constants.InputFolderPath}/cancelschedulemigrations");
var authenticationResult = await this.tokenProvider.GetTokenAsync();
foreach (var fileName in inputFileNames)
{
Console.WriteLine($"Processing file {fileName}");
using TextReader fileReader = File.OpenText(fileName);
using var csvReader = new CsvReader(fileReader, CultureInfo.InvariantCulture, leaveOpen: true);
var inputMigrationSchedules = csvReader.GetRecords<NewCommerceMigrationSchedule>().ToList();
if (inputMigrationSchedules.Count > 200)
{
Console.WriteLine($"There are too many migration schedule requests in the file: {fileName}. The maximum limit for migration uploads per file is 200. Please fix the input file to continue...");
continue;
}
var migrationSchedules = new ConcurrentBag<NewCommerceMigrationSchedule>();
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
var options = new ParallelOptions()
{
MaxDegreeOfParallelism = 5
};
long scheduleMigrationsCounter = 0;
var batchId = Guid.NewGuid().ToString();
var scheduledMigrationsToCancel = inputMigrationSchedules.Where(s => string.Equals(s.Status, "Cancel", StringComparison.OrdinalIgnoreCase));
await Parallel.ForEachAsync(scheduledMigrationsToCancel, options, async (scheduleMigration, cancellationToken) =>
{
try
{
NewCommerceMigrationSchedule migrationScheduleResult;
migrationScheduleResult = await this.CancelNewCommerceMigrationScheduleAsync(httpClient, scheduleMigration, cancellationToken);
migrationSchedules.Add(migrationScheduleResult);
}
catch (Exception)
{
Console.WriteLine($"Cancel Migration Schedule for subscription: {scheduleMigration.CurrentSubscriptionId} failed.");
}
finally
{
Interlocked.Increment(ref scheduleMigrationsCounter);
Console.WriteLine($"Processed {scheduleMigrationsCounter} cancel schedule migration requests.");
}
});
csvReader.Dispose();
fileReader.Close();
var index = fileName.LastIndexOf('\\');
var processedFileName = fileName[++index..];
Console.WriteLine("Exporting cancelled scheduled migrations");
await csvProvider.ExportCsv(migrationSchedules.Select(s => s), $"{Constants.OutputFolderPath}/cancelschedulemigrations/{processedFileName}_{batchId}.csv");
File.Move(fileName, $"{Constants.InputFolderPath}/cancelschedulemigrations/processed/{processedFileName}", true);
await Task.Delay(1000);
Console.WriteLine($"Exported cancelled schedule migrations at {Environment.CurrentDirectory}/{Constants.OutputFolderPath}/cancelschedulemigrations/{processedFileName}_{batchId}.csv");
}
return true;
}
private async Task<IEnumerable<Subscription>> GetLegacySubscriptionsAsync(HttpClient httpClient, CompanyProfile customer, CancellationToken cancellationToken)
{
var allSubscriptions = await this.GetSubscriptionsAsync(httpClient, customer, cancellationToken).ConfigureAwait(false);
var subscriptions = allSubscriptions.Where(s => Guid.TryParse(s.OfferId, out _));
return subscriptions;
}
private async Task<IEnumerable<Subscription>> GetSubscriptionsAsync(HttpClient httpClient, CompanyProfile customer, CancellationToken cancellationToken)
{
var subscriptionRequest = new HttpRequestMessage(HttpMethod.Get, string.Format(Routes.GetSubscriptions, customer.TenantId));
subscriptionRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
var subscriptionResponse = await httpClient.SendAsync(subscriptionRequest, cancellationToken).ConfigureAwait(false);
if (subscriptionResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authenticationResult = await this.tokenProvider.GetTokenAsync();
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
subscriptionRequest = new HttpRequestMessage(HttpMethod.Get, string.Format(Routes.GetSubscriptions, customer.TenantId));
subscriptionRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
subscriptionResponse = await httpClient.SendAsync(subscriptionRequest).ConfigureAwait(false);
}
subscriptionResponse.EnsureSuccessStatusCode();
var subscriptionCollection = await subscriptionResponse.Content.ReadFromJsonAsync<ResourceCollection<Subscription>>().ConfigureAwait(false);
return subscriptionCollection!.Items;
}
private async Task<ConcurrentBag<ScheduleMigrationRequest>> ValidateMigrationEligibility(CompanyProfile customer, HttpClient httpClient, ParallelOptions options, IEnumerable<Subscription> subscriptions)
{
var baseSubscriptions = subscriptions.Where(s => string.IsNullOrWhiteSpace(s.ParentSubscriptionId));
var addOns = subscriptions.Where(s => !string.IsNullOrWhiteSpace(s.ParentSubscriptionId));
var migrationRequests = new ConcurrentBag<ScheduleMigrationRequest>();
var addOnEligibilityList = new ConcurrentBag<IEnumerable<NewCommerceEligibility>>();
await Parallel.ForEachAsync(baseSubscriptions, options, async (subscription, cancellationToken) =>
{
var payload = new NewCommerceMigration
{
CurrentSubscriptionId = subscription.Id,
};
var migrationRequest = new HttpRequestMessage(HttpMethod.Post, string.Format(Routes.ValidateMigrationEligibility, customer.TenantId))
{
Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
};
migrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
var migrationResponse = await httpClient.SendAsync(migrationRequest, cancellationToken).ConfigureAwait(false);
if (migrationResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authenticationResult = await this.tokenProvider.GetTokenAsync();
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
migrationRequest = new HttpRequestMessage(HttpMethod.Post, string.Format(Routes.ValidateMigrationEligibility, customer.TenantId))
{
Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
};
migrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
migrationResponse = await httpClient.SendAsync(migrationRequest).ConfigureAwait(false);
}
migrationResponse.EnsureSuccessStatusCode();
var newCommerceEligibility = await migrationResponse.Content.ReadFromJsonAsync<NewCommerceEligibility>().ConfigureAwait(false);
if (newCommerceEligibility.AddOnMigrations.Any())
{
addOnEligibilityList.Add(newCommerceEligibility.AddOnMigrations);
}
migrationRequests.Add(PrepareMigrationRequest(customer, subscription, newCommerceEligibility!));
Interlocked.Increment(ref subscriptionsCntr);
Console.WriteLine($"Validated migration eligibility for {subscriptionsCntr} subscriptions.");
});
foreach (var addOn in addOns)
{
var addOnEligibility = addOnEligibilityList.SelectMany(a => a).SingleOrDefault(m => m.CurrentSubscriptionId.Equals(addOn.Id, StringComparison.OrdinalIgnoreCase));
if (addOnEligibility != null)
{
migrationRequests.Add(PrepareMigrationRequest(customer, addOn, addOnEligibility));
}
}
return migrationRequests;
}
private async Task<ConcurrentBag<ScheduleMigrationRequest>> GetMigrationScheduleAsync(CompanyProfile customer, HttpClient httpClient, ParallelOptions options, IEnumerable<ScheduleMigrationRequest> scheduleMigrationRequests)
{
var baseSubscriptions = scheduleMigrationRequests.Where(s => string.IsNullOrWhiteSpace(s.BaseSubscriptionId));
var addOns = scheduleMigrationRequests.Where(s => !string.IsNullOrWhiteSpace(s.BaseSubscriptionId));
var migrationRequests = new ConcurrentBag<ScheduleMigrationRequest>();
var addOnEligibilityList = new ConcurrentBag<IEnumerable<NewCommerceMigrationSchedule>>();
await Parallel.ForEachAsync(baseSubscriptions, options, async (subscription, cancellationToken) =>
{
var getScheduleMigrationRequest = new HttpRequestMessage(HttpMethod.Get, $"{Routes.GetNewCommerceMigrationSchedules}?CustomerTenantId={customer.TenantId}&CurrentSubscriptionId={subscription.LegacySubscriptionId}");
getScheduleMigrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
var migrationResponse = await httpClient.SendAsync(getScheduleMigrationRequest, cancellationToken).ConfigureAwait(false);
if (migrationResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authenticationResult = await this.tokenProvider.GetTokenAsync();
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
getScheduleMigrationRequest = new HttpRequestMessage(HttpMethod.Get, $"{Routes.GetNewCommerceMigrationSchedules}?CustomerTenantId={customer.TenantId}&CurrentSubscriptionId={subscription.LegacySubscriptionId}");
getScheduleMigrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
migrationResponse = await httpClient.SendAsync(getScheduleMigrationRequest).ConfigureAwait(false);
}
migrationResponse.EnsureSuccessStatusCode();
var newCommerceMigrationSchedule = (await migrationResponse.Content.ReadFromJsonAsync<IEnumerable<NewCommerceMigrationSchedule>>().ConfigureAwait(false)).SingleOrDefault();
migrationRequests.Add(PrepareMigrationRequest(subscription, newCommerceMigrationSchedule));
if (newCommerceMigrationSchedule?.AddOnMigrationSchedules.Any() ?? false)
{
addOnEligibilityList.Add(newCommerceMigrationSchedule.AddOnMigrationSchedules);
}
});
foreach (var addOn in addOns)
{
var addOnEligibility = addOnEligibilityList.SelectMany(a => a).SingleOrDefault(m => m.CurrentSubscriptionId.Equals(addOn.LegacySubscriptionId, StringComparison.OrdinalIgnoreCase));
migrationRequests.Add(PrepareMigrationRequest(addOn, addOnEligibility));
}
return migrationRequests;
}
private static ScheduleMigrationRequest PrepareMigrationRequest(CompanyProfile companyProfile, Subscription subscription, NewCommerceEligibility newCommerceEligibility)
{
return new ScheduleMigrationRequest
{
PartnerTenantId = partnerTenantId,
IndirectResellerMpnId = subscription.PartnerId,
CustomerName = companyProfile.CompanyName,
CustomerTenantId = companyProfile.TenantId,
LegacySubscriptionId = subscription.Id,
LegacySubscriptionName = subscription.FriendlyName,
LegacyProductName = subscription.OfferName,
ExpirationDate = subscription.CommitmentEndDate,
AutoRenewEnabled = subscription.AutoRenewEnabled,
MigrationEligible = newCommerceEligibility.IsEligible,
NcePsa = newCommerceEligibility.CatalogItemId,
CurrentTerm = subscription.TermDuration,
CurrentBillingPlan = subscription.BillingCycle.ToString(),
CurrentSeatCount = subscription.Quantity,
StartNewTermInNce = false,
Term = subscription.TermDuration,
BillingPlan = subscription.BillingCycle.ToString(),
SeatCount = subscription.Quantity,
CustomTermEndDate = newCommerceEligibility.CustomTermEndDate,
AddOn = !string.IsNullOrWhiteSpace(subscription.ParentSubscriptionId),
BaseSubscriptionId = subscription.ParentSubscriptionId,
MigrationIneligibilityReason = newCommerceEligibility.Errors.Any() ?
string.Join(";", newCommerceEligibility.Errors.Select(e => e.Description)) :
string.Empty
};
}
private static ScheduleMigrationRequest PrepareMigrationRequest(ScheduleMigrationRequest scheduleMigrationRequest, NewCommerceMigrationSchedule? newCommerceMigrationSchedule)
{
if (newCommerceMigrationSchedule is not null)
{
return scheduleMigrationRequest with
{
StartNewTermInNce = newCommerceMigrationSchedule.PurchaseFullTerm,
Term = newCommerceMigrationSchedule.TermDuration,
BillingPlan = newCommerceMigrationSchedule.BillingCycle,
SeatCount = newCommerceMigrationSchedule.Quantity,
CustomTermEndDate = newCommerceMigrationSchedule.CustomTermEndDate,
TargetDate = newCommerceMigrationSchedule.TargetDate,
MigrateOnRenewal = newCommerceMigrationSchedule.MigrateOnRenewal,
};
}
return scheduleMigrationRequest;
}
private async Task<List<ScheduleMigrationResult>> PostNewCommerceMigrationScheduleAsync(HttpClient httpClient, ScheduleMigrationRequest migrationRequest, IEnumerable<ScheduleMigrationRequest> addOnMigrationRequests, string batchId, CancellationToken cancellationToken)
{
var newCommerceMigrationRequest = new HttpRequestMessage(HttpMethod.Post, string.Format(Routes.PostNewCommerceMigrationSchedule, migrationRequest.CustomerTenantId));
var getSchedulesRoute = $"{Routes.GetNewCommerceMigrationSchedules}?CurrentSubscriptionId={migrationRequest.LegacySubscriptionId}";
var getSchedulesRequest = new HttpRequestMessage(HttpMethod.Get, getSchedulesRoute);
getSchedulesRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
var existingNewCommerceMigrationScheduleResponse = await httpClient.SendAsync(getSchedulesRequest).ConfigureAwait(false);
NewCommerceMigrationSchedule? existingSchedule = null;
if (existingNewCommerceMigrationScheduleResponse.IsSuccessStatusCode)
{
existingSchedule = (await existingNewCommerceMigrationScheduleResponse.Content.ReadFromJsonAsync<IEnumerable<NewCommerceMigrationSchedule>>().ConfigureAwait(false))?.FirstOrDefault();
}
newCommerceMigrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
var newCommerceMigrationSchedule = new NewCommerceMigrationSchedule
{
CurrentSubscriptionId = migrationRequest.LegacySubscriptionId,
Quantity = migrationRequest.SeatCount,
BillingCycle = migrationRequest.BillingPlan,
TermDuration = migrationRequest.Term,
CustomTermEndDate = migrationRequest.CustomTermEndDate,
TargetDate = migrationRequest.TargetDate,
MigrateOnRenewal = migrationRequest.MigrateOnRenewal,
ExternalReferenceId = batchId,
};
// If they want to start a new term, then we should take the input from the file.
if (migrationRequest.StartNewTermInNce)
{
newCommerceMigrationSchedule.PurchaseFullTerm = true;
}
if (existingSchedule is not null)
{
newCommerceMigrationRequest = new HttpRequestMessage(HttpMethod.Put, string.Format(Routes.UpdateNewCommerceMigrationSchedule, migrationRequest.CustomerTenantId, existingSchedule.Id));
newCommerceMigrationSchedule = newCommerceMigrationSchedule with
{
Id = existingSchedule.Id,
};
}
newCommerceMigrationSchedule.AddOnMigrationSchedules = GetAddOnMigrationSchedules(migrationRequest.LegacySubscriptionId, addOnMigrationRequests);
newCommerceMigrationRequest.Content = new StringContent(JsonSerializer.Serialize(newCommerceMigrationSchedule), Encoding.UTF8, "application/json");
var migrationResponse = await httpClient.SendAsync(newCommerceMigrationRequest, cancellationToken).ConfigureAwait(false);
if (migrationResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authenticationResult = await this.tokenProvider.GetTokenAsync();
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
if (existingSchedule is not null)
{
newCommerceMigrationRequest = new HttpRequestMessage(HttpMethod.Put, string.Format(Routes.UpdateNewCommerceMigrationSchedule, migrationRequest.CustomerTenantId, existingSchedule.Id));
newCommerceMigrationSchedule = newCommerceMigrationSchedule with
{
Id = existingSchedule.Id,
};
}
else
{
newCommerceMigrationRequest = new HttpRequestMessage(HttpMethod.Post, string.Format(Routes.PostNewCommerceMigrationSchedule, migrationRequest.CustomerTenantId));
}
newCommerceMigrationRequest.Content = new StringContent(JsonSerializer.Serialize(newCommerceMigrationSchedule), Encoding.UTF8, "application/json");
newCommerceMigrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
migrationResponse = await httpClient.SendAsync(newCommerceMigrationRequest).ConfigureAwait(false);
}
NewCommerceMigrationError? migrationError = null;
NewCommerceMigrationSchedule? migration = null;
if (migrationResponse.IsSuccessStatusCode)
{
migration = await migrationResponse.Content.ReadFromJsonAsync<NewCommerceMigrationSchedule>().ConfigureAwait(false);
}
else
{
migrationError = await migrationResponse.Content.ReadFromJsonAsync<NewCommerceMigrationError>().ConfigureAwait(false);
}
return this.PrepareMigrationResult(migrationRequest, addOnMigrationRequests, batchId, migration, migrationError);
}
private async Task<NewCommerceMigrationSchedule> CancelNewCommerceMigrationScheduleAsync(HttpClient httpClient, NewCommerceMigrationSchedule newCommerceMigrationSchedule, CancellationToken cancellationToken)
{
var cancelNewCommerceMigrationRequest = new HttpRequestMessage(HttpMethod.Post, string.Format(Routes.CancelNewCommerceMigrationSchedule, newCommerceMigrationSchedule.CustomerTenantId, newCommerceMigrationSchedule.Id));
cancelNewCommerceMigrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
var migrationScheduleResponse = await httpClient.SendAsync(cancelNewCommerceMigrationRequest, cancellationToken).ConfigureAwait(false);
if (migrationScheduleResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authenticationResult = await this.tokenProvider.GetTokenAsync();
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
cancelNewCommerceMigrationRequest = new HttpRequestMessage(HttpMethod.Post, string.Format(Routes.CancelNewCommerceMigrationSchedule, newCommerceMigrationSchedule.CustomerTenantId, newCommerceMigrationSchedule.Id));
cancelNewCommerceMigrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
cancelNewCommerceMigrationRequest.Content = new StringContent(JsonSerializer.Serialize(newCommerceMigrationSchedule), Encoding.UTF8, "application/json");
migrationScheduleResponse = await httpClient.SendAsync(cancelNewCommerceMigrationRequest).ConfigureAwait(false);
}
if (migrationScheduleResponse.IsSuccessStatusCode)
{
newCommerceMigrationSchedule = await migrationScheduleResponse.Content.ReadFromJsonAsync<NewCommerceMigrationSchedule>().ConfigureAwait(false);
}
else
{
var migrationScheduleError = await migrationScheduleResponse.Content.ReadFromJsonAsync<NewCommerceMigrationError>().ConfigureAwait(false);
newCommerceMigrationSchedule.ErrorCode = migrationScheduleError.Code;
newCommerceMigrationSchedule.ErrorDescription = migrationScheduleError.Description;
}
return newCommerceMigrationSchedule;
}
private static IEnumerable<NewCommerceMigrationSchedule> GetAddOnMigrationSchedules(string currentSubscriptionId, IEnumerable<ScheduleMigrationRequest> addOnMigrationRequests)
{
if (!addOnMigrationRequests.Any())
{
return Enumerable.Empty<NewCommerceMigrationSchedule>();
}
var allAddOnMigrations = new List<NewCommerceMigrationSchedule>();
var childAddOns = addOnMigrationRequests.Where(a => a.BaseSubscriptionId.Equals(currentSubscriptionId, StringComparison.OrdinalIgnoreCase));
if (childAddOns.Any())
{
var addOnNewCommerceMigrations = childAddOns.Select(request => new NewCommerceMigrationSchedule
{
CurrentSubscriptionId = request.LegacySubscriptionId,
Quantity = request.SeatCount,
BillingCycle = request.BillingPlan,
TermDuration = request.Term,
PurchaseFullTerm = request.StartNewTermInNce,
CustomTermEndDate = request.CustomTermEndDate,
TargetDate = request.TargetDate,
MigrateOnRenewal = request.MigrateOnRenewal,
});
allAddOnMigrations.AddRange(addOnNewCommerceMigrations);
foreach (var item in childAddOns)
{
var multiLevelAddons = GetAddOnMigrationSchedules(item.LegacySubscriptionId, addOnMigrationRequests);
allAddOnMigrations.AddRange(multiLevelAddons);
}
}
return allAddOnMigrations;
}
private List<ScheduleMigrationResult> PrepareMigrationResult(ScheduleMigrationRequest migrationRequest, IEnumerable<ScheduleMigrationRequest> addOnMigrationRequests, string batchId, NewCommerceMigrationSchedule? newCommerceMigrationSchedule = null, NewCommerceMigrationError? newCommerceMigrationError = null)
{
var migrationResults = new List<ScheduleMigrationResult>();
PrepareMigrationResult(migrationRequest, batchId, newCommerceMigrationSchedule, newCommerceMigrationError, migrationResults);
if (newCommerceMigrationSchedule?.AddOnMigrationSchedules.Any() == true)
{
PrepareAddOnMigrationResult(addOnMigrationRequests, batchId, newCommerceMigrationSchedule, newCommerceMigrationError, migrationResults);
}
return migrationResults;
}
private static void PrepareMigrationResult(ScheduleMigrationRequest migrationRequest, string batchId, NewCommerceMigrationSchedule? newCommerceMigrationSchedule, NewCommerceMigrationError? newCommerceMigrationError, List<ScheduleMigrationResult> migrationResults)
{
if (newCommerceMigrationError != null)
{
var migrationResult = new ScheduleMigrationResult
{
PartnerTenantId = migrationRequest.PartnerTenantId,
IndirectResellerMpnId = migrationRequest.IndirectResellerMpnId,
CustomerName = migrationRequest.CustomerName,
CustomerTenantId = migrationRequest.CustomerTenantId,
LegacySubscriptionId = migrationRequest.LegacySubscriptionId,
LegacySubscriptionName = migrationRequest.LegacySubscriptionName,
LegacyProductName = migrationRequest.LegacyProductName,
ExpirationDate = migrationRequest.ExpirationDate,
AddOn = migrationRequest.AddOn,
StartedNewTermInNce = migrationRequest.StartNewTermInNce,
NCETermDuration = migrationRequest.Term,
NCEBillingPlan = migrationRequest.BillingPlan,
NCESeatCount = migrationRequest.SeatCount,
CustomTermEndDate = migrationRequest.CustomTermEndDate,
TargetDate = migrationRequest.TargetDate,
MigrateOnRenewal = migrationRequest.MigrateOnRenewal,
ErrorCode = newCommerceMigrationError.Code,
ErrorReason = newCommerceMigrationError.Description,
};
migrationResults.Add(migrationResult);
}
if (newCommerceMigrationSchedule != null)
{
var migrationResult = new ScheduleMigrationResult
{
PartnerTenantId = migrationRequest.PartnerTenantId,
IndirectResellerMpnId = migrationRequest.IndirectResellerMpnId,
CustomerName = migrationRequest.CustomerName,
CustomerTenantId = migrationRequest.CustomerTenantId,
LegacySubscriptionId = migrationRequest.LegacySubscriptionId,
LegacySubscriptionName = migrationRequest.LegacySubscriptionName,
LegacyProductName = migrationRequest.LegacyProductName,
ExpirationDate = migrationRequest.ExpirationDate,
AddOn = migrationRequest.AddOn,
StartedNewTermInNce = migrationRequest.StartNewTermInNce,
NCETermDuration = newCommerceMigrationSchedule.TermDuration,
NCEBillingPlan = newCommerceMigrationSchedule.BillingCycle,
NCESeatCount = newCommerceMigrationSchedule.Quantity,
CustomTermEndDate = migrationRequest.CustomTermEndDate,
TargetDate = migrationRequest.TargetDate,
MigrateOnRenewal = migrationRequest.MigrateOnRenewal,
BatchId = batchId,
MigrationScheduleId = newCommerceMigrationSchedule.Id,
MigrationScheduleStatus = newCommerceMigrationSchedule.Status,
};
migrationResults.Add(migrationResult);
}
}
private List<ScheduleMigrationResult> PrepareAddOnMigrationResult(IEnumerable<ScheduleMigrationRequest> addOnMigrationRequests, string batchId, NewCommerceMigrationSchedule? newCommerceMigrationSchedule, NewCommerceMigrationError? newCommerceMigrationError, List<ScheduleMigrationResult> migrationResults)
{
if (newCommerceMigrationSchedule != null && addOnMigrationRequests?.Any() == true)
{
foreach (var addOnMigrationResponse in newCommerceMigrationSchedule.AddOnMigrationSchedules)
{
var addOnMigrationRequest = addOnMigrationRequests.SingleOrDefault(n => n.LegacySubscriptionId.Equals(addOnMigrationResponse.CurrentSubscriptionId, StringComparison.OrdinalIgnoreCase));
addOnMigrationResponse.Status = newCommerceMigrationSchedule.Status;
addOnMigrationResponse.Id = newCommerceMigrationSchedule.Id;
PrepareMigrationResult(addOnMigrationRequest, batchId, addOnMigrationResponse, newCommerceMigrationError, migrationResults);
}
}
return migrationResults;
}
private async Task<(ScheduleMigrationResult BaseMigrationResult, IEnumerable<NewCommerceMigrationSchedule> AddOnMigrationsResult)> GetNewCommerceMigrationScheduleByScheduleIdAsync(HttpClient httpClient, ScheduleMigrationResult migrationResult, CancellationToken cancellationToken)
{
// Validate that the migration result has a migrationId, if a migration didn't initiate the migrationId will be empty.
if (string.IsNullOrWhiteSpace(migrationResult.MigrationScheduleId))
{
// We cannot determine the status, we should return this migration result.
return (migrationResult, Enumerable.Empty<NewCommerceMigrationSchedule>());
}
var getNewCommerceMigrationSchedule = new HttpRequestMessage(HttpMethod.Get, string.Format(Routes.GetNewCommerceMigrationSchedule, migrationResult.CustomerTenantId, migrationResult.MigrationScheduleId));
getNewCommerceMigrationSchedule.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
var migrationResponse = await httpClient.SendAsync(getNewCommerceMigrationSchedule, cancellationToken).ConfigureAwait(false);
if (migrationResponse.StatusCode == HttpStatusCode.Unauthorized)
{
var authenticationResult = await this.tokenProvider.GetTokenAsync();
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
getNewCommerceMigrationSchedule = new HttpRequestMessage(HttpMethod.Get, string.Format(Routes.GetNewCommerceMigrationSchedule, migrationResult.CustomerTenantId, migrationResult.MigrationScheduleId));
getNewCommerceMigrationSchedule.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
migrationResponse = await httpClient.SendAsync(getNewCommerceMigrationSchedule).ConfigureAwait(false);
}
NewCommerceMigrationError? migrationError = null;
NewCommerceMigrationSchedule? migration = null;
if (migrationResponse.IsSuccessStatusCode)
{
migration = await migrationResponse.Content.ReadFromJsonAsync<NewCommerceMigrationSchedule>().ConfigureAwait(false);
}
var result = this.PrepareMigrationResult(migrationResult, migrationResult.BatchId, migration, migrationError);
return (result, migration?.AddOnMigrationSchedules);
}
private ScheduleMigrationResult PrepareMigrationResult(ScheduleMigrationResult migrationResult, string batchId, NewCommerceMigrationSchedule? newCommerceMigrationSchedule = null, NewCommerceMigrationError? newCommerceMigrationError = null)
{
ScheduleMigrationResult result = new ScheduleMigrationResult();
if (newCommerceMigrationError != null)
{
result = new ScheduleMigrationResult
{
PartnerTenantId = migrationResult.PartnerTenantId,
IndirectResellerMpnId = migrationResult.IndirectResellerMpnId,
CustomerName = migrationResult.CustomerName,
CustomerTenantId = migrationResult.CustomerTenantId,
LegacySubscriptionId = migrationResult.LegacySubscriptionId,
LegacySubscriptionName = migrationResult.LegacySubscriptionName,
LegacyProductName = migrationResult.LegacyProductName,
ExpirationDate = migrationResult.ExpirationDate,
StartedNewTermInNce = migrationResult.StartedNewTermInNce,
NCETermDuration = migrationResult.NCETermDuration,
NCEBillingPlan = migrationResult.NCEBillingPlan,
NCESeatCount = migrationResult.NCESeatCount,
CustomTermEndDate = migrationResult.CustomTermEndDate,
ErrorCode = newCommerceMigrationError.Code,
ErrorReason = newCommerceMigrationError.Description,
};
}
if (newCommerceMigrationSchedule != null)
{
result = new ScheduleMigrationResult
{
PartnerTenantId = migrationResult.PartnerTenantId,
IndirectResellerMpnId = migrationResult.IndirectResellerMpnId,
CustomerName = migrationResult.CustomerName,
CustomerTenantId = migrationResult.CustomerTenantId,
LegacySubscriptionId = migrationResult.LegacySubscriptionId,
LegacySubscriptionName = migrationResult.LegacySubscriptionName,
LegacyProductName = migrationResult.LegacyProductName,
ExpirationDate = migrationResult.ExpirationDate,
StartedNewTermInNce = migrationResult.StartedNewTermInNce,
NCETermDuration = newCommerceMigrationSchedule.TermDuration,
NCEBillingPlan = newCommerceMigrationSchedule.BillingCycle,
NCESeatCount = newCommerceMigrationSchedule.Quantity,
CustomTermEndDate = newCommerceMigrationSchedule.CustomTermEndDate,
TargetDate = newCommerceMigrationSchedule.TargetDate,
MigrateOnRenewal = newCommerceMigrationSchedule.MigrateOnRenewal,
BatchId = batchId,
MigrationScheduleId = newCommerceMigrationSchedule.Id,
MigrationScheduleStatus = newCommerceMigrationSchedule.Status,
ErrorCode = newCommerceMigrationSchedule.ErrorCode,
ErrorReason = newCommerceMigrationSchedule.ErrorDescription,
};
}
return result;
}
}
}

Просмотреть файл

@ -15,7 +15,7 @@ if (args.Length == 2)
}
else
{
AppId:
AppId:
Console.WriteLine("Enter AppId");
appId = Console.ReadLine();
@ -25,7 +25,7 @@ else
goto AppId;
}
Upn:
Upn:
Console.WriteLine("Enter Upn");
upn = Console.ReadLine();
if (string.IsNullOrWhiteSpace(upn))
@ -49,6 +49,7 @@ using IHost host = Host.CreateDefaultBuilder(args)
services.AddSingleton<ICustomerProvider, CustomerProvider>();
services.AddSingleton<ISubscriptionProvider, SubscriptionProvider>();
services.AddSingleton<INewCommerceMigrationProvider, NewCommerceMigrationProvider>();
services.AddSingleton<INewCommerceMigrationScheduleProvider, NewCommerceMigrationScheduleProvider>();
}).Build();
await RunAsync(host.Services);
@ -57,11 +58,7 @@ await host.RunAsync();
static async Task RunAsync(IServiceProvider serviceProvider)
{
Directory.CreateDirectory($"{Constants.InputFolderPath}/subscriptions/processed");
Directory.CreateDirectory($"{Constants.InputFolderPath}/migrations/processed");
Directory.CreateDirectory(Constants.OutputFolderPath);
ShowOptions:
ShowOptions:
Console.WriteLine("Please choose an option");
Console.WriteLine("1. Export customers");
@ -69,23 +66,33 @@ static async Task RunAsync(IServiceProvider serviceProvider)
Console.WriteLine("3. Upload migrations");
Console.WriteLine("4. Export migration status");
Console.WriteLine("5. Export NCE subscriptions");
Console.WriteLine("6. Exit");
Console.WriteLine("6. Export subscriptions with migration eligibility to schedule migrations");
Console.WriteLine("7. Upload migration schedules");
Console.WriteLine("8. Export schedule migrations");
Console.WriteLine("9. Cancel schedule migrations");
Console.WriteLine("10. Exit");
SelectOption:
var option = Console.ReadLine();
if (!short.TryParse(option, out short input) || !(input >= 1 && input <= 6))
if (!short.TryParse(option, out short input) || !(input >= 1 && input <= 10))
{
Console.WriteLine("Invalid input, Please try again! Possible values are {1, 2, 3, 4, 5, 6}");
Console.WriteLine("Invalid input, Please try again! Possible values are {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}");
goto SelectOption;
}
if(input == 6)
if (input == 10)
{
Console.WriteLine("Exiting the app!");
Environment.Exit(Environment.ExitCode);
}
Directory.CreateDirectory($"{Constants.InputFolderPath}/subscriptions/processed");
Directory.CreateDirectory($"{Constants.InputFolderPath}/migrations/processed");
Directory.CreateDirectory($"{Constants.InputFolderPath}/subscriptionsforschedule/processed");
Directory.CreateDirectory($"{Constants.InputFolderPath}/cancelschedulemigrations/processed");
Directory.CreateDirectory(Constants.OutputFolderPath);
Stopwatch stopwatch = Stopwatch.StartNew();
var result = input switch
@ -95,6 +102,10 @@ SelectOption:
3 => await serviceProvider.GetRequiredService<INewCommerceMigrationProvider>().UploadNewCommerceMigrationsAsync(),
4 => await serviceProvider.GetRequiredService<INewCommerceMigrationProvider>().ExportNewCommerceMigrationStatusAsync(),
5 => await serviceProvider.GetRequiredService<ISubscriptionProvider>().ExportModernSubscriptionsAsync(),
6 => await serviceProvider.GetRequiredService<INewCommerceMigrationScheduleProvider>().ValidateAndGetSubscriptionsToScheduleMigrationAsync(),
7 => await serviceProvider.GetRequiredService<INewCommerceMigrationScheduleProvider>().UploadNewCommerceMigrationSchedulesAsync(),
8 => await serviceProvider.GetRequiredService<INewCommerceMigrationScheduleProvider>().ExportNewCommerceMigrationSchedulesAsync(),
9 => await serviceProvider.GetRequiredService<INewCommerceMigrationScheduleProvider>().CancelNewCommerceMigrationSchedulesAsync(),
_ => throw new InvalidOperationException("Invalid input")
};

Просмотреть файл

@ -40,7 +40,10 @@ internal class SubscriptionProvider : ISubscriptionProvider
var authenticationResult = await this.tokenProvider.GetTokenAsync();
partnerTenantId = authenticationResult.TenantId;
var httpClient = new HttpClient();
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
@ -98,7 +101,10 @@ internal class SubscriptionProvider : ISubscriptionProvider
var authenticationResult = await this.tokenProvider.GetTokenAsync();
partnerTenantId = authenticationResult.TenantId;
var httpClient = new HttpClient();
var httpClient = new HttpClient
{
BaseAddress = new Uri(Routes.BaseUrl)
};
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
@ -182,7 +188,7 @@ internal class SubscriptionProvider : ISubscriptionProvider
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
subscriptionRequest = new HttpRequestMessage(HttpMethod.Get, Routes.GetCustomers);
subscriptionRequest = new HttpRequestMessage(HttpMethod.Get, string.Format(Routes.GetSubscriptions, customer.TenantId));
subscriptionRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
subscriptionResponse = await httpClient.SendAsync(subscriptionRequest).ConfigureAwait(false);
@ -229,7 +235,10 @@ internal class SubscriptionProvider : ISubscriptionProvider
httpClient.DefaultRequestHeaders.Clear();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authenticationResult.AccessToken);
httpClient.DefaultRequestHeaders.Add(Constants.PartnerCenterClientHeader, Constants.ClientName);
migrationRequest = new HttpRequestMessage(HttpMethod.Get, Routes.GetCustomers);
migrationRequest = new HttpRequestMessage(HttpMethod.Post, string.Format(Routes.ValidateMigrationEligibility, customer.TenantId))
{
Content = new StringContent(JsonSerializer.Serialize(payload), Encoding.UTF8, "application/json")
};
migrationRequest.Headers.Add("MS-CorrelationId", Guid.NewGuid().ToString());
migrationResponse = await httpClient.SendAsync(migrationRequest).ConfigureAwait(false);
@ -237,7 +246,7 @@ internal class SubscriptionProvider : ISubscriptionProvider
migrationResponse.EnsureSuccessStatusCode();
var newCommerceEligibility = await migrationResponse.Content.ReadFromJsonAsync<NewCommerceEligibility>().ConfigureAwait(false);
if (newCommerceEligibility!.AddOnMigrations.Any())
if (newCommerceEligibility.AddOnMigrations.Any())
{
addOnEligibilityList.Add(newCommerceEligibility.AddOnMigrations);
}
@ -289,6 +298,7 @@ internal class SubscriptionProvider : ISubscriptionProvider
Term = subscription.TermDuration,
BillingPlan = subscription.BillingCycle.ToString(),
SeatCount = subscription.Quantity,
CustomTermEndDate = newCommerceEligibility.CustomTermEndDate,
AddOn = !string.IsNullOrWhiteSpace(subscription.ParentSubscriptionId),
BaseSubscriptionId = subscription.ParentSubscriptionId,
MigrationIneligibilityReason = newCommerceEligibility.Errors.Any() ?