@Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException { try { List<? extends Network> networks = _niciraNvpElementService.listNiciraNvpDeviceNetworks(this); ListResponse<NetworkResponse> response = new ListResponse<NetworkResponse>(); List<NetworkResponse> networkResponses = new ArrayList<NetworkResponse>(); if (networks != null && !networks.isEmpty()) { for (Network network : networks) { NetworkResponse networkResponse = _responseGenerator.createNetworkResponse(network); networkResponses.add(networkResponse); } } response.setResponses(networkResponses); response.setResponseName(getCommandName()); this.setResponseObject(response); } catch (InvalidParameterValueException invalidParamExcp) { throw new ServerApiException(BaseCmd.PARAM_ERROR, invalidParamExcp.getMessage()); } catch (CloudRuntimeException runtimeExcp) { throw new ServerApiException(BaseCmd.INTERNAL_ERROR, runtimeExcp.getMessage()); } }
@Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException { try { List<CiscoVnmcControllerVO> ciscoVnmcResources = _ciscoVnmcElementService.listCiscoVnmcResources(this); ListResponse<CiscoVnmcResourceResponse> response = new ListResponse<CiscoVnmcResourceResponse>(); List<CiscoVnmcResourceResponse> ciscoVnmcResourcesResponse = new ArrayList<CiscoVnmcResourceResponse>(); if (ciscoVnmcResources != null && !ciscoVnmcResources.isEmpty()) { for (CiscoVnmcController ciscoVnmcResourceVO : ciscoVnmcResources) { CiscoVnmcResourceResponse ciscoVnmcResourceResponse = _ciscoVnmcElementService.createCiscoVnmcResourceResponse(ciscoVnmcResourceVO); ciscoVnmcResourceResponse.setObjectName("CiscoVnmcResource"); ciscoVnmcResourcesResponse.add(ciscoVnmcResourceResponse); } } response.setResponses(ciscoVnmcResourcesResponse); response.setResponseName(getCommandName()); this.setResponseObject(response); } catch (InvalidParameterValueException invalidParamExcp) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage()); } catch (CloudRuntimeException runtimeExcp) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage()); } }
@Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ResourceAllocationException { try { List<NiciraNvpDeviceVO> niciraDevices = niciraNvpElementService.listNiciraNvpDevices(this); ListResponse<NiciraNvpDeviceResponse> response = new ListResponse<NiciraNvpDeviceResponse>(); List<NiciraNvpDeviceResponse> niciraDevicesResponse = new ArrayList<NiciraNvpDeviceResponse>(); if (niciraDevices != null && !niciraDevices.isEmpty()) { for (NiciraNvpDeviceVO niciraDeviceVO : niciraDevices) { NiciraNvpDeviceResponse niciraDeviceResponse = niciraNvpElementService.createNiciraNvpDeviceResponse(niciraDeviceVO); niciraDevicesResponse.add(niciraDeviceResponse); } } response.setResponses(niciraDevicesResponse); response.setResponseName(getCommandName()); setResponseObject(response); } catch (InvalidParameterValueException invalidParamExcp) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage()); } catch (CloudRuntimeException runtimeExcp) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage()); } }
@Override public AsyncCallFuture<TemplateApiResult> copyTemplate( TemplateInfo srcTemplate, DataStore destStore) { // generate a URL from source template ssvm to download to destination data store String url = generateCopyUrl(srcTemplate); if (url == null) { s_logger.warn( "Unable to start/resume copy of template " + srcTemplate.getUniqueName() + " to " + destStore.getName() + ", no secondary storage vm in running state in source zone"); throw new CloudRuntimeException("No secondary VM in running state in source template zone "); } TemplateObject tmplForCopy = (TemplateObject) _templateFactory.getTemplate(srcTemplate, destStore); if (s_logger.isDebugEnabled()) { s_logger.debug("Setting source template url to " + url); } tmplForCopy.setUrl(url); if (s_logger.isDebugEnabled()) { s_logger.debug("Mark template_store_ref entry as Creating"); } AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>(); DataObject templateOnStore = destStore.create(tmplForCopy); templateOnStore.processEvent(Event.CreateOnlyRequested); if (s_logger.isDebugEnabled()) { s_logger.debug("Invoke datastore driver createAsync to create template on destination store"); } try { TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<TemplateApiResult>(null, (TemplateObject) templateOnStore, future); AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this); caller .setCallback(caller.getTarget().copyTemplateCrossZoneCallBack(null, null)) .setContext(context); destStore.getDriver().createAsync(destStore, templateOnStore, caller); } catch (CloudRuntimeException ex) { // clean up already persisted template_store_ref entry in case of createTemplateCallback is // never called TemplateDataStoreVO templateStoreVO = _vmTemplateStoreDao.findByStoreTemplate(destStore.getId(), srcTemplate.getId()); if (templateStoreVO != null) { TemplateInfo tmplObj = _templateFactory.getTemplate(srcTemplate, destStore); tmplObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); } TemplateApiResult res = new TemplateApiResult((TemplateObject) templateOnStore); res.setResult(ex.getMessage()); future.complete(res); } return future; }
@SuppressWarnings("deprecation") @Override public void execute() { try { Host externalFirewall = _srxElementService.addExternalFirewall(this); ExternalFirewallResponse response = _srxElementService.createExternalFirewallResponse(externalFirewall); response.setObjectName("externalfirewall"); response.setResponseName(getCommandName()); this.setResponseObject(response); } catch (InvalidParameterValueException ipve) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, ipve.getMessage()); } catch (CloudRuntimeException cre) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, cre.getMessage()); } }
@Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException { try { ExternalNetworkDeviceManager nwDeviceMgr; ComponentLocator locator = ComponentLocator.getLocator(ManagementService.Name); nwDeviceMgr = locator.getManager(ExternalNetworkDeviceManager.class); Host device = nwDeviceMgr.addNetworkDevice(this); NetworkDeviceResponse response = nwDeviceMgr.getApiResponse(device); response.setObjectName("networkdevice"); response.setResponseName(getCommandName()); this.setResponseObject(response); } catch (InvalidParameterValueException ipve) { throw new ServerApiException(BaseCmd.PARAM_ERROR, ipve.getMessage()); } catch (CloudRuntimeException cre) { throw new ServerApiException(BaseCmd.INTERNAL_ERROR, cre.getMessage()); } }
@Override public void createTemplateAsync( TemplateInfo template, DataStore store, AsyncCompletionCallback<TemplateApiResult> callback) { // persist template_store_ref entry TemplateObject templateOnStore = (TemplateObject) store.create(template); // update template_store_ref and template state try { templateOnStore.processEvent(ObjectInDataStoreStateMachine.Event.CreateOnlyRequested); } catch (Exception e) { TemplateApiResult result = new TemplateApiResult(templateOnStore); result.setResult(e.toString()); result.setSuccess(false); if (callback != null) { callback.complete(result); } return; } try { TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<TemplateApiResult>(callback, templateOnStore, null); AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this); caller.setCallback(caller.getTarget().createTemplateCallback(null, null)).setContext(context); store.getDriver().createAsync(store, templateOnStore, caller); } catch (CloudRuntimeException ex) { // clean up already persisted template_store_ref entry in case of createTemplateCallback is // never called TemplateDataStoreVO templateStoreVO = _vmTemplateStoreDao.findByStoreTemplate(store.getId(), template.getId()); if (templateStoreVO != null) { TemplateInfo tmplObj = _templateFactory.getTemplate(template, store); tmplObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); } TemplateApiResult result = new TemplateApiResult(template); result.setResult(ex.getMessage()); if (callback != null) { callback.complete(result); } } }
@Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException { try { boolean result = _ciscoVnmcElementService.deleteCiscoVnmcResource(this); if (result) { SuccessResponse response = new SuccessResponse(getCommandName()); response.setResponseName(getCommandName()); this.setResponseObject(response); } else { throw new ServerApiException( ApiErrorCode.INTERNAL_ERROR, "Failed to delete Cisco Vnmc resource."); } } catch (InvalidParameterValueException invalidParamExcp) { throw new ServerApiException(ApiErrorCode.PARAM_ERROR, invalidParamExcp.getMessage()); } catch (CloudRuntimeException runtimeExcp) { throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR, runtimeExcp.getMessage()); } }
@Override public void execute() throws ResourceUnavailableException, InsufficientCapacityException, ServerApiException, ConcurrentOperationException, ResourceAllocationException { try { ExternalFirewallDeviceVO fwDeviceVO = _srxFwService.addSrxFirewall(this); if (fwDeviceVO != null) { SrxFirewallResponse response = _srxFwService.createSrxFirewallResponse(fwDeviceVO); response.setObjectName("srxfirewall"); response.setResponseName(getCommandName()); this.setResponseObject(response); } else { throw new ServerApiException( BaseAsyncCmd.INTERNAL_ERROR, "Failed to add SRX firewall due to internal error."); } } catch (InvalidParameterValueException invalidParamExcp) { throw new ServerApiException(BaseCmd.PARAM_ERROR, invalidParamExcp.getMessage()); } catch (CloudRuntimeException runtimeExcp) { throw new ServerApiException(BaseCmd.INTERNAL_ERROR, runtimeExcp.getMessage()); } }
@Override protected void runInContext() { // 1. Select all entries with download_state = Not_Downloaded or Download_In_Progress // 2. Get corresponding volume // 3. Get EP using _epSelector // 4. Check if SSVM is owned by this MS // 5. If owned by MS then send command to appropriate SSVM // 6. In listener check for the answer and update DB accordingly List<VolumeDataStoreVO> volumeDataStores = _volumeDataStoreDao.listByVolumeState( Volume.State.NotUploaded, Volume.State.UploadInProgress); for (VolumeDataStoreVO volumeDataStore : volumeDataStores) { try { DataStore dataStore = storeMgr.getDataStore(volumeDataStore.getDataStoreId(), DataStoreRole.Image); EndPoint ep = _epSelector.select(dataStore, volumeDataStore.getExtractUrl()); if (ep == null) { s_logger.warn( "There is no secondary storage VM for image store " + dataStore.getName()); continue; } VolumeVO volume = _volumeDao.findById(volumeDataStore.getVolumeId()); if (volume == null) { s_logger.warn("Volume with id " + volumeDataStore.getVolumeId() + " not found"); continue; } Host host = _hostDao.findById(ep.getId()); UploadStatusCommand cmd = new UploadStatusCommand(volume.getUuid(), EntityType.Volume); if (host != null && host.getManagementServerId() != null) { if (_nodeId == host.getManagementServerId().longValue()) { Answer answer = null; try { answer = ep.sendMessage(cmd); } catch (CloudRuntimeException e) { s_logger.warn( "Unable to get upload status for volume " + volume.getUuid() + ". Error details: " + e.getMessage()); answer = new UploadStatusAnswer(cmd, UploadStatus.UNKNOWN, e.getMessage()); } if (answer == null || !(answer instanceof UploadStatusAnswer)) { s_logger.warn( "No or invalid answer corresponding to UploadStatusCommand for volume " + volumeDataStore.getVolumeId()); continue; } handleVolumeStatusResponse((UploadStatusAnswer) answer, volume, volumeDataStore); } } else { String error = "Volume " + volume.getUuid() + " failed to upload as SSVM is either destroyed or SSVM agent not in 'Up' state"; handleVolumeStatusResponse( new UploadStatusAnswer(cmd, UploadStatus.ERROR, error), volume, volumeDataStore); } } catch (Throwable th) { s_logger.warn( "Exception while checking status for uploaded volume " + volumeDataStore.getExtractUrl() + ". Error details: " + th.getMessage()); if (s_logger.isTraceEnabled()) { s_logger.trace("Exception details: ", th); } } } // Handle for template upload as well List<TemplateDataStoreVO> templateDataStores = _templateDataStoreDao.listByTemplateState( VirtualMachineTemplate.State.NotUploaded, VirtualMachineTemplate.State.UploadInProgress); for (TemplateDataStoreVO templateDataStore : templateDataStores) { try { DataStore dataStore = storeMgr.getDataStore(templateDataStore.getDataStoreId(), DataStoreRole.Image); EndPoint ep = _epSelector.select(dataStore, templateDataStore.getExtractUrl()); if (ep == null) { s_logger.warn( "There is no secondary storage VM for image store " + dataStore.getName()); continue; } VMTemplateVO template = _templateDao.findById(templateDataStore.getTemplateId()); if (template == null) { s_logger.warn("Template with id " + templateDataStore.getTemplateId() + " not found"); continue; } Host host = _hostDao.findById(ep.getId()); UploadStatusCommand cmd = new UploadStatusCommand(template.getUuid(), EntityType.Template); if (host != null && host.getManagementServerId() != null) { if (_nodeId == host.getManagementServerId().longValue()) { Answer answer = null; try { answer = ep.sendMessage(cmd); } catch (CloudRuntimeException e) { s_logger.warn( "Unable to get upload status for template " + template.getUuid() + ". Error details: " + e.getMessage()); answer = new UploadStatusAnswer(cmd, UploadStatus.UNKNOWN, e.getMessage()); } if (answer == null || !(answer instanceof UploadStatusAnswer)) { s_logger.warn( "No or invalid answer corresponding to UploadStatusCommand for template " + templateDataStore.getTemplateId()); continue; } handleTemplateStatusResponse( (UploadStatusAnswer) answer, template, templateDataStore); } } else { String error = "Template " + template.getUuid() + " failed to upload as SSVM is either destroyed or SSVM agent not in 'Up' state"; handleTemplateStatusResponse( new UploadStatusAnswer(cmd, UploadStatus.ERROR, error), template, templateDataStore); } } catch (Throwable th) { s_logger.warn( "Exception while checking status for uploaded template " + templateDataStore.getExtractUrl() + ". Error details: " + th.getMessage()); if (s_logger.isTraceEnabled()) { s_logger.trace("Exception details: ", th); } } } }
@Override public Answer execute( final BackupSnapshotCommand command, final LibvirtComputingResource libvirtComputingResource) { final Long dcId = command.getDataCenterId(); final Long accountId = command.getAccountId(); final Long volumeId = command.getVolumeId(); final String secondaryStoragePoolUrl = command.getSecondaryStorageUrl(); final String snapshotName = command.getSnapshotName(); String snapshotDestPath = null; String snapshotRelPath = null; final String vmName = command.getVmName(); KVMStoragePool secondaryStoragePool = null; final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr(); try { final LibvirtUtilitiesHelper libvirtUtilitiesHelper = libvirtComputingResource.getLibvirtUtilitiesHelper(); final Connect conn = libvirtUtilitiesHelper.getConnectionByVmName(vmName); secondaryStoragePool = storagePoolMgr.getStoragePoolByURI(secondaryStoragePoolUrl); final String ssPmountPath = secondaryStoragePool.getLocalPath(); snapshotRelPath = File.separator + "snapshots" + File.separator + dcId + File.separator + accountId + File.separator + volumeId; snapshotDestPath = ssPmountPath + File.separator + "snapshots" + File.separator + dcId + File.separator + accountId + File.separator + volumeId; final KVMStoragePool primaryPool = storagePoolMgr.getStoragePool( command.getPool().getType(), command.getPrimaryStoragePoolNameLabel()); final KVMPhysicalDisk snapshotDisk = primaryPool.getPhysicalDisk(command.getVolumePath()); final String manageSnapshotPath = libvirtComputingResource.manageSnapshotPath(); final int cmdsTimeout = libvirtComputingResource.getCmdsTimeout(); /** * RBD snapshots can't be copied using qemu-img, so we have to use the Java bindings for * librbd here. * * <p>These bindings will read the snapshot and write the contents to the secondary storage * directly * * <p>It will stop doing so if the amount of time spend is longer then cmds.timeout */ if (primaryPool.getType() == StoragePoolType.RBD) { try { final Rados r = new Rados(primaryPool.getAuthUserName()); r.confSet("mon_host", primaryPool.getSourceHost() + ":" + primaryPool.getSourcePort()); r.confSet("key", primaryPool.getAuthSecret()); r.confSet("client_mount_timeout", "30"); r.connect(); s_logger.debug("Succesfully connected to Ceph cluster at " + r.confGet("mon_host")); final IoCTX io = r.ioCtxCreate(primaryPool.getSourceDir()); final Rbd rbd = new Rbd(io); final RbdImage image = rbd.open(snapshotDisk.getName(), snapshotName); final File fh = new File(snapshotDestPath); try (BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fh)); ) { final int chunkSize = 4194304; long offset = 0; s_logger.debug( "Backuping up RBD snapshot " + snapshotName + " to " + snapshotDestPath); while (true) { final byte[] buf = new byte[chunkSize]; final int bytes = image.read(offset, buf, chunkSize); if (bytes <= 0) { break; } bos.write(buf, 0, bytes); offset += bytes; } s_logger.debug( "Completed backing up RBD snapshot " + snapshotName + " to " + snapshotDestPath + ". Bytes written: " + offset); } catch (final IOException ex) { s_logger.error("BackupSnapshotAnswer:Exception:" + ex.getMessage()); } r.ioCtxDestroy(io); } catch (final RadosException e) { s_logger.error("A RADOS operation failed. The error was: " + e.getMessage()); return new BackupSnapshotAnswer(command, false, e.toString(), null, true); } catch (final RbdException e) { s_logger.error( "A RBD operation on " + snapshotDisk.getName() + " failed. The error was: " + e.getMessage()); return new BackupSnapshotAnswer(command, false, e.toString(), null, true); } } else { final Script scriptCommand = new Script(manageSnapshotPath, cmdsTimeout, s_logger); scriptCommand.add("-b", snapshotDisk.getPath()); scriptCommand.add("-n", snapshotName); scriptCommand.add("-p", snapshotDestPath); scriptCommand.add("-t", snapshotName); final String result = scriptCommand.execute(); if (result != null) { s_logger.debug("Failed to backup snaptshot: " + result); return new BackupSnapshotAnswer(command, false, result, null, true); } } /* Delete the snapshot on primary */ DomainState state = null; Domain vm = null; if (vmName != null) { try { vm = libvirtComputingResource.getDomain(conn, command.getVmName()); state = vm.getInfo().state; } catch (final LibvirtException e) { s_logger.trace("Ignoring libvirt error.", e); } } final KVMStoragePool primaryStorage = storagePoolMgr.getStoragePool(command.getPool().getType(), command.getPool().getUuid()); if (state == DomainState.VIR_DOMAIN_RUNNING && !primaryStorage.isExternalSnapshot()) { final MessageFormat snapshotXML = new MessageFormat( " <domainsnapshot>" + " <name>{0}</name>" + " <domain>" + " <uuid>{1}</uuid>" + " </domain>" + " </domainsnapshot>"); final String vmUuid = vm.getUUIDString(); final Object[] args = new Object[] {snapshotName, vmUuid}; final String snapshot = snapshotXML.format(args); s_logger.debug(snapshot); final DomainSnapshot snap = vm.snapshotLookupByName(snapshotName); if (snap != null) { snap.delete(0); } else { throw new CloudRuntimeException("Unable to find vm snapshot with name -" + snapshotName); } /* * libvirt on RHEL6 doesn't handle resume event emitted from * qemu */ vm = libvirtComputingResource.getDomain(conn, command.getVmName()); state = vm.getInfo().state; if (state == DomainState.VIR_DOMAIN_PAUSED) { vm.resume(); } } else { final Script scriptCommand = new Script(manageSnapshotPath, cmdsTimeout, s_logger); scriptCommand.add("-d", snapshotDisk.getPath()); scriptCommand.add("-n", snapshotName); final String result = scriptCommand.execute(); if (result != null) { s_logger.debug("Failed to backup snapshot: " + result); return new BackupSnapshotAnswer( command, false, "Failed to backup snapshot: " + result, null, true); } } } catch (final LibvirtException e) { return new BackupSnapshotAnswer(command, false, e.toString(), null, true); } catch (final CloudRuntimeException e) { return new BackupSnapshotAnswer(command, false, e.toString(), null, true); } finally { if (secondaryStoragePool != null) { storagePoolMgr.deleteStoragePool( secondaryStoragePool.getType(), secondaryStoragePool.getUuid()); } } return new BackupSnapshotAnswer( command, true, null, snapshotRelPath + File.separator + snapshotName, true); }
@Override public AsyncCallFuture<TemplateApiResult> copyTemplate( TemplateInfo srcTemplate, DataStore destStore) { // for vmware template, we need to check if ova packing is needed, since template created from // snapshot does not have .ova file // we invoke createEntityExtractURL to trigger ova packing. Ideally, we can directly use // extractURL to pass to following createTemplate. // Need to understand what is the background to use two different urls for copy and extract. if (srcTemplate.getFormat() == ImageFormat.OVA) { ImageStoreEntity tmpltStore = (ImageStoreEntity) srcTemplate.getDataStore(); tmpltStore.createEntityExtractUrl( srcTemplate.getInstallPath(), srcTemplate.getFormat(), srcTemplate); } // generate a URL from source template ssvm to download to destination data store String url = generateCopyUrl(srcTemplate); if (url == null) { s_logger.warn( "Unable to start/resume copy of template " + srcTemplate.getUniqueName() + " to " + destStore.getName() + ", no secondary storage vm in running state in source zone"); throw new CloudRuntimeException("No secondary VM in running state in source template zone "); } TemplateObject tmplForCopy = (TemplateObject) _templateFactory.getTemplate(srcTemplate, destStore); if (s_logger.isDebugEnabled()) { s_logger.debug("Setting source template url to " + url); } tmplForCopy.setUrl(url); if (s_logger.isDebugEnabled()) { s_logger.debug("Mark template_store_ref entry as Creating"); } AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<TemplateApiResult>(); DataObject templateOnStore = destStore.create(tmplForCopy); templateOnStore.processEvent(Event.CreateOnlyRequested); if (s_logger.isDebugEnabled()) { s_logger.debug("Invoke datastore driver createAsync to create template on destination store"); } try { TemplateOpContext<TemplateApiResult> context = new TemplateOpContext<TemplateApiResult>(null, (TemplateObject) templateOnStore, future); AsyncCallbackDispatcher<TemplateServiceImpl, CreateCmdResult> caller = AsyncCallbackDispatcher.create(this); caller .setCallback(caller.getTarget().copyTemplateCrossZoneCallBack(null, null)) .setContext(context); destStore.getDriver().createAsync(destStore, templateOnStore, caller); } catch (CloudRuntimeException ex) { // clean up already persisted template_store_ref entry in case of createTemplateCallback is // never called TemplateDataStoreVO templateStoreVO = _vmTemplateStoreDao.findByStoreTemplate(destStore.getId(), srcTemplate.getId()); if (templateStoreVO != null) { TemplateInfo tmplObj = _templateFactory.getTemplate(srcTemplate, destStore); tmplObj.processEvent(ObjectInDataStoreStateMachine.Event.OperationFailed); } TemplateApiResult res = new TemplateApiResult((TemplateObject) templateOnStore); res.setResult(ex.getMessage()); future.complete(res); } return future; }