Refactor connections
This patch refactors the connections in order to: - Decrease dependency on Cinder code for attach/detach - Decrease dependency on Cinder code for connect/disconnect - Only use existing OVO fields in Pike - Store connection_info, connector_info, and device in the metadata storage
This commit is contained in:
@@ -12,6 +12,7 @@ History
|
||||
- Setting default project_id and user_id.
|
||||
- Persistence plugin mechanism
|
||||
- DB persistence plugin
|
||||
- No longer dependent on Cinder's attach/detach code
|
||||
|
||||
- Bug fixes:
|
||||
|
||||
|
||||
@@ -124,6 +124,8 @@ class Backend(object):
|
||||
# Prevent driver dynamic loading clearing configuration options
|
||||
volume_cmd.CONF._ConfigOpts__cache = MyDict()
|
||||
|
||||
cls.root_helper = root_helper
|
||||
|
||||
volume_cmd.CONF.version = volume_cmd.version.version_string()
|
||||
volume_cmd.CONF.register_opt(
|
||||
configuration.cfg.StrOpt('stateless_cinder'),
|
||||
|
||||
@@ -23,8 +23,10 @@ from cinder import context
|
||||
from cinder.cmd import volume as volume_cmd
|
||||
from cinder import objects as cinder_objs
|
||||
from cinder.objects import base as cinder_base_ovo
|
||||
from cinder import utils
|
||||
from os_brick import exception as brick_exception
|
||||
from os_brick import initiator as brick_initiator
|
||||
from os_brick.initiator import connector as brick_connector
|
||||
from oslo_log import log as logging
|
||||
from oslo_utils import timeutils
|
||||
from oslo_versionedobjects import base as base_ovo
|
||||
import six
|
||||
@@ -32,6 +34,7 @@ import six
|
||||
from cinderlib import exception
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
DEFAULT_PROJECT_ID = 'cinderlib'
|
||||
DEFAULT_USER_ID = 'cinderlib'
|
||||
|
||||
@@ -396,7 +399,9 @@ class Volume(NamedObject):
|
||||
return snap
|
||||
|
||||
def attach(self):
|
||||
connector_dict = utils.brick_get_connector_properties(
|
||||
connector_dict = brick_connector.get_connector_properties(
|
||||
self.backend_class.root_helper,
|
||||
volume_cmd.CONF.my_ip,
|
||||
self.backend.configuration.use_multipath_for_image_xfer,
|
||||
self.backend.configuration.enforce_multipath_for_image_xfer)
|
||||
conn = self.connect(connector_dict)
|
||||
@@ -470,6 +475,18 @@ class Volume(NamedObject):
|
||||
|
||||
|
||||
class Connection(Object):
|
||||
"""Cinderlib Connection info that maps to VolumeAttachment.
|
||||
|
||||
On Pike we don't have the connector field on the VolumeAttachment ORM
|
||||
instance so we use the connection_info to store everything.
|
||||
|
||||
We'll have a dictionary:
|
||||
{
|
||||
'conn': connection info
|
||||
'connector': connector dictionary
|
||||
'device': result of connect_volume
|
||||
}
|
||||
"""
|
||||
OVO_CLASS = volume_cmd.objects.VolumeAttachment
|
||||
|
||||
@classmethod
|
||||
@@ -481,22 +498,36 @@ class Connection(Object):
|
||||
volume=volume,
|
||||
status='attached',
|
||||
attach_mode='rw',
|
||||
connection_info=conn_info,
|
||||
connection_info={'conn': conn_info},
|
||||
*kwargs)
|
||||
conn.connector_info = connector
|
||||
cls.persistence.set_connection(conn)
|
||||
return conn
|
||||
|
||||
@staticmethod
|
||||
def _is_multipathed_conn(conn_info):
|
||||
iscsi_mp = 'target_iqns' in conn_info and 'target_portals' in conn_info
|
||||
fc_mp = not isinstance(conn_info.get('target_wwn', ''),
|
||||
six.string_types)
|
||||
|
||||
return iscsi_mp or fc_mp
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self.connected = True
|
||||
conn_info = (kwargs.get('connection_info') or {}).get('conn', {})
|
||||
|
||||
# If multipathed not defined autodetect it
|
||||
self.use_multipath = kwargs.pop('use_multipath',
|
||||
self._is_multipathed_conn(conn_info))
|
||||
|
||||
scan_attempts = brick_initiator.DEVICE_SCAN_ATTEMPTS_DEFAULT
|
||||
self.scan_attempts = kwargs.pop('device_scan_attempts', scan_attempts)
|
||||
self._volume = kwargs.pop('volume')
|
||||
self.connector = kwargs.pop('connector', None)
|
||||
self.attach_info = kwargs.pop('attach_info', None)
|
||||
self._connector = None
|
||||
if '__ovo' not in kwargs:
|
||||
kwargs['volume'] = self._volume._ovo
|
||||
kwargs['volume_id'] = self._volume._ovo.id
|
||||
|
||||
super(Connection, self).__init__(*args, **kwargs)
|
||||
|
||||
self._populate_data()
|
||||
|
||||
@property
|
||||
@@ -529,21 +560,86 @@ class Connection(Object):
|
||||
result['attachment'] = attach_info
|
||||
return result
|
||||
|
||||
@property
|
||||
def conn_info(self):
|
||||
conn_info = self._ovo.connection_info
|
||||
if conn_info:
|
||||
return conn_info.get('conn')
|
||||
return {}
|
||||
|
||||
@conn_info.setter
|
||||
def conn_info(self, value):
|
||||
if not value:
|
||||
self._ovo.connection_info = None
|
||||
return
|
||||
|
||||
if self._ovo.connection_info is None:
|
||||
self._ovo.connection_info = {}
|
||||
self._ovo.connection_info['conn'] = value
|
||||
|
||||
@property
|
||||
def protocol(self):
|
||||
return self.conn_info.get('driver_volume_type')
|
||||
|
||||
@property
|
||||
def connector_info(self):
|
||||
if self.connection_info:
|
||||
return self.connection_info.get('connector')
|
||||
return None
|
||||
|
||||
@connector_info.setter
|
||||
def connector_info(self, value):
|
||||
self.connection_info['connector'] = value
|
||||
# Since we are changing the dictionary the OVO won't detect the change
|
||||
self._changed_fields.add('connection_info')
|
||||
|
||||
@property
|
||||
def device(self):
|
||||
if self.connection_info:
|
||||
return self.connection_info.get('device')
|
||||
return None
|
||||
|
||||
@device.setter
|
||||
def device(self, value):
|
||||
if value:
|
||||
self.connection_info['device'] = value
|
||||
else:
|
||||
self.connection_info.pop('device', None)
|
||||
# Since we are changing the dictionary the OVO won't detect the change
|
||||
self._changed_fields.add('connection_info')
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
device = self.device
|
||||
if not device:
|
||||
return None
|
||||
return device['path']
|
||||
|
||||
@property
|
||||
def connector(self):
|
||||
if not self._connector:
|
||||
if not self.conn_info:
|
||||
return None
|
||||
self._connector = brick_connector.InitiatorConnector.factory(
|
||||
self.protocol, self.backend_class.root_helper,
|
||||
use_multipath=self.use_multipath,
|
||||
device_scan_attempts=self.scan_attempts,
|
||||
# NOTE(geguileo): afaik only remotefs uses the connection info
|
||||
conn=self.conn_info)
|
||||
return self._connector
|
||||
|
||||
@property
|
||||
def attached(self):
|
||||
return bool(self.device)
|
||||
|
||||
@property
|
||||
def connected(self):
|
||||
return bool(self.conn_info)
|
||||
|
||||
def _populate_data(self):
|
||||
# Ensure circular reference is set
|
||||
self._ovo.volume = self.volume._ovo
|
||||
|
||||
data = getattr(self._ovo, 'cinderlib_data', None)
|
||||
if data:
|
||||
self.connector = data.get('connector', None)
|
||||
self.attach_info = data.get('attachment', None)
|
||||
conn = (self.attach_info or {}).get('connector')
|
||||
if isinstance(conn, dict):
|
||||
self.attach_info['connector'] = utils.brick_get_connector(
|
||||
self.connection_info['driver_volume_type'],
|
||||
conn=self.connection_info,
|
||||
**conn)
|
||||
self.attached = bool(self.attach_info)
|
||||
if self._volume:
|
||||
self._ovo.volume = self.volume._ovo
|
||||
|
||||
def _replace_ovo(self, ovo):
|
||||
super(Connection, self)._replace_ovo(ovo)
|
||||
@@ -559,13 +655,11 @@ class Connection(Object):
|
||||
return Connection.objects[ovo.id]
|
||||
|
||||
def _disconnect(self, force=False):
|
||||
self.backend.driver.terminate_connection(self._ovo.volume,
|
||||
self.connector,
|
||||
self.backend.driver.terminate_connection(self.volume._ovo,
|
||||
self.connector_info,
|
||||
force=force)
|
||||
self.connected = False
|
||||
|
||||
self.conn_info = None
|
||||
self._ovo.status = 'detached'
|
||||
self._ovo.deleted = True
|
||||
self.persistence.delete_connection(self)
|
||||
|
||||
def disconnect(self, force=False):
|
||||
@@ -573,32 +667,40 @@ class Connection(Object):
|
||||
self.volume._disconnect(self)
|
||||
|
||||
def attach(self):
|
||||
self.attach_info = self.backend.driver._connect_device(
|
||||
self.connection_info)
|
||||
self.attached = True
|
||||
self.volume.local_attach = self
|
||||
self.device = self.connector.connect_volume(self.conn_info['data'])
|
||||
self.persistence.set_connection(self)
|
||||
try:
|
||||
unavailable = not self.connector.check_valid_device(self.path)
|
||||
except Exception:
|
||||
unavailable = True
|
||||
LOG.exception('Could not validate device %s', self.path)
|
||||
|
||||
if unavailable:
|
||||
raise exception.DeviceUnavailable(
|
||||
path=self.path, attach_info=self._ovo.connection_information,
|
||||
reason=('Unable to access the backend storage via path '
|
||||
'%s.') % self.path)
|
||||
if self._volume:
|
||||
self.volume.local_attach = self
|
||||
|
||||
def detach(self, force=False, ignore_errors=False, exc=None):
|
||||
if not exc:
|
||||
exc = brick_exception.ExceptionChainer()
|
||||
connector = self.attach_info['connector']
|
||||
with exc.context(force, 'Disconnect failed'):
|
||||
connector.disconnect_volume(self.connection_info['data'],
|
||||
self.attach_info['device'],
|
||||
force=force,
|
||||
ignore_errors=ignore_errors)
|
||||
self.attached = False
|
||||
self.volume.local_attach = None
|
||||
self.connector.disconnect_volume(self.conn_info['data'],
|
||||
self.device,
|
||||
force=force,
|
||||
ignore_errors=ignore_errors)
|
||||
if not exc or ignore_errors:
|
||||
if self._volume:
|
||||
self.volume.local_attach = None
|
||||
self.device = None
|
||||
self.persistence.set_connection(self)
|
||||
self._connector = None
|
||||
|
||||
if exc and not ignore_errors:
|
||||
raise exc
|
||||
|
||||
@property
|
||||
def path(self):
|
||||
if self.attach_info:
|
||||
return self.attach_info['device']['path']
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_by_id(cls, connection_id):
|
||||
result = cls.persistence.get_connections(connection_id=connection_id)
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# under the License.
|
||||
|
||||
from __future__ import absolute_import
|
||||
|
||||
import logging
|
||||
|
||||
from cinder.cmd import volume as volume_cmd
|
||||
@@ -21,13 +22,13 @@ from cinder.db import api as db_api
|
||||
from cinder.db import migration
|
||||
from cinder.db.sqlalchemy import api as sqla_api
|
||||
from cinder import objects as cinder_objs
|
||||
# from oslo_log import log as logging
|
||||
from oslo_log import log
|
||||
|
||||
from cinderlib import objects
|
||||
from cinderlib.persistence import base as persistence_base
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
class DBPersistence(persistence_base.PersistenceDriverBase):
|
||||
@@ -120,8 +121,6 @@ class DBPersistence(persistence_base.PersistenceDriverBase):
|
||||
|
||||
if 'connection_info' in changed:
|
||||
connection._convert_connection_info_to_db_format(changed)
|
||||
if 'connector' in changed:
|
||||
connection._convert_connector_to_db_format(changed)
|
||||
|
||||
# Create
|
||||
if 'id' in changed:
|
||||
|
||||
Reference in New Issue
Block a user