Skip to content

Multi-Tenant SaaS

Build scalable multi-tenant SaaS applications with complete tenant isolation

⏱️ Time: 30 minutes | 💡 Difficulty: Advanced

What You'll Learn

  • Design multi-tenant architecture with Nexus
  • Implement complete tenant isolation
  • Manage per-tenant workspaces and permissions
  • Handle cross-tenant data sharing securely
  • Scale to thousands of tenants
  • Implement tenant lifecycle management
  • Monitor tenant usage and quotas
  • Build production-ready SaaS infrastructure

Prerequisites

✅ Python 3.8+ installed ✅ Nexus installed (pip install nexus-ai-fs) ✅ Understanding of team collaboration (Team Collaboration) ✅ Familiarity with ReBAC permissions ✅ Basic knowledge of SaaS architecture concepts

📝 API Note: This tutorial uses Nexus's ReBAC (Relationship-Based Access Control) API for permissions. Admin operations use the RPC interface via _call_rpc(). User accounts are created implicitly when their first API key is generated.

Overview

Multi-tenant SaaS applications serve multiple customers (tenants) from a single infrastructure while ensuring complete data isolation and independent tenant management. Nexus provides built-in multi-tenancy support with ReBAC for fine-grained access control.

Use Cases: - 🏢 B2B SaaS platforms - 📊 Analytics-as-a-Service - 🤖 AI/ML platforms serving multiple customers - 📝 Document management systems - 💼 Enterprise collaboration tools - 🔐 Secure file sharing platforms

Multi-Tenant Architecture:

┌────────────────────────────────────────────────────────────┐
│                  SaaS Application Layer                    │
│  ┌──────────┐  ┌──────────┐  ┌──────────┐                │
│  │ Tenant A │  │ Tenant B │  │ Tenant C │                │
│  │ (Acme)   │  │ (Beta)   │  │ (Corp)   │                │
│  └────┬─────┘  └────┬─────┘  └────┬─────┘                │
└───────┼─────────────┼─────────────┼────────────────────────┘
        │             │             │ Isolated API Access
        └─────────────┼─────────────┘
┌────────────────────────────────────────────────────────────┐
│              Nexus Multi-Tenant Server                     │
│  ┌──────────────────────────────────────────────────────┐ │
│  │         Tenant Isolation & Authorization             │ │
│  │  ┌────────────┐  ┌────────────┐  ┌────────────┐    │ │
│  │  │  Tenant    │  │   ReBAC    │  │   Quota    │    │ │
│  │  │  Manager   │  │  Enforcer  │  │  Manager   │    │ │
│  │  └────────────┘  └────────────┘  └────────────┘    │ │
│  └────────────────────┬─────────────────────────────────┘ │
│         ↓             ↓              ↓                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │   Tenant A   │  │   Tenant B   │  │   Tenant C   │   │
│  │  Workspace   │  │  Workspace   │  │  Workspace   │   │
│  │              │  │              │  │              │   │
│  │  /tenants/   │  │  /tenants/   │  │  /tenants/   │   │
│  │    acme/     │  │    beta/     │  │    corp/     │   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
└────────────────────────────────────────────────────────────┘

Step 1: Start Multi-Tenant Nexus Server

Start a Nexus server configured for multi-tenancy:

# Start server with multi-tenant configuration
nexus serve \
  --host 0.0.0.0 \
  --port 2026 \
  --data-dir ./nexus-saas-data \
  --database-url "postgresql://postgres:nexus@localhost/nexus" \
  &

# Wait for server to start
sleep 3

# Verify server is running
curl http://localhost:2026/health

Expected output:

{"status":"ok","version":"0.5.0","mode":"multi-tenant"}

💡 Pro Tip: Use PostgreSQL for production multi-tenant deployments to handle thousands of tenants efficiently.


Step 2: Create Tenant Management System

Build a tenant management system:

Important Note: This example uses Nexus's internal RPC API via _call_rpc() for admin operations and the ReBAC API for permissions. The admin operations create API keys which implicitly create user accounts.

# tenant_manager.py
import nexus
import json
from datetime import datetime
from typing import Dict, List, Optional

