def extend_volume(self, connection_properties): """Update the local kernel's size information. Try and update the local kernel's size information for a ScaleIO volume. """ LOG.info("ScaleIO rescan volumes: %(cmd)s", {'cmd': self.RESCAN_VOLS_CMD}) try: (out, err) = self._execute(*self.RESCAN_VOLS_CMD, run_as_root=True, root_helper=self._root_helper) LOG.debug("Rescan volumes %(cmd)s: stdout=%(out)s", { 'cmd': self.RESCAN_VOLS_CMD, 'out': out }) except putils.ProcessExecutionError as e: msg = (_("Error querying volumes: %(err)s") % {'err': e.stderr}) LOG.error(msg) raise exception.BrickException(message=msg) volume_paths = self.get_volume_paths(connection_properties) if volume_paths: return self.get_device_size(volume_paths[0]) # if we got here, the volume is not mapped msg = (_("Error extending ScaleIO volume")) LOG.error(msg) raise exception.BrickException(message=msg)
def _wait_for_volume_path(self, path): if not os.path.isdir(path): msg = (_("ScaleIO volume %(volume_id)s not found at " "expected path.") % { 'volume_id': self.volume_id }) LOG.debug(msg) raise exception.BrickException(message=msg) disk_filename = None filenames = os.listdir(path) LOG.info("Files found in %(path)s path: %(files)s ", { 'path': path, 'files': filenames }) for filename in filenames: if (filename.startswith("emc-vol") and filename.endswith(self.volume_id)): disk_filename = filename break if not disk_filename: msg = (_("ScaleIO volume %(volume_id)s not found.") % { 'volume_id': self.volume_id }) LOG.debug(msg) raise exception.BrickException(message=msg) return disk_filename
def _get_client_id(self): request = ("https://%(server_ip)s:%(server_port)s/" "api/types/Client/instances/getByIp::%(sdc_ip)s/" % { 'server_ip': self.server_ip, 'server_port': self.server_port, 'sdc_ip': self.local_sdc_ip }) LOG.info("ScaleIO get client id by ip request: %(request)s", {'request': request}) r = requests.get(request, auth=(self.server_username, self.server_token), verify=False) r = self._check_response(r, request) sdc_id = r.json() if not sdc_id: msg = (_("Client with ip %(sdc_ip)s was not found.") % { 'sdc_ip': self.local_sdc_ip }) raise exception.BrickException(message=msg) if r.status_code != 200 and "errorCode" in sdc_id: msg = (_("Error getting sdc id from ip %(sdc_ip)s: %(err)s") % { 'sdc_ip': self.local_sdc_ip, 'err': sdc_id['message'] }) LOG.error(msg) raise exception.BrickException(message=msg) LOG.info("ScaleIO sdc id is %(sdc_id)s.", {'sdc_id': sdc_id}) return sdc_id
def get_volume_paths(self, connection_properties): """Return the list of existing paths for a volume. The job of this method is to find out what paths in the system are associated with a volume as described by the connection_properties. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain 'volume' and 'device_path' values. :type connection_properties: dict """ volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException( 'Invalid StorPool connection data, no volume ID specified.') volume = self._attach.volumeName(volume_id) path = '/dev/storpool/' + volume dpath = connection_properties.get('device_path', None) if dpath is not None and dpath != path: raise exception.BrickException( 'Internal error: StorPool volume path {path} does not ' 'match device path {dpath}', { path: path, dpath: dpath }) return [path]
def connect_volume(self, connection_properties): """Connect to a volume. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain the StorPool 'client_id' and the common 'volume' and 'access_mode' values. :type connection_properties: dict :returns: dict """ client_id = connection_properties.get('client_id', None) if client_id is None: raise exception.BrickException( 'Invalid StorPool connection data, no client ID specified.') volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException( 'Invalid StorPool connection data, no volume ID specified.') volume = self._attach.volumeName(volume_id) mode = connection_properties.get('access_mode', None) if mode is None or mode not in ('rw', 'ro'): raise exception.BrickException( 'Invalid access_mode specified in the connection data.') req_id = 'brick-%s-%s' % (client_id, volume_id) self._attach.add( req_id, { 'volume': volume, 'type': 'brick', 'id': req_id, 'rights': 1 if mode == 'ro' else 2, 'volsnap': False }) self._attach.sync(req_id, None) return {'type': 'block', 'path': '/dev/storpool/' + volume}
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 disconnect_volume(self, connection_properties, device_info): """Disconnect a volume from an instance.""" volume_name = None if 'name' in connection_properties.keys(): volume_name = connection_properties['name'] if volume_name is None or len(volume_name) != GUID_STR_LEN: msg = _("Failed to disconnect volume: invalid volume name") raise exception.BrickException(message=msg) cmd_arg = {'operation': 'disconnect_volume'} cmd_arg['volume_guid'] = volume_name cmdarg_json = json.dumps(cmd_arg) LOG.debug("HyperScale command hscli: %(cmd_arg)s", {'cmd_arg': cmdarg_json}) try: (out, err) = self._execute('hscli', cmdarg_json, run_as_root=True, root_helper=self._root_helper) except putils.ProcessExecutionError as e: msg = (_("Error executing hscli: %(err)s") % {'err': e.stderr}) raise exception.BrickException(message=msg) if err: msg = (_("Failed to connect volume: stdout=%(out)s " "stderr=%(err)s") % { 'out': out, 'err': err }) raise exception.BrickException(message=msg)
def connect_volume(self, connection_properties): """Connect to a volume. :param connection_properties: The dictionary that describes all of the target volume attributes. :type connection_properties: dict :returns: dict """ LOG.debug("Connect_volume connection properties: %s.", connection_properties) out = self._attach_volume(connection_properties['volume_id']) if not out or int(out['ret_code']) not in (self.attached_success_code, self.has_been_attached_code, self.attach_mnid_done_code): msg = (_("Attach volume failed, " "error code is %s") % out['ret_code']) raise exception.BrickException(message=msg) try: volume_path = self._get_volume_path(connection_properties) except Exception: msg = _("query attached volume failed or volume not attached.") LOG.error(msg) raise exception.BrickException(message=msg) device_info = {'type': 'block', 'path': volume_path} return device_info
def disconnect_volume(self, connection_properties, device_info): """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 disconnect_volume(self, connection_properties, device_info): """Disconnect a volume from the local host. The connection_properties are the same as from connect_volume. The device_info is returned from connect_volume. :param connection_properties: The dictionary that describes all of the target volume attributes; it needs to contain the StorPool 'client_id' and the common 'volume' values. :type connection_properties: dict :param device_info: historical difference, but same as connection_props :type device_info: dict """ client_id = connection_properties.get('client_id', None) if client_id is None: raise exception.BrickException( _LE('Invalid StorPool connection data, no client ID specified.' )) volume_id = connection_properties.get('volume', None) if volume_id is None: raise exception.BrickException( _LE('Invalid StorPool connection data, no volume ID specified.' )) volume = self._attach.volumeName(volume_id) req_id = 'brick-%s-%s' % (client_id, volume_id) self._attach.sync(req_id, volume) self._attach.remove(req_id)
def connect_volume(self, connection_properties): # NOTE(e0ne): sanity check if ceph-common is installed. self._setup_rbd_class() # Extract connection parameters and generate config file try: user = connection_properties['auth_username'] pool, volume = connection_properties['name'].split('/') cluster_name = connection_properties.get('cluster_name') monitor_ips = connection_properties.get('hosts') monitor_ports = connection_properties.get('ports') keyring = connection_properties.get('keyring') except IndexError: msg = 'Malformed connection properties' raise exception.BrickException(msg) conf = self._create_ceph_conf(monitor_ips, monitor_ports, str(cluster_name), user, keyring) link_name = self.get_rbd_device_name(pool, volume) real_path = os.path.realpath(link_name) try: # Map RBD volume if it's not already mapped if not os.path.islink(link_name) or not os.path.exists(real_path): cmd = ['rbd', 'map', volume, '--pool', pool, '--conf', conf] cmd += self._get_rbd_args(connection_properties) stdout, stderr = self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) real_path = stdout.strip() # The host may not have RBD installed, and therefore won't # create the symlinks, ensure they exist if self.containerized: self._ensure_link(real_path, link_name) except Exception as exec_exception: try: try: self._unmap(real_path, conf, connection_properties) finally: fileutils.delete_if_exists(conf) except Exception: exc = traceback.format_exc() print( 'Exception occurred while cleaning up after connection ' 'error\n%s', exc) finally: raise exception.BrickException('Error connecting volume: %s' % six.text_type(exec_exception)) return {'path': real_path, 'conf': conf, 'type': 'block'}
def _get_volume_id(self): volname_encoded = urllib.parse.quote(self.volume_name, '') volname_double_encoded = urllib.parse.quote(volname_encoded, '') LOG.debug(_( "Volume name after double encoding is %(volume_name)s."), {'volume_name': volname_double_encoded} ) request = ( "https://%(server_ip)s:%(server_port)s/api/types/Volume/instances" "/getByName::%(encoded_volume_name)s" % { 'server_ip': self.server_ip, 'server_port': self.server_port, 'encoded_volume_name': volname_double_encoded } ) LOG.info( "ScaleIO get volume id by name request: %(request)s", {'request': request} ) r = requests.get(request, auth=(self.server_username, self.server_token), verify=False) r = self._check_response(r, request) volume_id = r.json() if not volume_id: msg = (_("Volume with name %(volume_name)s wasn't found.") % {'volume_name': self.volume_name}) LOG.error(msg) raise exception.BrickException(message=msg) if r.status_code != self.OK_STATUS_CODE and "errorCode" in volume_id: msg = ( _("Error getting volume id from name %(volume_name)s: " "%(err)s") % {'volume_name': self.volume_name, 'err': volume_id['message']} ) LOG.error(msg) raise exception.BrickException(message=msg) LOG.info("ScaleIO volume id is %(volume_id)s.", {'volume_id': volume_id}) return volume_id
def _create_ceph_conf(self, monitor_ips, monitor_ports, cluster_name, user, keyring_path): monitors = [ "%s:%s" % (ip, port) for ip, port in zip( self._sanitize_mon_hosts(monitor_ips), monitor_ports) ] mon_hosts = "mon_host = %s" % (','.join(monitors)) client_section = "[client.%s]" % user if keyring_path is None: keyring = ("keyring = /etc/ceph/%s.client.%s.keyring" % (cluster_name, user)) else: keyring = "keyring = %s" % keyring_path try: fd, ceph_conf_path = tempfile.mkstemp(prefix="brickrbd_") with os.fdopen(fd, 'w') as conf_file: conf_file.writelines( [mon_hosts, "\n", client_section, "\n", keyring, "\n"]) return ceph_conf_path except IOError: msg = (_("Failed to write data to %s.") % (ceph_conf_path)) raise exception.BrickException(msg=msg)
def _get_rbd_handle(self, connection_properties): try: user = connection_properties['auth_username'] pool, volume = connection_properties['name'].split('/') cluster_name = connection_properties['cluster_name'] monitor_ips = connection_properties['hosts'] monitor_ports = connection_properties['ports'] # NOTE: cinder no longer passes keyring data in the connection # properties as of the victoria release. See OSSN-0085. But # cinderlib does, so we must keep the code related to the keyring. keyring = connection_properties.get('keyring') except (KeyError, ValueError): msg = _("Connect volume failed, malformed connection properties.") raise exception.BrickException(msg=msg) conf = self._create_ceph_conf(monitor_ips, monitor_ports, str(cluster_name), user, keyring) try: rbd_client = linuxrbd.RBDClient(user, pool, conffile=conf, rbd_cluster_name=str(cluster_name)) rbd_volume = linuxrbd.RBDVolume(rbd_client, volume) rbd_handle = linuxrbd.RBDVolumeIOWrapper( linuxrbd.RBDImageMetadata(rbd_volume, pool, user, conf)) except Exception: fileutils.delete_if_exists(conf) raise return rbd_handle
def disconnect_volume(self, connection_properties, device_info, force=False, ignore_errors=False): tmp_file_path = device_info['path'] if not os.path.exists(tmp_file_path): msg = _("Vmdk: %s not found.") % tmp_file_path raise exception.NotFound(message=msg) session = None try: # We upload the temporary file to vCenter server only if it is # modified after connect_volume. if os.path.getmtime(tmp_file_path) > device_info['last_modified']: self._load_config(connection_properties) session = self._create_session() backing = vim_util.get_moref(connection_properties['volume'], "VirtualMachine") # Currently there is no way we can restore the volume if it # contains redo-log based snapshots (bug 1599026). if self._snapshot_exists(session, backing): msg = (_("Backing of volume: %s contains one or more " "snapshots; cannot disconnect.") % connection_properties['volume_id']) raise exception.BrickException(message=msg) ds_ref = vim_util.get_moref( connection_properties['datastore'], "Datastore") dc_ref = vim_util.get_moref( connection_properties['datacenter'], "Datacenter") vmdk_path = connection_properties['vmdk_path'] self._disconnect( backing, tmp_file_path, session, ds_ref, dc_ref, vmdk_path) finally: os.remove(tmp_file_path) if session: session.logout()
def _get_rbd_handle(self, connection_properties): try: user = connection_properties['auth_username'] pool, volume = connection_properties['name'].split('/') cluster_name = connection_properties['cluster_name'] monitor_ips = connection_properties['hosts'] monitor_ports = connection_properties['ports'] keyring = connection_properties.get('keyring') except (KeyError, ValueError): msg = _("Connect volume failed, malformed connection properties.") raise exception.BrickException(msg=msg) conf = self._create_ceph_conf(monitor_ips, monitor_ports, str(cluster_name), user, keyring) try: rbd_client = linuxrbd.RBDClient(user, pool, conffile=conf, rbd_cluster_name=str(cluster_name)) rbd_volume = linuxrbd.RBDVolume(rbd_client, volume) rbd_handle = linuxrbd.RBDVolumeIOWrapper( linuxrbd.RBDImageMetadata(rbd_volume, pool, user, conf)) except Exception: fileutils.delete_if_exists(conf) raise return rbd_handle
def test_detach_volume_fail_disconnect(self, mock_cinder_api_cls, mock_get_connector_prprts, mock_get_volume_connector): volume = mock.MagicMock() volume.volume_id = self.fake_volume_id volume.connection_info = jsonutils.dumps(self.fake_conn_info) mock_cinder_api = mock.MagicMock() mock_cinder_api_cls.return_value = mock_cinder_api mock_connector = mock.MagicMock() mock_get_connector_prprts.return_value = self.fake_conn_prprts mock_get_volume_connector.return_value = mock_connector mock_connector.disconnect_volume.side_effect = \ os_brick_exception.BrickException() cinder = cinder_workflow.CinderWorkflow(self.context) self.assertRaises(os_brick_exception.BrickException, cinder.detach_volume, volume) mock_cinder_api.begin_detaching.assert_called_once_with( self.fake_volume_id) mock_connector.disconnect_volume.assert_called_once_with( self.fake_conn_info['data'], None) mock_cinder_api.terminate_connection.assert_not_called() mock_cinder_api.detach.assert_not_called() mock_cinder_api.roll_detaching.assert_called_once_with( self.fake_volume_id)
def _get_rbd_handle(self, connection_properties): try: user = connection_properties['auth_username'] pool, volume = connection_properties['name'].split('/') cluster_name = connection_properties.get('cluster_name') monitor_ips = connection_properties.get('hosts') monitor_ports = connection_properties.get('ports') except IndexError: msg = _("Connect volume failed, malformed connection properties") raise exception.BrickException(msg=msg) conf = self._create_ceph_conf(monitor_ips, monitor_ports, str(cluster_name), user) rbd_client = linuxrbd.RBDClient(user, pool, conffile=conf, rbd_cluster_name=str(cluster_name)) rbd_volume = linuxrbd.RBDVolume(rbd_client, volume) rbd_handle = linuxrbd.RBDVolumeIOWrapper( linuxrbd.RBDImageMetadata(rbd_volume, pool, user, conf)) if os.path.exists(conf): os.remove(conf) return rbd_handle
def _mount_nfs(self, nfs_share, mount_path, flags=None): """Mount nfs share using present mount types.""" mnt_errors = {} # This loop allows us to first try to mount with NFS 4.1 for pNFS # support but falls back to mount NFS 4 or NFS 3 if either the client # or server do not support it. for mnt_type in sorted(self._nfs_mount_type_opts.keys(), reverse=True): options = self._nfs_mount_type_opts[mnt_type] try: self._do_mount('nfs', nfs_share, mount_path, options, flags) LOG.debug('Mounted %(sh)s using %(mnt_type)s.', { 'sh': nfs_share, 'mnt_type': mnt_type }) return except Exception as e: mnt_errors[mnt_type] = str(e) LOG.debug('Failed to do %s mount.', mnt_type) raise exception.BrickException( _("NFS mount failed for share %(sh)s. " "Error - %(error)s") % { 'sh': nfs_share, 'error': mnt_errors })
def disconnect_volume(self, connection_properties, device_info): """Disconnect 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 """ LOG.info("Connection Properties : %s", connection_properties) self.DETACH_VOLUME.append(connection_properties['provider_id']) try: (out, err) = self._execute(*self.DETACH_VOLUME, run_as_root=True, root_helper=self._root_helper) LOG.info("DeMap volume %(cmd)s: stdout=%(out)s " "stderr=%(err)s", { 'cmd': self.ATTACH_VOLUME, 'out': out, 'err': err }) self._wait_for_disconnect(connection_properties['provider_id']) except putils.ProcessExecutionError as e: msg = (_("Error querying pxctl host detach: %(err)s") % { 'err': e.stderr }) LOG.error(msg) raise exception.BrickException(message=msg) finally: self.DETACH_VOLUME.pop() return '/dev/pxd/pxd' + connection_properties['provider_id']
def _do_mount(self, mount_type, vz_share, mount_path, mount_options=None, flags=None): m = re.search(r"(?:(\S+):\/)?([a-zA-Z0-9_-]+)(?::(\S+))?", vz_share) if not m: msg = (_("Invalid Virtuozzo Storage share specification: %r." "Must be: [MDS1[,MDS2],...:/]<CLUSTER NAME>[:PASSWORD].") % vz_share) raise exception.BrickException(msg) mdss = m.group(1) cluster_name = m.group(2) passwd = m.group(3) if mdss: mdss = mdss.split(',') self._vzstorage_write_mds_list(cluster_name, mdss) if passwd: self._execute('pstorage', '-c', cluster_name, 'auth-node', '-P', process_input=passwd, root_helper=self._root_helper, run_as_root=True) mnt_cmd = ['pstorage-mount', '-c', cluster_name] if flags: mnt_cmd.extend(flags) mnt_cmd.extend([mount_path]) self._execute(*mnt_cmd, root_helper=self._root_helper, run_as_root=True, check_exit_code=0)
def _get_volume_path(self, connection_properties): out = self._query_attached_volume(connection_properties['volume_id']) if not out or int(out['ret_code']) != 0: msg = _("Couldn't find attached volume.") LOG.error(msg) raise exception.BrickException(message=msg) return out['dev_addr']
def _check_device_paths(self, device_paths): if len(device_paths) > 1: err_msg = _("Multiple volume paths were found: %s. This can " "occur if multipath is used and MPIO is not " "properly configured, thus not claiming the device " "paths. This issue must be addressed urgently as " "it can lead to data corruption.") raise exception.BrickException(err_msg % device_paths)
def __init__(self, root_helper, driver=None, *args, **kwargs): super(StorPoolConnector, self).__init__(root_helper, driver=driver, *args, **kwargs) if storpool is None: raise exception.BrickException( 'Could not import the StorPool API bindings') try: self._attach = spopenstack.AttachDB(log=LOG) self._attach.api() except Exception as e: raise exception.BrickException( 'Could not initialize the StorPool API bindings: %s' % (e))
def _get_guid(self): try: guid = priv_scaleio.get_guid(self.GET_GUID_OP_CODE) LOG.info("Current sdc guid: %s", guid) return guid except (IOError, OSError, ValueError) as e: msg = _("Error querying sdc guid: %s") % e LOG.error(msg) raise exception.BrickException(message=msg)
def _extract_snapshot_name(self, volume_and_snap): tokens = volume_and_snap.split("@") if len(tokens) == 1: return tokens[0], None if len(tokens) == 2: return tokens[0], tokens[1] else: msg = _('Wrong volume or snapshot name %s') % volume_and_snap raise exception.BrickException(msg=msg)
def _rescan_vols(self): LOG.info("ScaleIO rescan volumes") try: priv_scaleio.rescan_vols(self.RESCAN_VOLS_OP_CODE) except (IOError, OSError) as e: msg = _("Error querying volumes: %s") % e LOG.error(msg) raise exception.BrickException(message=msg)
def _wait_for_disconnect(self, volume_id): path = "/dev/pxd/pxd" + volume_id if os.path.exists(path): msg = (_("PX volume %(volume_id)s found at " "not expected path.") % { 'volume_id': self.volume_id }) LOG.debug(msg) raise exception.BrickException(message=msg)
def get_volume_paths(self, connection_properties): volume_path = None try: volume_path = self._get_volume_path(connection_properties) except Exception: msg = _("Couldn't find a volume.") LOG.warning(msg) raise exception.BrickException(message=msg) return [volume_path]
def _local_attach_volume(self, connection_properties): # NOTE(e0ne): sanity check if ceph-common is installed. try: self._execute('which', 'rbd') except putils.ProcessExecutionError: msg = _("ceph-common package is not installed.") LOG.error(msg) raise exception.BrickException(message=msg) # NOTE(e0ne): map volume to a block device # via the rbd kernel module. pool, volume = connection_properties['name'].split('/') rbd_dev_path = self.get_rbd_device_name(pool, volume) # If we are not running on OpenStack, create config file conf = self.create_non_openstack_config(connection_properties) try: if (not os.path.islink(rbd_dev_path) or not os.path.exists(os.path.realpath(rbd_dev_path))): # TODO(stephenfin): Update to the unified 'rbd device map' # command introduced in ceph 13.0 (commit 6a57358add1157629a6d) # when we drop support earlier versions cmd = ['rbd', 'map', volume, '--pool', pool] cmd += self._get_rbd_args(connection_properties, conf) self._execute(*cmd, root_helper=self._root_helper, run_as_root=True) else: LOG.debug( 'Volume %(vol)s is already mapped to local device %(dev)s', { 'vol': volume, 'dev': os.path.realpath(rbd_dev_path) }) if (not os.path.islink(rbd_dev_path) or not os.path.exists(os.path.realpath(rbd_dev_path))): LOG.warning( 'Volume %(vol)s has not been mapped to local device ' '%(dev)s; is the udev daemon running and are the ' 'ceph-renamer udev rules configured? See bug #1884114 for ' 'more information.', { 'vol': volume, 'dev': rbd_dev_path }, ) except Exception: # Cleanup conf file on failure with excutils.save_and_reraise_exception(): if conf: rbd_privsep.delete_if_exists(conf) res = {'path': rbd_dev_path, 'type': 'block'} if conf: res['conf'] = conf return res