- Introduced `saas.plan` model to define subscription plans with limits and pricing. - Created `saas.restaurant` model to manage restaurant tenants, including database provisioning and subscription management. - Implemented views for managing SaaS plans and restaurant tenants, including tree and form views. - Added security access rights for the new models. - Developed a backup management view for database backups. - Updated menu structure to include new SaaS management options. - Added Docker and deployment configurations for PostgreSQL, Redis, and Odoo services. - Included scaling guide and backup scripts for production environments. - Enhanced theme with new images and layout adjustments.
118 lines
4.6 KiB
Python
118 lines
4.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
import os
|
|
import tempfile
|
|
import logging
|
|
from datetime import datetime
|
|
from odoo import models, fields, api
|
|
from odoo.exceptions import UserError
|
|
from odoo.service import db
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
try:
|
|
import boto3
|
|
except ImportError:
|
|
boto3 = None
|
|
|
|
class SaasBackup(models.Model):
|
|
_name = 'saas.backup'
|
|
_description = 'Dine360 SaaS Backup Log'
|
|
_order = 'timestamp desc'
|
|
|
|
restaurant_id = fields.Many2one('saas.restaurant', string='Restaurant Tenant', required=True)
|
|
database_name = fields.Char(related='restaurant_id.database_name', store=True, readonly=True)
|
|
backup_file = fields.Char(string='Backup Filename', readonly=True)
|
|
timestamp = fields.Datetime(string='Backup Time', default=fields.Datetime.now, readonly=True)
|
|
status = fields.Selection([
|
|
('success', 'Success'),
|
|
('failed', 'Failed')
|
|
], string='Status', default='failed', readonly=True)
|
|
storage_type = fields.Selection([
|
|
('local', 'Local Storage'),
|
|
('s3', 'Amazon S3')
|
|
], string='Storage Type', default='local', required=True)
|
|
s3_url = fields.Char(string='S3 URL', readonly=True)
|
|
|
|
def action_backup_database(self):
|
|
self.ensure_one()
|
|
db_name = self.database_name
|
|
|
|
if not db_name:
|
|
raise UserError("Database name is missing on the restaurant record!")
|
|
|
|
timestamp_str = datetime.now().strftime('%Y%m%d_%H%M%S')
|
|
filename = f"{db_name}_{timestamp_str}.zip"
|
|
|
|
# Create temporary file to store Odoo dump
|
|
temp_dir = tempfile.gettempdir()
|
|
temp_filepath = os.path.join(temp_dir, filename)
|
|
|
|
try:
|
|
_logger.info(f"Dumping database {db_name} to {temp_filepath}...")
|
|
with open(temp_filepath, 'wb') as f:
|
|
# Odoo service dump database to stream
|
|
db.dump_db(db_name, f, format='zip')
|
|
_logger.info("Database dumped successfully.")
|
|
|
|
# S3 upload logic
|
|
s3_bucket = os.environ.get('AWS_S3_BUCKET')
|
|
aws_access_key = os.environ.get('AWS_ACCESS_KEY_ID')
|
|
aws_secret_key = os.environ.get('AWS_SECRET_ACCESS_KEY')
|
|
|
|
if self.storage_type == 's3' and boto3 and s3_bucket:
|
|
_logger.info(f"Uploading {filename} to AWS S3 bucket {s3_bucket}...")
|
|
s3_client = boto3.client(
|
|
's3',
|
|
aws_access_key_id=aws_access_key,
|
|
aws_secret_access_key=aws_secret_key
|
|
)
|
|
s3_client.upload_file(temp_filepath, s3_bucket, filename)
|
|
s3_url = f"https://{s3_bucket}.s3.amazonaws.com/{filename}"
|
|
self.write({
|
|
'backup_file': filename,
|
|
'status': 'success',
|
|
's3_url': s3_url
|
|
})
|
|
# Clean up local temp file
|
|
os.remove(temp_filepath)
|
|
else:
|
|
# Local storage: move backup to persistent local directory
|
|
backup_dir = os.environ.get('ODOO_BACKUP_DIR', '/var/lib/odoo/backups')
|
|
if not os.path.exists(backup_dir):
|
|
os.makedirs(backup_dir)
|
|
persistent_path = os.path.join(backup_dir, filename)
|
|
os.rename(temp_filepath, persistent_path)
|
|
|
|
self.write({
|
|
'backup_file': filename,
|
|
'status': 'success',
|
|
's3_url': f"file://{persistent_path}"
|
|
})
|
|
|
|
except Exception as e:
|
|
_logger.error(f"Backup generation failed: {str(e)}")
|
|
self.write({
|
|
'backup_file': filename,
|
|
'status': 'failed'
|
|
})
|
|
if os.path.exists(temp_filepath):
|
|
os.remove(temp_filepath)
|
|
raise UserError(f"Backup failed: {str(e)}")
|
|
|
|
@api.model
|
|
def cron_automatic_backups(self):
|
|
""" Triggered by Odoo cron job to run daily backups for all active tenants """
|
|
tenants = self.env['saas.restaurant'].search([('status', '=', 'active')])
|
|
s3_bucket = os.environ.get('AWS_S3_BUCKET')
|
|
storage = 's3' if s3_bucket else 'local'
|
|
|
|
for tenant in tenants:
|
|
try:
|
|
backup = self.create({
|
|
'restaurant_id': tenant.id,
|
|
'storage_type': storage
|
|
})
|
|
backup.action_backup_database()
|
|
except Exception as e:
|
|
_logger.error(f"Scheduled backup failed for {tenant.database_name}: {str(e)}")
|