class TenantManager:
    """Manage multi-tenant SaaS tenants"""

    def __init__(self, admin_api_key: str, server_url: str = "http://localhost:2026"):
        self.server_url = server_url
        self.admin = nexus.connect(config={
            "url": server_url,
            "api_key": admin_api_key
        })

    def create_tenant(
        self,
        tenant_id: str,
        name: str,
        plan: str = "free",
        max_users: int = 5,
        max_storage_gb: int = 10
    ) -> Dict:
        """Create a new tenant with isolated workspace"""

        # Create tenant workspace
        tenant_path = f"/tenants/{tenant_id}"
        self.admin.mkdir(tenant_path)

        # Create tenant metadata
        metadata = {
            "tenant_id": tenant_id,
            "name": name,
            "plan": plan,
            "created_at": datetime.now().isoformat(),
            "status": "active",
            "limits": {
                "max_users": max_users,
                "max_storage_gb": max_storage_gb,
                "max_files": 10000 if plan == "free" else None
            },
            "usage": {
                "users": 0,
                "storage_gb": 0,
                "files": 0
            }
        }

        # Save metadata
        self.admin.write(
            f"{tenant_path}/.tenant.json",
            json.dumps(metadata, indent=2).encode()
        )

        # Create standard tenant directories
        for subdir in ["users", "shared", "uploads", "exports"]:
            self.admin.mkdir(f"{tenant_path}/{subdir}")

        print(f"✅ Created tenant: {name} ({tenant_id})")
        print(f"   Plan: {plan}, Max Users: {max_users}, Storage: {max_storage_gb}GB")

        return metadata

    def create_tenant_admin(self, tenant_id: str, username: str, email: str) -> Dict:
        """Create admin user for a tenant"""

        # Create user account and API key
        # Note: In Nexus, users are created implicitly when their first API key is generated
        result = self.admin._call_rpc("admin_create_key", {
            "user_id": username,
            "name": f"{username} (Tenant Admin)",
            "subject_type": "user",
            "tenant_id": tenant_id,
            "is_admin": False,  # Tenant admin, not system admin
        })

        api_key = result["api_key"]

        # Grant tenant admin full access to tenant workspace
        tenant_path = f"/tenants/{tenant_id}"
        self.admin.rebac_create(
            subject=("user", username),
            relation="owner",
            object=("file", tenant_path),
            tenant_id=tenant_id
        )

        print(f"✅ Created tenant admin: {username}")
        print(f"   Tenant: {tenant_id}, Email: {email}")

        return {
            "username": username,
            "email": email,
            "tenant_id": tenant_id,
            "api_key": api_key,
            "role": "tenant_admin"
        }

    def add_tenant_user(
        self,
        tenant_id: str,
        username: str,
        email: str,
        role: str = "user"
    ) -> Dict:
        """Add a user to a tenant"""

        # Create user account and API key
        result = self.admin._call_rpc("admin_create_key", {
            "user_id": username,
            "name": username,
            "subject_type": "user",
            "tenant_id": tenant_id,
            "is_admin": False,
        })

        api_key = result["api_key"]

        # Grant appropriate permissions based on role
        tenant_path = f"/tenants/{tenant_id}"

        if role == "admin":
            relation = "owner"
        elif role == "editor":
            relation = "can_write"
        else:  # role == "user"
            relation = "can_read"

        self.admin.rebac_create(
            subject=("user", username),
            relation=relation,
            object=("file", f"{tenant_path}/shared"),
            tenant_id=tenant_id
        )

        # Grant user access to their personal folder
        user_folder = f"{tenant_path}/users/{username}"
        self.admin.mkdir(user_folder)
        self.admin.rebac_create(
            subject=("user", username),
            relation="owner",
            object=("file", user_folder),
            tenant_id=tenant_id
        )

        print(f"✅ Added user: {username} (role: {role})")

        # Update tenant user count
        self._update_tenant_usage(tenant_id, users_delta=1)

        return {
            "username": username,
            "email": email,
            "tenant_id": tenant_id,
            "role": role,
            "api_key": api_key
        }

    def get_tenant_info(self, tenant_id: str) -> Dict:
        """Get tenant information and usage"""
        metadata = json.loads(
            self.admin.read(f"/tenants/{tenant_id}/.tenant.json").decode()
        )
        return metadata

    def list_tenants(self) -> List[Dict]:
        """List all tenants"""
        tenant_dirs = self.admin.list("/tenants", recursive=False)

        tenants = []
        for tenant_dir in tenant_dirs:
            tenant_id = tenant_dir['path'].split('/')[-1]
            try:
                info = self.get_tenant_info(tenant_id)
                tenants.append(info)
            except:
                pass

        return tenants

    def _update_tenant_usage(self, tenant_id: str, **kwargs):
        """Update tenant usage metrics"""
        metadata = self.get_tenant_info(tenant_id)

        for key, value in kwargs.items():
            if key.endswith('_delta'):
                metric = key.replace('_delta', '')
                if metric in metadata['usage']:
                    metadata['usage'][metric] += value

        self.admin.write(
            f"/tenants/{tenant_id}/.tenant.json",
            json.dumps(metadata, indent=2).encode()
        )

    def check_quota(self, tenant_id: str, resource: str, amount: int = 1) -> bool:
        """Check if tenant is within quota limits"""
        metadata = self.get_tenant_info(tenant_id)

        limits = metadata['limits']
        usage = metadata['usage']

        if resource == 'users':
            return usage['users'] + amount <= limits['max_users']
        elif resource == 'storage_gb':
            return usage['storage_gb'] + amount <= limits['max_storage_gb']
        elif resource == 'files':
            max_files = limits.get('max_files')
            if max_files is None:
                return True
            return usage['files'] + amount <= max_files

        return True

