
And pep8 fixing (suddenly this pull request was rejected by jenkins and pep8. so this commit also contains some small style changes for pep8.) Currently the default value for logging is INFO and it is hardcoded. Additional command will be helpful for debug/development process. Implements blueprint: logging-level-cli Change-Id: I1105e3da277e2a948f3ff50b04751169eddf26ed
318 lines
12 KiB
Python
318 lines
12 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 LVM related functions
|
|
"""
|
|
|
|
import logging
|
|
import os
|
|
import re
|
|
import subprocess
|
|
|
|
from freezer import utils
|
|
|
|
|
|
def lvm_snap_remove(backup_opt_dict):
|
|
"""
|
|
Unmount the snapshot and removes it
|
|
|
|
:param backup_opt_dict.lvm_dirmount: mount point of the snapshot
|
|
:param backup_opt_dict.lvm_volgroup: volume group to which the lv belongs
|
|
:param backup_opt_dict.lvm_snapname: name of the snapshot lv
|
|
:return: None, raises on error
|
|
"""
|
|
os.chdir(backup_opt_dict.work_dir)
|
|
_umount(backup_opt_dict.lvm_dirmount)
|
|
lv = os.path.join('/dev',
|
|
backup_opt_dict.lvm_volgroup,
|
|
backup_opt_dict.lvm_snapname)
|
|
_lvremove(lv)
|
|
logging.info('[*] Snapshot volume {0} removed'.format(lv))
|
|
|
|
|
|
def lvm_snap(backup_opt_dict):
|
|
"""
|
|
Checks the provided parameters and create the lvm snapshot if requested
|
|
|
|
The path_to_backup might be adjusted in case the user requested
|
|
a lvm snapshot without specifying an exact path for the snapshot
|
|
(lvm_auto_snap).
|
|
The assumption in this case is that the user wants to use the lvm snapshot
|
|
capability to backup the specified filesystem path, leaving out all
|
|
the rest of the parameters which will guessed and set by freezer.
|
|
|
|
if a snapshot is requested using the --snapshot flag, but lvm_auto_snap
|
|
is not provided, then path_to_backup is supposed to be the path to backup
|
|
*before* any information about the snapshot is added and will be
|
|
adjusted.
|
|
|
|
:param backup_opt_dict: the configuration dict
|
|
:return: True if the snapshot has been taken, False otherwise
|
|
"""
|
|
if backup_opt_dict.snapshot:
|
|
if not backup_opt_dict.lvm_auto_snap:
|
|
# 1) the provided path_to_backup has the meaning of
|
|
# the lvm_auto_snap and is therefore copied into it
|
|
# 2) the correct value of path_to_backup, which takes into
|
|
# consideration the snapshot mount-point, is cleared
|
|
# and will be calculated by freezer
|
|
backup_opt_dict.lvm_auto_snap =\
|
|
backup_opt_dict.path_to_backup
|
|
backup_opt_dict.path_to_backup = ''
|
|
|
|
if backup_opt_dict.lvm_auto_snap:
|
|
# adjust/check lvm parameters according to provided lvm_auto_snap
|
|
lvm_info = get_lvm_info(backup_opt_dict.lvm_auto_snap)
|
|
|
|
if not backup_opt_dict.lvm_volgroup:
|
|
backup_opt_dict.lvm_volgroup = lvm_info['volgroup']
|
|
|
|
if not backup_opt_dict.lvm_srcvol:
|
|
backup_opt_dict.lvm_srcvol = lvm_info['srcvol']
|
|
|
|
path_to_backup = os.path.join(backup_opt_dict.lvm_dirmount,
|
|
lvm_info['snap_path'])
|
|
if backup_opt_dict.path_to_backup:
|
|
# path_to_backup is user-provided, check if consistent
|
|
if backup_opt_dict.path_to_backup != path_to_backup:
|
|
raise Exception('Path to backup mismatch. '
|
|
'provided: {0}, should be LVM-mounted: {1}'.
|
|
format(backup_opt_dict.path_to_backup,
|
|
path_to_backup))
|
|
else:
|
|
# path_to_backup not provided: use the one calculated above
|
|
backup_opt_dict.path_to_backup = path_to_backup
|
|
|
|
if not validate_lvm_params(backup_opt_dict):
|
|
logging.info('[*] No LVM requested/configured')
|
|
return False
|
|
|
|
utils.create_dir(backup_opt_dict.lvm_dirmount)
|
|
|
|
lvm_create_command = (
|
|
'{0} --size {1} --snapshot --permission {2} '
|
|
'--name {3} {4}'.format(
|
|
utils.find_executable('lvcreate'),
|
|
backup_opt_dict.lvm_snapsize,
|
|
('r' if backup_opt_dict.lvm_snapperm == 'ro'
|
|
else backup_opt_dict.lvm_snapperm),
|
|
backup_opt_dict.lvm_snapname,
|
|
backup_opt_dict.lvm_srcvol))
|
|
|
|
# If backup mode is mysql, then the db will be flushed and read locked
|
|
# before the creation of the lvm snap
|
|
if backup_opt_dict.mode == 'mysql':
|
|
cursor = backup_opt_dict.mysql_db_inst.cursor()
|
|
cursor.execute('FLUSH TABLES WITH READ LOCK')
|
|
backup_opt_dict.mysql_db_inst.commit()
|
|
|
|
lvm_process = subprocess.Popen(
|
|
lvm_create_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE, shell=True,
|
|
executable=utils.find_executable('bash'))
|
|
(lvm_out, lvm_err) = lvm_process.communicate()
|
|
|
|
# Unlock MySQL Tables if backup is == mysql
|
|
# regardless of the snapshot being taken or not
|
|
if backup_opt_dict.mode == 'mysql':
|
|
cursor.execute('UNLOCK TABLES')
|
|
backup_opt_dict.mysql_db_inst.commit()
|
|
cursor.close()
|
|
backup_opt_dict.mysql_db_inst.close()
|
|
|
|
if lvm_process.returncode:
|
|
raise Exception('lvm snapshot creation error: {0}'.format(lvm_err))
|
|
|
|
logging.debug('[*] {0}'.format(lvm_out))
|
|
logging.warning('[*] Logical volume "{0}" created'.
|
|
format(backup_opt_dict.lvm_snapname))
|
|
|
|
# Guess the file system of the provided source volume and st mount
|
|
# options accordingly
|
|
filesys_type = utils.get_vol_fs_type(backup_opt_dict.lvm_srcvol)
|
|
mount_options = '-o {}'.format(backup_opt_dict.lvm_snapperm)
|
|
if 'xfs' == filesys_type:
|
|
mount_options = ' -onouuid '
|
|
# Mount the newly created snapshot to dir_mount
|
|
abs_snap_name = '/dev/{0}/{1}'.format(
|
|
backup_opt_dict.lvm_volgroup,
|
|
backup_opt_dict.lvm_snapname)
|
|
mount_command = '{0} {1} {2} {3}'.format(
|
|
utils.find_executable('mount'),
|
|
mount_options,
|
|
abs_snap_name,
|
|
backup_opt_dict.lvm_dirmount)
|
|
mount_process = subprocess.Popen(
|
|
mount_command, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE, shell=True,
|
|
executable=utils.find_executable('bash'))
|
|
mount_err = mount_process.communicate()[1]
|
|
if 'already mounted' in mount_err:
|
|
logging.warning('[*] Volume {0} already mounted on {1}\
|
|
'.format(abs_snap_name, backup_opt_dict.lvm_dirmount))
|
|
return True
|
|
if mount_err:
|
|
logging.error("[*] Snapshot mount error. Removing snapshot")
|
|
lvm_snap_remove(backup_opt_dict)
|
|
raise Exception('lvm snapshot mounting error: {0}'.format(mount_err))
|
|
else:
|
|
logging.warning(
|
|
'[*] Volume {0} succesfully mounted on {1}'.format(
|
|
abs_snap_name, backup_opt_dict.lvm_dirmount))
|
|
|
|
return True
|
|
|
|
|
|
def get_lvm_info(lvm_auto_snap):
|
|
"""
|
|
Take a file system path as argument as backup_opt_dict.path_to_backup
|
|
and return a list containing lvm_srcvol, lvm_volgroup
|
|
where the path is mounted on.
|
|
|
|
:param lvm_auto_snap: the original file system path where backup needs
|
|
to be executed
|
|
:returns: a dict containing the keys 'volgroup', 'srcvol' and 'snap_path'
|
|
"""
|
|
|
|
mount_point_path, snap_path = utils.get_mount_from_path(lvm_auto_snap)
|
|
|
|
with open('/proc/mounts', 'r') as mount_fd:
|
|
mount_points = mount_fd.readlines()
|
|
lvm_volgroup, lvm_srcvol, lvm_device = lvm_guess(
|
|
mount_point_path, mount_points, '/proc/mounts')
|
|
|
|
if not lvm_device:
|
|
mount_process = subprocess.Popen(
|
|
['mount'], stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
env=os.environ)
|
|
mount_out, mount_err = mount_process.communicate()
|
|
mount_points = mount_out.split('\n')
|
|
lvm_volgroup, lvm_srcvol, lvm_device = lvm_guess(
|
|
mount_point_path, mount_points, 'mount')
|
|
|
|
if not lvm_device:
|
|
raise Exception(
|
|
'Cannot find {0} in {1}, please provide volume group and '
|
|
'volume name explicitly'.format(mount_point_path, mount_points))
|
|
|
|
lvm_params = {'volgroup': lvm_volgroup,
|
|
'srcvol': lvm_device,
|
|
'snap_path': snap_path}
|
|
|
|
return lvm_params
|
|
|
|
|
|
def lvm_guess(mount_point_path, mount_points, source='/proc/mounts'):
|
|
"""Guess lvm vol group and vol name from mount point
|
|
|
|
Extract the vol group and vol name from given list
|
|
of mount_points and mount_point_path
|
|
|
|
:param mount_point_path: mount path
|
|
:param mount_points: list of currently mounted devices
|
|
:return: a list containing volume group, volume name and full device path
|
|
"""
|
|
|
|
lvm_volgroup = lvm_srcvol = lvm_device = None
|
|
for mount_line in mount_points:
|
|
if source == '/proc/mounts':
|
|
device, mount_path = mount_line.split(' ')[0:2]
|
|
elif source == 'mount':
|
|
mount_list = mount_line.split(' ')[0:3]
|
|
device = mount_list[0]
|
|
mount_path = mount_list[2]
|
|
if mount_point_path.strip() == mount_path.strip():
|
|
mount_match = re.search(
|
|
r'/dev/mapper/(\w.+?\w)-(\w.+?\w)$', device)
|
|
if mount_match:
|
|
lvm_volgroup = mount_match.group(1).replace('--', '-')
|
|
lvm_srcvol = mount_match.group(2).replace('--', '-')
|
|
lvm_device = u'/dev/{0}/{1}'.format(lvm_volgroup, lvm_srcvol)
|
|
break
|
|
|
|
return lvm_volgroup, lvm_srcvol, lvm_device
|
|
|
|
|
|
def validate_lvm_params(backup_opt_dict):
|
|
"""
|
|
Validates the parameters and raises in case of missing values
|
|
|
|
:param backup_opt_dict:
|
|
:return: False is snapshot is not requested,
|
|
True snapshot is requested and parameters are valid
|
|
"""
|
|
if backup_opt_dict.lvm_snapperm not in ('ro', 'rw'):
|
|
raise ValueError('[*] Error: Invalid value for option lvm-snap-perm: '
|
|
'{}'.format(backup_opt_dict.lvm_snapperm))
|
|
|
|
if not backup_opt_dict.path_to_backup:
|
|
raise ValueError('[*] Error: no path-to-backup and '
|
|
'no lvm-auto-snap provided')
|
|
|
|
if not backup_opt_dict.lvm_srcvol and not backup_opt_dict.lvm_volgroup:
|
|
# no lvm parameters provided, assume lvm snapshot is not requested
|
|
return False
|
|
|
|
if not backup_opt_dict.lvm_srcvol:
|
|
raise ValueError('[*] Error: no lvm-srcvol and '
|
|
'no lvm-auto-snap provided')
|
|
if not backup_opt_dict.lvm_volgroup:
|
|
raise ValueError('[*] Error: no lvm-volgroup and '
|
|
'no lvm-auto-snap provided')
|
|
|
|
logging.info('[*] Source LVM Volume: {0}'.format(
|
|
backup_opt_dict.lvm_srcvol))
|
|
logging.info('[*] LVM Volume Group: {0}'.format(
|
|
backup_opt_dict.lvm_volgroup))
|
|
logging.info('[*] Snapshot name: {0}'.format(
|
|
backup_opt_dict.lvm_snapname))
|
|
logging.info('[*] Snapshot size: {0}'.format(
|
|
backup_opt_dict.lvm_snapsize))
|
|
logging.info('[*] Directory where the lvm snaphost will be mounted on:'
|
|
' {0}'.format(backup_opt_dict.lvm_dirmount.strip()))
|
|
logging.info('[*] Path to backup (including snapshot): {0}'
|
|
.format(backup_opt_dict.path_to_backup))
|
|
|
|
return True
|
|
|
|
|
|
def _umount(path):
|
|
# TODO: check if cwd==path and change working directory to unmount ?
|
|
umount_proc = subprocess.Popen('{0} -l -f {1}'.format(
|
|
utils.find_executable('umount'), path),
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
shell=True, executable=utils.find_executable('bash'))
|
|
(umount_out, mount_err) = umount_proc.communicate()
|
|
|
|
if umount_proc.returncode:
|
|
raise Exception('impossible to umount {0}. {1}'
|
|
.format(path, mount_err))
|
|
|
|
logging.info('[*] Volume {0} unmounted'.format(path))
|
|
|
|
|
|
def _lvremove(lv):
|
|
lvremove_proc = subprocess.Popen(
|
|
'{0} -f {1}'.format(utils.find_executable('lvremove'), lv),
|
|
stdin=subprocess.PIPE,
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.PIPE, shell=True,
|
|
executable=utils.find_executable('bash'))
|
|
output, error = lvremove_proc.communicate()
|
|
if lvremove_proc.returncode:
|
|
raise Exception(
|
|
'unable to remove snapshot {0}. {1}'.format(lv, error))
|