/** * Abiquo community edition * cloud management application for hybrid clouds * Copyright (C) 2008-2010 - Abiquo Holdings S.L. * * This application is free software; you can redistribute it and/or * modify it under the terms of the GNU LESSER GENERAL PUBLIC * LICENSE as published by the Free Software Foundation under * version 3 of the License * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * LESSER GENERAL PUBLIC LICENSE v.3 for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package com.abiquo.am.services.download; import static com.abiquo.am.services.TemplateConventions.getFileUrl; import java.io.File; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.dmtf.schemas.ovf.envelope._1.DiskSectionType; import org.dmtf.schemas.ovf.envelope._1.EnvelopeType; import org.dmtf.schemas.ovf.envelope._1.FileType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.abiquo.am.exceptions.AMError; import com.abiquo.am.services.EnterpriseRepositoryService; import com.abiquo.am.services.ErepoFactory; import com.abiquo.am.services.TemplateConventions; import com.abiquo.am.services.notify.AMNotifier; import com.abiquo.am.services.ovfformat.TemplateToOVFEnvelope; import com.abiquo.appliancemanager.client.ExternalHttpConnection; import com.abiquo.appliancemanager.exceptions.AMException; import com.abiquo.appliancemanager.exceptions.DownloadException; import com.abiquo.appliancemanager.transport.TemplateDto; import com.abiquo.appliancemanager.transport.TemplateStateDto; import com.abiquo.appliancemanager.transport.TemplateStatusEnumType; import com.abiquo.ovfmanager.ovf.OVFEnvelopeUtils; import com.abiquo.ovfmanager.ovf.exceptions.InvalidSectionException; import com.abiquo.ovfmanager.ovf.exceptions.SectionNotPresentException; import com.ning.http.client.AsyncHttpClient; /** * Take an OVF-Envelope document and download all its references into the internal repository * (exposed by an NFS-Servers), also copy the source OVF document changing the file references to * make it repository relative. * * @author apuig */ @Component public class TemplateDownloader { private final static Logger LOG = LoggerFactory.getLogger(TemplateDownloader.class); /** used in the {@link DownloadingFile} constructor */ @Autowired private AMNotifier notifier; private final AsyncHttpClient httpClient = new AsyncHttpClient(ExternalHttpConnection.createHttpClientConf()); private final Map<String, DownloadingFile> inprogress = new ConcurrentHashMap<String, DownloadingFile>(); /** * Make the provided template available on the enterprise repository. Creates a new directory * for the provided template definition into the repository (using the OVF file name), inspect * the OVF-Envelope to download all its File References into its package folder. Also change the * envelope to ensure use relative paths on the File ''href''. Sets the OVFState to DOWNLOADING * on the OVFIndex. * * @param ovfid, URL where the OVF can be downloaded. * @param enterpirseId, the enterprise requesting its availability. * @throws RepositoryException, it the ovfPackageLocation can not be reached or its * @throws DownloadException, content is not a valid OVF envelope document or any error during * the download of some file on the package. */ public synchronized void deployTemplate(final String enterpriseId, final String ovfId, final EnvelopeType envelope) { LOG.debug("Deploy request [{}]", ovfId); try { final DownloadingFile DownloadingFile = createFileTransfers(ovfId, envelope, enterpriseId); httpClient.prepareGet(DownloadingFile.fileUrl).execute(DownloadingFile); inprogress.put(ovfId, DownloadingFile); purgeInprogress(); } catch (Exception e) { throw new AMException(AMError.TEMPLATE_INSTALL, e); } } private synchronized void purgeInprogress() { for (String ovfid : inprogress.keySet()) { if (inprogress.get(ovfid).done()) { inprogress.remove(ovfid); } } } /** * @param envelope, the OVF envelope document. * @param enterprise, the current enterprise being deploying. * @throws InvalidSectionException * @throws SectionNotPresentException */ private DownloadingFile createFileTransfers(final String ovfId, final EnvelopeType envelope, final String enterpriseId) throws SectionNotPresentException, InvalidSectionException { // TODO prior validation /* * if (envelope.getReferences().getFile().size() != 1) { throw new * AMException(AMError.TEMPLATE_INVALID_MULTIPLE_FILES); } */ final EnterpriseRepositoryService enterpirseRepository = ErepoFactory.getRepo(enterpriseId); DiskSectionType diskSectionType = OVFEnvelopeUtils.getSection(envelope, DiskSectionType.class); int index = 0; int references = 0; for (FileType fileType : envelope.getReferences().getFile()) { if (diskSectionType.getDisk().get(0).getFileRef().equals(fileType.getId())) { index = references; } references++; } final FileType fileType = envelope.getReferences().getFile().get(index); // XXX final Long expectedBytes = fileType.getSize().longValue(); if (!enterpirseRepository.isEnoughtSpaceOn(expectedBytes)) { // note the expected bytes are from the OVF document, but for progress we use the // content-length header throw new AMException(AMError.REPO_NO_SPACE, String.format("Requested %s MB", String.valueOf(expectedBytes / 1048576))); } final String destinationPath = TemplateConventions.createFileInfo(enterpirseRepository.path(), fileType, ovfId); final String fileURL = getFileUrl(fileType.getHref(), ovfId); return new DownloadingFile(fileURL, destinationPath, enterpriseId, ovfId, notifier); } /** * XXX Block until the upload transfer ends. OVFId and EnterpriseId form the disk info * * @return the OVFid of the just uploaded package * @throws IOException */ public synchronized String uploadTemplate(final TemplateDto diskInfo, final File diskFile) throws IOException { final long idEnterprise = diskInfo.getEnterpriseRepositoryId(); final String ovfId = diskInfo.getUrl(); // XXX EnterpriseRepositoryService enterpriseRepository = ErepoFactory.getRepo(String.valueOf(idEnterprise)); // create and write the OVF Envelope EnvelopeType envelope = TemplateToOVFEnvelope.createOVFEnvelopeFromTemplate(diskInfo); enterpriseRepository.createTemplateFolder(ovfId); enterpriseRepository.createTemplateFolder(ovfId, envelope); enterpriseRepository.copyFileToOVFPackagePath(ovfId, diskFile); return ovfId; } /** * Cancel an active OVF package deployment (stopping download all its files). * * @param ovfId, the OVF package identifier. * @throws RepositoryException if the package is not on DOWNLOADING state. */ public synchronized void cancelDeployTemplate(final String ovfId, final String enterpriseId) { EnterpriseRepositoryService enterpriseRepository = ErepoFactory.getRepo(enterpriseId); final TemplateStateDto state = enterpriseRepository.getTemplateStatus(ovfId); final TemplateStatusEnumType status = state.getStatus(); if (status == TemplateStatusEnumType.DOWNLOADING) { if (inprogress.containsKey(ovfId)) { // also delete all the being download files. inprogress.get(ovfId).onCancel(true); inprogress.remove(ovfId); } else { throw new AMException(AMError.TEMPLATE_CANCEL, String.format( "Provided OVF[%s] appears as DOWNLOADING but is not beeing deployed", ovfId)); } } else { throw new AMException(AMError.TEMPLATE_CANCEL, String.format( "Provided OVF[%s] is not on DOWNLOADING state," + " its [%s]. So it can not be cancelled", ovfId, status.name())); } purgeInprogress(); } @Override protected void finalize() throws Throwable { super.finalize(); for (DownloadingFile downloads : inprogress.values()) { downloads.onCancel(true); } } }