2015-04-07 23:06:57 -07:00
|
|
|
# Copyright (c) 2014 Cloudbase Solutions SRL
|
|
|
|
# All Rights Reserved.
|
|
|
|
#
|
|
|
|
# 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.
|
|
|
|
|
|
|
|
import os
|
2017-10-06 14:31:21 +03:00
|
|
|
import re
|
2015-04-07 23:06:57 -07:00
|
|
|
import sys
|
|
|
|
|
2016-09-29 13:03:34 +03:00
|
|
|
from os_brick.remotefs import windows_remotefs as remotefs_brick
|
2017-08-21 15:19:06 +03:00
|
|
|
from os_win import constants as os_win_const
|
2015-10-14 15:40:00 +03:00
|
|
|
from os_win import utilsfactory
|
2015-04-07 23:06:57 -07:00
|
|
|
from oslo_config import cfg
|
|
|
|
from oslo_log import log as logging
|
2015-07-08 15:18:13 +03:00
|
|
|
from oslo_utils import fileutils
|
2015-04-07 23:06:57 -07:00
|
|
|
from oslo_utils import units
|
|
|
|
|
2017-05-11 14:22:19 +03:00
|
|
|
from cinder import context
|
2017-05-11 16:52:49 +03:00
|
|
|
from cinder import coordination
|
2015-04-07 23:06:57 -07:00
|
|
|
from cinder import exception
|
2017-03-09 15:49:01 -06:00
|
|
|
from cinder.i18n import _
|
2015-04-07 23:06:57 -07:00
|
|
|
from cinder.image import image_utils
|
2016-03-23 16:39:07 -05:00
|
|
|
from cinder import interface
|
2017-12-13 11:25:23 +02:00
|
|
|
from cinder import objects
|
2017-06-23 15:19:14 +03:00
|
|
|
from cinder import utils
|
2017-04-05 14:52:30 +02:00
|
|
|
from cinder.volume import configuration
|
2015-02-17 18:28:07 +02:00
|
|
|
from cinder.volume.drivers import remotefs as remotefs_drv
|
2015-04-07 23:06:57 -07:00
|
|
|
|
2015-02-16 14:28:14 +02:00
|
|
|
VERSION = '1.1.0'
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
2017-02-15 16:27:15 +02:00
|
|
|
volume_opts = [
|
|
|
|
cfg.StrOpt('smbfs_shares_config',
|
|
|
|
default=r'C:\OpenStack\smbfs_shares.txt',
|
|
|
|
help='File with the list of available smbfs shares.'),
|
|
|
|
cfg.StrOpt('smbfs_default_volume_format',
|
|
|
|
default='vhd',
|
|
|
|
choices=['vhd', 'vhdx'],
|
|
|
|
help=('Default format that will be used when creating volumes '
|
|
|
|
'if no volume format is specified.')),
|
|
|
|
cfg.StrOpt('smbfs_mount_point_base',
|
|
|
|
default=r'C:\OpenStack\_mnt',
|
|
|
|
help=('Base dir containing mount points for smbfs shares.')),
|
2016-12-14 18:52:50 +02:00
|
|
|
cfg.DictOpt('smbfs_pool_mappings',
|
|
|
|
default={},
|
|
|
|
help=('Mappings between share locations and pool names. '
|
|
|
|
'If not specified, the share names will be used as '
|
|
|
|
'pool names. Example: '
|
|
|
|
'//addr/share:pool_name,//addr/share2:pool_name2')),
|
2017-02-15 16:27:15 +02:00
|
|
|
]
|
|
|
|
|
2015-04-07 23:06:57 -07:00
|
|
|
CONF = cfg.CONF
|
2017-04-05 14:52:30 +02:00
|
|
|
CONF.register_opts(volume_opts, group=configuration.SHARED_CONF_GROUP)
|
2017-02-15 16:27:15 +02:00
|
|
|
|
|
|
|
|
2016-03-23 16:39:07 -05:00
|
|
|
@interface.volumedriver
|
2017-10-18 12:32:13 +03:00
|
|
|
class WindowsSmbfsDriver(remotefs_drv.RevertToSnapshotMixin,
|
|
|
|
remotefs_drv.RemoteFSPoolMixin,
|
2017-10-06 14:31:21 +03:00
|
|
|
remotefs_drv.RemoteFSManageableVolumesMixin,
|
2017-05-11 16:52:49 +03:00
|
|
|
remotefs_drv.RemoteFSSnapDriverDistributed):
|
2017-02-15 16:27:15 +02:00
|
|
|
VERSION = VERSION
|
|
|
|
|
|
|
|
driver_volume_type = 'smbfs'
|
|
|
|
driver_prefix = 'smbfs'
|
|
|
|
volume_backend_name = 'Generic_SMBFS'
|
|
|
|
SHARE_FORMAT_REGEX = r'//.+/.+'
|
2015-04-07 23:06:57 -07:00
|
|
|
VERSION = VERSION
|
2016-07-27 05:04:58 -07:00
|
|
|
|
2017-02-15 16:27:15 +02:00
|
|
|
_DISK_FORMAT_VHD = 'vhd'
|
|
|
|
_DISK_FORMAT_VHD_LEGACY = 'vpc'
|
|
|
|
_DISK_FORMAT_VHDX = 'vhdx'
|
|
|
|
|
2016-07-27 05:04:58 -07:00
|
|
|
# ThirdPartySystems wiki page
|
|
|
|
CI_WIKI_NAME = "Microsoft_iSCSI_CI"
|
|
|
|
|
2015-02-16 14:28:14 +02:00
|
|
|
_MINIMUM_QEMU_IMG_VERSION = '1.6'
|
2015-04-07 23:06:57 -07:00
|
|
|
|
2017-10-06 14:31:21 +03:00
|
|
|
_SUPPORTED_IMAGE_FORMATS = [_DISK_FORMAT_VHD,
|
|
|
|
_DISK_FORMAT_VHD_LEGACY,
|
|
|
|
_DISK_FORMAT_VHDX]
|
|
|
|
_VALID_IMAGE_EXTENSIONS = [_DISK_FORMAT_VHD, _DISK_FORMAT_VHDX]
|
|
|
|
_MANAGEABLE_IMAGE_RE = re.compile(
|
|
|
|
'.*\.(?:%s)$' % '|'.join(_VALID_IMAGE_EXTENSIONS),
|
|
|
|
re.IGNORECASE)
|
2017-02-15 16:27:15 +02:00
|
|
|
|
2017-06-09 14:02:56 +03:00
|
|
|
_always_use_temp_snap_when_cloning = False
|
2017-07-17 19:18:43 +03:00
|
|
|
_thin_provisioning_support = True
|
2017-06-09 14:02:56 +03:00
|
|
|
|
2017-08-21 15:19:06 +03:00
|
|
|
_vhd_type_mapping = {'thin': os_win_const.VHD_TYPE_DYNAMIC,
|
|
|
|
'thick': os_win_const.VHD_TYPE_FIXED}
|
|
|
|
_vhd_qemu_subformat_mapping = {'thin': 'dynamic',
|
|
|
|
'thick': 'fixed'}
|
|
|
|
|
2015-04-07 23:06:57 -07:00
|
|
|
def __init__(self, *args, **kwargs):
|
2017-02-15 16:27:15 +02:00
|
|
|
self._remotefsclient = None
|
2015-04-07 23:06:57 -07:00
|
|
|
super(WindowsSmbfsDriver, self).__init__(*args, **kwargs)
|
2017-02-15 16:27:15 +02:00
|
|
|
|
|
|
|
self.configuration.append_config_values(volume_opts)
|
|
|
|
|
2015-04-07 23:06:57 -07:00
|
|
|
self.base = getattr(self.configuration,
|
2017-04-05 14:52:30 +02:00
|
|
|
'smbfs_mount_point_base')
|
2016-09-29 13:03:34 +03:00
|
|
|
self._remotefsclient = remotefs_brick.WindowsRemoteFsClient(
|
2015-04-07 23:06:57 -07:00
|
|
|
'cifs', root_helper=None, smbfs_mount_point_base=self.base,
|
2017-02-15 16:27:15 +02:00
|
|
|
local_path_for_loopback=True)
|
2015-10-14 15:40:00 +03:00
|
|
|
|
|
|
|
self._vhdutils = utilsfactory.get_vhdutils()
|
|
|
|
self._pathutils = utilsfactory.get_pathutils()
|
|
|
|
self._smbutils = utilsfactory.get_smbutils()
|
2017-05-05 12:51:17 +03:00
|
|
|
self._diskutils = utilsfactory.get_diskutils()
|
2015-04-07 23:06:57 -07:00
|
|
|
|
2017-08-21 15:19:06 +03:00
|
|
|
thin_enabled = (
|
|
|
|
CONF.backend_defaults.nas_volume_prov_type ==
|
|
|
|
'thin')
|
|
|
|
self._thin_provisioning_support = thin_enabled
|
|
|
|
self._thick_provisioning_support = not thin_enabled
|
|
|
|
|
2015-04-07 23:06:57 -07:00
|
|
|
def do_setup(self, context):
|
|
|
|
self._check_os_platform()
|
2017-07-17 18:46:04 +03:00
|
|
|
|
2016-11-28 05:38:09 +02:00
|
|
|
super(WindowsSmbfsDriver, self).do_setup(context)
|
2017-02-15 16:27:15 +02:00
|
|
|
|
|
|
|
image_utils.check_qemu_img_version(self._MINIMUM_QEMU_IMG_VERSION)
|
|
|
|
|
|
|
|
config = self.configuration.smbfs_shares_config
|
|
|
|
if not config:
|
|
|
|
msg = (_("SMBFS config file not set (smbfs_shares_config)."))
|
|
|
|
LOG.error(msg)
|
|
|
|
raise exception.SmbfsException(msg)
|
|
|
|
if not os.path.exists(config):
|
|
|
|
msg = (_("SMBFS config file at %(config)s doesn't exist.") %
|
|
|
|
{'config': config})
|
|
|
|
LOG.error(msg)
|
|
|
|
raise exception.SmbfsException(msg)
|
|
|
|
if not os.path.isabs(self.base):
|
|
|
|
msg = _("Invalid mount point base: %s") % self.base
|
|
|
|
LOG.error(msg)
|
|
|
|
raise exception.SmbfsException(msg)
|
|
|
|
|
|
|
|
self.shares = {} # address : options
|
|
|
|
self._ensure_shares_mounted()
|
2016-12-14 18:52:50 +02:00
|
|
|
self._setup_pool_mappings()
|
|
|
|
|
|
|
|
def _setup_pool_mappings(self):
|
|
|
|
self._pool_mappings = self.configuration.smbfs_pool_mappings
|
|
|
|
|
|
|
|
pools = list(self._pool_mappings.values())
|
|
|
|
duplicate_pools = set([pool for pool in pools
|
|
|
|
if pools.count(pool) > 1])
|
|
|
|
if duplicate_pools:
|
|
|
|
msg = _("Found multiple mappings for pools %(pools)s. "
|
|
|
|
"Requested pool mappings: %(pool_mappings)s")
|
|
|
|
raise exception.SmbfsException(
|
|
|
|
msg % dict(pools=duplicate_pools,
|
|
|
|
pool_mappings=self._pool_mappings))
|
|
|
|
|
|
|
|
shares_missing_mappings = (
|
|
|
|
set(self.shares).difference(set(self._pool_mappings)))
|
|
|
|
for share in shares_missing_mappings:
|
|
|
|
msg = ("No pool name was requested for share %(share)s "
|
|
|
|
"Using the share name instead.")
|
|
|
|
LOG.warning(msg, dict(share=share))
|
|
|
|
|
|
|
|
self._pool_mappings[share] = self._get_share_name(share)
|
2017-02-15 16:27:15 +02:00
|
|
|
|
2017-05-11 16:52:49 +03:00
|
|
|
@coordination.synchronized('{self.driver_prefix}-{volume.id}')
|
2017-02-15 16:27:15 +02:00
|
|
|
def initialize_connection(self, volume, connector):
|
|
|
|
"""Allow connection to connector and return connection info.
|
|
|
|
|
|
|
|
:param volume: volume reference
|
|
|
|
:param connector: connector reference
|
|
|
|
"""
|
|
|
|
# Find active image
|
|
|
|
active_file = self.get_active_image_from_info(volume)
|
|
|
|
fmt = self.get_volume_format(volume)
|
|
|
|
|
|
|
|
data = {'export': volume.provider_location,
|
|
|
|
'format': fmt,
|
|
|
|
'name': active_file}
|
|
|
|
if volume.provider_location in self.shares:
|
2017-12-13 11:25:23 +02:00
|
|
|
data['options'] = self.shares[volume.provider_location]
|
|
|
|
return {
|
|
|
|
'driver_volume_type': self.driver_volume_type,
|
|
|
|
'data': data,
|
|
|
|
'mount_point_base': self._get_mount_point_base()
|
|
|
|
}
|
|
|
|
|
|
|
|
@coordination.synchronized('{self.driver_prefix}-{snapshot.volume.id}')
|
|
|
|
def initialize_connection_snapshot(self, snapshot, connector):
|
|
|
|
backing_file = self._get_snapshot_backing_file(snapshot)
|
|
|
|
volume = snapshot.volume
|
|
|
|
fmt = self.get_volume_format(volume)
|
|
|
|
|
|
|
|
data = {'export': volume.provider_location,
|
|
|
|
'format': fmt,
|
|
|
|
'name': backing_file,
|
|
|
|
'access_mode': 'ro'}
|
|
|
|
|
|
|
|
if volume.provider_location in self.shares:
|
2017-02-15 16:27:15 +02:00
|
|
|
data['options'] = self.shares[volume.provider_location]
|
|
|
|
return {
|
|
|
|
'driver_volume_type': self.driver_volume_type,
|
|
|
|
'data': data,
|
|
|
|
'mount_point_base': self._get_mount_point_base()
|
|
|
|
}
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
def _check_os_platform(self):
|
|
|
|
if sys.platform != 'win32':
|
|
|
|
_msg = _("This system platform (%s) is not supported. This "
|
|
|
|
"driver supports only Win32 platforms.") % sys.platform
|
|
|
|
raise exception.SmbfsException(_msg)
|
|
|
|
|
2017-02-15 16:27:15 +02:00
|
|
|
def _get_total_allocated(self, smbfs_share):
|
2017-05-11 14:22:19 +03:00
|
|
|
pool_name = self._get_pool_name_from_share(smbfs_share)
|
|
|
|
host = "#".join([self.host, pool_name])
|
|
|
|
|
|
|
|
vol_sz_sum = self.db.volume_data_get_for_host(
|
|
|
|
context=context.get_admin_context(),
|
|
|
|
host=host)[1]
|
|
|
|
return float(vol_sz_sum * units.Gi)
|
2017-02-15 16:27:15 +02:00
|
|
|
|
|
|
|
def local_path(self, volume):
|
|
|
|
"""Get volume path (mounted locally fs path) for given volume.
|
|
|
|
|
|
|
|
:param volume: volume reference
|
|
|
|
"""
|
|
|
|
volume_path_template = self._get_local_volume_path_template(volume)
|
|
|
|
volume_path = self._lookup_local_volume_path(volume_path_template)
|
|
|
|
if volume_path:
|
|
|
|
return volume_path
|
|
|
|
|
|
|
|
# The image does not exist, so retrieve the volume format
|
|
|
|
# in order to build the path.
|
|
|
|
fmt = self.get_volume_format(volume)
|
|
|
|
volume_path = volume_path_template + '.' + fmt
|
|
|
|
return volume_path
|
|
|
|
|
|
|
|
def _get_local_volume_path_template(self, volume):
|
|
|
|
local_dir = self._local_volume_dir(volume)
|
|
|
|
local_path_template = os.path.join(local_dir, volume.name)
|
|
|
|
return local_path_template
|
|
|
|
|
|
|
|
def _lookup_local_volume_path(self, volume_path_template):
|
2017-10-06 14:31:21 +03:00
|
|
|
for ext in self._VALID_IMAGE_EXTENSIONS:
|
2017-02-15 16:27:15 +02:00
|
|
|
volume_path = (volume_path_template + '.' + ext
|
|
|
|
if ext else volume_path_template)
|
|
|
|
if os.path.exists(volume_path):
|
|
|
|
return volume_path
|
|
|
|
|
|
|
|
def _get_new_snap_path(self, snapshot):
|
|
|
|
vol_path = self.local_path(snapshot.volume)
|
|
|
|
snap_path, ext = os.path.splitext(vol_path)
|
|
|
|
snap_path += '.' + snapshot.id + ext
|
|
|
|
return snap_path
|
|
|
|
|
|
|
|
def get_volume_format(self, volume, qemu_format=False):
|
|
|
|
volume_path_template = self._get_local_volume_path_template(volume)
|
|
|
|
volume_path = self._lookup_local_volume_path(volume_path_template)
|
|
|
|
|
|
|
|
if volume_path:
|
|
|
|
ext = os.path.splitext(volume_path)[1].strip('.').lower()
|
2017-10-06 14:31:21 +03:00
|
|
|
if ext in self._VALID_IMAGE_EXTENSIONS:
|
2017-02-15 16:27:15 +02:00
|
|
|
volume_format = ext
|
|
|
|
else:
|
|
|
|
# Hyper-V relies on file extensions so we're enforcing them.
|
|
|
|
raise exception.SmbfsException(
|
|
|
|
_("Invalid image file extension: %s") % ext)
|
|
|
|
else:
|
|
|
|
volume_format = (
|
|
|
|
self._get_volume_format_spec(volume) or
|
|
|
|
self.configuration.smbfs_default_volume_format)
|
|
|
|
|
|
|
|
if qemu_format and volume_format == self._DISK_FORMAT_VHD:
|
|
|
|
volume_format = self._DISK_FORMAT_VHD_LEGACY
|
|
|
|
elif volume_format == self._DISK_FORMAT_VHD_LEGACY:
|
|
|
|
volume_format = self._DISK_FORMAT_VHD
|
|
|
|
|
|
|
|
return volume_format
|
|
|
|
|
|
|
|
def _get_volume_format_spec(self, volume):
|
|
|
|
vol_type = volume.volume_type
|
|
|
|
extra_specs = {}
|
|
|
|
if vol_type and vol_type.extra_specs:
|
|
|
|
extra_specs = vol_type.extra_specs
|
|
|
|
|
|
|
|
extra_specs.update(volume.metadata or {})
|
|
|
|
|
|
|
|
return (extra_specs.get('volume_format') or
|
2017-05-15 13:08:21 +03:00
|
|
|
extra_specs.get('smbfs:volume_format') or
|
2017-02-15 16:27:15 +02:00
|
|
|
self.configuration.smbfs_default_volume_format)
|
|
|
|
|
2017-05-11 16:52:49 +03:00
|
|
|
@coordination.synchronized('{self.driver_prefix}-{volume.id}')
|
2017-02-15 16:27:15 +02:00
|
|
|
def create_volume(self, volume):
|
|
|
|
return super(WindowsSmbfsDriver, self).create_volume(volume)
|
|
|
|
|
2015-04-07 23:06:57 -07:00
|
|
|
def _do_create_volume(self, volume):
|
|
|
|
volume_path = self.local_path(volume)
|
|
|
|
volume_format = self.get_volume_format(volume)
|
2016-11-28 13:46:50 +02:00
|
|
|
volume_size_bytes = volume.size * units.Gi
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
if os.path.exists(volume_path):
|
|
|
|
err_msg = _('File already exists at: %s') % volume_path
|
|
|
|
raise exception.InvalidVolume(err_msg)
|
|
|
|
|
2017-02-15 16:27:15 +02:00
|
|
|
if volume_format not in self._SUPPORTED_IMAGE_FORMATS:
|
2015-04-07 23:06:57 -07:00
|
|
|
err_msg = _("Unsupported volume format: %s ") % volume_format
|
|
|
|
raise exception.InvalidVolume(err_msg)
|
|
|
|
|
2017-08-21 15:19:06 +03:00
|
|
|
vhd_type = self._get_vhd_type()
|
|
|
|
|
|
|
|
self._vhdutils.create_vhd(volume_path, vhd_type,
|
|
|
|
max_internal_size=volume_size_bytes)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
def _ensure_share_mounted(self, smbfs_share):
|
2016-09-29 13:03:34 +03:00
|
|
|
mnt_flags = None
|
2015-04-07 23:06:57 -07:00
|
|
|
if self.shares.get(smbfs_share) is not None:
|
|
|
|
mnt_flags = self.shares[smbfs_share]
|
2016-09-29 13:03:34 +03:00
|
|
|
self._remotefsclient.mount(smbfs_share, mnt_flags)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
2017-05-11 16:52:49 +03:00
|
|
|
@coordination.synchronized('{self.driver_prefix}-{volume.id}')
|
2017-02-15 16:27:15 +02:00
|
|
|
def delete_volume(self, volume):
|
|
|
|
"""Deletes a logical volume."""
|
|
|
|
if not volume.provider_location:
|
2017-03-09 15:49:01 -06:00
|
|
|
LOG.warning('Volume %s does not have provider_location '
|
|
|
|
'specified, skipping.', volume.name)
|
2017-02-15 16:27:15 +02:00
|
|
|
return
|
|
|
|
|
|
|
|
self._ensure_share_mounted(volume.provider_location)
|
|
|
|
volume_dir = self._local_volume_dir(volume)
|
|
|
|
mounted_path = os.path.join(volume_dir,
|
|
|
|
self.get_active_image_from_info(volume))
|
|
|
|
if os.path.exists(mounted_path):
|
|
|
|
self._delete(mounted_path)
|
|
|
|
else:
|
|
|
|
LOG.debug("Skipping deletion of volume %s as it does not exist.",
|
|
|
|
mounted_path)
|
|
|
|
|
|
|
|
info_path = self._local_path_volume_info(volume)
|
|
|
|
self._delete(info_path)
|
|
|
|
|
2015-04-07 23:06:57 -07:00
|
|
|
def _delete(self, path):
|
|
|
|
fileutils.delete_if_exists(path)
|
|
|
|
|
|
|
|
def _get_capacity_info(self, smbfs_share):
|
|
|
|
"""Calculate available space on the SMBFS share.
|
|
|
|
|
|
|
|
:param smbfs_share: example //172.18.194.100/var/smbfs
|
|
|
|
"""
|
2017-05-05 12:51:17 +03:00
|
|
|
mount_point = self._get_mount_point_for_share(smbfs_share)
|
|
|
|
total_size, total_available = self._diskutils.get_disk_capacity(
|
|
|
|
mount_point)
|
2015-04-07 23:06:57 -07:00
|
|
|
total_allocated = self._get_total_allocated(smbfs_share)
|
|
|
|
return_value = [total_size, total_available, total_allocated]
|
2017-03-09 15:49:01 -06:00
|
|
|
LOG.info('Smb share %(share)s Total size %(size)s '
|
|
|
|
'Total allocated %(allocated)s',
|
2015-03-16 06:26:11 -07:00
|
|
|
{'share': smbfs_share, 'size': total_size,
|
|
|
|
'allocated': total_allocated})
|
2015-04-07 23:06:57 -07:00
|
|
|
return [float(x) for x in return_value]
|
|
|
|
|
|
|
|
def _img_commit(self, snapshot_path):
|
2015-10-14 15:40:00 +03:00
|
|
|
self._vhdutils.merge_vhd(snapshot_path)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
def _rebase_img(self, image, backing_file, volume_format):
|
|
|
|
# Relative path names are not supported in this case.
|
|
|
|
image_dir = os.path.dirname(image)
|
|
|
|
backing_file_path = os.path.join(image_dir, backing_file)
|
2015-10-14 15:40:00 +03:00
|
|
|
self._vhdutils.reconnect_parent_vhd(image, backing_file_path)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
def _qemu_img_info(self, path, volume_name=None):
|
|
|
|
# This code expects to deal only with relative filenames.
|
|
|
|
# As this method is needed by the upper class and qemu-img does
|
|
|
|
# not fully support vhdx images, for the moment we'll use Win32 API
|
|
|
|
# for retrieving image information.
|
2015-10-14 15:40:00 +03:00
|
|
|
parent_path = self._vhdutils.get_vhd_parent_path(path)
|
2015-04-07 23:06:57 -07:00
|
|
|
file_format = os.path.splitext(path)[1][1:].lower()
|
|
|
|
|
|
|
|
if parent_path:
|
|
|
|
backing_file_name = os.path.split(parent_path)[1].lower()
|
|
|
|
else:
|
|
|
|
backing_file_name = None
|
|
|
|
|
|
|
|
class ImageInfo(object):
|
|
|
|
def __init__(self, image, backing_file):
|
|
|
|
self.image = image
|
|
|
|
self.backing_file = backing_file
|
|
|
|
self.file_format = file_format
|
|
|
|
|
|
|
|
return ImageInfo(os.path.basename(path),
|
|
|
|
backing_file_name)
|
|
|
|
|
|
|
|
def _do_create_snapshot(self, snapshot, backing_file, new_snap_path):
|
2017-12-15 14:27:51 +02:00
|
|
|
if self._is_volume_attached(snapshot.volume):
|
2016-11-28 05:38:09 +02:00
|
|
|
LOG.debug("Snapshot is in-use. Performing Nova "
|
|
|
|
"assisted creation.")
|
2017-12-13 11:25:23 +02:00
|
|
|
else:
|
|
|
|
backing_file_full_path = os.path.join(
|
|
|
|
self._local_volume_dir(snapshot.volume),
|
|
|
|
backing_file)
|
|
|
|
self._vhdutils.create_differencing_vhd(new_snap_path,
|
|
|
|
backing_file_full_path)
|
|
|
|
|
|
|
|
# We're setting the backing file information in the DB as we may not
|
|
|
|
# be able to query the image while it's in use due to file locks.
|
|
|
|
#
|
|
|
|
# When dealing with temporary snapshots created by the driver, we
|
|
|
|
# may not receive an actual snapshot VO. We currently need this check
|
|
|
|
# in order to avoid breaking the volume clone operation.
|
|
|
|
#
|
|
|
|
# TODO(lpetrut): remove this check once we'll start using db entries
|
|
|
|
# for such temporary snapshots, most probably when we'll add support
|
|
|
|
# for cloning in-use volumes.
|
|
|
|
if isinstance(snapshot, objects.Snapshot):
|
|
|
|
snapshot.metadata['backing_file'] = backing_file
|
|
|
|
snapshot.save()
|
|
|
|
else:
|
|
|
|
LOG.debug("Received a '%s' object, skipping setting the backing "
|
|
|
|
"file in the DB.", type(snapshot))
|
2015-04-07 23:06:57 -07:00
|
|
|
|
2017-06-09 14:02:56 +03:00
|
|
|
def _extend_volume(self, volume, size_gb):
|
2017-02-15 16:27:15 +02:00
|
|
|
self._check_extend_volume_support(volume, size_gb)
|
|
|
|
|
2017-05-05 12:03:43 +02:00
|
|
|
volume_path = self._local_path_active_image(volume)
|
2017-02-15 16:27:15 +02:00
|
|
|
|
2017-03-09 15:49:01 -06:00
|
|
|
LOG.info('Resizing file %(volume_path)s to %(size_gb)sGB.',
|
2017-02-15 16:27:15 +02:00
|
|
|
dict(volume_path=volume_path, size_gb=size_gb))
|
|
|
|
|
2016-06-13 15:48:05 +03:00
|
|
|
self._vhdutils.resize_vhd(volume_path, size_gb * units.Gi,
|
|
|
|
is_file_max_size=False)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
2016-11-28 05:38:09 +02:00
|
|
|
def _delete_snapshot(self, snapshot):
|
|
|
|
# NOTE(lpetrut): We're slightly diverging from the super class
|
|
|
|
# workflow. The reason is that we cannot query in-use vhd/x images,
|
|
|
|
# nor can we add or remove images from a vhd/x chain in this case.
|
|
|
|
info_path = self._local_path_volume_info(snapshot.volume)
|
|
|
|
snap_info = self._read_info_file(info_path, empty_if_missing=True)
|
|
|
|
|
|
|
|
if snapshot.id not in snap_info:
|
|
|
|
LOG.info('Snapshot record for %s is not present, allowing '
|
|
|
|
'snapshot_delete to proceed.', snapshot.id)
|
|
|
|
return
|
|
|
|
|
|
|
|
file_to_merge = snap_info[snapshot.id]
|
2017-12-13 11:25:23 +02:00
|
|
|
deleting_latest_snap = utils.paths_normcase_equal(snap_info['active'],
|
|
|
|
file_to_merge)
|
|
|
|
|
|
|
|
if not self._is_volume_attached(snapshot.volume):
|
|
|
|
super(WindowsSmbfsDriver, self)._delete_snapshot(snapshot)
|
|
|
|
else:
|
|
|
|
delete_info = {'file_to_merge': file_to_merge,
|
|
|
|
'volume_id': snapshot.volume.id}
|
|
|
|
self._nova_assisted_vol_snap_delete(
|
|
|
|
snapshot._context, snapshot, delete_info)
|
|
|
|
|
|
|
|
# At this point, the image file should no longer be in use, so we
|
|
|
|
# may safely query it so that we can update the 'active' image
|
|
|
|
# reference, if needed.
|
|
|
|
merged_img_path = os.path.join(
|
|
|
|
self._local_volume_dir(snapshot.volume),
|
|
|
|
file_to_merge)
|
|
|
|
if deleting_latest_snap:
|
|
|
|
new_active_file_path = self._vhdutils.get_vhd_parent_path(
|
|
|
|
merged_img_path).lower()
|
|
|
|
snap_info['active'] = os.path.basename(new_active_file_path)
|
|
|
|
|
|
|
|
self._delete(merged_img_path)
|
|
|
|
|
|
|
|
# TODO(lpetrut): drop snapshot info file usage.
|
|
|
|
del(snap_info[snapshot.id])
|
|
|
|
self._write_info_file(info_path, snap_info)
|
|
|
|
|
|
|
|
if not isinstance(snapshot, objects.Snapshot):
|
|
|
|
LOG.debug("Received a '%s' object, skipping setting the backing "
|
|
|
|
"file in the DB.", type(snapshot))
|
|
|
|
elif not deleting_latest_snap:
|
|
|
|
backing_file = snapshot['metadata'].get('backing_file')
|
|
|
|
higher_snapshot = self._get_snapshot_by_backing_file(
|
|
|
|
snapshot.volume, file_to_merge)
|
|
|
|
# The snapshot objects should have a backing file set, unless
|
|
|
|
# created before an upgrade. If the snapshot we're deleting
|
|
|
|
# does not have a backing file set yet there is a newer one that
|
|
|
|
# does, we're clearing it out so that it won't provide wrong info.
|
|
|
|
if higher_snapshot:
|
|
|
|
LOG.debug("Updating backing file reference (%(backing_file)s) "
|
|
|
|
"for higher snapshot: %(higher_snapshot_id)s.",
|
|
|
|
dict(backing_file=snapshot.metadata['backing_file'],
|
|
|
|
higher_snapshot_id=higher_snapshot.id))
|
|
|
|
|
|
|
|
higher_snapshot.metadata['backing_file'] = (
|
|
|
|
snapshot.metadata['backing_file'])
|
|
|
|
higher_snapshot.save()
|
|
|
|
if not (higher_snapshot and backing_file):
|
|
|
|
LOG.info(
|
|
|
|
"The deleted snapshot is not latest one, yet we could not "
|
|
|
|
"find snapshot backing file information in the DB. This "
|
|
|
|
"may happen after an upgrade. Certain operations against "
|
|
|
|
"this volume may be unavailable while it's in-use.")
|
|
|
|
|
|
|
|
def _get_snapshot_by_backing_file(self, volume, backing_file):
|
|
|
|
all_snapshots = objects.SnapshotList.get_all_for_volume(
|
|
|
|
context.get_admin_context(), volume.id)
|
|
|
|
for snapshot in all_snapshots:
|
|
|
|
snap_backing_file = snapshot.metadata.get('backing_file')
|
|
|
|
if utils.paths_normcase_equal(snap_backing_file or '',
|
|
|
|
backing_file):
|
|
|
|
return snapshot
|
|
|
|
|
|
|
|
def _get_snapshot_backing_file(self, snapshot):
|
|
|
|
backing_file = snapshot.metadata.get('backing_file')
|
|
|
|
if not backing_file:
|
|
|
|
LOG.info("Could not find the snapshot backing file in the DB. "
|
|
|
|
"This may happen after an upgrade. Attempting to "
|
|
|
|
"query the image as a fallback. This may fail if "
|
|
|
|
"the image is in-use.")
|
|
|
|
backing_file = super(
|
|
|
|
WindowsSmbfsDriver, self)._get_snapshot_backing_file(snapshot)
|
|
|
|
|
|
|
|
return backing_file
|
2016-11-28 05:38:09 +02:00
|
|
|
|
2017-02-15 16:27:15 +02:00
|
|
|
def _check_extend_volume_support(self, volume, size_gb):
|
2017-05-05 12:03:43 +02:00
|
|
|
snapshots_exist = self._snapshots_exist(volume)
|
|
|
|
fmt = self.get_volume_format(volume)
|
2017-02-15 16:27:15 +02:00
|
|
|
|
2017-05-05 12:03:43 +02:00
|
|
|
if snapshots_exist and fmt == self._DISK_FORMAT_VHD:
|
|
|
|
msg = _('Extending volumes backed by VHD images is not supported '
|
|
|
|
'when snapshots exist. Please use VHDX images.')
|
2017-02-15 16:27:15 +02:00
|
|
|
raise exception.InvalidVolume(msg)
|
|
|
|
|
2017-05-11 16:52:49 +03:00
|
|
|
@coordination.synchronized('{self.driver_prefix}-{volume.id}')
|
2015-04-07 23:06:57 -07:00
|
|
|
def copy_volume_to_image(self, context, volume, image_service, image_meta):
|
|
|
|
"""Copy the volume to the specified image."""
|
|
|
|
|
|
|
|
# If snapshots exist, flatten to a temporary image, and upload it
|
|
|
|
|
|
|
|
active_file = self.get_active_image_from_info(volume)
|
|
|
|
active_file_path = os.path.join(self._local_volume_dir(volume),
|
|
|
|
active_file)
|
2015-10-14 15:40:00 +03:00
|
|
|
backing_file = self._vhdutils.get_vhd_parent_path(active_file_path)
|
2015-04-07 23:06:57 -07:00
|
|
|
root_file_fmt = self.get_volume_format(volume)
|
|
|
|
|
|
|
|
temp_path = None
|
|
|
|
|
|
|
|
try:
|
2017-05-12 16:59:31 +03:00
|
|
|
if backing_file:
|
2015-04-07 23:06:57 -07:00
|
|
|
temp_file_name = '%s.temp_image.%s.%s' % (
|
2016-11-28 13:46:50 +02:00
|
|
|
volume.id,
|
2015-04-07 23:06:57 -07:00
|
|
|
image_meta['id'],
|
2017-05-12 16:59:31 +03:00
|
|
|
root_file_fmt)
|
2015-04-07 23:06:57 -07:00
|
|
|
temp_path = os.path.join(self._local_volume_dir(volume),
|
|
|
|
temp_file_name)
|
|
|
|
|
2015-10-14 15:40:00 +03:00
|
|
|
self._vhdutils.convert_vhd(active_file_path, temp_path)
|
2015-04-07 23:06:57 -07:00
|
|
|
upload_path = temp_path
|
|
|
|
else:
|
|
|
|
upload_path = active_file_path
|
|
|
|
|
|
|
|
image_utils.upload_volume(context,
|
|
|
|
image_service,
|
|
|
|
image_meta,
|
|
|
|
upload_path,
|
2017-05-12 16:59:31 +03:00
|
|
|
root_file_fmt)
|
2015-04-07 23:06:57 -07:00
|
|
|
finally:
|
|
|
|
if temp_path:
|
|
|
|
self._delete(temp_path)
|
|
|
|
|
|
|
|
def copy_image_to_volume(self, context, volume, image_service, image_id):
|
|
|
|
"""Fetch the image from image_service and write it to the volume."""
|
2015-02-16 14:28:14 +02:00
|
|
|
volume_path = self.local_path(volume)
|
2015-04-07 23:06:57 -07:00
|
|
|
volume_format = self.get_volume_format(volume, qemu_format=True)
|
2017-08-21 15:19:06 +03:00
|
|
|
volume_subformat = self._get_vhd_type(qemu_subformat=True)
|
2015-02-16 14:28:14 +02:00
|
|
|
self._delete(volume_path)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
image_utils.fetch_to_volume_format(
|
|
|
|
context, image_service, image_id,
|
2015-02-16 14:28:14 +02:00
|
|
|
volume_path, volume_format,
|
2017-08-21 15:19:06 +03:00
|
|
|
self.configuration.volume_dd_blocksize,
|
|
|
|
volume_subformat)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
2015-10-14 15:40:00 +03:00
|
|
|
self._vhdutils.resize_vhd(self.local_path(volume),
|
2016-11-28 13:46:50 +02:00
|
|
|
volume.size * units.Gi,
|
2016-06-13 15:48:05 +03:00
|
|
|
is_file_max_size=False)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
def _copy_volume_from_snapshot(self, snapshot, volume, volume_size):
|
|
|
|
"""Copy data from snapshot to destination volume."""
|
|
|
|
|
|
|
|
LOG.debug("snapshot: %(snap)s, volume: %(vol)s, "
|
2015-03-16 06:26:11 -07:00
|
|
|
"volume_size: %(size)s",
|
2016-11-28 13:46:50 +02:00
|
|
|
{'snap': snapshot.id,
|
|
|
|
'vol': volume.id,
|
|
|
|
'size': snapshot.volume_size})
|
2015-04-07 23:06:57 -07:00
|
|
|
|
2016-11-28 13:46:50 +02:00
|
|
|
vol_dir = self._local_volume_dir(snapshot.volume)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
# Find the file which backs this file, which represents the point
|
|
|
|
# when this snapshot was created.
|
2017-12-13 11:25:23 +02:00
|
|
|
backing_file = self._get_snapshot_backing_file(snapshot)
|
|
|
|
snapshot_path = os.path.join(vol_dir, backing_file)
|
2015-04-07 23:06:57 -07:00
|
|
|
|
|
|
|
volume_path = self.local_path(volume)
|
2017-08-21 15:19:06 +03:00
|
|
|
vhd_type = self._get_vhd_type()
|
|
|
|
|
2015-04-07 23:06:57 -07:00
|
|
|
self._delete(volume_path)
|
2015-10-14 15:40:00 +03:00
|
|
|
self._vhdutils.convert_vhd(snapshot_path,
|
2017-08-21 15:19:06 +03:00
|
|
|
volume_path,
|
|
|
|
vhd_type=vhd_type)
|
2016-06-13 15:48:05 +03:00
|
|
|
self._vhdutils.resize_vhd(volume_path, volume_size * units.Gi,
|
|
|
|
is_file_max_size=False)
|
2016-12-14 18:52:50 +02:00
|
|
|
|
2017-06-09 14:02:56 +03:00
|
|
|
def _copy_volume_image(self, src_path, dest_path):
|
|
|
|
self._pathutils.copy(src_path, dest_path)
|
|
|
|
|
2016-12-14 18:52:50 +02:00
|
|
|
def _get_share_name(self, share):
|
|
|
|
return share.replace('/', '\\').lstrip('\\').split('\\', 1)[1]
|
|
|
|
|
|
|
|
def _get_pool_name_from_share(self, share):
|
|
|
|
return self._pool_mappings[share]
|
|
|
|
|
|
|
|
def _get_share_from_pool_name(self, pool_name):
|
|
|
|
mappings = {pool: share
|
|
|
|
for share, pool in self._pool_mappings.items()}
|
|
|
|
share = mappings.get(pool_name)
|
|
|
|
|
|
|
|
if not share:
|
|
|
|
msg = _("Could not find any share for pool %(pool_name)s. "
|
|
|
|
"Pool mappings: %(pool_mappings)s.")
|
|
|
|
raise exception.SmbfsException(
|
|
|
|
msg % dict(pool_name=pool_name,
|
|
|
|
pool_mappings=self._pool_mappings))
|
|
|
|
return share
|
2017-08-21 15:19:06 +03:00
|
|
|
|
|
|
|
def _get_vhd_type(self, qemu_subformat=False):
|
|
|
|
prov_type = CONF.backend_defaults.nas_volume_prov_type
|
|
|
|
|
|
|
|
if qemu_subformat:
|
|
|
|
vhd_type = self._vhd_qemu_subformat_mapping[prov_type]
|
|
|
|
else:
|
|
|
|
vhd_type = self._vhd_type_mapping[prov_type]
|
|
|
|
|
|
|
|
return vhd_type
|
2017-10-06 14:31:21 +03:00
|
|
|
|
|
|
|
def _get_managed_vol_expected_path(self, volume, volume_location):
|
|
|
|
fmt = self._vhdutils.get_vhd_format(volume_location['vol_local_path'])
|
|
|
|
return os.path.join(volume_location['mountpoint'],
|
|
|
|
volume.name + ".%s" % fmt).lower()
|
|
|
|
|
|
|
|
def _set_rw_permissions(self, path):
|
|
|
|
# The SMBFS driver does not manage file permissions. We chose
|
|
|
|
# to let this up to the deployer.
|
|
|
|
pass
|