backend/bms_app/models.py (297 lines of code) (raw):

# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from datetime import datetime from enum import Enum from sqlalchemy.ext.hybrid import hybrid_property from sqlalchemy.orm import relationship from sqlalchemy_utils import ChoiceType from bms_app import db now = datetime.now source_db_to_label = db.Table('source_db_labels', db.Model.metadata, db.Column('db_id', db.Integer, db.ForeignKey('source_dbs.id')), db.Column('label_id', db.Integer, db.ForeignKey('labels.id')) ) class Project(db.Model): __tablename__ = 'projects' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, nullable=False, unique=True) vpc = db.Column(db.String, nullable=False) subnet = db.Column(db.String, nullable=False) description = db.Column(db.String, nullable=False) waves = relationship('Wave', back_populates='project') source_dbs = relationship('SourceDB', back_populates='project') labels = relationship('Label', back_populates='project') class BMSServer(db.Model): __tablename__ = 'bms_servers' id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, unique=True) state = db.Column(db.String) machine_type = db.Column(db.String) luns = db.Column(db.JSON) networks = db.Column(db.JSON) deleted = db.Column(db.Boolean, default=False) secret_name = db.Column(db.String) cpu = db.Column(db.String) socket = db.Column(db.String) ram = db.Column(db.String) created_at = db.Column(db.DateTime, default=datetime.now) location = db.Column(db.String) mapping = relationship('Mapping', back_populates='bms') class SourceDBType(Enum): SI = 'SI' RAC = 'RAC' DG = 'DG' class SourceDBStatus(Enum): EMPTY = 'EMPTY' ROLLBACKED = 'ROLLBACKED' FAILED = 'FAILED' DEPLOYED = 'DEPLOYED' PRE_RESTORE = 'PRE_RESTORE' PRE_RESTORE_COMPLETE = 'PRE_RESTORE_COMPLETE' PRE_RESTORE_FAILED = 'PRE_RESTORE_FAILED' DT = 'DT' DT_COMPLETE = 'DT_COMPLETE' DT_FAILED = 'DT_FAILED' DT_PARTIALLY = 'DT_PARTIALLY' DT_ROLLBACK = 'DT_ROLLBACK' FAILOVER = 'FAILOVER' FAILOVER_COMPLETE = 'FAILOVER_COMPLETE' FAILOVER_FAILED = 'FAILOVER_FAILED' class SourceDBEngine(Enum): ORACLE = 'ORACLE' POSTGRES = 'POSTGRES' class SourceDB(db.Model): __tablename__ = 'source_dbs' __table_args__ = (db.UniqueConstraint('server', 'db_name', 'project_id'), ) DEPLOYABLE_STATUSES = (SourceDBStatus.EMPTY, SourceDBStatus.ROLLBACKED) id = db.Column(db.Integer, primary_key=True) server = db.Column(db.String, nullable=False) db_engine = db.Column(ChoiceType(SourceDBEngine, impl=db.String(20)), default=SourceDBEngine.ORACLE) oracle_version = db.Column(db.String) oracle_release = db.Column(db.String) oracle_edition = db.Column(db.String, default='EE') db_type = db.Column(ChoiceType(SourceDBType, impl=db.String(10)), default=SourceDBType.SI) rac_nodes = db.Column(db.Integer, default=0) # value parsed from assessment file fe_rac_nodes = db.Column(db.Integer) arch = db.Column(db.String) cores = db.Column(db.Integer) ram = db.Column(db.Integer) allocated_memory = db.Column(db.Integer) db_name = db.Column(db.String, nullable=False) db_size = db.Column(db.Numeric) status = db.Column(ChoiceType(SourceDBStatus, impl=db.String(20)), default=SourceDBStatus.EMPTY) project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=False) wave_id = db.Column(db.Integer, db.ForeignKey('waves.id')) wave = relationship('Wave', back_populates='source_db', uselist=False) config = relationship('Config', back_populates='source_db', uselist=False) mappings = relationship('Mapping', back_populates='source_db') project = relationship(Project, back_populates='source_dbs', uselist=False) restore_config = relationship('RestoreConfig', back_populates='source_db', uselist=False) scheduled_tasks = relationship('ScheduledTask', back_populates='source_db') labels = db.relationship('Label', secondary=source_db_to_label, back_populates='source_dbs', cascade='save-update, merge') @hybrid_property def is_rac(self): return self.db_type == SourceDBType.RAC @hybrid_property def is_deployable(self): return self.status in self.DEPLOYABLE_STATUSES @is_deployable.expression def is_deployable(cls): return cls.status.in_(cls.DEPLOYABLE_STATUSES) class Config(db.Model): __tablename__ = 'configs' id = db.Column(db.Integer, primary_key=True) db_id = db.Column(db.Integer, db.ForeignKey('source_dbs.id'), unique=True, nullable=False) install_config_values = db.Column(db.JSON) db_params_values = db.Column(db.JSON) data_mounts_values = db.Column(db.JSON) asm_config_values = db.Column(db.JSON) rac_config_values = db.Column(db.JSON) misc_config_values = db.Column(db.JSON) dms_config_values = db.Column(db.JSON) created_at = db.Column(db.DateTime) is_configured = db.Column(db.Boolean, default=False) source_db = relationship('SourceDB', back_populates='config') class Wave(db.Model): __tablename__ = 'waves' __table_args__ = (db.UniqueConstraint('name', 'project_id'), ) id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String, nullable=False) project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=False) is_running = db.Column(db.Boolean, default=False) project = relationship(Project, back_populates='waves', uselist=False) operations = relationship('Operation', back_populates='wave') source_db = relationship('SourceDB', back_populates='wave') operation_details = relationship('OperationDetails', back_populates='wave') class Mapping(db.Model): __tablename__ = 'mappings' __table_args__ = (db.UniqueConstraint('db_id', 'bms_id'), ) id = db.Column(db.Integer, primary_key=True) db_id = db.Column(db.Integer, db.ForeignKey('source_dbs.id'), nullable=False) bms_id = db.Column(db.Integer, db.ForeignKey('bms_servers.id'), nullable=False) rac_node = db.Column(db.Integer) # node order in RAC bms = relationship('BMSServer', back_populates='mapping') source_db = relationship('SourceDB', back_populates='mappings') operation_details = relationship('OperationDetails', back_populates='mapping') class OperationStatus(Enum): STARTING = 'STARTING' IN_PROGRESS = 'IN_PROGRESS' COMPLETE = 'COMPLETE' FAILED = 'FAILED' COMPLETE_PARTIALLY = 'COMPLETE_PARTIALLY' class OperationType(Enum): DEPLOYMENT = 'DEPLOYMENT' ROLLBACK = 'ROLLBACK' PRE_RESTORE = 'PRE_RESTORE' BACKUP_RESTORE = 'BACKUP_RESTORE' IMPORT_EXPORT = 'IMPORT_EXPORT' DATA_GUARD = 'DATA_GUARD' ROLLBACK_RESTORE = 'ROLLBACK_RESTORE' DB_FAILOVER = 'DB_FAILOVER' class Operation(db.Model): __tablename__ = 'operations' id = db.Column(db.Integer, primary_key=True) wave_id = db.Column(db.Integer, db.ForeignKey('waves.id'), nullable=True) operation_type = db.Column(ChoiceType(OperationType, impl=db.String(20))) status = db.Column(ChoiceType(OperationStatus, impl=db.String(20))) started_at = db.Column(db.DateTime, default=now) completed_at = db.Column(db.DateTime) wave = relationship('Wave', back_populates='operations', uselist=False) operation_details = relationship('OperationDetails', back_populates='operation') @hybrid_property def is_deployment(self): return self.operation_type == OperationType.DEPLOYMENT @hybrid_property def is_rollback(self): return self.operation_type == OperationType.ROLLBACK class OperationDetails(db.Model): __tablename__ = 'operation_details' id = db.Column(db.Integer, primary_key=True) mapping_id = db.Column(db.Integer, db.ForeignKey('mappings.id')) wave_id = db.Column(db.Integer, db.ForeignKey('waves.id'), nullable=True) operation_id = db.Column(db.Integer, db.ForeignKey('operations.id'), nullable=False) operation_type = db.Column(ChoiceType(OperationType, impl=db.String(20))) step = db.Column(db.String(20)) step_upd_at = db.Column(db.DateTime) status = db.Column(ChoiceType(OperationStatus, impl=db.String(20))) started_at = db.Column(db.DateTime, default=now) completed_at = db.Column(db.DateTime) mapping = relationship('Mapping', back_populates='operation_details') wave = relationship('Wave', back_populates='operation_details') operation = relationship('Operation', back_populates='operation_details') errors = relationship('OperationDetailsError', back_populates='operation_details') @hybrid_property def is_deployment(self): return self.operation_type == OperationType.DEPLOYMENT @hybrid_property def is_rollback(self): return self.operation_type == OperationType.ROLLBACK class OperationDetailsError(db.Model): __tablename__ = 'operation_details_errors' id = db.Column(db.Integer, primary_key=True) operation_details_id = db.Column(db.Integer, db.ForeignKey('operation_details.id'), nullable=False) message = db.Column(db.String(255), nullable=False) operation_details = relationship('OperationDetails', back_populates='errors', uselist=False) class BackupType(Enum): NONE = '' FULL = 'full' INCREMENTAL = 'incremental' ARCHIVE_LOG = 'archivelogs' @classmethod def values(cls): return [x.value for x in cls] class RestoreConfig(db.Model): __tablename__ = 'restore_configs' id = db.Column(db.Integer, primary_key=True) db_id = db.Column(db.Integer, db.ForeignKey('source_dbs.id'), unique=True, nullable=False) backup_location = db.Column(db.String, nullable=False, server_default='', default='') rman_cmd = db.Column(db.Text, nullable=False, server_default='', default='') is_configured = db.Column(db.Boolean, nullable=False, server_default='f', default=False) pfile_file = db.Column(db.String, nullable=False, server_default='', default='') pwd_file = db.Column(db.String, nullable=False, server_default='', default='') tnsnames_file = db.Column(db.String, nullable=False, server_default='', default='') listener_file = db.Column(db.String, nullable=False, server_default='', default='') backup_type = db.Column(ChoiceType(BackupType, impl=db.String()), default='') run_pre_restore = db.Column(db.Boolean, nullable=False, default=False, server_default='0') control_file = db.Column(db.String, nullable=False, server_default='', default='') validations = db.Column(db.JSON, default=list) source_db = relationship('SourceDB', back_populates='restore_config') @hybrid_property def is_full_backup_type(self): return self.backup_type is BackupType.FULL class ScheduledTask(db.Model): __tablename__ = 'scheduled_tasks' id = db.Column(db.Integer, primary_key=True) g_task_name = db.Column(db.String, nullable=False, default='') # task name in GoogleTask schedule_time = db.Column(db.DateTime, nullable=False) # utc tz completed = db.Column(db.Boolean, nullable=False, default=False) db_id = db.Column(db.Integer, db.ForeignKey('source_dbs.id'), nullable=False) source_db = relationship('SourceDB', back_populates='scheduled_tasks') class Label(db.Model): __tablename__ = 'labels' __table_args__ = (db.UniqueConstraint('name', 'project_id'), ) id = db.Column(db.Integer, primary_key=True) project_id = db.Column(db.Integer, db.ForeignKey('projects.id'), nullable=False) name = db.Column(db.String(15), nullable=False, index=True) project = relationship(Project, back_populates='labels') source_dbs = relationship('SourceDB', secondary=source_db_to_label, back_populates='labels', cascade='save-update, merge') # statuses that mean that operation is alredy finished FINISHED_OPERATION_STATUSES = ( OperationStatus.COMPLETE, OperationStatus.FAILED ) # order in which operation status can change OPERATION_STATUSES_ORDER = [ OperationStatus.STARTING, OperationStatus.IN_PROGRESS, OperationStatus.FAILED, # final OperationStatus.COMPLETE, # final ] # DB statuses that displayed on Data Transfer Manager page DATA_TRANSFER_DB_STATUSES = [ SourceDBStatus.DEPLOYED, SourceDBStatus.PRE_RESTORE, SourceDBStatus.PRE_RESTORE_COMPLETE, SourceDBStatus.PRE_RESTORE_FAILED, SourceDBStatus.DT, SourceDBStatus.DT_COMPLETE, SourceDBStatus.DT_FAILED, SourceDBStatus.DT_PARTIALLY, SourceDBStatus.DT_ROLLBACK, SourceDBStatus.FAILOVER, SourceDBStatus.FAILOVER_COMPLETE, SourceDBStatus.FAILOVER_FAILED, ] WAVE_OPERATIONS = [ OperationType.DEPLOYMENT, OperationType.ROLLBACK ] PRE_RESTORE_ALLOWED_STATUSES = [ SourceDBStatus.DEPLOYED, SourceDBStatus.PRE_RESTORE_FAILED, SourceDBStatus.PRE_RESTORE_COMPLETE, ] RESTORE_ALLOWED_STATUSES = [ # Also depends on RestoreConfig.run_pre_restore # See bms_app.services.operations.utils.is_restore_allowed function SourceDBStatus.DT_COMPLETE, SourceDBStatus.DT_PARTIALLY, SourceDBStatus.FAILOVER_FAILED, ] ROLLBACK_RESTORE_ALLOWED_STATUSES = [ SourceDBStatus.DT_COMPLETE, SourceDBStatus.DT_PARTIALLY, SourceDBStatus.DT_FAILED, SourceDBStatus.FAILOVER_FAILED, SourceDBStatus.FAILOVER_COMPLETE, ] FAILOVER_ALLOWED_STATUSES = [ SourceDBStatus.DT_COMPLETE ] # statuses which mean that db is deployed DEPLOYED_STATUSES = [ SourceDBStatus.DEPLOYED, SourceDBStatus.PRE_RESTORE, SourceDBStatus.PRE_RESTORE_COMPLETE, SourceDBStatus.PRE_RESTORE_FAILED, SourceDBStatus.DT, SourceDBStatus.DT_COMPLETE, SourceDBStatus.DT_FAILED, SourceDBStatus.DT_PARTIALLY, SourceDBStatus.DT_ROLLBACK, SourceDBStatus.FAILOVER, SourceDBStatus.FAILOVER_COMPLETE, SourceDBStatus.FAILOVER_FAILED, ]