# Demo usage
if __name__ == "__main__":
    # Admin API key (replace with actual key)
    ADMIN_KEY = "admin_key_here"

    # Initialize tenant manager
    tm = TenantManager(ADMIN_KEY)

    # Create tenants
    acme_tenant = tm.create_tenant(
        tenant_id="acme",
        name="Acme Corporation",
        plan="enterprise",
        max_users=100,
        max_storage_gb=1000
    )

    beta_tenant = tm.create_tenant(
        tenant_id="beta",
        name="Beta Inc",
        plan="pro",
        max_users=50,
        max_storage_gb=500
    )

    startup_tenant = tm.create_tenant(
        tenant_id="startup",
        name="Startup Co",
        plan="free",
        max_users=5,
        max_storage_gb=10
    )

    # Create tenant admins
    acme_admin = tm.create_tenant_admin(
        tenant_id="acme",
        username="alice",
        email="alice@acme.com"
    )

    beta_admin = tm.create_tenant_admin(
        tenant_id="beta",
        username="bob",
        email="bob@beta.com"
    )

    # Add users to tenants
    tm.add_tenant_user(
        tenant_id="acme",
        username="charlie",
        email="charlie@acme.com",
        role="editor"
    )

    tm.add_tenant_user(
        tenant_id="acme",
        username="diana",
        email="diana@acme.com",
        role="user"
    )

    # List all tenants
    print("\n📋 All Tenants:")
    for tenant in tm.list_tenants():
        print(f"  - {tenant['name']} ({tenant['tenant_id']})")
        print(f"    Plan: {tenant['plan']}, Users: {tenant['usage']['users']}/{tenant['limits']['max_users']}")

Run it:

python tenant_manager.py

Step 3: Implement Tenant Isolation

Ensure complete data isolation between tenants:

# tenant_isolation_demo.py
import nexus

# Acme tenant user (Alice)
alice = nexus.connect(config={
    "url": "http://localhost:2026",
    "api_key": "alice_key_here"
})

# Beta tenant user (Bob)
bob = nexus.connect(config={
    "url": "http://localhost:2026",
    "api_key": "bob_key_here"
})

# Alice creates files in Acme tenant workspace
alice.write(
    "/tenants/acme/shared/project-plan.md",
    b"""# Acme Project Plan

## Q1 Goals
- Launch new product
- Expand to EMEA
- Hire 10 engineers

CONFIDENTIAL - Acme Corporation
"""
)

print("✅ Alice created Acme project plan")

# Alice creates personal files
alice.write(
    "/tenants/acme/users/alice/notes.md",
    b"Personal notes for Alice - Acme internal"
)

print("✅ Alice created personal notes")

# Bob creates files in Beta tenant workspace
bob.write(
    "/tenants/beta/shared/roadmap.md",
    b"""# Beta Product Roadmap

## Features
- AI integration
- Mobile app
- API v2

CONFIDENTIAL - Beta Inc
"""
)

print("✅ Bob created Beta roadmap")

# Verify isolation: Bob CANNOT access Acme files
print("\n🔒 Testing Tenant Isolation:")

try:
    # Bob tries to read Acme files
    acme_file = bob.read("/tenants/acme/shared/project-plan.md")
    print("❌ SECURITY BREACH: Bob accessed Acme files!")
except nexus.NexusPermissionError:
    print("✅ Isolation working: Bob cannot access Acme files")

try:
    # Alice tries to read Beta files
    beta_file = alice.read("/tenants/beta/shared/roadmap.md")
    print("❌ SECURITY BREACH: Alice accessed Beta files!")
except nexus.NexusPermissionError:
    print("✅ Isolation working: Alice cannot access Beta files")

# Verify: Users can only see their tenant's files
alice_files = alice.list("/tenants/acme", recursive=True)
print(f"\n📁 Alice can see {len(alice_files)} Acme files:")
for f in alice_files[:5]:
    print(f"  - {f['path']}")

bob_files = bob.list("/tenants/beta", recursive=True)
print(f"\n📁 Bob can see {len(bob_files)} Beta files:")
for f in bob_files[:5]:
    print(f"  - {f['path']}")

print("\n✅ Complete tenant isolation verified!")

Step 4: Cross-Tenant Data Sharing (Controlled)

Implement secure cross-tenant sharing:

# cross_tenant_sharing.py
import nexus
import json
from datetime import datetime, timedelta

