init projectmycelium

This commit is contained in:
mik-tf
2025-09-01 21:37:01 -04:00
commit b41efb0e99
319 changed files with 128160 additions and 0 deletions

View File

@@ -0,0 +1,406 @@
//! Slice calculator service for automatic slice calculation from node capacity
//! Follows the established builder pattern for consistent API design
use crate::models::user::{NodeCapacity, FarmNode};
use rust_decimal::Decimal;
use chrono::{DateTime, Utc};
use serde::{Serialize, Deserialize};
/// Base slice unit definition (1 vCPU, 4GB RAM, 200GB storage)
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SliceUnit {
pub cpu_cores: u32, // 1
pub memory_gb: u32, // 4
pub storage_gb: u32, // 200
}
impl Default for SliceUnit {
fn default() -> Self {
Self {
cpu_cores: 1,
memory_gb: 4,
storage_gb: 200,
}
}
}
/// Calculated slice combination from node capacity
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SliceCombination {
pub id: String,
pub multiplier: u32, // How many base slices this uses
pub cpu_cores: u32, // Slice-specific resource
pub memory_gb: u32, // Slice-specific resource
pub storage_gb: u32, // Slice-specific resource
pub quantity_available: u32, // How many of this combination available
pub price_per_hour: Decimal,
pub base_slices_required: u32,
// Inherited from parent node
pub node_uptime_percentage: f64,
pub node_bandwidth_mbps: u32,
pub node_location: String,
pub node_certification_type: String,
pub node_id: String,
pub farmer_email: String,
}
/// Track individual slice rentals
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SliceAllocation {
pub allocation_id: String,
pub slice_combination_id: String,
pub renter_email: String,
pub base_slices_used: u32,
pub rental_start: DateTime<Utc>,
pub rental_end: Option<DateTime<Utc>>,
pub status: AllocationStatus,
pub monthly_cost: Decimal,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum AllocationStatus {
Active,
Expired,
Cancelled,
}
/// Pricing configuration for node slices
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SlicePricing {
pub base_price_per_hour: Decimal, // Price for 1 base slice per hour
pub currency: String,
pub pricing_multiplier: Decimal, // Farmer can adjust pricing (0.5x - 2.0x)
}
impl Default for SlicePricing {
fn default() -> Self {
Self {
base_price_per_hour: Decimal::from(1), // $1 per hour for base slice
currency: "USD".to_string(),
pricing_multiplier: Decimal::from(1),
}
}
}
/// Service for slice calculations following builder pattern
#[derive(Clone)]
pub struct SliceCalculatorService {
base_slice: SliceUnit,
pricing_limits: PricingLimits,
}
/// Platform-enforced pricing limits
#[derive(Debug, Clone)]
pub struct PricingLimits {
pub min_price_per_hour: Decimal, // e.g., $0.10
pub max_price_per_hour: Decimal, // e.g., $10.00
}
impl Default for PricingLimits {
fn default() -> Self {
Self {
min_price_per_hour: Decimal::from_str_exact("0.10").unwrap(),
max_price_per_hour: Decimal::from_str_exact("10.00").unwrap(),
}
}
}
/// Builder for SliceCalculatorService
#[derive(Default)]
pub struct SliceCalculatorServiceBuilder {
base_slice: Option<SliceUnit>,
pricing_limits: Option<PricingLimits>,
}
impl SliceCalculatorServiceBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn base_slice(mut self, base_slice: SliceUnit) -> Self {
self.base_slice = Some(base_slice);
self
}
pub fn pricing_limits(mut self, limits: PricingLimits) -> Self {
self.pricing_limits = Some(limits);
self
}
pub fn build(self) -> Result<SliceCalculatorService, String> {
Ok(SliceCalculatorService {
base_slice: self.base_slice.unwrap_or_default(),
pricing_limits: self.pricing_limits.unwrap_or_default(),
})
}
}
impl SliceCalculatorService {
pub fn builder() -> SliceCalculatorServiceBuilder {
SliceCalculatorServiceBuilder::new()
}
/// Calculate maximum base slices from node capacity
pub fn calculate_max_base_slices(&self, capacity: &NodeCapacity) -> u32 {
let cpu_slices = capacity.cpu_cores as u32 / self.base_slice.cpu_cores;
let memory_slices = capacity.memory_gb as u32 / self.base_slice.memory_gb;
let storage_slices = capacity.storage_gb as u32 / self.base_slice.storage_gb;
// Return the limiting factor
std::cmp::min(std::cmp::min(cpu_slices, memory_slices), storage_slices)
}
/// Generate all possible slice combinations from available base slices
pub fn generate_slice_combinations(
&self,
max_base_slices: u32,
allocated_slices: u32,
node: &FarmNode,
farmer_email: &str
) -> Vec<SliceCombination> {
let available_base_slices = max_base_slices.saturating_sub(allocated_slices);
let mut combinations = Vec::new();
if available_base_slices == 0 {
return combinations;
}
// Generate practical slice combinations up to a reasonable limit
// We'll generate combinations for multipliers 1x, 2x, 3x, 4x, 5x, 6x, 8x, 10x, 12x, 16x, 20x, 24x, 32x
let practical_multipliers = vec![1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32];
for multiplier in practical_multipliers {
// Skip if multiplier is larger than available slices
if multiplier > available_base_slices {
continue;
}
// Calculate how many complete units of this multiplier we can create
let quantity = available_base_slices / multiplier;
// Skip if we can't create at least one complete unit
if quantity == 0 {
continue;
}
let combination = SliceCombination {
id: format!("{}x{}", quantity, multiplier),
multiplier,
cpu_cores: self.base_slice.cpu_cores * multiplier,
memory_gb: self.base_slice.memory_gb * multiplier,
storage_gb: self.base_slice.storage_gb * multiplier,
quantity_available: quantity,
price_per_hour: self.calculate_combination_price(multiplier, node.slice_pricing.as_ref()
.and_then(|sp| serde_json::from_value(sp.clone()).ok())
.as_ref()
.unwrap_or(&crate::services::slice_calculator::SlicePricing::default())),
base_slices_required: multiplier,
// Inherited from parent node
node_uptime_percentage: node.uptime_percentage as f64,
node_bandwidth_mbps: node.capacity.bandwidth_mbps as u32,
node_location: node.location.clone(),
node_certification_type: node.grid_data.as_ref()
.map(|g| g.get("certification_type")
.and_then(|cert| cert.as_str())
.unwrap_or("DIY")
.to_string())
.unwrap_or_else(|| "DIY".to_string()),
node_id: node.id.clone(),
farmer_email: farmer_email.to_string(),
};
combinations.push(combination);
}
// Sort by multiplier (smallest slices first)
combinations.sort_by_key(|c| c.multiplier);
combinations
}
/// Generate slice combinations with explicit SLA values (for user-defined SLAs)
pub fn generate_slice_combinations_with_sla(
&self,
max_base_slices: u32,
allocated_slices: u32,
node: &FarmNode,
farmer_email: &str,
uptime_percentage: f64,
bandwidth_mbps: u32,
base_price_per_hour: Decimal
) -> Vec<SliceCombination> {
let available_base_slices = max_base_slices.saturating_sub(allocated_slices);
let mut combinations = Vec::new();
if available_base_slices == 0 {
return combinations;
}
// Generate practical slice combinations up to a reasonable limit
// We'll generate combinations for multipliers 1x, 2x, 3x, 4x, 5x, 6x, 8x, 10x, 12x, 16x, 20x, 24x, 32x
let practical_multipliers = vec![1, 2, 3, 4, 5, 6, 8, 10, 12, 16, 20, 24, 32];
// Create custom pricing with user's base price
let custom_pricing = SlicePricing {
base_price_per_hour,
currency: "USD".to_string(),
pricing_multiplier: Decimal::from(1),
};
for multiplier in practical_multipliers {
// Skip if multiplier is larger than available slices
if multiplier > available_base_slices {
continue;
}
// Calculate how many complete units of this multiplier we can create
let quantity = available_base_slices / multiplier;
// Skip if we can't create at least one complete unit
if quantity == 0 {
continue;
}
let combination = SliceCombination {
id: format!("{}x{}", quantity, multiplier),
multiplier,
cpu_cores: self.base_slice.cpu_cores * multiplier,
memory_gb: self.base_slice.memory_gb * multiplier,
storage_gb: self.base_slice.storage_gb * multiplier,
quantity_available: quantity,
price_per_hour: self.calculate_combination_price(multiplier, &custom_pricing),
base_slices_required: multiplier,
// Use explicit SLA values instead of inheriting from node
node_uptime_percentage: uptime_percentage,
node_bandwidth_mbps: bandwidth_mbps,
node_location: node.location.clone(),
node_certification_type: node.grid_data.as_ref()
.map(|g| g.get("certification_type")
.and_then(|cert| cert.as_str())
.unwrap_or("DIY")
.to_string())
.unwrap_or_else(|| "DIY".to_string()),
node_id: node.id.clone(),
farmer_email: farmer_email.to_string(),
};
combinations.push(combination);
}
// Sort by multiplier (smallest slices first)
combinations.sort_by_key(|c| c.multiplier);
combinations
}
/// Calculate price for a slice combination
fn calculate_combination_price(&self, multiplier: u32, pricing: &SlicePricing) -> Decimal {
pricing.base_price_per_hour * pricing.pricing_multiplier * Decimal::from(multiplier)
}
/// Update availability after rental
pub fn update_availability_after_rental(
&self,
node: &mut FarmNode,
rented_base_slices: u32,
farmer_email: &str
) -> Result<(), String> {
// Update allocated count
node.allocated_base_slices += rented_base_slices as i32;
// Recalculate available combinations
let combinations = self.generate_slice_combinations(
node.total_base_slices as u32,
node.allocated_base_slices as u32,
node,
farmer_email
);
node.available_combinations = combinations.iter()
.map(|c| serde_json::to_value(c).unwrap_or_default())
.collect();
Ok(())
}
/// Update availability after rental expiry
pub fn update_availability_after_release(
&self,
node: &mut FarmNode,
released_base_slices: u32,
farmer_email: &str
) -> Result<(), String> {
// Update allocated count
node.allocated_base_slices = node.allocated_base_slices.saturating_sub(released_base_slices as i32);
// Recalculate available combinations
node.available_combinations = self.generate_slice_combinations(
node.total_base_slices as u32,
node.allocated_base_slices as u32,
node,
farmer_email
).iter()
.map(|c| serde_json::to_value(c).unwrap_or_default())
.collect();
Ok(())
}
/// Validate slice price within platform limits
pub fn validate_slice_price(&self, price: Decimal) -> Result<(), String> {
if price < self.pricing_limits.min_price_per_hour {
return Err(format!("Price too low. Minimum: ${}/hour", self.pricing_limits.min_price_per_hour));
}
if price > self.pricing_limits.max_price_per_hour {
return Err(format!("Price too high. Maximum: ${}/hour", self.pricing_limits.max_price_per_hour));
}
Ok(())
}
}
/// Slice rental record for users with deployment options
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SliceRental {
pub rental_id: String,
pub slice_combination_id: String,
pub node_id: String,
pub farmer_email: String,
pub slice_allocation: SliceAllocation,
pub total_cost: Decimal,
pub payment_status: PaymentStatus,
#[serde(default)]
pub slice_format: String,
#[serde(default)]
pub user_email: String,
#[serde(default)]
pub status: String,
#[serde(default)]
pub start_date: Option<chrono::DateTime<chrono::Utc>>,
#[serde(default)]
pub rental_duration_days: Option<u32>,
#[serde(default)]
pub monthly_cost: Option<Decimal>,
#[serde(default)]
pub id: String,
// NEW: Deployment information
#[serde(default)]
pub deployment_type: Option<String>, // "vm" or "kubernetes"
#[serde(default)]
pub deployment_name: Option<String>,
#[serde(default)]
pub deployment_config: Option<serde_json::Value>,
#[serde(default)]
pub deployment_status: Option<String>, // "Provisioning", "Active", "Stopped", "Failed"
#[serde(default)]
pub deployment_endpoint: Option<String>, // Access URL/IP for the deployment
#[serde(default)]
pub deployment_metadata: Option<std::collections::HashMap<String, serde_json::Value>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PaymentStatus {
Pending,
Paid,
Failed,
Refunded,
}