backend/bms_app/services/ansible/restore.py (142 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.
import os
import yaml
from bms_app import settings
from bms_app.inventory_manager.services import get_client_ip
from bms_app.services.gcs import copy_blob, upload_blob_from_string
from bms_app.services.restore_config import RestoreConfigValidationsService
from .mixins import FileUploadMixin
class AnsibleBaseDataTransferConfigService(FileUploadMixin):
"""Generate ansible inventory and config files and upload them to GCS.
Files:
- inventory - used by all other playbooks
- pfile.ora - db parameters file
"""
INVENTORY_FILE = 'inventory'
def __init__(self, db_mappings_object, gcs_config_dir):
self.db_mappings_object = db_mappings_object
self.gcs_config_dir = gcs_config_dir or ''
def run(self):
inventory_data = self._generate_inventory_data()
self._save_inventory(inventory_data)
self._save_config_files()
def _save_inventory(self, inventory_data):
self._upload_yaml(
data=inventory_data,
file_name=self.INVENTORY_FILE
)
def _save_config_files(self):
self._copy_file(
self.db_mappings_object.db.restore_config.pfile_file,
'pfile.ora'
)
def _copy_file(self, gcs_key, destination_file_name):
"""Copy file to ansible config dir."""
copy_blob(
settings.GCS_BUCKET,
gcs_key,
settings.GCS_BUCKET,
os.path.join(self.gcs_config_dir, destination_file_name)
)
def _upload_yaml(self, data, file_name):
"""Upload file in yaml format to GCS."""
file_content = yaml.safe_dump(data)
gcs_key = os.path.join(self.gcs_config_dir, file_name)
upload_blob_from_string(settings.GCS_BUCKET, gcs_key, file_content)
def _generate_inventory_data(self):
"""Generate ansible file inventory data."""
inventory_host = self._generate_main_inventory_hosts()
inventory_data = self._generate_inventory_file_data(inventory_host)
return inventory_data
def _generate_main_inventory_hosts(self):
if self.db_mappings_object.db.is_rac:
raise NotImplementedError('rac db is not supported yet')
# return cls._generate_rac_install_inventory_host(db_map_obj)
return self._generate_si_install_inventory_host()
def _generate_si_install_inventory_host(self):
"""Single Instance config."""
source_db = self.db_mappings_object.db
config = source_db.config
bms = self.db_mappings_object.mappings[0].bms
config_data = {
'ansible_ssh_host': get_client_ip(bms),
'ansible_ssh_user': 'customeradmin',
'ansible_ssh_private_key_file': '/root/.ssh/id_rsa_bms_toolkit',
'ansible_ssh_extra_args': '-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o IdentityAgent=no',
'oracle_ver': source_db.oracle_version,
'oracle_edition': source_db.oracle_edition,
'db_name': config.db_params_values['db_name'],
'compatible_rdbms': config.misc_config_values['compatible_rdbms'],
'oracle_user': config.install_config_values['oracle_user'],
'oracle_group': config.install_config_values['oracle_group'],
'oracle_root': config.misc_config_values['oracle_root'],
'home_name': config.install_config_values['home_name'],
'sm_token': bms.secret_name,
}
if 'swap_blk_device' in config.misc_config_values:
config_data['swap_blk_device'] = config.misc_config_values['swap_blk_device']
self._add_extra_config_data(source_db, bms, config_data)
return {
bms.name: config_data
}
@staticmethod
def _add_extra_config_data(source_db, bms, config_data):
pass
@staticmethod
def _generate_inventory_file_data(hosts):
"""Generate complete inventory data structure."""
return {
'all': {
'children': {
'dbasm': {
'hosts': hosts
}
}
}
}
class AnsiblePreRestoreConfigService(AnsibleBaseDataTransferConfigService):
"""Generate ansible inventory for PreRestore"""
def _add_extra_config_data(self, source_db, bms, config_data):
restore_config = source_db.restore_config
rcv = RestoreConfigValidationsService(source_db)
splitted = [x for x in restore_config.backup_location.split('/') if x]
config_data.update(
{
'backup_bucket': splitted[0],
'pfile_file': 'pfile.ora',
'fuse_direct': True,
'control_file': restore_config.control_file,
'rman_commands': rcv.get_rman_commands(),
}
)
if len(splitted) > 1:
config_data['gcs_backup_folder'] = '/'.join(splitted[1:])
return config_data
class AnsibleRestoreConfigService(AnsibleBaseDataTransferConfigService):
"""Generate ansible inventory for Restore"""
def _save_config_files(self):
super()._save_config_files()
if self.db_mappings_object.db.restore_config.pwd_file:
self._copy_file(
self.db_mappings_object.db.restore_config.pwd_file,
'orapw.file'
)
self._save_rman_command()
def _add_extra_config_data(self, source_db, bms, config_data):
restore_config = self.db_mappings_object.db.restore_config
splitted = [x for x in restore_config.backup_location.split('/') if x]
config_data.update(
{
'backup_bucket': splitted[0],
'pfile_file': 'pfile.ora',
'fuse_direct': True,
'restore_type': restore_config.backup_type.value.lower(),
'control_file': restore_config.control_file,
}
)
if len(splitted) > 1:
config_data['gcs_backup_folder'] = '/'.join(splitted[1:])
if restore_config.pwd_file:
config_data['psw_file'] = restore_config.pwd_file
return config_data
def _save_rman_command(self):
restore_config = self.db_mappings_object.db.restore_config
if restore_config.is_full_backup_type:
template_name = 'rman_restore_db.cmd.j2'
else:
template_name = 'rman_restore_inc.cmd.j2'
upload_blob_from_string(
settings.GCS_BUCKET,
os.path.join(self.gcs_config_dir, template_name),
restore_config.rman_cmd
)
class AnsibleRollbackRestoreConfigService(AnsibleBaseDataTransferConfigService):
"""Generate ansible inventory for RollbackRestore"""
pass
class AnsibleFailOverConfigService(AnsibleBaseDataTransferConfigService):
"""Generate ansible inventory for FailOver"""
pass