def create(self, docker_volume_name, volume_opts): if not volume_opts: volume_opts = {} connector = self._get_connector() cinder_volume, state = self._get_docker_volume(docker_volume_name) LOG.info( _LI("Get docker volume {0} {1} with state " "{2}").format(docker_volume_name, cinder_volume, state)) device_info = {} if state == ATTACH_TO_THIS: LOG.warn( _LW("The volume {0} {1} already exists and attached to " "this server").format(docker_volume_name, cinder_volume)) device_info = {'path': connector.get_device_path(cinder_volume)} elif state == NOT_ATTACH: LOG.warn( _LW("The volume {0} {1} is already exists but not " "attached").format(docker_volume_name, cinder_volume)) device_info = connector.connect_volume(cinder_volume) elif state == ATTACH_TO_OTHER: if cinder_volume.multiattach: fstype = volume_opts.get('fstype', cinder_conf.fstype) vol_fstype = cinder_volume.metadata.get( 'fstype', cinder_conf.fstype) if fstype != vol_fstype: msg = _LE("Volume already exists with fstype: {0}, but " "currently provided fstype is {1}, not " "match").format(vol_fstype, fstype) LOG.error(msg) raise exceptions.FuxiException('FSType Not Match') device_info = connector.connect_volume(cinder_volume) else: msg = _LE("The volume {0} {1} is already attached to another " "server").format(docker_volume_name, cinder_volume) LOG.error(msg) raise exceptions.FuxiException(msg) elif state == UNKNOWN: if 'volume_id' in volume_opts: cinder_volume = self._create_from_existing_volume( docker_volume_name, volume_opts.pop('volume_id'), volume_opts) if self._check_attached_to_this(cinder_volume): device_info = { 'path': connector.get_device_path(cinder_volume) } else: device_info = connector.connect_volume(cinder_volume) else: cinder_volume = self._create_volume(docker_volume_name, volume_opts) device_info = connector.connect_volume(cinder_volume) return device_info
def mount(self, docker_volume_name): cinder_volume, state = self._get_docker_volume(docker_volume_name) LOG.info(_LI("Get docker volume %(d_v)s %(vol)s with state %(st)s"), { 'd_v': docker_volume_name, 'vol': cinder_volume, 'st': state }) connector = self._get_connector() if state == NOT_ATTACH: connector.connect_volume(cinder_volume) elif state == ATTACH_TO_OTHER: if cinder_volume.multiattach: connector.connect_volume(cinder_volume) else: msg = _("Volume {0} {1} is not shareable").format( docker_volume_name, cinder_volume) raise exceptions.FuxiException(msg) elif state != ATTACH_TO_THIS: msg = _("Volume %(vol_name)s %(c_vol)s is not in correct state, " "current state is %(state)s") LOG.error( msg, { 'vol_name': docker_volume_name, 'c_vol': cinder_volume, 'state': state }) raise exceptions.NotMatchedState() link_path = connector.get_device_path(cinder_volume) if not os.path.exists(link_path): LOG.warning( _LW("Could not find device link file, " "so rebuild it")) connector.disconnect_volume(cinder_volume) connector.connect_volume(cinder_volume) devpath = os.path.realpath(link_path) if not devpath or not os.path.exists(devpath): msg = _("Can't find volume device path") LOG.error(msg) raise exceptions.FuxiException(msg) mountpoint = self._get_mountpoint(docker_volume_name) self._create_mountpoint(mountpoint) fstype = cinder_volume.metadata.get('fstype', cinder_conf.fstype) mount.do_mount(devpath, mountpoint, fstype) return mountpoint
def _get_connector(self): connector = cinder_conf.volume_connector if not connector or connector not in volume_connector_conf: msg = _LE("Must provide an valid volume connector") LOG.error(msg) raise exceptions.FuxiException(msg) return importutils.import_class(volume_connector_conf[connector])()
def get_device_size(self, device): try: nr_sectors = open(device + '/size').read().rstrip('\n') sect_size = open(device + '/queue/hw_sector_size')\ .read().rstrip('\n') return (float(nr_sectors) * float(sect_size)) / units.Gi except IOError as e: LOG.error(_LE("Failed to read device size. {0}").format(e)) raise exceptions.FuxiException(e.message)
def get_instance_uuid(): try: inst_uuid = '' inst_uuid_count = 0 dirs = os.listdir(cloud_init_conf) for uuid_dir in dirs: if uuidutils.is_uuid_like(uuid_dir): inst_uuid = uuid_dir inst_uuid_count += 1 # If not or not only get on instance_uuid, then search # it from metadata server. if inst_uuid_count == 1: return inst_uuid except Exception: LOG.warning("Get instance_uuid from cloud-init failed") try: resp = requests.get('http://169.254.169.254/openstack', timeout=constants.CURL_MD_TIMEOUT) metadata_api_versions = resp.text.split() metadata_api_versions.sort(reverse=True) except Exception as e: LOG.error("Get metadata apis failed. Error: %s", e) raise exceptions.FuxiException("Metadata API Not Found") for api_version in metadata_api_versions: metadata_url = ''.join(['http://169.254.169.254/openstack/', api_version, '/meta_data.json']) try: resp = requests.get(metadata_url, timeout=constants.CURL_MD_TIMEOUT) metadata = resp.json() if metadata.get('uuid', None): return metadata['uuid'] except Exception as e: LOG.warning("Get instance_uuid from metadata server" " %(md_url)s failed. Error: %(err)s", {'md_url': metadata_url, 'err': e}) continue raise exceptions.FuxiException("Instance UUID Not Found")
def _get_mountpoint(self, docker_volume_name): """Generate a mount point for volume. :param docker_volume_name: :rtype: str """ if not docker_volume_name: LOG.error(_LE("Volume name could not be None")) raise exceptions.FuxiException("Volume name could not be None") if self.volume_provider_type: return os.path.join(CONF.volume_dir, self.volume_provider_type, docker_volume_name) else: return os.path.join(CONF.volume_dir, docker_volume_name)
def delete(self, docker_volume_name): cinder_volume, state = self._get_docker_volume(docker_volume_name) LOG.info(_LI("Get docker volume {0} {1} with state " "{2}").format(docker_volume_name, cinder_volume, state)) if state == ATTACH_TO_THIS: link_path = self._get_connector().get_device_path(cinder_volume) if not link_path or not os.path.exists(link_path): msg = _LE( "Could not find device link path for volume {0} {1} " "in host").format(docker_volume_name, cinder_volume) LOG.error(msg) raise exceptions.FuxiException(msg) devpath = os.path.realpath(link_path) if not os.path.exists(devpath): msg = _LE("Could not find device path for volume {0} {1} in " "host").format(docker_volume_name, cinder_volume) LOG.error(msg) raise exceptions.FuxiException(msg) mounter = mount.Mounter() mps = mounter.get_mps_by_device(devpath) ref_count = len(mps) if ref_count > 0: mountpoint = self._get_mountpoint(docker_volume_name) if mountpoint in mps: mounter.unmount(mountpoint) self._clear_mountpoint(mountpoint) # If this volume is still mounted on other mount point, # then return. if ref_count > 1: return True else: return True # Detach device from this server. self._get_connector().disconnect_volume(cinder_volume) available_volume = self.opensdsclient.get(cinder_volume.id) # If this volume is not used by other server any more, # than delete it from Cinder. available_volume = APIDictWrapper(available_volume) if not available_volume.attachments: msg = _LW("No other servers still use this volume {0} " "{1} any more, so delete it from OpenSDS" "").format(docker_volume_name, cinder_volume) LOG.warning(msg) self._delete_volume(available_volume) return True elif state == NOT_ATTACH: self._delete_volume(cinder_volume) return True elif state == ATTACH_TO_OTHER: msg = _LW("Volume %s is still in use, could not delete it") LOG.warning(msg, cinder_volume) return True elif state == UNKNOWN: return False else: msg = _LE("Volume %(vol_name)s %(c_vol)s " "state %(state)s is invalid") LOG.error(msg, {'vol_name': docker_volume_name, 'c_vol': cinder_volume, 'state': state}) raise exceptions.NotMatchedState()
def connect_volume(self, volume, **connect_opts): bdm = blockdevice.BlockerDeviceManager() ori_devices = bdm.device_scan() # Do volume-attach try: server_id = connect_opts.get('server_id', None) if not server_id: server_id = utils.get_instance_uuid() LOG.info("Start to connect to volume %s", volume) nova_volume = self.novaclient.volumes.create_server_volume( server_id=server_id, volume_id=volume.id, device=None) volume_monitor = state_monitor.StateMonitor( self.cinderclient, nova_volume, 'in-use', ( 'available', 'attaching', )) attached_volume = volume_monitor.monitor_cinder_volume() except nova_exception.ClientException as ex: LOG.error( "Attaching volume %(vol)s to server %(s)s " "failed. Error: %(err)s", { 'vol': volume.id, 's': server_id, 'err': ex }) raise # Get all devices on host after do volume-attach, # and then find attached device. LOG.info("After connected to volume, scan the added " "block device on host") curr_devices = bdm.device_scan() start_time = time.time() delta_devices = list(set(curr_devices) - set(ori_devices)) while not delta_devices: time.sleep(consts.DEVICE_SCAN_TIME_DELAY) curr_devices = bdm.device_scan() delta_devices = list(set(curr_devices) - set(ori_devices)) if time.time() - start_time > consts.DEVICE_SCAN_TIMEOUT: msg = _("Could not detect added device with " "limited time") raise exceptions.FuxiException(msg) LOG.info("Get extra added block device %s", delta_devices) for device in delta_devices: if bdm.get_device_size(device) == volume.size: device = device.replace('/sys/block', '/dev') LOG.info( "Find attached device %(dev)s" " for volume %(at)s %(vol)s", { 'dev': device, 'at': attached_volume.name, 'vol': volume }) link_path = os.path.join(consts.VOLUME_LINK_DIR, volume.id) try: utils.execute('ln', '-s', device, link_path, run_as_root=True) except processutils.ProcessExecutionError as e: LOG.error( "Error happened when create link file for" " block device attached by Nova." " Error: %s", e) raise return {'path': link_path} LOG.warning("Could not find matched device") raise exceptions.NotFound("Not Found Matched Device")
def create(self, docker_volume_name, volume_opts): if not volume_opts: volume_opts = {} connector = self._get_connector() cinder_volume, state = self._get_docker_volume(docker_volume_name) LOG.info("Get docker volume %(d_v)s %(vol)s with state %(st)s", { 'd_v': docker_volume_name, 'vol': cinder_volume, 'st': state }) device_info = {} if state == ATTACH_TO_THIS: LOG.warning( "The volume %(d_v)s %(vol)s already exists " "and attached to this server", { 'd_v': docker_volume_name, 'vol': cinder_volume }) device_info = {'path': connector.get_device_path(cinder_volume)} elif state == NOT_ATTACH: LOG.warning( "The volume %(d_v)s %(vol)s is already exists " "but not attached", { 'd_v': docker_volume_name, 'vol': cinder_volume }) device_info = connector.connect_volume(cinder_volume) elif state == ATTACH_TO_OTHER: if cinder_volume.multiattach: fstype = volume_opts.get('fstype', cinder_conf.fstype) vol_fstype = cinder_volume.metadata.get( 'fstype', cinder_conf.fstype) if fstype != vol_fstype: LOG.error( ("Volume already exists with fstype: %{v_fs}s, but " "currently provided fstype is %{fs}s, not " "match"), { 'v_fs': vol_fstype, 'fs': fstype }) raise exceptions.FuxiException('FSType Not Match') device_info = connector.connect_volume(cinder_volume) else: msg = _("The volume {0} {1} is already attached to another " "server").format(docker_volume_name, cinder_volume) LOG.error(msg) raise exceptions.FuxiException(msg) elif state == UNKNOWN: if 'volume_id' in volume_opts: cinder_volume = self._create_from_existing_volume( docker_volume_name, volume_opts.pop('volume_id'), volume_opts) if self._check_attached_to_this(cinder_volume): device_info = { 'path': connector.get_device_path(cinder_volume) } else: device_info = connector.connect_volume(cinder_volume) else: cinder_volume = self._create_volume(docker_volume_name, volume_opts) device_info = connector.connect_volume(cinder_volume) return device_info