| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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. |
| */ |
| package org.apache.cloudstack.storage.image; |
| |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.sql.PreparedStatement; |
| import java.sql.ResultSet; |
| import java.sql.SQLException; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Date; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.stream.Collectors; |
| |
| import javax.inject.Inject; |
| |
| import com.cloud.agent.api.to.NfsTO; |
| import com.cloud.agent.api.to.OVFInformationTO; |
| import com.cloud.storage.DataStoreRole; |
| import com.cloud.storage.Upload; |
| import org.apache.cloudstack.storage.image.deployasis.DeployAsIsHelper; |
| import org.apache.log4j.Logger; |
| |
| import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; |
| import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataObject; |
| import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; |
| import org.apache.cloudstack.engine.subsystem.api.storage.EndPoint; |
| import org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector; |
| import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo; |
| import org.apache.cloudstack.framework.async.AsyncCallbackDispatcher; |
| import org.apache.cloudstack.framework.async.AsyncCompletionCallback; |
| import org.apache.cloudstack.framework.async.AsyncRpcContext; |
| import org.apache.cloudstack.framework.config.dao.ConfigurationDao; |
| import org.apache.cloudstack.storage.command.CommandResult; |
| import org.apache.cloudstack.storage.command.CopyCommand; |
| import org.apache.cloudstack.storage.command.DeleteCommand; |
| import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; |
| import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; |
| import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreDao; |
| import org.apache.cloudstack.storage.datastore.db.VolumeDataStoreVO; |
| import org.apache.cloudstack.storage.endpoint.DefaultEndPointSelector; |
| |
| import com.cloud.agent.AgentManager; |
| import com.cloud.agent.api.Answer; |
| import com.cloud.agent.api.storage.CreateDatadiskTemplateCommand; |
| import com.cloud.agent.api.storage.DownloadAnswer; |
| import com.cloud.agent.api.storage.GetDatadisksAnswer; |
| import com.cloud.agent.api.storage.GetDatadisksCommand; |
| import com.cloud.agent.api.to.DataObjectType; |
| import com.cloud.agent.api.to.DataTO; |
| import com.cloud.agent.api.to.DatadiskTO; |
| import com.cloud.alert.AlertManager; |
| import com.cloud.configuration.Config; |
| import com.cloud.exception.AgentUnavailableException; |
| import com.cloud.exception.OperationTimedoutException; |
| import com.cloud.host.Host; |
| import com.cloud.host.dao.HostDao; |
| import com.cloud.secstorage.CommandExecLogDao; |
| import com.cloud.secstorage.CommandExecLogVO; |
| import com.cloud.storage.StorageManager; |
| import com.cloud.storage.VMTemplateStorageResourceAssoc; |
| import com.cloud.storage.VMTemplateVO; |
| import com.cloud.storage.VolumeVO; |
| import com.cloud.storage.dao.VMTemplateDao; |
| import com.cloud.storage.dao.VMTemplateZoneDao; |
| import com.cloud.storage.dao.VolumeDao; |
| import com.cloud.storage.download.DownloadMonitor; |
| import com.cloud.user.ResourceLimitService; |
| import com.cloud.user.dao.AccountDao; |
| import com.cloud.utils.NumbersUtil; |
| import com.cloud.utils.db.TransactionLegacy; |
| import com.cloud.utils.exception.CloudRuntimeException; |
| import com.cloud.utils.net.Proxy; |
| import com.cloud.vm.VirtualMachineManager; |
| import com.cloud.vm.dao.SecondaryStorageVmDao; |
| |
| public abstract class BaseImageStoreDriverImpl implements ImageStoreDriver { |
| private static final Logger LOGGER = Logger.getLogger(BaseImageStoreDriverImpl.class); |
| |
| @Inject |
| protected VMTemplateDao _templateDao; |
| @Inject |
| DownloadMonitor _downloadMonitor; |
| @Inject |
| VolumeDao volumeDao; |
| @Inject |
| VolumeDataStoreDao _volumeStoreDao; |
| @Inject |
| TemplateDataStoreDao _templateStoreDao; |
| @Inject |
| EndPointSelector _epSelector; |
| @Inject |
| ConfigurationDao configDao; |
| @Inject |
| VMTemplateZoneDao _vmTemplateZoneDao; |
| @Inject |
| AlertManager _alertMgr; |
| @Inject |
| DefaultEndPointSelector _defaultEpSelector; |
| @Inject |
| AccountDao _accountDao; |
| @Inject |
| ResourceLimitService _resourceLimitMgr; |
| @Inject |
| DeployAsIsHelper deployAsIsHelper; |
| @Inject |
| HostDao hostDao; |
| @Inject |
| CommandExecLogDao _cmdExecLogDao; |
| @Inject |
| StorageManager storageMgr; |
| @Inject |
| protected SecondaryStorageVmDao _secStorageVmDao; |
| @Inject |
| AgentManager agentMgr; |
| |
| protected String _proxy = null; |
| |
| protected Proxy getHttpProxy() { |
| if (_proxy == null) { |
| return null; |
| } |
| try { |
| URI uri = new URI(_proxy); |
| Proxy prx = new Proxy(uri); |
| return prx; |
| } catch (URISyntaxException e) { |
| return null; |
| } |
| } |
| |
| @Override |
| public Map<String, String> getCapabilities() { |
| return null; |
| } |
| |
| @Override |
| public DataTO getTO(DataObject data) { |
| return null; |
| } |
| |
| protected class CreateContext<T> extends AsyncRpcContext<T> { |
| final DataObject data; |
| |
| public CreateContext(AsyncCompletionCallback<T> callback, DataObject data) { |
| super(callback); |
| this.data = data; |
| } |
| } |
| |
| protected Long getMaxTemplateSizeInBytes() { |
| try { |
| return Long.parseLong(configDao.getValue("max.template.iso.size")) * 1024L * 1024L * 1024L; |
| } catch (NumberFormatException e) { |
| return null; |
| } |
| } |
| |
| @Override |
| public void createAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) { |
| CreateContext<CreateCmdResult> context = new CreateContext<CreateCmdResult>(callback, data); |
| AsyncCallbackDispatcher<BaseImageStoreDriverImpl, DownloadAnswer> caller = AsyncCallbackDispatcher.create(this); |
| caller.setContext(context); |
| if (data.getType() == DataObjectType.TEMPLATE) { |
| caller.setCallback(caller.getTarget().createTemplateAsyncCallback(null, null)); |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Downloading template to data store " + dataStore.getId()); |
| } |
| _downloadMonitor.downloadTemplateToStorage(data, caller); |
| } else if (data.getType() == DataObjectType.VOLUME) { |
| caller.setCallback(caller.getTarget().createVolumeAsyncCallback(null, null)); |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Downloading volume to data store " + dataStore.getId()); |
| } |
| _downloadMonitor.downloadVolumeToStorage(data, caller); |
| } |
| } |
| |
| protected Void createTemplateAsyncCallback(AsyncCallbackDispatcher<? extends BaseImageStoreDriverImpl, DownloadAnswer> callback, |
| CreateContext<CreateCmdResult> context) { |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Performing image store createTemplate async callback"); |
| } |
| DownloadAnswer answer = callback.getResult(); |
| DataObject obj = context.data; |
| DataStore store = obj.getDataStore(); |
| |
| VMTemplateVO template = _templateDao.findById(obj.getId()); |
| TemplateDataStoreVO tmpltStoreVO = _templateStoreDao.findByStoreTemplate(store.getId(), obj.getId()); |
| if (tmpltStoreVO != null) { |
| if (tmpltStoreVO.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) { |
| if (template.isDeployAsIs() && answer != null) { |
| OVFInformationTO ovfInformationTO = answer.getOvfInformationTO(); |
| boolean persistDeployAsIs = deployAsIsHelper.persistTemplateOVFInformationAndUpdateGuestOS(template.getId(), ovfInformationTO, tmpltStoreVO); |
| if (!persistDeployAsIs) { |
| LOGGER.info("Failed persisting deploy-as-is template details for template " + template.getName()); |
| return null; |
| } |
| } |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Template is already in DOWNLOADED state, ignore further incoming DownloadAnswer"); |
| } |
| return null; |
| } |
| LOGGER.info("Updating store ref entry for template " + template.getName()); |
| TemplateDataStoreVO updateBuilder = _templateStoreDao.createForUpdate(); |
| updateBuilder.setDownloadPercent(answer.getDownloadPct()); |
| updateBuilder.setDownloadState(answer.getDownloadStatus()); |
| updateBuilder.setLastUpdated(new Date()); |
| updateBuilder.setErrorString(answer.getErrorString()); |
| updateBuilder.setJobId(answer.getJobId()); |
| updateBuilder.setLocalDownloadPath(answer.getDownloadPath()); |
| updateBuilder.setInstallPath(answer.getInstallPath()); |
| updateBuilder.setSize(answer.getTemplateSize()); |
| updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize()); |
| _templateStoreDao.update(tmpltStoreVO.getId(), updateBuilder); |
| // update size in vm_template table |
| VMTemplateVO tmlptUpdater = _templateDao.createForUpdate(); |
| tmlptUpdater.setSize(answer.getTemplateSize()); |
| _templateDao.update(obj.getId(), tmlptUpdater); |
| } |
| |
| AsyncCompletionCallback<CreateCmdResult> caller = context.getParentCallback(); |
| |
| if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR || |
| answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.ABANDONED || answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.UNKNOWN) { |
| CreateCmdResult result = new CreateCmdResult(null, null); |
| result.setSuccess(false); |
| result.setResult(answer.getErrorString()); |
| caller.complete(result); |
| String msg = "Failed to register template: " + obj.getUuid() + " with error: " + answer.getErrorString(); |
| _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED, _vmTemplateZoneDao.listByTemplateId(obj.getId()).get(0).getZoneId(), null, msg, msg); |
| LOGGER.error(msg); |
| } else if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) { |
| if (answer.getCheckSum() != null) { |
| VMTemplateVO templateDaoBuilder = _templateDao.createForUpdate(); |
| templateDaoBuilder.setChecksum(answer.getCheckSum()); |
| _templateDao.update(obj.getId(), templateDaoBuilder); |
| } |
| |
| CreateCmdResult result = new CreateCmdResult(null, null); |
| caller.complete(result); |
| } |
| return null; |
| } |
| |
| protected Void |
| createVolumeAsyncCallback(AsyncCallbackDispatcher<? extends BaseImageStoreDriverImpl, DownloadAnswer> callback, CreateContext<CreateCmdResult> context) { |
| DownloadAnswer answer = callback.getResult(); |
| DataObject obj = context.data; |
| DataStore store = obj.getDataStore(); |
| |
| VolumeDataStoreVO volStoreVO = _volumeStoreDao.findByStoreVolume(store.getId(), obj.getId()); |
| if (volStoreVO != null) { |
| if (volStoreVO.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) { |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Volume is already in DOWNLOADED state, ignore further incoming DownloadAnswer"); |
| } |
| return null; |
| } |
| VolumeDataStoreVO updateBuilder = _volumeStoreDao.createForUpdate(); |
| updateBuilder.setDownloadPercent(answer.getDownloadPct()); |
| updateBuilder.setDownloadState(answer.getDownloadStatus()); |
| updateBuilder.setLastUpdated(new Date()); |
| updateBuilder.setErrorString(answer.getErrorString()); |
| updateBuilder.setJobId(answer.getJobId()); |
| updateBuilder.setLocalDownloadPath(answer.getDownloadPath()); |
| updateBuilder.setInstallPath(answer.getInstallPath()); |
| updateBuilder.setSize(answer.getTemplateSize()); |
| updateBuilder.setPhysicalSize(answer.getTemplatePhySicalSize()); |
| _volumeStoreDao.update(volStoreVO.getId(), updateBuilder); |
| // update size in volume table |
| VolumeVO volUpdater = volumeDao.createForUpdate(); |
| volUpdater.setSize(answer.getTemplateSize()); |
| volumeDao.update(obj.getId(), volUpdater); |
| } |
| |
| AsyncCompletionCallback<CreateCmdResult> caller = context.getParentCallback(); |
| |
| if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR || |
| answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.ABANDONED || answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.UNKNOWN) { |
| CreateCmdResult result = new CreateCmdResult(null, null); |
| result.setSuccess(false); |
| result.setResult(answer.getErrorString()); |
| caller.complete(result); |
| String msg = "Failed to upload volume: " + obj.getUuid() + " with error: " + answer.getErrorString(); |
| _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_UPLOAD_FAILED, |
| (volStoreVO == null ? -1L : volStoreVO.getZoneId()), null, msg, msg); |
| LOGGER.error(msg); |
| } else if (answer.getDownloadStatus() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED) { |
| CreateCmdResult result = new CreateCmdResult(null, null); |
| caller.complete(result); |
| } |
| return null; |
| } |
| |
| @Override |
| public void deleteAsync(DataStore dataStore, DataObject data, AsyncCompletionCallback<CommandResult> callback) { |
| CommandResult result = new CommandResult(); |
| try { |
| DeleteCommand cmd = new DeleteCommand(data.getTO()); |
| EndPoint ep = _epSelector.select(data); |
| Answer answer = null; |
| if (ep == null) { |
| String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; |
| LOGGER.error(errMsg); |
| answer = new Answer(cmd, false, errMsg); |
| } else { |
| answer = ep.sendMessage(cmd); |
| } |
| if (answer != null && !answer.getResult()) { |
| result.setResult(answer.getDetails()); |
| } |
| } catch (Exception ex) { |
| LOGGER.debug("Unable to destoy " + data.getType().toString() + ": " + data.getId(), ex); |
| result.setResult(ex.toString()); |
| } |
| callback.complete(result); |
| } |
| |
| @Override |
| public void copyAsync(DataObject srcdata, DataObject destData, AsyncCompletionCallback<CopyCommandResult> callback) { |
| if (!canCopy(srcdata, destData)) { |
| return; |
| } |
| |
| if ((srcdata.getType() == DataObjectType.TEMPLATE && destData.getType() == DataObjectType.TEMPLATE) || |
| (srcdata.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.SNAPSHOT) || |
| (srcdata.getType() == DataObjectType.VOLUME && destData.getType() == DataObjectType.VOLUME)) { |
| |
| int nMaxExecutionMinutes = NumbersUtil.parseInt(configDao.getValue(Config.SecStorageCmdExecutionTimeMax.key()), 30); |
| CopyCommand cmd = new CopyCommand(srcdata.getTO(), destData.getTO(), nMaxExecutionMinutes * 60 * 1000, VirtualMachineManager.ExecuteInSequence.value()); |
| Answer answer = null; |
| |
| // Select host endpoint such that the load is balanced out |
| List<EndPoint> eps = _epSelector.findAllEndpointsForScope(srcdata.getDataStore()); |
| if (eps == null || eps.isEmpty()) { |
| String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; |
| LOGGER.error(errMsg); |
| answer = new Answer(cmd, false, errMsg); |
| } else { |
| // select endpoint with least number of commands running on them |
| answer = sendToLeastBusyEndpoint(eps, cmd); |
| } |
| CopyCommandResult result = new CopyCommandResult("", answer); |
| callback.complete(result); |
| } |
| } |
| |
| @Override |
| public void copyAsync(DataObject srcData, DataObject destData, Host destHost, AsyncCompletionCallback<CopyCommandResult> callback) { |
| copyAsync(srcData, destData, callback); |
| } |
| |
| private Answer sendToLeastBusyEndpoint(List<EndPoint> eps, CopyCommand cmd) { |
| Answer answer = null; |
| EndPoint endPoint = null; |
| List<Long> epIds = ssvmWithLeastMigrateJobs(); |
| |
| if (epIds.isEmpty()) { |
| Collections.shuffle(eps); |
| endPoint = eps.get(0); |
| } else { |
| List<EndPoint> remainingEps = eps.stream().filter(ep -> !epIds.contains(ep.getId())).collect(Collectors.toList()); |
| if (!remainingEps.isEmpty()) { |
| Collections.shuffle(remainingEps); |
| endPoint = remainingEps.get(0); |
| } else { |
| endPoint = _defaultEpSelector.getEndPointFromHostId(epIds.get(0)); |
| } |
| } |
| CommandExecLogVO execLog = new CommandExecLogVO(endPoint.getId(), _secStorageVmDao.findByInstanceName(hostDao.findById(endPoint.getId()).getName()).getId(), "DataMigrationCommand", 1); |
| Long cmdExecId = _cmdExecLogDao.persist(execLog).getId(); |
| String errMsg = null; |
| try { |
| answer = agentMgr.send(endPoint.getId(), cmd); |
| answer.setContextParam("cmd", cmdExecId.toString()); |
| return answer; |
| } catch (AgentUnavailableException e) { |
| errMsg = e.toString(); |
| LOGGER.debug("Failed to send command, due to Agent:" + endPoint.getId() + ", " + e.toString()); |
| } catch (OperationTimedoutException e) { |
| errMsg = e.toString(); |
| LOGGER.debug("Failed to send command, due to Agent:" + endPoint.getId() + ", " + e.toString()); |
| } |
| throw new CloudRuntimeException("Failed to send command, due to Agent:" + endPoint.getId() + ", " + errMsg); |
| } |
| |
| @Override |
| public boolean canCopy(DataObject srcData, DataObject destData) { |
| DataStore srcStore = srcData.getDataStore(); |
| DataStore destStore = destData.getDataStore(); |
| if ((srcData.getDataStore().getTO() instanceof NfsTO && destData.getDataStore().getTO() instanceof NfsTO) && |
| (srcStore.getRole() == DataStoreRole.Image && destStore.getRole() == DataStoreRole.Image) && |
| ((srcData.getType() == DataObjectType.TEMPLATE && destData.getType() == DataObjectType.TEMPLATE) || |
| (srcData.getType() == DataObjectType.SNAPSHOT && destData.getType() == DataObjectType.SNAPSHOT) || |
| (srcData.getType() == DataObjectType.VOLUME && destData.getType() == DataObjectType.VOLUME))) { |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public void resize(DataObject data, AsyncCompletionCallback<CreateCmdResult> callback) { |
| } |
| |
| @Override |
| public void deleteEntityExtractUrl(DataStore store, String installPath, String url, Upload.Type entityType) { |
| } |
| |
| @Override |
| public List<DatadiskTO> getDataDiskTemplates(DataObject obj, String configurationId) { |
| List<DatadiskTO> dataDiskDetails = new ArrayList<DatadiskTO>(); |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Get the data disks present in the OVA template"); |
| } |
| DataStore store = obj.getDataStore(); |
| GetDatadisksCommand cmd = new GetDatadisksCommand(obj.getTO(), configurationId); |
| EndPoint ep = _defaultEpSelector.select(store); |
| Answer answer = null; |
| if (ep == null) { |
| String errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; |
| LOGGER.error(errMsg); |
| answer = new Answer(cmd, false, errMsg); |
| } else { |
| answer = ep.sendMessage(cmd); |
| } |
| if (answer != null && answer.getResult()) { |
| GetDatadisksAnswer getDatadisksAnswer = (GetDatadisksAnswer)answer; |
| dataDiskDetails = getDatadisksAnswer.getDataDiskDetails(); // Details - Disk path, virtual size |
| } |
| else { |
| throw new CloudRuntimeException("Get Data disk command failed " + answer.getDetails()); |
| } |
| return dataDiskDetails; |
| } |
| |
| @Override |
| public Void createDataDiskTemplateAsync(TemplateInfo dataDiskTemplate, String path, String diskId, boolean bootable, long fileSize, AsyncCompletionCallback<CreateCmdResult> callback) { |
| Answer answer = null; |
| String errMsg = null; |
| if (LOGGER.isDebugEnabled()) { |
| LOGGER.debug("Create Datadisk template: " + dataDiskTemplate.getId()); |
| } |
| CreateDatadiskTemplateCommand cmd = new CreateDatadiskTemplateCommand(dataDiskTemplate.getTO(), path, diskId, fileSize, bootable); |
| EndPoint ep = _defaultEpSelector.select(dataDiskTemplate.getDataStore()); |
| if (ep == null) { |
| errMsg = "No remote endpoint to send command, check if host or ssvm is down?"; |
| LOGGER.error(errMsg); |
| answer = new Answer(cmd, false, errMsg); |
| } else { |
| answer = ep.sendMessage(cmd); |
| } |
| if (answer != null && !answer.getResult()) { |
| errMsg = answer.getDetails(); |
| } |
| CreateCmdResult result = new CreateCmdResult(null, answer); |
| result.setResult(errMsg); |
| callback.complete(result); |
| return null; |
| } |
| |
| private Integer getCopyCmdsCountToSpecificSSVM(Long ssvmId) { |
| return _cmdExecLogDao.getCopyCmdCountForSSVM(ssvmId); |
| } |
| |
| private List<Long> ssvmWithLeastMigrateJobs() { |
| LOGGER.debug("Picking ssvm from the pool with least commands running on it"); |
| String query = "select host_id, count(*) from cmd_exec_log group by host_id order by 2;"; |
| TransactionLegacy txn = TransactionLegacy.currentTxn(); |
| |
| List<Long> result = new ArrayList<Long>(); |
| PreparedStatement pstmt = null; |
| try { |
| pstmt = txn.prepareAutoCloseStatement(query); |
| ResultSet rs = pstmt.executeQuery(); |
| while (rs.next()) { |
| result.add((long) rs.getInt(1)); |
| } |
| } catch (SQLException e) { |
| LOGGER.debug("SQLException caught", e); |
| } |
| return result; |
| } |
| } |