backend/bms_app/mapping/services.py (164 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 ValidationError
from bms_app.models import (
DEPLOYED_STATUSES, BMSServer, Config, Mapping, SourceDB, SourceDBType,
Wave, db
)
from bms_app.services.source_db import (
clear_bms_target_params, does_db_have_operation
)
def validate_db(db_id):
"""Check if db can be mapped.
Only RAC db can be mapped to multiple bms targets.
"""
source_db = SourceDB.query.get(db_id)
if not source_db:
raise ValidationError({'db_id': ['no such database']})
is_rac = source_db.db_type == SourceDBType.RAC
if Mapping.query.filter(Mapping.db_id == db_id).count() \
and not is_rac:
raise ValidationError({'db_id': ['database already has mapping']})
def validate_bms(bms_id, db_id=None):
if not BMSServer.query.get(bms_id):
raise ValidationError({'bms_id': ['no such bms server']})
qs = Mapping.query.filter(Mapping.bms_id == bms_id)
if db_id: # exclude current mapping
qs = qs.filter(Mapping.db_id != db_id)
if qs.count():
raise ValidationError({'bms_id': ['server already has mapping']})
def validate_wave(wave_id):
if not Wave.query.get(wave_id):
raise ValidationError({'wave_id': ['no such wave']})
class GetMappingsService:
@classmethod
def run(cls, project_id=None, db_id=None):
"""Return list of mappings. Filter by project/db_id optionally."""
query = cls._generate_query(project_id, db_id)
data = {}
for mapping, source_db, bms_server, config in query:
if mapping.db_id not in data:
data[mapping.db_id] = cls._add_main_data(
mapping, source_db, config
)
for label in source_db.labels:
data[mapping.db_id]['labels'].append(
cls._add_label_data(label)
)
data[mapping.db_id]['bms'].append(
cls._add_bms_data(bms_server)
)
return list(data.values())
@staticmethod
def _generate_query(project_id, db_id):
query = (
db.session.query(Mapping, SourceDB, BMSServer, Config)
.join(BMSServer)
.join(SourceDB)
.outerjoin(Config) # might not exists at some point
.order_by(Mapping.rac_node)
)
if project_id:
query = query.filter(SourceDB.project_id == project_id)
if db_id:
query = query.filter(Mapping.db_id == db_id)
return query.all()
@classmethod
def _add_main_data(cls, mapping, source_db, config):
return {
'id': mapping.id,
'db_id': mapping.db_id,
'bms': [],
'wave_id': source_db.wave_id,
'source_hostname': source_db.server,
'db_name': source_db.db_name,
'oracle_version': source_db.oracle_version,
'oracle_release': source_db.oracle_release,
'db_type': source_db.db_type.value,
'is_rac': source_db.db_type == SourceDBType.RAC,
'fe_rac_nodes': source_db.fe_rac_nodes,
'is_configured': config.is_configured if config else False,
'is_deployed': cls._check_if_deployed(source_db),
'labels': [],
'editable': not does_db_have_operation(source_db.id),
}
@staticmethod
def _check_if_deployed(source_db):
return source_db.status in DEPLOYED_STATUSES
@staticmethod
def _add_bms_data(bms_server):
return {
'id': bms_server.id,
'name': bms_server.name,
# 'rac_node': mapping.rac_node,
}
@staticmethod
def _add_label_data(label):
return {
'id': label.id,
'name': label.name
}
class AddMappingService:
@classmethod
def run(cls, db_id, bms_ids, wave_id=None, fe_rac_nodes=None):
cls._validate(db_id, bms_ids)
source_db = SourceDB.query.get_or_404(db_id)
source_db.wave_id = wave_id
db.session.add(source_db)
if fe_rac_nodes is not None:
source_db.fe_rac_nodes = fe_rac_nodes
db.session.add(source_db)
mappings = []
for index, bms_id in enumerate(bms_ids, 1):
mappings.append(Mapping(
db_id=db_id,
bms_id=bms_id,
rac_node=index if source_db.is_rac else None,
))
db.session.add_all(mappings)
db.session.commit()
return mappings
@classmethod
def _validate(cls, db_id, bms_ids):
validate_db(db_id)
for bms_id in bms_ids:
validate_bms(bms_id)
class EditMappingService:
@classmethod
def run(cls, db_id, new_bms_ids, wave_id, fe_rac_nodes):
cls._validate(wave_id, new_bms_ids, db_id)
source_db = SourceDB.query.get_or_404(db_id)
if fe_rac_nodes is not None:
source_db.fe_rac_nodes = fe_rac_nodes
db.session.add(source_db)
if source_db.wave_id != wave_id:
source_db.wave_id = wave_id
db.session.add(source_db)
cls._re_create_mappings(source_db, new_bms_ids)
db.session.commit()
@classmethod
def _re_create_mappings(cls, source_db, new_bms_ids):
db_id = source_db.id
qs = db.session.query(Mapping) \
.with_entities(Mapping.bms_id) \
.filter(Mapping.db_id == db_id) \
.all()
existing_bms_ids = [x[0] for x in qs]
# re-create mappings and clear config in case there are some changes
if set(existing_bms_ids) != set(new_bms_ids):
# delete all mappings for specific db
db.session.query(Mapping).filter(Mapping.db_id == db_id)\
.delete(synchronize_session=False)
# create new mappings for db
for index, bms_id in enumerate(new_bms_ids, 1):
mapping = Mapping(
db_id=db_id,
bms_id=bms_id,
rac_node=index if source_db.is_rac else None,
)
db.session.add(mapping)
if not new_bms_ids:
source_db.wave_id = None
db.session.add(source_db)
cls._clear_db_config(source_db)
@staticmethod
def _clear_db_config(source_db):
config = source_db.config
if config:
clear_bms_target_params(config)
@classmethod
def _validate(cls, wave_id, bms_ids, db_id):
for bms_id in bms_ids:
validate_bms(bms_id, db_id=db_id)
if wave_id:
validate_wave(wave_id)