@chimoney.io/iaas-k8s-deployment
Version:
Kubernetes Infrastructure as a Service deployment package for streamlined multi-cloud environments
1,042 lines (922 loc) • 30.1 kB
text/typescript
import { TierCalculator } from "./tier-calculator.js";
import {
PlanTier,
getTierConfiguration,
validateTierConfiguration,
TierConfiguration,
PLAN_TIER_DEFINITIONS,
} from "../types/plans.js";
import { KubecostClient } from "./kubecost-client.js";
import { Logger } from "../types/index.js";
import * as pulumi from "@pulumi/pulumi";
import * as k8s from "@pulumi/kubernetes";
export interface MigrationPlan {
companyName: string;
fromTier: PlanTier;
toTier: PlanTier;
namespace: string;
migrationType: "upgrade" | "downgrade";
requiresDowntime: boolean;
estimatedDuration: string;
preChecks: string[];
steps: string[];
rollbackSteps: string[];
warnings: string[];
costImpact?: {
currentMonthlyCost: number;
newMonthlyCost: number;
difference: number;
};
}
export interface MigrationResult {
success: boolean;
duration?: string;
error?: string;
rollbackRequired?: boolean;
details?: Record<string, any>;
}
export interface MigrationValidationResult {
isValid: boolean;
errors: string[];
warnings: string[];
recommendations: string[];
}
export interface MigrationPreCheck {
name: string;
description: string;
passed: boolean;
error?: string;
details?: Record<string, any>;
}
export interface ResourceUsage {
cpu: number; // millicores
memory: number; // Mi
storage: number; // Gi
}
export class TierMigrationManager {
private tierCalculator: TierCalculator;
private kubecostClient: KubecostClient;
private logger: Logger;
constructor(
tierCalculator: TierCalculator,
kubecostClient: KubecostClient,
logger: Logger
) {
this.tierCalculator = tierCalculator;
this.kubecostClient = kubecostClient;
this.logger = logger;
}
/**
* Plan a migration between tiers
*/
async planMigration(
companyName: string,
fromTier: PlanTier,
toTier: PlanTier,
namespace?: string
): Promise<MigrationPlan> {
this.logger.info(
`Planning migration for ${companyName}: ${fromTier} -> ${toTier}`
);
if (fromTier === toTier) {
throw new Error("Source and target tiers cannot be the same");
}
const targetNamespace = namespace || `shared-${companyName.toLowerCase()}`;
const migrationType = this.getMigrationType(fromTier, toTier);
const plan: MigrationPlan = {
companyName,
fromTier,
toTier,
namespace: targetNamespace,
migrationType,
requiresDowntime: migrationType === "downgrade",
estimatedDuration: this.estimateMigrationDuration(fromTier, toTier),
preChecks: [],
steps: [],
rollbackSteps: [],
warnings: [],
};
// Add pre-checks
plan.preChecks.push(`Validate current ${fromTier} tier resources`);
plan.preChecks.push(`Check cluster capacity for ${toTier} tier`);
plan.preChecks.push("Backup current configuration");
plan.preChecks.push("Verify current resource usage");
plan.preChecks.push("Check for pending operations");
// Add migration steps
if (migrationType === "upgrade") {
plan.steps.push("Create backup of current configuration");
plan.steps.push(`Generate ${toTier} tier configuration`);
plan.steps.push("Update resource quotas to accommodate new resources");
plan.steps.push(`Scale up services to ${toTier} tier specifications`);
plan.steps.push("Update limit ranges");
plan.steps.push("Apply new tier labels and annotations");
plan.steps.push("Verify all services are running with new resources");
plan.steps.push("Update Kubecost configuration if enabled");
plan.steps.push("Verify monitoring and alerting configuration");
} else {
plan.steps.push("Create backup of current configuration");
plan.steps.push("Verify resource usage fits within target tier limits");
plan.steps.push(
"Validate that current workload can run on smaller resources"
);
plan.steps.push(`Scale down services to ${toTier} tier specifications`);
plan.steps.push("Update resource quotas and limits");
plan.steps.push("Apply new tier labels and annotations");
plan.steps.push("Monitor service health during downscale");
plan.steps.push("Update Kubecost configuration if enabled");
plan.steps.push("Verify all services remain healthy");
}
// Add rollback steps
plan.rollbackSteps.push(
`Restore ${fromTier} tier configuration from backup`
);
plan.rollbackSteps.push("Revert resource quotas and limits");
plan.rollbackSteps.push("Restore original service configurations");
plan.rollbackSteps.push("Remove new tier labels and annotations");
plan.rollbackSteps.push("Verify service health after rollback");
plan.rollbackSteps.push("Restore Kubecost configuration");
// Add warnings
if (migrationType === "downgrade") {
plan.warnings.push("Downgrade may result in service degradation");
plan.warnings.push(
"Ensure current resource usage fits within target tier limits"
);
plan.warnings.push(
"Some features available in higher tiers may be disabled"
);
plan.warnings.push(
"Performance may be reduced due to lower resource allocation"
);
}
if (this.tierRequiresDowntime(fromTier, toTier)) {
plan.warnings.push("This migration may cause brief service interruption");
}
// Calculate cost impact
try {
const currentCost = this.tierCalculator.calculateMonthlyCost(fromTier);
const newCost = this.tierCalculator.calculateMonthlyCost(toTier);
plan.costImpact = {
currentMonthlyCost: currentCost,
newMonthlyCost: newCost,
difference: newCost - currentCost,
};
if (plan.costImpact.difference > 0) {
plan.warnings.push(
`Migration will increase monthly cost by $${plan.costImpact.difference}`
);
} else if (plan.costImpact.difference < 0) {
plan.warnings.push(
`Migration will reduce monthly cost by $${Math.abs(
plan.costImpact.difference
)}`
);
}
} catch (error) {
plan.warnings.push(
`Could not calculate cost impact: ${
error instanceof Error ? error.message : String(error)
}`
);
}
return plan;
}
/**
* Execute a migration plan
*/
async executeMigration(
companyName: string,
fromTier: PlanTier,
toTier: PlanTier,
namespace?: string,
options: {
dryRun?: boolean;
force?: boolean;
skipPreChecks?: boolean;
k8sProvider?: any;
} = {}
): Promise<MigrationResult> {
const startTime = Date.now();
try {
this.logger.info(
`Executing migration for ${companyName}: ${fromTier} -> ${toTier}`
);
const plan = await this.planMigration(
companyName,
fromTier,
toTier,
namespace
);
if (options.dryRun) {
this.logger.info("Dry run mode - no actual changes will be made");
return {
success: true,
duration: "0s",
details: {
dryRun: true,
plan,
},
};
}
// Run pre-checks unless skipped
if (!options.skipPreChecks) {
this.logger.info("Running pre-checks...");
const preCheckResults = await this.runPreChecks(
plan,
options.k8sProvider
);
if (!preCheckResults.every((check) => check.passed) && !options.force) {
const failedChecks = preCheckResults.filter((check) => !check.passed);
throw new Error(
`Pre-checks failed: ${failedChecks.map((c) => c.error).join(", ")}`
);
}
}
// Execute migration steps
const executionResults: Array<{
step: string;
success: boolean;
error?: string;
}> = [];
for (const [index, step] of plan.steps.entries()) {
this.logger.info(
`Executing step ${index + 1}/${plan.steps.length}: ${step}`
);
try {
await this.executeStep(step, plan, options.k8sProvider);
executionResults.push({ step, success: true });
// Add delay between steps for safety
if (index < plan.steps.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
this.logger.error(`Step failed: ${step} - ${errorMessage}`);
executionResults.push({ step, success: false, error: errorMessage });
// If a critical step fails, attempt rollback
throw new Error(
`Migration failed at step: ${step} - ${errorMessage}`
);
}
}
const duration = `${Math.round((Date.now() - startTime) / 1000)}s`;
this.logger.info(`Migration completed successfully in ${duration}`);
return {
success: true,
duration,
details: {
fromTier,
toTier,
namespace: plan.namespace,
stepsExecuted: executionResults.length,
executionResults,
costImpact: plan.costImpact,
},
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
this.logger.error(`Failed to execute migration: ${errorMessage}`);
return {
success: false,
error: errorMessage,
rollbackRequired: true,
details: {
fromTier,
toTier,
failurePoint: errorMessage,
},
};
}
}
/**
* Execute rollback of a failed migration
*/
async rollbackMigration(
companyName: string,
fromTier: PlanTier,
toTier: PlanTier,
namespace?: string,
options: {
k8sProvider?: any;
} = {}
): Promise<MigrationResult> {
const startTime = Date.now();
try {
this.logger.info(
`Rolling back migration for ${companyName}: ${toTier} -> ${fromTier}`
);
const plan = await this.planMigration(
companyName,
toTier,
fromTier,
namespace
);
// Execute rollback steps
for (const [index, step] of plan.rollbackSteps.entries()) {
this.logger.info(
`Executing rollback step ${index + 1}/${
plan.rollbackSteps.length
}: ${step}`
);
try {
await this.executeStep(step, plan, options.k8sProvider);
// Add delay between steps
if (index < plan.rollbackSteps.length - 1) {
await new Promise((resolve) => setTimeout(resolve, 2000));
}
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
this.logger.error(`Rollback step failed: ${step} - ${errorMessage}`);
// Continue with other rollback steps even if one fails
}
}
const duration = `${Math.round((Date.now() - startTime) / 1000)}s`;
this.logger.info(`Rollback completed in ${duration}`);
return {
success: true,
duration,
details: {
rollbackFrom: toTier,
rollbackTo: fromTier,
namespace: plan.namespace,
},
};
} catch (error) {
const errorMessage =
error instanceof Error ? error.message : String(error);
this.logger.error(`Rollback failed: ${errorMessage}`);
return {
success: false,
error: errorMessage,
details: {
rollbackFailure: true,
error: errorMessage,
},
};
}
}
/**
* Validate if a migration is possible and safe
*/
async validateMigration(
companyName: string,
fromTier: PlanTier,
toTier: PlanTier,
namespace?: string,
options: {
checkResourceUsage?: boolean;
checkClusterCapacity?: boolean;
} = {}
): Promise<MigrationValidationResult> {
const errors: string[] = [];
const warnings: string[] = [];
const recommendations: string[] = [];
try {
// Basic validation
if (fromTier === toTier) {
errors.push("Source and target tiers cannot be the same");
}
if (!Object.values(PlanTier).includes(fromTier)) {
errors.push(`Invalid source tier: ${fromTier}`);
}
if (!Object.values(PlanTier).includes(toTier)) {
errors.push(`Invalid target tier: ${toTier}`);
}
// Get tier configurations
const fromConfig = getTierConfiguration(fromTier);
const toConfig = getTierConfiguration(toTier);
// Validate tier configurations
const fromValidation = validateTierConfiguration(fromConfig);
const toValidation = validateTierConfiguration(toConfig);
if (fromValidation.length > 0) {
errors.push(
`Source tier configuration invalid: ${fromValidation.join(", ")}`
);
}
if (toValidation.length > 0) {
errors.push(
`Target tier configuration invalid: ${toValidation.join(", ")}`
);
}
// Check migration direction
const migrationType = this.getMigrationType(fromTier, toTier);
if (migrationType === "downgrade") {
warnings.push(
"Downgrade migrations require careful resource usage validation"
);
recommendations.push(
"Monitor current resource usage before proceeding"
);
recommendations.push("Consider running a dry-run first");
}
// Resource usage validation for downgrades
if (migrationType === "downgrade" && options.checkResourceUsage) {
try {
const currentUsage = await this.getCurrentResourceUsage(
companyName,
namespace
);
const targetLimits = this.getTierResourceLimits(toTier);
if (currentUsage.cpu > targetLimits.cpu) {
errors.push(
`Current CPU usage (${currentUsage.cpu}m) exceeds target tier limit (${targetLimits.cpu}m)`
);
}
if (currentUsage.memory > targetLimits.memory) {
errors.push(
`Current memory usage (${currentUsage.memory}Mi) exceeds target tier limit (${targetLimits.memory}Mi)`
);
}
if (currentUsage.storage > targetLimits.storage) {
errors.push(
`Current storage usage (${currentUsage.storage}Gi) exceeds target tier limit (${targetLimits.storage}Gi)`
);
}
} catch (error) {
warnings.push("Could not validate current resource usage");
}
}
// Network policies validation
if (
fromConfig.resourceAllocation.networkPoliciesEnabled &&
!toConfig.resourceAllocation.networkPoliciesEnabled
) {
warnings.push(
"Target tier disables network policies - this may reduce security"
);
recommendations.push(
"Consider upgrading to a tier with network policies enabled"
);
}
// Cost impact validation
try {
const fromCost = this.tierCalculator.calculateMonthlyCost(fromTier);
const toCost = this.tierCalculator.calculateMonthlyCost(toTier);
const costDelta = toCost - fromCost;
if (costDelta > 500) {
warnings.push(
`Migration will significantly increase costs by $${costDelta}/month`
);
}
} catch (error) {
warnings.push("Could not calculate cost impact");
}
// General recommendations
if (migrationType === "upgrade") {
recommendations.push(
"Upgrade migrations are generally safe and can be performed with minimal downtime"
);
}
recommendations.push(
"Always backup your configuration before performing migrations"
);
recommendations.push("Monitor service health during and after migration");
} catch (error) {
errors.push(
`Validation failed: ${
error instanceof Error ? error.message : String(error)
}`
);
}
return {
isValid: errors.length === 0,
errors,
warnings,
recommendations,
};
}
/**
* Get current resource usage for a company/namespace
*/
async getCurrentResourceUsage(
companyName: string,
namespace?: string
): Promise<ResourceUsage> {
const targetNamespace = namespace || `shared-${companyName.toLowerCase()}`;
try {
// Use Kubecost client to get current usage
// Note: Getting usage by company name isn't directly supported in current getTierCostSummary method
// This is a placeholder implementation that would need actual tier information
// For now, we'll return conservative estimates
this.logger.warn(
"getCurrentResourceUsage: Actual usage retrieval not implemented, returning conservative estimates"
);
// In real implementation, you'd need to determine the current tier or have a different method
// const costs = await this.kubecostClient.getTierCostSummary(
// currentTier, // Need to determine current tier first
// targetNamespace,
// companyName,
// "1d" // Last 24 hours
// );
return {
cpu: 0, // Conservative estimate - would use costs.usage.cpuCoreHours * 1000
memory: 0, // Conservative estimate - would use costs.usage.ramGBHours * 1024
storage: 0, // Conservative estimate - would use costs.usage.storageGBHours
};
} catch (error) {
this.logger.warn(
`Could not get current resource usage: ${
error instanceof Error ? error.message : String(error)
}`
);
// Return conservative estimates
return {
cpu: 0,
memory: 0,
storage: 0,
};
}
}
/**
* Get resource limits for a specific tier
*/
private getTierResourceLimits(tier: PlanTier): ResourceUsage {
const tierDef = PLAN_TIER_DEFINITIONS[tier];
const quota = tierDef.quota;
return {
cpu: parseInt(quota.limits.cpu.replace("m", "")),
memory: parseInt(quota.limits.memory.replace(/[^0-9]/g, "")),
storage: parseInt(quota.limits.storage.replace(/[^0-9]/g, "")),
};
}
/**
* Run pre-checks before migration
*/
private async runPreChecks(
plan: MigrationPlan,
k8sProvider?: any
): Promise<MigrationPreCheck[]> {
const checks: MigrationPreCheck[] = [];
// Check 1: Validate tier configurations
checks.push({
name: "Tier Configuration Validation",
description: "Validate source and target tier configurations",
passed: true, // This would have failed earlier if invalid
});
// Check 2: Check namespace exists (for shared deployments)
if (k8sProvider) {
try {
// This is a simplified check - in real implementation you'd use k8s API
checks.push({
name: "Namespace Validation",
description: `Verify namespace ${plan.namespace} exists and is accessible`,
passed: true,
});
} catch (error) {
checks.push({
name: "Namespace Validation",
description: `Verify namespace ${plan.namespace} exists and is accessible`,
passed: false,
error: error instanceof Error ? error.message : String(error),
});
}
}
// Check 3: Verify no other migrations in progress
checks.push({
name: "Migration Lock Check",
description: "Ensure no other migrations are currently in progress",
passed: true, // In real implementation, you'd check for migration locks
});
// Check 4: Resource usage validation for downgrades
if (plan.migrationType === "downgrade") {
try {
const currentUsage = await this.getCurrentResourceUsage(
plan.companyName,
plan.namespace
);
const targetLimits = this.getTierResourceLimits(plan.toTier);
const resourcesWithinLimits =
currentUsage.cpu <= targetLimits.cpu &&
currentUsage.memory <= targetLimits.memory &&
currentUsage.storage <= targetLimits.storage;
checks.push({
name: "Resource Usage Validation",
description: "Verify current usage fits within target tier limits",
passed: resourcesWithinLimits,
error: resourcesWithinLimits
? undefined
: "Current resource usage exceeds target tier limits",
details: {
currentUsage,
targetLimits,
},
});
} catch (error) {
checks.push({
name: "Resource Usage Validation",
description: "Verify current usage fits within target tier limits",
passed: false,
error: `Failed to check resource usage: ${
error instanceof Error ? error.message : String(error)
}`,
});
}
}
return checks;
}
/**
* Execute a single migration step
*/
private async executeStep(
step: string,
plan: MigrationPlan,
k8sProvider?: any
): Promise<void> {
// This is a simplified implementation - in a real system you'd implement
// actual Kubernetes operations for each step type
if (step.includes("backup")) {
await this.createConfigurationBackup(plan.companyName, plan.namespace);
} else if (step.includes("resource quota")) {
await this.updateResourceQuota(plan, k8sProvider);
} else if (step.includes("scale")) {
await this.scaleServices(plan, k8sProvider);
} else if (step.includes("labels")) {
await this.updateTierLabels(plan, k8sProvider);
} else if (step.includes("limit range")) {
await this.updateLimitRange(plan, k8sProvider);
} else if (step.includes("Kubecost")) {
await this.updateKubecostConfiguration(plan);
} else {
// Generic step - simulate work
await new Promise((resolve) => setTimeout(resolve, 1000));
}
this.logger.debug(`Completed step: ${step}`);
}
/**
* Create configuration backup
*/
private async createConfigurationBackup(
companyName: string,
namespace: string
): Promise<void> {
this.logger.info(
`Creating configuration backup for ${companyName} in namespace ${namespace}`
);
// In real implementation, this would backup current Kubernetes resources
await new Promise((resolve) => setTimeout(resolve, 1000));
}
/**
* Update resource quota for new tier
*/
private async updateResourceQuota(
plan: MigrationPlan,
k8sProvider?: any
): Promise<void> {
this.logger.info(`Updating resource quota for ${plan.toTier} tier`);
if (k8sProvider) {
const newQuota = this.tierCalculator.generateResourceQuota(
getTierConfiguration(plan.toTier),
plan.namespace
);
// In real implementation, apply the new resource quota
this.logger.debug(`Generated new resource quota`, newQuota);
}
await new Promise((resolve) => setTimeout(resolve, 2000));
}
/**
* Scale services to new tier specifications
*/
private async scaleServices(
plan: MigrationPlan,
k8sProvider?: any
): Promise<void> {
this.logger.info(`Scaling services to ${plan.toTier} tier specifications`);
const targetConfig = getTierConfiguration(plan.toTier);
const services = targetConfig.resourceAllocation.services;
// In real implementation, update each service's resource allocation
for (const [serviceName, serviceConfig] of Object.entries(services)) {
this.logger.debug(
`Scaling ${serviceName} to ${serviceConfig.replicas} replicas`
);
await new Promise((resolve) => setTimeout(resolve, 500));
}
}
/**
* Update tier labels and annotations
*/
private async updateTierLabels(
plan: MigrationPlan,
k8sProvider?: any
): Promise<void> {
this.logger.info(`Updating tier labels to ${plan.toTier}`);
// In real implementation, update labels on all resources
await new Promise((resolve) => setTimeout(resolve, 1000));
}
/**
* Update limit range for new tier
*/
private async updateLimitRange(
plan: MigrationPlan,
k8sProvider?: any
): Promise<void> {
this.logger.info(`Updating limit range for ${plan.toTier} tier`);
if (k8sProvider) {
const newLimitRange = this.tierCalculator.generateLimitRange(
getTierConfiguration(plan.toTier),
plan.namespace
);
// In real implementation, apply the new limit range
this.logger.debug(`Generated new limit range`, newLimitRange);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
/**
* Update Kubecost configuration
*/
private async updateKubecostConfiguration(
plan: MigrationPlan
): Promise<void> {
this.logger.info(`Updating Kubecost configuration for ${plan.toTier} tier`);
try {
// In real implementation, update Kubecost labels and configuration
await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) {
this.logger.warn(
`Failed to update Kubecost configuration: ${
error instanceof Error ? error.message : String(error)
}`
);
}
}
/**
* Check if migration requires downtime
*/
private tierRequiresDowntime(fromTier: PlanTier, toTier: PlanTier): boolean {
const migrationType = this.getMigrationType(fromTier, toTier);
// Downgrades typically require more careful handling and potential downtime
if (migrationType === "downgrade") {
return true;
}
// Large tier jumps may require downtime
const tiers = [
PlanTier.BASIC,
PlanTier.STANDARD,
PlanTier.PREMIUM,
PlanTier.ENTERPRISE,
];
const fromIndex = tiers.indexOf(fromTier);
const toIndex = tiers.indexOf(toTier);
return Math.abs(toIndex - fromIndex) > 1;
}
/**
* Check if a tier can be upgraded
*/
canUpgradeTier(tier: PlanTier): boolean {
const tiers = [
PlanTier.BASIC,
PlanTier.STANDARD,
PlanTier.PREMIUM,
PlanTier.ENTERPRISE,
];
const currentIndex = tiers.indexOf(tier);
return currentIndex >= 0 && currentIndex < tiers.length - 1;
}
/**
* Check if a tier can be downgraded
*/
canDowngradeTier(tier: PlanTier): boolean {
const tiers = [
PlanTier.BASIC,
PlanTier.STANDARD,
PlanTier.PREMIUM,
PlanTier.ENTERPRISE,
];
const currentIndex = tiers.indexOf(tier);
return currentIndex > 0;
}
/**
* Get the next higher tier
*/
getNextTier(tier: PlanTier): PlanTier | null {
const tiers = [
PlanTier.BASIC,
PlanTier.STANDARD,
PlanTier.PREMIUM,
PlanTier.ENTERPRISE,
];
const currentIndex = tiers.indexOf(tier);
if (currentIndex >= 0 && currentIndex < tiers.length - 1) {
return tiers[currentIndex + 1];
}
return null;
}
/**
* Get the next lower tier
*/
getPreviousTier(tier: PlanTier): PlanTier | null {
const tiers = [
PlanTier.BASIC,
PlanTier.STANDARD,
PlanTier.PREMIUM,
PlanTier.ENTERPRISE,
];
const currentIndex = tiers.indexOf(tier);
if (currentIndex > 0) {
return tiers[currentIndex - 1];
}
return null;
}
/**
* Get all possible migration paths from a tier
*/
getPossibleMigrations(tier: PlanTier): Array<{
targetTier: PlanTier;
migrationType: "upgrade" | "downgrade";
estimatedDuration: string;
complexity: "low" | "medium" | "high";
}> {
const tiers = [
PlanTier.BASIC,
PlanTier.STANDARD,
PlanTier.PREMIUM,
PlanTier.ENTERPRISE,
];
const currentIndex = tiers.indexOf(tier);
const migrations: Array<{
targetTier: PlanTier;
migrationType: "upgrade" | "downgrade";
estimatedDuration: string;
complexity: "low" | "medium" | "high";
}> = [];
for (let i = 0; i < tiers.length; i++) {
if (i === currentIndex) continue;
const targetTier = tiers[i];
const migrationType = i > currentIndex ? "upgrade" : "downgrade";
const tierDifference = Math.abs(i - currentIndex);
migrations.push({
targetTier,
migrationType,
estimatedDuration: this.estimateMigrationDuration(tier, targetTier),
complexity:
tierDifference === 1
? "low"
: tierDifference === 2
? "medium"
: "high",
});
}
return migrations;
}
/**
* Determine migration type
*/
private getMigrationType(
fromTier: PlanTier,
toTier: PlanTier
): "upgrade" | "downgrade" {
const tiers = [
PlanTier.BASIC,
PlanTier.STANDARD,
PlanTier.PREMIUM,
PlanTier.ENTERPRISE,
];
const fromIndex = tiers.indexOf(fromTier);
const toIndex = tiers.indexOf(toTier);
return toIndex > fromIndex ? "upgrade" : "downgrade";
}
/**
* Estimate migration duration
*/
private estimateMigrationDuration(
fromTier: PlanTier,
toTier: PlanTier
): string {
const migrationType = this.getMigrationType(fromTier, toTier);
const tiers = [
PlanTier.BASIC,
PlanTier.STANDARD,
PlanTier.PREMIUM,
PlanTier.ENTERPRISE,
];
const tierDifference = Math.abs(
tiers.indexOf(toTier) - tiers.indexOf(fromTier)
);
// Base duration on migration type and complexity
if (migrationType === "upgrade") {
switch (tierDifference) {
case 1:
return "5-10 minutes";
case 2:
return "10-15 minutes";
case 3:
return "15-20 minutes";
default:
return "10-15 minutes";
}
} else {
// Downgrades take longer due to validation requirements
switch (tierDifference) {
case 1:
return "10-15 minutes";
case 2:
return "15-25 minutes";
case 3:
return "20-30 minutes";
default:
return "15-25 minutes";
}
}
}
}