Initial Django project setup for Postgres cluster management - Created Django project structure with postgres_handholder main app - Added clusters, backups, and monitoring apps - Implemented comprehensive models for PostgresCluster, PostgresInstance, ClusterUser, ClusterDatabase - Added forms and views for cluster management - Created Bootstrap 5 templates with modern UI - Added Docker and Docker Compose configuration - Included Celery for background tasks - Added comprehensive requirements.txt with all dependencies - Updated README with installation and usage instructions - Added environment configuration example - Set up proper URL routing and app structure
This commit is contained in:
200
clusters/models.py
Normal file
200
clusters/models.py
Normal file
@ -0,0 +1,200 @@
|
||||
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)
|
Reference in New Issue
Block a user