class CrossTenantSharing:
    """Manage secure cross-tenant data sharing"""

    def __init__(self, admin_api_key: str):
        self.admin = nexus.connect(config={
            "url": "http://localhost:2026",
            "api_key": admin_api_key
        })

    def create_shared_link(
        self,
        tenant_id: str,
        file_path: str,
        target_tenant_id: str,
        permission: str = "can_read",
        expires_in_days: int = 7
    ) -> Dict:
        """Create a secure cross-tenant share"""

        # Create share metadata
        share_id = f"share_{datetime.now().timestamp()}"
        expires_at = datetime.now() + timedelta(days=expires_in_days)

        share_metadata = {
            "share_id": share_id,
            "source_tenant": tenant_id,
            "target_tenant": target_tenant_id,
            "file_path": file_path,
            "permission": permission,
            "created_at": datetime.now().isoformat(),
            "expires_at": expires_at.isoformat(),
            "status": "active"
        }

        # Grant temporary cross-tenant permission
        # Create a symlink or reference in target tenant's shared folder
        target_path = f"/tenants/{target_tenant_id}/shared/from-{tenant_id}"
        self.admin.mkdir(target_path)

        # Copy file to shared location (or create reference)
        shared_file_path = f"{target_path}/{file_path.split('/')[-1]}"

        # Grant target tenant read access
        # Note: Grant to all users in target tenant by using a group or specific users
        # For simplicity, we'll create a share that specific users can access
        self.admin.rebac_create(
            subject=("tenant", target_tenant_id),
            relation=permission,
            object=("file", shared_file_path),
            tenant_id=tenant_id
        )

        # Store share metadata
        self.admin.write(
            f"{target_path}/.share-{share_id}.json",
            json.dumps(share_metadata, indent=2).encode()
        )

        print(f"✅ Created cross-tenant share: {share_id}")
        print(f"   {tenant_id}{target_tenant_id}")
        print(f"   File: {file_path}")
        print(f"   Permission: {permission}")
        print(f"   Expires: {expires_at.date()}")

        return share_metadata

    def revoke_share(self, share_id: str):
        """Revoke a cross-tenant share"""
        # Find and delete share
        # Remove permissions
        # Clean up shared file
        print(f"✅ Revoked share: {share_id}")

    def list_active_shares(self, tenant_id: str) -> List[Dict]:
        """List all active shares for a tenant"""
        # Implementation would scan for share metadata
        pass

# Usage example
sharing = CrossTenantSharing("admin_key_here")

# Acme shares a file with Beta (partnership)
share = sharing.create_shared_link(
    tenant_id="acme",
    file_path="/tenants/acme/shared/partnership-proposal.pdf",
    target_tenant_id="beta",
    permission="can_read",
    expires_in_days=30
)

Step 5: Implement Tenant Quotas and Billing

Track usage and enforce limits:

# tenant_quotas.py
import nexus
import json
from typing import Dict

class TenantQuotaManager:
    """Manage tenant quotas and usage tracking"""

    def __init__(self, admin_api_key: str):
        self.admin = nexus.connect(config={
            "url": "http://localhost:2026",
            "api_key": admin_api_key
        })

    def check_and_enforce_quota(
        self,
        tenant_id: str,
        operation: str,
        amount: int = 1
    ) -> bool:
        """Check quota before allowing operation"""

        # Get tenant metadata
        metadata = json.loads(
            self.admin.read(f"/tenants/{tenant_id}/.tenant.json").decode()
        )

        limits = metadata['limits']
        usage = metadata['usage']

        # Check specific quota
        if operation == "create_user":
            if usage['users'] >= limits['max_users']:
                raise QuotaExceededError(
                    f"Tenant {tenant_id} has reached user limit: {limits['max_users']}"
                )

        elif operation == "upload_file":
            if limits.get('max_files') and usage['files'] >= limits['max_files']:
                raise QuotaExceededError(
                    f"Tenant {tenant_id} has reached file limit: {limits['max_files']}"
                )

        elif operation == "storage":
            if usage['storage_gb'] + (amount / 1024**3) > limits['max_storage_gb']:
                raise QuotaExceededError(
                    f"Tenant {tenant_id} would exceed storage limit: {limits['max_storage_gb']}GB"
                )

        return True

    def update_usage(self, tenant_id: str, metric: str, delta: float):
        """Update tenant usage metrics"""
        metadata = json.loads(
            self.admin.read(f"/tenants/{tenant_id}/.tenant.json").decode()
        )

        metadata['usage'][metric] += delta

        self.admin.write(
            f"/tenants/{tenant_id}/.tenant.json",
            json.dumps(metadata, indent=2).encode()
        )

    def get_usage_report(self, tenant_id: str) -> Dict:
        """Generate usage report for billing"""
        metadata = json.loads(
            self.admin.read(f"/tenants/{tenant_id}/.tenant.json").decode()
        )

        usage = metadata['usage']
        limits = metadata['limits']

        return {
            "tenant_id": tenant_id,
            "plan": metadata['plan'],
            "usage": usage,
            "limits": limits,
            "utilization": {
                "users": f"{usage['users']}/{limits['max_users']} ({usage['users']/limits['max_users']*100:.1f}%)",
                "storage": f"{usage['storage_gb']:.2f}/{limits['max_storage_gb']}GB ({usage['storage_gb']/limits['max_storage_gb']*100:.1f}%)",
            }
        }

    def calculate_overage_charges(self, tenant_id: str) -> Dict:
        """Calculate overage charges for billing"""
        report = self.get_usage_report(tenant_id)

        charges = {
            "base_charge": 0,
            "overage_charges": {},
            "total": 0
        }

        # Example pricing
        plan_prices = {
            "free": 0,
            "pro": 99,
            "enterprise": 499
        }

        charges["base_charge"] = plan_prices.get(report['plan'], 0)

        # Calculate overages (simplified)
        usage = report['usage']
        limits = report['limits']

        if usage['users'] > limits['max_users']:
            overage = usage['users'] - limits['max_users']
            charges["overage_charges"]["users"] = overage * 10  # $10 per extra user

        if usage['storage_gb'] > limits['max_storage_gb']:
            overage = usage['storage_gb'] - limits['max_storage_gb']
            charges["overage_charges"]["storage"] = overage * 0.50  # $0.50 per extra GB

        charges["total"] = charges["base_charge"] + sum(charges["overage_charges"].values())

        return charges

