impuls/lib/python3.11/site-packages/os_brick/initiator/windows/rbd.py

170 lines
6.6 KiB
Python

# Copyright 2020 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 ctypes
import errno
import json
from oslo_concurrency import processutils
from oslo_log import log as logging
from oslo_service import loopingcall
from os_brick import exception
from os_brick.i18n import _
from os_brick.initiator.connectors import base_rbd
from os_brick.initiator.windows import base as win_conn_base
from os_brick import utils
LOG = logging.getLogger(__name__)
class WindowsRBDConnector(base_rbd.RBDConnectorMixin,
win_conn_base.BaseWindowsConnector):
"""Connector class to attach/detach RBD volumes.
The Windows RBD connector is very similar to the Linux one.
There are a few main differences though:
* the Ceph python bindings are not available on Windows yet, so we'll
always do a local mount. Besides, Hyper-V cannot use librbd, so
we'll need to do a local mount anyway.
* The device names aren't handled in the same way. On Windows,
disk names such as "\\\\.\\PhysicalDrive1" are provided by the OS and
cannot be explicitly requsted.
"""
def __init__(self, *args, **kwargs):
super(WindowsRBDConnector, self).__init__(*args, **kwargs)
self._ensure_rbd_available()
def _check_rbd(self):
cmd = ['where.exe', 'rbd']
try:
self._execute(*cmd)
return True
except processutils.ProcessExecutionError:
LOG.warning("rbd.exe is not available.")
return False
def _ensure_rbd_available(self):
if not self._check_rbd():
msg = _("rbd.exe is not available.")
LOG.error(msg)
raise exception.BrickException(msg)
def get_volume_paths(self, connection_properties):
return [self.get_device_name(connection_properties)]
def _show_rbd_mapping(self, connection_properties):
# TODO(lpetrut): consider using "rbd device show" if/when
# it becomes available.
cmd = ['rbd-wnbd', 'show', connection_properties['name'],
'--format', 'json']
try:
out, err = self._execute(*cmd)
return json.loads(out)
except processutils.ProcessExecutionError as ex:
if abs(ctypes.c_int32(ex.exit_code).value) == errno.ENOENT:
LOG.debug("Couldn't find RBD mapping: %s",
connection_properties['name'])
return
raise
except json.decoder.JSONDecodeError:
msg = _("Could not get rbd mappping.")
LOG.exception(msg)
raise exception.BrickException(msg)
def get_device_name(self, connection_properties, expect=True):
mapping = self._show_rbd_mapping(connection_properties)
if mapping:
dev_num = mapping['disk_number']
LOG.debug(
"Located RBD mapping: %(image)s. "
"Disk number: %(disk_number)s.",
dict(image=connection_properties['name'],
disk_number=dev_num))
return self._diskutils.get_device_name_by_device_number(dev_num)
elif expect:
msg = _("The specified RBD image is not mounted: %s")
raise exception.VolumeDeviceNotFound(
msg % connection_properties['name'])
def _wait_for_volume(self, connection_properties):
"""Wait for the specified volume to become accessible."""
attempt = 0
dev_path = None
def _check_rbd_device():
rbd_dev_path = self.get_device_name(
connection_properties, expect=False)
if rbd_dev_path:
try:
# Under high load, it can take a second before the disk
# becomes accessible.
with open(rbd_dev_path, 'rb'):
pass
nonlocal dev_path
dev_path = rbd_dev_path
raise loopingcall.LoopingCallDone()
except FileNotFoundError:
LOG.debug("The RBD image %(image)s mapped to local device "
"%(dev)s isn't available yet.",
{'image': connection_properties['name'],
'dev': rbd_dev_path})
nonlocal attempt
attempt += 1
if attempt >= self.device_scan_attempts:
msg = _("The mounted RBD image isn't available: %s")
raise exception.VolumeDeviceNotFound(
msg % connection_properties['name'])
timer = loopingcall.FixedIntervalLoopingCall(_check_rbd_device)
timer.start(interval=self.device_scan_interval).wait()
return dev_path
@utils.trace
def connect_volume(self, connection_properties):
rbd_dev_path = self.get_device_name(connection_properties,
expect=False)
if not rbd_dev_path:
cmd = ['rbd', 'device', 'map', connection_properties['name']]
cmd += self._get_rbd_args(connection_properties)
self._execute(*cmd)
rbd_dev_path = self._wait_for_volume(connection_properties)
else:
LOG.debug('The RBD image %(image)s is already mapped to local '
'device %(dev)s',
{'image': connection_properties['name'],
'dev': rbd_dev_path})
dev_num = self._diskutils.get_device_number_from_device_name(
rbd_dev_path)
# TODO(lpetrut): remove this once wnbd honors the SAN policy setting.
self._diskutils.set_disk_offline(dev_num)
return {'path': rbd_dev_path,
'type': 'block'}
@utils.trace
def disconnect_volume(self, connection_properties, device_info=None,
force=False, ignore_errors=False):
cmd = ['rbd', 'device', 'unmap', connection_properties['name']]
cmd += self._get_rbd_args(connection_properties)
if force:
cmd += ["-o", "hard-disconnect"]
self._execute(*cmd)