[ 
https://issues.apache.org/jira/browse/CLOUDSTACK-10290?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16480738#comment-16480738
 ] 

ASF GitHub Bot commented on CLOUDSTACK-10290:
---------------------------------------------

DaanHoogland closed pull request #2614: WIP perform config drive creation on 
primary storage (CLOUDSTACK-10290)
URL: https://github.com/apache/cloudstack/pull/2614
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/api/src/com/cloud/network/NetworkModel.java 
b/api/src/com/cloud/network/NetworkModel.java
index 35a6a63a992..943efac5ba2 100644
--- a/api/src/com/cloud/network/NetworkModel.java
+++ b/api/src/com/cloud/network/NetworkModel.java
@@ -73,7 +73,7 @@
             AVAILABILITY_ZONE_FILE, "availability_zone",
             LOCAL_HOSTNAME_FILE, "hostname",
             VM_ID_FILE, "uuid",
-            INSTANCE_ID_FILE, "name"
+            PUBLIC_HOSTNAME_FILE, "name"
     );
 
     static final ConfigKey<Integer> MACIdentifier = new 
ConfigKey<Integer>("Advanced",Integer.class, "mac.identifier", "0",
diff --git a/config-drive/pom.xml b/config-drive/pom.xml
new file mode 100644
index 00000000000..ea8c5b6710a
--- /dev/null
+++ b/config-drive/pom.xml
@@ -0,0 +1,71 @@
+<!-- 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. -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"; 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+  <artifactId>cloud-config-drive</artifactId>
+  <name>Apache CloudStack Config-Drive Component</name>
+  <parent>
+    <groupId>org.apache.cloudstack</groupId>
+    <artifactId>cloudstack</artifactId>
+    <version>4.11.1.0-SNAPSHOT</version>
+    <relativePath>../pom.xml</relativePath>
+  </parent>
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-core</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>log4j</groupId>
+      <artifactId>log4j</artifactId>
+    </dependency>
+  </dependencies>
+
+  <build>
+    <sourceDirectory>src/main/java</sourceDirectory>
+    <testSourceDirectory>src/test/java</testSourceDirectory>
+    <outputDirectory>target/classes</outputDirectory>
+    <testOutputDirectory>target/test-classes</testOutputDirectory>
+    <resources>
+      <resource>
+        <directory>src/main/resources</directory>
+      </resource>
+    </resources>
+    <testResources>
+      <testResource>
+        <directory>src/test/resources</directory>
+      </testResource>
+    </testResources>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-jar-plugin</artifactId>
+        <executions>
+          <execution>
+            <goals>
+              <goal>test-jar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-surefire-plugin</artifactId>
+      </plugin>
+    </plugins>
+  </build>
+</project>
diff --git 
a/config-drive/src/main/java/org/apache/cloudstack/storage/ConfigDriveFactory.java
 
b/config-drive/src/main/java/org/apache/cloudstack/storage/ConfigDriveFactory.java
new file mode 100644
index 00000000000..0d8cfa3bf2a
--- /dev/null
+++ 
b/config-drive/src/main/java/org/apache/cloudstack/storage/ConfigDriveFactory.java
@@ -0,0 +1,425 @@
+// 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;
+
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.HandleConfigDriveIsoCommand;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.agent.api.to.NfsTO;
+import com.cloud.network.NetworkModel;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.Script;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+import com.google.common.io.Files;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import javax.naming.ConfigurationException;
+
+import static com.cloud.network.NetworkModel.CONFIGDATA_CONTENT;
+import static com.cloud.network.NetworkModel.CONFIGDATA_DIR;
+import static com.cloud.network.NetworkModel.CONFIGDATA_FILE;
+
+import static com.cloud.network.NetworkModel.METATDATA_DIR;
+import static com.cloud.network.NetworkModel.PASSWORD_DIR;
+import static com.cloud.network.NetworkModel.PASSWORD_FILE;
+import static com.cloud.network.NetworkModel.PUBLIC_KEYS_FILE;
+import static com.cloud.network.NetworkModel.USERDATA_DIR;
+import static com.cloud.network.NetworkModel.USERDATA_FILE;
+
+public class ConfigDriveFactory {
+    public static final Logger s_logger = 
Logger.getLogger(ConfigDriveFactory.class);
+
+    private static final String CLOUD_STACK_CONFIG_DRIVE_NAME = "/cloudstack/";
+    private static final String OPEN_STACK_CONFIG_DRIVE_NAME = 
"/openstack/latest/";
+    public static final String FAILED_TO_CREATE_FOLDER = "Failed to create 
folder ";
+    public static final String FAILED_TO_CREATE_FILE = "Failed to create file 
";
+    public static final String EMPTY_SET = "{}";
+    private final Integer nfsVersion;
+    protected boolean inSystemVM = false;
+
+    private StorageAttacher attacher;
+
+    private static final Map<String, String> updatableConfigData = 
Maps.newHashMap();
+    static {
+
+        updatableConfigData.put(PUBLIC_KEYS_FILE, METATDATA_DIR);
+        updatableConfigData.put(USERDATA_FILE, USERDATA_DIR);
+        updatableConfigData.put(PASSWORD_FILE, PASSWORD_DIR);
+    }
+
+
+    private int timeout;
+
+    public ConfigDriveFactory(Integer pNfsVersion, StorageAttacher pAttacher) {
+        nfsVersion = pNfsVersion;
+        attacher = pAttacher;
+    }
+
+    public int getTimeout() {
+        return timeout;
+    }
+
+    public void setTimeout(int timeout) {
+        this.timeout = timeout;
+    }
+
+    private String linkUserData(String tempDirName) {
+        if(s_logger.isDebugEnabled()) {
+            s_logger.debug("Hard link the user_data.txt file with the 
user_data file in the OpenStack directory in " + tempDirName);
+        }
+        String userDataFilePath = tempDirName + CLOUD_STACK_CONFIG_DRIVE_NAME 
+ "userdata/user_data.txt";
+        if ((new File(userDataFilePath).exists())) {
+            Script hardLink = new Script(!inSystemVM, "ln", timeout, s_logger);
+            hardLink.add(userDataFilePath);
+            hardLink.add(tempDirName + OPEN_STACK_CONFIG_DRIVE_NAME + 
"user_data");
+            if(s_logger.isDebugEnabled()) {
+                s_logger.debug("execute command: " + hardLink.toString());
+            }
+            return hardLink.execute();
+        }
+        return null;
+    }
+
+    public Answer executeRequest(HandleConfigDriveIsoCommand cmd) {
+
+        if(s_logger.isDebugEnabled()) {
+            s_logger.debug(String.format("VMdata %s, attach = %s", 
cmd.getVmData(), cmd.isCreate()));
+        }
+        if (cmd.isCreate()) {
+            if(cmd.getVmData() == null) return new Answer(cmd, false, "No 
Vmdata available");
+            String nfsMountPoint = 
attacher.getRootDir(cmd.getDestStore().getUrl(), nfsVersion);
+            File isoFile = new File(nfsMountPoint, cmd.getIsoFile());
+            if(isoFile.exists()) {
+                if (!cmd.isUpdate()) {
+                    return new Answer(cmd, true, "ISO already available");
+                } else {
+                    // Find out if we have to recover the password/ssh-key 
from the already available ISO.
+                    try {
+                        List<String[]> recoveredVmData = 
recoverVmData(isoFile);
+                        for (String[] vmDataEntry : cmd.getVmData()) {
+                            if 
(updatableConfigData.containsKey(vmDataEntry[CONFIGDATA_FILE])
+                                    && 
updatableConfigData.get(vmDataEntry[CONFIGDATA_FILE]).equals(vmDataEntry[CONFIGDATA_DIR]))
 {
+                                updateVmData(recoveredVmData, vmDataEntry);
+                            }
+                        }
+                        cmd.setVmData(recoveredVmData);
+                    } catch (IOException e) {
+                        return new Answer(cmd, e);
+                    }
+                }
+            }
+            return createConfigDriveIsoForVM(cmd);
+        } else {
+            DataStoreTO dstore = cmd.getDestStore();
+            if (dstore instanceof NfsTO) {
+                NfsTO nfs = (NfsTO) dstore;
+                String relativeTemplatePath = new 
File(cmd.getIsoFile()).getParent();
+                String nfsMountPoint = attacher.getRootDir(nfs.getUrl(), 
nfsVersion);
+                File tmpltPath = new File(nfsMountPoint, relativeTemplatePath);
+                try {
+                    FileUtils.deleteDirectory(tmpltPath);
+                } catch (IOException e) {
+                    return new Answer(cmd, e);
+                }
+                return new Answer(cmd);
+            } else {
+                return new Answer(cmd, false, "Not implemented yet");
+            }
+        }
+    }
+
+    public Answer createConfigDriveIsoForVM(HandleConfigDriveIsoCommand cmd) {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("handling configdrive: " + 
cmd.getConfigDriveLabel());
+        }
+        //create folder for the VM
+        if (cmd.getVmData() != null) {
+
+            Path tempDir = null;
+            String tempDirName = null;
+            try {
+                tempDir = 
java.nio.file.Files.createTempDirectory("ConfigDrive");
+                tempDirName = tempDir.toString();
+
+                //create OpenStack files
+                //create folder with empty files
+                File openStackFolder = new File(tempDirName + 
OPEN_STACK_CONFIG_DRIVE_NAME);
+                if (openStackFolder.exists() || openStackFolder.mkdirs()) {
+                    File vendorDataFile = new 
File(openStackFolder,"vendor_data.json");
+                    WriteFileProcedure writeFileProcedure = new 
WriteFileProcedure(cmd, vendorDataFile, EMPTY_SET);
+                    if (writeFileProcedure.invoke())
+                        return new Answer(cmd, writeFileProcedure.getEx());
+                    File networkDataFile = new File(openStackFolder, 
"network_data.json");
+                    writeFileProcedure = new WriteFileProcedure(cmd, 
networkDataFile, EMPTY_SET);
+                    if (writeFileProcedure.invoke())
+                        return new Answer(cmd, writeFileProcedure.getEx());
+                } else {
+                    s_logger.error(FAILED_TO_CREATE_FOLDER + openStackFolder);
+                    return new Answer(cmd, false, FAILED_TO_CREATE_FOLDER + 
openStackFolder);
+                }
+
+                JsonObject metaData = new JsonObject();
+                for (String[] item : cmd.getVmData()) {
+                    String dataType = item[CONFIGDATA_DIR];
+                    String fileName = item[CONFIGDATA_FILE];
+                    String content = item[CONFIGDATA_CONTENT];
+                    s_logger.debug(String.format("[createConfigDriveIsoForVM] 
dataType=%s, filename=%s, content=%s",
+                            dataType, fileName, 
(fileName.equals(PASSWORD_FILE)?"********":content)));
+
+                    // create file with content in folder
+                    if (dataType != null && !dataType.isEmpty()) {
+                        //create folder
+                        File typeFolder = new File(tempDirName + 
CLOUD_STACK_CONFIG_DRIVE_NAME + dataType);
+                        if (typeFolder.exists() || typeFolder.mkdirs()) {
+                            if (StringUtils.isNotEmpty(content)) {
+                                File file = new File(typeFolder, fileName + 
".txt");
+                                WriteFileProcedure writeFileProcedure = new 
WriteFileProcedure(cmd, file, content);
+                                if (writeFileProcedure.invoke())
+                                    return new Answer(cmd, 
writeFileProcedure.getEx());
+                            }
+                        } else {
+                            s_logger.error(FAILED_TO_CREATE_FOLDER + 
typeFolder);
+                            return new Answer(cmd, false, 
FAILED_TO_CREATE_FOLDER + typeFolder);
+                        }
+
+                        //now write the file to the OpenStack directory
+                        metaData = constructOpenStackMetaData(metaData, 
dataType, fileName, content);
+                    }
+                }
+
+                File metaDataFile = new File(openStackFolder, 
"meta_data.json");
+                WriteFileProcedure writeFileProcedure = new 
WriteFileProcedure(cmd, metaDataFile, metaData.toString());
+                if(writeFileProcedure.invoke())
+                    return new Answer(cmd,writeFileProcedure.getEx());
+
+                String linkResult = linkUserData(tempDirName);
+                if (linkResult != null) {
+                    String errMsg = "Unable to create user_data link due to " 
+ linkResult;
+                    s_logger.warn(errMsg);
+                    return new Answer(cmd, false, errMsg);
+                }
+
+                File tmpIsoStore = new File(tempDirName, new 
File(cmd.getIsoFile()).getName());
+                Script command = new Script(!inSystemVM, 
"/usr/bin/genisoimage", timeout, s_logger);
+                command.add("-o", tmpIsoStore.getAbsolutePath());
+                command.add("-ldots");
+                command.add("-allow-lowercase");
+                command.add("-allow-multidot");
+                command.add("-cache-inodes"); // Enable caching inode and 
device numbers to find hard links to files.
+                command.add("-l");
+                command.add("-quiet");
+                command.add("-J");
+                command.add("-r");
+                command.add("-V", cmd.getConfigDriveLabel());
+                command.add(tempDirName);
+                s_logger.debug("execute command: " + command.toString());
+                String result = command.execute();
+                if (result != null) {
+                    String errMsg = "Unable to create iso file: " + 
cmd.getIsoFile() + " due to " + result;
+                    s_logger.warn(errMsg);
+                    return new Answer(cmd, false, errMsg);
+                }
+                copyLocalToNfs(tmpIsoStore, new File(cmd.getIsoFile()), 
cmd.getDestStore());
+
+            } catch (IOException e) {
+                return new Answer(cmd, e);
+            } catch (ConfigurationException e) {
+                s_logger.warn("SecondStorageException ", e);
+                return new Answer(cmd, e);
+            } finally {
+                try {
+                    FileUtils.deleteDirectory(tempDir.toFile());
+                } catch (IOException ioe) {
+                    s_logger.warn("Failed to delete ConfigDrive temporary 
directory: " + tempDirName, ioe);
+                }
+            }
+        }
+        return new Answer(cmd);
+    }
+
+    private List<String[]> recoverVmData(File isoFile) throws IOException {
+        String tempDirName = null;
+        List<String[]> recoveredVmData = Lists.newArrayList();
+        boolean mounted = false;
+        try {
+            Path tempDir = 
java.nio.file.Files.createTempDirectory("ConfigDrive");
+            tempDirName = tempDir.toString();
+
+            // Unpack the current config drive file
+            Script command = new Script(!inSystemVM, "mount", timeout, 
s_logger);
+            command.add("-o", "loop");
+            command.add(isoFile.getAbsolutePath());
+            command.add(tempDirName);
+            String result = command.execute();
+
+            if (result != null) {
+                String errMsg = "Unable to mount " + isoFile.getAbsolutePath() 
+ " at " + tempDirName + " due to " + result;
+                s_logger.error(errMsg);
+                throw new IOException(errMsg);
+            }
+            mounted = true;
+
+
+            // Scan directory structure
+            for (File configDirectory: (new File(tempDirName, 
"cloudstack")).listFiles()){
+                for (File configFile: configDirectory.listFiles()) {
+                    recoveredVmData.add(new String[]{configDirectory.getName(),
+                            
Files.getNameWithoutExtension(configFile.getName()),
+                            Files.readFirstLine(configFile, 
Charset.defaultCharset())});
+                }
+            }
+
+        } finally {
+            if (mounted) {
+                Script command = new Script(!inSystemVM, "umount", timeout, 
s_logger);
+                command.add(tempDirName);
+                String result = command.execute();
+                if (result != null) {
+                    s_logger.warn("Unable to umount " + tempDirName + " due to 
" + result);
+                }
+            }
+            try {
+                FileUtils.deleteDirectory(new File(tempDirName));
+            } catch (IOException ioe) {
+                s_logger.warn("Failed to delete ConfigDrive temporary 
directory: " + tempDirName, ioe);
+            }
+        }
+        return  recoveredVmData;
+    }
+    JsonObject constructOpenStackMetaData(JsonObject metaData, String 
dataType, String fileName, String content) {
+        if(s_logger.isDebugEnabled()) {
+            s_logger.debug("adding " + content + " to " + fileName);
+        }
+        if (dataType.equals(NetworkModel.METATDATA_DIR) &&  
StringUtils.isNotEmpty(content)) {
+            //keys are a special case in OpenStack format
+            if (NetworkModel.PUBLIC_KEYS_FILE.equals(fileName)) {
+                String[] keyArray = content.replace("\\n", "").split(" ");
+                String keyName = "key";
+                if (keyArray.length > 3 && 
StringUtils.isNotEmpty(keyArray[2])){
+                    keyName = keyArray[2];
+                }
+
+                JsonObject keyLegacy = new JsonObject();
+                keyLegacy.addProperty("type", "ssh");
+                keyLegacy.addProperty("data", content.replace("\\n", ""));
+                keyLegacy.addProperty("name", keyName);
+                metaData.add("keys", arrayOf(keyLegacy));
+
+                JsonObject key = new JsonObject();
+                key.addProperty(keyName, content);
+                metaData.add("public_keys", key);
+            } else if (NetworkModel.openStackFileMapping.get(fileName) != 
null) {
+                
metaData.addProperty(NetworkModel.openStackFileMapping.get(fileName), content);
+            }
+        }
+        return metaData;
+    }
+    private static JsonArray arrayOf(JsonElement... elements) {
+        JsonArray array = new JsonArray();
+        for (JsonElement element : elements) {
+            array.add(element);
+        }
+        return array;
+    }
+
+    private void updateVmData(List<String[]> recoveredVmData, String[] 
vmDataEntry) {
+        for (String[] recoveredEntry : recoveredVmData) {
+            if 
(recoveredEntry[CONFIGDATA_DIR].equals(vmDataEntry[CONFIGDATA_DIR])
+                    && 
recoveredEntry[CONFIGDATA_FILE].equals(vmDataEntry[CONFIGDATA_FILE])) {
+                recoveredEntry[CONFIGDATA_CONTENT] = 
vmDataEntry[CONFIGDATA_CONTENT];
+                return;
+            }
+        }
+        recoveredVmData.add(vmDataEntry);
+    }
+
+    protected void copyLocalToNfs(File localFile, File isoFile, DataStoreTO 
destData) throws ConfigurationException, IOException {
+        if(s_logger.isDebugEnabled()) {
+            s_logger.debug("copyfest: from " + localFile + " to " + isoFile + 
" on " + destData.getUrl());
+        }
+        String scriptsDir = "scripts/storage/secondary";
+        String 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);
+
+        int installTimeoutPerGig = 180 * 60 * 1000;
+        int imgSizeGigs = (int) Math.ceil(localFile.length() * 1.0d / (1024 * 
1024 * 1024));
+        imgSizeGigs++; // add one just in case
+        long timeout = imgSizeGigs * installTimeoutPerGig;
+
+        Script scr = new Script(createVolScr, timeout, s_logger);
+        scr.add("-s", Integer.toString(imgSizeGigs));
+        scr.add("-n", isoFile.getName());
+        scr.add("-t", attacher.getRootDir(destData.getUrl(), nfsVersion) + "/" 
+ isoFile.getParent());
+        scr.add("-f", localFile.getAbsolutePath());
+        scr.add("-d", "configDrive");
+        if(s_logger.isDebugEnabled()) {
+            s_logger.debug("executing " + scr.toString());
+        }
+        String result;
+        result = scr.execute();
+
+        if (result != null) {
+            // script execution failure
+            throw new CloudRuntimeException("Failed to run script " + 
createVolScr);
+        }
+    }
+
+    private class WriteFileProcedure {
+        private HandleConfigDriveIsoCommand cmd;
+        private File vendorDataFile;
+        private String content;
+        private IOException ex;
+
+        public WriteFileProcedure(HandleConfigDriveIsoCommand cmd, File 
vendorDataFile, String content) {
+            this.cmd = cmd;
+            this.vendorDataFile = vendorDataFile;
+            this.content = content;
+        }
+
+        public IOException getEx() {
+            return ex;
+        }
+
+        public boolean invoke() {
+            try (FileWriter fw = new FileWriter(vendorDataFile); 
BufferedWriter bw = new BufferedWriter(fw)) {
+                bw.write(content);
+            } catch (IOException ex) 
{ConfigDriveFactory.WriteFileProcedure.this.ex = ex;
+                s_logger.error(FAILED_TO_CREATE_FILE, ex);
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git 
a/config-drive/src/main/java/org/apache/cloudstack/storage/StorageAttacher.java 
b/config-drive/src/main/java/org/apache/cloudstack/storage/StorageAttacher.java
new file mode 100644
index 00000000000..8809a52087e
--- /dev/null
+++ 
b/config-drive/src/main/java/org/apache/cloudstack/storage/StorageAttacher.java
@@ -0,0 +1,27 @@
+// 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;
+
+import java.net.URI;
+
+public interface StorageAttacher {
+    String getRootDir(String url, Integer nfsVersion);
+
+    void umount(String localRootPath, URI uri);
+
+    void mount(String localRootPath, String remoteDevice, URI uri, Integer 
nfsVersion);
+}
diff --git 
a/config-drive/src/test/java/org/apache/cloudstack/storage/ConfigDriveFactoryTest.java
 
b/config-drive/src/test/java/org/apache/cloudstack/storage/ConfigDriveFactoryTest.java
new file mode 100644
index 00000000000..d238b654175
--- /dev/null
+++ 
b/config-drive/src/test/java/org/apache/cloudstack/storage/ConfigDriveFactoryTest.java
@@ -0,0 +1,66 @@
+// 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;
+
+import com.cloud.agent.api.HandleConfigDriveIsoCommand;
+import com.cloud.agent.api.to.DataStoreTO;
+import com.cloud.storage.DataStoreRole;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.util.List;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConfigDriveFactoryTest {
+    private final static String CONFIGDRIVEFILENAME = "configdrive.iso";
+    private final static String CONFIGDRIVEDIR = "ConfigDrive";
+
+    @Mock
+    StorageAttacher storageAttacher;
+
+    @Test
+    public void executeRequest() {
+        List<String[]> vmData = null;
+        String label = null;
+        DataStoreTO destStore = new DataStoreTO() {
+            @Override public DataStoreRole getRole() {
+                return DataStoreRole.Primary;
+            }
+
+            @Override public String getUuid() {
+                return "0123456789abcdef";
+            }
+
+            @Override public String getUrl() {
+                return "";
+            }
+
+            @Override public String getPathSeparator() {
+                return "/";
+            }
+        };
+        String isoFile = "/" + CONFIGDRIVEDIR + "/" + "bla" + "/" + 
CONFIGDRIVEFILENAME;
+        boolean create = false;
+        boolean update = false;
+        HandleConfigDriveIsoCommand cmd = new HandleConfigDriveIsoCommand(null,
+                null, destStore, isoFile, false, false);
+        ConfigDriveFactory configDriveFactory = new 
ConfigDriveFactory(1,storageAttacher);
+        configDriveFactory.executeRequest(cmd);
+    }
+}
\ No newline at end of file
diff --git a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java 
b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java
index d6d87d48c05..8f61f579b2f 100644
--- a/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java
+++ b/core/src/com/cloud/agent/api/HandleConfigDriveIsoCommand.java
@@ -64,6 +64,10 @@ public String getConfigDriveLabel() {
         return configDriveLabel;
     }
 
+    /**
+     *
+     * @return a PrimaryDataStore when executed in a HostAgent else a SecStore
+     */
     public DataStoreTO getDestStore() {
         return destStore;
     }
diff --git a/engine/storage/pom.xml b/engine/storage/pom.xml
index af6382c535d..7a321825bf7 100644
--- a/engine/storage/pom.xml
+++ b/engine/storage/pom.xml
@@ -30,13 +30,6 @@
       <artifactId>cloud-core</artifactId>
       <version>${project.version}</version>
     </dependency>
-    <!-- 
-    <dependency>
-      <groupId>org.apache.cloudstack</groupId>
-      <artifactId>cloud-server</artifactId>
-      <version>${project.version}</version>
-    </dependency>
-    -->
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-engine-components-api</artifactId>
diff --git a/packaging/centos63/cloud.spec b/packaging/centos63/cloud.spec
index 87d2c1ec578..9655ba72d72 100644
--- a/packaging/centos63/cloud.spec
+++ b/packaging/centos63/cloud.spec
@@ -130,6 +130,7 @@ Requires: perl
 Requires: libvirt-python
 Requires: qemu-img
 Requires: qemu-kvm
+Requires: genisoimage
 Provides: cloud-agent
 Obsoletes: cloud-agent < 4.1.0
 Obsoletes: cloud-agent-libs < 4.1.0
diff --git a/packaging/centos7/cloud.spec b/packaging/centos7/cloud.spec
index 525421c0108..0a8292e1012 100644
--- a/packaging/centos7/cloud.spec
+++ b/packaging/centos7/cloud.spec
@@ -111,6 +111,7 @@ Requires: perl
 Requires: libvirt-python
 Requires: qemu-img
 Requires: qemu-kvm
+Requires: genisoimage
 Provides: cloud-agent
 Group: System Environment/Libraries
 %description agent
diff --git a/plugins/hypervisors/kvm/pom.xml b/plugins/hypervisors/kvm/pom.xml
index d90e79adfc9..0433f4b175e 100644
--- a/plugins/hypervisors/kvm/pom.xml
+++ b/plugins/hypervisors/kvm/pom.xml
@@ -39,6 +39,11 @@
       <artifactId>cloud-plugin-network-ovs</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-config-drive</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>com.ceph</groupId>
       <artifactId>rados</artifactId>
diff --git 
a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
 
b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index fc5e5395b87..5feb6db4115 100644
--- 
a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ 
b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -26,6 +26,7 @@
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.UnknownHostException;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -47,6 +48,8 @@
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
+import org.apache.cloudstack.storage.ConfigDriveFactory;
+import org.apache.cloudstack.storage.StorageAttacher;
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
 import org.apache.cloudstack.utils.hypervisor.HypervisorUtils;
@@ -192,7 +195,7 @@
  *         private mac addresses for domrs | mac address | start + 126 || ||
  *         pool | the parent of the storage pool hierarchy * }
  **/
-public class LibvirtComputingResource extends ServerResourceBase implements 
ServerResource, VirtualRouterDeployer {
+public class LibvirtComputingResource extends ServerResourceBase implements 
ServerResource, VirtualRouterDeployer, StorageAttacher {
     private static final Logger s_logger = 
Logger.getLogger(LibvirtComputingResource.class);
 
     private String _modifyVlanPath;
@@ -314,6 +317,12 @@
 
     private final LibvirtUtilitiesHelper libvirtUtilitiesHelper = new 
LibvirtUtilitiesHelper();
 
+    ConfigDriveFactory configDriveFactory;
+
+    public ConfigDriveFactory getConfigDriveFactory() {
+        return configDriveFactory;
+    }
+
     @Override
     public ExecutionResult executeInVR(final String routerIp, final String 
script, final String args) {
         return executeInVR(routerIp, script, args, _timeout);
@@ -481,6 +490,136 @@ public StorageSubsystemCommandHandler getStorageHandler() 
{
         return storageHandler;
     }
 
+    @Override
+    synchronized public String getRootDir(String url, Integer nfsVersion) {
+        try {
+            URI uri = new URI(url);
+            String dir = mountUri(uri, nfsVersion);
+            return _mountPoint + "/" + dir;
+        } catch (Exception e) {
+            String msg = "GetRootDir for " + url + " failed due to " + 
e.toString();
+            s_logger.error(msg, e);
+            throw new CloudRuntimeException(msg);
+        }
+    }
+
+    protected String mountUri(URI uri, Integer nfsVersion) throws 
UnknownHostException {
+        String uriHostIp = getUriHostIp(uri);
+        String nfsPath = uriHostIp + ":" + uri.getPath();
+
+        // Single means of calculating mount directory regardless of scheme
+        String dir = 
UUID.nameUUIDFromBytes(nfsPath.getBytes(com.cloud.utils.StringUtils.getPreferredCharset())).toString();
+        String localRootPath = _mountPoint + "/" + dir;
+
+        // remote device syntax varies by scheme.
+        String remoteDevice = nfsPath;
+        s_logger.debug("Mounting device with nfs-style path of " + 
remoteDevice);
+
+        mount(localRootPath, remoteDevice, uri, nfsVersion);
+        return dir;
+    }
+
+    protected String getUriHostIp(URI uri) throws UnknownHostException {
+        String nfsHost = uri.getHost();
+        InetAddress nfsHostAddr = InetAddress.getByName(nfsHost);
+        String nfsHostIp = nfsHostAddr.getHostAddress();
+        s_logger.info("Determined host " + nfsHost + " corresponds to IP " + 
nfsHostIp);
+        return nfsHostIp;
+    }
+
+    @Override public void umount(String localRootPath, URI uri) {
+        ensureLocalRootPathExists(localRootPath, uri);
+
+        if (!mountExists(localRootPath, uri)) {
+            return;
+        }
+
+        Script command = new Script("mount", _timeout, s_logger);
+        command.add(localRootPath);
+        String result = command.execute();
+        if (result != null) {
+            // Fedora Core 12 errors out with any -o option executed from java
+            String errMsg = "Unable to umount " + localRootPath + " due to " + 
result;
+            s_logger.error(errMsg);
+            File file = new File(localRootPath);
+            if (file.exists()) {
+                file.delete();
+            }
+            throw new CloudRuntimeException(errMsg);
+        }
+        s_logger.debug("Successfully umounted " + localRootPath);
+    }
+
+    protected boolean mountExists(String localRootPath, URI uri) {
+        Script script = null;
+        script = new Script( "mount", _timeout, s_logger);
+
+        List<String> res = new ArrayList<String>();
+        AllLinesParser parser = new AllLinesParser();
+        script.execute(parser);
+        if(parser.getLines().contains(localRootPath)) {
+            s_logger.debug("Some device already mounted at " + localRootPath + 
", no need to mount " + uri.toString());
+            return true;
+        }
+        return false;
+    }
+
+    protected void ensureLocalRootPathExists(String localRootPath, URI uri) {
+        s_logger.debug("making available " + localRootPath + " on " + 
uri.toString());
+        File file = new File(localRootPath);
+        s_logger.debug("local folder for mount will be " + file.getPath());
+        if (!file.exists()) {
+            s_logger.debug("create mount point: " + file.getPath());
+            _storage.mkdir(file.getPath());
+
+            // Need to check after mkdir to allow O/S to complete operation
+            if (!file.exists()) {
+                String errMsg = "Unable to create local folder for: " + 
localRootPath + " in order to mount " + uri.toString();
+                s_logger.error(errMsg);
+                throw new CloudRuntimeException(errMsg);
+            }
+        }
+    }
+
+    @Override
+    public void mount(String localRootPath, String remoteDevice, URI uri, 
Integer nfsVersion) {
+        s_logger.debug("mount " + uri.toString() + " on " + localRootPath + 
((nfsVersion != null) ? " nfsVersion=" + nfsVersion : ""));
+        ensureLocalRootPathExists(localRootPath, uri);
+
+        if (mountExists(localRootPath, uri)) {
+            return;
+        }
+
+        attemptMount(localRootPath, remoteDevice, uri, nfsVersion);
+    }
+
+    protected void attemptMount(String localRootPath, String remoteDevice, URI 
uri, Integer nfsVersion) {
+        String result;
+        s_logger.debug("Make cmdline call to mount " + remoteDevice + " at " + 
localRootPath + " based on uri " + uri + ((nfsVersion != null) ? " nfsVersion=" 
+ nfsVersion : ""));
+        Script command = new Script("mount", _timeout, s_logger);
+
+        String scheme = uri.getScheme().toLowerCase();
+        if("networkfilesystem".equals(scheme)) {
+            scheme = "nfs";
+        }
+        command.add("-t", scheme);
+
+        command.add(remoteDevice);
+        command.add(localRootPath);
+        result = command.execute();
+        if (result != null) {
+            // Fedora Core 12 errors out with any -o option executed from java
+            String errMsg = "Unable to mount " + remoteDevice + " at " + 
localRootPath + " due to " + result;
+            s_logger.error(errMsg);
+            File file = new File(localRootPath);
+            if (file.exists()) {
+                file.delete();
+            }
+            throw new CloudRuntimeException(errMsg);
+        }
+        s_logger.debug("Successfully mounted " + remoteDevice + " at " + 
localRootPath);
+    }
+
     private static final class KeyValueInterpreter extends OutputInterpreter {
         private final Map<String, String> map = new HashMap<String, String>();
 
@@ -987,18 +1126,6 @@ public boolean configure(final String name, final 
Map<String, Object> params) th
 
         configureVifDrivers(params);
 
-        /*
-        switch (_bridgeType) {
-        case OPENVSWITCH:
-            getOvsPifs();
-            break;
-        case NATIVE:
-        default:
-            getPifs();
-            break;
-        }
-        */
-
         if (_pifs.get("private") == null) {
             s_logger.debug("Failed to get private nic name");
             throw new ConfigurationException("Failed to get private nic name");
@@ -1061,6 +1188,8 @@ public boolean configure(final String name, final 
Map<String, Object> params) th
         storageProcessor.configure(name, params);
         storageHandler = new 
StorageSubsystemCommandHandlerBase(storageProcessor);
 
+        configDriveFactory = new ConfigDriveFactory(3, this);
+
         return true;
     }
 
diff --git 
a/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java
new file mode 100644
index 00000000000..ce7d7c7ec00
--- /dev/null
+++ 
b/plugins/hypervisors/kvm/src/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtHandleConfigDriveCommandWrapper.java
@@ -0,0 +1,34 @@
+//
+//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 com.cloud.hypervisor.kvm.resource.wrapper;
+
+import com.cloud.agent.api.Answer;
+import com.cloud.agent.api.HandleConfigDriveIsoCommand;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.resource.CommandWrapper;
+import com.cloud.resource.ResourceWrapper;
+
+@ResourceWrapper(handles = HandleConfigDriveIsoCommand.class)
+public class LibvirtHandleConfigDriveCommandWrapper  extends 
CommandWrapper<HandleConfigDriveIsoCommand, Answer, LibvirtComputingResource>
+{
+    @Override public Answer execute(HandleConfigDriveIsoCommand command, 
LibvirtComputingResource serverResource) {
+        return serverResource.getConfigDriveFactory().executeRequest(command);
+    }
+}
diff --git a/pom.xml b/pom.xml
index 6ab0afcd63a..6b389324364 100644
--- a/pom.xml
+++ b/pom.xml
@@ -199,6 +199,7 @@
     <module>tools/checkstyle</module>
     <module>api</module>
     <module>agent</module>
+    <module>config-drive</module>
     <module>core</module>
     <module>server</module>
     <module>usage</module>
diff --git a/server/pom.xml b/server/pom.xml
index 534b1a34c70..76f06d445b6 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -32,6 +32,11 @@
       <artifactId>cloud-core</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-config-drive</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-framework-cluster</artifactId>
diff --git 
a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java 
b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java
index cc1df935388..482b57061d1 100644
--- a/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java
+++ b/server/src/com/cloud/network/element/ConfigDriveNetworkElement.java
@@ -22,6 +22,15 @@
 import java.util.Set;
 import javax.inject.Inject;
 
+import com.cloud.exception.CloudException;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.storage.DataStoreRole;
+import com.cloud.storage.VolumeVO;
+import com.cloud.storage.dao.VolumeDao;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
 import org.apache.log4j.Logger;
 
 import 
org.apache.cloudstack.engine.orchestration.service.VolumeOrchestrationService;
@@ -78,7 +87,7 @@
 import com.cloud.vm.dao.UserVmDetailsDao;
 
 public class ConfigDriveNetworkElement extends AdapterBase implements 
NetworkElement, UserDataServiceProvider,
-        StateListener<VirtualMachine.State, VirtualMachine.Event, 
VirtualMachine>, NetworkMigrationResponder {
+        StateListener<VirtualMachine.State, VirtualMachine.Event, 
VirtualMachine>, NetworkMigrationResponder, Configurable {
     private static final Logger s_logger = 
Logger.getLogger(ConfigDriveNetworkElement.class);
 
     private static final Map<Service, Map<Capability, String>> capabilities = 
setCapabilities();
@@ -117,6 +126,11 @@
     EndPointSelector _ep;
     @Inject
     VolumeOrchestrationService _volumeMgr;
+    @Inject
+    VolumeDao volumeDao;
+
+    @Inject
+    PrimaryDataStoreDao dataStoreDao;
 
     private final static String CONFIGDRIVEFILENAME = "configdrive.iso";
     private final static String CONFIGDRIVEDIR = "ConfigDrive";
@@ -141,6 +155,9 @@ public boolean implement(Network network, NetworkOffering 
offering, DeployDestin
     @Override
     public boolean prepare(Network network, NicProfile nic, 
VirtualMachineProfile vmProfile, DeployDestination dest, ReservationContext 
context) throws ConcurrentOperationException,
             InsufficientCapacityException, ResourceUnavailableException {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("\"preparing\" config drive for vm " + 
vmProfile.getInstanceName());
+        }
         return true;
     }
 
@@ -149,22 +166,70 @@ public boolean release(Network network, NicProfile nic, 
VirtualMachineProfile vm
         if (!nic.isDefaultNic()) {
             return true;
         }
-        // Remove form secondary storage
-        DataStore secondaryStore = 
_dataStoreMgr.getImageStore(network.getDataCenterId());
+
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("releasing network for configdrive of vm " + 
vm.getInstanceName());
+        }
+
+        DataStore dataStore = null;
+        try {
+            dataStore = getDataStore(network, vm, null);
+        } catch (CloudException e) {
+            s_logger.info("no primary storage found, nothing to release for vm 
"+ vm.getInstanceName());
+            return true;
+        }
 
         String isoFile =  "/" + CONFIGDRIVEDIR + "/" + vm.getInstanceName()+ 
"/" + CONFIGDRIVEFILENAME;
         HandleConfigDriveIsoCommand deleteCommand = new 
HandleConfigDriveIsoCommand(vm.getVmData(),
-                vm.getConfigDriveLabel(), secondaryStore.getTO(), isoFile, 
false, false);
-        // Delete the ISO on the secondary store
-        EndPoint endpoint = _ep.select(secondaryStore);
+                vm.getConfigDriveLabel(), dataStore.getTO(), isoFile, false, 
false);
+        s_logger.info("Delete the ISO on the secondary store; " + dataStore);
+        EndPoint endpoint = _ep.select(dataStore);
         if (endpoint == null) {
-            s_logger.error(String.format("Secondary store: %s not available", 
secondaryStore.getName()));
+            s_logger.error(String.format("ConfigDrive store: %s not 
available", dataStore.getName()));
             return false;
         }
         Answer answer = endpoint.sendMessage(deleteCommand);
         return answer.getResult();
     }
 
+    private DataStore getDataStore(Network network, VirtualMachineProfile vm, 
Long hostId) throws CloudException {
+        DataStore dataStore = null;
+        if(UsePrimaryStorage.value() && 
Hypervisor.HypervisorType.KVM.equals(vm.getHypervisorType())) {
+            dataStore = getPrimaryDatastoreForVM(vm);
+        } else {
+            dataStore = _dataStoreMgr.getImageStore(network.getDataCenterId());
+        }
+        return dataStore;
+    }
+
+    private DataStore getPrimaryDatastoreForVM(VirtualMachineProfile vm) 
throws CloudException {
+        DataStore dataStore = null;
+        List<VolumeVO> volumes = 
volumeDao.findUsableVolumesForInstance(vm.getId());
+        for(VolumeVO volume: volumes) {
+            Long poolId = volume.getPoolId();
+            if(poolId != null) {
+                dataStore = _dataStoreMgr.getPrimaryDataStore(poolId);
+                if (DataStoreRole.Primary.equals(dataStore.getRole())) {
+                    return dataStore;
+                }
+            }
+        }
+        List<StoragePoolVO> dataStores = dataStoreDao.listAll();
+        if(!dataStores.isEmpty()) {
+            if(s_logger.isDebugEnabled()) {
+                s_logger.debug("no data stores found for volumes belonging to 
vm " + vm.getInstanceName());
+            }
+            dataStore = 
_dataStoreMgr.getPrimaryDataStore(dataStores.get(0).getId());
+            if(s_logger.isDebugEnabled()) {
+                s_logger.debug("using generic store " + dataStore.getName() + 
" to create ConfigDrive for vm " + vm.getInstanceName());
+            }
+        }
+        if(dataStore == null) {
+            throw new CloudException("No data store found to put configdrive 
for vm " + vm.getInstanceName());
+        }
+        return dataStore;
+    }
+
     @Override
     public boolean shutdown(Network network, ReservationContext context, 
boolean cleanup) throws ConcurrentOperationException, 
ResourceUnavailableException {
         return true; // assume that the agent will remove userdata etc
@@ -215,6 +280,9 @@ private String getSshKey(VirtualMachineProfile profile) {
     public boolean addPasswordAndUserdata(Network network, NicProfile nic, 
VirtualMachineProfile profile, DeployDestination dest, ReservationContext 
context)
             throws ConcurrentOperationException, 
InsufficientCapacityException, ResourceUnavailableException {
         String sshPublicKey = getSshKey(profile);
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("adding password and userdata to configdrive for vm 
" + profile.getInstanceName());
+        }
         return (canHandle(network.getTrafficType())
                 && updateConfigDrive(profile, sshPublicKey, nic))
                 && updateConfigDriveIso(network, profile, dest.getHost(), 
false);
@@ -223,20 +291,38 @@ public boolean addPasswordAndUserdata(Network network, 
NicProfile nic, VirtualMa
     @Override
     public boolean savePassword(Network network, NicProfile nic, 
VirtualMachineProfile profile) throws ResourceUnavailableException {
         String sshPublicKey = getSshKey(profile);
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("saving password to configdrive for vm " + 
profile.getInstanceName());
+        }
         if (!(canHandle(network.getTrafficType()) && 
updateConfigDrive(profile, sshPublicKey, nic))) return false;
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("update configdrive iso with password for vm " + 
profile.getInstanceName());
+        }
         return updateConfigDriveIso(network, profile, true);
     }
 
     @Override
     public boolean saveSSHKey(Network network, NicProfile nic, 
VirtualMachineProfile vm, String sshPublicKey) throws 
ResourceUnavailableException {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("saving ssh key  to configdrive for vm " + 
vm.getInstanceName());
+        }
         if (!(canHandle(network.getTrafficType()) && updateConfigDrive(vm, 
sshPublicKey, nic))) return false;
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("updating configdrive iso with ssh key for vm " + 
vm.getInstanceName());
+        }
         return updateConfigDriveIso(network, vm, true);
     }
 
     @Override
     public boolean saveUserData(Network network, NicProfile nic, 
VirtualMachineProfile profile) throws ResourceUnavailableException {
         String sshPublicKey = getSshKey(profile);
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("saving userdata to configdrive for vm " + 
profile.getInstanceName());
+        }
         if (!(canHandle(network.getTrafficType()) && 
updateConfigDrive(profile, sshPublicKey, nic))) return false;
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("updating configdrive iso with userdata for vm " + 
profile.getInstanceName());
+        }
         return updateConfigDriveIso(network, profile, true);
     }
 
@@ -247,12 +333,18 @@ public boolean verifyServicesCombination(Set<Service> 
services) {
 
     @Override
     public boolean preStateTransitionEvent(VirtualMachine.State oldState, 
VirtualMachine.Event event, VirtualMachine.State newState, VirtualMachine vo, 
boolean status, Object opaque) {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("preparing to transistion state from " + oldState + 
" on event " + event + " for vm " + vo.getInstanceName());
+        }
         return true;
     }
 
     @Override
     public boolean 
postStateTransitionEvent(StateMachine2.Transition<VirtualMachine.State, 
VirtualMachine.Event> transition, VirtualMachine vo, boolean status, Object 
opaque) {
         if (transition.getToState().equals(VirtualMachine.State.Expunging) && 
transition.getEvent().equals(VirtualMachine.Event.ExpungeOperation)) {
+            if (s_logger.isDebugEnabled()) {
+                s_logger.debug("handling expunge state for configdrive of vm " 
+ vo.getInstanceName());
+            }
             Nic nic = _networkModel.getDefaultNic(vo.getId());
             try {
                 if (nic != null) {
@@ -261,13 +353,13 @@ public boolean 
postStateTransitionEvent(StateMachine2.Transition<VirtualMachine.
                     final Provider provider = 
userDataUpdateProvider.getProvider();
                     if (provider.equals(Provider.ConfigDrive)) {
                         // Delete config drive ISO on destroy
-                        DataStore secondaryStore = 
_dataStoreMgr.getImageStore(vo.getDataCenterId());
+                        DataStore dataStore = 
_dataStoreMgr.getImageStore(vo.getDataCenterId());
                         String isoFile = "/" + CONFIGDRIVEDIR + "/" + 
vo.getInstanceName() + "/" + CONFIGDRIVEFILENAME;
                         HandleConfigDriveIsoCommand deleteCommand = new 
HandleConfigDriveIsoCommand(null,
-                                null, secondaryStore.getTO(), isoFile, false, 
false);
-                        EndPoint endpoint = _ep.select(secondaryStore);
+                                null, dataStore.getTO(), isoFile, false, 
false);
+                        EndPoint endpoint = _ep.select(dataStore);
                         if (endpoint == null) {
-                            s_logger.error(String.format("Secondary store: %s 
not available", secondaryStore.getName()));
+                            s_logger.error(String.format("Secondary store: %s 
not available", dataStore.getName()));
                             return false;
                         }
                         Answer answer = endpoint.sendMessage(deleteCommand);
@@ -277,17 +369,28 @@ public boolean 
postStateTransitionEvent(StateMachine2.Transition<VirtualMachine.
                         }
                     }
                 }
-            } catch (UnsupportedServiceException usse) {}
+            } catch (UnsupportedServiceException usse) {
+                s_logger.debug("not supported configdrive removal 
detected",usse);
+            }
         }
         return true;
     }
 
     @Override
     public boolean prepareMigration(NicProfile nic, Network network, 
VirtualMachineProfile vm, DeployDestination dest, ReservationContext context) {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("preparing configdrive for migration for vm " + 
vm.getInstanceName());
+        }
         if (nic.isDefaultNic() && 
_networkModel.getUserDataUpdateProvider(network).getProvider().equals(Provider.ConfigDrive))
 {
             s_logger.trace(String.format("[prepareMigration] for vm: %s", 
vm.getInstanceName()));
-            DataStore secondaryStore = 
_dataStoreMgr.getImageStore(network.getDataCenterId());
-            configureConfigDriveDisk(vm, secondaryStore);
+            DataStore dataStore = null;
+            try {
+                dataStore = getDataStore(network,vm, null);
+            } catch (CloudException e) {
+                s_logger.info("no primary storage found, nothing to migrate 
for vm "+ vm.getInstanceName());
+                return true;
+            }
+            configureConfigDriveDisk(vm, dataStore);
             return false;
         }
         else return  true;
@@ -308,6 +411,9 @@ private boolean updateConfigDriveIso(Network network, 
VirtualMachineProfile prof
     }
 
     private boolean updateConfigDriveIso(Network network, 
VirtualMachineProfile profile, Host host, boolean update) throws 
ResourceUnavailableException {
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug("updating configdrive iso for vm " + 
profile.getInstanceName());
+        }
         Integer deviceKey = null;
         Long hostId;
         if (host == null) {
@@ -316,41 +422,51 @@ private boolean updateConfigDriveIso(Network network, 
VirtualMachineProfile prof
             hostId = host.getId();
         }
 
-        DataStore secondaryStore = 
_dataStoreMgr.getImageStore(network.getDataCenterId());
+        DataStore dataStore = null;
+        try {
+            dataStore = getDataStore(network,profile, hostId);
+        } catch (CloudException e) {
+            s_logger.info("no primary storage found, nothing to update for vm 
"+ profile.getInstanceName());
+            return true;
+        }
         // Detach the existing ISO file if the machine is running
         if (update && 
profile.getVirtualMachine().getState().equals(VirtualMachine.State.Running)) {
             s_logger.debug("Detach config drive ISO for  vm " + 
profile.getInstanceName() + " in host " + _hostDao.findById(hostId));
-            deviceKey = detachIso(secondaryStore, profile.getInstanceName(), 
hostId);
+            deviceKey = detachIso(dataStore, profile.getInstanceName(), 
hostId);
         }
 
-        // Create/Update the iso on the secondary store
-        s_logger.debug(String.format("%s config drive ISO for  vm %s in host 
%s",
-                (update?"update":"create"), profile.getInstanceName(), 
_hostDao.findById(hostId).getName()));
-        EndPoint endpoint = _ep.select(secondaryStore);
+        // Create/Update the iso on the data store
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug(String.format("%s config drive ISO for  vm %s in 
host %s",
+                    (update ? "update" : "create"),
+                    profile.getInstanceName(),
+                    _hostDao.findById(hostId).getName()));
+        }
+        EndPoint endpoint = _ep.select(dataStore);
         if (endpoint == null) {
-            throw new ResourceUnavailableException(String.format("%s failed, 
secondary store not available", (update ? "Update" : "Create")), 
secondaryStore.getClass(),
-                                                   secondaryStore.getId());
+            throw new ResourceUnavailableException(String.format("%s failed, 
secondary store not available", (update ? "Update" : "Create")), 
dataStore.getClass(),
+                                                   dataStore.getId());
         }
         String isoPath = CONFIGDRIVEDIR + "/" + profile.getInstanceName() + 
"/"  + CONFIGDRIVEFILENAME;
         HandleConfigDriveIsoCommand configDriveIsoCommand = new 
HandleConfigDriveIsoCommand(profile.getVmData(),
-                profile.getConfigDriveLabel(), secondaryStore.getTO(), 
isoPath, true, update);
+                profile.getConfigDriveLabel(), dataStore.getTO(), isoPath, 
true, update);
         Answer createIsoAnswer = endpoint.sendMessage(configDriveIsoCommand);
         if (!createIsoAnswer.getResult()) {
             throw new ResourceUnavailableException(String.format("%s ISO 
failed, details: %s",
                     (update?"Update":"Create"), createIsoAnswer.getDetails()), 
ConfigDriveNetworkElement.class, 0L);
         }
-        configureConfigDriveDisk(profile, secondaryStore);
+        configureConfigDriveDisk(profile, dataStore);
 
         // Re-attach the ISO if the machine is running
         if (update && 
profile.getVirtualMachine().getState().equals(VirtualMachine.State.Running)) {
             s_logger.debug("Re-attach config drive ISO for  vm " + 
profile.getInstanceName() + " in host " + _hostDao.findById(hostId));
-            attachIso(secondaryStore, profile.getInstanceName(), hostId, 
deviceKey);
+            attachIso(dataStore, profile.getInstanceName(), hostId, deviceKey);
         }
         return true;
 
     }
 
-    private void configureConfigDriveDisk(VirtualMachineProfile profile, 
DataStore secondaryStore) {
+    private void configureConfigDriveDisk(VirtualMachineProfile profile, 
DataStore dataStore) {
         boolean isoAvailable = false;
         String isoPath = CONFIGDRIVEDIR + "/" + profile.getInstanceName() + 
"/"  + CONFIGDRIVEFILENAME;
         for (DiskTO dataTo : profile.getDisks()) {
@@ -361,7 +477,7 @@ private void configureConfigDriveDisk(VirtualMachineProfile 
profile, DataStore s
         }
         if (!isoAvailable) {
             TemplateObjectTO dataTO = new TemplateObjectTO();
-            dataTO.setDataStore(secondaryStore.getTO());
+            dataTO.setDataStore(dataStore.getTO());
             dataTO.setUuid(profile.getUuid());
             dataTO.setPath(isoPath);
             dataTO.setFormat(Storage.ImageFormat.ISO);
@@ -389,10 +505,10 @@ private boolean updateConfigDrive(VirtualMachineProfile 
profile, String publicKe
         return true;
     }
 
-    private Integer detachIso (DataStore secondaryStore, String instanceName, 
Long hostId) throws ResourceUnavailableException {
+    private Integer detachIso (DataStore dataStore, String instanceName, Long 
hostId) throws ResourceUnavailableException {
         String isoPath = CONFIGDRIVEDIR + "/" + instanceName + "/"  + 
CONFIGDRIVEFILENAME;
-        AttachIsoCommand isoCommand = new AttachIsoCommand(instanceName, 
secondaryStore.getUri() + "/" + isoPath, false, CONFIGDRIVEDISKSEQ, true);
-        isoCommand.setStoreUrl(secondaryStore.getUri());
+        AttachIsoCommand isoCommand = new AttachIsoCommand(instanceName, 
dataStore.getUri() + "/" + isoPath, false, CONFIGDRIVEDISKSEQ, true);
+        isoCommand.setStoreUrl(dataStore.getUri());
         Answer attachIsoAnswer = null;
 
         try {
@@ -412,10 +528,10 @@ private Integer detachIso (DataStore secondaryStore, 
String instanceName, Long h
         }
     }
 
-    private void attachIso (DataStore secondaryStore, String instanceName, 
Long hostId, Integer deviceKey) throws ResourceUnavailableException {
+    private void attachIso (DataStore dataStore, String instanceName, Long 
hostId, Integer deviceKey) throws ResourceUnavailableException {
         String isoPath = CONFIGDRIVEDIR + "/" + instanceName + "/"  + 
CONFIGDRIVEFILENAME;
-        AttachIsoCommand isoCommand = new AttachIsoCommand(instanceName, 
secondaryStore.getUri() + "/" + isoPath, true);
-        isoCommand.setStoreUrl(secondaryStore.getUri());
+        AttachIsoCommand isoCommand = new AttachIsoCommand(instanceName, 
dataStore.getUri() + "/" + isoPath, true);
+        isoCommand.setStoreUrl(dataStore.getUri());
         isoCommand.setDeviceKey(deviceKey);
         Answer attachIsoAnswer = null;
         try {
@@ -428,4 +544,14 @@ private void attachIso (DataStore secondaryStore, String 
instanceName, Long host
         }
     }
 
+    public static final ConfigKey<Boolean> UsePrimaryStorage = new 
ConfigKey<Boolean>(Boolean.class, "configdrive.use.primary", "Advanced", 
"false",
+            "Whether to use primary storage instead of secondary to create 
config drive ISOs", false, ConfigKey.Scope.Global, null);
+
+    @Override public String getConfigComponentName() {
+        return ConfigDriveNetworkElement.class.getSimpleName();
+    }
+
+    @Override public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[] {UsePrimaryStorage};
+    }
 }
diff --git 
a/server/test/com/cloud/network/element/ConfigDriveNetworkElementTest.java 
b/server/test/com/cloud/network/element/ConfigDriveNetworkElementTest.java
index 7d5041536f0..b64919ddffa 100644
--- a/server/test/com/cloud/network/element/ConfigDriveNetworkElementTest.java
+++ b/server/test/com/cloud/network/element/ConfigDriveNetworkElementTest.java
@@ -166,6 +166,7 @@ public void setUp() throws NoSuchFieldException, 
IllegalAccessException {
         when(virtualMachine.getDataCenterId()).thenReturn(DATACENTERID);
         when(virtualMachine.getInstanceName()).thenReturn(VMINSTANCENAME);
         when(virtualMachine.getUserData()).thenReturn(VMUSERDATA);
+        when(virtualMachine.getHostName()).thenReturn("vm-hostname");
         when(deployDestination.getHost()).thenReturn(hostVO);
         when(hostVO.getId()).thenReturn(HOSTID);
         when(nic.isDefaultNic()).thenReturn(true);
diff --git a/services/secondary-storage/server/pom.xml 
b/services/secondary-storage/server/pom.xml
index 7c7cf9b68bf..c30b248a5b0 100644
--- a/services/secondary-storage/server/pom.xml
+++ b/services/secondary-storage/server/pom.xml
@@ -40,11 +40,16 @@
       <artifactId>commons-codec</artifactId>
     </dependency>
     <!-- required deps for the systemvm -->
-    <dependency>
-      <groupId>org.apache.cloudstack</groupId>
-      <artifactId>cloud-agent</artifactId>
-      <version>${project.version}</version>
-    </dependency>
+      <dependency>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloud-agent</artifactId>
+        <version>${project.version}</version>
+      </dependency>
+      <dependency>
+        <groupId>org.apache.cloudstack</groupId>
+        <artifactId>cloud-config-drive</artifactId>
+        <version>${project.version}</version>
+      </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-server</artifactId>
diff --git 
a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/LocalNfsSecondaryStorageResource.java
 
b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/LocalNfsSecondaryStorageResource.java
index c02fe8a610e..b2d537831ed 100644
--- 
a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/LocalNfsSecondaryStorageResource.java
+++ 
b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/LocalNfsSecondaryStorageResource.java
@@ -53,20 +53,20 @@ public Answer executeRequest(Command cmd) {
     }
 
     @Override
-    synchronized public String getRootDir(String secUrl, Integer nfsVersion) {
+    synchronized public String getRootDir(String url, Integer nfsVersion) {
         try {
-            URI uri = new URI(secUrl);
+            URI uri = new URI(url);
             String dir = mountUri(uri, nfsVersion);
             return _parent + "/" + dir;
         } catch (Exception e) {
-            String msg = "GetRootDir for " + secUrl + " failed due to " + 
e.toString();
+            String msg = "GetRootDir for " + url + " failed due to " + 
e.toString();
             s_logger.error(msg, e);
             throw new CloudRuntimeException(msg);
         }
     }
 
     @Override
-    protected void mount(String localRootPath, String remoteDevice, URI uri, 
Integer nfsVersion) {
+    public void mount(String localRootPath, String remoteDevice, URI uri, 
Integer nfsVersion) {
         ensureLocalRootPathExists(localRootPath, uri);
 
         if (mountExists(localRootPath, uri)) {
diff --git 
a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
 
b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
index da3ab32e203..c32287ed12c 100644
--- 
a/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
+++ 
b/services/secondary-storage/server/src/org/apache/cloudstack/storage/resource/NfsSecondaryStorageResource.java
@@ -16,15 +16,6 @@
 // under the License.
 package org.apache.cloudstack.storage.resource;
 
-import static com.cloud.network.NetworkModel.CONFIGDATA_CONTENT;
-import static com.cloud.network.NetworkModel.CONFIGDATA_DIR;
-import static com.cloud.network.NetworkModel.CONFIGDATA_FILE;
-import static com.cloud.network.NetworkModel.METATDATA_DIR;
-import static com.cloud.network.NetworkModel.PASSWORD_DIR;
-import static com.cloud.network.NetworkModel.PASSWORD_FILE;
-import static com.cloud.network.NetworkModel.PUBLIC_KEYS_FILE;
-import static com.cloud.network.NetworkModel.USERDATA_DIR;
-import static com.cloud.network.NetworkModel.USERDATA_FILE;
 import static com.cloud.utils.StringUtils.join;
 import static com.cloud.utils.storage.S3.S3Utils.putFile;
 import static java.lang.String.format;
@@ -45,8 +36,6 @@
 import java.net.InetAddress;
 import java.net.URI;
 import java.net.UnknownHostException;
-import java.nio.charset.Charset;
-import java.nio.file.Path;
 import java.security.NoSuchAlgorithmException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -70,7 +59,7 @@
 import io.netty.handler.logging.LogLevel;
 import io.netty.handler.logging.LoggingHandler;
 
-import org.apache.commons.codec.binary.Base64;
+import org.apache.cloudstack.storage.ConfigDriveFactory;
 import org.apache.commons.codec.digest.DigestUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.commons.io.FilenameUtils;
@@ -87,16 +76,11 @@
 import org.joda.time.format.ISODateTimeFormat;
 
 import com.amazonaws.services.s3.model.S3ObjectSummary;
-import com.google.common.collect.Lists;
-import com.google.common.collect.Maps;
-import com.google.common.io.Files;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
 
 import org.apache.cloudstack.framework.security.keystore.KeystoreManager;
+import org.apache.cloudstack.storage.StorageAttacher;
 import org.apache.cloudstack.storage.command.CopyCmdAnswer;
 import org.apache.cloudstack.storage.command.CopyCommand;
 import org.apache.cloudstack.storage.command.DeleteCommand;
@@ -164,7 +148,6 @@
 import com.cloud.host.Host;
 import com.cloud.host.Host.Type;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
-import com.cloud.network.NetworkModel;
 import com.cloud.resource.ServerResourceBase;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.Storage;
@@ -193,23 +176,13 @@
 import com.cloud.utils.storage.S3.S3Utils;
 import com.cloud.vm.SecondaryStorageVm;
 
-public class NfsSecondaryStorageResource extends ServerResourceBase implements 
SecondaryStorageResource {
+public class NfsSecondaryStorageResource extends ServerResourceBase implements 
SecondaryStorageResource, StorageAttacher {
 
     public static final Logger s_logger = 
Logger.getLogger(NfsSecondaryStorageResource.class);
 
     private static final String TEMPLATE_ROOT_DIR = "template/tmpl";
     private static final String VOLUME_ROOT_DIR = "volumes";
     private static final String POST_UPLOAD_KEY_LOCATION = 
"/etc/cloudstack/agent/ms-psk";
-    private static final String cloudStackConfigDriveName = "/cloudstack/";
-    private static final String openStackConfigDriveName = 
"/openstack/latest/";
-
-    private static final Map<String, String> updatableConfigData = 
Maps.newHashMap();
-    static {
-
-        updatableConfigData.put(PUBLIC_KEYS_FILE, METATDATA_DIR);
-        updatableConfigData.put(USERDATA_FILE, USERDATA_DIR);
-        updatableConfigData.put(PASSWORD_FILE, PASSWORD_DIR);
-    }
 
     int _timeout;
 
@@ -253,6 +226,7 @@ public void setTimeout(int timeout) {
     private String _ssvmPSK = null;
     private long processTimeout;
 
+    ConfigDriveFactory configDriveFactory;
     public void setParentPath(String path) {
         _parent = path;
     }
@@ -281,7 +255,7 @@ public static Integer 
retrieveNfsVersionFromParams(Map<String, Object> params) {
             try {
                 nfsVersion = Integer.valueOf(nfsVersionParam);
             } catch (NumberFormatException e){
-                s_logger.error("Couldn't cast " + nfsVersionParam + " to 
integer");
+                s_logger.error("No NFS version known; Couldn't cast " + 
nfsVersionParam + " to integer");
                 return null;
             }
         }
@@ -338,309 +312,13 @@ public Answer executeRequest(Command cmd) {
     }
 
     private Answer execute(HandleConfigDriveIsoCommand cmd) {
-
-        if (cmd.isCreate()) {
-            s_logger.debug(String.format("VMdata %s, attach = %s", 
cmd.getVmData(), cmd.isCreate()));
-            if(cmd.getVmData() == null) return new Answer(cmd, false, "No 
Vmdata available");
-            String nfsMountPoint = getRootDir(cmd.getDestStore().getUrl(), 
_nfsVersion);
-            File isoFile = new File(nfsMountPoint, cmd.getIsoFile());
-            if(isoFile.exists()) {
-                if (!cmd.isUpdate()) {
-                    return new Answer(cmd, true, "ISO already available");
-                } else {
-                    // Find out if we have to recover the password/ssh-key 
from the already available ISO.
-                    try {
-                        List<String[]> recoveredVmData = 
recoverVmData(isoFile);
-                        for (String[] vmDataEntry : cmd.getVmData()) {
-                            if 
(updatableConfigData.containsKey(vmDataEntry[CONFIGDATA_FILE])
-                                    && 
updatableConfigData.get(vmDataEntry[CONFIGDATA_FILE]).equals(vmDataEntry[CONFIGDATA_DIR]))
 {
-                                   updateVmData(recoveredVmData, vmDataEntry);
-                            }
-                        }
-                        cmd.setVmData(recoveredVmData);
-                    } catch (IOException e) {
-                        return new Answer(cmd, e);
-                    }
-                }
-            }
-            return createConfigDriveIsoForVM(cmd);
-        } else {
-            DataStoreTO dstore = cmd.getDestStore();
-            if (dstore instanceof NfsTO) {
-                NfsTO nfs = (NfsTO) dstore;
-                String relativeTemplatePath = new 
File(cmd.getIsoFile()).getParent();
-                String nfsMountPoint = getRootDir(nfs.getUrl(), _nfsVersion);
-                File tmpltPath = new File(nfsMountPoint, relativeTemplatePath);
-                try {
-                    FileUtils.deleteDirectory(tmpltPath);
-                } catch (IOException e) {
-                    return new Answer(cmd, e);
-                }
-                return new Answer(cmd);
-            } else {
-                return new Answer(cmd, false, "Not implemented yet");
-            }
-        }
-    }
-
-    private void updateVmData(List<String[]> recoveredVmData, String[] 
vmDataEntry) {
-        for (String[] recoveredEntry : recoveredVmData) {
-            if 
(recoveredEntry[CONFIGDATA_DIR].equals(vmDataEntry[CONFIGDATA_DIR])
-                    && 
recoveredEntry[CONFIGDATA_FILE].equals(vmDataEntry[CONFIGDATA_FILE])) {
-                recoveredEntry[CONFIGDATA_CONTENT] = 
vmDataEntry[CONFIGDATA_CONTENT];
-                return;
-            }
-        }
-        recoveredVmData.add(vmDataEntry);
-    }
-
-    private List<String[]> recoverVmData(File isoFile) throws IOException {
-        String tempDirName = null;
-        List<String[]> recoveredVmData = Lists.newArrayList();
-        boolean mounted = false;
-        try {
-            Path tempDir = 
java.nio.file.Files.createTempDirectory("ConfigDrive");
-            tempDirName = tempDir.toString();
-
-            // Unpack the current config drive file
-            Script command = new Script(!_inSystemVM, "mount", _timeout, 
s_logger);
-            command.add("-o", "loop");
-            command.add(isoFile.getAbsolutePath());
-            command.add(tempDirName);
-            String result = command.execute();
-
-            if (result != null) {
-                String errMsg = "Unable to mount " + isoFile.getAbsolutePath() 
+ " at " + tempDirName + " due to " + result;
-                s_logger.error(errMsg);
-                throw new IOException(errMsg);
-            }
-            mounted = true;
-
-
-            // Scan directory structure
-            for (File configDirectory: (new File(tempDirName, 
"cloudstack")).listFiles()){
-                for (File configFile: configDirectory.listFiles()) {
-                    recoveredVmData.add(new String[]{configDirectory.getName(),
-                            
Files.getNameWithoutExtension(configFile.getName()),
-                            Files.readFirstLine(configFile, 
Charset.defaultCharset())});
-                }
-            }
-
-        } finally {
-            if (mounted) {
-                Script command = new Script(!_inSystemVM, "umount", _timeout, 
s_logger);
-                command.add(tempDirName);
-                String result = command.execute();
-                if (result != null) {
-                    s_logger.warn("Unable to umount " + tempDirName + " due to 
" + result);
-                }
-            }
-            try {
-                FileUtils.deleteDirectory(new File(tempDirName));
-            } catch (IOException ioe) {
-                s_logger.warn("Failed to delete ConfigDrive temporary 
directory: " + tempDirName, ioe);
-            }
-        }
-        return  recoveredVmData;
-    }
-
-    public Answer createConfigDriveIsoForVM(HandleConfigDriveIsoCommand cmd) {
-        //create folder for the VM
-        if (cmd.getVmData() != null) {
-
-            Path tempDir = null;
-            String tempDirName = null;
-            try {
-                tempDir = 
java.nio.file.Files.createTempDirectory("ConfigDrive");
-                tempDirName = tempDir.toString();
-
-                //create OpenStack files
-                //create folder with empty files
-                File openStackFolder = new File(tempDirName + 
openStackConfigDriveName);
-                if (openStackFolder.exists() || openStackFolder.mkdirs()) {
-                    File vendorDataFile = new 
File(openStackFolder,"vendor_data.json");
-                    try (FileWriter fw = new FileWriter(vendorDataFile); 
BufferedWriter bw = new BufferedWriter(fw)) {
-                        bw.write("{}");
-                    } catch (IOException ex) {
-                        s_logger.error("Failed to create file ", ex);
-                        return new Answer(cmd, ex);
-                    }
-                    File networkDataFile = new File(openStackFolder, 
"network_data.json");
-                    try (FileWriter fw = new FileWriter(networkDataFile); 
BufferedWriter bw = new BufferedWriter(fw)) {
-                        bw.write("{}");
-                    } catch (IOException ex) {
-                        s_logger.error("Failed to create file ", ex);
-                        return new Answer(cmd, ex);
-                    }
-                } else {
-                    s_logger.error("Failed to create folder " + 
openStackFolder);
-                    return new Answer(cmd, false, "Failed to create folder " + 
openStackFolder);
-                }
-
-                JsonObject metaData = new JsonObject();
-                for (String[] item : cmd.getVmData()) {
-                    String dataType = item[CONFIGDATA_DIR];
-                    String fileName = item[CONFIGDATA_FILE];
-                    String content = item[CONFIGDATA_CONTENT];
-                    s_logger.debug(String.format("[createConfigDriveIsoForVM] 
dataType=%s, filename=%s, content=%s",
-                            dataType, fileName, 
(fileName.equals(PASSWORD_FILE)?"********":content)));
-
-                    // create file with content in folder
-                    if (dataType != null && !dataType.isEmpty()) {
-                        //create folder
-                        File typeFolder = new File(tempDirName + 
cloudStackConfigDriveName + dataType);
-                        if (typeFolder.exists() || typeFolder.mkdirs()) {
-                            if (StringUtils.isNotEmpty(content)) {
-                                File file = new File(typeFolder, fileName + 
".txt");
-                                try  {
-                                    if (fileName.equals(USERDATA_FILE)) {
-                                        // User Data is passed as a base64 
encoded string
-                                        FileUtils.writeByteArrayToFile(file, 
Base64.decodeBase64(content));
-                                    } else {
-                                        FileUtils.write(file, content, 
com.cloud.utils.StringUtils.getPreferredCharset());
-                                    }
-                                } catch (IOException ex) {
-                                    s_logger.error("Failed to create file ", 
ex);
-                                    return new Answer(cmd, ex);
-                                }
-                            }
-                        } else {
-                            s_logger.error("Failed to create folder " + 
typeFolder);
-                            return new Answer(cmd, false, "Failed to create 
folder " + typeFolder);
-                        }
-
-                        //now write the file to the OpenStack directory
-                        metaData = constructOpenStackMetaData(metaData, 
dataType, fileName, content);
-                    }
-                }
-
-                File metaDataFile = new File(openStackFolder, 
"meta_data.json");
-                try (FileWriter fw = new FileWriter(metaDataFile); 
BufferedWriter bw = new BufferedWriter(fw)) {
-                    bw.write(metaData.toString());
-                } catch (IOException ex) {
-                    s_logger.error("Failed to create file ", ex);
-                    return new Answer(cmd, ex);
-                }
-
-                String linkResult = linkUserData(tempDirName);
-                if (linkResult != null) {
-                    String errMsg = "Unable to create user_data link due to " 
+ linkResult;
-                    s_logger.warn(errMsg);
-                    return new Answer(cmd, false, errMsg);
-                }
-
-                File tmpIsoStore = new File(tempDirName, new 
File(cmd.getIsoFile()).getName());
-                Script command = new Script(!_inSystemVM, 
"/usr/bin/genisoimage", _timeout, s_logger);
-                command.add("-o", tmpIsoStore.getAbsolutePath());
-                command.add("-ldots");
-                command.add("-allow-lowercase");
-                command.add("-allow-multidot");
-                command.add("-cache-inodes"); // Enable caching inode and 
device numbers to find hard links to files.
-                command.add("-l");
-                command.add("-quiet");
-                command.add("-J");
-                command.add("-r");
-                command.add("-V", cmd.getConfigDriveLabel());
-                command.add(tempDirName);
-                s_logger.debug("execute command: " + command.toString());
-                String result = command.execute();
-                if (result != null) {
-                    String errMsg = "Unable to create iso file: " + 
cmd.getIsoFile() + " due to " + result;
-                    s_logger.warn(errMsg);
-                    return new Answer(cmd, false, errMsg);
-                }
-                copyLocalToNfs(tmpIsoStore, new File(cmd.getIsoFile()), 
cmd.getDestStore());
-
-            } catch (IOException e) {
-                return new Answer(cmd, e);
-            } catch (ConfigurationException e) {
-                s_logger.warn("SecondStorageException ", e);
-                return new Answer(cmd, e);
-            } finally {
-                try {
-                    FileUtils.deleteDirectory(tempDir.toFile());
-                } catch (IOException ioe) {
-                    s_logger.warn("Failed to delete ConfigDrive temporary 
directory: " + tempDirName, ioe);
-                }
-            }
-        }
-        return new Answer(cmd);
-    }
-
-    JsonObject constructOpenStackMetaData(JsonObject metaData, String 
dataType, String fileName, String content) {
-        if (dataType.equals(NetworkModel.METATDATA_DIR) &&  
StringUtils.isNotEmpty(content)) {
-            //keys are a special case in OpenStack format
-            if (NetworkModel.PUBLIC_KEYS_FILE.equals(fileName)) {
-                String[] keyArray = content.replace("\\n", "").split(" ");
-                String keyName = "key";
-                if (keyArray.length > 3 && 
StringUtils.isNotEmpty(keyArray[2])){
-                    keyName = keyArray[2];
-                }
-
-                JsonObject keyLegacy = new JsonObject();
-                keyLegacy.addProperty("type", "ssh");
-                keyLegacy.addProperty("data", content.replace("\\n", ""));
-                keyLegacy.addProperty("name", keyName);
-                metaData.add("keys", arrayOf(keyLegacy));
-
-                JsonObject key = new JsonObject();
-                key.addProperty(keyName, content);
-                metaData.add("public_keys", key);
-            } else if (NetworkModel.openStackFileMapping.get(fileName) != 
null) {
-                    
metaData.addProperty(NetworkModel.openStackFileMapping.get(fileName), content);
-            }
-        }
-        return metaData;
+        return configDriveFactory.executeRequest(cmd);
     }
 
-    private static JsonArray arrayOf(JsonElement... elements) {
-        JsonArray array = new JsonArray();
-        for (JsonElement element : elements) {
-            array.add(element);
-        }
-        return array;
-    }
 
-    private String linkUserData(String tempDirName) {
-        //Hard link the user_data.txt file with the user_data file in the 
OpenStack directory.
-        String userDataFilePath = tempDirName + cloudStackConfigDriveName + 
"userdata/user_data.txt";
-        if ((new File(userDataFilePath).exists())) {
-            Script hardLink = new Script(!_inSystemVM, "ln", _timeout, 
s_logger);
-            hardLink.add(userDataFilePath);
-            hardLink.add(tempDirName + openStackConfigDriveName + "user_data");
-            s_logger.debug("execute command: " + hardLink.toString());
-            return hardLink.execute();
-        }
-        return null;
-    }
 
-    protected void copyLocalToNfs(File localFile, File isoFile, DataStoreTO 
destData) throws ConfigurationException, IOException {
-        String scriptsDir = "scripts/storage/secondary";
-        String 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);
 
-        int installTimeoutPerGig = 180 * 60 * 1000;
-        int imgSizeGigs = (int) Math.ceil(localFile.length() * 1.0d / (1024 * 
1024 * 1024));
-        imgSizeGigs++; // add one just in case
-        long timeout = imgSizeGigs * installTimeoutPerGig;
 
-        Script scr = new Script(createVolScr, timeout, s_logger);
-        scr.add("-s", Integer.toString(imgSizeGigs));
-        scr.add("-n", isoFile.getName());
-        scr.add("-t", getRootDir(destData.getUrl(), _nfsVersion) + "/" + 
isoFile.getParent());
-        scr.add("-f", localFile.getAbsolutePath());
-        scr.add("-d", "configDrive");
-        String result;
-        result = scr.execute();
-
-        if (result != null) {
-            // script execution failure
-            throw new CloudRuntimeException("Failed to run script " + 
createVolScr);
-        }
-    }
 
     public Answer execute(GetDatadisksCommand cmd) {
         DataTO srcData = cmd.getData();
@@ -658,7 +336,7 @@ public Answer execute(GetDatadisksCommand cmd) {
 
         try {
             String secondaryMountPoint = getRootDir(secondaryStorageUrl, 
_nfsVersion);
-            s_logger.info("MDOVE Secondary storage mount point: " + 
secondaryMountPoint);
+            s_logger.info("MOVE Secondary storage mount point: " + 
secondaryMountPoint);
 
             String srcOVAFileName = 
getTemplateOnSecStorageFilePath(secondaryMountPoint, 
templateRelativeFolderPath, templateInfo.second(), 
ImageFormat.OVA.getFileExtension());
 
@@ -2684,16 +2362,16 @@ protected Answer deleteVolume(final DeleteCommand cmd) {
     }
 
     @Override
-    synchronized public String getRootDir(String secUrl, Integer nfsVersion) {
+    synchronized public String getRootDir(String url, Integer nfsVersion) {
         if (!_inSystemVM) {
             return _parent;
         }
         try {
-            URI uri = new URI(secUrl);
+            URI uri = new URI(url);
             String dir = mountUri(uri, nfsVersion);
             return _parent + "/" + dir;
         } catch (Exception e) {
-            String msg = "GetRootDir for " + secUrl + " failed due to " + 
e.toString();
+            String msg = "GetRootDir for " + url + " failed due to " + 
e.toString();
             s_logger.error(msg, e);
             throw new CloudRuntimeException(msg);
         }
@@ -2759,7 +2437,7 @@ public boolean configure(String name, Map<String, Object> 
params) throws Configu
         String inSystemVM = (String)params.get("secondary.storage.vm");
         if (inSystemVM == null || "true".equalsIgnoreCase(inSystemVM)) {
             s_logger.debug("conf secondary.storage.vm is true, act as if 
executing in SSVM");
-            _inSystemVM = true;
+            setInSystemVM(true);
         }
 
         _storageIp = (String)params.get("storageip");
@@ -2850,8 +2528,9 @@ public boolean configure(String name, Map<String, Object> 
params) throws Configu
             _nfsVersion = retrieveNfsVersionFromParams(params);
         }
 
+        configDriveFactory = new ConfigDriveFactory(_nfsVersion, this);
+        _params.put(StorageLayer.InstanceConfigKey, _storage);
         try {
-            _params.put(StorageLayer.InstanceConfigKey, _storage);
             _dlMgr = new DownloadManagerImpl();
             _dlMgr.configure("DownloadManager", _params);
             _upldMgr = new UploadManagerImpl();
@@ -3047,7 +2726,8 @@ protected String mountUri(URI uri, Integer nfsVersion) 
throws UnknownHostExcepti
         return dir;
     }
 
-    protected void umount(String localRootPath, URI uri) {
+    @Override
+    public void umount(String localRootPath, URI uri) {
         ensureLocalRootPathExists(localRootPath, uri);
 
         if (!mountExists(localRootPath, uri)) {
@@ -3070,7 +2750,8 @@ protected void umount(String localRootPath, URI uri) {
         s_logger.debug("Successfully umounted " + localRootPath);
     }
 
-    protected void mount(String localRootPath, String remoteDevice, URI uri, 
Integer nfsVersion) {
+    @Override
+    public void mount(String localRootPath, String remoteDevice, URI uri, 
Integer nfsVersion) {
         s_logger.debug("mount " + uri.toString() + " on " + localRootPath + 
((nfsVersion != null) ? " nfsVersion=" + nfsVersion : ""));
         ensureLocalRootPathExists(localRootPath, uri);
 


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


> Config drive - only supported for secondary storage
> ---------------------------------------------------
>
>                 Key: CLOUDSTACK-10290
>                 URL: https://issues.apache.org/jira/browse/CLOUDSTACK-10290
>             Project: CloudStack
>          Issue Type: Bug
>      Security Level: Public(Anyone can view this level - this is the 
> default.) 
>    Affects Versions: 4.11.0.0
>            Reporter: Rohit Yadav
>            Assignee: Daan Hoogland
>            Priority: Major
>
> Userdata disk looks like this:
>  <disk type='file' device='cdrom'>
>        <driver name='qemu' type='raw' cache='none'/>
>        <source 
> file='/mnt/eba12ff3-c3a6-394a-bf0f-23291f1f6266/configdrive.iso'/>
>        <backingStore/>
>        <target dev='hdd' bus='ide'/>
>        <readonly/>
>        <alias name='ide0-1-1'/>
>        <address type='drive' controller='0' bus='1' target='0' unit='1'/>
>      </disk>
> Mount is:
> root# df /mnt/eba12ff3-c3a6-394a-bf0f-23291f1f6266
>  Filesystem                                                       1K-blocks   
>  Used Available Use% Mounted on
>  some-nfs-server.com:/nfs/secondary/ConfigDrive/i-2-24-VM  66391040 2973696  
> 63417344   5% /mnt/eba12ff3-c3a6-394a-bf0f-23291f1f6266
>  
> issue: where to find a primary storage for a VM that is not yet deployed. The 
> configdrive is created before a storage is assigned. This order of execution 
> must be reversed for this to work.



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to