class QuotaExceededError(Exception):
    pass

# Usage
quota_mgr = TenantQuotaManager("admin_key_here")

# Check quota before operation
try:
    quota_mgr.check_and_enforce_quota("acme", "create_user")
    # Proceed with user creation...
    quota_mgr.update_usage("acme", "users", 1)
except QuotaExceededError as e:
    print(f"❌ {e}")

# Generate usage report
report = quota_mgr.get_usage_report("acme")
print(f"\n📊 Acme Usage Report:")
print(f"  Users: {report['utilization']['users']}")
print(f"  Storage: {report['utilization']['storage']}")

# Calculate charges
charges = quota_mgr.calculate_overage_charges("acme")
print(f"\n💰 Acme Billing:")
print(f"  Base: ${charges['base_charge']}")
if charges['overage_charges']:
    print(f"  Overages: ${sum(charges['overage_charges'].values()):.2f}")
print(f"  Total: ${charges['total']:.2f}")

Step 6: Tenant Lifecycle Management

Manage tenant creation, suspension, and deletion:

# tenant_lifecycle.py
import nexus
import json
from datetime import datetime
from typing import Dict

class TenantLifecycle:
    """Manage complete tenant lifecycle"""

    def __init__(self, admin_api_key: str):
        self.admin = nexus.connect(config={
            "url": "http://localhost:2026",
            "api_key": admin_api_key
        })

    def suspend_tenant(self, tenant_id: str, reason: str):
        """Suspend a tenant (maintain data, block access)"""

        # Update tenant status
        metadata = json.loads(
            self.admin.read(f"/tenants/{tenant_id}/.tenant.json").decode()
        )

        metadata['status'] = 'suspended'
        metadata['suspended_at'] = datetime.now().isoformat()
        metadata['suspension_reason'] = reason

        self.admin.write(
            f"/tenants/{tenant_id}/.tenant.json",
            json.dumps(metadata, indent=2).encode()
        )

        # Revoke all user access (but keep data)
        users = self._get_tenant_users(tenant_id)
        for user in users:
            self._revoke_user_access(user, tenant_id)

        print(f"⏸️  Suspended tenant: {tenant_id}")
        print(f"   Reason: {reason}")

    def reactivate_tenant(self, tenant_id: str):
        """Reactivate a suspended tenant"""

        metadata = json.loads(
            self.admin.read(f"/tenants/{tenant_id}/.tenant.json").decode()
        )

        metadata['status'] = 'active'
        metadata['reactivated_at'] = datetime.now().isoformat()

        self.admin.write(
            f"/tenants/{tenant_id}/.tenant.json",
            json.dumps(metadata, indent=2).encode()
        )

        # Restore user access
        users = self._get_tenant_users(tenant_id)
        for user in users:
            self._restore_user_access(user, tenant_id)

        print(f"▶️  Reactivated tenant: {tenant_id}")

    def export_tenant_data(self, tenant_id: str) -> str:
        """Export all tenant data for backup or migration"""

        tenant_path = f"/tenants/{tenant_id}"
        export_path = f"/exports/{tenant_id}-{datetime.now().strftime('%Y%m%d')}.tar.gz"

        # In production, this would create a compressed archive
        # For demo, we'll list what would be exported

        files = self.admin.list(tenant_path, recursive=True)

        print(f"📦 Exporting tenant data: {tenant_id}")
        print(f"   Total files: {len(files)}")
        print(f"   Export path: {export_path}")

        return export_path

    def delete_tenant(self, tenant_id: str, confirm: bool = False):
        """Permanently delete a tenant and all data"""

        if not confirm:
            print("⚠️  DANGER: This will permanently delete all tenant data!")
            print("   Call with confirm=True to proceed")
            return

        # Delete all tenant data
        tenant_path = f"/tenants/{tenant_id}"

        # Remove all permissions
        # Delete all user accounts
        # Delete tenant workspace

        self.admin.rmdir(tenant_path, recursive=True)

        print(f"🗑️  Deleted tenant: {tenant_id}")
        print("   All data permanently removed")

    def _get_tenant_users(self, tenant_id: str) -> list:
        """Get all users in a tenant"""
        # Implementation would query user database
        return []

    def _revoke_user_access(self, user: str, tenant_id: str):
        """Revoke user access to tenant"""
        pass

    def _restore_user_access(self, user: str, tenant_id: str):
        """Restore user access to tenant"""
        pass

# Usage
lifecycle = TenantLifecycle("admin_key_here")

# Suspend tenant for non-payment
lifecycle.suspend_tenant("startup", "Payment overdue")

# Export data before deletion
lifecycle.export_tenant_data("startup")

# Delete tenant (requires confirmation)
lifecycle.delete_tenant("startup", confirm=True)

Step 7: Scaling to Thousands of Tenants

Optimize for scale:

# scalable_saas.py
import nexus
from typing import Dict, List
import asyncio
from concurrent.futures import ThreadPoolExecutor

