You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by nv...@apache.org on 2019/05/29 11:36:43 UTC

[cloudstack] branch master updated: DPDK vHost User mode selection (#3153)

This is an automated email from the ASF dual-hosted git repository.

nvazquez pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/cloudstack.git


The following commit(s) were added to refs/heads/master by this push:
     new 501aa7c  DPDK vHost User mode selection (#3153)
501aa7c is described below

commit 501aa7cd9199c90d4904b113d159779ffa5b8eeb
Author: Nicolas Vazquez <ni...@gmail.com>
AuthorDate: Wed May 29 08:36:33 2019 -0300

    DPDK vHost User mode selection (#3153)
    
    * DPDK vHost User mode selection
    
    * SQL text field and DPDK classes refactor
    
    * Fix NullPointerException after refactor
    
    * Fix unit test
    
    * Refactor details type
---
 .../resources/META-INF/db/schema-41200to41300.sql  |   2 +
 .../com/cloud/hypervisor/kvm/dpdk/DPDKDriver.java  |  56 +++++++++
 .../cloud/hypervisor/kvm/dpdk/DPDKDriverImpl.java  | 115 ++++++++++++++++++
 .../kvm/resource/LibvirtComputingResource.java     |  11 +-
 .../hypervisor/kvm/resource/LibvirtVMDef.java      |   8 +-
 .../hypervisor/kvm/resource/OvsVifDriver.java      |  74 +++--------
 .../DPDKDriverTest.java}                           |  52 ++++++--
 .../configuration/ConfigurationManagerImpl.java    |  17 ++-
 .../com/cloud/hypervisor/HypervisorGuruBase.java   |   6 +-
 .../main/java/com/cloud/hypervisor/KVMGuru.java    |  31 +++++
 .../com/cloud/hypervisor/kvm/dpdk/DPDKHelper.java  |  66 ++++++++++
 .../cloud/hypervisor/kvm/dpdk/DPDKHelperImpl.java  |  68 +++++++++++
 .../core/spring-server-core-misc-context.xml       |   2 +
 .../java/com/cloud/hypervisor/KVMGuruTest.java     |  52 +++++++-
 .../hypervisor/kvm/dpdk/DPDKHelperImplTest.java    | 135 +++++++++++++++++++++
 15 files changed, 615 insertions(+), 80 deletions(-)

diff --git a/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql b/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
index d4de8c4..e2221f2 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41200to41300.sql
@@ -19,3 +19,5 @@
 -- Schema upgrade from 4.12.0.0 to 4.13.0.0
 --;
 
+-- DPDK client and server mode support
+ALTER TABLE `cloud`.`service_offering_details` CHANGE COLUMN `value` `value` TEXT NOT NULL;
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriver.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriver.java
new file mode 100644
index 0000000..f5306e2
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriver.java
@@ -0,0 +1,56 @@
+/*
+ * 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.dpdk;
+
+import com.cloud.utils.component.Adapter;
+
+import java.util.Map;
+
+public interface DPDKDriver extends Adapter {
+
+    /**
+     * Get the next DPDK port name to be created
+     */
+    String getNextDpdkPort();
+
+    /**
+     * Get the latest DPDK port number created on a DPDK enabled host
+     */
+    int getDpdkLatestPortNumberUsed();
+
+    /**
+     * Add OVS port (if it does not exist) to bridge with DPDK support
+     */
+    void addDpdkPort(String bridgeName, String port, String vlan, DPDKHelper.VHostUserMode vHostUserMode, String dpdkOvsPath);
+
+    /**
+     * Since DPDK user client/server mode, retrieve the guest interfaces mode from the DPDK vHost User mode
+     */
+    String getGuestInterfacesModeFromDPDKVhostUserMode(DPDKHelper.VHostUserMode dpdKvHostUserMode);
+
+    /**
+     * Get DPDK vHost User mode from extra config. If it is not present, server is returned as default
+     */
+    DPDKHelper.VHostUserMode getDPDKvHostUserMode(Map<String, String> extraConfig);
+
+    /**
+     * Check for additional extra 'dpdk-interface' configurations, return them appended
+     */
+    String getExtraDpdkProperties(Map<String, String> extraConfig);
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriverImpl.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriverImpl.java
new file mode 100644
index 0000000..71da0ae
--- /dev/null
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriverImpl.java
@@ -0,0 +1,115 @@
+/*
+ * 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.dpdk;
+
+import com.cloud.utils.component.AdapterBase;
+import com.cloud.utils.script.Script;
+import org.apache.commons.lang.StringUtils;
+import org.apache.log4j.Logger;
+
+import java.util.Map;
+
+public class DPDKDriverImpl extends AdapterBase implements DPDKDriver {
+    static final String DPDK_PORT_PREFIX = "csdpdk-";
+
+    private final String dpdkPortVhostUserType = "dpdkvhostuser";
+    private final String dpdkPortVhostUserClientType = "dpdkvhostuserclient";
+
+    private static final Logger s_logger = Logger.getLogger(DPDKDriver.class);
+
+    public DPDKDriverImpl() {
+    }
+
+    /**
+     * Get the next DPDK port name to be created
+     */
+    public String getNextDpdkPort() {
+        int portNumber = getDpdkLatestPortNumberUsed();
+        return DPDK_PORT_PREFIX + String.valueOf(portNumber + 1);
+    }
+
+    /**
+     * Get the latest DPDK port number created on a DPDK enabled host
+     */
+    public int getDpdkLatestPortNumberUsed() {
+        s_logger.debug("Checking the last DPDK port created");
+        String cmd = "ovs-vsctl show | grep Port | grep " + DPDK_PORT_PREFIX + " | " +
+                "awk '{ print $2 }' | sort -rV | head -1";
+        String port = Script.runSimpleBashScript(cmd);
+        int portNumber = 0;
+        if (StringUtils.isNotBlank(port)) {
+            String unquotedPort = port.replace("\"", "");
+            String dpdkPortNumber = unquotedPort.split(DPDK_PORT_PREFIX)[1];
+            portNumber = Integer.valueOf(dpdkPortNumber);
+        }
+        return portNumber;
+    }
+
+    /**
+     * Add OVS port (if it does not exist) to bridge with DPDK support
+     */
+    public void addDpdkPort(String bridgeName, String port, String vlan, DPDKHelper.VHostUserMode vHostUserMode, String dpdkOvsPath) {
+        String type = vHostUserMode == DPDKHelper.VHostUserMode.SERVER ?
+                dpdkPortVhostUserType :
+                dpdkPortVhostUserClientType;
+
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append(String.format("ovs-vsctl add-port %s %s " +
+                "vlan_mode=access tag=%s " +
+                "-- set Interface %s type=%s", bridgeName, port, vlan, port, type));
+
+        if (vHostUserMode == DPDKHelper.VHostUserMode.CLIENT) {
+            stringBuilder.append(String.format(" options:vhost-server-path=%s/%s",
+                    dpdkOvsPath, port));
+        }
+
+        String cmd = stringBuilder.toString();
+        s_logger.debug("DPDK property enabled, executing: " + cmd);
+        Script.runSimpleBashScript(cmd);
+    }
+
+    /**
+     * Since DPDK user client/server mode, retrieve the guest interfaces mode from the DPDK vHost User mode
+     */
+    public String getGuestInterfacesModeFromDPDKVhostUserMode(DPDKHelper.VHostUserMode dpdKvHostUserMode) {
+        return dpdKvHostUserMode == DPDKHelper.VHostUserMode.CLIENT ? "server" : "client";
+    }
+
+    /**
+     * Get DPDK vHost User mode from extra config. If it is not present, server is returned as default
+     */
+    public DPDKHelper.VHostUserMode getDPDKvHostUserMode(Map<String, String> extraConfig) {
+        return extraConfig.containsKey(DPDKHelper.DPDK_VHOST_USER_MODE) ?
+                DPDKHelper.VHostUserMode.fromValue(extraConfig.get(DPDKHelper.DPDK_VHOST_USER_MODE)) :
+                DPDKHelper.VHostUserMode.SERVER;
+    }
+
+    /**
+     * Check for additional extra 'dpdk-interface' configurations, return them appended
+     */
+    public String getExtraDpdkProperties(Map<String, String> extraConfig) {
+        StringBuilder stringBuilder = new StringBuilder();
+        for (String key : extraConfig.keySet()) {
+            if (key.startsWith(DPDKHelper.DPDK_INTERFACE_PREFIX)) {
+                stringBuilder.append(extraConfig.get(key));
+            }
+        }
+        return stringBuilder.toString();
+    }
+}
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index ee47a47..5f2a911 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -46,8 +46,8 @@ import javax.xml.parsers.DocumentBuilder;
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.parsers.ParserConfigurationException;
 
+import com.cloud.hypervisor.kvm.dpdk.DPDKHelper;
 import com.cloud.resource.RequestWrapper;
-import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 import org.apache.cloudstack.storage.to.TemplateObjectTO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
@@ -524,9 +524,6 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
 
     protected boolean dpdkSupport = false;
     protected String dpdkOvsPath;
-    protected static final String DPDK_NUMA = ApiConstants.EXTRA_CONFIG + "-dpdk-numa";
-    protected static final String DPDK_HUGE_PAGES = ApiConstants.EXTRA_CONFIG + "-dpdk-hugepages";
-    protected static final String DPDK_INTERFACE_PREFIX = ApiConstants.EXTRA_CONFIG + "-dpdk-interface-";
 
     private String getEndIpFromStartIp(final String startIp, final int numIps) {
         final String[] tokens = startIp.split("[.]");
@@ -2073,7 +2070,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
         vm.setPlatformEmulator(vmTO.getPlatformEmulator());
 
         Map<String, String> extraConfig = vmTO.getExtraConfig();
-        if (dpdkSupport && (!extraConfig.containsKey(DPDK_NUMA) || !extraConfig.containsKey(DPDK_HUGE_PAGES))) {
+        if (dpdkSupport && (!extraConfig.containsKey(DPDKHelper.DPDK_NUMA) || !extraConfig.containsKey(DPDKHelper.DPDK_HUGE_PAGES))) {
             s_logger.info("DPDK is enabled but it needs extra configurations for CPU NUMA and Huge Pages for VM deployment");
         }
 
@@ -2110,7 +2107,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
         grd.setVcpuNum(vcpus);
         vm.addComp(grd);
 
-        if (!extraConfig.containsKey(DPDK_NUMA)) {
+        if (!extraConfig.containsKey(DPDKHelper.DPDK_NUMA)) {
             final CpuModeDef cmd = new CpuModeDef();
             cmd.setMode(_guestCpuMode);
             cmd.setModel(_guestCpuModel);
@@ -2238,7 +2235,7 @@ public class LibvirtComputingResource extends ServerResourceBase implements Serv
         if (MapUtils.isNotEmpty(extraConfig)) {
             StringBuilder extraConfigBuilder = new StringBuilder();
             for (String key : extraConfig.keySet()) {
-                if (!key.startsWith(DPDK_INTERFACE_PREFIX)) {
+                if (!key.startsWith(DPDKHelper.DPDK_INTERFACE_PREFIX) && !key.equals(DPDKHelper.DPDK_VHOST_USER_MODE)) {
                     extraConfigBuilder.append(extraConfig.get(key));
                 }
             }
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
index 617c278..400e16d 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtVMDef.java
@@ -1017,6 +1017,7 @@ public class LibvirtVMDef {
         private String _dpdkSourcePath;
         private String _dpdkSourcePort;
         private String _dpdkExtraLines;
+        private String _interfaceMode;
 
         public void defBridgeNet(String brName, String targetBrName, String macAddr, NicModel model) {
             defBridgeNet(brName, targetBrName, macAddr, model, 0);
@@ -1031,7 +1032,8 @@ public class LibvirtVMDef {
             _networkRateKBps = networkRateKBps;
         }
 
-        public void defDpdkNet(String dpdkSourcePath, String dpdkPort, String macAddress, NicModel model, Integer networkRateKBps, String extra) {
+        public void defDpdkNet(String dpdkSourcePath, String dpdkPort, String macAddress, NicModel model,
+                               Integer networkRateKBps, String extra, String interfaceMode) {
             _netType = GuestNetType.VHOSTUSER;
             _dpdkSourcePath = dpdkSourcePath;
             _dpdkSourcePort = dpdkPort;
@@ -1039,6 +1041,7 @@ public class LibvirtVMDef {
             _model = model;
             _networkRateKBps = networkRateKBps;
             _dpdkExtraLines = extra;
+            _interfaceMode = interfaceMode;
         }
 
         public void defDirectNet(String sourceName, String targetName, String macAddr, NicModel model, String sourceMode) {
@@ -1184,7 +1187,8 @@ public class LibvirtVMDef {
             } else if (_netType == GuestNetType.DIRECT) {
                 netBuilder.append("<source dev='" + _sourceName + "' mode='" + _netSourceMode + "'/>\n");
             } else if (_netType == GuestNetType.VHOSTUSER) {
-                netBuilder.append("<source type='unix' path='"+ _dpdkSourcePath + _dpdkSourcePort + "' mode='client'/>\n");
+                netBuilder.append("<source type='unix' path='"+ _dpdkSourcePath + _dpdkSourcePort +
+                        "' mode='" + _interfaceMode + "'/>\n");
             }
             if (_networkName != null) {
                 netBuilder.append("<target dev='" + _networkName + "'/>\n");
diff --git a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java
index db3ee4d..8208530 100644
--- a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java
+++ b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/OvsVifDriver.java
@@ -24,6 +24,9 @@ import java.util.Map;
 
 import javax.naming.ConfigurationException;
 
+import com.cloud.hypervisor.kvm.dpdk.DPDKDriver;
+import com.cloud.hypervisor.kvm.dpdk.DPDKDriverImpl;
+import com.cloud.hypervisor.kvm.dpdk.DPDKHelper;
 import com.cloud.utils.exception.CloudRuntimeException;
 import org.apache.commons.lang.StringUtils;
 import org.apache.log4j.Logger;
@@ -41,8 +44,7 @@ import com.cloud.utils.script.Script;
 public class OvsVifDriver extends VifDriverBase {
     private static final Logger s_logger = Logger.getLogger(OvsVifDriver.class);
     private int _timeout;
-
-    protected static final String DPDK_PORT_PREFIX = "csdpdk-";
+    private DPDKDriver dpdkDriver;
 
     @Override
     public void configure(Map<String, Object> params) throws ConfigurationException {
@@ -55,6 +57,11 @@ public class OvsVifDriver extends VifDriverBase {
             networkScriptsDir = "scripts/vm/network/vnet";
         }
 
+        String dpdk = (String) params.get("openvswitch.dpdk.enabled");
+        if (StringUtils.isNotBlank(dpdk) && Boolean.parseBoolean(dpdk)) {
+            dpdkDriver = new DPDKDriverImpl();
+        }
+
         String value = (String)params.get("scripts.timeout");
         _timeout = NumbersUtil.parseInt(value, 30 * 60) * 1000;
     }
@@ -80,55 +87,6 @@ public class OvsVifDriver extends VifDriverBase {
         s_logger.debug("done looking for pifs, no more bridges");
     }
 
-    /**
-     * Get the latest DPDK port number created on a DPDK enabled host
-     */
-    protected int getDpdkLatestPortNumberUsed() {
-        s_logger.debug("Checking the last DPDK port created");
-        String cmd = "ovs-vsctl show | grep Port | grep " + DPDK_PORT_PREFIX + " | " +
-                "awk '{ print $2 }' | sort -rV | head -1";
-        String port = Script.runSimpleBashScript(cmd);
-        int portNumber = 0;
-        if (StringUtils.isNotBlank(port)) {
-            String unquotedPort = port.replace("\"", "");
-            String dpdkPortNumber = unquotedPort.split(DPDK_PORT_PREFIX)[1];
-            portNumber = Integer.valueOf(dpdkPortNumber);
-        }
-        return portNumber;
-    }
-
-    /**
-     * Get the next DPDK port name to be created
-     */
-    protected String getNextDpdkPort() {
-        int portNumber = getDpdkLatestPortNumberUsed();
-        return DPDK_PORT_PREFIX + String.valueOf(portNumber + 1);
-    }
-
-    /**
-     * Add OVS port (if it does not exist) to bridge with DPDK support
-     */
-    protected void addDpdkPort(String bridgeName, String port, String vlan) {
-        String cmd = String.format("ovs-vsctl add-port %s %s " +
-                "vlan_mode=access tag=%s " +
-                "-- set Interface %s type=dpdkvhostuser", bridgeName, port, vlan, port);
-        s_logger.debug("DPDK property enabled, executing: " + cmd);
-        Script.runSimpleBashScript(cmd);
-    }
-
-    /**
-     * Check for additional extra 'dpdk-interface' configurations, return them appended
-     */
-    private String getExtraDpdkProperties(Map<String, String> extraConfig) {
-        StringBuilder stringBuilder = new StringBuilder();
-        for (String key : extraConfig.keySet()) {
-            if (key.startsWith(LibvirtComputingResource.DPDK_INTERFACE_PREFIX)) {
-                stringBuilder.append(extraConfig.get(key));
-            }
-        }
-        return stringBuilder.toString();
-    }
-
     @Override
     public InterfaceDef plug(NicTO nic, String guestOsType, String nicAdapter, Map<String, String> extraConfig) throws InternalErrorException, LibvirtException {
         s_logger.debug("plugging nic=" + nic);
@@ -158,12 +116,18 @@ public class OvsVifDriver extends VifDriverBase {
                 if (trafficLabel != null && !trafficLabel.isEmpty()) {
                     if (_libvirtComputingResource.dpdkSupport && !nic.isDpdkDisabled()) {
                         s_logger.debug("DPDK support enabled: configuring per traffic label " + trafficLabel);
-                        if (StringUtils.isBlank(_libvirtComputingResource.dpdkOvsPath)) {
+                        String dpdkOvsPath = _libvirtComputingResource.dpdkOvsPath;
+                        if (StringUtils.isBlank(dpdkOvsPath)) {
                             throw new CloudRuntimeException("DPDK is enabled on the host but no OVS path has been provided");
                         }
-                        String port = getNextDpdkPort();
-                        addDpdkPort(_pifs.get(trafficLabel), port, vlanId);
-                        intf.defDpdkNet(_libvirtComputingResource.dpdkOvsPath, port, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), 0, getExtraDpdkProperties(extraConfig));
+                        String port = dpdkDriver.getNextDpdkPort();
+                        DPDKHelper.VHostUserMode dpdKvHostUserMode = dpdkDriver.getDPDKvHostUserMode(extraConfig);
+                        dpdkDriver.addDpdkPort(_pifs.get(trafficLabel), port, vlanId, dpdKvHostUserMode, dpdkOvsPath);
+                        String interfaceMode = dpdkDriver.getGuestInterfacesModeFromDPDKVhostUserMode(dpdKvHostUserMode);
+                        intf.defDpdkNet(dpdkOvsPath, port, nic.getMac(),
+                                getGuestNicModel(guestOsType, nicAdapter), 0,
+                                dpdkDriver.getExtraDpdkProperties(extraConfig),
+                                interfaceMode);
                     } else {
                         s_logger.debug("creating a vlan dev and bridge for guest traffic per traffic label " + trafficLabel);
                         intf.defBridgeNet(_pifs.get(trafficLabel), null, nic.getMac(), getGuestNicModel(guestOsType, nicAdapter), networkRateKBps);
diff --git a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/OvsVifDriverTest.java b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriverTest.java
similarity index 53%
rename from plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/OvsVifDriverTest.java
rename to plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriverTest.java
index 71a6353..a2a62fe 100644
--- a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/OvsVifDriverTest.java
+++ b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/dpdk/DPDKDriverTest.java
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package com.cloud.hypervisor.kvm.resource;
+package com.cloud.hypervisor.kvm.dpdk;
 
 import com.cloud.utils.script.Script;
 import org.junit.Assert;
@@ -30,19 +30,25 @@ import org.powermock.api.mockito.PowerMockito;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @PrepareForTest({ Script.class })
 @RunWith(PowerMockRunner.class)
-public class OvsVifDriverTest {
+public class DPDKDriverTest {
 
     private static final int dpdkPortNumber = 7;
 
-    private OvsVifDriver driver = new OvsVifDriver();
+    private DPDKDriver driver = new DPDKDriverImpl();
+
+    private Map<String, String> extraConfig;
 
     @Before
     public void initMocks() {
         MockitoAnnotations.initMocks(this);
         PowerMockito.mockStatic(Script.class);
         Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).thenReturn(null);
+        extraConfig = new HashMap<>();
     }
 
     @Test
@@ -53,7 +59,7 @@ public class OvsVifDriverTest {
     @Test
     public void testGetDpdkLatestPortNumberUsedExistingDpdkPorts() {
         Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).
-                thenReturn(OvsVifDriver.DPDK_PORT_PREFIX + String.valueOf(dpdkPortNumber));
+                thenReturn(DPDKDriverImpl.DPDK_PORT_PREFIX + String.valueOf(dpdkPortNumber));
         Assert.assertEquals(dpdkPortNumber, driver.getDpdkLatestPortNumberUsed());
     }
 
@@ -61,15 +67,47 @@ public class OvsVifDriverTest {
     public void testGetNextDpdkPortNoDpdkPorts() {
         Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).
                 thenReturn(null);
-        String expectedPortName = OvsVifDriver.DPDK_PORT_PREFIX + String.valueOf(1);
+        String expectedPortName = DPDKDriverImpl.DPDK_PORT_PREFIX + String.valueOf(1);
         Assert.assertEquals(expectedPortName, driver.getNextDpdkPort());
     }
 
     @Test
     public void testGetNextDpdkPortExistingDpdkPorts() {
         Mockito.when(Script.runSimpleBashScript(Matchers.anyString())).
-                thenReturn(OvsVifDriver.DPDK_PORT_PREFIX + String.valueOf(dpdkPortNumber));
-        String expectedPortName = OvsVifDriver.DPDK_PORT_PREFIX + String.valueOf(dpdkPortNumber + 1);
+                thenReturn(DPDKDriverImpl.DPDK_PORT_PREFIX + String.valueOf(dpdkPortNumber));
+        String expectedPortName = DPDKDriverImpl.DPDK_PORT_PREFIX + String.valueOf(dpdkPortNumber + 1);
         Assert.assertEquals(expectedPortName, driver.getNextDpdkPort());
     }
+
+    @Test
+    public void testGetGuestInterfacesModeFromDPDKVhostUserModeClientDPDK() {
+        String guestMode = driver.getGuestInterfacesModeFromDPDKVhostUserMode(DPDKHelper.VHostUserMode.CLIENT);
+        Assert.assertEquals("server", guestMode);
+    }
+
+    @Test
+    public void testGetGuestInterfacesModeFromDPDKVhostUserModeServerDPDK() {
+        String guestMode = driver.getGuestInterfacesModeFromDPDKVhostUserMode(DPDKHelper.VHostUserMode.SERVER);
+        Assert.assertEquals("client", guestMode);
+    }
+
+    @Test
+    public void testGetDPDKvHostUserModeServerExtraConfig() {
+        extraConfig.put(DPDKHelper.DPDK_VHOST_USER_MODE, DPDKHelper.VHostUserMode.SERVER.toString());
+        DPDKHelper.VHostUserMode dpdKvHostUserMode = driver.getDPDKvHostUserMode(extraConfig);
+        Assert.assertEquals(DPDKHelper.VHostUserMode.SERVER, dpdKvHostUserMode);
+    }
+
+    @Test
+    public void testGetDPDKvHostUserModeServerClientExtraConfig() {
+        extraConfig.put(DPDKHelper.DPDK_VHOST_USER_MODE, DPDKHelper.VHostUserMode.CLIENT.toString());
+        DPDKHelper.VHostUserMode dpdKvHostUserMode = driver.getDPDKvHostUserMode(extraConfig);
+        Assert.assertEquals(DPDKHelper.VHostUserMode.CLIENT, dpdKvHostUserMode);
+    }
+
+    @Test
+    public void testGetDPDKvHostUserModeServerEmptyExtraConfig() {
+        DPDKHelper.VHostUserMode dpdKvHostUserMode = driver.getDPDKvHostUserMode(extraConfig);
+        Assert.assertEquals(DPDKHelper.VHostUserMode.SERVER, dpdKvHostUserMode);
+    }
 }
\ No newline at end of file
diff --git a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index cebc9e1..8df1750 100755
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -16,8 +16,10 @@
 // under the License.
 package com.cloud.configuration;
 
+import java.io.UnsupportedEncodingException;
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.net.URLDecoder;
 import java.sql.Date;
 import java.sql.PreparedStatement;
 import java.util.ArrayList;
@@ -2492,17 +2494,26 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati
             }
             detailsVO = new ArrayList<ServiceOfferingDetailsVO>();
             for (final Entry<String, String> detailEntry : details.entrySet()) {
+                String detailEntryValue = detailEntry.getValue();
                 if (detailEntry.getKey().equals(GPU.Keys.pciDevice.toString())) {
-                    if (detailEntry.getValue() == null) {
+                    if (detailEntryValue == null) {
                         throw new InvalidParameterValueException("Please specify a GPU Card.");
                     }
                 }
                 if (detailEntry.getKey().equals(GPU.Keys.vgpuType.toString())) {
-                    if (detailEntry.getValue() == null) {
+                    if (detailEntryValue == null) {
                         throw new InvalidParameterValueException("vGPUType value cannot be null");
                     }
                 }
-                detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), detailEntry.getKey(), detailEntry.getValue(), true));
+                if (detailEntry.getKey().startsWith(ApiConstants.EXTRA_CONFIG)) {
+                    try {
+                        detailEntryValue = URLDecoder.decode(detailEntry.getValue(), "UTF-8");
+                    } catch (UnsupportedEncodingException | IllegalArgumentException e) {
+                        s_logger.error("Cannot decode extra configuration value for key: " + detailEntry.getKey() + ", skipping it");
+                        continue;
+                    }
+                }
+                detailsVO.add(new ServiceOfferingDetailsVO(offering.getId(), detailEntry.getKey(), detailEntryValue, true));
             }
         }
 
diff --git a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
index 445997a..6410b43 100644
--- a/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
+++ b/server/src/main/java/com/cloud/hypervisor/HypervisorGuruBase.java
@@ -72,7 +72,7 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis
     @Inject
     private ResourceManager _resourceMgr;
     @Inject
-    private ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
+    protected ServiceOfferingDetailsDao _serviceOfferingDetailsDao;
     @Inject
     private ServiceOfferingDao _serviceOfferingDao;
 
@@ -172,8 +172,8 @@ public abstract class HypervisorGuruBase extends AdapterBase implements Hypervis
         }
 
         // Set GPU details
-        ServiceOfferingDetailsVO offeringDetail = null;
-        if ((offeringDetail = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString())) != null) {
+        ServiceOfferingDetailsVO offeringDetail = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.vgpuType.toString());
+        if (offeringDetail != null) {
             ServiceOfferingDetailsVO groupName = _serviceOfferingDetailsDao.findDetail(offering.getId(), GPU.Keys.pciDevice.toString());
             to.setGpuDevice(_resourceMgr.getGPUDevice(vm.getHostId(), groupName.getValue(), offeringDetail.getValue()));
         }
diff --git a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java
index c512f06..5d20638 100644
--- a/server/src/main/java/com/cloud/hypervisor/KVMGuru.java
+++ b/server/src/main/java/com/cloud/hypervisor/KVMGuru.java
@@ -22,6 +22,9 @@ import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
 import com.cloud.hypervisor.Hypervisor.HypervisorType;
+import com.cloud.hypervisor.kvm.dpdk.DPDKHelper;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.service.ServiceOfferingDetailsVO;
 import com.cloud.storage.DataStoreRole;
 import com.cloud.storage.GuestOSHypervisorVO;
 import com.cloud.storage.GuestOSVO;
@@ -31,13 +34,16 @@ import com.cloud.utils.Pair;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineProfile;
+import org.apache.cloudstack.api.ApiConstants;
 import org.apache.cloudstack.storage.command.CopyCommand;
 import org.apache.cloudstack.storage.command.StorageSubSystemCommand;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 
 import javax.inject.Inject;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
+import java.util.List;
 import java.util.Map;
 
 public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
@@ -47,6 +53,8 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
     GuestOSHypervisorDao _guestOsHypervisorDao;
     @Inject
     HostDao _hostDao;
+    @Inject
+    DPDKHelper dpdkHelper;
 
     public static final Logger s_logger = Logger.getLogger(KVMGuru.class);
 
@@ -106,6 +114,11 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
     public VirtualMachineTO implement(VirtualMachineProfile vm) {
         VirtualMachineTO to = toVirtualMachineTO(vm);
         setVmQuotaPercentage(to, vm);
+        addServiceOfferingExtraConfiguration(to, vm);
+
+        if (dpdkHelper.isDPDKvHostUserModeSettingOnServiceOffering(vm)) {
+            dpdkHelper.setDpdkVhostUserMode(to, vm);
+        }
 
         // Determine the VM's OS description
         GuestOSVO guestOS = _guestOsDao.findByIdIncludingRemoved(vm.getVirtualMachine().getGuestOSId());
@@ -124,6 +137,24 @@ public class KVMGuru extends HypervisorGuruBase implements HypervisorGuru {
         return to;
     }
 
+    /**
+     * Add extra configurations from service offering to the VM TO.
+     * Extra configuration keys are expected in formats:
+     * - "extraconfig-N"
+     * - "extraconfig-CONFIG_NAME"
+     */
+    protected void addServiceOfferingExtraConfiguration(VirtualMachineTO to, VirtualMachineProfile vmProfile) {
+        ServiceOffering offering = vmProfile.getServiceOffering();
+        List<ServiceOfferingDetailsVO> details = _serviceOfferingDetailsDao.listDetails(offering.getId());
+        if (CollectionUtils.isNotEmpty(details)) {
+            for (ServiceOfferingDetailsVO detail : details) {
+                if (detail.getName().startsWith(ApiConstants.EXTRA_CONFIG)) {
+                    to.addExtraConfig(detail.getName(), detail.getValue());
+                }
+            }
+        }
+    }
+
     @Override
     public Pair<Boolean, Long> getCommandHostDelegation(long hostId, Command cmd) {
         if (cmd instanceof StorageSubSystemCommand) {
diff --git a/server/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelper.java b/server/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelper.java
new file mode 100644
index 0000000..f1dadc0
--- /dev/null
+++ b/server/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelper.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 com.cloud.hypervisor.kvm.dpdk;
+
+import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.vm.VirtualMachineProfile;
+import org.apache.cloudstack.api.ApiConstants;
+
+public interface DPDKHelper {
+
+    String DPDK_VHOST_USER_MODE = "DPDK-VHOSTUSER";
+    String DPDK_NUMA = ApiConstants.EXTRA_CONFIG + "-dpdk-numa";
+    String DPDK_HUGE_PAGES = ApiConstants.EXTRA_CONFIG + "-dpdk-hugepages";
+    String DPDK_INTERFACE_PREFIX = ApiConstants.EXTRA_CONFIG + "-dpdk-interface-";
+
+    enum VHostUserMode {
+        CLIENT("client"), SERVER("server");
+
+        private String str;
+
+        VHostUserMode(String str) {
+            this.str = str;
+        }
+
+        public static VHostUserMode fromValue(String val) {
+            if (val.equalsIgnoreCase("client")) {
+                return CLIENT;
+            } else if (val.equalsIgnoreCase("server")) {
+                return SERVER;
+            } else {
+                throw new IllegalArgumentException("Invalid DPDK vHost User mode:" + val);
+            }
+        }
+
+        @Override
+        public String toString() {
+            return str;
+        }
+    }
+
+    /**
+     * True if the DPDK vHost user mode setting is part of the VM service offering details, false if not.
+     * @param vm
+     */
+    boolean isDPDKvHostUserModeSettingOnServiceOffering(VirtualMachineProfile vm);
+
+    /**
+     * Add DPDK vHost User Mode as extra configuration to the VM TO (if present on the VM service offering details)
+     */
+    void setDpdkVhostUserMode(VirtualMachineTO to, VirtualMachineProfile vm);
+
+}
diff --git a/server/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelperImpl.java b/server/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelperImpl.java
new file mode 100644
index 0000000..70d448f
--- /dev/null
+++ b/server/src/main/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelperImpl.java
@@ -0,0 +1,68 @@
+// 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.dpdk;
+
+import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.service.ServiceOfferingDetailsVO;
+import com.cloud.service.dao.ServiceOfferingDetailsDao;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.vm.VirtualMachineProfile;
+import org.apache.log4j.Logger;
+
+import javax.inject.Inject;
+
+public class DPDKHelperImpl implements DPDKHelper {
+
+    @Inject
+    ServiceOfferingDetailsDao serviceOfferingDetailsDao;
+
+    public static final Logger s_logger = Logger.getLogger(DPDKHelperImpl.class);
+
+    private ServiceOffering getServiceOfferingFromVMProfile(VirtualMachineProfile virtualMachineProfile) {
+        ServiceOffering offering = virtualMachineProfile.getServiceOffering();
+        if (offering == null) {
+            throw new CloudRuntimeException("VM does not have an associated service offering");
+        }
+        return offering;
+    }
+
+    @Override
+    public boolean isDPDKvHostUserModeSettingOnServiceOffering(VirtualMachineProfile vm) {
+        ServiceOffering offering = getServiceOfferingFromVMProfile(vm);
+        ServiceOfferingDetailsVO detail = serviceOfferingDetailsDao.findDetail(offering.getId(), DPDK_VHOST_USER_MODE);
+        return detail != null;
+    }
+
+    @Override
+    public void setDpdkVhostUserMode(VirtualMachineTO to, VirtualMachineProfile vm) {
+        ServiceOffering offering = getServiceOfferingFromVMProfile(vm);
+        ServiceOfferingDetailsVO detail = serviceOfferingDetailsDao.findDetail(offering.getId(), DPDK_VHOST_USER_MODE);
+        if (detail != null) {
+            String mode = detail.getValue();
+            try {
+                VHostUserMode dpdKvHostUserMode = VHostUserMode.fromValue(mode);
+                to.addExtraConfig(DPDK_VHOST_USER_MODE, dpdKvHostUserMode.toString());
+            } catch (IllegalArgumentException e) {
+                s_logger.error(String.format("DPDK vHost User mode found as a detail for service offering: %s " +
+                                "but value: %s is not supported. Supported values: %s, %s",
+                        offering.getId(), mode,
+                        VHostUserMode.CLIENT.toString(), VHostUserMode.SERVER.toString()));
+            }
+        }
+    }
+}
diff --git a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml
index 481db24..a201250 100644
--- a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml
+++ b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-misc-context.xml
@@ -78,5 +78,7 @@
     <bean id="ExternalIpAddressAllocator" class="com.cloud.network.ExternalIpAddressAllocator">
         <property name="name" value="Basic" />
     </bean>
+
+    <bean id="DPDKHelper" class="com.cloud.hypervisor.kvm.dpdk.DPDKHelperImpl" />
     
 </beans>
\ No newline at end of file
diff --git a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java
index 597e20f..e4deffd 100644
--- a/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java
+++ b/server/src/test/java/com/cloud/hypervisor/KVMGuruTest.java
@@ -19,9 +19,13 @@ package com.cloud.hypervisor;
 import com.cloud.agent.api.to.VirtualMachineTO;
 import com.cloud.host.HostVO;
 import com.cloud.host.dao.HostDao;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.service.ServiceOfferingDetailsVO;
+import com.cloud.service.dao.ServiceOfferingDetailsDao;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.vm.VirtualMachine;
 import com.cloud.vm.VirtualMachineProfile;
+import org.apache.cloudstack.api.ApiConstants;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,11 +35,16 @@ import org.mockito.Mockito;
 import org.mockito.Spy;
 import org.mockito.runners.MockitoJUnitRunner;
 
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+
 @RunWith(MockitoJUnitRunner.class)
 public class KVMGuruTest {
 
     @Mock
     HostDao hostDao;
+    @Mock
+    ServiceOfferingDetailsDao serviceOfferingDetailsDao;
 
     @Spy
     @InjectMocks
@@ -49,18 +58,42 @@ public class KVMGuruTest {
     VirtualMachine vm;
     @Mock
     HostVO host;
+    @Mock
+    ServiceOffering serviceOffering;
+    @Mock
+    ServiceOfferingDetailsVO detail1;
+    @Mock
+    ServiceOfferingDetailsVO detail2;
+
+    private static final long hostId = 1L;
+    private static final Long offeringId = 1L;
 
-    private static final long hostId = 1l;
+    private static final String detail1Key = ApiConstants.EXTRA_CONFIG + "-config-1";
+    private static final String detail1Value = "value1";
+    private static final String detail2Key = "detail2";
+    private static final String detail2Value = "value2";
 
     @Before
-    public void setup() {
+    public void setup() throws UnsupportedEncodingException {
         Mockito.when(vmTO.getLimitCpuUse()).thenReturn(true);
         Mockito.when(vmProfile.getVirtualMachine()).thenReturn(vm);
         Mockito.when(vm.getHostId()).thenReturn(hostId);
         Mockito.when(hostDao.findById(hostId)).thenReturn(host);
         Mockito.when(host.getCpus()).thenReturn(3);
-        Mockito.when(host.getSpeed()).thenReturn(1995l);
+        Mockito.when(host.getSpeed()).thenReturn(1995L);
         Mockito.when(vmTO.getMaxSpeed()).thenReturn(500);
+        Mockito.when(serviceOffering.getId()).thenReturn(offeringId);
+        Mockito.when(vmProfile.getServiceOffering()).thenReturn(serviceOffering);
+
+        Mockito.when(detail1.getName()).thenReturn(detail1Key);
+        Mockito.when(detail1.getValue()).thenReturn(detail1Value);
+        Mockito.when(detail1.getResourceId()).thenReturn(offeringId);
+        Mockito.when(detail2.getName()).thenReturn(detail2Key);
+        Mockito.when(detail2.getResourceId()).thenReturn(offeringId);
+        Mockito.when(detail2.getValue()).thenReturn(detail2Value);
+
+        Mockito.when(serviceOfferingDetailsDao.listDetails(offeringId)).thenReturn(
+                Arrays.asList(detail1, detail2));
     }
 
     @Test
@@ -96,4 +129,17 @@ public class KVMGuruTest {
         guru.setVmQuotaPercentage(vmTO, vmProfile);
         Mockito.verify(vmTO).setCpuQuotaPercentage(1d);
     }
+
+    @Test
+    public void testAddServiceOfferingExtraConfigurationDpdkDetails() {
+        guru.addServiceOfferingExtraConfiguration(vmTO, vmProfile);
+        Mockito.verify(vmTO).addExtraConfig(detail1Key, detail1Value);
+    }
+
+    @Test
+    public void testAddServiceOfferingExtraConfigurationEmptyDetails() {
+        Mockito.when(serviceOfferingDetailsDao.listDetails(offeringId)).thenReturn(null);
+        guru.addServiceOfferingExtraConfiguration(vmTO, vmProfile);
+        Mockito.verify(vmTO, Mockito.never()).addExtraConfig(Mockito.anyString(), Mockito.anyString());
+    }
 }
\ No newline at end of file
diff --git a/server/src/test/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelperImplTest.java b/server/src/test/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelperImplTest.java
new file mode 100644
index 0000000..b666ad4
--- /dev/null
+++ b/server/src/test/java/com/cloud/hypervisor/kvm/dpdk/DPDKHelperImplTest.java
@@ -0,0 +1,135 @@
+// 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.dpdk;
+
+import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.offering.ServiceOffering;
+import com.cloud.service.ServiceOfferingDetailsVO;
+import com.cloud.service.dao.ServiceOfferingDetailsDao;
+import com.cloud.vm.VirtualMachineProfile;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import java.io.UnsupportedEncodingException;
+import java.net.URLEncoder;
+import java.util.Arrays;
+
+@RunWith(MockitoJUnitRunner.class)
+public class DPDKHelperImplTest {
+
+    @Mock
+    ServiceOfferingDetailsDao serviceOfferingDetailsDao;
+
+    @Spy
+    @InjectMocks
+    private DPDKHelper dpdkHelper = new DPDKHelperImpl();
+
+    @Mock
+    VirtualMachineTO vmTO;
+    @Mock
+    VirtualMachineProfile vmProfile;
+    @Mock
+    ServiceOfferingDetailsVO dpdkVhostUserModeDetailVO;
+    @Mock
+    ServiceOfferingDetailsVO dpdkNumaDetailVO;
+    @Mock
+    ServiceOfferingDetailsVO dpdkHugePagesDetailVO;
+    @Mock
+    ServiceOffering serviceOffering;
+
+    private String dpdkVhostMode = DPDKHelper.VHostUserMode.SERVER.toString();
+
+    private static final String dpdkNumaConf =
+            "<cpu mode=\"host-passthrough\">\n" +
+                    "  <numa>\n" +
+                    "    <cell id=\"0\" cpus=\"0\" memory=\"9437184\" unit=\"KiB\" memAccess=\"shared\"/>\n" +
+                    "  </numa>\n" +
+                    "</cpu>";
+    private static final String dpdkHugePagesConf =
+            "<memoryBacking>\n" +
+                    "  <hugePages/>\n" +
+                    "</memoryBacking>";
+    private static String dpdkNumaValue;
+    private static String dpdkHugePagesValue;
+    private static final Long offeringId = 1L;
+
+    @Before
+    public void setup() throws UnsupportedEncodingException {
+        dpdkHugePagesValue = URLEncoder.encode(dpdkHugePagesConf, "UTF-8");
+        dpdkNumaValue = URLEncoder.encode(dpdkNumaConf, "UTF-8");
+
+        Mockito.when(dpdkVhostUserModeDetailVO.getName()).thenReturn(DPDKHelper.DPDK_VHOST_USER_MODE);
+        Mockito.when(dpdkVhostUserModeDetailVO.getValue()).thenReturn(dpdkVhostMode);
+        Mockito.when(dpdkVhostUserModeDetailVO.getResourceId()).thenReturn(offeringId);
+        Mockito.when(dpdkNumaDetailVO.getName()).thenReturn(DPDKHelper.DPDK_NUMA);
+        Mockito.when(dpdkNumaDetailVO.getResourceId()).thenReturn(offeringId);
+        Mockito.when(dpdkNumaDetailVO.getValue()).thenReturn(dpdkNumaValue);
+        Mockito.when(dpdkHugePagesDetailVO.getName()).thenReturn(DPDKHelper.DPDK_HUGE_PAGES);
+        Mockito.when(dpdkHugePagesDetailVO.getResourceId()).thenReturn(offeringId);
+        Mockito.when(dpdkHugePagesDetailVO.getValue()).thenReturn(dpdkHugePagesValue);
+
+        Mockito.when(serviceOfferingDetailsDao.listDetails(offeringId)).thenReturn(
+                Arrays.asList(dpdkNumaDetailVO, dpdkHugePagesDetailVO, dpdkVhostUserModeDetailVO));
+        Mockito.when(vmProfile.getServiceOffering()).thenReturn(serviceOffering);
+        Mockito.when(serviceOffering.getId()).thenReturn(offeringId);
+    }
+
+    @Test
+    public void testSetDpdkVhostUserModeValidDetail() {
+        Mockito.when(serviceOfferingDetailsDao.findDetail(offeringId, DPDKHelper.DPDK_VHOST_USER_MODE)).
+                thenReturn(dpdkVhostUserModeDetailVO);
+        dpdkHelper.setDpdkVhostUserMode(vmTO, vmProfile);
+        Mockito.verify(vmTO).addExtraConfig(DPDKHelper.DPDK_VHOST_USER_MODE, dpdkVhostMode);
+    }
+
+    @Test
+    public void testSetDpdkVhostUserModeInvalidDetail() {
+        Mockito.when(dpdkVhostUserModeDetailVO.getValue()).thenReturn("serverrrr");
+        Mockito.verify(vmTO, Mockito.never()).addExtraConfig(Mockito.anyString(), Mockito.anyString());
+    }
+
+    @Test
+    public void testSetDpdkVhostUserModeNotExistingDetail() {
+        Mockito.when(serviceOfferingDetailsDao.listDetails(offeringId)).thenReturn(
+                Arrays.asList(dpdkNumaDetailVO, dpdkHugePagesDetailVO));
+        Mockito.verify(vmTO, Mockito.never()).addExtraConfig(Mockito.anyString(), Mockito.anyString());
+    }
+
+    @Test
+    public void testDPDKvHostUserFromValueClient() {
+        DPDKHelper.VHostUserMode mode = DPDKHelper.VHostUserMode.fromValue("client");
+        Assert.assertEquals(DPDKHelper.VHostUserMode.CLIENT, mode);
+    }
+
+    @Test
+    public void testDPDKvHostUserFromValueServer() {
+        DPDKHelper.VHostUserMode mode = DPDKHelper.VHostUserMode.fromValue("server");
+        Assert.assertEquals(DPDKHelper.VHostUserMode.SERVER, mode);
+    }
+
+    @Test(expected = IllegalArgumentException.class)
+    public void testDPDKvHostUserFromValueServerInvalid() {
+        DPDKHelper.VHostUserMode.fromValue("serverrrr");
+    }
+}