Repository URL to install this package:
|
Version:
2.5 ▾
|
# Copyright 2015 TrilioData Inc.
# All Rights Reserved.
import copy
import os
import uuid
import json
import time
import rbd
import rados
import re
from tempfile import mkstemp
try:
from oslo_log import log as logging
except ImportError:
from nova.openstack.common import log as logging
try:
from oslo_utils import encodeutils
except ImportError:
from nova.openstack.common import strutils as encodeutils
try:
from oslo_config import cfg
except ImportError:
from oslo.config import cfg
try:
from oslo_utils import fileutils
except ImportError:
from nova.openstack.common import fileutils
from keystoneclient.auth.identity import v2 as v2_auth
from keystoneclient import session
from nova import utils
try:
import nova.virt.libvirt.rbd_utils as rbd_utils
except BaseException:
from nova.virt.libvirt.storage import rbd_utils
import nova.virt.libvirt.utils as libvirt_utils
from nova.virt.libvirt.imagebackend import Rbd
from contego.nova.extension.driver import qemuimages
from contego.nova.extension.driver import vault
from contego import utils as contego_utils
from cinder_backend import CinderBackend
rbd_opts = [
cfg.StrOpt('ceph_dir',
default='/etc/ceph/',
help='by default ceph config dir'
' It can be override depending on user configuration'),
cfg.StrOpt('keyring_ext',
default='.keyring,.secret,.key',
help='by default ceph config dir'
' It can be override depending on user configuration'),
]
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
CONF.register_opts(rbd_opts, 'ceph')
_DELTA_DIRS = 'rbd_delta_files'
def enqueue_output(out, queue):
line = out.read(17)
while line:
line = out.read(17)
queue.put(line)
out.close()
def get_new_context(context):
try:
auth_plugin = v2_auth.Password(
auth_url=CONF.neutron.admin_auth_url,
username="admin",
password=CONF.neutron.admin_password,
tenant_id=context.tenant)
keystone_session = session.Session.load_from_conf_options(
CONF, "neutron")
new_token = auth_plugin.get_token(keystone_session)
new_context = copy.deepcopy(context)
new_context.auth_token = new_token
return new_context
except Exception:
return context
class make_copy_ceph_conf:
def __init__(self, outerclass):
self.outerclass = outerclass
self.ceph_conf_path = CONF.ceph.ceph_dir + "ceph.conf"
self.temp_ceph_conf_path = \
self.outerclass._get_temp_file_path(True)
self.ceph_default_format_option = "rbd default format = 2"
def __enter__(self):
with open(self.ceph_conf_path, "r") as f,\
open(self.temp_ceph_conf_path, "w") as f1:
for line in f:
f1.write(line)
if '[global]' in line:
f1.write(self.ceph_default_format_option + '\n')
return self.temp_ceph_conf_path
def __exit__(self, type, value, traceback):
os.remove(self.temp_ceph_conf_path)
class RBDBaseBackend(Rbd, CinderBackend):
key_file = None
key_user = None
def __init__(self, **kwargs):
Rbd.__init__(self, **kwargs)
CinderBackend.__init__(self, **kwargs)
self.backend = 'rbd'
def prepare_snapshot(self, devices, **kwargs):
pass
def rbd_keyring_search_and_execute(self, command, *args, **kwargs):
out = None
err = None
if self.key_file is not None and self.key_user is not None:
out, err = utils.execute('rbd', command, '--name', self.key_user,
'-k', self.key_file, *args, **kwargs)
return out, err
else:
for keyring in os.listdir(CONF.ceph.ceph_dir):
if keyring.endswith(tuple(CONF.ceph.keyring_ext.split(","))):
keyring = CONF.ceph.ceph_dir + keyring
try:
auth_kwargs = {}
out1, err1 = utils.execute('ceph-authtool', '-l',
keyring, **auth_kwargs)
name = out1.splitlines()[0][1:-1]
out, err = utils.execute('rbd', command, '--name',
name, '-k', keyring, *args,
**kwargs)
self.key_file = keyring
self.key_user = name
return out, err
except BaseException:
pass
return out, err
def _create_qcow2_from_extents(self, qcow2file, extentsfile,
backing_file, snap_full_name,
progress_tracker_path):
qcow2_info = qemuimages.qemu_img_info(qcow2file)
assert qcow2_info.cluster_size == 1024 * 64
assert qcow2_info.backing_file is None
#backing_file = self._get_temp_file_path(True)
# libvirt_utils.create_cow_image(None, backing_file,
# qcow2_info.virtual_size)
ceph_conf_path = os.path.join(CONF.ceph.ceph_dir, "ceph.conf")
pool_name = str(snap_full_name.split('/')[0].strip())
snap_name = str(snap_full_name.split('/')[1].strip())
assert snap_name is not None
args = [pool_name]
kwargs = {}
self.rbd_keyring_search_and_execute('ls', *args, **kwargs)
if not self.key_user:
raise Exception("Could not find any valid ceph key to use. "
"Please make sure 'rbd ls -l' can be run successfully")
#./qemu-img convert -f raw -O qcow2 -W
# -E ../tests/qemu-img/extents-real -D /tmp/base.qcow2
# rbd:volumes/volume-27b7e647-2de6-441d-9b0c-552d82fc0bfd:id=cinder:conf=/etc/ceph/ceph.conf
# /home/centos/output.qcow2
with make_copy_ceph_conf(self) as temp_path:
try:
backing_file = 'rbd:' + backing_file
backing_file += ':id=' + self.key_user.split('.')[1]
backing_file += ':conf=' + temp_path
volume_endpoint = 'rbd:' + snap_full_name
volume_endpoint += ':id=' + self.key_user.split('.')[1]
volume_endpoint += ':conf=' + temp_path
qemu_img_bin = self.get_qemu_img_path()
cmdspec = [qemu_img_bin, 'convert', '-p', '-f',
'raw', '-O', 'qcow2', '-F', 'raw', '-W',
'-E', extentsfile, '-D',
backing_file, volume_endpoint, qcow2file]
cmd = " ".join(cmdspec)
LOG.debug(('_create_qcow2_from_extents cmd %s ' % cmd))
self._execute_qemu_img_and_track_progress(
cmdspec, snap_full_name, qcow2file,
progress_tracker_path)
qemuimages.qemu_img_info(qcow2file)
# remove backing file
out1, err1 = utils.execute(qemu_img_bin, 'rebase',
'-u', qcow2file)
finally:
pass
# os.remove(backing_file)
def update_snapshot_info(self, disk_info, **kwargs):
# TODO update the snapshot size
try:
if 'prev_disk_info' in disk_info and disk_info['prev_disk_info']:
current_snapshot = disk_info['path'].split(':')[1] + "@" + \
disk_info['snapshot_name']
previous_snapshot = encodeutils.safe_encode(
disk_info['prev_disk_info']['snapshot_name'])
diffsize = self.diff_size(current_snapshot,
from_snap=previous_snapshot)
disk_info['size'] = diffsize
except Exception as ex:
LOG.exception(ex)
pass
# if we are able to successfully update, send the latest size
# else send old size which is whole volume
return disk_info
def _size(self, disk_path):
args = [disk_path]
args += ['--format=json']
kwargs = {'run_as_root': True, 'root_helper': 'sudo'}
out, err = self.rbd_keyring_search_and_execute('info', *args, **kwargs)
return json.loads(out)['size']
def _get_temp_file_path(self, remove=False):
"""Makes tmpfile under CONF.instances_path."""
dirpath = CONF.backends.contego_staging_dir or CONF.instances_path
fd, tmp_file = mkstemp(dir=dirpath)
os.close(fd)
if (remove):
os.remove(tmp_file)
return tmp_file
def diff_size(self, snapshot_name, from_snap=None):
"""Calculate the data change size between two snapshots.
Uses the command line diff instead of librbd does not support
export-diff.
:base: Path to snapshot
:diff_file: extents file to write to
:from_snap: If None, it calculates from base image otherwise with
respect to snapshot
"""
args = [snapshot_name, ]
if from_snap:
args += ['--from-snap', from_snap]
args += ['--format=json']
kwargs = {'run_as_root': True, 'root_helper': 'sudo'}
out, err = self.rbd_keyring_search_and_execute('diff', *args, **kwargs)
extents = json.loads(out)
diffsize = 0
for extent in extents:
if extent['exists']:
diffsize += extent['length']
return diffsize
def _export_diff(self, snapshot_name, diff_file, from_snap=None):
"""Calculate changed extents between two snapshots.
Uses the command line diff instead of librbd does not support
export-diff.
:base: Path to snapshot
:diff_file: extents file to write to
:from_snap: If None, it calculates from base image otherwise with
respect to snapshot
"""
args = [snapshot_name, ]
if from_snap:
args += ['--from-snap', from_snap]
kwargs = {'run_as_root': True, 'root_helper': 'sudo'}
out, err = self.rbd_keyring_search_and_execute('diff', *args, **kwargs)
with open(diff_file, 'w') as f:
f.write(out)
return
def get_dev_size(self, filename):
with open(filename, 'rb') as f:
f.seek(0, 2)
size = f.tell()
return size