class ScalableSaaS:
    """Production-ready multi-tenant SaaS implementation"""

    def __init__(self, admin_api_key: str, max_workers: int = 10):
        self.admin = nexus.connect(config={
            "url": "http://localhost:2026",
            "api_key": admin_api_key
        })
        self.executor = ThreadPoolExecutor(max_workers=max_workers)

    def bulk_create_tenants(self, tenant_configs: List[Dict]):
        """Create multiple tenants in parallel"""

        def create_single_tenant(config):
            try:
                return self._create_tenant(**config)
            except Exception as e:
                return {"error": str(e), "config": config}

        # Process tenants in parallel
        with ThreadPoolExecutor(max_workers=10) as executor:
            results = list(executor.map(create_single_tenant, tenant_configs))

        successful = [r for r in results if "error" not in r]
        failed = [r for r in results if "error" in r]

        print(f"✅ Created {len(successful)} tenants")
        if failed:
            print(f"❌ Failed to create {len(failed)} tenants")

        return {"successful": successful, "failed": failed}

    def _create_tenant(self, tenant_id: str, **kwargs) -> Dict:
        """Create a single tenant"""
        # Implementation from TenantManager
        pass

    def get_tenant_health_metrics(self) -> Dict:
        """Get system-wide tenant health metrics"""

        all_tenants = self._list_all_tenants()

        metrics = {
            "total_tenants": len(all_tenants),
            "by_plan": {},
            "by_status": {},
            "total_users": 0,
            "total_storage_gb": 0,
            "avg_users_per_tenant": 0,
            "avg_storage_per_tenant": 0
        }

        for tenant in all_tenants:
            # Count by plan
            plan = tenant.get('plan', 'unknown')
            metrics['by_plan'][plan] = metrics['by_plan'].get(plan, 0) + 1

            # Count by status
            status = tenant.get('status', 'unknown')
            metrics['by_status'][status] = metrics['by_status'].get(status, 0) + 1

            # Aggregate usage
            usage = tenant.get('usage', {})
            metrics['total_users'] += usage.get('users', 0)
            metrics['total_storage_gb'] += usage.get('storage_gb', 0)

        if metrics['total_tenants'] > 0:
            metrics['avg_users_per_tenant'] = metrics['total_users'] / metrics['total_tenants']
            metrics['avg_storage_per_tenant'] = metrics['total_storage_gb'] / metrics['total_tenants']

        return metrics

    def _list_all_tenants(self) -> List[Dict]:
        """List all tenants efficiently"""
        # Implementation would use optimized queries
        return []

    def implement_tenant_caching(self):
        """Implement caching for tenant metadata"""

        # Use Redis or similar for caching tenant info
        # Cache tenant permissions
        # Cache quota information

        print("✅ Tenant caching enabled")
        print("   - Metadata cache: Redis")
        print("   - Permission cache: In-memory LRU")
        print("   - Quota cache: 5-minute TTL")

# Usage
saas = ScalableSaaS("admin_key_here", max_workers=20)

# Bulk create 100 tenants
tenant_configs = [
    {"tenant_id": f"tenant{i:03d}", "name": f"Tenant {i}", "plan": "free"}
    for i in range(100)
]

results = saas.bulk_create_tenants(tenant_configs)

# Get health metrics
metrics = saas.get_tenant_health_metrics()
print(f"\n📊 SaaS Platform Health:")
print(f"  Total Tenants: {metrics['total_tenants']}")
print(f"  Total Users: {metrics['total_users']}")
print(f"  Total Storage: {metrics['total_storage_gb']:.2f}GB")
print(f"  Avg Users/Tenant: {metrics['avg_users_per_tenant']:.1f}")

Complete Production Example

Here's a full multi-tenant SaaS implementation:

#!/usr/bin/env python3
"""
Production Multi-Tenant SaaS Platform
Complete implementation with isolation, quotas, and lifecycle management
"""
import nexus
import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional
from enum import Enum

class TenantPlan(Enum):
    FREE = "free"
    PRO = "pro"
    ENTERPRISE = "enterprise"

class TenantStatus(Enum):
    ACTIVE = "active"
    SUSPENDED = "suspended"
    TRIAL = "trial"
    CANCELLED = "cancelled"

