# 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 import socket from oslo_concurrency import processutils as putils from oslo_log import log as logging from os_brick import exception from os_brick.i18n import _ from os_brick import initiator from os_brick.initiator.connectors import base from os_brick import utils LOG = logging.getLogger(__name__) class HGSTConnector(base.BaseLinuxConnector): """Connector class to attach/detach HGST volumes.""" VGCCLUSTER = 'vgc-cluster' def __init__(self, root_helper, driver=None, device_scan_attempts=initiator.DEVICE_SCAN_ATTEMPTS_DEFAULT, *args, **kwargs): super(HGSTConnector, self).__init__(root_helper, driver=driver, device_scan_attempts= device_scan_attempts, *args, **kwargs) self._vgc_host = None @staticmethod def get_connector_properties(root_helper, *args, **kwargs): """The HGST connector properties.""" return {} def _log_cli_err(self, err): """Dumps the full command output to a logfile in error cases.""" LOG.error("CLI fail: '%(cmd)s' = %(code)s\nout: %(stdout)s\n" "err: %(stderr)s", {'cmd': err.cmd, 'code': err.exit_code, 'stdout': err.stdout, 'stderr': err.stderr}) def _find_vgc_host(self): """Finds vgc-cluster hostname for this box.""" params = [self.VGCCLUSTER, "domain-list", "-1"] try: out, unused = self._execute(*params, run_as_root=True, root_helper=self._root_helper) except putils.ProcessExecutionError as err: self._log_cli_err(err) msg = _("Unable to get list of domain members, check that " "the cluster is running.") raise exception.BrickException(message=msg) domain = out.splitlines() params = ["ip", "addr", "list"] try: out, unused = self._execute(*params, run_as_root=False) except putils.ProcessExecutionError as err: self._log_cli_err(err) msg = _("Unable to get list of IP addresses on this host, " "check permissions and networking.") raise exception.BrickException(message=msg) nets = out.splitlines() for host in domain: try: ip = socket.gethostbyname(host) for l in nets: x = l.strip() if x.startswith("inet %s/" % ip): return host except socket.error: pass msg = _("Current host isn't part of HGST domain.") raise exception.BrickException(message=msg) def _hostname(self): """Returns hostname to use for cluster operations on this box.""" if self._vgc_host is None: self._vgc_host = self._find_vgc_host() return self._vgc_host def get_search_path(self): return "/dev" def get_volume_paths(self, connection_properties): path = ("%(path)s/%(name)s" % {'path': self.get_search_path(), 'name': connection_properties['name']}) volume_path = None if os.path.exists(path): volume_path = path return [volume_path] @utils.trace def connect_volume(self, connection_properties): """Attach a Space volume to running host. :param connection_properties: The dictionary that describes all of the target volume attributes. connection_properties for HGST must include: name - Name of space to attach :type connection_properties: dict :returns: dict """ if connection_properties is None: msg = _("Connection properties passed in as None.") raise exception.BrickException(message=msg) if 'name' not in connection_properties: msg = _("Connection properties missing 'name' field.") raise exception.BrickException(message=msg) device_info = { 'type': 'block', 'device': connection_properties['name'], 'path': '/dev/' + connection_properties['name'] } volname = device_info['device'] params = [self.VGCCLUSTER, 'space-set-apphosts'] params += ['-n', volname] params += ['-A', self._hostname()] params += ['--action', 'ADD'] try: self._execute(*params, run_as_root=True, root_helper=self._root_helper) except putils.ProcessExecutionError as err: self._log_cli_err(err) msg = (_("Unable to set apphost for space %s") % volname) raise exception.BrickException(message=msg) return device_info @utils.trace 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. For HGST must include: name - Name of space to detach noremovehost - Host which should never be removed :type connection_properties: dict :param device_info: historical difference, but same as connection_props :type device_info: dict """ if connection_properties is None: msg = _("Connection properties passed in as None.") raise exception.BrickException(message=msg) if 'name' not in connection_properties: msg = _("Connection properties missing 'name' field.") raise exception.BrickException(message=msg) if 'noremovehost' not in connection_properties: msg = _("Connection properties missing 'noremovehost' field.") raise exception.BrickException(message=msg) if connection_properties['noremovehost'] != self._hostname(): params = [self.VGCCLUSTER, 'space-set-apphosts'] params += ['-n', connection_properties['name']] params += ['-A', self._hostname()] params += ['--action', 'DELETE'] try: self._execute(*params, run_as_root=True, root_helper=self._root_helper) except putils.ProcessExecutionError as err: self._log_cli_err(err) msg = (_("Unable to set apphost for space %s") % connection_properties['name']) raise exception.BrickException(message=msg) def extend_volume(self, connection_properties): # TODO(walter-boring): is this possible? raise NotImplementedError