freezer/freezer/backup.py

303 lines
11 KiB
Python

"""
(c) Copyright 2014,2015 Hewlett-Packard Development Company, L.P.
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.
Freezer Backup modes related functions
"""
import logging
import os
import time
from freezer import config
from freezer import lvm
from freezer import utils
from freezer import vss
from freezer import winutils
home = os.path.expanduser("~")
def backup_mode_sql_server(backup_opt_dict, storage):
"""
Execute a SQL Server DB backup. Currently only backups with shadow
copy are supported. This mean, as soon as the shadow copy is created
the db writes will be blocked and a checkpoint will be created, as soon
as the backup finish the db will be unlocked and the backup will be
uploaded. A sql_server.conf_file is required for this operation.
"""
with open(backup_opt_dict.sql_server_conf, 'r') as sql_conf_file_fd:
parsed_config = config.ini_parse(sql_conf_file_fd.read())
sql_server_instance = parsed_config["instance"]
# Dirty hack - please remove any modification of backup_opt_dict
backup_opt_dict.sql_server_instance = sql_server_instance
try:
winutils.stop_sql_server(sql_server_instance)
backup(backup_opt_dict, storage, backup_opt_dict.engine)
finally:
if not backup_opt_dict.snapshot:
# if snapshot is false, wait until the backup is complete
# to start sql server again
winutils.start_sql_server(sql_server_instance)
def backup_mode_mysql(backup_opt_dict, storage):
"""
Execute a MySQL DB backup. currently only backup with lvm snapshots
are supported. This mean, just before the lvm snap vol is created,
the db tables will be flushed and locked for read, then the lvm create
command will be executed and after that, the table will be unlocked and
the backup will be executed. It is important to have the available in
backup_args.mysql_conf the file where the database host, name, user,
password and port are set.
"""
try:
import pymysql as MySQLdb
except ImportError:
raise ImportError('Please install PyMySQL module')
if not backup_opt_dict.mysql_conf:
raise ValueError('MySQL: please provide a valid config file')
with open(backup_opt_dict.mysql_conf, 'r') as mysql_file_fd:
parsed_config = config.ini_parse(mysql_file_fd.read())
# Initialize the DB object and connect to the db according to
# the db mysql backup file config
try:
backup_opt_dict.mysql_db_inst = MySQLdb.connect(
host=parsed_config.get("host", False),
port=int(parsed_config.get("port", 3306)),
user=parsed_config.get("user", False),
passwd=parsed_config.get("password", False))
except Exception as error:
raise Exception('[*] MySQL: {0}'.format(error))
# Execute backup
backup(backup_opt_dict, storage, backup_opt_dict.engine)
def backup_mode_mongo(backup_opt_dict, storage):
"""
Execute the necessary tasks for file system backup mode
"""
try:
import pymongo
except ImportError:
raise ImportError('please install pymongo module')
logging.info('[*] MongoDB backup is being executed...')
logging.info('[*] Checking is the localhost is Master/Primary...')
mongodb_port = '27017'
local_hostname = backup_opt_dict.hostname
db_host_port = '{0}:{1}'.format(local_hostname, mongodb_port)
mongo_client = pymongo.MongoClient(db_host_port)
master_dict = dict(mongo_client.admin.command("isMaster"))
mongo_me = master_dict['me']
mongo_primary = master_dict['primary']
if mongo_me == mongo_primary:
backup(backup_opt_dict, storage, backup_opt_dict.engine)
else:
logging.warning('[*] localhost {0} is not Master/Primary,\
exiting...'.format(local_hostname))
return True
class BackupOs:
def __init__(self, client_manager, container, storage):
"""
:param client_manager:
:param container:
:param storage:
:type storage: freezer.swift.SwiftStorage
:return:
"""
self.client_manager = client_manager
self.container = container
self.storage = storage
def backup_nova(self, instance_id):
"""
Implement nova backup
:param instance_id: Id of the instance for backup
:return:
"""
instance_id = instance_id
client_manager = self.client_manager
nova = client_manager.get_nova()
instance = nova.servers.get(instance_id)
glance = client_manager.get_glance()
if instance.__dict__['OS-EXT-STS:task_state']:
time.sleep(5)
instance = nova.servers.get(instance)
image_id = nova.servers.create_image(instance,
"snapshot_of_%s" % instance_id)
image = glance.images.get(image_id)
while image.status != 'active':
time.sleep(5)
try:
image = glance.images.get(image_id)
except Exception as e:
logging.error(e)
stream = client_manager.download_image(image)
package = "{0}/{1}".format(instance_id, utils.DateTime.now().timestamp)
logging.info("[*] Uploading image to swift")
headers = {"x-object-meta-name": instance._info['name'],
"x-object-meta-flavor-id": instance._info['flavor']['id']}
self.storage.add_stream(stream, package, headers)
logging.info("[*] Deleting temporary image")
glance.images.delete(image)
def backup_cinder_by_glance(self, volume_id):
"""
Implements cinder backup:
1) Gets a stream of the image from glance
2) Stores resulted image to the swift as multipart object
:param volume_id: id of volume for backup
"""
client_manager = self.client_manager
cinder = client_manager.get_cinder()
volume = cinder.volumes.get(volume_id)
logging.debug("Creation temporary snapshot")
snapshot = client_manager.provide_snapshot(
volume, "backup_snapshot_for_volume_%s" % volume_id)
logging.debug("Creation temporary volume")
copied_volume = client_manager.do_copy_volume(snapshot)
logging.debug("Creation temporary glance image")
image = client_manager.make_glance_image("name", copied_volume)
logging.debug("Download temporary glance image {0}".format(image.id))
stream = client_manager.download_image(image)
package = "{0}/{1}".format(volume_id, utils.DateTime.now().timestamp)
logging.debug("Uploading image to swift")
headers = {}
self.storage.add_stream(stream, package, headers=headers)
logging.debug("Deleting temporary snapshot")
client_manager.clean_snapshot(snapshot)
logging.debug("Deleting temporary volume")
cinder.volumes.delete(copied_volume)
logging.debug("Deleting temporary image")
client_manager.get_glance().images.delete(image.id)
def backup_cinder(self, volume_id, name=None, description=None):
client_manager = self.client_manager
cinder = client_manager.get_cinder()
cinder.backups.create(volume_id, self.container, name, description)
def snapshot_create(backup_opt_dict):
"""
Calls the code to take fs snapshots, depending on the platform
:param backup_opt_dict:
:return: boolean value, True if snapshot has been taken, false otherwise
"""
if winutils.is_windows():
if backup_opt_dict.snapshot:
# Create a shadow copy.
backup_opt_dict.shadow_path, backup_opt_dict.shadow = \
vss.vss_create_shadow_copy(backup_opt_dict.windows_volume)
backup_opt_dict.path_to_backup = winutils.use_shadow(
backup_opt_dict.path_to_backup,
backup_opt_dict.windows_volume)
# execute this after the snapshot creation
if backup_opt_dict.mode == 'sqlserver':
winutils.start_sql_server(backup_opt_dict.sql_server_instance)
return True
return False
else:
return lvm.lvm_snap(backup_opt_dict)
def snapshot_remove(backup_opt_dict, shadow, windows_volume):
if winutils.is_windows():
# Delete the shadow copy after the backup
vss.vss_delete_shadow_copy(shadow, windows_volume)
else:
# Unmount and remove lvm snapshot volume
lvm.lvm_snap_remove(backup_opt_dict)
def backup(backup_opt_dict, storage, engine):
"""
:param backup_opt_dict:
:param storage:
:type storage: freezer.storage.base.Storage
:param engine: Backup Engine
:type engine: freezer.engine.engine.BackupEngine
:return:
"""
backup_media = backup_opt_dict.backup_media
time_stamp = utils.DateTime.now().timestamp
backup_opt_dict.time_stamp = time_stamp
if backup_media == 'fs':
snapshot_taken = snapshot_create(backup_opt_dict)
try:
filepath = '.'
chdir_path = os.path.expanduser(
os.path.normpath(backup_opt_dict.path_to_backup.strip()))
if not os.path.isdir(chdir_path):
filepath = os.path.basename(chdir_path)
chdir_path = os.path.dirname(chdir_path)
os.chdir(chdir_path)
hostname_backup_name = backup_opt_dict.hostname_backup_name
backup_instance = storage.create_backup(
hostname_backup_name,
backup_opt_dict.no_incremental,
backup_opt_dict.max_level,
backup_opt_dict.always_level,
backup_opt_dict.restart_always_level,
time_stamp=time_stamp)
engine.backup(filepath, backup_instance)
finally:
# whether an error occurred or not, remove the snapshot anyway
if snapshot_taken:
snapshot_remove(backup_opt_dict, backup_opt_dict.shadow,
backup_opt_dict.windows_volume)
return
backup_os = BackupOs(backup_opt_dict.client_manager,
backup_opt_dict.container,
storage)
if backup_media == 'nova':
logging.info('[*] Executing nova backup')
backup_os.backup_nova(backup_opt_dict.nova_inst_id)
elif backup_media == 'cindernative':
logging.info('[*] Executing cinder backup')
backup_os.backup_cinder(backup_opt_dict.cindernative_vol_id)
elif backup_media == 'cinder':
logging.info('[*] Executing cinder snapshot')
backup_os.backup_cinder_by_glance(backup_opt_dict.cinder_vol_id)
else:
raise Exception('unknown parameter backup_media %s' % backup_media)