backend/bms_app/source_db/services.py (203 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 marshmallow import EXCLUDE, Schema, fields, post_load
from bms_app import ma
from bms_app.models import (
BMSServer, Config, Mapping, Operation, OperationDetails, SourceDB,
SourceDBType, db
)
from bms_app.schema import (
ASMConfigSchema, DataMountSchema, DbParamsSchema, InstallConfigSchema,
MiscConfigSchema, RACConfigSchema, DMSConfigSchema
)
from bms_app.services.utils import generate_target_gcp_logs_link
class InSourceDBSchema(ma.SQLAlchemyAutoSchema):
class Meta:
model = SourceDB
unknown = EXCLUDE
@post_load
def db_type(self, data, many, **kwargs):
if 'db_engine' in data and data['db_engine'] != 'ORACLE':
data['db_type'] = None
elif 'rac_nodes' in data and data['rac_nodes']:
data['db_type'] = SourceDBType.RAC
else:
data['db_type'] = SourceDBType.SI
print(data)
return data
class ASMConfigItem(Schema):
class Meta:
unknown = EXCLUDE
diskgroup = fields.Str()
au_size = fields.Str()
redundancy = fields.Str()
class InMiscConfig(Schema):
class Meta:
unknown = EXCLUDE
compatible_asm = fields.Str()
compatible_rdbms = fields.Str()
class InASMConfig(Schema):
class Meta:
unknown = EXCLUDE
asm = fields.List(fields.Nested(ASMConfigItem))
in_db_schema = InSourceDBSchema(
only=['server', 'oracle_version', 'arch', 'cores', 'ram',
'allocated_memory', 'db_name', 'db_size', 'oracle_release',
'rac_nodes', 'db_engine']
)
def update_db_config(source_db, raw_db_data):
"""Update Config."""
asm_data = InASMConfig().load(raw_db_data)
misc_data = InMiscConfig().load(raw_db_data)
upd_data = {
'db_id': source_db.id,
'asm_config_values': asm_data['asm'] if 'asm' in asm_data else None,
'misc_config_values': misc_data
}
if source_db.config:
db.session.query(Config) \
.filter(Config.db_id == source_db.id) \
.update(upd_data)
else:
config = Config(**upd_data)
db.session.add(config)
db.session.commit()
def update_db(source_db, raw_db_data):
"""Update SourceDB."""
source_db_data = in_db_schema.load(raw_db_data)
db.session.query(SourceDB) \
.filter(SourceDB.id == source_db.id) \
.update(source_db_data)
db.session.commit()
update_db_config(source_db, raw_db_data)
def add_db(raw_db_data, project_id):
"""Add SourceDB using parsed data"""
source_db_data = in_db_schema.load(raw_db_data)
source_db_data['project_id'] = project_id
source_db = SourceDB(**source_db_data)
db.session.add(source_db)
db.session.commit()
update_db_config(source_db, raw_db_data)
def save_source_dbs(parsed_dbs_data, overwrite, project_id):
added = updated = skipped = 0
for raw_db_data in parsed_dbs_data:
source_db = db.session.query(SourceDB) \
.filter(
SourceDB.server == raw_db_data['server'],
SourceDB.db_name == raw_db_data['db_name'],
SourceDB.project_id == project_id)\
.first()
if source_db:
if overwrite:
deployment_exists = db.session.query(OperationDetails) \
.filter(Mapping.db_id == source_db.id) \
.filter(Mapping.id == OperationDetails.mapping_id) \
.count()
if not deployment_exists:
update_db(source_db, raw_db_data)
updated += 1
else:
skipped += 1
else:
skipped += 1
else:
add_db(raw_db_data, project_id)
added += 1
return {
'added': added,
'updated': updated,
'skipped': skipped
}
class SaveSourceDBConfigService:
"""Save/Update Config data.
Perform data validation if is_configured
Or save without any validation in other case (draft).
"""
ATTR_SCHEMA_MAP = {
'install_config_values': InstallConfigSchema(),
'db_params_values': DbParamsSchema(),
'data_mounts_values': DataMountSchema(many=True),
'asm_config_values': ASMConfigSchema(many=True),
'misc_config_values': MiscConfigSchema(),
'rac_config_values': RACConfigSchema(),
'dms_config_values': DMSConfigSchema()
}
CONFIG_ATTRS = (
'install_config_values', 'db_params_values', 'data_mounts_values',
'asm_config_values', 'misc_config_values', 'rac_config_values',
'dms_config_values'
)
@classmethod
def run(cls, db_id, data):
config = cls._get_config_obj(db_id)
cls._update_config_values(config, data)
db.session.add(config)
db.session.commit()
return config
@classmethod
def _update_config_values(cls, config, data):
is_configured = cls._get_is_configured(data)
if is_configured:
value_getter = cls._get_validated_value
else:
value_getter = cls._get_draft_value
config.is_configured = is_configured
for attr in cls.CONFIG_ATTRS:
value = value_getter(data, attr)
setattr(config, attr, value)
@staticmethod
def _get_is_configured(data):
"""Return is_configured value"""
is_configured = data.get('is_configured', False)
if not isinstance(is_configured, bool):
is_configured = str(is_configured).lower() != 'false'
return is_configured
@staticmethod
def _get_config_obj(db_id):
config = db.session.query(Config).filter(Config.db_id == db_id).first()
if not config:
config = Config(db_id=db_id)
return config
@staticmethod
def _get_draft_value(data, attr):
"""Return data without any validation."""
return data.get(attr)
@classmethod
def _get_validated_value(cls, data, attr):
"""Validate and return data."""
if attr in data:
schema = cls.ATTR_SCHEMA_MAP[attr]
return schema.load(data[attr])
class SourceDBOperationsHistoryService:
@classmethod
def run(cls, source_db):
data = cls._get_source_db_data(source_db)
data['operations'] = cls._get_operations(source_db)
return data
@staticmethod
def _get_operations(source_db):
"""Return operations history data."""
query = db.session.query(OperationDetails, Operation, BMSServer) \
.join(Mapping, OperationDetails.mapping_id == Mapping.id) \
.join(SourceDB, Mapping.db_id == SourceDB.id) \
.join(BMSServer, Mapping.bms_id == BMSServer.id) \
.join(Operation, OperationDetails.operation_id == Operation.id) \
.filter(SourceDB.id == source_db.id) \
.order_by(OperationDetails.id) \
.all()
operations_data = {}
for op_details, operation, bms in query:
op_id = op_details.operation_id
if op_id not in operations_data:
operations_data[op_id] = {
'wave_id': operation.wave_id,
'operation_type': operation.operation_type.value,
'started_at': operation.started_at,
'completed_at': operation.completed_at,
'bms': []
}
logs_url = generate_target_gcp_logs_link(op_details, bms)
operations_data[op_id]['bms'].append({
'id': bms.id,
'name': bms.name,
'logs_url': logs_url,
'operation_status': op_details.status.value
})
return list(operations_data.values())
@staticmethod
def _get_source_db_data(source_db):
return {
'id': source_db.id,
'source_hostname': source_db.server,
'db_name': source_db.db_name,
'oracle_version': source_db.oracle_version,
'db_type': source_db.db_type.value,
'operations': [],
}