class MultiTenantSaaS:
    """Complete multi-tenant SaaS platform"""

    def __init__(self, server_url: str, admin_api_key: str):
        self.server_url = server_url
        self.admin = nexus.connect(config={
            "url": server_url,
            "api_key": admin_api_key
        })

        # Plan configurations
        self.plan_limits = {
            TenantPlan.FREE: {
                "max_users": 5,
                "max_storage_gb": 10,
                "max_files": 1000,
                "features": ["basic_storage", "basic_sharing"]
            },
            TenantPlan.PRO: {
                "max_users": 50,
                "max_storage_gb": 500,
                "max_files": 50000,
                "features": ["advanced_storage", "advanced_sharing", "api_access"]
            },
            TenantPlan.ENTERPRISE: {
                "max_users": None,  # Unlimited
                "max_storage_gb": None,  # Unlimited
                "max_files": None,  # Unlimited
                "features": ["all", "custom_integrations", "dedicated_support"]
            }
        }

    def onboard_tenant(
        self,
        tenant_id: str,
        name: str,
        admin_email: str,
        plan: TenantPlan = TenantPlan.FREE
    ) -> Dict:
        """Complete tenant onboarding workflow"""

        print(f"\n🚀 Starting tenant onboarding: {name}")

        # Step 1: Create tenant workspace
        tenant_path = f"/tenants/{tenant_id}"
        self.admin.mkdir(tenant_path)
        print(f"  ✅ Created tenant workspace")

        # Step 2: Set up directory structure
        for subdir in ["users", "shared", "uploads", "templates", "exports"]:
            self.admin.mkdir(f"{tenant_path}/{subdir}")
        print(f"  ✅ Created directory structure")

        # Step 3: Create tenant metadata
        limits = self.plan_limits[plan]
        metadata = {
            "tenant_id": tenant_id,
            "name": name,
            "plan": plan.value,
            "status": TenantStatus.TRIAL.value,
            "created_at": datetime.now().isoformat(),
            "trial_ends_at": (datetime.now() + timedelta(days=14)).isoformat(),
            "limits": limits,
            "usage": {
                "users": 0,
                "storage_gb": 0,
                "files": 0,
                "api_calls": 0
            },
            "settings": {
                "allow_public_sharing": False,
                "enforce_2fa": False,
                "data_retention_days": 90
            }
        }

        self.admin.write(
            f"{tenant_path}/.tenant.json",
            json.dumps(metadata, indent=2).encode()
        )
        print(f"  ✅ Saved tenant metadata")

        # Step 4: Create admin user
        admin_username = f"{tenant_id}_admin"
        result = self.admin._call_rpc("admin_create_key", {
            "user_id": admin_username,
            "name": f"{name} Admin",
            "subject_type": "user",
            "tenant_id": tenant_id,
            "is_admin": False,
        })

        api_key = result["api_key"]

        # Grant admin full access
        self.admin.rebac_create(
            subject=("user", admin_username),
            relation="owner",
            object=("file", tenant_path),
            tenant_id=tenant_id
        )
        print(f"  ✅ Created admin user: {admin_email}")

        # Step 5: Copy templates
        self._copy_welcome_templates(tenant_id)
        print(f"  ✅ Copied welcome templates")

        # Step 6: Send welcome email (simulated)
        self._send_welcome_email(name, admin_email, api_key)
        print(f"  ✅ Sent welcome email")

        print(f"\n✅ Tenant onboarding complete!")
        print(f"   Tenant ID: {tenant_id}")
        print(f"   Plan: {plan.value}")
        print(f"   Trial ends: {metadata['trial_ends_at']}")

        return {
            "tenant_id": tenant_id,
            "admin_username": admin_username,
            "api_key": api_key,
            "metadata": metadata
        }

    def _copy_welcome_templates(self, tenant_id: str):
        """Copy welcome templates to new tenant"""
        welcome_doc = b"""# Welcome to Your Workspace!

## Getting Started

1. Invite your team members
2. Upload your files
3. Start collaborating

## Features Available

- Secure file storage
- Real-time collaboration
- Version history
- Access control

Need help? Contact support@example.com
"""
        self.admin.write(
            f"/tenants/{tenant_id}/shared/Welcome.md",
            welcome_doc
        )

    def _send_welcome_email(self, tenant_name: str, email: str, api_key: str):
        """Send welcome email (simulated)"""
        # In production: use SendGrid, AWS SES, etc.
        print(f"\n📧 Email to {email}:")
        print(f"   Subject: Welcome to {tenant_name}!")
        print(f"   API Key: {api_key[:20]}...")

    def upgrade_tenant(self, tenant_id: str, new_plan: TenantPlan) -> Dict:
        """Upgrade tenant to a new plan"""

        metadata = self._get_tenant_metadata(tenant_id)

        old_plan = TenantPlan(metadata['plan'])
        metadata['plan'] = new_plan.value
        metadata['limits'] = self.plan_limits[new_plan]
        metadata['upgraded_at'] = datetime.now().isoformat()
        metadata['status'] = TenantStatus.ACTIVE.value

        self._save_tenant_metadata(tenant_id, metadata)

        print(f"⬆️  Upgraded {tenant_id}: {old_plan.value}{new_plan.value}")

        return metadata

    def monitor_usage(self, tenant_id: str) -> Dict:
        """Monitor and report tenant usage"""

        metadata = self._get_tenant_metadata(tenant_id)

        usage = metadata['usage']
        limits = metadata['limits']

        # Calculate utilization percentages
        utilization = {}

        for resource in ['users', 'storage_gb', 'files']:
            limit = limits.get(f'max_{resource}')
            if limit is None:
                utilization[resource] = 0  # Unlimited
            else:
                current = usage.get(resource, 0)
                utilization[resource] = (current / limit * 100) if limit > 0 else 0

        # Check for quota violations
        warnings = []
        if utilization.get('users', 0) > 80:
            warnings.append(f"Users at {utilization['users']:.0f}% of limit")
        if utilization.get('storage_gb', 0) > 80:
            warnings.append(f"Storage at {utilization['storage_gb']:.0f}% of limit")

        return {
            "tenant_id": tenant_id,
            "usage": usage,
            "limits": limits,
            "utilization": utilization,
            "warnings": warnings
        }

    def _get_tenant_metadata(self, tenant_id: str) -> Dict:
        """Get tenant metadata"""
        return json.loads(
            self.admin.read(f"/tenants/{tenant_id}/.tenant.json").decode()
        )

    def _save_tenant_metadata(self, tenant_id: str, metadata: Dict):
        """Save tenant metadata"""
        self.admin.write(
            f"/tenants/{tenant_id}/.tenant.json",
            json.dumps(metadata, indent=2).encode()
        )

