177 lines
6.3 KiB
Python
177 lines
6.3 KiB
Python
# 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
|
|
|
|
from oslo_concurrency import lockutils
|
|
from oslo_log import log as logging
|
|
from oslo_service import loopingcall
|
|
|
|
from os_brick import exception
|
|
from os_brick import initiator
|
|
|
|
from os_brick.initiator.connectors import base
|
|
from os_brick import utils
|
|
|
|
DEVICE_SCAN_ATTEMPTS_DEFAULT = 3
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
class AoEConnector(base.BaseLinuxConnector):
|
|
"""Connector class to attach/detach AoE volumes."""
|
|
|
|
def __init__(self, root_helper, driver=None,
|
|
device_scan_attempts=initiator.DEVICE_SCAN_ATTEMPTS_DEFAULT,
|
|
*args, **kwargs):
|
|
super(AoEConnector, self).__init__(
|
|
root_helper,
|
|
driver=driver,
|
|
device_scan_attempts=device_scan_attempts,
|
|
*args, **kwargs)
|
|
|
|
@staticmethod
|
|
def get_connector_properties(root_helper, *args, **kwargs):
|
|
"""The AoE connector properties."""
|
|
return {}
|
|
|
|
def get_search_path(self):
|
|
return '/dev/etherd'
|
|
|
|
def get_volume_paths(self, connection_properties):
|
|
aoe_device, aoe_path = self._get_aoe_info(connection_properties)
|
|
volume_paths = []
|
|
if os.path.exists(aoe_path):
|
|
volume_paths.append(aoe_path)
|
|
|
|
return volume_paths
|
|
|
|
def _get_aoe_info(self, connection_properties):
|
|
shelf = connection_properties['target_shelf']
|
|
lun = connection_properties['target_lun']
|
|
aoe_device = 'e%(shelf)s.%(lun)s' % {'shelf': shelf,
|
|
'lun': lun}
|
|
path = self.get_search_path()
|
|
aoe_path = '%(path)s/%(device)s' % {'path': path,
|
|
'device': aoe_device}
|
|
return aoe_device, aoe_path
|
|
|
|
@utils.trace
|
|
@lockutils.synchronized('aoe_control', 'aoe-')
|
|
def connect_volume(self, connection_properties):
|
|
"""Discover and attach the volume.
|
|
|
|
:param connection_properties: The dictionary that describes all
|
|
of the target volume attributes.
|
|
:type connection_properties: dict
|
|
:returns: dict
|
|
|
|
connection_properties for AoE must include:
|
|
target_shelf - shelf id of volume
|
|
target_lun - lun id of volume
|
|
"""
|
|
aoe_device, aoe_path = self._get_aoe_info(connection_properties)
|
|
|
|
device_info = {
|
|
'type': 'block',
|
|
'device': aoe_device,
|
|
'path': aoe_path,
|
|
}
|
|
|
|
if os.path.exists(aoe_path):
|
|
self._aoe_revalidate(aoe_device)
|
|
else:
|
|
self._aoe_discover()
|
|
|
|
waiting_status = {'tries': 0}
|
|
|
|
# NOTE(jbr_): Device path is not always present immediately
|
|
def _wait_for_discovery(aoe_path):
|
|
if os.path.exists(aoe_path):
|
|
raise loopingcall.LoopingCallDone
|
|
|
|
if waiting_status['tries'] >= self.device_scan_attempts:
|
|
raise exception.VolumeDeviceNotFound(device=aoe_path)
|
|
|
|
LOG.info("AoE volume not yet found at: %(path)s. "
|
|
"Try number: %(tries)s",
|
|
{'path': aoe_device, 'tries': waiting_status['tries']})
|
|
|
|
self._aoe_discover()
|
|
waiting_status['tries'] += 1
|
|
|
|
timer = loopingcall.FixedIntervalLoopingCall(_wait_for_discovery,
|
|
aoe_path)
|
|
timer.start(interval=2).wait()
|
|
|
|
if waiting_status['tries']:
|
|
LOG.debug("Found AoE device %(path)s "
|
|
"(after %(tries)s rediscover)",
|
|
{'path': aoe_path,
|
|
'tries': waiting_status['tries']})
|
|
|
|
return device_info
|
|
|
|
@utils.trace
|
|
@lockutils.synchronized('aoe_control', 'aoe-')
|
|
def disconnect_volume(self, connection_properties, device_info,
|
|
force=False, ignore_errors=False):
|
|
"""Detach and flush the volume.
|
|
|
|
:param connection_properties: The dictionary that describes all
|
|
of the target volume attributes.
|
|
:type connection_properties: dict
|
|
:param device_info: historical difference, but same as connection_props
|
|
:type device_info: dict
|
|
|
|
connection_properties for AoE must include:
|
|
target_shelf - shelf id of volume
|
|
target_lun - lun id of volume
|
|
"""
|
|
aoe_device, aoe_path = self._get_aoe_info(connection_properties)
|
|
|
|
if os.path.exists(aoe_path):
|
|
self._aoe_flush(aoe_device)
|
|
|
|
def _aoe_discover(self):
|
|
(out, err) = self._execute('aoe-discover',
|
|
run_as_root=True,
|
|
root_helper=self._root_helper,
|
|
check_exit_code=0)
|
|
|
|
LOG.debug('aoe-discover: stdout=%(out)s stderr%(err)s',
|
|
{'out': out, 'err': err})
|
|
|
|
def _aoe_revalidate(self, aoe_device):
|
|
(out, err) = self._execute('aoe-revalidate',
|
|
aoe_device,
|
|
run_as_root=True,
|
|
root_helper=self._root_helper,
|
|
check_exit_code=0)
|
|
|
|
LOG.debug('aoe-revalidate %(dev)s: stdout=%(out)s stderr%(err)s',
|
|
{'dev': aoe_device, 'out': out, 'err': err})
|
|
|
|
def _aoe_flush(self, aoe_device):
|
|
(out, err) = self._execute('aoe-flush',
|
|
aoe_device,
|
|
run_as_root=True,
|
|
root_helper=self._root_helper,
|
|
check_exit_code=0)
|
|
LOG.debug('aoe-flush %(dev)s: stdout=%(out)s stderr%(err)s',
|
|
{'dev': aoe_device, 'out': out, 'err': err})
|
|
|
|
def extend_volume(self, connection_properties):
|
|
# TODO(walter-boring): is this possible?
|
|
raise NotImplementedError
|