def initialize_connection(self, volume, connector): """Allow connection to connector and return connection info.""" resp = {} LOG.debug( "Initialize_connection: initiator - %(initiator)s host - " "%(host)s ip - %(ip)s", { 'initiator': connector['initiator'], 'host': connector['host'], 'ip': connector['ip'] }) iqn = self._get_iqn(connector) # Pick a random single target to give the connector since # there is no multipathing support tgt = random.choice(self.gateway_iscsi_ip_addresses) resp = self.common.vmem_mg.client.create_client( name=connector['host'], proto='iSCSI', iscsi_iqns=connector['initiator']) # Raise if we failed for any reason other than a 'client # already exists' error code if not resp['success'] and 'Error: 0x900100cd' not in resp['msg']: msg = _("Failed to create iscsi client") raise exception.ViolinBackendErr(message=msg) resp = self.common.vmem_mg.client.create_iscsi_target( name=iqn, client_name=connector['host'], ip=self.gateway_iscsi_ip_addresses, access_mode='ReadWrite') # Same here, raise for any failure other than a 'target # already exists' error code if not resp['success'] and 'Error: 0x09024309' not in resp['msg']: msg = (_("Failed to create iscsi target: %(msg)s") % { 'msg': resp['msg'] }) raise exception.ViolinBackendErr(message=msg) lun_id = self._export_lun(volume, iqn, connector) properties = {} properties['target_discovered'] = False properties['target_iqn'] = iqn properties['target_portal'] = '%s:%s' % (tgt, '3260') properties['target_lun'] = lun_id properties['volume_id'] = volume['id'] properties['access_mode'] = 'rw' LOG.debug("Return ISCSI data: %(properties)s.", {'properties': properties}) return {'driver_volume_type': 'iscsi', 'data': properties}
def _loop_func(): LOG.debug("Entering _wait_run_delete_lun_snapshot loop: " "vol=%(vol)s, snap_id=%(snap_id)s, oid=%(oid)s", {'vol': cinder_volume_id, 'oid': oid, 'snap_id': cinder_snapshot_id}) try: ans = self.vmem_mg.snapshot.delete_lun_snapshot( snapshot_object_id=oid) except Exception: msg = (_("Failed to delete snapshot " "%(snap)s of volume %(vol)s") % {'snap': cinder_snapshot_id, 'vol': cinder_volume_id}) raise exception.ViolinBackendErr(msg) if ans['success']: LOG.debug("Delete snapshot %(snap_id)s of %(vol)s: " "success", {'vol': cinder_volume_id, 'snap_id': cinder_snapshot_id}) raise loopingcall.LoopingCallDone(retvalue=True) else: LOG.warning("Delete snapshot %(snap)s of %(vol)s " "encountered temporary error: %(msg)s", {'snap': cinder_snapshot_id, 'vol': cinder_volume_id, 'msg': ans['msg']})
def _unexport_lun(self, volume, target, connector): """Removes the export configuration for the given volume. The equivalent CLI command is "no lun export container <container_name> name <lun_name>" Arguments: volume -- volume object provided by the Manager """ v = self.common.vmem_mg LOG.info(_LI("Unexporting lun %(vol)s host is %(host)s."), { 'vol': volume['id'], 'host': connector['host'] }) try: self.common._send_cmd(v.lun.unassign_lun_from_iscsi_target, "Unassign device successfully", volume['id'], target, True) except exception.ViolinBackendErrNotFound: LOG.info(_LI("Lun %s already unexported, continuing..."), volume['id']) except Exception: LOG.exception(_LE("LUN unexport failed!")) msg = _("LUN unexport failed") raise exception.ViolinBackendErr(message=msg)
def test_export_lun_fails_with_exception(self): lun_id = '1' response = {'status': False, 'msg': 'Generic error'} failure = exception.ViolinBackendErr self.driver.common.vmem_mg = self.setup_mock_concerto() self.driver.common._send_cmd_and_verify = mock.Mock( side_effect=exception.ViolinBackendErr(response['msg'])) self.driver._get_lun_id = mock.Mock(return_value=lun_id) self.assertRaises(failure, self.driver._export_lun, VOLUME, CONNECTOR)
def _create_lun(self, volume): """Creates a new lun. :param volume: volume object provided by the Manager """ spec_dict = {} selected_pool = {} size_mb = volume['size'] * units.Ki full_size_mb = size_mb LOG.debug("Creating LUN %(name)s, %(size)s MB.", {'name': volume['name'], 'size': size_mb}) spec_dict = self._process_extra_specs(volume) try: selected_pool = self._get_storage_pool( volume, size_mb, spec_dict['pool_type'], "create_lun") except exception.ViolinResourceNotFound: raise except Exception: msg = _('No suitable storage pool found') LOG.exception(msg) raise exception.ViolinBackendErr(message=msg) try: # Note: In the following create_lun command for setting up a dedup # or thin lun the size_mb parameter is ignored and 10% of the # full_size_mb specified is the size actually allocated to # the lun. full_size_mb is the size the lun is allowed to # grow. On the other hand, if it is a thick lun, the # full_size_mb is ignored and size_mb is the actual # allocated size of the lun. self._send_cmd(self.vmem_mg.lun.create_lun, "Create resource successfully.", volume['id'], spec_dict['size_mb'], selected_pool['dedup'], selected_pool['thin'], full_size_mb, storage_pool_id=selected_pool['storage_pool_id']) except exception.ViolinBackendErrExists: LOG.debug("Lun %s already exists, continuing.", volume['id']) except Exception: LOG.exception("Lun create for %s failed!", volume['id']) raise
def _validate_lun_type_for_copy(self, lun_type): """Make sure volume type is thick. :param lun_type: Cinder volume type :raises: VolumeBackendAPIException: if volume type is not thick, copying the lun is not possible. """ if lun_type != CONCERTO_LUN_TYPE_THICK: msg = _('Lun copy currently only supported for thick luns') LOG.error(msg) raise exception.ViolinBackendErr(message=msg)
def test_extend_lun_new_size_is_too_small(self): """Volume extend fails when new size would shrink the volume.""" new_volume_size = 0 response = {'code': 14036, 'message': 'Failure'} conf = { 'lun.resize_lun.return_value': response, } self.driver.vip = self.setup_mock_vshare(m_conf=conf) self.driver._send_cmd = mock.Mock( side_effect=exception.ViolinBackendErr(message='fail')) self.assertRaises(exception.ViolinBackendErr, self.driver._extend_lun, VOLUME, new_volume_size)
def _check_error_code(self, response): """Raise an exception when backend returns certain errors. Error codes returned from the backend have to be examined individually. Not all of them are fatal. For example, lun attach failing becase the client is already attached is not a fatal error. :param response: a response dict result from the vmemclient request """ if "Error: 0x9001003c" in response['msg']: # This error indicates a duplicate attempt to attach lun, # non-fatal error pass elif "Error: 0x9002002b" in response['msg']: # lun unexport failed - lun is not exported to any clients, # non-fatal error pass elif "Error: 0x09010023" in response['msg']: # lun delete failed - dependent snapshot copy in progress, # fatal error raise exception.ViolinBackendErr(message=response['msg']) elif "Error: 0x09010048" in response['msg']: # lun delete failed - dependent snapshots still exist, # fatal error raise exception.ViolinBackendErr(message=response['msg']) elif "Error: 0x90010022" in response['msg']: # lun create failed - lun with same name already exists, # fatal error raise exception.ViolinBackendErrExists() elif "Error: 0x90010089" in response['msg']: # lun export failed - lun is still being created as copy, # fatal error raise exception.ViolinBackendErr(message=response['msg']) else: # assume any other error is fatal raise exception.ViolinBackendErr(message=response['msg'])
def _get_storage_pool(self, volume, size_in_mb, pool_type, usage): # User-specified pool takes precedence over others pool = None typeid = volume['volume_type_id'] if typeid: # Extract the storage_pool name if one is specified pool = self._get_violin_extra_spec(volume, "storage_pool") #Select a storage pool selected_pool = self.vmem_mg.pool.select_storage_pool( size_in_mb, pool_type, pool, self.config.dedup_only_pools, self.config.dedup_capable_pools, self.config.pool_allocation_method, usage) if (selected_pool is None): # Backend has not provided a suitable storage pool msg = _("Backend does not have a suitable storage pool.") raise exception.ViolinBackendErr(message=msg) LOG.debug("Storage pool returned is %s" % selected_pool['storage_pool']) return selected_pool
def _fatal_error_code(self, response): """Raise an exception for certain errors in a XG response. Error codes are extracted from vdmd_mgmt.c. Arguments: response -- a response dict result from an XG request """ # known non-fatal response codes: # 1024: 'lun deletion in progress, try again later' # 14032: 'lc_err_lock_busy' if response['code'] == 14000: # lc_generic_error raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14002: # lc_err_assertion_failed raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14004: # lc_err_not_found raise exception.ViolinBackendErrNotFound() elif response['code'] == 14005: # lc_err_exists raise exception.ViolinBackendErrExists() elif response['code'] == 14008: # lc_err_unexpected_arg raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14014: # lc_err_io_error raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14016: # lc_err_io_closed raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14017: # lc_err_io_timeout raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14021: # lc_err_unexpected_case raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14025: # lc_err_no_fs_space raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14035: # lc_err_range raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14036: # lc_err_invalid_param raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 14121: # lc_err_cancelled_err raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 512: # Not enough free space in container (vdmd bug) raise exception.ViolinBackendErr(message=response['message']) elif response['code'] == 1 and 'LUN ID conflict' \ in response['message']: # lun id conflict while attempting to export raise exception.ViolinBackendErr(message=response['message'])
def _ensure_snapshot_resource_area(self, volume_id): """Make sure concerto snapshot resource area exists on volume. :param volume_id: Cinder volume ID corresponding to the backend LUN :raises: VolumeBackendAPIException: if cinder volume does not exist on backnd, or SRA could not be created. """ ctxt = context.get_admin_context() volume = api.volume_get(ctxt, volume_id) spec_dict = {} if not volume: msg = (_("Failed to ensure snapshot resource area, could not " "locate volume for id %s") % volume_id) raise exception.VolumeBackendAPIException(data=msg) if not self.vmem_mg.snapshot.lun_has_a_snapshot_resource( lun=volume_id): # Per Concerto documentation, the SRA size should be computed # as follows # Size-of-original-LUN Reserve for SRA # < 500MB 100% # 500MB to 2G 50% # >= 2G 20% # Note: cinder volume.size is in GB, vmemclient wants MB. lun_size_mb = volume['size'] * units.Ki if lun_size_mb < 500: snap_size_mb = lun_size_mb elif lun_size_mb < 2000: snap_size_mb = 0.5 * lun_size_mb else: snap_size_mb = 0.2 * lun_size_mb snap_size_mb = int(math.ceil(snap_size_mb)) spec_dict = self._process_extra_specs(volume) try: selected_pool = self._get_storage_pool( volume, snap_size_mb, spec_dict['pool_type'], None) LOG.debug("Creating SRA of %(ssmb)sMB for lun of %(lsmb)sMB " "on %(vol_id)s", {'ssmb': snap_size_mb, 'lsmb': lun_size_mb, 'vol_id': volume_id}) except exception.ViolinResourceNotFound: raise except Exception: msg = _('No suitable storage pool found') LOG.exception(msg) raise exception.ViolinBackendErr(message=msg) res = self.vmem_mg.snapshot.create_snapshot_resource( lun=volume_id, size=snap_size_mb, enable_notification=False, policy=CONCERTO_DEFAULT_SRA_POLICY, enable_expansion=CONCERTO_DEFAULT_SRA_ENABLE_EXPANSION, expansion_threshold=CONCERTO_DEFAULT_SRA_EXPANSION_THRESHOLD, expansion_increment=CONCERTO_DEFAULT_SRA_EXPANSION_INCREMENT, expansion_max_size=CONCERTO_DEFAULT_SRA_EXPANSION_MAX_SIZE, enable_shrink=CONCERTO_DEFAULT_SRA_ENABLE_SHRINK, storage_pool_id=selected_pool['storage_pool_id']) if (not res['success']): msg = (_("Failed to create snapshot resource area on " "volume %(vol)s: %(res)s.") % {'vol': volume_id, 'res': res['msg']}) raise exception.VolumeBackendAPIException(data=msg)
def _create_volume_from_snapshot(self, snapshot, volume): """Create a new cinder volume from a given snapshot of a lun This maps onto a Concerto 'copy snapshot to lun'. Concerto creates the lun and then copies the snapshot into it. :param snapshot: cinder snapshot object provided by the Manager :param volume: cinder volume to be created """ cinder_volume_id = volume['id'] cinder_snapshot_id = snapshot['id'] size_mb = volume['size'] * units.Ki result = None spec_dict = {} LOG.debug("Copying snapshot %(snap_id)s onto volume %(vol_id)s " "%(dpy_name)s", {'snap_id': cinder_snapshot_id, 'vol_id': cinder_volume_id, 'dpy_name': snapshot['display_name']}) source_lun_info = self.vmem_mg.lun.get_lun_info(snapshot['volume_id']) self._validate_lun_type_for_copy(source_lun_info['subType']) spec_dict = self._process_extra_specs(volume) try: selected_pool = self._get_storage_pool(volume, size_mb, spec_dict['pool_type'], "create_lun") except exception.ViolinResourceNotFound: raise except Exception: msg = _('No suitable storage pool found') LOG.exception(msg) raise exception.ViolinBackendErr(message=msg) try: result = self.vmem_mg.lun.copy_snapshot_to_new_lun( source_lun=snapshot['volume_id'], source_snapshot_comment=self._compress_snapshot_id( cinder_snapshot_id), destination=cinder_volume_id, storage_pool_id=selected_pool['storage_pool_id']) if not result['success']: self._check_error_code(result) except Exception: LOG.exception("Copy snapshot to volume for " "snapshot %(snap)s volume %(vol)s failed!", {'snap': cinder_snapshot_id, 'vol': cinder_volume_id}) raise # get the destination lun info and extract virtualdeviceid info = self.vmem_mg.lun.get_lun_info(object_id=result['object_id']) self._wait_for_lun_or_snap_copy( snapshot['volume_id'], dest_vdev_id=info['virtualDeviceID'])