Files
projectmycelium/docs/dev/design/archive/vision/parts/roadmap/deployment-automation-spec.md
2025-09-01 21:37:01 -04:00

20 KiB

ThreeFold Grid Deployment Automation Specification

Document Purpose: Technical specification for automated deployment pipeline from TFC payment to ThreeFold Grid service activation.

Last Updated: 2025-08-04
Status: Implementation Ready


Overview

This document outlines the automated deployment pipeline that converts TFC credits to TFT tokens and deploys services on the ThreeFold Grid, providing seamless Web2-to-Web3 bridge functionality.


Deployment Pipeline Architecture

graph TD
    A[User Purchase Complete] --> B[TFC Deducted]
    B --> C[Deployment Queued]
    C --> D[Commission Calculated]
    D --> E[USD → TFT Conversion]
    E --> F[Grid Node Selection]
    F --> G[Resource Allocation]
    G --> H[Service Deployment]
    H --> I[Health Check]
    I --> J{Deployment Success?}
    J -->|Yes| K[Service Active]
    J -->|No| L[Rollback & Refund]
    K --> M[User Notification]
    L --> N[Error Notification]

Core Components

1. Deployment Queue Service

// src/services/deployment_queue.rs
use tokio::sync::mpsc;
use std::collections::VecDeque;
use serde::{Deserialize, Serialize};

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DeploymentJob {
    pub id: String,
    pub user_id: String,
    pub service_spec: ServiceSpec,
    pub tfc_amount: Decimal,
    pub commission_amount: Decimal,
    pub grid_payment_amount: Decimal,
    pub priority: DeploymentPriority,
    pub created_at: DateTime<Utc>,
    pub max_retries: u32,
    pub retry_count: u32,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum DeploymentPriority {
    Low,
    Normal,
    High,
    Critical,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServiceSpec {
    pub service_type: ServiceType,
    pub cpu_cores: u32,
    pub memory_gb: u32,
    pub storage_gb: u32,
    pub network_config: NetworkConfig,
    pub environment_vars: HashMap<String, String>,
    pub docker_image: Option<String>,
    pub kubernetes_manifest: Option<String>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ServiceType {
    VM { os: String, version: String },
    Container { image: String, ports: Vec<u16> },
    KubernetesCluster { node_count: u32 },
    Storage { storage_type: String },
    Application { app_id: String },
}

pub struct DeploymentQueueService {
    queue: Arc<Mutex<VecDeque<DeploymentJob>>>,
    workers: Vec<DeploymentWorker>,
    tx: mpsc::UnboundedSender<DeploymentJob>,
    rx: Arc<Mutex<mpsc::UnboundedReceiver<DeploymentJob>>>,
}

impl DeploymentQueueService {
    pub fn new(worker_count: usize) -> Self {
        let (tx, rx) = mpsc::unbounded_channel();
        let queue = Arc::new(Mutex::new(VecDeque::new()));
        
        let mut workers = Vec::new();
        for i in 0..worker_count {
            workers.push(DeploymentWorker::new(i, rx.clone()));
        }
        
        Self {
            queue,
            workers,
            tx,
            rx: Arc::new(Mutex::new(rx)),
        }
    }
    
    pub async fn queue_deployment(&self, job: DeploymentJob) -> Result<(), DeploymentError> {
        // Add to persistent queue
        self.queue.lock().await.push_back(job.clone());
        
        // Send to worker pool
        self.tx.send(job).map_err(|_| DeploymentError::QueueFull)?;
        
        Ok(())
    }
    
    pub async fn start_workers(&self) {
        for worker in &self.workers {
            worker.start().await;
        }
    }
}

2. ThreeFold Grid Integration

// src/services/threefold_grid.rs
use reqwest::Client;
use serde_json::json;

pub struct ThreeFoldGridService {
    client: Client,
    grid_proxy_url: String,
    tft_wallet: TFTWallet,
}

impl ThreeFoldGridService {
    pub async fn deploy_vm(
        &self,
        spec: &ServiceSpec,
        tft_amount: Decimal,
    ) -> Result<GridDeployment, GridError> {
        // 1. Find suitable nodes
        let nodes = self.find_suitable_nodes(spec).await?;
        let selected_node = self.select_best_node(&nodes, spec)?;
        
        // 2. Create deployment contract
        let contract = self.create_deployment_contract(spec, &selected_node, tft_amount).await?;
        
        // 3. Deploy on grid
        let deployment = self.execute_deployment(contract).await?;
        
        // 4. Wait for deployment to be ready
        self.wait_for_deployment_ready(&deployment.id, Duration::from_secs(300)).await?;
        
        // 5. Get connection details
        let connection_info = self.get_deployment_info(&deployment.id).await?;
        
        Ok(GridDeployment {
            id: deployment.id,
            node_id: selected_node.id,
            contract_id: contract.id,
            connection_info,
            status: DeploymentStatus::Active,
            created_at: Utc::now(),
        })
    }
    
    async fn find_suitable_nodes(&self, spec: &ServiceSpec) -> Result<Vec<GridNode>, GridError> {
        let query = json!({
            "cpu": spec.cpu_cores,
            "memory": spec.memory_gb * 1024 * 1024 * 1024, // Convert GB to bytes
            "storage": spec.storage_gb * 1024 * 1024 * 1024,
            "status": "up",
            "available": true
        });
        
        let response = self.client
            .post(&format!("{}/nodes/search", self.grid_proxy_url))
            .json(&query)
            .send()
            .await?;
            
        let nodes: Vec<GridNode> = response.json().await?;
        Ok(nodes)
    }
    
    fn select_best_node(&self, nodes: &[GridNode], spec: &ServiceSpec) -> Result<GridNode, GridError> {
        // Node selection algorithm:
        // 1. Filter by resource availability
        // 2. Prefer nodes with better uptime
        // 3. Consider geographic proximity
        // 4. Balance load across nodes
        
        let suitable_nodes: Vec<_> = nodes.iter()
            .filter(|node| {
                node.available_cpu >= spec.cpu_cores &&
                node.available_memory >= spec.memory_gb * 1024 * 1024 * 1024 &&
                node.available_storage >= spec.storage_gb * 1024 * 1024 * 1024 &&
                node.uptime_percentage > 95.0
            })
            .collect();
            
        if suitable_nodes.is_empty() {
            return Err(GridError::NoSuitableNodes);
        }
        
        // Select node with best score (uptime + available resources)
        let best_node = suitable_nodes.iter()
            .max_by(|a, b| {
                let score_a = a.uptime_percentage + (a.available_cpu as f64 * 10.0);
                let score_b = b.uptime_percentage + (b.available_cpu as f64 * 10.0);
                score_a.partial_cmp(&score_b).unwrap()
            })
            .unwrap();
            
        Ok((*best_node).clone())
    }
    
    async fn create_deployment_contract(
        &self,
        spec: &ServiceSpec,
        node: &GridNode,
        tft_amount: Decimal,
    ) -> Result<DeploymentContract, GridError> {
        match &spec.service_type {
            ServiceType::VM { os, version } => {
                self.create_vm_contract(spec, node, os, version, tft_amount).await
            }
            ServiceType::Container { image, ports } => {
                self.create_container_contract(spec, node, image, ports, tft_amount).await
            }
            ServiceType::KubernetesCluster { node_count } => {
                self.create_k8s_contract(spec, node, *node_count, tft_amount).await
            }
            ServiceType::Storage { storage_type } => {
                self.create_storage_contract(spec, node, storage_type, tft_amount).await
            }
            ServiceType::Application { app_id } => {
                self.create_app_contract(spec, node, app_id, tft_amount).await
            }
        }
    }
    
    async fn create_vm_contract(
        &self,
        spec: &ServiceSpec,
        node: &GridNode,
        os: &str,
        version: &str,
        tft_amount: Decimal,
    ) -> Result<DeploymentContract, GridError> {
        let vm_config = json!({
            "type": "vm",
            "node_id": node.id,
            "cpu": spec.cpu_cores,
            "memory": spec.memory_gb,
            "storage": spec.storage_gb,
            "os": os,
            "version": version,
            "network": spec.network_config,
            "environment": spec.environment_vars,
            "payment": {
                "amount": tft_amount,
                "currency": "TFT"
            }
        });
        
        let response = self.client
            .post(&format!("{}/deployments/vm", self.grid_proxy_url))
            .json(&vm_config)
            .send()
            .await?;
            
        let contract: DeploymentContract = response.json().await?;
        Ok(contract)
    }
}

3. TFT Conversion Service

// src/services/tft_conversion.rs
use rust_decimal::Decimal;

pub struct TFTConversionService {
    grid_client: ThreeFoldGridService,
    price_oracle: TFTPriceOracle,
}

impl TFTConversionService {
    pub async fn convert_usd_to_tft(&self, usd_amount: Decimal) -> Result<TFTConversion, ConversionError> {
        // Get current TFT/USD rate
        let tft_rate = self.price_oracle.get_tft_usd_rate().await?;
        let tft_amount = usd_amount / tft_rate;
        
        // Add small buffer for price fluctuations (2%)
        let tft_with_buffer = tft_amount * Decimal::from_str("1.02")?;
        
        Ok(TFTConversion {
            usd_amount,
            tft_rate,
            tft_amount: tft_with_buffer,
            conversion_timestamp: Utc::now(),
        })
    }
    
    pub async fn execute_conversion(&self, conversion: &TFTConversion) -> Result<TFTTransaction, ConversionError> {
        // Execute the actual TFT purchase/conversion
        // This would integrate with TFT DEX or direct wallet operations
        
        let transaction = self.grid_client.tft_wallet.convert_usd_to_tft(
            conversion.usd_amount,
            conversion.tft_amount,
        ).await?;
        
        Ok(transaction)
    }
}

#[derive(Debug, Clone)]
pub struct TFTConversion {
    pub usd_amount: Decimal,
    pub tft_rate: Decimal,
    pub tft_amount: Decimal,
    pub conversion_timestamp: DateTime<Utc>,
}

pub struct TFTPriceOracle {
    client: Client,
}

impl TFTPriceOracle {
    pub async fn get_tft_usd_rate(&self) -> Result<Decimal, PriceError> {
        // Get TFT price from multiple sources and average
        let sources = vec![
            self.get_rate_from_dex().await,
            self.get_rate_from_coingecko().await,
            self.get_rate_from_grid_stats().await,
        ];
        
        let valid_rates: Vec<Decimal> = sources.into_iter()
            .filter_map(|r| r.ok())
            .collect();
            
        if valid_rates.is_empty() {
            return Err(PriceError::NoValidSources);
        }
        
        // Calculate average rate
        let sum: Decimal = valid_rates.iter().sum();
        let average = sum / Decimal::from(valid_rates.len());
        
        Ok(average)
    }
}

4. Deployment Worker

// src/services/deployment_worker.rs
pub struct DeploymentWorker {
    id: usize,
    grid_service: Arc<ThreeFoldGridService>,
    tft_service: Arc<TFTConversionService>,
    notification_service: Arc<NotificationService>,
}

impl DeploymentWorker {
    pub async fn process_deployment(&self, job: DeploymentJob) -> Result<(), DeploymentError> {
        log::info!("Worker {} processing deployment {}", self.id, job.id);
        
        // Update status to "processing"
        self.update_deployment_status(&job.id, DeploymentStatus::Processing).await?;
        
        // 1. Convert USD to TFT
        let conversion = self.tft_service.convert_usd_to_tft(job.grid_payment_amount).await?;
        let tft_transaction = self.tft_service.execute_conversion(&conversion).await?;
        
        // 2. Deploy on ThreeFold Grid
        let grid_deployment = self.grid_service.deploy_vm(&job.service_spec, conversion.tft_amount).await?;
        
        // 3. Update deployment record
        self.update_deployment_with_grid_info(&job.id, &grid_deployment, &tft_transaction).await?;
        
        // 4. Verify deployment health
        self.verify_deployment_health(&grid_deployment).await?;
        
        // 5. Update status to "active"
        self.update_deployment_status(&job.id, DeploymentStatus::Active).await?;
        
        // 6. Notify user
        self.notification_service.send_deployment_success(&job.user_id, &job.id).await?;
        
        log::info!("Worker {} completed deployment {}", self.id, job.id);
        Ok(())
    }
    
    async fn handle_deployment_failure(&self, job: &DeploymentJob, error: &DeploymentError) {
        log::error!("Deployment {} failed: {:?}", job.id, error);
        
        // Update status to failed
        self.update_deployment_status(&job.id, DeploymentStatus::Failed).await.ok();
        
        // Refund TFC credits to user
        if let Err(refund_error) = self.refund_tfc_credits(job).await {
            log::error!("Failed to refund TFC for deployment {}: {:?}", job.id, refund_error);
        }
        
        // Notify user of failure
        self.notification_service.send_deployment_failure(&job.user_id, &job.id, error).await.ok();
    }
    
    async fn refund_tfc_credits(&self, job: &DeploymentJob) -> Result<(), RefundError> {
        // Refund the full TFC amount back to user
        self.tfc_service.add_tfc_credits(
            &job.user_id,
            job.tfc_amount,
            &format!("Refund for failed deployment {}", job.id),
        ).await?;
        
        Ok(())
    }
}

Service Management Dashboard

1. Real-time Deployment Status

<!-- Deployment Status Component -->
<div class="deployment-status-card">
    <div class="card-header">
        <h5>Deployment Status</h5>
        <span class="status-badge status-{{deployment.status}}">{{deployment.status}}</span>
    </div>
    <div class="card-body">
        <div class="progress-timeline">
            <div class="timeline-step {{#if deployment.payment_completed}}completed{{/if}}">
                <i class="bi bi-credit-card"></i>
                <span>Payment Processed</span>
            </div>
            <div class="timeline-step {{#if deployment.queued}}completed{{/if}}">
                <i class="bi bi-clock"></i>
                <span>Queued for Deployment</span>
            </div>
            <div class="timeline-step {{#if deployment.converting}}completed{{/if}}">
                <i class="bi bi-arrow-left-right"></i>
                <span>Converting TFC → TFT</span>
            </div>
            <div class="timeline-step {{#if deployment.deploying}}completed{{/if}}">
                <i class="bi bi-cloud-upload"></i>
                <span>Deploying on Grid</span>
            </div>
            <div class="timeline-step {{#if deployment.active}}completed{{/if}}">
                <i class="bi bi-check-circle"></i>
                <span>Service Active</span>
            </div>
        </div>
        
        {{#if deployment.active}}
        <div class="connection-details">
            <h6>Connection Details</h6>
            <div class="detail-item">
                <label>IP Address:</label>
                <span class="copyable">{{deployment.ip_address}}</span>
            </div>
            <div class="detail-item">
                <label>SSH Access:</label>
                <code class="copyable">ssh root@{{deployment.ip_address}}</code>
            </div>
            {{#if deployment.web_url}}
            <div class="detail-item">
                <label>Web Interface:</label>
                <a href="{{deployment.web_url}}" target="_blank">{{deployment.web_url}}</a>
            </div>
            {{/if}}
        </div>
        {{/if}}
        
        <div class="deployment-metrics">
            <div class="metric">
                <label>TFC Spent:</label>
                <span>{{deployment.tfc_amount}} TFC</span>
            </div>
            <div class="metric">
                <label>TFT Converted:</label>
                <span>{{deployment.tft_amount}} TFT</span>
            </div>
            <div class="metric">
                <label>Grid Node:</label>
                <span>{{deployment.node_id}}</span>
            </div>
        </div>
    </div>
</div>

2. Service Management Controls

// Service Management Functions
class ServiceManager {
    constructor(deploymentId) {
        this.deploymentId = deploymentId;
        this.statusPolling = null;
    }
    
    startStatusPolling() {
        this.statusPolling = setInterval(async () => {
            await this.updateDeploymentStatus();
        }, 5000); // Poll every 5 seconds
    }
    
    async updateDeploymentStatus() {
        try {
            const response = await fetch(`/api/deployments/${this.deploymentId}/status`);
            const deployment = await response.json();
            
            this.updateStatusDisplay(deployment);
            
            // Stop polling if deployment is complete or failed
            if (deployment.status === 'Active' || deployment.status === 'Failed') {
                this.stopStatusPolling();
            }
        } catch (error) {
            console.error('Failed to update deployment status:', error);
        }
    }
    
    updateStatusDisplay(deployment) {
        // Update progress timeline
        document.querySelectorAll('.timeline-step').forEach((step, index) => {
            const stepStates = ['payment_completed', 'queued', 'converting', 'deploying', 'active'];
            if (deployment[stepStates[index]]) {
                step.classList.add('completed');
            }
        });
        
        // Update status badge
        const statusBadge = document.querySelector('.status-badge');
        statusBadge.className = `status-badge status-${deployment.status.toLowerCase()}`;
        statusBadge.textContent = deployment.status;
        
        // Update connection details if active
        if (deployment.status === 'Active' && deployment.connection_info) {
            this.showConnectionDetails(deployment.connection_info);
        }
    }
    
    async restartService() {
        try {
            const response = await fetch(`/api/deployments/${this.deploymentId}/restart`, {
                method: 'POST'
            });
            
            if (response.ok) {
                showSuccessToast('Service restart initiated');
                this.startStatusPolling();
            } else {
                showErrorToast('Failed to restart service');
            }
        } catch (error) {
            showErrorToast('Failed to restart service');
        }
    }
    
    async stopService() {
        if (confirm('Are you sure you want to stop this service? This action cannot be undone.')) {
            try {
                const response = await fetch(`/api/deployments/${this.deploymentId}/stop`, {
                    method: 'POST'
                });
                
                if (response.ok) {
                    showSuccessToast('Service stopped successfully');
                    window.location.href = '/dashboard/deployments';
                } else {
                    showErrorToast('Failed to stop service');
                }
            } catch (error) {
                showErrorToast('Failed to stop service');
            }
        }
    }
}

Configuration & Environment

# config/deployment.toml
[deployment_queue]
worker_count = 4
max_queue_size = 1000
retry_attempts = 3
retry_delay_seconds = 30

[threefold_grid]
grid_proxy_url = "https://gridproxy.grid.tf"
substrate_url = "wss://tfchain.grid.tf/ws"
relay_url = "wss://relay.grid.tf"

[tft_conversion]
price_sources = ["dex", "coingecko", "grid_stats"]
conversion_buffer_percent = 2.0
max_slippage_percent = 5.0

[notifications]
email_enabled = true
webhook_enabled = true
slack_enabled = false

This deployment automation specification provides the complete technical foundation for seamless TFC-to-Grid deployment automation, ensuring reliable and scalable service provisioning on the ThreeFold Grid.