You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by bf...@apache.org on 2013/04/10 19:45:42 UTC

[02/50] [abbrv] QuickCloud: Enable secondary storage daemon to run outside the system vm

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e7983b25/services/secondary-storage/src/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
----------------------------------------------------------------------
diff --git a/services/secondary-storage/src/org/apache/cloudstack/storage/template/DownloadManagerImpl.java b/services/secondary-storage/src/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
new file mode 100755
index 0000000..a9d23cb
--- /dev/null
+++ b/services/secondary-storage/src/org/apache/cloudstack/storage/template/DownloadManagerImpl.java
@@ -0,0 +1,1074 @@
+// 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.template;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.math.BigInteger;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+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.ejb.Local;
+import javax.naming.ConfigurationException;
+
+import org.apache.cloudstack.storage.resource.SecondaryStorageResource;
+import org.apache.log4j.Logger;
+
+import com.cloud.agent.api.storage.DownloadAnswer;
+import com.cloud.agent.api.storage.DownloadCommand;
+import com.cloud.agent.api.storage.DownloadCommand.Proxy;
+import com.cloud.agent.api.storage.DownloadCommand.ResourceType;
+import com.cloud.agent.api.storage.DownloadProgressCommand;
+import com.cloud.agent.api.storage.DownloadProgressCommand.RequestType;
+import com.cloud.exception.InternalErrorException;
+import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.StorageLayer;
+import com.cloud.storage.VMTemplateHostVO;
+import com.cloud.storage.VMTemplateStorageResourceAssoc;
+import com.cloud.storage.template.HttpTemplateDownloader;
+import com.cloud.storage.template.IsoProcessor;
+import com.cloud.storage.template.LocalTemplateDownloader;
+import com.cloud.storage.template.Processor;
+import com.cloud.storage.template.Processor.FormatInfo;
+import com.cloud.storage.template.QCOW2Processor;
+import com.cloud.storage.template.RawImageProcessor;
+import com.cloud.storage.template.ScpTemplateDownloader;
+import com.cloud.storage.template.TemplateConstants;
+import com.cloud.storage.template.TemplateDownloader;
+import com.cloud.storage.template.TemplateDownloader.DownloadCompleteCallback;
+import com.cloud.storage.template.TemplateDownloader.Status;
+import com.cloud.storage.template.TemplateInfo;
+import com.cloud.storage.template.TemplateLocation;
+import com.cloud.storage.template.VhdProcessor;
+import com.cloud.storage.template.VmdkProcessor;
+import com.cloud.utils.NumbersUtil;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.OutputInterpreter;
+import com.cloud.utils.script.Script;
+
+@Local(value = DownloadManager.class)
+public class DownloadManagerImpl extends ManagerBase implements DownloadManager {
+    private String _name;
+    StorageLayer _storage;
+    Map<String, Processor> _processors;
+
+    public class Completion implements DownloadCompleteCallback {
+        private final String jobId;
+
+        public Completion(String jobId) {
+            this.jobId = jobId;
+        }
+
+        @Override
+        public void downloadComplete(Status status) {
+            setDownloadStatus(jobId, status);
+        }
+    }
+
+    private static class DownloadJob {
+        private final TemplateDownloader td;
+        private final String jobId;
+        private final String tmpltName;
+        private final boolean hvm;
+        private final ImageFormat format;
+        private String tmpltPath;
+        private final String description;
+        private String checksum;
+        private final Long accountId;
+        private final String installPathPrefix;
+        private long templatesize;
+        private long templatePhysicalSize;
+        private final long id;
+        private final ResourceType resourceType;
+
+        public DownloadJob(TemplateDownloader td, String jobId, long id, String tmpltName, ImageFormat format, boolean hvm, Long accountId, String descr, String cksum, String installPathPrefix, ResourceType resourceType) {
+            super();
+            this.td = td;
+            this.jobId = jobId;
+            this.tmpltName = tmpltName;
+            this.format = format;
+            this.hvm = hvm;
+            this.accountId = accountId;
+            this.description = descr;
+            this.checksum = cksum;
+            this.installPathPrefix = installPathPrefix;
+            this.templatesize = 0;
+            this.id = id;
+            this.resourceType = resourceType;
+        }
+
+        public TemplateDownloader getTd() {
+            return td;
+        }
+
+        public String getDescription() {
+            return description;
+        }
+
+        public String getChecksum() {
+            return checksum;
+        }
+
+        public TemplateDownloader getTemplateDownloader() {
+            return td;
+        }
+
+        public String getJobId() {
+            return jobId;
+        }
+
+        public String getTmpltName() {
+            return tmpltName;
+        }
+
+        public ImageFormat getFormat() {
+            return format;
+        }
+
+        public boolean isHvm() {
+            return hvm;
+        }
+
+        public Long getAccountId() {
+            return accountId;
+        }
+
+        public long getId() {
+            return id;
+        }
+
+        public ResourceType getResourceType() {
+            return resourceType;
+        }
+
+        public void setTmpltPath(String tmpltPath) {
+            this.tmpltPath = tmpltPath;
+        }
+
+        public String getTmpltPath() {
+            return tmpltPath;
+        }
+
+        public String getInstallPathPrefix() {
+            return installPathPrefix;
+        }
+
+        public void cleanup() {
+            if (td != null) {
+                String dnldPath = td.getDownloadLocalPath();
+                if (dnldPath != null) {
+                    File f = new File(dnldPath);
+                    File dir = f.getParentFile();
+                    f.delete();
+                    if (dir != null) {
+                        dir.delete();
+                    }
+                }
+            }
+
+        }
+
+        public void setTemplatesize(long templatesize) {
+            this.templatesize = templatesize;
+        }
+
+        public long getTemplatesize() {
+            return templatesize;
+        }
+
+        public void setTemplatePhysicalSize(long templatePhysicalSize) {
+            this.templatePhysicalSize = templatePhysicalSize;
+        }
+
+        public long getTemplatePhysicalSize() {
+            return templatePhysicalSize;
+        }
+
+        public void setCheckSum(String checksum) {
+            this.checksum = checksum;
+        }
+    }
+
+    public static final Logger s_logger = Logger.getLogger(DownloadManagerImpl.class);
+    private String _templateDir;
+    private String _volumeDir;
+    private String createTmpltScr;
+    private String createVolScr;
+    private List<Processor> processors;
+
+    private ExecutorService threadPool;
+
+    private final Map<String, DownloadJob> jobs = new ConcurrentHashMap<String, DownloadJob>();
+    private String listTmpltScr;
+    private String listVolScr;
+    private int installTimeoutPerGig = 180 * 60 * 1000;
+    private boolean _sslCopy;
+
+    /**
+     * Get notified of change of job status. Executed in context of downloader thread
+     * 
+     * @param jobId
+     *            the id of the job
+     * @param status
+     *            the status of the job
+     */
+    public void setDownloadStatus(String jobId, Status status) {
+        DownloadJob dj = jobs.get(jobId);
+        if (dj == null) {
+            s_logger.warn("setDownloadStatus for jobId: " + jobId + ", status=" + status + " no job found");
+            return;
+        }
+        TemplateDownloader td = dj.getTemplateDownloader();
+        s_logger.info("Download Completion for jobId: " + jobId + ", status=" + status);
+        s_logger.info("local: " + td.getDownloadLocalPath() + ", bytes=" + td.getDownloadedBytes() + ", error=" + td.getDownloadError() + ", pct=" + td.getDownloadPercent());
+
+        switch (status) {
+        case ABORTED:
+        case NOT_STARTED:
+        case UNRECOVERABLE_ERROR:
+            // TODO
+            dj.cleanup();
+            break;
+        case UNKNOWN:
+            return;
+        case IN_PROGRESS:
+            s_logger.info("Resuming jobId: " + jobId + ", status=" + status);
+            td.setResume(true);
+            threadPool.execute(td);
+            break;
+        case RECOVERABLE_ERROR:
+            threadPool.execute(td);
+            break;
+        case DOWNLOAD_FINISHED:
+            td.setDownloadError("Download success, starting install ");
+            String result = postDownload(jobId);
+            if (result != null) {
+                s_logger.error("Failed post download script: " + result);
+                td.setStatus(Status.UNRECOVERABLE_ERROR);
+                td.setDownloadError("Failed post download script: " + result);
+            } else {
+                td.setStatus(Status.POST_DOWNLOAD_FINISHED);
+                td.setDownloadError("Install completed successfully at " + new SimpleDateFormat().format(new Date()));
+            }
+            dj.cleanup();
+            break;
+        default:
+            break;
+        }
+    }
+
+    private String computeCheckSum(File f) {
+        byte[] buffer = new byte[8192];
+        int read = 0;
+        MessageDigest digest;
+        String checksum = null;        
+        InputStream is = null;
+        try {
+            digest = MessageDigest.getInstance("MD5");           
+            is = new FileInputStream(f);     
+            while( (read = is.read(buffer)) > 0) {
+                digest.update(buffer, 0, read);
+            }       
+            byte[] md5sum = digest.digest();
+            BigInteger bigInt = new BigInteger(1, md5sum);
+            checksum = String.format("%032x",bigInt);
+            return checksum;
+        }catch(IOException e) {
+            return null;
+        }catch (NoSuchAlgorithmException e) {         
+            return null;
+        }
+        finally {
+            try {
+                if(is != null)
+                    is.close();
+            } catch (IOException e) {
+                return null;
+            }                        
+        }
+    }
+
+    /**
+     * Post download activity (install and cleanup). Executed in context of downloader thread
+     * 
+     * @throws IOException
+     */
+    private String postDownload(String jobId) {
+        DownloadJob dnld = jobs.get(jobId);
+        TemplateDownloader td = dnld.getTemplateDownloader();
+        String resourcePath = null;               
+        ResourceType resourceType = dnld.getResourceType();
+
+        // once template path is set, remove the parent dir so that the template is installed with a relative path
+        String finalResourcePath = "";
+        if (resourceType == ResourceType.TEMPLATE){
+            finalResourcePath += _templateDir + File.separator + dnld.getAccountId() + File.separator + dnld.getId() + File.separator;
+            resourcePath = dnld.getInstallPathPrefix() + dnld.getAccountId() + File.separator + dnld.getId() + File.separator;// dnld.getTmpltName();
+        }else {
+            finalResourcePath += _volumeDir + File.separator + dnld.getId() + File.separator;
+            resourcePath = dnld.getInstallPathPrefix() + dnld.getId() + File.separator;// dnld.getTmpltName();
+        }
+
+        _storage.mkdirs(resourcePath);
+        dnld.setTmpltPath(finalResourcePath);
+
+        File originalTemplate = new File(td.getDownloadLocalPath());
+        String checkSum = computeCheckSum(originalTemplate);
+        if (checkSum == null) {
+            s_logger.warn("Something wrong happened when try to calculate the checksum of downloaded template!");
+        }
+        dnld.setCheckSum(checkSum);
+
+        int imgSizeGigs = (int) Math.ceil(_storage.getSize(td.getDownloadLocalPath()) * 1.0d / (1024 * 1024 * 1024));
+        imgSizeGigs++; // add one just in case
+        long timeout = imgSizeGigs * installTimeoutPerGig;
+        Script scr = null;
+        String script = resourceType == ResourceType.TEMPLATE ? createTmpltScr : createVolScr;
+        scr = new Script(script, timeout, s_logger);
+        scr.add("-s", Integer.toString(imgSizeGigs));
+        scr.add("-S", Long.toString(td.getMaxTemplateSizeInBytes()));
+        if (dnld.getDescription() != null && dnld.getDescription().length() > 1) {
+            scr.add("-d", dnld.getDescription());
+        }
+        if (dnld.isHvm()) {
+            scr.add("-h");
+        }
+
+        // add options common to ISO and template
+        String extension = dnld.getFormat().getFileExtension();
+        String templateName = "";
+        if( extension.equals("iso")) {
+            templateName = jobs.get(jobId).getTmpltName().trim().replace(" ", "_");
+        } else {
+            templateName = java.util.UUID.nameUUIDFromBytes((jobs.get(jobId).getTmpltName() + System.currentTimeMillis()).getBytes()).toString();
+        }
+
+        String templateFilename = templateName + "." + extension;
+        dnld.setTmpltPath(finalResourcePath + "/" + templateFilename);
+        scr.add("-n", templateFilename);
+
+        scr.add("-t", resourcePath);
+        scr.add("-f", td.getDownloadLocalPath());
+        if (dnld.getChecksum() != null && dnld.getChecksum().length() > 1) {
+            scr.add("-c", dnld.getChecksum());
+        }
+        scr.add("-u"); // cleanup
+        String result;
+        result = scr.execute();
+
+        if (result != null) {
+            return result;
+        }
+
+        // Set permissions for the downloaded template
+        File downloadedTemplate = new File(resourcePath + "/" + templateFilename);
+        _storage.setWorldReadableAndWriteable(downloadedTemplate);
+
+        // Set permissions for template/volume.properties
+        String propertiesFile = resourcePath;
+        if (resourceType == ResourceType.TEMPLATE){
+            propertiesFile += "/template.properties";
+        }else{
+            propertiesFile += "/volume.properties";
+        }
+        File templateProperties = new File(propertiesFile);
+        _storage.setWorldReadableAndWriteable(templateProperties);
+
+        TemplateLocation loc = new TemplateLocation(_storage, resourcePath);
+        try {
+            loc.create(dnld.getId(), true, dnld.getTmpltName());
+        } catch (IOException e) {
+            s_logger.warn("Something is wrong with template location " + resourcePath, e);
+            loc.purge();
+            return "Unable to download due to " + e.getMessage();
+        }
+
+        Iterator<Processor> en = _processors.values().iterator();
+        while (en.hasNext()) {
+            Processor processor = en.next();
+
+            FormatInfo info = null;
+            try {
+                info = processor.process(resourcePath, null, templateName);
+            } catch (InternalErrorException e) {
+                s_logger.error("Template process exception ", e);
+                return e.toString();
+            }
+            if (info != null) {
+                loc.addFormat(info);
+                dnld.setTemplatesize(info.virtualSize);
+                dnld.setTemplatePhysicalSize(info.size);
+                break;
+            }
+        }
+
+        if (!loc.save()) {
+            s_logger.warn("Cleaning up because we're unable to save the formats");
+            loc.purge();
+        }
+
+        return null;
+    }
+
+    @Override
+    public Status getDownloadStatus(String jobId) {
+        DownloadJob job = jobs.get(jobId);
+        if (job != null) {
+            TemplateDownloader td = job.getTemplateDownloader();
+            if (td != null) {
+                return td.getStatus();
+            }
+        }
+        return Status.UNKNOWN;
+    }
+
+    @Override
+    public String downloadPublicTemplate(long id, String url, String name, ImageFormat format, boolean hvm, Long accountId, String descr, String cksum, String installPathPrefix, String user, String password, long maxTemplateSizeInBytes, Proxy proxy, ResourceType resourceType) {
+        UUID uuid = UUID.randomUUID();
+        String jobId = uuid.toString();
+        String tmpDir = "";
+        if(resourceType == ResourceType.TEMPLATE){
+            tmpDir = installPathPrefix + File.separator + accountId + File.separator + id;
+        }else {
+            tmpDir = installPathPrefix + File.separator + id;
+        }
+
+        try {
+
+            if (!_storage.mkdirs(tmpDir)) {
+                s_logger.warn("Unable to create " + tmpDir);
+                return "Unable to create " + tmpDir;
+            }
+            //	TO DO - define constant for volume properties.
+            File file = ResourceType.TEMPLATE == resourceType ? _storage.getFile(tmpDir + File.separator + TemplateLocation.Filename) : 
+                _storage.getFile(tmpDir + File.separator + "volume.properties");
+            if ( file.exists() ) {
+                file.delete();
+            }
+
+            if (!file.createNewFile()) {
+                s_logger.warn("Unable to create new file: " + file.getAbsolutePath());
+                return "Unable to create new file: " + file.getAbsolutePath();
+            }
+
+            URI uri;
+            try {
+                uri = new URI(url);
+            } catch (URISyntaxException e) {
+                throw new CloudRuntimeException("URI is incorrect: " + url);
+            }
+            TemplateDownloader td;
+            if ((uri != null) && (uri.getScheme() != null)) {
+                if (uri.getScheme().equalsIgnoreCase("http") || uri.getScheme().equalsIgnoreCase("https")) {
+                    td = new HttpTemplateDownloader(_storage, url, tmpDir, new Completion(jobId), maxTemplateSizeInBytes, user, password, proxy, resourceType);
+                } else if (uri.getScheme().equalsIgnoreCase("file")) {
+                    td = new LocalTemplateDownloader(_storage, url, tmpDir, maxTemplateSizeInBytes, new Completion(jobId));
+                } else if (uri.getScheme().equalsIgnoreCase("scp")) {
+                    td = new ScpTemplateDownloader(_storage, url, tmpDir, maxTemplateSizeInBytes, new Completion(jobId));
+                } else if (uri.getScheme().equalsIgnoreCase("nfs")) {
+                    td = null;
+                    // TODO: implement this.
+                    throw new CloudRuntimeException("Scheme is not supported " + url);
+                } else {
+                    throw new CloudRuntimeException("Scheme is not supported " + url);
+                }
+            } else {
+                throw new CloudRuntimeException("Unable to download from URL: " + url);
+            }
+            DownloadJob dj = new DownloadJob(td, jobId, id, name, format, hvm, accountId, descr, cksum, installPathPrefix, resourceType);
+            jobs.put(jobId, dj);
+            threadPool.execute(td);
+
+            return jobId;
+        } catch (IOException e) {
+            s_logger.warn("Unable to download to " + tmpDir, e);
+            return null;
+        }
+    }
+
+    @Override
+    public String getDownloadError(String jobId) {
+        DownloadJob dj = jobs.get(jobId);
+        if (dj != null) {
+            return dj.getTemplateDownloader().getDownloadError();
+        }
+        return null;
+    }
+
+    public long getDownloadTemplateSize(String jobId) {
+        DownloadJob dj = jobs.get(jobId);
+        if (dj != null) {
+            return dj.getTemplatesize();
+        }
+        return 0;
+    }
+
+    public String getDownloadCheckSum(String jobId) {
+        DownloadJob dj = jobs.get(jobId);
+        if (dj != null) {
+            return dj.getChecksum();
+        }
+        return null;
+    }
+
+    public long getDownloadTemplatePhysicalSize(String jobId) {
+        DownloadJob dj = jobs.get(jobId);
+        if (dj != null) {
+            return dj.getTemplatePhysicalSize();
+        }
+        return 0;
+    }
+
+    // @Override
+    public String getDownloadLocalPath(String jobId) {
+        DownloadJob dj = jobs.get(jobId);
+        if (dj != null) {
+            return dj.getTemplateDownloader().getDownloadLocalPath();
+        }
+        return null;
+    }
+
+    @Override
+    public int getDownloadPct(String jobId) {
+        DownloadJob dj = jobs.get(jobId);
+        if (dj != null) {
+            return dj.getTemplateDownloader().getDownloadPercent();
+        }
+        return 0;
+    }
+
+    public static VMTemplateHostVO.Status convertStatus(Status tds) {
+        switch (tds) {
+        case ABORTED:
+            return VMTemplateHostVO.Status.NOT_DOWNLOADED;
+        case DOWNLOAD_FINISHED:
+            return VMTemplateHostVO.Status.DOWNLOAD_IN_PROGRESS;
+        case IN_PROGRESS:
+            return VMTemplateHostVO.Status.DOWNLOAD_IN_PROGRESS;
+        case NOT_STARTED:
+            return VMTemplateHostVO.Status.NOT_DOWNLOADED;
+        case RECOVERABLE_ERROR:
+            return VMTemplateHostVO.Status.NOT_DOWNLOADED;
+        case UNKNOWN:
+            return VMTemplateHostVO.Status.UNKNOWN;
+        case UNRECOVERABLE_ERROR:
+            return VMTemplateHostVO.Status.DOWNLOAD_ERROR;
+        case POST_DOWNLOAD_FINISHED:
+            return VMTemplateHostVO.Status.DOWNLOADED;
+        default:
+            return VMTemplateHostVO.Status.UNKNOWN;
+        }
+    }
+
+    @Override
+    public com.cloud.storage.VMTemplateHostVO.Status getDownloadStatus2(String jobId) {
+        return convertStatus(getDownloadStatus(jobId));
+    }
+
+    @Override
+    public DownloadAnswer handleDownloadCommand(SecondaryStorageResource resource, DownloadCommand cmd) {
+        ResourceType resourceType = cmd.getResourceType();
+        if (cmd instanceof DownloadProgressCommand) {
+            return handleDownloadProgressCmd( resource, (DownloadProgressCommand) cmd);
+        }
+
+        if (cmd.getUrl() == null) {
+            return new DownloadAnswer(resourceType.toString() + " is corrupted on storage due to an invalid url , cannot download", VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR);
+        }
+
+        if (cmd.getName() == null) {
+            return new DownloadAnswer("Invalid Name", VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR);
+        }
+
+        String installPathPrefix = null;
+        if (ResourceType.TEMPLATE == resourceType){
+            installPathPrefix = resource.getRootDir(cmd) + File.separator + _templateDir;
+        }else {
+            installPathPrefix = resource.getRootDir(cmd) + File.separator + _volumeDir;
+        }
+
+        String user = null;
+        String password = null;
+        if (cmd.getAuth() != null) {
+            user = cmd.getAuth().getUserName();
+            password = new String(cmd.getAuth().getPassword());
+        }
+        //TO DO - Define Volume max size as well
+        long maxDownloadSizeInBytes = (cmd.getMaxDownloadSizeInBytes() == null) ? TemplateDownloader.DEFAULT_MAX_TEMPLATE_SIZE_IN_BYTES : (cmd.getMaxDownloadSizeInBytes());
+        String jobId = downloadPublicTemplate(cmd.getId(), cmd.getUrl(), cmd.getName(), cmd.getFormat(), cmd.isHvm(), cmd.getAccountId(), cmd.getDescription(), cmd.getChecksum(), installPathPrefix, user, password, maxDownloadSizeInBytes, cmd.getProxy(), resourceType);
+        sleep();
+        if (jobId == null) {
+            return new DownloadAnswer("Internal Error", VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR);
+        }
+        return new DownloadAnswer(jobId, getDownloadPct(jobId), getDownloadError(jobId), getDownloadStatus2(jobId), getDownloadLocalPath(jobId), getInstallPath(jobId),
+                getDownloadTemplateSize(jobId), getDownloadTemplateSize(jobId), getDownloadCheckSum(jobId));
+    }
+
+    private void sleep() {
+        try {
+            Thread.sleep(3000);
+        } catch (InterruptedException e) {
+            // ignore
+        }
+    }
+
+    private DownloadAnswer handleDownloadProgressCmd(SecondaryStorageResource resource, DownloadProgressCommand cmd) {
+        String jobId = cmd.getJobId();
+        DownloadAnswer answer;
+        DownloadJob dj = null;
+        if (jobId != null) {
+            dj = jobs.get(jobId);
+        }
+        if (dj == null) {
+            if (cmd.getRequest() == RequestType.GET_OR_RESTART) {
+                DownloadCommand dcmd = new DownloadCommand(cmd);
+                return handleDownloadCommand(resource, dcmd);
+            } else {
+                return new DownloadAnswer("Cannot find job", VMTemplateStorageResourceAssoc.Status.DOWNLOAD_ERROR.UNKNOWN);
+            }
+        }
+        TemplateDownloader td = dj.getTemplateDownloader();
+        switch (cmd.getRequest()) {
+        case GET_STATUS:
+            break;
+        case ABORT:
+            td.stopDownload();
+            sleep();
+            break;
+        case RESTART:
+            td.stopDownload();
+            sleep();
+            threadPool.execute(td);
+            break;
+        case PURGE:
+            td.stopDownload();
+            answer = new DownloadAnswer(jobId, getDownloadPct(jobId), getDownloadError(jobId), getDownloadStatus2(jobId), getDownloadLocalPath(jobId),
+                    getInstallPath(jobId), getDownloadTemplateSize(jobId), getDownloadTemplatePhysicalSize(jobId), getDownloadCheckSum(jobId));
+            jobs.remove(jobId);
+            return answer;
+        default:
+            break; // TODO
+        }
+        return new DownloadAnswer(jobId, getDownloadPct(jobId), getDownloadError(jobId), getDownloadStatus2(jobId), getDownloadLocalPath(jobId),
+                getInstallPath(jobId), getDownloadTemplateSize(jobId), getDownloadTemplatePhysicalSize(jobId), getDownloadCheckSum(jobId));
+    }
+
+    private String getInstallPath(String jobId) {
+        DownloadJob dj = jobs.get(jobId);
+        if (dj != null) {
+            return dj.getTmpltPath();
+        }
+        return null;
+    }
+
+    private String createTempDir(File rootDir, String name) throws IOException {
+
+        File f = File.createTempFile(name, "", rootDir);
+        f.delete();
+        f.mkdir();
+        _storage.setWorldReadableAndWriteable(f);
+        return f.getAbsolutePath();
+
+    }
+
+
+    private List<String> listVolumes(String rootdir) {
+        List<String> result = new ArrayList<String>();
+
+        Script script = new Script(listVolScr, s_logger);
+        script.add("-r", rootdir);
+        ZfsPathParser zpp = new ZfsPathParser(rootdir);
+        script.execute(zpp);
+        result.addAll(zpp.getPaths());
+        s_logger.info("found " + zpp.getPaths().size() + " volumes" + zpp.getPaths());
+        return result;
+    }
+
+
+
+    private List<String> listTemplates(String rootdir) {
+        List<String> result = new ArrayList<String>();
+
+        Script script = new Script(listTmpltScr, s_logger);
+        script.add("-r", rootdir);
+        ZfsPathParser zpp = new ZfsPathParser(rootdir);
+        script.execute(zpp);
+        result.addAll(zpp.getPaths());
+        s_logger.info("found " + zpp.getPaths().size() + " templates" + zpp.getPaths());
+        return result;
+    }
+
+    @Override
+    public Map<String, TemplateInfo> gatherTemplateInfo(String rootDir) {
+        Map<String, TemplateInfo> result = new HashMap<String, TemplateInfo>();
+        String templateDir = rootDir + File.separator + _templateDir;
+
+        if (! _storage.exists(templateDir)) {
+            _storage.mkdirs(templateDir);
+        }
+
+        List<String> publicTmplts = listTemplates(templateDir);
+        for (String tmplt : publicTmplts) {
+            String path = tmplt.substring(0, tmplt.lastIndexOf(File.separator));
+            TemplateLocation loc = new TemplateLocation(_storage, path);
+            try {
+                if (!loc.load()) {
+                    s_logger.warn("Post download installation was not completed for " + path);
+                    //loc.purge();
+                    _storage.cleanup(path, templateDir);
+                    continue;
+                }
+            } catch (IOException e) {
+                s_logger.warn("Unable to load template location " + path, e);
+                continue;
+            }
+
+            TemplateInfo tInfo = loc.getTemplateInfo();
+
+			if ((tInfo.getSize() == tInfo.getPhysicalSize())
+					&& (tInfo.getInstallPath().endsWith(ImageFormat.OVA.getFileExtension()))) {
+                try {
+                    Processor processor = _processors.get("VMDK Processor");
+                    VmdkProcessor vmdkProcessor = (VmdkProcessor)processor;
+					long vSize =
+							vmdkProcessor.getTemplateVirtualSize(
+									path,
+									tInfo.getInstallPath().substring(
+											tInfo.getInstallPath().lastIndexOf(File.separator) + 1));
+					tInfo.setSize(vSize);
+                    loc.updateVirtualSize(vSize);
+                    loc.save();
+                } catch (Exception e) {
+					s_logger.error("Unable to get the virtual size of the template: " + tInfo.getInstallPath()
+							+ " due to " + e.getMessage());
+                }
+            }
+
+			result.put(tInfo.getTemplateName(), tInfo);
+			s_logger.debug("Added template name: " + tInfo.getTemplateName() + ", path: " + tmplt);
+        }
+        /*
+        for (String tmplt : isoTmplts) {
+            String tmp[];
+            tmp = tmplt.split("/");
+            String tmpltName = tmp[tmp.length - 2];
+            tmplt = tmplt.substring(tmplt.lastIndexOf("iso/"));
+            TemplateInfo tInfo = new TemplateInfo(tmpltName, tmplt, false);
+            s_logger.debug("Added iso template name: " + tmpltName + ", path: " + tmplt);
+            result.put(tmpltName, tInfo);
+        }
+         */
+        return result;
+    }
+
+    @Override
+    public Map<Long, TemplateInfo> gatherVolumeInfo(String rootDir) {	
+        Map<Long, TemplateInfo> result = new HashMap<Long, TemplateInfo>();
+        String volumeDir = rootDir + File.separator + _volumeDir;
+
+        if (! _storage.exists(volumeDir)) {
+            _storage.mkdirs(volumeDir);
+        }
+
+        List<String> vols = listVolumes(volumeDir);
+        for (String vol : vols) {
+            String path = vol.substring(0, vol.lastIndexOf(File.separator));
+            TemplateLocation loc = new TemplateLocation(_storage, path);
+            try {
+                if (!loc.load()) {
+                    s_logger.warn("Post download installation was not completed for " + path);
+                    //loc.purge();
+                    _storage.cleanup(path, volumeDir);
+                    continue;
+                }
+            } catch (IOException e) {
+                s_logger.warn("Unable to load volume location " + path, e);
+                continue;
+            }
+
+            TemplateInfo vInfo = loc.getTemplateInfo();
+
+			if ((vInfo.getSize() == vInfo.getPhysicalSize())
+					&& (vInfo.getInstallPath().endsWith(ImageFormat.OVA.getFileExtension()))) {
+                try {
+                    Processor processor = _processors.get("VMDK Processor");
+                    VmdkProcessor vmdkProcessor = (VmdkProcessor)processor;
+					long vSize =
+							vmdkProcessor.getTemplateVirtualSize(
+									path,
+									vInfo.getInstallPath().substring(
+											vInfo.getInstallPath().lastIndexOf(File.separator) + 1));
+					vInfo.setSize(vSize);
+                    loc.updateVirtualSize(vSize);
+                    loc.save();
+                } catch (Exception e) {
+					s_logger.error("Unable to get the virtual size of the volume: " + vInfo.getInstallPath()
+							+ " due to " + e.getMessage());
+                }
+            }
+
+            result.put(vInfo.getId(), vInfo);
+			s_logger.debug("Added volume name: " + vInfo.getTemplateName() + ", path: " + vol);
+        }
+        return result;
+    }
+
+    private int deleteDownloadDirectories(File downloadPath, int deleted) {
+        try {
+            if (downloadPath.exists()) {
+                File[] files = downloadPath.listFiles();
+                for (int i = 0; i < files.length; i++) {
+                    if (files[i].isDirectory()) {
+                        deleteDownloadDirectories(files[i], deleted);
+                        files[i].delete();
+                        deleted++;
+                    } else {
+                        files[i].delete();
+                        deleted++;
+                    }
+                }
+            }
+        } catch (Exception ex) {
+            s_logger.info("Failed to clean up template downloads directory " + ex.toString());
+        }
+        return deleted;
+    }
+
+    public static class ZfsPathParser extends OutputInterpreter {
+        String _parent;
+        List<String> paths = new ArrayList<String>();
+
+        public ZfsPathParser(String parent) {
+            _parent = parent;
+        }
+
+        @Override
+        public String interpret(BufferedReader reader) throws IOException {
+            String line = null;
+            while ((line = reader.readLine()) != null) {
+                paths.add(line);
+            }
+            return null;
+        }
+
+        public List<String> getPaths() {
+            return paths;
+        }
+
+        @Override
+        public boolean drain() {
+            return true;
+        }
+    }
+
+    public DownloadManagerImpl() {
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    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);
+                _storage = clazz.newInstance();
+            } catch (ClassNotFoundException e) {
+                throw new ConfigurationException("Unable to instantiate " + value);
+            } catch (InstantiationException e) {
+                throw new ConfigurationException("Unable to instantiate " + value);
+            } catch (IllegalAccessException e) {
+                throw new ConfigurationException("Unable to instantiate " + value);
+            }
+        }
+        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("DownloadManager: 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";
+        }
+
+        listTmpltScr = Script.findScript(scriptsDir, "listvmtmplt.sh");
+        if (listTmpltScr == null) {
+            throw new ConfigurationException("Unable to find the listvmtmplt.sh");
+        }
+        s_logger.info("listvmtmplt.sh found in " + listTmpltScr);
+
+        createTmpltScr = Script.findScript(scriptsDir, "createtmplt.sh");
+        if (createTmpltScr == null) {
+            throw new ConfigurationException("Unable to find createtmplt.sh");
+        }
+        s_logger.info("createtmplt.sh found in " + createTmpltScr);
+
+        listVolScr = Script.findScript(scriptsDir, "listvolume.sh");
+        if (listVolScr == null) {
+            throw new ConfigurationException("Unable to find the listvolume.sh");
+        }
+        s_logger.info("listvolume.sh found in " + listVolScr);
+
+        createVolScr = Script.findScript(scriptsDir, "createvolume.sh");
+        if (createVolScr == null) {
+            throw new ConfigurationException("Unable to find createvolume.sh");
+        }
+        s_logger.info("createvolume.sh found in " + createVolScr);
+
+        _processors = new HashMap<String, Processor>();
+
+        Processor processor = new VhdProcessor();
+        processor.configure("VHD Processor", params);
+        _processors.put("VHD Processor", processor);
+
+        processor = new IsoProcessor();
+        processor.configure("ISO Processor", params);
+        _processors.put("ISO Processor", processor);
+
+        processor = new QCOW2Processor();
+        processor.configure("QCOW2 Processor", params);
+        _processors.put("QCOW2 Processor", processor);
+
+        processor = new VmdkProcessor();
+        processor.configure("VMDK Processor", params);
+        _processors.put("VMDK Processor", processor);
+
+        processor = new RawImageProcessor();
+        processor.configure("Raw Image Processor", params);
+        _processors.put("Raw Image Processor", processor);
+
+        _templateDir = (String) params.get("public.templates.root.dir");
+        if (_templateDir == null) {
+            _templateDir = TemplateConstants.DEFAULT_TMPLT_ROOT_DIR;
+        }
+        _templateDir += File.separator + TemplateConstants.DEFAULT_TMPLT_FIRST_LEVEL_DIR;
+        _volumeDir = TemplateConstants.DEFAULT_VOLUME_ROOT_DIR + File.separator;
+        // Add more processors here.
+        threadPool = Executors.newFixedThreadPool(numInstallThreads);
+        return true;
+    }
+
+    private void blockOutgoingOnPrivate() {
+        Script command = new Script("/bin/bash", s_logger);
+        String intf = "eth1";
+        command.add("-c");
+        command.add("iptables -A OUTPUT -o " + intf + " -p tcp -m state --state NEW -m tcp --dport " + "80" + " -j REJECT;" +
+                "iptables -A OUTPUT -o " + intf + " -p tcp -m state --state NEW -m tcp --dport " + "443" + " -j REJECT;");
+
+        String result = command.execute();
+        if (result != null) {
+            s_logger.warn("Error in blocking outgoing to port 80/443 err=" + result );
+            return;
+        }
+    }
+
+    @Override
+    public String getName() {
+        return _name;
+    }
+
+    @Override
+    public boolean start() {
+        return true;
+    }
+
+    @Override
+    public boolean stop() {
+        return true;
+    }
+
+    private void startAdditionalServices() {
+
+        Script command = new Script("/bin/bash", s_logger);
+        command.add("-c");
+        command.add("if [ -d /etc/apache2 ] ; then service apache2 stop; else service httpd stop; fi ");
+        String result = command.execute();
+        if (result != null) {
+            s_logger.warn("Error in stopping httpd service err=" + result );
+        }
+        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 -I INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + port + " -j ACCEPT;" +
+                "iptables -I INPUT -i " + intf + " -p tcp -m state --state NEW -m tcp --dport " + "443" + " -j ACCEPT;");
+
+        result = command.execute();
+        if (result != null) {
+            s_logger.warn("Error in opening up httpd port err=" + result );
+            return;
+        }
+
+        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;
+        }
+        command = new Script("mkdir", s_logger);
+        command.add("-p");
+        command.add("/var/www/html/copy/template");
+        result = command.execute();
+        if (result != null) {
+            s_logger.warn("Error in creating directory =" + result );
+            return;
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e7983b25/services/secondary-storage/src/org/apache/cloudstack/storage/template/UploadManager.java
----------------------------------------------------------------------
diff --git a/services/secondary-storage/src/org/apache/cloudstack/storage/template/UploadManager.java b/services/secondary-storage/src/org/apache/cloudstack/storage/template/UploadManager.java
new file mode 100755
index 0000000..14de150
--- /dev/null
+++ b/services/secondary-storage/src/org/apache/cloudstack/storage/template/UploadManager.java
@@ -0,0 +1,87 @@
+// 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.template;
+
+import org.apache.cloudstack.storage.resource.SecondaryStorageResource;
+
+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.storage.Storage.ImageFormat;
+import com.cloud.storage.Upload.Status;
+import com.cloud.storage.template.TemplateUploader;
+import com.cloud.utils.component.Manager;
+
+public interface UploadManager extends Manager {
+
+
+	/**
+	 * @param jobId job Id
+	 * @return status of the upload job
+	 */
+	public TemplateUploader.Status getUploadStatus(String jobId);
+	
+	/**
+	 * @param jobId job Id
+	 * @return status of the upload job
+	 */
+	public Status getUploadStatus2(String jobId);
+
+	/**
+	 * Get the upload percent of a upload job
+	 * @param jobId job Id
+	 * @return
+	 */
+	public int getUploadPct(String jobId);
+
+	/**
+	 * Get the upload error if any
+	 * @param jobId job Id
+	 * @return
+	 */
+	public String getUploadError(String jobId);
+
+	/**
+	 * Get the local path for the upload
+	 * @param jobId job Id
+	 * @return
+	public String getUploadLocalPath(String jobId);
+     */
+	
+	/** Handle upload commands from the management server
+	 * @param cmd cmd from server
+	 * @return answer representing status of upload.
+	 */
+	public UploadAnswer handleUploadCommand(SecondaryStorageResource resource, UploadCommand cmd);		
+    
+    public String getPublicTemplateRepo();
+
+
+	String uploadPublicTemplate(long id, String url, String name,
+			ImageFormat format, Long accountId, String descr,
+			String cksum, String installPathPrefix, String user,
+			String password, long maxTemplateSizeInBytes);
+	
+
+    CreateEntityDownloadURLAnswer handleCreateEntityURLCommand(CreateEntityDownloadURLCommand cmd);
+    
+    DeleteEntityDownloadURLAnswer handleDeleteEntityDownloadURLCommand(DeleteEntityDownloadURLCommand cmd);
+	
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e7983b25/services/secondary-storage/src/org/apache/cloudstack/storage/template/UploadManagerImpl.java
----------------------------------------------------------------------
diff --git a/services/secondary-storage/src/org/apache/cloudstack/storage/template/UploadManagerImpl.java b/services/secondary-storage/src/org/apache/cloudstack/storage/template/UploadManagerImpl.java
new file mode 100755
index 0000000..88623a9
--- /dev/null
+++ b/services/secondary-storage/src/org/apache/cloudstack/storage/template/UploadManagerImpl.java
@@ -0,0 +1,648 @@
+// 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.template;
+
+import java.io.File;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+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.cloudstack.storage.resource.SecondaryStorageResource;
+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.template.FtpTemplateUploader;
+import com.cloud.storage.template.Processor;
+import com.cloud.storage.template.TemplateUploader;
+import com.cloud.storage.template.TemplateUploader.Status;
+import com.cloud.storage.template.TemplateUploader.UploadCompleteCallback;
+import com.cloud.utils.NumbersUtil;
+import com.cloud.utils.component.ManagerBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.Script;
+
+public class UploadManagerImpl extends ManagerBase 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 List<Processor> _processors;
+    private String publicTemplateRepo;
+    private final String extractMountPoint = "/mnt/SecStorage/extractmnt";
+    private StorageLayer _storage;
+    private int installTimeoutPerGig;
+    private boolean _sslCopy;
+    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 {
+
+        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);
+                _storage = clazz.newInstance();
+            } catch (ClassNotFoundException e) {
+                throw new ConfigurationException("Unable to instantiate " + value);
+            } catch (InstantiationException e) {
+                throw new ConfigurationException("Unable to instantiate " + value);
+            } catch (IllegalAccessException e) {
+                throw new ConfigurationException("Unable to instantiate " + value);
+            }
+        }
+        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;
+        }
+
+    }
+
+    /**
+     * 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;
+    }
+}    

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e7983b25/tools/devcloud/pom.xml
----------------------------------------------------------------------
diff --git a/tools/devcloud/pom.xml b/tools/devcloud/pom.xml
index d7b82c9..d32d84b 100644
--- a/tools/devcloud/pom.xml
+++ b/tools/devcloud/pom.xml
@@ -142,5 +142,38 @@
         </plugins>
       </build>
     </profile>
+    <profile>
+      <id>quicksvr</id>
+      <activation>
+        <property>
+          <name>quicksvr</name>
+        </property>
+      </activation>
+      <build> 
+        <plugins>
+          <plugin>
+         <groupId>org.codehaus.mojo</groupId>
+        <artifactId>exec-maven-plugin</artifactId>
+        <version>1.2.1</version>
+        <executions>
+          <execution>
+            <phase>package</phase>
+            <goals>
+              <goal>exec</goal>
+            </goals>
+          </execution>
+        </executions>
+        <configuration>
+          <executable>python</executable>
+          <arguments>
+            <argument>../marvin/marvin/deployDataCenter.py</argument>
+            <argument>-i</argument>
+            <argument>quickcloud.cfg</argument>
+          </arguments>
+        </configuration>
+          </plugin>
+        </plugins>
+      </build>
+    </profile>
   </profiles>
 </project>

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e7983b25/tools/devcloud/quickcloud.cfg
----------------------------------------------------------------------
diff --git a/tools/devcloud/quickcloud.cfg b/tools/devcloud/quickcloud.cfg
new file mode 100644
index 0000000..0e1fb4f
--- /dev/null
+++ b/tools/devcloud/quickcloud.cfg
@@ -0,0 +1,120 @@
+#  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.
+#
+
+{
+    "zones": [
+        {
+            "name": "QuickCloud00", 
+            "enabled" : "True",
+            "details" : [
+                    {"key" : "enable.secstorage.vm", "value": "False"},
+                    {"key" : "enable.consoleproxy.vm", "value": "False"}
+            ],
+            "physical_networks": [
+                {
+                    "broadcastdomainrange": "Zone", 
+                    "name": "test-network", 
+                    "traffictypes": [
+                        {
+                            "typ": "Guest"
+                        }, 
+                        {
+                            "typ": "Management"
+                        } 
+                    ], 
+                    "providers": [
+                        {
+                            "broadcastdomainrange": "ZONE", 
+                            "name": "VirtualRouter"
+                        },
+                        {
+                            "broadcastdomainrange": "Pod",
+                            "name": "SecurityGroupProvider"
+                        }
+                    ]
+                }
+            ],
+            "dns2": "4.4.4.4", 
+            "dns1": "8.8.8.8",
+            "securitygroupenabled": "true", 
+            "localstorageenabled": "true",
+            "networktype": "Basic", 
+            "pods": [
+                {
+                    "endip": "192.168.56.220", 
+                    "name": "test00", 
+                    "startip": "192.168.56.200", 
+                    "guestIpRanges": [
+                        {
+                            "startip": "192.168.56.100", 
+                            "endip": "192.168.56.199", 
+                            "netmask": "255.255.255.0", 
+                            "gateway": "192.168.56.1"
+                        }
+                    ], 
+                    "netmask": "255.255.255.0", 
+                    "clusters": [
+                        {
+                            "clustername": "test000", 
+                            "hypervisor": "XenServer", 
+                            "hosts": [
+                                {
+                                    "username": "root", 
+                                    "url": "http://192.168.56.10/", 
+                                    "password": "password"
+                                }
+                            ], 
+                            "clustertype": "CloudManaged"
+                        }
+                    ], 
+                    "gateway": "192.168.56.1"
+                }
+            ], 
+            "internaldns1": "192.168.56.1",
+            "secondaryStorages": [
+                {
+                    "url": "nfs://192.168.56.10:/opt/storage/secondary"
+                }
+            ]
+        }
+    ], 
+    "logger": [
+        {
+            "name": "TestClient", 
+            "file": "testclient.log"
+        }, 
+        {
+            "name": "TestCase", 
+            "file": "testcase.log"
+        }
+    ], 
+    "mgtSvr": [
+        {
+            "mgtSvrIp": "127.0.0.1", 
+            "port": 8096
+        }
+    ],
+    "dbSvr": 
+        {
+	    "dbSvr": "127.0.0.1",
+	    "port": 3306,
+	    "user": "cloud",
+	    "passwd": "cloud",
+	    "db": "cloud"
+        }
+}

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e7983b25/tools/marvin/marvin/cloudstackConnection.py
----------------------------------------------------------------------
diff --git a/tools/marvin/marvin/cloudstackConnection.py b/tools/marvin/marvin/cloudstackConnection.py
index e8b861e..1caeef3 100644
--- a/tools/marvin/marvin/cloudstackConnection.py
+++ b/tools/marvin/marvin/cloudstackConnection.py
@@ -162,9 +162,9 @@ class cloudConnection(object):
                     else:
                         requests.pop(param)
                         i = 0
-                        for v in value:
-                            for key, val in v.iteritems():
-                                requests["%s[%d].%s"%(param,i,key)] = val
+                        for val in value:
+                            for k,v in val.iteritems():
+                                requests["%s[%d].%s"%(param,i,k)] = v
                             i = i + 1
         
         if self.logging is not None:

http://git-wip-us.apache.org/repos/asf/cloudstack/blob/e7983b25/tools/marvin/marvin/deployDataCenter.py
----------------------------------------------------------------------
diff --git a/tools/marvin/marvin/deployDataCenter.py b/tools/marvin/marvin/deployDataCenter.py
index cec920c..42bc5f9 100644
--- a/tools/marvin/marvin/deployDataCenter.py
+++ b/tools/marvin/marvin/deployDataCenter.py
@@ -270,6 +270,12 @@ class deployDataCenters():
         zoneCmd.allocationstate = allocation_state
         return self.apiClient.updateZone(zoneCmd)
 
+    def updateZoneDetails(self, zoneid, details):
+        zoneCmd = updateZone.updateZoneCmd()
+        zoneCmd.id = zoneid
+        zoneCmd.details = details
+        return self.apiClient.updateZone(zoneCmd)
+
     def createZones(self, zones):
         for zone in zones:
             createzone = createZone.createZoneCmd()
@@ -320,7 +326,15 @@ class deployDataCenters():
                                         zoneId)
 
             self.createSecondaryStorages(zone.secondaryStorages, zoneId)
-            self.enableZone(zoneId, "Enabled")
+            
+            enabled = getattr(zone, 'enabled', 'True')
+            if enabled == 'True' or enabled == 'None':
+                self.enableZone(zoneId, "Enabled")
+            details = getattr(zone, 'details')
+            if details is not None:
+                det = [d.__dict__ for d in details]
+                self.updateZoneDetails(zoneId, det)
+
         return
     
     def isEipElbZone(self, zone):