def main():
    """Demo the multi-tenant SaaS platform"""

    SERVER_URL = "http://localhost:2026"
    ADMIN_KEY = "admin_key_here"  # Replace with actual admin key

    saas = MultiTenantSaaS(SERVER_URL, ADMIN_KEY)

    # Onboard new tenants
    acme = saas.onboard_tenant(
        tenant_id="acme",
        name="Acme Corporation",
        admin_email="admin@acme.com",
        plan=TenantPlan.ENTERPRISE
    )

    beta = saas.onboard_tenant(
        tenant_id="beta",
        name="Beta Inc",
        admin_email="admin@beta.com",
        plan=TenantPlan.PRO
    )

    startup = saas.onboard_tenant(
        tenant_id="startup",
        name="Startup Co",
        admin_email="admin@startup.com",
        plan=TenantPlan.FREE
    )

    # Upgrade a tenant
    saas.upgrade_tenant("startup", TenantPlan.PRO)

    # Monitor usage
    usage = saas.monitor_usage("acme")
    print(f"\n📊 Acme Usage:")
    print(f"  Users: {usage['usage']['users']}")
    print(f"  Storage: {usage['usage']['storage_gb']}GB")
    if usage['warnings']:
        print(f"  ⚠️  Warnings: {', '.join(usage['warnings'])}")

if __name__ == "__main__":
    main()

Troubleshooting

Issue: Cross-Tenant Data Leakage

Problem: User can access another tenant's data

Solution:

# Verify permissions are properly scoped
permissions = nx.rebac_list_tuples(
    subject=("user", "alice")
)

# Ensure all permissions start with /tenants/{tenant_id}
for perm in permissions:
    if not perm['object_id'].startswith(f"/tenants/{alice_tenant_id}"):
        print(f"⚠️  Cross-tenant permission detected: {perm}")


Issue: Quota Not Enforced

Problem: Tenants exceed quotas

Solution:

# Implement quota check middleware
def enforce_quota_middleware(tenant_id, operation, amount):
    metadata = get_tenant_metadata(tenant_id)

    # Check before allowing operation
    if not check_quota(metadata, operation, amount):
        raise QuotaExceededError(
            f"Tenant {tenant_id} quota exceeded for {operation}"
        )

    # Allow operation
    # ...

    # Update usage after operation
    update_usage(tenant_id, operation, amount)


Best Practices

1. Always Use Tenant Prefixes

# ✅ Good: All tenant data under /tenants/{tenant_id}
tenant_path = f"/tenants/{tenant_id}/data"

# ❌ Bad: Mixed tenant data
user_path = f"/users/{user_id}/data"  # Crosses tenant boundaries

2. Implement Audit Logging

def audit_log(tenant_id, user, action, resource):
    log_entry = {
        "timestamp": datetime.now().isoformat(),
        "tenant_id": tenant_id,
        "user": user,
        "action": action,
        "resource": resource
    }

    nx.append(
        f"/tenants/{tenant_id}/.audit/log.jsonl",
        (json.dumps(log_entry) + '\n').encode()
    )

3. Use Database for Tenant Metadata

# ✅ Good: Use PostgreSQL for tenant metadata
# Fast queries, transactions, indexing

# ❌ Bad: Store tenant metadata only in files
# Slow to query, no transactions

What's Next?

Congratulations! You've built a production-ready multi-tenant SaaS platform with Nexus.

  1. Administration & Operations (25 min) Learn advanced admin operations and monitoring

  2. Multi-Backend Storage (20 min) Scale storage across multiple backends

  3. Production Deployment Deploy your SaaS platform to production

🔧 Advanced Topics


Summary

🎉 You've completed the Multi-Tenant SaaS tutorial!

What you learned: - ✅ Design and implement multi-tenant architecture - ✅ Ensure complete tenant isolation - ✅ Manage tenant lifecycle (create, suspend, delete) - ✅ Implement quotas and billing - ✅ Enable secure cross-tenant sharing - ✅ Scale to thousands of tenants - ✅ Monitor usage and enforce limits

Key Takeaways: - Tenant isolation is critical for security - Use /tenants/{tenant_id} prefix for all tenant data - Implement quotas from day one - Monitor usage for billing and capacity planning - Use PostgreSQL for production deployments


Next: Administration & Operations →

Questions? Check our Multi-Tenancy Guide or GitHub Discussions