from django.db import models from django.contrib.auth.models import User from django.core.validators import MinValueValidator, MaxValueValidator import json class PostgresCluster(models.Model): """Model for managing Postgres cluster configurations.""" CLUSTER_TYPES = [ ('single', 'Single Instance'), ('replica', 'Primary with Replicas'), ('standby', 'Standby Cluster'), ] DEPLOYMENT_TYPES = [ ('docker', 'Docker'), ('kubernetes', 'Kubernetes'), ('local', 'Local Linux'), ] name = models.CharField(max_length=100, unique=True) description = models.TextField(blank=True) cluster_type = models.CharField(max_length=20, choices=CLUSTER_TYPES, default='single') deployment_type = models.CharField(max_length=20, choices=DEPLOYMENT_TYPES, default='docker') # PostgreSQL Configuration postgres_version = models.CharField(max_length=10, default='15') port = models.IntegerField(default=5432, validators=[MinValueValidator(1024), MaxValueValidator(65535)]) data_directory = models.CharField(max_length=255, default='/var/lib/postgresql/data') # Resource Configuration cpu_limit = models.CharField(max_length=20, default='2') memory_limit = models.CharField(max_length=20, default='4Gi') storage_size = models.CharField(max_length=20, default='10Gi') # Network Configuration host = models.CharField(max_length=255, default='localhost') external_port = models.IntegerField(default=5432, validators=[MinValueValidator(1024), MaxValueValidator(65535)]) # Authentication admin_user = models.CharField(max_length=50, default='postgres') admin_password = models.CharField(max_length=255) # Extensions and Libraries extensions = models.JSONField(default=list, blank=True) libraries = models.JSONField(default=list, blank=True) # TLS Configuration tls_enabled = models.BooleanField(default=False) tls_cert_path = models.CharField(max_length=255, blank=True) tls_key_path = models.CharField(max_length=255, blank=True) tls_ca_path = models.CharField(max_length=255, blank=True) # Connection Pooling pgpool_enabled = models.BooleanField(default=False) pgpool_instances = models.IntegerField(default=1, validators=[MinValueValidator(1), MaxValueValidator(10)]) # Status status = models.CharField(max_length=20, default='stopped', choices=[ ('running', 'Running'), ('stopped', 'Stopped'), ('starting', 'Starting'), ('stopping', 'Stopping'), ('error', 'Error'), ('updating', 'Updating'), ]) # Metadata created_by = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: ordering = ['-created_at'] def __str__(self): return self.name def get_connection_string(self): """Get the connection string for this cluster.""" return f"postgresql://{self.admin_user}:{self.admin_password}@{self.host}:{self.external_port}/postgres" def get_extensions_list(self): """Get the list of enabled extensions.""" return self.extensions if isinstance(self.extensions, list) else [] def get_libraries_list(self): """Get the list of enabled libraries.""" return self.libraries if isinstance(self.libraries, list) else [] class PostgresInstance(models.Model): """Model for managing individual Postgres instances within a cluster.""" INSTANCE_TYPES = [ ('primary', 'Primary'), ('replica', 'Replica'), ('standby', 'Standby'), ] cluster = models.ForeignKey(PostgresCluster, on_delete=models.CASCADE, related_name='instances') name = models.CharField(max_length=100) instance_type = models.CharField(max_length=20, choices=INSTANCE_TYPES, default='primary') # Instance Configuration host = models.CharField(max_length=255) port = models.IntegerField(validators=[MinValueValidator(1024), MaxValueValidator(65535)]) data_directory = models.CharField(max_length=255) # Status status = models.CharField(max_length=20, default='stopped', choices=[ ('running', 'Running'), ('stopped', 'Stopped'), ('starting', 'Starting'), ('stopping', 'Stopping'), ('error', 'Error'), ('syncing', 'Syncing'), ]) # Replication Configuration (for replicas) replication_slot = models.CharField(max_length=100, blank=True) lag_seconds = models.IntegerField(default=0) # Resource Usage cpu_usage = models.FloatField(default=0.0) memory_usage = models.FloatField(default=0.0) disk_usage = models.FloatField(default=0.0) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = ['cluster', 'name'] ordering = ['instance_type', 'name'] def __str__(self): return f"{self.cluster.name} - {self.name}" class ClusterUser(models.Model): """Model for managing database users within clusters.""" cluster = models.ForeignKey(PostgresCluster, on_delete=models.CASCADE, related_name='users') username = models.CharField(max_length=50) password = models.CharField(max_length=255) is_superuser = models.BooleanField(default=False) can_create_db = models.BooleanField(default=False) can_login = models.BooleanField(default=True) # Permissions permissions = models.JSONField(default=dict, blank=True) # Metadata created_by = models.ForeignKey(User, on_delete=models.CASCADE) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = ['cluster', 'username'] ordering = ['username'] def __str__(self): return f"{self.cluster.name} - {self.username}" class ClusterDatabase(models.Model): """Model for managing databases within clusters.""" cluster = models.ForeignKey(PostgresCluster, on_delete=models.CASCADE, related_name='databases') name = models.CharField(max_length=50) owner = models.ForeignKey(ClusterUser, on_delete=models.CASCADE) # Configuration encoding = models.CharField(max_length=20, default='UTF8') collation = models.CharField(max_length=50, default='en_US.utf8') ctype = models.CharField(max_length=50, default='en_US.utf8') # Size tracking size_bytes = models.BigIntegerField(default=0) # Metadata created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) class Meta: unique_together = ['cluster', 'name'] ordering = ['name'] def __str__(self): return f"{self.cluster.name} - {self.name}" def get_size_mb(self): """Get database size in MB.""" return round(self.size_bytes / (1024 * 1024), 2) def get_size_gb(self): """Get database size in GB.""" return round(self.size_bytes / (1024 * 1024 * 1024), 2)