Why Gemfury? Push, build, and install  RubyGems npm packages Python packages Maven artifacts PHP packages Go Modules Debian packages RPM packages NuGet packages

Repository URL to install this package:

Details    
Size: Mime:
# 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