// Copyright 2012 Citrix Systems, Inc. Licensed under the // Apache License, Version 2.0 (the "License"); you may not use this // file except in compliance with the License. Citrix Systems, Inc. // reserves all rights not expressly granted by 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. // // Automatically generated by addcopyright.py at 04/03/2012 package com.cloud.storage.template; import java.io.File; import java.net.URI; import java.net.URISyntaxException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; import com.cloud.agent.api.storage.CreateEntityDownloadURLAnswer; import com.cloud.agent.api.storage.CreateEntityDownloadURLCommand; import com.cloud.agent.api.storage.DeleteEntityDownloadURLAnswer; import com.cloud.agent.api.storage.DeleteEntityDownloadURLCommand; import com.cloud.agent.api.storage.UploadAnswer; import com.cloud.agent.api.storage.UploadCommand; import com.cloud.agent.api.storage.UploadProgressCommand; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.StorageLayer; import com.cloud.storage.Upload; import com.cloud.storage.UploadVO; import com.cloud.storage.resource.SecondaryStorageResource; import com.cloud.storage.template.TemplateUploader.Status; import com.cloud.storage.template.TemplateUploader.UploadCompleteCallback; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.Adapters; import com.cloud.utils.component.ComponentLocator; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; public class UploadManagerImpl implements UploadManager { public class Completion implements UploadCompleteCallback { private final String jobId; public Completion(String jobId) { this.jobId = jobId; } @Override public void uploadComplete(Status status) { setUploadStatus(jobId, status); } } private static class UploadJob { private final TemplateUploader tu; private final String jobId; private final String name; private final ImageFormat format; private String tmpltPath; private String description; private String checksum; private Long accountId; private String installPathPrefix; private long templatesize; private long id; public UploadJob(TemplateUploader tu, String jobId, long id, String name, ImageFormat format, boolean hvm, Long accountId, String descr, String cksum, String installPathPrefix) { super(); this.tu = tu; this.jobId = jobId; this.name = name; this.format = format; this.accountId = accountId; this.description = descr; this.checksum = cksum; this.installPathPrefix = installPathPrefix; this.templatesize = 0; this.id = id; } public TemplateUploader getTd() { return tu; } public String getDescription() { return description; } public String getChecksum() { return checksum; } public UploadJob(TemplateUploader td, String jobId, UploadCommand cmd) { this.tu = td; this.jobId = jobId; this.name = cmd.getName(); this.format = cmd.getFormat(); } public TemplateUploader getTemplateUploader() { return tu; } public String getJobId() { return jobId; } public String getTmpltName() { return name; } public ImageFormat getFormat() { return format; } public Long getAccountId() { return accountId; } public long getId() { return id; } public void setTmpltPath(String tmpltPath) { this.tmpltPath = tmpltPath; } public String getTmpltPath() { return tmpltPath; } public String getInstallPathPrefix() { return installPathPrefix; } public void cleanup() { if (tu != null) { String upldPath = tu.getUploadLocalPath(); if (upldPath != null) { File f = new File(upldPath); f.delete(); } } } public void setTemplatesize(long templatesize) { this.templatesize = templatesize; } public long getTemplatesize() { return templatesize; } } public static final Logger s_logger = Logger.getLogger(UploadManagerImpl.class); private ExecutorService threadPool; private final Map<String, UploadJob> jobs = new ConcurrentHashMap<String, UploadJob>(); private String parentDir; private Adapters<Processor> _processors; private String publicTemplateRepo; private String extractMountPoint = "/mnt/SecStorage/extractmnt"; private StorageLayer _storage; private int installTimeoutPerGig; private boolean _sslCopy; private String _name; private boolean hvm; @Override public String uploadPublicTemplate(long id, String url, String name, ImageFormat format, Long accountId, String descr, String cksum, String installPathPrefix, String userName, String passwd, long templateSizeInBytes) { UUID uuid = UUID.randomUUID(); String jobId = uuid.toString(); String completePath = parentDir + File.separator + installPathPrefix; s_logger.debug("Starting upload from " + completePath); URI uri; try { uri = new URI(url); } catch (URISyntaxException e) { s_logger.error("URI is incorrect: " + url); throw new CloudRuntimeException("URI is incorrect: " + url); } TemplateUploader tu; if ((uri != null) && (uri.getScheme() != null)) { if (uri.getScheme().equalsIgnoreCase("ftp")) { tu = new FtpTemplateUploader(completePath, url, new Completion(jobId), templateSizeInBytes); } else { s_logger.error("Scheme is not supported " + url); throw new CloudRuntimeException("Scheme is not supported " + url); } } else { s_logger.error("Unable to download from URL: " + url); throw new CloudRuntimeException("Unable to download from URL: " + url); } UploadJob uj = new UploadJob(tu, jobId, id, name, format, hvm, accountId, descr, cksum, installPathPrefix); jobs.put(jobId, uj); threadPool.execute(tu); return jobId; } @Override public String getUploadError(String jobId) { UploadJob uj = jobs.get(jobId); if (uj != null) { return uj.getTemplateUploader().getUploadError(); } return null; } @Override public int getUploadPct(String jobId) { UploadJob uj = jobs.get(jobId); if (uj != null) { return uj.getTemplateUploader().getUploadPercent(); } return 0; } @Override public Status getUploadStatus(String jobId) { UploadJob job = jobs.get(jobId); if (job != null) { TemplateUploader tu = job.getTemplateUploader(); if (tu != null) { return tu.getStatus(); } } return Status.UNKNOWN; } public static UploadVO.Status convertStatus(Status tds) { switch (tds) { case ABORTED: return UploadVO.Status.NOT_UPLOADED; case UPLOAD_FINISHED: return UploadVO.Status.UPLOAD_IN_PROGRESS; case IN_PROGRESS: return UploadVO.Status.UPLOAD_IN_PROGRESS; case NOT_STARTED: return UploadVO.Status.NOT_UPLOADED; case RECOVERABLE_ERROR: return UploadVO.Status.NOT_UPLOADED; case UNKNOWN: return UploadVO.Status.UNKNOWN; case UNRECOVERABLE_ERROR: return UploadVO.Status.UPLOAD_ERROR; case POST_UPLOAD_FINISHED: return UploadVO.Status.UPLOADED; default: return UploadVO.Status.UNKNOWN; } } @Override public com.cloud.storage.UploadVO.Status getUploadStatus2(String jobId) { return convertStatus(getUploadStatus(jobId)); } @Override public String getPublicTemplateRepo() { // TODO Auto-generated method stub return null; } private UploadAnswer handleUploadProgressCmd(UploadProgressCommand cmd) { String jobId = cmd.getJobId(); UploadAnswer answer; UploadJob uj = null; if (jobId != null) uj = jobs.get(jobId); if (uj == null) { return new UploadAnswer(null, 0, "Cannot find job", com.cloud.storage.UploadVO.Status.UNKNOWN, "", "", 0); } TemplateUploader td = uj.getTemplateUploader(); switch (cmd.getRequest()) { case GET_STATUS: break; case ABORT: td.stopUpload(); sleep(); break; /*case RESTART: td.stopUpload(); sleep(); threadPool.execute(td); break;*/ case PURGE: td.stopUpload(); answer = new UploadAnswer(jobId, getUploadPct(jobId), getUploadError(jobId), getUploadStatus2(jobId), getUploadLocalPath(jobId), getInstallPath(jobId), getUploadTemplateSize(jobId)); jobs.remove(jobId); return answer; default: break; // TODO } return new UploadAnswer(jobId, getUploadPct(jobId), getUploadError(jobId), getUploadStatus2(jobId), getUploadLocalPath(jobId), getInstallPath(jobId), getUploadTemplateSize(jobId)); } @Override public UploadAnswer handleUploadCommand(SecondaryStorageResource resource, UploadCommand cmd) { s_logger.warn("Handling the upload " +cmd.getInstallPath() + " " + cmd.getId()); if (cmd instanceof UploadProgressCommand) { return handleUploadProgressCmd((UploadProgressCommand) cmd); } String user = null; String password = null; String jobId = uploadPublicTemplate(cmd.getId(), cmd.getUrl(), cmd.getName(), cmd.getFormat(), cmd.getAccountId(), cmd.getDescription(), cmd.getChecksum(), cmd.getInstallPath(), user, password, cmd.getTemplateSizeInBytes()); sleep(); if (jobId == null) { return new UploadAnswer(null, 0, "Internal Error", com.cloud.storage.UploadVO.Status.UPLOAD_ERROR, "", "", 0); } return new UploadAnswer(jobId, getUploadPct(jobId), getUploadError(jobId), getUploadStatus2(jobId), getUploadLocalPath(jobId), getInstallPath(jobId), getUploadTemplateSize(jobId)); } @Override public CreateEntityDownloadURLAnswer handleCreateEntityURLCommand(CreateEntityDownloadURLCommand cmd){ boolean isApacheUp = checkAndStartApache(); if (!isApacheUp){ String errorString = "Error in starting Apache server "; s_logger.error(errorString); return new CreateEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE); } // Create the directory structure so that its visible under apache server root String extractDir = "/var/www/html/userdata/"; Script command = new Script("mkdir", s_logger); command.add("-p"); command.add(extractDir); String result = command.execute(); if (result != null) { String errorString = "Error in creating directory =" + result; s_logger.error(errorString); return new CreateEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE); } // Create a random file under the directory for security reasons. String uuid = cmd.getExtractLinkUUID(); command = new Script("touch", s_logger); command.add(extractDir + uuid); result = command.execute(); if (result != null) { String errorString = "Error in creating file " +uuid+ " ,error: " + result; s_logger.warn(errorString); return new CreateEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE); } // Create a symbolic link from the actual directory to the template location. The entity would be directly visible under /var/www/html/userdata/cmd.getInstallPath(); command = new Script("/bin/bash", s_logger); command.add("-c"); command.add("ln -sf /mnt/SecStorage/" + cmd.getParent() + File.separator + cmd.getInstallPath() + " " + extractDir + uuid); result = command.execute(); if (result != null) { String errorString = "Error in linking err=" + result; s_logger.error(errorString); return new CreateEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE); } return new CreateEntityDownloadURLAnswer("", CreateEntityDownloadURLAnswer.RESULT_SUCCESS); } @Override public DeleteEntityDownloadURLAnswer handleDeleteEntityDownloadURLCommand(DeleteEntityDownloadURLCommand cmd){ //Delete the soft link. Example path = volumes/8/74eeb2c6-8ab1-4357-841f-2e9d06d1f360.vhd s_logger.warn("handleDeleteEntityDownloadURLCommand Path:"+cmd.getPath() + " Type:" +cmd.getType().toString()); String path = cmd.getPath(); Script command = new Script("/bin/bash", s_logger); command.add("-c"); //We just need to remove the UUID.vhd String extractUrl = cmd.getExtractUrl(); command.add("unlink /var/www/html/userdata/" +extractUrl.substring(extractUrl.lastIndexOf(File.separator) + 1)); String result = command.execute(); if (result != null) { String errorString = "Error in deleting =" + result; s_logger.warn(errorString); return new DeleteEntityDownloadURLAnswer(errorString, CreateEntityDownloadURLAnswer.RESULT_FAILURE); } // If its a volume also delete the Hard link since it was created only for the purpose of download. if(cmd.getType() == Upload.Type.VOLUME){ command = new Script("/bin/bash", s_logger); command.add("-c"); command.add("rm -f /mnt/SecStorage/" + cmd.getParentPath() +File.separator+ path); s_logger.warn(" " +parentDir +File.separator+ path); 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); } private String getInstallPath(String jobId) { // TODO Auto-generated method stub return null; } private String getUploadLocalPath(String jobId) { // TODO Auto-generated method stub return null; } private long getUploadTemplateSize(String jobId){ UploadJob uj = jobs.get(jobId); if (uj != null) { return uj.getTemplatesize(); } return 0; } @Override public boolean configure(String name, Map<String, Object> params) throws ConfigurationException { _name = name; String value = null; _storage = (StorageLayer) params.get(StorageLayer.InstanceConfigKey); if (_storage == null) { value = (String) params.get(StorageLayer.ClassConfigKey); if (value == null) { throw new ConfigurationException("Unable to find the storage layer"); } Class<StorageLayer> clazz; try { clazz = (Class<StorageLayer>) Class.forName(value); } catch (ClassNotFoundException e) { throw new ConfigurationException("Unable to instantiate " + value); } _storage = ComponentLocator.inject(clazz); } String useSsl = (String)params.get("sslcopy"); if (useSsl != null) { _sslCopy = Boolean.parseBoolean(useSsl); } String inSystemVM = (String)params.get("secondary.storage.vm"); if (inSystemVM != null && "true".equalsIgnoreCase(inSystemVM)) { s_logger.info("UploadManager: starting additional services since we are inside system vm"); startAdditionalServices(); //blockOutgoingOnPrivate(); } value = (String) params.get("install.timeout.pergig"); this.installTimeoutPerGig = NumbersUtil.parseInt(value, 15 * 60) * 1000; value = (String) params.get("install.numthreads"); final int numInstallThreads = NumbersUtil.parseInt(value, 10); String scriptsDir = (String) params.get("template.scripts.dir"); if (scriptsDir == null) { scriptsDir = "scripts/storage/secondary"; } // Add more processors here. threadPool = Executors.newFixedThreadPool(numInstallThreads); return true; } private void startAdditionalServices() { Script command = new Script("rm", s_logger); command.add("-rf"); command.add(extractMountPoint); String result = command.execute(); if (result != null) { s_logger.warn("Error in creating file " +extractMountPoint+ " ,error: " + result ); return; } command = new Script("touch", s_logger); command.add(extractMountPoint); result = command.execute(); if (result != null) { s_logger.warn("Error in creating file " +extractMountPoint+ " ,error: " + result ); return; } command = new Script("/bin/bash", s_logger); command.add("-c"); command.add("ln -sf " + parentDir + " " +extractMountPoint); result = command.execute(); if (result != null) { s_logger.warn("Error in linking err=" + result ); return; } } @Override public String getName() { return _name; } @Override public boolean start() { return true; } @Override public boolean stop() { return true; } /** * Get notified of change of job status. Executed in context of uploader thread * * @param jobId * the id of the job * @param status * the status of the job */ public void setUploadStatus(String jobId, Status status) { UploadJob uj = jobs.get(jobId); if (uj == null) { s_logger.warn("setUploadStatus for jobId: " + jobId + ", status=" + status + " no job found"); return; } TemplateUploader tu = uj.getTemplateUploader(); s_logger.warn("Upload Completion for jobId: " + jobId + ", status=" + status); s_logger.warn("UploadedBytes=" + tu.getUploadedBytes() + ", error=" + tu.getUploadError() + ", pct=" + tu.getUploadPercent()); switch (status) { case ABORTED: case NOT_STARTED: case UNRECOVERABLE_ERROR: // Delete the entity only if its a volume. TO DO - find a better way of finding it a volume. if(uj.getTemplateUploader().getUploadLocalPath().indexOf("volume") > -1){ uj.cleanup(); } break; case UNKNOWN: return; case IN_PROGRESS: s_logger.info("Resuming jobId: " + jobId + ", status=" + status); tu.setResume(true); threadPool.execute(tu); break; case RECOVERABLE_ERROR: threadPool.execute(tu); break; case UPLOAD_FINISHED: tu.setUploadError("Upload success, starting install "); String result = postUpload(jobId); if (result != null) { s_logger.error("Failed post upload script: " + result); tu.setStatus(Status.UNRECOVERABLE_ERROR); tu.setUploadError("Failed post upload script: " + result); } else { s_logger.warn("Upload completed successfully at " + new SimpleDateFormat().format(new Date())); tu.setStatus(Status.POST_UPLOAD_FINISHED); tu.setUploadError("Upload completed successfully at " + new SimpleDateFormat().format(new Date())); } // Delete the entity only if its a volume. TO DO - find a better way of finding it a volume. if(uj.getTemplateUploader().getUploadLocalPath().indexOf("volume") > -1){ uj.cleanup(); } break; default: break; } } private String postUpload(String jobId) { return null; } private void sleep() { try { Thread.sleep(3000); } catch (InterruptedException e) { // ignore } } private boolean checkAndStartApache() { //Check whether the Apache server is running Script command = new Script("/bin/bash", s_logger); command.add("-c"); command.add("if [ -d /etc/apache2 ] ; then service apache2 status | grep pid; else service httpd status | grep pid; fi "); String result = command.execute(); //Apache Server is not running. Try to start it. if (result != null) { /*s_logger.warn("Apache server not running, trying to start it"); String port = Integer.toString(TemplateConstants.DEFAULT_TMPLT_COPY_PORT); String intf = TemplateConstants.DEFAULT_TMPLT_COPY_INTF; command = new Script("/bin/bash", s_logger); command.add("-c"); command.add("iptables -D INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + port + " -j DROP;" + "iptables -D INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + port + " -j HTTP;" + "iptables -D INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + "443" + " -j DROP;" + "iptables -D INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + "443" + " -j HTTP;" + "iptables -F HTTP;" + "iptables -X HTTP;" + "iptables -N HTTP;" + "iptables -I INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + port + " -j DROP;" + "iptables -I INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + "443" + " -j DROP;" + "iptables -I INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + port + " -j HTTP;" + "iptables -I INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + "443" + " -j HTTP;"); result = command.execute(); if (result != null) { s_logger.warn("Error in opening up httpd port err=" + result ); return false; }*/ command = new Script("/bin/bash", s_logger); command.add("-c"); command.add("if [ -d /etc/apache2 ] ; then service apache2 start; else service httpd start; fi "); result = command.execute(); if (result != null) { s_logger.warn("Error in starting httpd service err=" + result ); return false; } } return true; } }