220 lines
7.9 KiB
Python
220 lines
7.9 KiB
Python
import psycopg2
|
|
from odoo import models, fields, api, _
|
|
from odoo.exceptions import UserError
|
|
import secrets
|
|
import string
|
|
import subprocess
|
|
import logging
|
|
from datetime import datetime, timedelta
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
class DatabaseInstance(models.Model):
|
|
_name = 'database.instance'
|
|
_description = 'Database Instance'
|
|
_rec_name = 'database_name'
|
|
|
|
subscription_id = fields.Many2one('user.subscription', string='Subscription',
|
|
required=True, ondelete='cascade')
|
|
database_name = fields.Char(string='Database Name', required=True, index=True)
|
|
subdomain = fields.Char(string='Subdomain', required=True)
|
|
full_domain = fields.Char(string='Full Domain', compute='_compute_full_domain')
|
|
|
|
# Database status
|
|
db_state = fields.Selection([
|
|
('pending', 'Pending'),
|
|
('creating', 'Creating'),
|
|
('installing', 'Installing Apps'),
|
|
('ready', 'Ready'),
|
|
('failed', 'Failed'),
|
|
], string='Status', default='pending', tracking=True)
|
|
|
|
# PostgreSQL connection
|
|
pg_host = fields.Char(string='PostgreSQL Host', default='localhost')
|
|
pg_port = fields.Integer(string='PostgreSQL Port', default=5432)
|
|
pg_user = fields.Char(string='Database User')
|
|
pg_password = fields.Char(string='Database Password')
|
|
|
|
# Instance configuration
|
|
workers = fields.Integer(string='Workers', default=2)
|
|
timeout = fields.Integer(string='Timeout (seconds)', default=300)
|
|
memory_limit_mb = fields.Integer(string='Memory Limit (MB)', default=512)
|
|
max_connections = fields.Integer(string='Max Connections', default=20)
|
|
|
|
# Timestamps
|
|
created_at = fields.Datetime(string='Created At', default=fields.Datetime.now)
|
|
ready_at = fields.Datetime(string='Ready At')
|
|
last_accessed = fields.Datetime(string='Last Accessed')
|
|
|
|
# Admin credentials
|
|
admin_login = fields.Char(string='Admin Login')
|
|
admin_password = fields.Char(string='Admin Password')
|
|
admin_email = fields.Char(string='Admin Email')
|
|
|
|
installed_apps = fields.Text(string='Installed Apps')
|
|
error_message = fields.Text(string='Error Message')
|
|
|
|
@api.depends('subdomain')
|
|
def _compute_full_domain(self):
|
|
base_domain = self.env['ir.config_parameter'].sudo().get_param('web.base.domain', 'localhost')
|
|
for record in self:
|
|
if record.subdomain:
|
|
record.full_domain = f"{record.subdomain}.{base_domain}"
|
|
else:
|
|
record.full_domain = False
|
|
|
|
def generate_unique_subdomain(self, company_name):
|
|
"""Generate unique subdomain from company name"""
|
|
import re
|
|
# Convert to lowercase and remove special characters
|
|
base = re.sub(r'[^a-z0-9]+', '-', company_name.lower()).strip('-')
|
|
# Add random string
|
|
random_str = ''.join(secrets.choice(string.digits) for _ in range(5))
|
|
subdomain = f"{base}-{random_str}"
|
|
|
|
# Ensure uniqueness
|
|
counter = 1
|
|
original_subdomain = subdomain
|
|
while self.search_count([('subdomain', '=', subdomain)]) > 0:
|
|
subdomain = f"{original_subdomain}-{counter}"
|
|
counter += 1
|
|
|
|
return subdomain
|
|
|
|
def generate_password(self, length=12):
|
|
"""Generate secure random password"""
|
|
chars = string.ascii_letters + string.digits + "!@#$%^&*"
|
|
return ''.join(secrets.choice(chars) for _ in range(length))
|
|
|
|
def create_database(self):
|
|
"""Create PostgreSQL database for tenant"""
|
|
self.ensure_one()
|
|
|
|
try:
|
|
self.db_state = 'creating'
|
|
self._cr.commit()
|
|
|
|
# Generate credentials
|
|
self.pg_user = f"user_{self.database_name.replace('-', '_')}"
|
|
self.pg_password = self.generate_password()
|
|
self.admin_login = 'admin'
|
|
self.admin_password = self.generate_password()
|
|
|
|
# Connect to PostgreSQL
|
|
conn = psycopg2.connect(
|
|
host=self.pg_host,
|
|
port=self.pg_port,
|
|
database='postgres',
|
|
user='odoo', # Master PostgreSQL user
|
|
password='odoo_password' # Should be in config
|
|
)
|
|
conn.autocommit = True
|
|
cursor = conn.cursor()
|
|
|
|
# Create database user
|
|
try:
|
|
cursor.execute(f"DROP ROLE IF EXISTS {self.pg_user}")
|
|
cursor.execute(
|
|
f"CREATE ROLE {self.pg_user} WITH LOGIN PASSWORD %s",
|
|
(self.pg_password,)
|
|
)
|
|
except Exception as e:
|
|
_logger.error(f"Error creating user: {e}")
|
|
|
|
# Create database
|
|
cursor.execute(f"DROP DATABASE IF EXISTS {self.database_name}")
|
|
cursor.execute(
|
|
f"CREATE DATABASE {self.database_name} "
|
|
f"OWNER {self.pg_user} "
|
|
f"ENCODING 'UTF8' "
|
|
f"TEMPLATE template0"
|
|
)
|
|
|
|
# Set permissions
|
|
cursor.execute(
|
|
f"GRANT ALL PRIVILEGES ON DATABASE {self.database_name} TO {self.pg_user}"
|
|
)
|
|
|
|
cursor.close()
|
|
conn.close()
|
|
|
|
_logger.info(f"Database {self.database_name} created successfully")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.db_state = 'failed'
|
|
self.error_message = str(e)
|
|
_logger.error(f"Failed to create database: {e}")
|
|
return False
|
|
|
|
def install_apps(self, app_list):
|
|
"""Install selected Odoo apps in the database"""
|
|
self.ensure_one()
|
|
|
|
try:
|
|
self.db_state = 'installing'
|
|
self.installed_apps = ', '.join(app_list)
|
|
self._cr.commit()
|
|
|
|
# This would typically use Odoo's database provisioning API
|
|
# For now, we'll simulate it
|
|
import odoo
|
|
from odoo import api, SUPERUSER_ID
|
|
|
|
# Initialize new database
|
|
registry = odoo.modules.registry.Registry.new(
|
|
self.database_name,
|
|
force_demo=False,
|
|
signal=False,
|
|
update_module=True
|
|
)
|
|
|
|
with api.Environment.manage(), registry.cursor() as cr:
|
|
env = api.Environment(cr, SUPERUSER_ID, {})
|
|
|
|
# Install selected modules
|
|
for module_name in app_list:
|
|
try:
|
|
module = env['ir.module.module'].search([('name', '=', module_name)])
|
|
if module:
|
|
module.button_immediate_install()
|
|
_logger.info(f"Installed module: {module_name}")
|
|
except Exception as e:
|
|
_logger.warning(f"Failed to install {module_name}: {e}")
|
|
|
|
# Create admin user
|
|
env['res.users'].create({
|
|
'name': 'Administrator',
|
|
'login': self.admin_login,
|
|
'password': self.admin_password,
|
|
'email': self.admin_email,
|
|
'groups_id': [(6, 0, [env.ref('base.group_system').id])],
|
|
})
|
|
|
|
self.db_state = 'ready'
|
|
self.ready_at = fields.Datetime.now()
|
|
_logger.info(f"Database {self.database_name} is ready")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.db_state = 'failed'
|
|
self.error_message = str(e)
|
|
_logger.error(f"Failed to install apps: {e}")
|
|
return False
|
|
|
|
def configure_instance(self):
|
|
"""Configure system instance parameters"""
|
|
self.ensure_one()
|
|
|
|
# Update odoo.conf or use environment variables
|
|
config_params = {
|
|
'workers': self.workers,
|
|
'timeout': self.timeout,
|
|
'limit_memory_hard': self.memory_limit_mb * 1024 * 1024,
|
|
'max_cron_threads': 1,
|
|
}
|
|
|
|
# These would be applied at the Odoo server level
|
|
_logger.info(f"Configuring instance {self.database_name}: {config_params}")
|
|
return True |