From 497c60d63d50373d032a6887bba6788eb7e17547 Mon Sep 17 00:00:00 2001 From: nit Date: Tue, 26 Oct 2010 18:33:50 +0530 Subject: [PATCH] Extending extract functionlity for volumes to allow download. The extraction can have two modes FTP_UPLOAD and HTTP_DOWNLOAD. In the former one the user would provide the ftp url where the entity needs to be uploaded and in the later the user would be provided a HTTP URL where from he/she can download the entity. This url would be exposed for a specific time limit and would not function after the time limit --- .../DeleteEntityDownloadURLCommand.java | 15 +- core/src/com/cloud/event/EventTypes.java | 1 + .../storage/template/UploadManagerImpl.java | 18 ++- .../cloud/api/commands/ExtractVolumeCmd.java | 40 +++-- .../cloud/api/response/ExtractResponse.java | 18 ++- .../executor/ExtractJobResultObject.java | 0 .../com/cloud/server/ManagementServer.java | 3 +- .../cloud/server/ManagementServerImpl.java | 148 ++++++++++++------ .../cloud/storage/upload/UploadMonitor.java | 4 + .../storage/upload/UploadMonitorImpl.java | 57 ++++++- .../cloud/template/TemplateManagerImpl.java | 4 +- 11 files changed, 235 insertions(+), 73 deletions(-) mode change 100644 => 100755 core/src/com/cloud/agent/api/storage/DeleteEntityDownloadURLCommand.java mode change 100644 => 100755 server/src/com/cloud/async/executor/ExtractJobResultObject.java mode change 100644 => 100755 server/src/com/cloud/server/ManagementServer.java diff --git a/core/src/com/cloud/agent/api/storage/DeleteEntityDownloadURLCommand.java b/core/src/com/cloud/agent/api/storage/DeleteEntityDownloadURLCommand.java old mode 100644 new mode 100755 index 19f916278c7..cd4007e829c --- a/core/src/com/cloud/agent/api/storage/DeleteEntityDownloadURLCommand.java +++ b/core/src/com/cloud/agent/api/storage/DeleteEntityDownloadURLCommand.java @@ -1,12 +1,16 @@ package com.cloud.agent.api.storage; +import com.cloud.storage.Upload; + public class DeleteEntityDownloadURLCommand extends AbstractDownloadCommand { String path; + Upload.Type type; - public DeleteEntityDownloadURLCommand(String path) { + public DeleteEntityDownloadURLCommand(String path, Upload.Type type) { super(); this.path = path; + this.type = type; } public DeleteEntityDownloadURLCommand() { @@ -20,6 +24,13 @@ public class DeleteEntityDownloadURLCommand extends AbstractDownloadCommand { public void setPath(String path) { this.path = path; } - + + public Upload.Type getType() { + return type; + } + + public void setType(Upload.Type type) { + this.type = type; + } } diff --git a/core/src/com/cloud/event/EventTypes.java b/core/src/com/cloud/event/EventTypes.java index 714026b623b..5a4e0b797b7 100755 --- a/core/src/com/cloud/event/EventTypes.java +++ b/core/src/com/cloud/event/EventTypes.java @@ -96,6 +96,7 @@ public class EventTypes { public static final String EVENT_VOLUME_DELETE = "VOLUME.DELETE"; public static final String EVENT_VOLUME_ATTACH = "VOLUME.ATTACH"; public static final String EVENT_VOLUME_DETACH = "VOLUME.DETACH"; + public static final String EVENT_VOLUME_EXTRACT = "VOLUME.EXTRACT"; public static final String EVENT_VOLUME_UPLOAD = "VOLUME.UPLOAD"; // Domains diff --git a/core/src/com/cloud/storage/template/UploadManagerImpl.java b/core/src/com/cloud/storage/template/UploadManagerImpl.java index 87b51c8577a..ad3ea86ecb1 100755 --- a/core/src/com/cloud/storage/template/UploadManagerImpl.java +++ b/core/src/com/cloud/storage/template/UploadManagerImpl.java @@ -26,6 +26,7 @@ import com.cloud.agent.api.storage.UploadProgressCommand; import com.cloud.agent.api.storage.UploadCommand; import com.cloud.storage.StorageLayer; import com.cloud.storage.StorageResource; +import com.cloud.storage.Upload; import com.cloud.storage.UploadVO; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.template.TemplateUploader.UploadCompleteCallback; @@ -338,7 +339,7 @@ public class UploadManagerImpl implements UploadManager { return new CreateEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE); } - // Create a symbolic link from the actual directory to the template location + // Create a symbolic link from the actual directory to the template location. The entity would be directly visible under /var/www/html/ cmd.getInstallPath(); command = new Script("/bin/bash", s_logger); command.add("-c"); @@ -357,6 +358,7 @@ public class UploadManagerImpl implements UploadManager { @Override public DeleteEntityDownloadURLAnswer handleDeleteEntityDownloadURLCommand(DeleteEntityDownloadURLCommand cmd){ + //Delete the soft link s_logger.debug("handleDeleteEntityDownloadURLCommand "+cmd.getPath()); Script command = new Script("/bin/bash", s_logger); command.add("-c"); @@ -367,6 +369,20 @@ public class UploadManagerImpl implements UploadManager { s_logger.warn(errorString); return new DeleteEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE); } + + // If its a volume also delete the Hard link + if(cmd.getType() == Upload.Type.VOLUME){ + command = new Script("/bin/bash", s_logger); + command.add("-c"); + command.add("rm -f " + publicTemplateRepo + cmd.getPath()); + result = command.execute(); + if (result != null) { + String errorString = "Error in linking err=" + result; + s_logger.warn(errorString); + return new DeleteEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE); + } + } + return new DeleteEntityDownloadURLAnswer("", CreateEntityDownloadURLAnswer.RESULT_SUCCESS); } diff --git a/server/src/com/cloud/api/commands/ExtractVolumeCmd.java b/server/src/com/cloud/api/commands/ExtractVolumeCmd.java index c9b6ee2b159..b849f6415dd 100755 --- a/server/src/com/cloud/api/commands/ExtractVolumeCmd.java +++ b/server/src/com/cloud/api/commands/ExtractVolumeCmd.java @@ -23,8 +23,10 @@ import com.cloud.api.ApiDBUtils; import com.cloud.api.BaseAsyncCmd; import com.cloud.api.Implementation; import com.cloud.api.Parameter; +import com.cloud.api.BaseCmd.CommandType; import com.cloud.api.response.ExtractResponse; import com.cloud.event.EventTypes; +import com.cloud.storage.UploadVO; import com.cloud.storage.VolumeVO; import com.cloud.user.Account; @@ -39,16 +41,19 @@ public class ExtractVolumeCmd extends BaseAsyncCmd { ///////////////////////////////////////////////////// //FIXME - add description - @Parameter(name="id", type=CommandType.LONG, required=true) + @Parameter(name="id", type=CommandType.LONG, required=true, description="the ID of the volume") private Long id; //FIXME - add description - @Parameter(name="url", type=CommandType.STRING, required=true) + @Parameter(name="url", type=CommandType.STRING, required=false, description="the url to which the volume would be extracted") private String url; //FIXME - add description - @Parameter(name="zoneid", type=CommandType.LONG, required=true) + @Parameter(name="zoneid", type=CommandType.LONG, required=true, description="the ID of the zone where the volume is located") private Long zoneId; + + @Parameter(name="mode", type=CommandType.STRING, required=true, description="the mode of extraction - HTTP_DOWNLOAD or FTP_UPLOAD") + private String mode; ///////////////////////////////////////////////////// /////////////////// Accessors /////////////////////// @@ -65,12 +70,16 @@ public class ExtractVolumeCmd extends BaseAsyncCmd { public Long getZoneId() { return zoneId; } + + public String getMode() { + return mode; + } ///////////////////////////////////////////////////// /////////////// API Implementation/////////////////// ///////////////////////////////////////////////////// - @Override + @Override public String getName() { return s_name; } @@ -79,7 +88,7 @@ public class ExtractVolumeCmd extends BaseAsyncCmd { public long getAccountId() { VolumeVO volume = ApiDBUtils.findVolumeById(getId()); if (volume != null) { - return volume.getId(); + return volume.getAccountId(); } // invalid id, parent this command to SYSTEM so ERROR events are tracked @@ -88,7 +97,7 @@ public class ExtractVolumeCmd extends BaseAsyncCmd { @Override public String getEventType() { - return EventTypes.EVENT_VOLUME_UPLOAD; + return EventTypes.EVENT_VOLUME_EXTRACT; } @Override @@ -98,9 +107,22 @@ public class ExtractVolumeCmd extends BaseAsyncCmd { @Override @SuppressWarnings("unchecked") public ExtractResponse getResponse() { - ExtractResponse response = (ExtractResponse)getResponseObject(); - response.setResponseName(getName()); - return response; + Long uploadId = (Long)getResponseObject(); + UploadVO uploadInfo = ApiDBUtils.findUploadById(uploadId); + + ExtractResponse response = new ExtractResponse(); + response.setResponseName(getName()); + response.setId(id); + response.setName(ApiDBUtils.findVolumeById(id).getName()); + response.setZoneId(zoneId); + response.setZoneName(ApiDBUtils.findZoneById(zoneId).getName()); + response.setMode(mode); + response.setUploadId(uploadId); + response.setState(uploadInfo.getUploadState().toString()); + response.setAccountId(getAccountId()); + //FIX ME - Need to set the url once the gson jar is upgraded since it is throwing an error right now. + //response.setUrl(uploadInfo.getUploadUrl()); + return response; } public static String getStaticName() { diff --git a/server/src/com/cloud/api/response/ExtractResponse.java b/server/src/com/cloud/api/response/ExtractResponse.java index d694931036c..5ab5e7b48ef 100755 --- a/server/src/com/cloud/api/response/ExtractResponse.java +++ b/server/src/com/cloud/api/response/ExtractResponse.java @@ -29,13 +29,13 @@ public class ExtractResponse extends BaseResponse { @SerializedName("name") @Param(description="the name of the extracted object") private String name; - @SerializedName("uploadId") @Param(description="the upload id of extracted object") + @SerializedName("extractId") @Param(description="the upload id of extracted object") private Long uploadId; @SerializedName("uploadpercentage") @Param(description="the percentage of the entity uploaded to the specified location") private Integer uploadPercent; - @SerializedName("status") @Param(description="the status of the ") + @SerializedName("status") @Param(description="the status of the extraction") private String status; @SerializedName("accountid") @Param(description="the account id to which the extracted object belongs") @@ -68,8 +68,20 @@ public class ExtractResponse extends BaseResponse { private String mode; @SerializedName("url") @Param(description="if mode = upload then url of the uploaded entity. if mode = download the url from which the entity can be downloaded") - private String url; + private String url; + public ExtractResponse(){ + } + + public ExtractResponse(Long volumeId, String volName, long accountId, + String state, Long uploadId) { + this.id = volumeId; + this.name = volName; + this.accountId = accountId; + this.state = state; + this.uploadId = uploadId; + } + public Long getId() { return id; } diff --git a/server/src/com/cloud/async/executor/ExtractJobResultObject.java b/server/src/com/cloud/async/executor/ExtractJobResultObject.java old mode 100644 new mode 100755 diff --git a/server/src/com/cloud/server/ManagementServer.java b/server/src/com/cloud/server/ManagementServer.java old mode 100644 new mode 100755 index e6d189d26c1..e620d53ef25 --- a/server/src/com/cloud/server/ManagementServer.java +++ b/server/src/com/cloud/server/ManagementServer.java @@ -1207,9 +1207,10 @@ public interface ManagementServer { * @param cmd the command specifying url (where the volume needs to be extracted to), zoneId (zone where the volume exists), id (the id of the volume) * @throws URISyntaxException * @throws InternalErrorException + * @throws PermissionDeniedException * */ - void extractVolume(ExtractVolumeCmd cmd) throws URISyntaxException, InternalErrorException; + Long extractVolume(ExtractVolumeCmd cmd) throws URISyntaxException, InternalErrorException, PermissionDeniedException; /** * return an array of available hypervisors diff --git a/server/src/com/cloud/server/ManagementServerImpl.java b/server/src/com/cloud/server/ManagementServerImpl.java index 0fe1e29d75a..82e129a5d3a 100755 --- a/server/src/com/cloud/server/ManagementServerImpl.java +++ b/server/src/com/cloud/server/ManagementServerImpl.java @@ -137,6 +137,7 @@ import com.cloud.api.commands.UpdateTemplatePermissionsCmd; import com.cloud.api.commands.UpdateUserCmd; import com.cloud.api.commands.UpdateVMGroupCmd; import com.cloud.api.commands.UploadCustomCertificateCmd; +import com.cloud.api.response.ExtractResponse; import com.cloud.async.AsyncInstanceCreateStatus; import com.cloud.async.AsyncJobExecutor; import com.cloud.async.AsyncJobManager; @@ -233,6 +234,7 @@ import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePoolHostVO; import com.cloud.storage.StoragePoolVO; import com.cloud.storage.StorageStats; +import com.cloud.storage.Upload.Mode; import com.cloud.storage.Upload.Type; import com.cloud.storage.UploadVO; import com.cloud.storage.VMTemplateVO; @@ -6675,41 +6677,66 @@ public class ManagementServerImpl implements ManagementServer { } @Override - public void extractVolume(ExtractVolumeCmd cmd) throws URISyntaxException, InternalErrorException { + public Long extractVolume(ExtractVolumeCmd cmd) throws URISyntaxException, InternalErrorException, PermissionDeniedException { Long volumeId = cmd.getId(); String url = cmd.getUrl(); Long zoneId = cmd.getZoneId(); AsyncJobVO job = cmd.getJob(); - + String mode = cmd.getMode(); + Account account = UserContext.current().getAccount(); + VolumeVO volume = _volumeDao.findById(volumeId); if (volume == null) { - throw new ServerApiException(BaseCmd.INTERNAL_ERROR, "Unable to find volume with id " + volumeId); + throw new ServerApiException(BaseCmd.PARAM_ERROR, "Unable to find volume with id " + volumeId); } - URI uri = new URI(url); - if ( (uri.getScheme() == null) || (!uri.getScheme().equalsIgnoreCase("ftp") )) { - throw new IllegalArgumentException("Unsupported scheme for url: " + url); - } - - String host = uri.getHost(); - try { - InetAddress hostAddr = InetAddress.getByName(host); - if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress() ) { - throw new IllegalArgumentException("Illegal host specified in url"); - } - if (hostAddr instanceof Inet6Address) { - throw new IllegalArgumentException("IPV6 addresses not supported (" + hostAddr.getHostAddress() + ")"); - } - } catch (UnknownHostException uhe) { - throw new IllegalArgumentException("Unable to resolve " + host); - } - if (_dcDao.findById(zoneId) == null) { - throw new IllegalArgumentException("Please specify a valid zone."); + throw new ServerApiException(BaseCmd.PARAM_ERROR, "Please specify a valid zone."); + } + + Upload.Mode extractMode; + if( mode == null || (!mode.equals(Upload.Mode.FTP_UPLOAD.toString()) && !mode.equals(Upload.Mode.HTTP_DOWNLOAD.toString())) ){ + throw new ServerApiException(BaseCmd.PARAM_ERROR, "Please specify a valid extract Mode "); + }else{ + extractMode = mode.equals(Upload.Mode.FTP_UPLOAD.toString()) ? Upload.Mode.FTP_UPLOAD : Upload.Mode.HTTP_DOWNLOAD; } - if ( _uploadMonitor.isTypeUploadInProgress(volumeId, Type.VOLUME) ){ - throw new IllegalArgumentException(volume.getName() + " upload is in progress. Please wait for some time to schedule another upload for the same"); + if (account != null) { + if(!isAdmin(account.getType())){ + if (volume.getAccountId() != account.getId()){ + throw new PermissionDeniedException("Unable to find volume with ID: " + volumeId + " for account: " + account.getAccountName()); + } + } else { + Account userAccount = _accountDao.findById(volume.getAccountId()); + if((userAccount == null) || !_domainDao.isChildDomain(account.getDomainId(), userAccount.getDomainId())) { + throw new PermissionDeniedException("Unable to extract volume:" + volumeId + " - permission denied."); + } + } + } + + // If mode is upload perform extra checks on url and also see if there is an ongoing upload on the same. + if (extractMode == Upload.Mode.FTP_UPLOAD){ + URI uri = new URI(url); + if ( (uri.getScheme() == null) || (!uri.getScheme().equalsIgnoreCase("ftp") )) { + throw new IllegalArgumentException("Unsupported scheme for url: " + url); + } + + String host = uri.getHost(); + try { + InetAddress hostAddr = InetAddress.getByName(host); + if (hostAddr.isAnyLocalAddress() || hostAddr.isLinkLocalAddress() || hostAddr.isLoopbackAddress() || hostAddr.isMulticastAddress() ) { + throw new IllegalArgumentException("Illegal host specified in url"); + } + if (hostAddr instanceof Inet6Address) { + throw new IllegalArgumentException("IPV6 addresses not supported (" + hostAddr.getHostAddress() + ")"); + } + } catch (UnknownHostException uhe) { + throw new IllegalArgumentException("Unable to resolve " + host); + } + + if ( _uploadMonitor.isTypeUploadInProgress(volumeId, Type.VOLUME) ){ + throw new IllegalArgumentException(volume.getName() + " upload is in progress. Please wait for some time to schedule another upload for the same"); + } } long userId = UserContext.current().getUserId(); @@ -6721,42 +6748,59 @@ public class ManagementServerImpl implements ManagementServer { List storageServers = _hostDao.listByTypeDataCenter(Host.Type.SecondaryStorage, zoneId); HostVO sserver = storageServers.get(0); - EventUtils.saveStartedEvent(1L, volume.getAccountId(), EventTypes.EVENT_VOLUME_UPLOAD, "Starting upload of " +volume.getName()+ " to " +url, cmd.getStartEventId()); - UploadVO uploadJob = _uploadMonitor.createNewUploadEntry(sserver.getId(), volumeId, UploadVO.Status.COPY_IN_PROGRESS, 0, Type.VOLUME, null, null, url); - uploadJob = _uploadDao.createForUpdate(uploadJob.getId()); + EventUtils.saveStartedEvent(userId, accountId, EventTypes.EVENT_VOLUME_UPLOAD, "Starting extraction of " +volume.getName()+ " mode:"+mode, cmd.getStartEventId()); + List extractURLList = _uploadDao.listByTypeUploadStatus(volumeId, Upload.Type.VOLUME, UploadVO.Status.DOWNLOAD_URL_CREATED); - // Update the async Job - ExtractJobResultObject resultObj = new ExtractJobResultObject(volume.getAccountId(), volume.getName(), UploadVO.Status.COPY_IN_PROGRESS.toString(), 0, uploadJob.getId()); - _asyncMgr.updateAsyncJobAttachment(job.getId(), Type.VOLUME.toString(), volumeId); - _asyncMgr.updateAsyncJobStatus(job.getId(), AsyncJobResult.STATUS_IN_PROGRESS, resultObj); + if (extractMode == Upload.Mode.HTTP_DOWNLOAD && extractURLList.size() > 0){ + return extractURLList.get(0).getId(); // If download url already exists then return + }else { + UploadVO uploadJob = _uploadMonitor.createNewUploadEntry(sserver.getId(), volumeId, UploadVO.Status.COPY_IN_PROGRESS, 0, Type.VOLUME, null, null, url); + uploadJob = _uploadDao.createForUpdate(uploadJob.getId()); + + // Update the async Job + ExtractResponse resultObj = new ExtractResponse(volumeId, volume.getName(), accountId, UploadVO.Status.COPY_IN_PROGRESS.toString(), uploadJob.getId()); + _asyncMgr.updateAsyncJobAttachment(job.getId(), Type.VOLUME.toString(), volumeId); + _asyncMgr.updateAsyncJobStatus(job.getId(), AsyncJobResult.STATUS_IN_PROGRESS, resultObj); + + // Copy the volume from the source storage pool to secondary storage + CopyVolumeCommand cvCmd = new CopyVolumeCommand(volume.getId(), volume.getPath(), srcPool, secondaryStorageURL, true); + CopyVolumeAnswer cvAnswer = (CopyVolumeAnswer) _agentMgr.easySend(sourceHostId, cvCmd); - // Copy the volume from the source storage pool to secondary storage - CopyVolumeCommand cvCmd = new CopyVolumeCommand(volume.getId(), volume.getPath(), srcPool, secondaryStorageURL, true); - CopyVolumeAnswer cvAnswer = (CopyVolumeAnswer) _agentMgr.easySend(sourceHostId, cvCmd); + // Check if you got a valid answer. + if (cvAnswer == null || !cvAnswer.getResult()) { + String errorString = "Failed to copy the volume from the source primary storage pool to secondary storage."; + + //Update the async job. + resultObj.setResultString(errorString); + resultObj.setUploadStatus(UploadVO.Status.COPY_ERROR.toString()); + _asyncMgr.completeAsyncJob(job.getId(), AsyncJobResult.STATUS_FAILED, 0, resultObj); + + //Update the DB that volume couldn't be copied + uploadJob.setUploadState(UploadVO.Status.COPY_ERROR); + uploadJob.setErrorString(errorString); + uploadJob.setLastUpdated(new Date()); + _uploadDao.update(uploadJob.getId(), uploadJob); - if (cvAnswer == null || !cvAnswer.getResult()) { + EventUtils.saveEvent(userId, accountId, EventTypes.EVENT_VOLUME_UPLOAD, errorString); + throw new InternalErrorException(errorString); + } - String errorString = "Failed to copy the volume from the source primary storage pool to secondary storage."; - - resultObj.setResult_string(errorString); - resultObj.setUploadStatus(UploadVO.Status.COPY_ERROR.toString()); - _asyncMgr.completeAsyncJob(job.getId(), AsyncJobResult.STATUS_FAILED, 0, resultObj); - - uploadJob.setUploadState(UploadVO.Status.COPY_ERROR); - uploadJob.setErrorString(errorString); + String volumeLocalPath = "volumes/"+volume.getId()+"/"+cvAnswer.getVolumePath()+".vhd"; + //Update the DB that volume is copied + uploadJob.setUploadState(UploadVO.Status.COPY_COMPLETE); uploadJob.setLastUpdated(new Date()); _uploadDao.update(uploadJob.getId(), uploadJob); - EventUtils.saveEvent(1L, volume.getAccountId(), EventTypes.EVENT_VOLUME_UPLOAD, errorString); - - throw new InternalErrorException(errorString); + if (extractMode == Mode.FTP_UPLOAD){ // Now that the volume is copied perform the actual uploading + _uploadMonitor.extractVolume(uploadJob, sserver, volume, url, zoneId, volumeLocalPath, cmd.getStartEventId(), job.getId(), _asyncMgr); + return uploadJob.getId(); + }else{ // Volume is copied now make it visible under apache and create a URL. + s_logger.debug("volumepath " +volumeLocalPath); + _uploadMonitor.createVolumeDownloadURL(volumeId, volumeLocalPath, Type.VOLUME, zoneId, uploadJob.getId()); + EventUtils.saveEvent(userId, accountId, EventVO.LEVEL_INFO, cmd.getEventType(), "Completed extraction of "+volume.getName()+ " in mode:" +mode, null, cmd.getStartEventId()); + return uploadJob.getId(); + } } - String volumeLocalPath = "volumes/"+volume.getId()+"/"+cvAnswer.getVolumePath()+".vhd"; - uploadJob.setUploadState(UploadVO.Status.COPY_COMPLETE); - uploadJob.setLastUpdated(new Date()); - _uploadDao.update(uploadJob.getId(), uploadJob); - - _uploadMonitor.extractVolume(uploadJob, sserver, volume, url, zoneId, volumeLocalPath, cmd.getStartEventId(), job.getId(), _asyncMgr); } @Override diff --git a/server/src/com/cloud/storage/upload/UploadMonitor.java b/server/src/com/cloud/storage/upload/UploadMonitor.java index a490cd383b5..df0f68b7920 100755 --- a/server/src/com/cloud/storage/upload/UploadMonitor.java +++ b/server/src/com/cloud/storage/upload/UploadMonitor.java @@ -27,6 +27,7 @@ import com.cloud.storage.VMTemplateHostVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.VolumeVO; import com.cloud.utils.component.Manager; +import com.cloud.utils.exception.CloudRuntimeException; /** * Monitor upload progress of all entities. @@ -55,4 +56,7 @@ public interface UploadMonitor extends Manager{ UploadVO createEntityDownloadURL(VMTemplateVO template, VMTemplateHostVO vmTemplateHost, Long dataCenterId, long eventId); + void createVolumeDownloadURL(Long entityId, String path, Type type, + Long dataCenterId, Long uploadId) throws CloudRuntimeException; + } \ No newline at end of file diff --git a/server/src/com/cloud/storage/upload/UploadMonitorImpl.java b/server/src/com/cloud/storage/upload/UploadMonitorImpl.java index 9793fe1130e..6b718d560dc 100755 --- a/server/src/com/cloud/storage/upload/UploadMonitorImpl.java +++ b/server/src/com/cloud/storage/upload/UploadMonitorImpl.java @@ -24,6 +24,7 @@ import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand; import com.cloud.agent.api.storage.DeleteEntityDownloadURLCommand; import com.cloud.agent.api.storage.UploadCommand; import com.cloud.agent.api.storage.UploadProgressCommand.RequestType; +import com.cloud.api.ApiDBUtils; import com.cloud.async.AsyncJobManager; import com.cloud.configuration.dao.ConfigurationDao; import com.cloud.event.EventTypes; @@ -118,7 +119,7 @@ public class UploadMonitorImpl implements UploadMonitor { String errorString, String jobId, String uploadUrl){ UploadVO uploadObj = new UploadVO(hostId, typeId, new Date(), - uploadState, 0, type, null, "jobid0000", uploadUrl); + uploadState, 0, type, null, null, uploadUrl); _uploadDao.persist(uploadObj); return uploadObj; @@ -175,6 +176,9 @@ public class UploadMonitorImpl implements UploadMonitor { } + + + @Override public UploadVO createEntityDownloadURL(VMTemplateVO template, VMTemplateHostVO vmTemplateHost, Long dataCenterId, long eventId) { @@ -225,10 +229,57 @@ public class UploadMonitorImpl implements UploadMonitor { _uploadDao.update(uploadTemplateObj.getId(), vo); return _uploadDao.findById(uploadTemplateObj.getId(), true); } - throw new CloudRuntimeException("Couldnt find a running SSVM in the zone" + dataCenterId+ ".couldnt create the extraction URL."); + throw new CloudRuntimeException("Couldnt find a running SSVM in the zone" + dataCenterId+ ". Couldnt create the extraction URL."); } + @Override + public void createVolumeDownloadURL(Long entityId, String path, Type type, Long dataCenterId, Long uploadId) throws CloudRuntimeException{ + + List storageServers = _serverDao.listByTypeDataCenter(Host.Type.SecondaryStorage, dataCenterId); + if(storageServers == null ) + throw new CloudRuntimeException("No Storage Server found at the datacenter - " +dataCenterId); + + // Update DB for state = DOWNLOAD_URL_NOT_CREATED. + UploadVO uploadJob = _uploadDao.createForUpdate(uploadId); + uploadJob.setUploadState(Status.DOWNLOAD_URL_NOT_CREATED); + uploadJob.setLastUpdated(new Date()); + _uploadDao.update(uploadJob.getId(), uploadJob); + + // Create Symlink at ssvm + CreateEntityDownloadURLCommand cmd = new CreateEntityDownloadURLCommand(path); + long result = send(ApiDBUtils.findUploadById(uploadId).getHostId(), cmd, null); + if (result == -1){ + String errorString = "Unable to create a link for " +type+ " id:"+entityId; + s_logger.warn(errorString); + throw new CloudRuntimeException(errorString); + } + + //Construct actual URL locally now that the symlink exists at SSVM + List ssVms = _secStorageVmDao.getSecStorageVmListInStates(dataCenterId, State.Running); + if (ssVms.size() > 0) { + SecondaryStorageVmVO ssVm = ssVms.get(0); + if (ssVm.getPublicIpAddress() == null) { + s_logger.warn("A running secondary storage vm has a null public ip?"); + throw new CloudRuntimeException("SSVM has null public IP - couldnt create the URL"); + } + String extractURL = generateCopyUrl(ssVm.getPublicIpAddress(), path); + UploadVO vo = _uploadDao.createForUpdate(); + vo.setLastUpdated(new Date()); + vo.setUploadUrl(extractURL); + vo.setUploadState(Status.DOWNLOAD_URL_CREATED); + + if(extractURL == null){ + vo.setUploadState(Status.ERROR); + vo.setErrorString("Could not create the download URL"); + } + _uploadDao.update(uploadId, vo); + return; + } + throw new CloudRuntimeException("Couldnt find a running SSVM in the zone" + dataCenterId+ ". Couldnt create the extraction URL."); + + } + private String generateCopyUrl(String ipAddress, String path){ String hostname = ipAddress; String scheme = "http"; @@ -401,7 +452,7 @@ public class UploadMonitorImpl implements UploadMonitor { for (UploadVO extractURL : extractURLs){ if( getTimeDiff(extractURL.getLastUpdated()) < EXTRACT_URL_TIME_LIMIT) continue; String path = extractURL.getUploadUrl().substring( (extractURL.getUploadUrl().lastIndexOf("/")) +1 ); - DeleteEntityDownloadURLCommand cmd = new DeleteEntityDownloadURLCommand(path); + DeleteEntityDownloadURLCommand cmd = new DeleteEntityDownloadURLCommand(path, extractURL.getType()); long result = send(extractURL.getHostId(), cmd, null); if (result == -1){ s_logger.warn("Unable to delete the link for " +extractURL.getType()+ " id=" +extractURL.getTypeId()+ " url="+extractURL.getUploadUrl()); diff --git a/server/src/com/cloud/template/TemplateManagerImpl.java b/server/src/com/cloud/template/TemplateManagerImpl.java index c4be9fbaf16..cea8c185fd3 100755 --- a/server/src/com/cloud/template/TemplateManagerImpl.java +++ b/server/src/com/cloud/template/TemplateManagerImpl.java @@ -537,10 +537,10 @@ public class TemplateManagerImpl implements TemplateManager { if (vo!=null){ ExtractJobResultObject resultObject = new ExtractJobResultObject(template.getAccountId(), template.getName(), Upload.Status.DOWNLOAD_URL_CREATED.toString(), vo.getId(), url); mgr.completeAsyncJob(job.getId(), AsyncJobResult.STATUS_SUCCEEDED, 1, resultObject); - EventUtils.saveEvent(userId, accountId, EventVO.LEVEL_INFO, event, "Completed extraction of "+template.getName()+ " in mode:" +extractMode.toString(), null, eventId); + EventUtils.saveEvent(userId, accountId, EventVO.LEVEL_INFO, event, "Completed extraction of "+template.getName()+ " in mode:" +mode, null, eventId); return vo.getId(); }else{ - EventUtils.saveEvent(userId, accountId, EventVO.LEVEL_ERROR, event, "Failed extraction of "+template.getName()+ " in mode:" +extractMode.toString(), null, eventId); + EventUtils.saveEvent(userId, accountId, EventVO.LEVEL_ERROR, event, "Failed extraction of "+template.getName()+ " in mode:" +mode, null, eventId); mgr.completeAsyncJob(job.getId(), AsyncJobResult.STATUS_FAILED, 2, null); return null; }