← Back to team overview

openstack team mailing list archive

Volume with ZVol : opinion on code

 

Hi,

Following my ideas about using ZFS (ZVol) instead of LVM for the volume (https://lists.launchpad.net/openstack/msg13009.html). I write a piece of code : in attachment or on github/nicolas2bonfils <https://github.com/nicolas2bonfils/cinder/blob/master/cinder/volume/driver_zvol.py> (for line comments or push request). I write and test the code with ZFSOnLinux <http://zfsonlinux.org/> and Debian sid.

What it does :

 * create ZVol instead of LVM,
 * use ISCSI sharing from the existing cinder driver,
 * use ZFS snapshot for the snapshot feature,
 * is working for create/delete volume,
 * can create/delete native ZFS snapshot (see below for the linked
   problem).


What need to be improve :

 * check if native ZFS snapshot can be use for volume snapshot (same
   use/meaning ?),
 * when deleting a volume with snapshot (ZFS one), do we delete the
   derived snapshot or export them as independent volume.


What it does not (yet) :

 * share with native ZFS ISCSI (due to ZFSOnLinux limitation)


Thanks for all your ideas, advice, ...

--- Nicolas de Bonfils
# vim: tabstop=4 shiftwidth=4 softtabstop=4

# Copyright 2012 Nicolas de Bonfils openstack@xxxxxxxxxxxxxxxxxxx
#
#    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.

#TODO(nicolas) : i18n the log message

from cinder.volume.driver import *

class ZVolDriver(ISCSIDriver):
    """
    Drivers for volumes using Zvol instead of LVM.
    Tested with ZFSOnLinux on a Debian sid system

    """

    def __init__(self, *args, **kwargs):
        #TODO(nicolas) : use configuration flag
        self.zfs_bin = "/sbin/zfs"
        self.zpool_bin = "/sbin/zpool"
        super(ZVolDriver, self).__init__(*args, **kwargs)

    def check_for_setup_error(self):
        """Returns an error if prerequisites aren't met"""
        out, err = self._execute('%s' % self.zfs_bin, 'list', run_as_root=True)
        volume_groups = out.split()
        if not FLAGS.volume_group in volume_groups:
            raise exception.Error(_("zfs base volume group '%s' doesn't exist")
                                  % FLAGS.volume_group)

    def _create_volume(self, volume_name, sizestr):
        LOG.debug('Create volume with command "%s create -V %s %s/%s"' % (self.zfs_bin, sizestr, FLAGS.volume_group, volume_name))
        self._try_execute('%s' % self.zfs_bin, 'create', 
						  '-V', sizestr,
                          '%s/%s' % (FLAGS.volume_group, volume_name), 
                          run_as_root=True)

    def _copy_volume(self, srcstr, deststr, size_in_g):
        LOG.debug('copy volume with method "dd if=%s of=%s count=%d bs=1M"' % (srcstr, deststr, (size_in_g * 1024)))
        self._execute('dd', 'if=%s' % srcstr, 'of=%s' % deststr,
                      'count=%d' % (size_in_g * 1024), 'bs=1M',
                      run_as_root=True)

    def _volume_not_present(self, volume_name):
        path_name = '%s/%s' % (FLAGS.volume_group, volume_name)
        out, err = self._execute('%s' % self.zfs_bin, 'list', run_as_root=True)
        volume_search = out.split()
        LOG.debug('List volume with command "%s list"' % self.zfs_bin)
        LOG.debug(volume_search)
        return not path_name in volume_search

    def _delete_volume(self, volume, size_in_g):
        """Deletes a zvol."""
        # zero out old volumes to prevent data leaking between users
        # TODO(ja): reclaiming space should be done lazy and low priority
        self._copy_volume('/dev/zero', self.local_path(volume), size_in_g)
        LOG.debug('Destroy volume with command "%s destroy %s/%s"' % (self.zfs_bin, FLAGS.volume_group, volume['name']))
        self._try_execute('%s' % self.zfs_bin, 'destroy', 
                          '%s/%s' % (FLAGS.volume_group, volume['name']),
                          run_as_root=True)

    def delete_volume(self, volume):
        """Deletes a zvol."""
        if self._volume_not_present(volume['name']):
            # If the volume isn't present, then don't attempt to delete
            return True

		# When snapshots exist, what to do ?
		# remove them or export them as new independent volume
         
        self._delete_volume(volume, volume['size'])

    def create_snapshot(self, snapshot):
        """Creates a snapshot."""
        LOG.debug('Snapshot volume with command "%s snapshot %s/%s"' % (self.zfs_bin, 
                                                                        FLAGS.volume_group, 
                                                                        self._snapshot_full_name(snapshot)))
        self._try_execute('%s' % self.zfs_bin, 'snapshot',
                          '%s/%s' % (FLAGS.volume_group, self._snapshot_full_name(snapshot)), 
                          run_as_root=True)

    def delete_snapshot(self, snapshot):
        """Deletes a snapshot."""
        if self._volume_not_present(self._snapshot_full_name(snapshot)):
            # If the snapshot isn't present, then don't attempt to delete
            return True

        self._delete_volume(snapshot, snapshot['volume_size'])

    def local_path(self, volume):
        """Give full path to the system /dev object"""
        full_name = ""
        # if we got a volume name, then it's a snapshot so create the full path
        # otherwise it a "regular" volume
        
        # it's a hack to know if volume "respond to" volume_name key.
        #FIXME(nicolas) : volume is sqlalchemy object, how to query existence for a property/method ?
        try:
            volume_name = volume['volume_name']
            full_name = self._snapshot_full_name(volume)
        except Exception as e:
            full_name = volume['name']
        return "/dev/zvol/%s/%s" % (FLAGS.volume_group, full_name)
        
    def _snapshot_full_name(self, snapshot):
        """Give the snapshot full name : volume name + snapshot name with '@' in the middle """
        return '%s@%s' % (snapshot['volume_name'], snapshot['name'])