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

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


The following commit(s) were added to refs/heads/4.18 by this push:
     new beebeed5e23 framework/cluster: improve cluster service and integration 
API service
beebeed5e23 is described below

commit beebeed5e23ea7dd8391d914d92017cd49cee95a
Author: Abhishek Kumar <[email protected]>
AuthorDate: Tue Jul 2 17:52:52 2024 +0530

    framework/cluster: improve cluster service and integration API service
    
    - mTLS implementation for cluster service communication
    - Listen only on the specified cluster node IP address instead of all 
interfaces
    - Validate incoming cluster service requests are from peer management 
servers based on the server's certificate dns name which can be through global 
config - ca.framework.cert.management.custom.san
    - Hardening of KVM command wrapper script execution
    - Improve API server integration port check
    - cloudstack-management.default: don't have JMX configuration if not 
needed. JMX is used for instrumentation; users who need to use it should enable 
it explicitly
    
    Co-authored-by: Abhishek Kumar <[email protected]>
    Co-authored-by: Wei Zhou <[email protected]>
    Co-authored-by: Rohit Yadav <[email protected]>
    
    Signed-off-by: Abhishek Kumar <[email protected]>
---
 .../java/org/apache/cloudstack/ca/CAManager.java   |   8 +
 .../java/com/cloud/resource/CommandWrapper.java    |  27 +++-
 .../apache/cloudstack/framework/ca/CAProvider.java |   4 +
 .../apache/cloudstack/framework/ca/CAService.java  |   3 +
 .../java/com/cloud/cluster/ClusterManager.java     |   4 +-
 .../java/com/cloud/cluster/ClusterManagerImpl.java |   7 +-
 .../com/cloud/cluster/ClusterServiceAdapter.java   |   2 -
 .../cluster/ClusterServiceServletAdapter.java      |  18 ++-
 .../cluster/ClusterServiceServletContainer.java    | 111 ++++++++++----
 .../cloud/cluster/ClusterServiceServletImpl.java   | 162 ++++++++++++++-------
 .../com/cloud/cluster/ClusterManagerImplTest.java} |  30 ++--
 .../cluster/ClusterServiceServletAdapterTest.java  |   2 +-
 .../ClusterServiceServletContainerTest.java        |  87 +++++++++++
 .../cluster/ClusterServiceServletImplTest.java     |  64 ++++++++
 packaging/systemd/cloudstack-management.default    |   2 +-
 .../cloudstack/ca/provider/RootCAProvider.java     |  35 ++++-
 .../cloudstack/ca/provider/RootCAProviderTest.java |  62 +++++++-
 .../LibvirtDeleteVMSnapshotCommandWrapper.java     |  17 ++-
 .../LibvirtGetVmIpAddressCommandWrapper.java       |  50 +++++--
 .../LibvirtOvsFetchInterfaceCommandWrapper.java    |  77 ++++++++--
 .../LibvirtPrepareForMigrationCommandWrapper.java  |   5 +-
 .../wrapper/LibvirtReadyCommandWrapper.java        |  19 ++-
 ...virtRevokeDirectDownloadCertificateWrapper.java |  31 ++--
 ...tupDirectDownloadCertificateCommandWrapper.java |  49 +++++--
 .../wrapper/LibvirtStopCommandWrapper.java         |  16 +-
 ...LibvirtOvsFetchInterfaceCommandWrapperTest.java | 105 +++++++++++++
 ...bvirtPrepareForMigrationCommandWrapperTest.java |  45 ++++--
 ...irectDownloadCertificateCommandWrapperTest.java |  93 ++++++++++++
 .../wrapper/LibvirtStopCommandWrapperTest.java     |  63 ++++++++
 server/src/main/java/com/cloud/api/ApiServer.java  |  16 +-
 .../org/apache/cloudstack/ca/CAManagerImpl.java    |  11 +-
 .../src/test/java/com/cloud/api/ApiServerTest.java |  73 ++++++++++
 utils/src/main/java/com/cloud/utils/FileUtil.java  |  21 ++-
 .../main/java/com/cloud/utils/script/Script.java   | 126 ++++++++++++----
 .../java/com/cloud/utils/script/ScriptTest.java    |  81 +++++++++++
 35 files changed, 1282 insertions(+), 244 deletions(-)

diff --git a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java 
b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java
index 12a9d3d7b41..b0fb1ac73c2 100644
--- a/api/src/main/java/org/apache/cloudstack/ca/CAManager.java
+++ b/api/src/main/java/org/apache/cloudstack/ca/CAManager.java
@@ -77,6 +77,14 @@ public interface CAManager extends CAService, Configurable, 
PluggableService {
                                                     "15",
                                                     "The number of days before 
expiry of a client certificate, the validations are checked. Admins are alerted 
when auto-renewal is not allowed, otherwise auto-renewal is attempted.", true, 
ConfigKey.Scope.Cluster);
 
+
+    ConfigKey<String> CertManagementCustomSubjectAlternativeName = new 
ConfigKey<>("Advanced", String.class,
+            "ca.framework.cert.management.custom.san",
+            "cloudstack.internal",
+            "The custom Subject Alternative Name that will be added to the 
management server certificate. " +
+                    "The actual implementation will depend on the configured 
CA provider.",
+            false);
+
     /**
      * Returns a list of available CA provider plugins
      * @return returns list of CAProvider
diff --git a/core/src/main/java/com/cloud/resource/CommandWrapper.java 
b/core/src/main/java/com/cloud/resource/CommandWrapper.java
index d9c1ea234e8..ee6aa161e33 100644
--- a/core/src/main/java/com/cloud/resource/CommandWrapper.java
+++ b/core/src/main/java/com/cloud/resource/CommandWrapper.java
@@ -19,9 +19,12 @@
 
 package com.cloud.resource;
 
+import org.apache.log4j.Logger;
+
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.Command;
-import org.apache.log4j.Logger;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.script.Script;
 
 public abstract class CommandWrapper<T extends Command, A extends Answer, R 
extends ServerResource> {
     protected Logger logger = Logger.getLogger(getClass());
@@ -32,4 +35,26 @@ public abstract class CommandWrapper<T extends Command, A 
extends Answer, R exte
      * @return A and the Answer from the command.
      */
     public abstract A execute(T command, R serverResource);
+
+    protected String sanitizeBashCommandArgument(String input) {
+        StringBuilder sanitized = new StringBuilder();
+        for (char c : input.toCharArray()) {
+            if ("\\\"'`$|&;()<>*?![]{}~".indexOf(c) != -1) {
+                sanitized.append('\\');
+            }
+            sanitized.append(c);
+        }
+        return sanitized.toString();
+    }
+
+    public void removeDpdkPort(String portToRemove) {
+        logger.debug("Removing DPDK port: " + portToRemove);
+        int port;
+        try {
+            port = Integer.valueOf(portToRemove);
+        } catch (NumberFormatException nfe) {
+            throw new CloudRuntimeException(String.format("Invalid DPDK port 
specified: '%s'", portToRemove));
+        }
+        Script.executeCommand("ovs-vsctl", "del-port", String.valueOf(port));
+    }
 }
diff --git 
a/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAProvider.java 
b/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAProvider.java
index 388cae7e007..77b3ee27783 100644
--- 
a/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAProvider.java
+++ 
b/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAProvider.java
@@ -22,6 +22,7 @@ import java.math.BigInteger;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.util.List;
 import java.util.Map;
@@ -45,6 +46,7 @@ public interface CAProvider {
 
     /**
      * Issues certificate with provided options
+     *
      * @param domainNames
      * @param ipAddresses
      * @param validityDays
@@ -104,4 +106,6 @@ public interface CAProvider {
      * @return returns description
      */
     String getDescription();
+
+    boolean isManagementCertificate(java.security.cert.Certificate 
certificate) throws CertificateParsingException;
 }
diff --git 
a/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAService.java 
b/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAService.java
index facf13a5cb6..721c88bee50 100644
--- 
a/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAService.java
+++ 
b/framework/ca/src/main/java/org/apache/cloudstack/framework/ca/CAService.java
@@ -21,6 +21,7 @@ import java.io.IOException;
 import java.security.GeneralSecurityException;
 import java.security.KeyStore;
 import java.security.KeyStoreException;
+import java.security.cert.CertificateParsingException;
 
 import javax.net.ssl.SSLContext;
 import javax.net.ssl.SSLEngine;
@@ -47,4 +48,6 @@ public interface CAService {
      * @return returns char[] passphrase
      */
     char[] getKeyStorePassphrase();
+
+    boolean isManagementCertificate(java.security.cert.Certificate 
certificate) throws CertificateParsingException;
 }
diff --git 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java
index 1b1406c1cec..54f575830e4 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManager.java
@@ -16,8 +16,8 @@
 // under the License.
 package com.cloud.cluster;
 
-import org.apache.cloudstack.management.ManagementServerHost;
 import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.management.ManagementServerHost;
 
 import com.cloud.utils.component.Manager;
 
@@ -77,6 +77,8 @@ public interface ClusterManager extends Manager {
      */
     String getSelfPeerName();
 
+    String getSelfNodeIP();
+
     long getManagementNodeId();
 
     /**
diff --git 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java
index 289638fe22d..d601c094ca7 100644
--- a/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java
+++ b/framework/cluster/src/main/java/com/cloud/cluster/ClusterManagerImpl.java
@@ -40,17 +40,17 @@ import java.util.concurrent.TimeUnit;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
-import com.cloud.cluster.dao.ManagementServerStatusDao;
-import org.apache.cloudstack.management.ManagementServerHost;
 import org.apache.cloudstack.framework.config.ConfigDepot;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.managed.context.ManagedContextRunnable;
+import org.apache.cloudstack.management.ManagementServerHost;
 import org.apache.cloudstack.utils.identity.ManagementServerNode;
 import org.apache.log4j.Logger;
 
 import com.cloud.cluster.dao.ManagementServerHostDao;
 import com.cloud.cluster.dao.ManagementServerHostPeerDao;
+import com.cloud.cluster.dao.ManagementServerStatusDao;
 import com.cloud.utils.DateUtil;
 import com.cloud.utils.Profiler;
 import com.cloud.utils.component.ComponentLifecycle;
@@ -130,7 +130,7 @@ public class ClusterManagerImpl extends ManagerBase 
implements ClusterManager, C
         // recursive remote calls between nodes
         //
         _executor = Executors.newCachedThreadPool(new 
NamedThreadFactory("Cluster-Worker"));
-        setRunLevel(ComponentLifecycle.RUN_LEVEL_FRAMEWORK);
+        setRunLevel(ComponentLifecycle.RUN_LEVEL_COMPONENT);
     }
 
     private void registerRequestPdu(final ClusterServiceRequestPdu pdu) {
@@ -475,6 +475,7 @@ public class ClusterManagerImpl extends ManagerBase 
implements ClusterManager, C
         return Long.toString(_msId);
     }
 
+    @Override
     public String getSelfNodeIP() {
         return _clusterNodeIP;
     }
diff --git 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java
index 735de5bdac9..e073a28a622 100644
--- 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java
+++ 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java
@@ -28,7 +28,5 @@ public interface ClusterServiceAdapter extends Adapter {
 
     public ClusterService getPeerService(String strPeer) throws 
RemoteException;
 
-    public String getServiceEndpointName(String strPeer);
-
     public int getServicePort();
 }
diff --git 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java
 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java
index 7451b5f4226..15ee055f9e1 100644
--- 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java
+++ 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletAdapter.java
@@ -23,8 +23,9 @@ import java.util.Properties;
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
-import org.apache.log4j.Logger;
+import org.apache.cloudstack.ca.CAManager;
 import org.apache.cloudstack.framework.config.ConfigDepot;
+import org.apache.log4j.Logger;
 
 import com.cloud.cluster.dao.ManagementServerHostDao;
 import com.cloud.utils.NumbersUtil;
@@ -44,6 +45,8 @@ public class ClusterServiceServletAdapter extends AdapterBase 
implements Cluster
     @Inject
     private ManagementServerHostDao _mshostDao;
     @Inject
+    private CAManager caService;
+    @Inject
     protected ConfigDepot _configDepot;
 
     private ClusterServiceServletContainer _servletContainer;
@@ -51,7 +54,7 @@ public class ClusterServiceServletAdapter extends AdapterBase 
implements Cluster
     private int _clusterServicePort = DEFAULT_SERVICE_PORT;
 
     public ClusterServiceServletAdapter() {
-        setRunLevel(ComponentLifecycle.RUN_LEVEL_FRAMEWORK);
+        setRunLevel(ComponentLifecycle.RUN_LEVEL_COMPONENT);
     }
 
     @Override
@@ -66,12 +69,10 @@ public class ClusterServiceServletAdapter extends 
AdapterBase implements Cluster
         String serviceUrl = getServiceEndpointName(strPeer);
         if (serviceUrl == null)
             return null;
-
-        return new ClusterServiceServletImpl(serviceUrl);
+        return new ClusterServiceServletImpl(serviceUrl, caService);
     }
 
-    @Override
-    public String getServiceEndpointName(String strPeer) {
+    protected String getServiceEndpointName(String strPeer) {
         try {
             init();
         } catch (ConfigurationException e) {
@@ -95,7 +96,7 @@ public class ClusterServiceServletAdapter extends AdapterBase 
implements Cluster
 
     private String composeEndpointName(String nodeIP, int port) {
         StringBuffer sb = new StringBuffer();
-        
sb.append("http://";).append(nodeIP).append(":").append(port).append("/clusterservice");
+        
sb.append("https://";).append(nodeIP).append(":").append(port).append("/clusterservice");
         return sb.toString();
     }
 
@@ -108,7 +109,8 @@ public class ClusterServiceServletAdapter extends 
AdapterBase implements Cluster
     @Override
     public boolean start() {
         _servletContainer = new ClusterServiceServletContainer();
-        _servletContainer.start(new 
ClusterServiceServletHttpHandler(_manager), _clusterServicePort);
+        _servletContainer.start(new 
ClusterServiceServletHttpHandler(_manager), _manager.getSelfNodeIP(),
+                _clusterServicePort, caService);
         return true;
     }
 
diff --git 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java
 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java
index 69cc871dc64..1aa9caae50a 100644
--- 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java
+++ 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletContainer.java
@@ -17,11 +17,23 @@
 package com.cloud.cluster;
 
 import java.io.IOException;
-import java.net.ServerSocket;
+import java.net.InetAddress;
 import java.net.Socket;
+import java.security.GeneralSecurityException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateParsingException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
+
+import org.apache.cloudstack.framework.ca.CAService;
+import org.apache.cloudstack.managed.context.ManagedContextRunnable;
 import org.apache.http.ConnectionClosedException;
 import org.apache.http.HttpException;
 import org.apache.http.impl.DefaultConnectionReuseStrategy;
@@ -43,9 +55,9 @@ import org.apache.http.protocol.ResponseDate;
 import org.apache.http.protocol.ResponseServer;
 import org.apache.log4j.Logger;
 
-import org.apache.cloudstack.managed.context.ManagedContextRunnable;
-
+import com.cloud.utils.StringUtils;
 import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.nio.Link;
 
 public class ClusterServiceServletContainer {
     private static final Logger s_logger = 
Logger.getLogger(ClusterServiceServletContainer.class);
@@ -55,9 +67,9 @@ public class ClusterServiceServletContainer {
     public ClusterServiceServletContainer() {
     }
 
-    public boolean start(HttpRequestHandler requestHandler, int port) {
+    public boolean start(HttpRequestHandler requestHandler, String ip, int 
port, CAService caService) {
 
-        listenerThread = new ListenerThread(requestHandler, port);
+        listenerThread = new ListenerThread(requestHandler, ip, port, 
caService);
         listenerThread.start();
 
         return true;
@@ -69,24 +81,43 @@ public class ClusterServiceServletContainer {
         }
     }
 
+    protected static SSLServerSocket getSecuredServerSocket(SSLContext 
sslContext, String ip, int port)
+            throws IOException {
+        SSLServerSocketFactory sslFactory = 
sslContext.getServerSocketFactory();
+        SSLServerSocket serverSocket = null;
+        if (StringUtils.isNotEmpty(ip)) {
+            serverSocket = (SSLServerSocket) 
sslFactory.createServerSocket(port, 0,
+                    InetAddress.getByName(ip));
+        } else {
+            serverSocket = (SSLServerSocket) 
sslFactory.createServerSocket(port);
+        }
+        serverSocket.setNeedClientAuth(true);
+        return serverSocket;
+    }
+
     static class ListenerThread extends Thread {
-        private HttpService _httpService = null;
-        private volatile ServerSocket _serverSocket = null;
-        private HttpParams _params = null;
-        private ExecutorService _executor;
+        private HttpService httpService = null;
+        private volatile SSLServerSocket serverSocket = null;
+        private HttpParams params = null;
+        private ExecutorService executor;
+        private CAService caService = null;
 
-        public ListenerThread(HttpRequestHandler requestHandler, int port) {
-            _executor = Executors.newCachedThreadPool(new 
NamedThreadFactory("Cluster-Listener"));
+        public ListenerThread(HttpRequestHandler requestHandler, String ip, 
int port,
+                      CAService caService) {
+            this.executor = Executors.newCachedThreadPool(new 
NamedThreadFactory("Cluster-Listener"));
+            this.caService = caService;
 
             try {
-                _serverSocket = new ServerSocket(port);
-            } catch (IOException ioex) {
-                s_logger.error("error initializing cluster service servlet 
container", ioex);
+                SSLContext sslContext = 
Link.initManagementSSLContext(caService);
+                serverSocket = getSecuredServerSocket(sslContext, ip, port);
+            } catch (IOException | GeneralSecurityException e) {
+                s_logger.error("Error initializing cluster service servlet 
container for secure connection",
+                        e);
                 return;
             }
 
-            _params = new BasicHttpParams();
-            _params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
+            params = new BasicHttpParams();
+            params.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, 5000)
                 .setIntParameter(CoreConnectionPNames.SOCKET_BUFFER_SIZE, 8 * 
1024)
                 
.setBooleanParameter(CoreConnectionPNames.STALE_CONNECTION_CHECK, false)
                 .setBooleanParameter(CoreConnectionPNames.TCP_NODELAY, true)
@@ -104,35 +135,55 @@ public class ClusterServiceServletContainer {
             reqistry.register("/clusterservice", requestHandler);
 
             // Set up the HTTP service
-            _httpService = new HttpService(httpproc, new 
DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());
-            _httpService.setParams(_params);
-            _httpService.setHandlerResolver(reqistry);
+            httpService = new HttpService(httpproc, new 
DefaultConnectionReuseStrategy(), new DefaultHttpResponseFactory());
+            httpService.setParams(params);
+            httpService.setHandlerResolver(reqistry);
         }
 
         public void stopRunning() {
-            if (_serverSocket != null) {
+            if (serverSocket != null) {
                 try {
-                    _serverSocket.close();
+                    serverSocket.close();
                 } catch (IOException e) {
                     s_logger.info("[ignored] error on closing server socket", 
e);
                 }
-                _serverSocket = null;
+                serverSocket = null;
             }
         }
 
+        protected boolean isValidPeerConnection(Socket socket) throws 
SSLPeerUnverifiedException,
+                CertificateParsingException {
+            SSLSocket sslSocket = (SSLSocket) socket;
+            SSLSession session = sslSocket.getSession();
+            if (session == null || !session.isValid()) {
+                return false;
+            }
+            Certificate[] certs = session.getPeerCertificates();
+            if (certs == null || certs.length < 1) {
+                return false;
+            }
+            return caService.isManagementCertificate(certs[0]);
+        }
+
         @Override
         public void run() {
             if (s_logger.isInfoEnabled())
-                s_logger.info("Cluster service servlet container listening on 
port " + _serverSocket.getLocalPort());
+                s_logger.info(String.format("Cluster service servlet container 
listening on host: %s and port %d",
+                        serverSocket.getInetAddress().getHostAddress(), 
serverSocket.getLocalPort()));
 
-            while (_serverSocket != null) {
+            while (serverSocket != null) {
                 try {
                     // Set up HTTP connection
-                    Socket socket = _serverSocket.accept();
+                    Socket socket = serverSocket.accept();
                     final DefaultHttpServerConnection conn = new 
DefaultHttpServerConnection();
-                    conn.bind(socket, _params);
-
-                    _executor.execute(new ManagedContextRunnable() {
+                    conn.bind(socket, params);
+                    if (!isValidPeerConnection(socket)) {
+                        s_logger.warn(String.format("Failure during validating 
cluster request from %s",
+                                socket.getInetAddress().getHostAddress()));
+                        conn.shutdown();
+                        continue;
+                    }
+                    executor.execute(new ManagedContextRunnable() {
                         @Override
                         protected void runInContext() {
                             HttpContext context = new BasicHttpContext(null);
@@ -141,7 +192,7 @@ public class ClusterServiceServletContainer {
                                     if (s_logger.isTraceEnabled())
                                         s_logger.trace("dispatching cluster 
request from " + conn.getRemoteAddress().toString());
 
-                                    _httpService.handleRequest(conn, context);
+                                    httpService.handleRequest(conn, context);
 
                                     if (s_logger.isTraceEnabled())
                                         s_logger.trace("Cluster request from " 
+ conn.getRemoteAddress().toString() + " is processed");
@@ -176,7 +227,7 @@ public class ClusterServiceServletContainer {
                 }
             }
 
-            _executor.shutdown();
+            executor.shutdown();
             if (s_logger.isInfoEnabled())
                 s_logger.info("Cluster service servlet container shutdown");
         }
diff --git 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java
 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java
index ec8b90866d0..c5b61452169 100644
--- 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java
+++ 
b/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceServletImpl.java
@@ -17,98 +17,143 @@
 package com.cloud.cluster;
 
 import java.io.IOException;
+import java.io.UnsupportedEncodingException;
 import java.rmi.RemoteException;
+import java.security.GeneralSecurityException;
+import java.util.ArrayList;
+import java.util.List;
 
-import org.apache.commons.httpclient.HttpClient;
-import org.apache.commons.httpclient.HttpException;
+import javax.net.ssl.SSLContext;
+
+import org.apache.cloudstack.framework.ca.CAService;
 import org.apache.commons.httpclient.HttpStatus;
-import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
-import org.apache.commons.httpclient.methods.PostMethod;
-import org.apache.commons.httpclient.params.HttpClientParams;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
 import org.apache.log4j.Logger;
 
+import com.cloud.utils.HttpUtils;
 import com.cloud.utils.Profiler;
+import com.cloud.utils.nio.Link;
+import com.google.gson.Gson;
 
 public class ClusterServiceServletImpl implements ClusterService {
     private static final long serialVersionUID = 4574025200012566153L;
     private static final Logger s_logger = 
Logger.getLogger(ClusterServiceServletImpl.class);
 
-    private String _serviceUrl;
+    private String serviceUrl;
+
+    private CAService caService;
+
+    private Gson gson = new Gson();
 
-    protected static HttpClient s_client = null;
+    protected static CloseableHttpClient s_client = null;
+
+    private void logPostParametersForFailedEncoding(List<NameValuePair> 
parameters) {
+        if (s_logger.isTraceEnabled()) {
+            s_logger.trace(String.format("%s encoding failed for POST 
parameters: %s", HttpUtils.UTF_8,
+                    gson.toJson(parameters)));
+        }
+    }
 
     public ClusterServiceServletImpl() {
     }
 
-    public ClusterServiceServletImpl(final String serviceUrl) {
-        s_logger.info("Setup cluster service servlet. service url: " + 
serviceUrl + ", request timeout: " + 
ClusterServiceAdapter.ClusterMessageTimeOut.value() +
-                " seconds");
+    public ClusterServiceServletImpl(final String serviceUrl, final CAService 
caService) {
+        s_logger.info(String.format("Setup cluster service servlet. service 
url: %s, request timeout: %d seconds", serviceUrl,
+                ClusterServiceAdapter.ClusterMessageTimeOut.value()));
+        this.serviceUrl = serviceUrl;
+        this.caService = caService;
+    }
 
-        _serviceUrl = serviceUrl;
+    protected List<NameValuePair> getClusterServicePduPostParameters(final 
ClusterServicePdu pdu) {
+        List<NameValuePair> postParameters = new ArrayList<>();
+        postParameters.add(new BasicNameValuePair("method", 
Integer.toString(RemoteMethodConstants.METHOD_DELIVER_PDU)));
+        postParameters.add(new BasicNameValuePair("sourcePeer", 
pdu.getSourcePeer()));
+        postParameters.add(new BasicNameValuePair("destPeer", 
pdu.getDestPeer()));
+        postParameters.add(new BasicNameValuePair("pduSeq", 
Long.toString(pdu.getSequenceId())));
+        postParameters.add(new BasicNameValuePair("pduAckSeq", 
Long.toString(pdu.getAckSequenceId())));
+        postParameters.add(new BasicNameValuePair("agentId", 
Long.toString(pdu.getAgentId())));
+        postParameters.add(new BasicNameValuePair("gsonPackage", 
pdu.getJsonPackage()));
+        postParameters.add(new BasicNameValuePair("stopOnError", 
pdu.isStopOnError() ? "1" : "0"));
+        postParameters.add(new BasicNameValuePair("pduType", 
Integer.toString(pdu.getPduType())));
+        return postParameters;
     }
 
     @Override
     public String execute(final ClusterServicePdu pdu) throws RemoteException {
-
-        final HttpClient client = getHttpClient();
-        final PostMethod method = new PostMethod(_serviceUrl);
-
-        method.addParameter("method", 
Integer.toString(RemoteMethodConstants.METHOD_DELIVER_PDU));
-        method.addParameter("sourcePeer", pdu.getSourcePeer());
-        method.addParameter("destPeer", pdu.getDestPeer());
-        method.addParameter("pduSeq", Long.toString(pdu.getSequenceId()));
-        method.addParameter("pduAckSeq", 
Long.toString(pdu.getAckSequenceId()));
-        method.addParameter("agentId", Long.toString(pdu.getAgentId()));
-        method.addParameter("gsonPackage", pdu.getJsonPackage());
-        method.addParameter("stopOnError", pdu.isStopOnError() ? "1" : "0");
-        method.addParameter("pduType", Integer.toString(pdu.getPduType()));
+        if (s_logger.isDebugEnabled()) {
+            s_logger.debug(String.format("Executing ClusterServicePdu with 
service URL: %s", serviceUrl));
+        }
+        final CloseableHttpClient client = getHttpClient();
+        final HttpPost method = new HttpPost(serviceUrl);
+        final List<NameValuePair> postParameters = 
getClusterServicePduPostParameters(pdu);
+        try {
+            method.setEntity(new UrlEncodedFormEntity(postParameters, 
HttpUtils.UTF_8));
+        } catch (UnsupportedEncodingException e) {
+            s_logger.error("Failed to encode request POST parameters", e);
+            logPostParametersForFailedEncoding(postParameters);
+            throw new RemoteException("Failed to encode request POST 
parameters", e);
+        }
 
         return executePostMethod(client, method);
     }
 
+    protected List<NameValuePair> getPingPostParameters(final String 
callingPeer) {
+        List<NameValuePair> postParameters = new ArrayList<>();
+        postParameters.add(new BasicNameValuePair("method", 
Integer.toString(RemoteMethodConstants.METHOD_PING)));
+        postParameters.add(new BasicNameValuePair("callingPeer", callingPeer));
+        return postParameters;
+    }
+
     @Override
     public boolean ping(final String callingPeer) throws RemoteException {
         if (s_logger.isDebugEnabled()) {
-            s_logger.debug("Ping at " + _serviceUrl);
+            s_logger.debug("Ping at " + serviceUrl);
         }
 
-        final HttpClient client = getHttpClient();
-        final PostMethod method = new PostMethod(_serviceUrl);
+        final CloseableHttpClient client = getHttpClient();
+        final HttpPost method = new HttpPost(serviceUrl);
 
-        method.addParameter("method", 
Integer.toString(RemoteMethodConstants.METHOD_PING));
-        method.addParameter("callingPeer", callingPeer);
+        List<NameValuePair> postParameters = 
getPingPostParameters(callingPeer);
+        try {
+            method.setEntity(new UrlEncodedFormEntity(postParameters, 
HttpUtils.UTF_8));
+        } catch (UnsupportedEncodingException e) {
+            s_logger.error("Failed to encode ping request POST parameters", e);
+            logPostParametersForFailedEncoding(postParameters);
+            throw new RemoteException("Failed to encode ping request POST 
parameters", e);
+        }
 
         final String returnVal = executePostMethod(client, method);
-        if ("true".equalsIgnoreCase(returnVal)) {
-            return true;
-        }
-        return false;
+        return Boolean.TRUE.toString().equalsIgnoreCase(returnVal);
     }
 
-    private String executePostMethod(final HttpClient client, final PostMethod 
method) {
-        int response = 0;
+    private String executePostMethod(final CloseableHttpClient client, final 
HttpPost method) {
         String result = null;
         try {
             final Profiler profiler = new Profiler();
             profiler.start();
-            response = client.executeMethod(method);
+            CloseableHttpResponse httpResponse = client.execute(method);
+            int response = httpResponse.getStatusLine().getStatusCode();
             if (response == HttpStatus.SC_OK) {
-                result = method.getResponseBodyAsString();
+                result = EntityUtils.toString(httpResponse.getEntity());
                 profiler.stop();
                 if (s_logger.isDebugEnabled()) {
-                    s_logger.debug("POST " + _serviceUrl + " response :" + 
result + ", responding time: " + profiler.getDurationInMillis() + " ms");
+                    s_logger.debug("POST " + serviceUrl + " response :" + 
result + ", responding time: " + profiler.getDurationInMillis() + " ms");
                 }
             } else {
                 profiler.stop();
-                s_logger.error("Invalid response code : " + response + ", from 
: " + _serviceUrl + ", method : " + method.getParameter("method") + " 
responding time: " +
+                s_logger.error("Invalid response code : " + response + ", from 
: " + serviceUrl + ", method : " + method.getParams().getParameter("method") + 
" responding time: " +
                         profiler.getDurationInMillis());
             }
-        } catch (final HttpException e) {
-            s_logger.error("HttpException from : " + _serviceUrl + ", method : 
" + method.getParameter("method"));
-        } catch (final IOException e) {
-            s_logger.error("IOException from : " + _serviceUrl + ", method : " 
+ method.getParameter("method"));
-        } catch (final Throwable e) {
-            s_logger.error("Exception from : " + _serviceUrl + ", method : " + 
method.getParameter("method") + ", exception :", e);
+        } catch (IOException e) {
+            s_logger.error("Exception from : " + serviceUrl + ", method : " + 
method.getParams().getParameter("method") + ", exception :", e);
         } finally {
             method.releaseConnection();
         }
@@ -116,20 +161,25 @@ public class ClusterServiceServletImpl implements 
ClusterService {
         return result;
     }
 
-    private HttpClient getHttpClient() {
-
+    private CloseableHttpClient getHttpClient() {
         if (s_client == null) {
-            final MultiThreadedHttpConnectionManager mgr = new 
MultiThreadedHttpConnectionManager();
-            mgr.getParams().setDefaultMaxConnectionsPerHost(4);
-
-            // TODO make it configurable
-            mgr.getParams().setMaxTotalConnections(1000);
+            SSLContext sslContext = null;
+            try {
+                sslContext = Link.initManagementSSLContext(caService);
+            } catch (GeneralSecurityException | IOException e) {
+                throw new RuntimeException(e);
+            }
 
-            s_client = new HttpClient(mgr);
-            final HttpClientParams clientParams = new HttpClientParams();
-            
clientParams.setSoTimeout(ClusterServiceAdapter.ClusterMessageTimeOut.value() * 
1000);
+            int timeout = ClusterServiceAdapter.ClusterMessageTimeOut.value() 
* 1000;
+            RequestConfig config = RequestConfig.custom()
+                    .setConnectTimeout(timeout)
+                    .setConnectionRequestTimeout(timeout)
+                    .setSocketTimeout(timeout).build();
 
-            s_client.setParams(clientParams);
+            s_client = HttpClientBuilder.create()
+                    .setDefaultRequestConfig(config)
+                    .setSSLContext(sslContext)
+                    .build();
         }
         return s_client;
     }
diff --git 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java 
b/framework/cluster/src/test/java/com/cloud/cluster/ClusterManagerImplTest.java
similarity index 57%
copy from 
framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java
copy to 
framework/cluster/src/test/java/com/cloud/cluster/ClusterManagerImplTest.java
index 735de5bdac9..9b1854f7348 100644
--- 
a/framework/cluster/src/main/java/com/cloud/cluster/ClusterServiceAdapter.java
+++ 
b/framework/cluster/src/test/java/com/cloud/cluster/ClusterManagerImplTest.java
@@ -14,21 +14,25 @@
 // KIND, either express or implied.  See the License for the
 // specific language governing permissions and limitations
 // under the License.
-package com.cloud.cluster;
-
-import java.rmi.RemoteException;
-
-import org.apache.cloudstack.framework.config.ConfigKey;
 
-import com.cloud.utils.component.Adapter;
-
-public interface ClusterServiceAdapter extends Adapter {
-    final ConfigKey<Integer> ClusterMessageTimeOut = new 
ConfigKey<Integer>(Integer.class, "cluster.message.timeout.seconds", "Advance", 
"300",
-        "Time (in seconds) to wait before a inter-management server message 
post times out.", true);
+package com.cloud.cluster;
 
-    public ClusterService getPeerService(String strPeer) throws 
RemoteException;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
-    public String getServiceEndpointName(String strPeer);
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterManagerImplTest {
+    @InjectMocks
+    ClusterManagerImpl clusterManager = new ClusterManagerImpl();
 
-    public int getServicePort();
+    @Test
+    public void testGetSelfNodeIP() {
+        String ip = "1.2.3.4";
+        ReflectionTestUtils.setField(clusterManager, "_clusterNodeIP", ip);
+        Assert.assertEquals(ip, clusterManager.getSelfNodeIP());
+    }
 }
diff --git 
a/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java
 
b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java
index 91d8b611a0f..3827236d615 100644
--- 
a/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java
+++ 
b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletAdapterTest.java
@@ -50,7 +50,7 @@ public class ClusterServiceServletAdapterTest {
     @Test
     public void testRunLevel() {
         int runLevel = clusterServiceServletAdapter.getRunLevel();
-        assertTrue(runLevel == ComponentLifecycle.RUN_LEVEL_FRAMEWORK);
+        assertTrue(runLevel == ComponentLifecycle.RUN_LEVEL_COMPONENT);
         assertTrue(runLevel == clusterManagerImpl.getRunLevel());
     }
 }
diff --git 
a/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletContainerTest.java
 
b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletContainerTest.java
new file mode 100644
index 00000000000..baf4e5841bd
--- /dev/null
+++ 
b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletContainerTest.java
@@ -0,0 +1,87 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package com.cloud.cluster;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLContextSpi;
+import javax.net.ssl.SSLServerSocket;
+import javax.net.ssl.SSLServerSocketFactory;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.mockito.stubbing.Answer;
+import org.springframework.test.util.ReflectionTestUtils;
+
+import com.cloud.utils.StringUtils;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterServiceServletContainerTest {
+
+    private void runGetSecuredServerSocket(String ip) {
+        SSLContext sslContext = Mockito.mock(SSLContext.class);
+        SSLContextSpi sslContextSpi = Mockito.mock(SSLContextSpi.class);
+        ReflectionTestUtils.setField(sslContext, "contextSpi", sslContextSpi);
+        SSLServerSocketFactory factory = 
Mockito.mock(SSLServerSocketFactory.class);
+        Mockito.when(sslContext.getServerSocketFactory()).thenReturn(factory);
+        int port = 9090;
+        final List<Boolean> socketNeedClientAuth = new ArrayList<>();
+        try {
+            SSLServerSocket socketMock = Mockito.mock(SSLServerSocket.class);
+            if (StringUtils.isBlank(ip)) {
+                
Mockito.when(factory.createServerSocket(port)).thenReturn(socketMock);
+            } else {
+                Mockito.when(factory.createServerSocket(Mockito.anyInt(), 
Mockito.anyInt(),
+                        
Mockito.any(InetAddress.class))).thenReturn(socketMock);
+            }
+            Mockito.doAnswer((Answer<Void>) invocationOnMock -> {
+                boolean needClientAuth = (boolean) 
invocationOnMock.getArguments()[0];
+                socketNeedClientAuth.add(needClientAuth);
+                return null;
+            }).when(socketMock).setNeedClientAuth(Mockito.anyBoolean());
+            SSLServerSocket socket = 
ClusterServiceServletContainer.getSecuredServerSocket(sslContext, ip, 9090);
+            if (StringUtils.isBlank(ip)) {
+                Mockito.verify(factory, 
Mockito.times(1)).createServerSocket(port);
+            } else {
+                Mockito.verify(factory, 
Mockito.times(1)).createServerSocket(port, 0, InetAddress.getByName(ip));
+            }
+            Mockito.verify(socket, 
Mockito.times(1)).setNeedClientAuth(Mockito.anyBoolean());
+            
Assert.assertTrue(CollectionUtils.isNotEmpty(socketNeedClientAuth));
+            Assert.assertTrue(socketNeedClientAuth.get(0));
+        } catch (IOException e) {
+            Assert.fail("Exception occurred: " + e.getMessage());
+        }
+    }
+
+    @Test
+    public void testGetSecuredServerSocketNoIp() {
+        runGetSecuredServerSocket("");
+    }
+
+    @Test
+    public void testGetSecuredServerSocketIp() {
+        runGetSecuredServerSocket("1.2.3.4");
+    }
+}
diff --git 
a/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletImplTest.java
 
b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletImplTest.java
new file mode 100644
index 00000000000..361c77dbeff
--- /dev/null
+++ 
b/framework/cluster/src/test/java/com/cloud/cluster/ClusterServiceServletImplTest.java
@@ -0,0 +1,64 @@
+// 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.cluster;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.http.NameValuePair;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ClusterServiceServletImplTest {
+
+    @InjectMocks
+    ClusterServiceServletImpl clusterServiceServlet = new 
ClusterServiceServletImpl();
+
+    @Test
+    public void testClusterServicePduPostParameters() {
+        List<NameValuePair> parameters =
+                
clusterServiceServlet.getClusterServicePduPostParameters(Mockito.mock(ClusterServicePdu.class));
+        Assert.assertTrue(CollectionUtils.isNotEmpty(parameters));
+        Optional<NameValuePair> opt = parameters.stream().filter(x -> 
x.getName().equals("method")).findFirst();
+        Assert.assertTrue(opt.isPresent());
+        NameValuePair val = opt.get();
+        
Assert.assertEquals(Integer.toString(RemoteMethodConstants.METHOD_DELIVER_PDU), 
val.getValue());
+    }
+
+    @Test
+    public void testPingPostParameters() {
+        String peer = "1.2.3.4";
+        List<NameValuePair> parameters =
+                clusterServiceServlet.getPingPostParameters(peer);
+        Assert.assertTrue(CollectionUtils.isNotEmpty(parameters));
+        Optional<NameValuePair> opt = parameters.stream().filter(x -> 
x.getName().equals("method")).findFirst();
+        Assert.assertTrue(opt.isPresent());
+        NameValuePair val = opt.get();
+        
Assert.assertEquals(Integer.toString(RemoteMethodConstants.METHOD_PING), 
val.getValue());
+        opt = parameters.stream().filter(x -> 
x.getName().equals("callingPeer")).findFirst();
+        Assert.assertTrue(opt.isPresent());
+        val = opt.get();
+        Assert.assertEquals(peer, val.getValue());
+    }
+}
diff --git a/packaging/systemd/cloudstack-management.default 
b/packaging/systemd/cloudstack-management.default
index 252fb4b78f6..d0b41b4b564 100644
--- a/packaging/systemd/cloudstack-management.default
+++ b/packaging/systemd/cloudstack-management.default
@@ -15,7 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-JAVA_OPTS="-Djava.security.properties=/etc/cloudstack/management/java.security.ciphers
 -Djava.awt.headless=true -Dcom.sun.management.jmxremote=false -Xmx2G 
-XX:+UseParallelGC -XX:MaxGCPauseMillis=500 -XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/var/log/cloudstack/management/ 
-XX:ErrorFile=/var/log/cloudstack/management/cloudstack-management.err "
+JAVA_OPTS="-Djava.security.properties=/etc/cloudstack/management/java.security.ciphers
 -Djava.awt.headless=true -Xmx2G -XX:+UseParallelGC -XX:MaxGCPauseMillis=500 
-XX:+HeapDumpOnOutOfMemoryError 
-XX:HeapDumpPath=/var/log/cloudstack/management/ 
-XX:ErrorFile=/var/log/cloudstack/management/cloudstack-management.err "
 
 
CLASSPATH="/usr/share/cloudstack-management/lib/*:/etc/cloudstack/management:/usr/share/cloudstack-common:/usr/share/cloudstack-management/setup:/usr/share/cloudstack-management:/usr/share/java/mysql-connector-java.jar:/usr/share/cloudstack-mysql-ha/lib/*"
 
diff --git 
a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
 
b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
index f71274bbc88..821704179fc 100644
--- 
a/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
+++ 
b/plugins/ca/root-ca/src/main/java/org/apache/cloudstack/ca/provider/RootCAProvider.java
@@ -33,9 +33,11 @@ import java.security.Security;
 import java.security.SignatureException;
 import java.security.UnrecoverableKeyException;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.security.spec.InvalidKeySpecException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -57,6 +59,7 @@ import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.utils.security.CertUtils;
 import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.log4j.Logger;
 import org.bouncycastle.asn1.pkcs.Attribute;
 import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
@@ -127,6 +130,8 @@ public final class RootCAProvider extends AdapterBase 
implements CAProvider, Con
             "true",
             "When set to true, it will allow expired client certificate during 
SSL handshake.", true);
 
+    private static String managementCertificateCustomSAN;
+
 
     ///////////////////////////////////////////////////////////
     /////////////// Root CA Private Methods ///////////////////
@@ -365,8 +370,11 @@ public final class RootCAProvider extends AdapterBase 
implements CAProvider, Con
         if (managementKeyStore != null) {
             return true;
         }
-        final Certificate serverCertificate = 
issueCertificate(Collections.singletonList(NetUtils.getHostName()),
-                NetUtils.getAllDefaultNicIps(), getCaValidityDays());
+        List<String> domainNames = new ArrayList<>();
+        domainNames.add(NetUtils.getHostName());
+        
domainNames.add(CAManager.CertManagementCustomSubjectAlternativeName.value());
+        final Certificate serverCertificate = issueCertificate(
+                domainNames, NetUtils.getAllDefaultNicIps(), 
getCaValidityDays());
         if (serverCertificate == null || serverCertificate.getPrivateKey() == 
null) {
             throw new CloudRuntimeException("Failed to generate management 
server certificate and load management server keystore");
         }
@@ -402,6 +410,7 @@ public final class RootCAProvider extends AdapterBase 
implements CAProvider, Con
 
     @Override
     public boolean start() {
+        managementCertificateCustomSAN = 
CAManager.CertManagementCustomSubjectAlternativeName.value();
         return loadRootCAKeyPair() && loadRootCAKeyPair() && 
loadManagementKeyStore();
     }
 
@@ -456,4 +465,26 @@ public final class RootCAProvider extends AdapterBase 
implements CAProvider, Con
     public String getDescription() {
         return "CloudStack's Root CA provider plugin";
     }
+
+    @Override
+    public boolean isManagementCertificate(java.security.cert.Certificate 
certificate) throws CertificateParsingException {
+        if (!(certificate instanceof X509Certificate)) {
+            return false;
+        }
+        X509Certificate x509Certificate = (X509Certificate) certificate;
+
+        // Check for alternative names
+        Collection<List<?>> altNames = 
x509Certificate.getSubjectAlternativeNames();
+        if (CollectionUtils.isEmpty(altNames)) {
+            return false;
+        }
+        for (List<?> altName : altNames) {
+            int type = (Integer) altName.get(0);
+            String name = (String) altName.get(1);
+            if (type == GeneralName.dNSName && 
managementCertificateCustomSAN.equals(name)) {
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git 
a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
 
b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
index 15514b91c78..8311f4d45ab 100644
--- 
a/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
+++ 
b/plugins/ca/root-ca/src/test/java/org/apache/cloudstack/ca/provider/RootCAProviderTest.java
@@ -26,8 +26,13 @@ import java.security.NoSuchAlgorithmException;
 import java.security.NoSuchProviderException;
 import java.security.SignatureException;
 import java.security.cert.CertificateException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.UUID;
 
 import javax.net.ssl.SSLEngine;
 
@@ -35,15 +40,16 @@ import org.apache.cloudstack.framework.ca.Certificate;
 import org.apache.cloudstack.framework.config.ConfigKey;
 import org.apache.cloudstack.utils.security.CertUtils;
 import org.apache.cloudstack.utils.security.SSLUtils;
+import org.bouncycastle.asn1.x509.GeneralName;
 import org.joda.time.DateTime;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-
-import org.mockito.junit.MockitoJUnitRunner;
 import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.test.util.ReflectionTestUtils;
 
 
 @RunWith(MockitoJUnitRunner.class)
@@ -150,4 +156,56 @@ public class RootCAProviderTest {
         Assert.assertEquals(provider.getProviderName(), "root");
     }
 
+    @Test
+    public void testIsManagementCertificateNotX509() {
+        try {
+            
Assert.assertFalse(provider.isManagementCertificate(Mockito.mock(java.security.cert.Certificate.class)));
+        } catch (CertificateParsingException e) {
+            Assert.fail(String.format("Exception occurred: %s", 
e.getMessage()));
+        }
+    }
+
+    @Test
+    public void testIsManagementCertificateNoAltNames() {
+        try {
+            X509Certificate certificate = Mockito.mock(X509Certificate.class);
+            
Mockito.when(certificate.getSubjectAlternativeNames()).thenReturn(new 
ArrayList<>());
+            Assert.assertFalse(provider.isManagementCertificate(certificate));
+        } catch (CertificateParsingException e) {
+            Assert.fail(String.format("Exception occurred: %s", 
e.getMessage()));
+        }
+    }
+
+    @Test
+    public void testIsManagementCertificateNoMatch() {
+        ReflectionTestUtils.setField(provider, 
"managementCertificateCustomSAN", "cloudstack");
+        try {
+            X509Certificate certificate = Mockito.mock(X509Certificate.class);
+            List<List<?>> altNames = new ArrayList<>();
+            altNames.add(List.of(GeneralName.dNSName, 
UUID.randomUUID().toString()));
+            altNames.add(List.of(GeneralName.dNSName, 
UUID.randomUUID().toString()));
+            Collection<List<?>> collection = new ArrayList<>(altNames);
+            
Mockito.when(certificate.getSubjectAlternativeNames()).thenReturn(collection);
+            Assert.assertFalse(provider.isManagementCertificate(certificate));
+        } catch (CertificateParsingException e) {
+            Assert.fail(String.format("Exception occurred: %s", 
e.getMessage()));
+        }
+    }
+
+    @Test
+    public void testIsManagementCertificateMatch() {
+        String customSAN = "cloudstack";
+        ReflectionTestUtils.setField(provider, 
"managementCertificateCustomSAN", customSAN);
+        try {
+            X509Certificate certificate = Mockito.mock(X509Certificate.class);
+            List<List<?>> altNames = new ArrayList<>();
+            altNames.add(List.of(GeneralName.dNSName, customSAN));
+            altNames.add(List.of(GeneralName.dNSName, 
UUID.randomUUID().toString()));
+            Collection<List<?>> collection = new ArrayList<>(altNames);
+            
Mockito.when(certificate.getSubjectAlternativeNames()).thenReturn(collection);
+            Assert.assertTrue(provider.isManagementCertificate(certificate));
+        } catch (CertificateParsingException e) {
+            Assert.fail(String.format("Exception occurred: %s", 
e.getMessage()));
+        }
+    }
 }
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
index 5b55db24f4d..f95948d73dd 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtDeleteVMSnapshotCommandWrapper.java
@@ -19,6 +19,9 @@
 
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
+import java.util.ArrayList;
+import java.util.List;
+
 import org.apache.cloudstack.storage.to.PrimaryDataStoreTO;
 import org.apache.cloudstack.storage.to.VolumeObjectTO;
 import org.apache.log4j.Logger;
@@ -36,8 +39,8 @@ import com.cloud.hypervisor.kvm.storage.KVMPhysicalDisk;
 import com.cloud.hypervisor.kvm.storage.KVMStoragePoolManager;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
-import com.cloud.storage.Volume;
 import com.cloud.storage.Storage.ImageFormat;
+import com.cloud.storage.Volume;
 import com.cloud.utils.script.Script;
 
 @ResourceWrapper(handles =  DeleteVMSnapshotCommand.class)
@@ -96,12 +99,20 @@ public final class LibvirtDeleteVMSnapshotCommandWrapper 
extends CommandWrapper<
                     PrimaryDataStoreTO primaryStore = (PrimaryDataStoreTO) 
rootVolume.getDataStore();
                     KVMPhysicalDisk rootDisk = 
storagePoolMgr.getPhysicalDisk(primaryStore.getPoolType(),
                             primaryStore.getUuid(), rootVolume.getPath());
-                    String qemu_img_snapshot = 
Script.runSimpleBashScript("qemu-img snapshot -l " + rootDisk.getPath() + " | 
tail -n +3 | awk -F ' ' '{print $2}' | grep ^" + 
cmd.getTarget().getSnapshotName() + "$");
+                    String qemuImgPath = 
Script.getExecutableAbsolutePath("qemu-img");
+                    List<String[]> commands = new ArrayList<>();
+                    commands.add(new String[]{qemuImgPath, "snapshot", "-l", 
sanitizeBashCommandArgument(rootDisk.getPath())});
+                    commands.add(new 
String[]{Script.getExecutableAbsolutePath("tail"), "-n", "+3"});
+                    commands.add(new 
String[]{Script.getExecutableAbsolutePath("awk"), "-F", " ", "{print $2}"});
+                    commands.add(new 
String[]{Script.getExecutableAbsolutePath("grep"), "^" + 
sanitizeBashCommandArgument(cmd.getTarget().getSnapshotName()) + "$"});
+                    String qemu_img_snapshot = 
Script.executePipedCommands(commands, 0).second();
                     if (qemu_img_snapshot == null) {
                         s_logger.info("Cannot find snapshot " + 
cmd.getTarget().getSnapshotName() + " in file " + rootDisk.getPath() + ", 
return true");
                         return new DeleteVMSnapshotAnswer(cmd, 
cmd.getVolumeTOs());
                     }
-                    int result = 
Script.runSimpleBashScriptForExitValue("qemu-img snapshot -d " + 
cmd.getTarget().getSnapshotName() + " " + rootDisk.getPath());
+                    int result = 
Script.executeCommandForExitValue(qemuImgPath, "snapshot", "-d",
+                            
sanitizeBashCommandArgument(cmd.getTarget().getSnapshotName()),
+                            sanitizeBashCommandArgument(rootDisk.getPath()));
                     if (result != 0) {
                         return new DeleteVMSnapshotAnswer(cmd, false,
                                 "Delete VM Snapshot Failed due to can not 
remove snapshot from image file " + rootDisk.getPath()  + " : " + result);
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java
index 1c27bdd958f..da2839d9cee 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtGetVmIpAddressCommandWrapper.java
@@ -19,6 +19,11 @@
 
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.GetVmIpAddressCommand;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
@@ -26,7 +31,6 @@ import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
 import com.cloud.utils.net.NetUtils;
 import com.cloud.utils.script.Script;
-import org.apache.log4j.Logger;
 
 @ResourceWrapper(handles =  GetVmIpAddressCommand.class)
 public final class LibvirtGetVmIpAddressCommandWrapper extends 
CommandWrapper<GetVmIpAddressCommand, Answer, LibvirtComputingResource> {
@@ -37,31 +41,51 @@ public final class LibvirtGetVmIpAddressCommandWrapper 
extends CommandWrapper<Ge
     public Answer execute(final GetVmIpAddressCommand command, final 
LibvirtComputingResource libvirtComputingResource) {
         String ip = null;
         boolean result = false;
+        String vmName = command.getVmName();
+        String sanitizedVmName = sanitizeBashCommandArgument(vmName);
         String networkCidr = command.getVmNetworkCidr();
+        List<String[]> commands = new ArrayList<>();
+        final String virt_ls_path = 
Script.getExecutableAbsolutePath("virt-ls");
+        final String virt_cat_path = 
Script.getExecutableAbsolutePath("virt-cat");
+        final String virt_win_reg_path = 
Script.getExecutableAbsolutePath("virt-win-reg");
+        final String tail_path = Script.getExecutableAbsolutePath("tail");
+        final String grep_path = Script.getExecutableAbsolutePath("grep");
+        final String awk_path = Script.getExecutableAbsolutePath("awk");
+        final String sed_path = Script.getExecutableAbsolutePath("sed");
         if(!command.isWindows()) {
             //List all dhcp lease files inside guestVm
-            String leasesList = Script.runSimpleBashScript(new 
StringBuilder().append("virt-ls ").append(command.getVmName())
-                    .append(" /var/lib/dhclient/ | grep 
.*\\*.leases").toString());
+            commands.add(new String[]{virt_ls_path, sanitizedVmName, 
"/var/lib/dhclient/"});
+            commands.add(new String[]{grep_path, ".*\\*.leases"});
+            String leasesList = Script.executePipedCommands(commands, 
0).second();
             if(leasesList != null) {
                 String[] leasesFiles = leasesList.split("\n");
                 for(String leaseFile : leasesFiles){
-                    //Read from each dhclient lease file inside guest Vm using 
virt-cat libguestfs ulitiy
-                    String ipAddr = Script.runSimpleBashScript(new 
StringBuilder().append("virt-cat ").append(command.getVmName())
-                            .append(" /var/lib/dhclient/" + leaseFile + " | 
tail -16 | grep 'fixed-address' | awk '{print $2}' | sed -e 
's/;//'").toString());
+                    //Read from each dhclient lease file inside guest Vm using 
virt-cat libguestfs utility
+                    commands = new ArrayList<>();
+                    commands.add(new String[]{virt_cat_path, sanitizedVmName, 
"/var/lib/dhclient/" + leaseFile});
+                    commands.add(new String[]{tail_path, "-16"});
+                    commands.add(new String[]{grep_path, "fixed-address"});
+                    commands.add(new String[]{awk_path, "{print $2}"});
+                    commands.add(new String[]{sed_path, "-e", "s/;//"});
+                    String ipAddr = Script.executePipedCommands(commands, 
0).second();
                     // Check if the IP belongs to the network
-                    if((ipAddr != null) && 
NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)){
+                    if((ipAddr != null) && 
NetUtils.isIpWithInCidrRange(ipAddr, networkCidr)) {
                         ip = ipAddr;
                         break;
                     }
-                    s_logger.debug("GetVmIp: "+command.getVmName()+ " Ip: 
"+ipAddr+" does not belong to network "+networkCidr);
+                    s_logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does 
not belong to network "+networkCidr);
                 }
             }
         } else {
             // For windows, read from guest Vm registry using virt-win-reg 
libguestfs ulitiy. Registry Path: 
HKEY_LOCAL_MACHINE\SYSTEM\ControlSet001\Services\Tcpip\Parameters\Interfaces\<service>\DhcpIPAddress
-            String ipList = Script.runSimpleBashScript(new 
StringBuilder().append("virt-win-reg --unsafe-printable-strings 
").append(command.getVmName())
-                    .append(" 
'HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces'
 | grep DhcpIPAddress | awk -F : '{print $2}' | sed -e 's/^\"//' -e 
's/\"$//'").toString());
+            commands = new ArrayList<>();
+            commands.add(new String[]{virt_win_reg_path, 
"--unsafe-printable-strings", sanitizedVmName, 
"HKEY_LOCAL_MACHINE\\SYSTEM\\ControlSet001\\Services\\Tcpip\\Parameters\\Interfaces"});
+            commands.add(new String[]{grep_path, "DhcpIPAddress"});
+            commands.add(new String[]{awk_path, "-F", ":", "{print $2}"});
+            commands.add(new String[]{sed_path, "-e", "s/^\"//", "-e", 
"s/\"$//"});
+            String ipList = Script.executePipedCommands(commands, 0).second();
             if(ipList != null) {
-                s_logger.debug("GetVmIp: "+command.getVmName()+ "Ips: 
"+ipList);
+                s_logger.debug("GetVmIp: "+ vmName + "Ips: "+ipList);
                 String[] ips = ipList.split("\n");
                 for (String ipAddr : ips){
                     // Check if the IP belongs to the network
@@ -69,13 +93,13 @@ public final class LibvirtGetVmIpAddressCommandWrapper 
extends CommandWrapper<Ge
                         ip = ipAddr;
                         break;
                     }
-                    s_logger.debug("GetVmIp: "+command.getVmName()+ " Ip: 
"+ipAddr+" does not belong to network "+networkCidr);
+                    s_logger.debug("GetVmIp: "+ vmName + " Ip: "+ipAddr+" does 
not belong to network "+networkCidr);
                 }
             }
         }
         if(ip != null){
             result = true;
-            s_logger.debug("GetVmIp: "+command.getVmName()+ " Found Ip: "+ip);
+            s_logger.debug("GetVmIp: "+ vmName + " Found Ip: "+ip);
         }
         return new Answer(command, result, ip);
     }
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java
index 5c79de5f4bf..117a832a68b 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapper.java
@@ -19,7 +19,12 @@
 
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
-import org.apache.commons.lang3.StringUtils;
+import java.net.InetAddress;
+import java.net.InterfaceAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+
 import org.apache.log4j.Logger;
 
 import com.cloud.agent.api.Answer;
@@ -28,33 +33,73 @@ import com.cloud.agent.api.OvsFetchInterfaceCommand;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
-import com.cloud.utils.script.Script;
+import com.cloud.utils.Ternary;
 
 @ResourceWrapper(handles =  OvsFetchInterfaceCommand.class)
 public final class LibvirtOvsFetchInterfaceCommandWrapper extends 
CommandWrapper<OvsFetchInterfaceCommand, Answer, LibvirtComputingResource> {
 
     private static final Logger s_logger = 
Logger.getLogger(LibvirtOvsFetchInterfaceCommandWrapper.class);
 
+    private String getSubnetMaskForAddress(NetworkInterface networkInterface, 
InetAddress inetAddress) {
+        for (InterfaceAddress address : 
networkInterface.getInterfaceAddresses()) {
+            if (!inetAddress.equals(address.getAddress())) {
+                continue;
+            }
+            int prefixLength = address.getNetworkPrefixLength();
+            int mask = 0xffffffff << (32 - prefixLength);
+            return String.format("%d.%d.%d.%d",
+                    (mask >>> 24) & 0xff,
+                    (mask >>> 16) & 0xff,
+                    (mask >>> 8) & 0xff,
+                    mask & 0xff);
+        }
+        return "";
+    }
+
+    private String getMacAddress(NetworkInterface networkInterface) throws 
SocketException {
+        byte[] macBytes = networkInterface.getHardwareAddress();
+        if (macBytes == null) {
+            return "";
+        }
+        StringBuilder macAddress = new StringBuilder();
+        for (byte b : macBytes) {
+            macAddress.append(String.format("%02X:", b));
+        }
+        if (macAddress.length() > 0) {
+            macAddress.deleteCharAt(macAddress.length() - 1);  // Remove 
trailing colon
+        }
+        return macAddress.toString();
+    }
+
+    public Ternary<String, String, String> getInterfaceDetails(String 
interfaceName) throws SocketException {
+        NetworkInterface networkInterface = 
NetworkInterface.getByName(interfaceName);
+        if (networkInterface == null) {
+            logger.warn(String.format("Network interface: '%s' not found", 
interfaceName));
+            return new Ternary<>(null, null, null);
+        }
+        Enumeration<InetAddress> inetAddresses = 
networkInterface.getInetAddresses();
+        while (inetAddresses.hasMoreElements()) {
+            InetAddress inetAddress = inetAddresses.nextElement();
+            if (inetAddress instanceof java.net.Inet4Address) {
+                String ipAddress = inetAddress.getHostAddress();
+                String subnetMask = getSubnetMaskForAddress(networkInterface, 
inetAddress);
+                String macAddress = getMacAddress(networkInterface);
+                return new Ternary<>(ipAddress, subnetMask, macAddress);
+            }
+        }
+        return new Ternary<>(null, null, null);
+    }
+
     @Override
     public Answer execute(final OvsFetchInterfaceCommand command, final 
LibvirtComputingResource libvirtComputingResource) {
-        final String label = command.getLabel();
+        final String label = "'" + command.getLabel() + "'";
 
         s_logger.debug("Will look for network with name-label:" + label);
         try {
-            String ipadd = Script.runSimpleBashScript("ifconfig " + label + " 
| grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}'");
-            if (StringUtils.isEmpty(ipadd)) {
-                ipadd = Script.runSimpleBashScript("ifconfig " + label + " | 
grep ' inet ' | awk '{ print $2}'");
-            }
-            String mask = Script.runSimpleBashScript("ifconfig " + label + " | 
grep 'inet addr:' | cut -d: -f4");
-            if (StringUtils.isEmpty(mask)) {
-                mask = Script.runSimpleBashScript("ifconfig " + label + " | 
grep ' inet ' | awk '{ print $4}'");
-            }
-            String mac = Script.runSimpleBashScript("ifconfig " + label + " | 
grep HWaddr | awk -F \" \" '{print $5}'");
-            if (StringUtils.isEmpty(mac)) {
-                mac = Script.runSimpleBashScript("ifconfig " + label + " | 
grep ' ether ' | awk '{ print $2}'");
-            }
+            Ternary<String, String, String> interfaceDetails = 
getInterfaceDetails(label);
             return new OvsFetchInterfaceAnswer(command, true, "Interface " + 
label
-                    + " retrieved successfully", ipadd, mask, mac);
+                    + " retrieved successfully", interfaceDetails.first(), 
interfaceDetails.second(),
+                    interfaceDetails.third());
 
         } catch (final Exception e) {
             s_logger.warn("Caught execption when fetching interface", e);
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
index 3f281e54bba..769482fa0d7 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapper.java
@@ -47,7 +47,6 @@ import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
 import com.cloud.storage.Volume;
 import com.cloud.utils.exception.CloudRuntimeException;
-import com.cloud.utils.script.Script;
 
 @ResourceWrapper(handles =  PrepareForMigrationCommand.class)
 public final class LibvirtPrepareForMigrationCommandWrapper extends 
CommandWrapper<PrepareForMigrationCommand, Answer, LibvirtComputingResource> {
@@ -126,9 +125,7 @@ public final class LibvirtPrepareForMigrationCommandWrapper 
extends CommandWrapp
         } catch (final LibvirtException | CloudRuntimeException | 
InternalErrorException | URISyntaxException e) {
             if (MapUtils.isNotEmpty(dpdkInterfaceMapping)) {
                 for (DpdkTO to : dpdkInterfaceMapping.values()) {
-                    String cmd = String.format("ovs-vsctl del-port %s", 
to.getPort());
-                    s_logger.debug("Removing DPDK port: " + to.getPort());
-                    Script.runSimpleBashScript(cmd);
+                    removeDpdkPort(to.getPort());
                 }
             }
             return new PrepareForMigrationAnswer(command, e.toString());
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
index fc57cd412f0..0803fc2b956 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtReadyCommandWrapper.java
@@ -19,9 +19,13 @@
 
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
+import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
+import org.apache.log4j.Logger;
+
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.ReadyAnswer;
 import com.cloud.agent.api.ReadyCommand;
@@ -31,8 +35,6 @@ import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
 import com.cloud.utils.script.Script;
 
-import org.apache.log4j.Logger;
-
 @ResourceWrapper(handles =  ReadyCommand.class)
 public final class LibvirtReadyCommandWrapper extends 
CommandWrapper<ReadyCommand, Answer, LibvirtComputingResource> {
 
@@ -50,12 +52,17 @@ public final class LibvirtReadyCommandWrapper extends 
CommandWrapper<ReadyComman
     }
 
     private boolean hostSupportsUefi(boolean isUbuntuHost) {
-        String cmd = "rpm -qa | grep -i ovmf";
+        int result;
         if (isUbuntuHost) {
-            cmd = "dpkg -l ovmf";
+            s_logger.debug("Running command : dpkg -l ovmf");
+            result = 
Script.executeCommandForExitValue(Script.getExecutableAbsolutePath("dpkg"), 
"-l", "ovmf");
+        } else {
+            s_logger.debug("Running command : rpm -qa | grep -i ovmf");
+            List<String[]> commands = new ArrayList<>();
+            commands.add(new String[]{Script.getExecutableAbsolutePath("rpm"), 
"-qa"});
+            commands.add(new 
String[]{Script.getExecutableAbsolutePath("grep"), "-i", "ovmf"});
+            result = Script.executePipedCommands(commands, 0).first();
         }
-        s_logger.debug("Running command : " + cmd);
-        int result = Script.runSimpleBashScriptForExitValue(cmd);
         s_logger.debug("Got result : " + result);
         return result == 0;
     }
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
index 6c83c4d9f06..f230d3f4973 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtRevokeDirectDownloadCertificateWrapper.java
@@ -19,6 +19,15 @@
 
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+
+import 
org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
 import com.cloud.agent.api.Answer;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.resource.CommandWrapper;
@@ -26,14 +35,6 @@ import com.cloud.resource.ResourceWrapper;
 import com.cloud.utils.PropertiesUtil;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.script.Script;
-import 
org.apache.cloudstack.agent.directdownload.RevokeDirectDownloadCertificateCommand;
-import org.apache.cloudstack.utils.security.KeyStoreUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.Logger;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
 
 @ResourceWrapper(handles =  RevokeDirectDownloadCertificateCommand.class)
 public class LibvirtRevokeDirectDownloadCertificateWrapper extends 
CommandWrapper<RevokeDirectDownloadCertificateCommand, Answer, 
LibvirtComputingResource> {
@@ -84,17 +85,17 @@ public class LibvirtRevokeDirectDownloadCertificateWrapper 
extends CommandWrappe
             }
 
             final String keyStoreFile = getKeyStoreFilePath(agentFile);
-
-            String checkCmd = String.format("keytool -list -alias %s -keystore 
%s -storepass %s",
-                    certificateAlias, keyStoreFile, privatePassword);
-            int existsCmdResult = 
Script.runSimpleBashScriptForExitValue(checkCmd);
+            String keyToolPath = Script.getExecutableAbsolutePath("keytool");
+            int existsCmdResult = 
Script.executeCommandForExitValue(keyToolPath, "-list", "-alias",
+                    sanitizeBashCommandArgument(certificateAlias), 
"-keystore", keyStoreFile, "-storepass",
+                    privatePassword);
             if (existsCmdResult == 1) {
                 s_logger.error("Certificate alias " + certificateAlias + " 
does not exist, no need to revoke it");
             } else {
-                String revokeCmd = String.format("keytool -delete -alias %s 
-keystore %s -storepass %s",
-                        certificateAlias, keyStoreFile, privatePassword);
                 s_logger.debug("Revoking certificate alias " + 
certificateAlias + " from keystore " + keyStoreFile);
-                Script.runSimpleBashScriptForExitValue(revokeCmd);
+                Script.executeCommandForExitValue(keyToolPath, "-delete", 
"-alias",
+                        sanitizeBashCommandArgument(certificateAlias), 
"-keystore", keyStoreFile, "-storepass",
+                        privatePassword);;
             }
         } catch (FileNotFoundException | CloudRuntimeException e) {
             s_logger.error("Error while setting up certificate " + 
certificateAlias, e);
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
index fff8da7c4ea..0774d306b8a 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapper.java
@@ -18,21 +18,26 @@
 //
 package com.cloud.hypervisor.kvm.resource.wrapper;
 
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+import 
org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand;
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.log4j.Logger;
+
 import com.cloud.agent.api.Answer;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
+import com.cloud.utils.FileUtil;
 import com.cloud.utils.PropertiesUtil;
 import com.cloud.utils.exception.CloudRuntimeException;
 import com.cloud.utils.script.Script;
-import 
org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificateCommand;
-import org.apache.cloudstack.utils.security.KeyStoreUtils;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.log4j.Logger;
-
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.IOException;
 
 @ResourceWrapper(handles =  SetupDirectDownloadCertificateCommand.class)
 public class LibvirtSetupDirectDownloadCertificateCommandWrapper extends 
CommandWrapper<SetupDirectDownloadCertificateCommand, Answer, 
LibvirtComputingResource> {
@@ -79,9 +84,10 @@ public class 
LibvirtSetupDirectDownloadCertificateCommandWrapper extends Command
      */
     private void importCertificate(String tempCerFilePath, String 
keyStoreFile, String certificateName, String privatePassword) {
         s_logger.debug("Importing certificate from temporary file to 
keystore");
-        String importCommandFormat = "keytool -importcert -file %s -keystore 
%s -alias '%s' -storepass '%s' -noprompt";
-        String importCmd = String.format(importCommandFormat, tempCerFilePath, 
keyStoreFile, certificateName, privatePassword);
-        int result = Script.runSimpleBashScriptForExitValue(importCmd);
+        String keyToolPath = Script.getExecutableAbsolutePath("keytool");
+        int result = Script.executeCommandForExitValue(keyToolPath, 
"-importcert", "file", tempCerFilePath,
+                "-keystore", keyStoreFile, "-alias", 
sanitizeBashCommandArgument(certificateName), "-storepass",
+                privatePassword, "-noprompt");
         if (result != 0) {
             s_logger.debug("Certificate " + certificateName + " not imported 
as it already exist on keystore");
         }
@@ -94,8 +100,7 @@ public class 
LibvirtSetupDirectDownloadCertificateCommandWrapper extends Command
         String tempCerFilePath = String.format("%s/%s-%s",
                 agentFile.getParent(), temporaryCertFilePrefix, 
certificateName);
         s_logger.debug("Creating temporary certificate file into: " + 
tempCerFilePath);
-        int result = 
Script.runSimpleBashScriptForExitValue(String.format("echo '%s' > %s", 
certificate, tempCerFilePath));
-        if (result != 0) {
+        if (!FileUtil.writeToFile(tempCerFilePath, certificate)) {
             throw new CloudRuntimeException("Could not create the certificate 
file on path: " + tempCerFilePath);
         }
         return tempCerFilePath;
@@ -104,9 +109,23 @@ public class 
LibvirtSetupDirectDownloadCertificateCommandWrapper extends Command
     /**
      * Remove temporary file
      */
-    private void cleanupTemporaryFile(String temporaryFile) {
+    protected void cleanupTemporaryFile(String temporaryFile) {
         s_logger.debug("Cleaning up temporary certificate file");
-        Script.runSimpleBashScript("rm -f " + temporaryFile);
+        if (StringUtils.isBlank(temporaryFile)) {
+            s_logger.debug("Provided temporary certificate file path is 
empty");
+            return;
+        }
+        try {
+            Path filePath = Paths.get(temporaryFile);
+            if (!Files.exists(filePath)) {
+                s_logger.debug("Temporary certificate file does not exist: " + 
temporaryFile);
+                return;
+            }
+            Files.delete(filePath);
+        } catch (IOException e) {
+            s_logger.warn(String.format("Error while cleaning up temporary 
file: %s", temporaryFile));
+            s_logger.debug(String.format("Error while cleaning up temporary 
file: %s", temporaryFile), e);
+        }
     }
 
     @Override
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
index 7ee6ccddf66..518ee2b6f0a 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapper.java
@@ -23,28 +23,27 @@ import java.io.File;
 import java.util.List;
 import java.util.Map;
 
-import com.cloud.agent.api.to.DpdkTO;
-import com.cloud.hypervisor.kvm.resource.LibvirtKvmAgentHook;
-import com.cloud.utils.Pair;
-import com.cloud.utils.script.Script;
-import com.cloud.utils.ssh.SshHelper;
 import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.collections.MapUtils;
 import org.apache.log4j.Logger;
 import org.libvirt.Connect;
 import org.libvirt.Domain;
 import org.libvirt.DomainInfo.DomainState;
+import org.libvirt.LibvirtException;
 
 import com.cloud.agent.api.Answer;
 import com.cloud.agent.api.StopAnswer;
 import com.cloud.agent.api.StopCommand;
+import com.cloud.agent.api.to.DpdkTO;
 import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.hypervisor.kvm.resource.LibvirtKvmAgentHook;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.DiskDef;
 import com.cloud.hypervisor.kvm.resource.LibvirtVMDef.InterfaceDef;
 import com.cloud.hypervisor.kvm.resource.VifDriver;
 import com.cloud.resource.CommandWrapper;
 import com.cloud.resource.ResourceWrapper;
-import org.libvirt.LibvirtException;
+import com.cloud.utils.Pair;
+import com.cloud.utils.ssh.SshHelper;
 
 @ResourceWrapper(handles =  StopCommand.class)
 public final class LibvirtStopCommandWrapper extends 
CommandWrapper<StopCommand, Answer, LibvirtComputingResource> {
@@ -121,10 +120,7 @@ public final class LibvirtStopCommandWrapper extends 
CommandWrapper<StopCommand,
                     Map<String, DpdkTO> dpdkInterfaceMapping = 
command.getDpdkInterfaceMapping();
                     if (MapUtils.isNotEmpty(dpdkInterfaceMapping)) {
                         for (DpdkTO to : dpdkInterfaceMapping.values()) {
-                            String portToRemove = to.getPort();
-                            String cmd = String.format("ovs-vsctl del-port 
%s", portToRemove);
-                            s_logger.debug("Removing DPDK port: " + 
portToRemove);
-                            Script.runSimpleBashScript(cmd);
+                            removeDpdkPort(to.getPort());
                         }
                     }
                 } else {
diff --git 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapperTest.java
 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapperTest.java
new file mode 100644
index 00000000000..fbc9c2bcb4b
--- /dev/null
+++ 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtOvsFetchInterfaceCommandWrapperTest.java
@@ -0,0 +1,105 @@
+// 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 java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.cloud.utils.StringUtils;
+import com.cloud.utils.Ternary;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(value = {LibvirtOvsFetchInterfaceCommandWrapper.class})
+public class LibvirtOvsFetchInterfaceCommandWrapperTest {
+
+    @Spy
+    LibvirtOvsFetchInterfaceCommandWrapper wrapper = new 
LibvirtOvsFetchInterfaceCommandWrapper();
+
+    @Test
+    public void testGetInterfaceDetailsValidValid() {
+        String interfaceName = null;
+        String ipAddress = null;
+        try {
+            Enumeration<NetworkInterface> interfaces = 
NetworkInterface.getNetworkInterfaces();
+            while(interfaces.hasMoreElements()) {
+                NetworkInterface networkInterface = interfaces.nextElement();
+                if (networkInterface.getInetAddresses().hasMoreElements() &&
+                        (networkInterface.getName().startsWith("eth") ||
+                                networkInterface.getName().startsWith("wl"))) {
+                    interfaceName = networkInterface.getName();
+                    Enumeration<InetAddress> addresses = 
networkInterface.getInetAddresses();
+                    while(addresses.hasMoreElements()) {
+                        InetAddress addr = addresses.nextElement();
+                        if (addr instanceof Inet4Address) {
+                            ipAddress  = addr.getHostAddress();
+                            break;
+                        };
+                    }
+                }
+            }
+        } catch (SocketException ignored) {}
+        Ternary<String, String, String> result = null;
+        try {
+            result = wrapper.getInterfaceDetails(interfaceName);
+        } catch (SocketException e) {
+            Assert.fail("Exception occurred: " + e.getMessage());
+        }
+        Assert.assertNotNull(result);
+        Assert.assertEquals(ipAddress, result.first().trim());
+    }
+
+    private String getTempFilepath() {
+        return String.format("%s/%s.txt", 
System.getProperty("java.io.tmpdir"), UUID.randomUUID());
+    }
+
+    private void runTestGetInterfaceDetailsForRandomInterfaceName(String arg) {
+        try {
+            Ternary<String, String, String> result = 
wrapper.getInterfaceDetails(arg);
+            Assert.assertTrue(StringUtils.isAllEmpty(result.first(), 
result.second(), result.third()));
+        } catch (SocketException e) {
+            Assert.fail(String.format("Exception occurred: %s", 
e.getMessage()));
+        }
+    }
+
+    @Test
+    public void testGetInterfaceDetailsForRandomInterfaceName() {
+        List<String> commandVariants = List.of(
+                "';touch %s'",
+                ";touch %s",
+                "&& touch %s",
+                "|| touch %s",
+                UUID.randomUUID().toString());
+        for (String cmd : commandVariants) {
+            String filePath = getTempFilepath();
+            String arg = String.format(cmd, filePath);
+            runTestGetInterfaceDetailsForRandomInterfaceName(arg);
+        }
+    }
+}
diff --git 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
index 5530819c2e4..e534f815e3d 100644
--- 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
+++ 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtPrepareForMigrationCommandWrapperTest.java
@@ -1,4 +1,3 @@
-//
 // 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
@@ -15,15 +14,14 @@
 // 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.PrepareForMigrationAnswer;
-import com.cloud.agent.api.PrepareForMigrationCommand;
-import com.cloud.agent.api.to.DpdkTO;
-import com.cloud.agent.api.to.VirtualMachineTO;
-import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
 import org.junit.Assert;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -33,8 +31,12 @@ import org.mockito.Spy;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
 
-import java.util.HashMap;
-import java.util.Map;
+import com.cloud.agent.api.PrepareForMigrationAnswer;
+import com.cloud.agent.api.PrepareForMigrationCommand;
+import com.cloud.agent.api.to.DpdkTO;
+import com.cloud.agent.api.to.VirtualMachineTO;
+import com.cloud.hypervisor.kvm.resource.LibvirtComputingResource;
+import com.cloud.utils.exception.CloudRuntimeException;
 
 @RunWith(PowerMockRunner.class)
 @PrepareForTest(value = {LibvirtPrepareForMigrationCommandWrapper.class})
@@ -72,4 +74,29 @@ public class LibvirtPrepareForMigrationCommandWrapperTest {
 
         Assert.assertEquals(cpuShares, 
prepareForMigrationAnswer.getNewVmCpuShares().intValue());
     }
+
+    private String getTempFilepath() {
+        return String.format("%s/%s.txt", 
System.getProperty("java.io.tmpdir"), UUID.randomUUID());
+    }
+
+    private void runTestRemoveDpdkPortForCommandInjection(String 
portWithCommand) {
+        try {
+            
libvirtPrepareForMigrationCommandWrapperSpy.removeDpdkPort(portWithCommand);
+            Assert.fail(String.format("Command injection working for 
portWithCommand: %s", portWithCommand));
+        } catch (CloudRuntimeException ignored) {}
+    }
+
+    @Test
+    public void testRemoveDpdkPortForCommandInjection() {
+        List<String> commandVariants = List.of(
+                "';touch %s'",
+                ";touch %s",
+                "&& touch %s",
+                "|| touch %s",
+                UUID.randomUUID().toString());
+        for (String cmd : commandVariants) {
+            String portWithCommand = String.format(cmd, getTempFilepath());
+            runTestRemoveDpdkPortForCommandInjection(portWithCommand);
+        }
+    }
 }
diff --git 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapperTest.java
 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapperTest.java
new file mode 100644
index 00000000000..cd78733a237
--- /dev/null
+++ 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtSetupDirectDownloadCertificateCommandWrapperTest.java
@@ -0,0 +1,93 @@
+//
+// 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 java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(value = 
{LibvirtSetupDirectDownloadCertificateCommandWrapper.class})
+public class LibvirtSetupDirectDownloadCertificateCommandWrapperTest {
+
+    @Spy
+    LibvirtSetupDirectDownloadCertificateCommandWrapper wrapper = new 
LibvirtSetupDirectDownloadCertificateCommandWrapper();
+
+    private String getTempFilepath() {
+        return String.format("%s/%s.txt", 
System.getProperty("java.io.tmpdir"), UUID.randomUUID());
+    }
+
+    private void runTestCleanupTemporaryFileForRandomFileNames(String 
fileWithCommand, String filePath) {
+        wrapper.cleanupTemporaryFile(fileWithCommand);
+        File f = new File(filePath);
+        if(f.exists() && !f.isDirectory()) {
+            Assert.fail(String.format("Command injection working for 
fileWithCommand: %s", fileWithCommand));
+        }
+    }
+
+    @Test
+    public void testCleanupTemporaryFileForRandomFileNames() {
+        List<String> commandVariants = List.of(
+                "';touch %s'",
+                ";touch %s",
+                "&& touch %s",
+                "|| touch %s",
+                "%s");
+        for (String cmd : commandVariants) {
+            String filePath = getTempFilepath();
+            String arg = String.format(cmd, filePath);
+            runTestCleanupTemporaryFileForRandomFileNames(arg, filePath);
+        }
+    }
+
+    private String createTempFile() {
+        String filePath = getTempFilepath();
+        Path path = Paths.get(getTempFilepath());
+        try {
+            if (Files.notExists(path)) {
+                Files.createFile(path);
+            }
+        } catch (IOException e) {
+            Assert.fail(String.format("Error while creating file: %s due to 
%s", filePath, e.getMessage()));
+        }
+        return filePath;
+    }
+
+    @Test
+    public void testCleanupTemporaryFileValid() {
+        String filePath = createTempFile();
+        wrapper.cleanupTemporaryFile(filePath);
+        File f = new File(filePath);
+        if(f.exists() && !f.isDirectory()) {
+            Assert.fail(String.format("Command injection working for 
fileWithCommand: %s", filePath));
+        }
+    }
+}
diff --git 
a/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapperTest.java
 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapperTest.java
new file mode 100644
index 00000000000..c701946edaa
--- /dev/null
+++ 
b/plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtStopCommandWrapperTest.java
@@ -0,0 +1,63 @@
+// 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 java.util.List;
+import java.util.UUID;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Spy;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(value = {LibvirtStopCommandWrapper.class})
+public class LibvirtStopCommandWrapperTest {
+
+    @Spy
+    LibvirtStopCommandWrapper wrapper = new LibvirtStopCommandWrapper();
+
+    private String getTempFilepath() {
+        return String.format("%s/%s.txt", 
System.getProperty("java.io.tmpdir"), UUID.randomUUID());
+    }
+
+    private void runTestRemoveDpdkPortForCommandInjection(String 
portWithCommand) {
+        try {
+            wrapper.removeDpdkPort(portWithCommand);
+            Assert.fail(String.format("Command injection working for 
portWithCommand: %s", portWithCommand));
+        } catch (CloudRuntimeException ignored) {}
+    }
+
+    @Test
+    public void testRemoveDpdkPortForCommandInjection() {
+        List<String> commandVariants = List.of(
+                "';touch %s'",
+                ";touch %s",
+                "&& touch %s",
+                "|| touch %s",
+                UUID.randomUUID().toString());
+        for (String cmd : commandVariants) {
+            String portWithCommand = String.format(cmd, getTempFilepath());
+            runTestRemoveDpdkPortForCommandInjection(portWithCommand);
+        }
+    }
+}
diff --git a/server/src/main/java/com/cloud/api/ApiServer.java 
b/server/src/main/java/com/cloud/api/ApiServer.java
index 4a7259c6d33..cf0e689ed27 100644
--- a/server/src/main/java/com/cloud/api/ApiServer.java
+++ b/server/src/main/java/com/cloud/api/ApiServer.java
@@ -399,6 +399,17 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
         }
     }
 
+    protected void setupIntegrationPortListener(Integer apiPort) {
+        if (apiPort == null || apiPort <= 0) {
+            s_logger.trace(String.format("Skipping setting up listener for 
integration port as %s is set to %d",
+                    IntegrationAPIPort.key(), apiPort));
+            return;
+        }
+        s_logger.debug(String.format("Setting up integration API service 
listener on port: %d", apiPort));
+        final ListenerThread listenerThread = new ListenerThread(this, 
apiPort);
+        listenerThread.start();
+    }
+
     @Override
     public boolean start() {
         Security.addProvider(new BouncyCastleProvider());
@@ -444,10 +455,7 @@ public class ApiServer extends ManagerBase implements 
HttpRequestHandler, ApiSer
 
         setEncodeApiResponse(EncodeApiResponse.value());
 
-        if (apiPort != null) {
-            final ListenerThread listenerThread = new ListenerThread(this, 
apiPort);
-            listenerThread.start();
-        }
+        setupIntegrationPortListener(apiPort);
 
         return true;
     }
diff --git a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java 
b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java
index facad1add16..609c4d512a9 100644
--- a/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java
+++ b/server/src/main/java/org/apache/cloudstack/ca/CAManagerImpl.java
@@ -24,6 +24,7 @@ import java.security.KeyStore;
 import java.security.KeyStoreException;
 import java.security.cert.CertificateExpiredException;
 import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.CertificateParsingException;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -432,6 +433,14 @@ public class CAManagerImpl extends ManagerBase implements 
CAManager {
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {CAProviderPlugin, CertKeySize, 
CertSignatureAlgorithm, CertValidityPeriod, AutomaticCertRenewal, 
AllowHostIPInSysVMAgentCert, CABackgroundJobDelay, CertExpiryAlertPeriod};
+        return new ConfigKey<?>[] {CAProviderPlugin, CertKeySize, 
CertSignatureAlgorithm, CertValidityPeriod,
+                AutomaticCertRenewal, AllowHostIPInSysVMAgentCert, 
CABackgroundJobDelay, CertExpiryAlertPeriod,
+                CertManagementCustomSubjectAlternativeName
+        };
+    }
+
+    @Override
+    public boolean isManagementCertificate(java.security.cert.Certificate 
certificate) throws CertificateParsingException {
+        return getConfiguredCaProvider().isManagementCertificate(certificate);
     }
 }
diff --git a/server/src/test/java/com/cloud/api/ApiServerTest.java 
b/server/src/test/java/com/cloud/api/ApiServerTest.java
new file mode 100644
index 00000000000..2c7eebddc49
--- /dev/null
+++ b/server/src/test/java/com/cloud/api/ApiServerTest.java
@@ -0,0 +1,73 @@
+// 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.api;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.InjectMocks;
+import org.mockito.Mockito;
+import org.powermock.api.mockito.PowerMockito;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+import org.powermock.modules.junit4.PowerMockRunner;
+
+@RunWith(PowerMockRunner.class)
+@PrepareForTest(ApiServer.class)
+public class ApiServerTest {
+
+    @InjectMocks
+    ApiServer apiServer = new ApiServer();
+
+    private List<ApiServer.ListenerThread> createdListeners;
+
+    private void runTestSetupIntegrationPortListenerInvalidPorts(Integer port) 
{
+        try {
+            ApiServer.ListenerThread mocked = 
Mockito.mock(ApiServer.ListenerThread.class);
+            
PowerMockito.whenNew(ApiServer.ListenerThread.class).withAnyArguments().thenReturn(mocked);
+            apiServer.setupIntegrationPortListener(port);
+            Mockito.verify(mocked, Mockito.never()).start();
+        } catch (Exception e) {
+            Assert.fail(String.format("Exception occurred: %s", 
e.getMessage()));
+        }
+    }
+
+    @Test
+    public void testSetupIntegrationPortListenerInvalidPorts() {
+        List<Integer> ports = new ArrayList<>(List.of(-1, -10, 0));
+        ports.add(null);
+        for (Integer port : ports) {
+            runTestSetupIntegrationPortListenerInvalidPorts(port);
+        }
+    }
+
+    @Test
+    public void testSetupIntegrationPortListenerValidPort() {
+        Integer validPort = 8080;
+        try {
+            ApiServer.ListenerThread mocked = 
Mockito.mock(ApiServer.ListenerThread.class);
+            
PowerMockito.whenNew(ApiServer.ListenerThread.class).withAnyArguments().thenReturn(mocked);
+            apiServer.setupIntegrationPortListener(validPort);
+            
PowerMockito.verifyNew(ApiServer.ListenerThread.class).withArguments(apiServer, 
validPort);
+            Mockito.verify(mocked).start();
+        } catch (Exception e) {
+            Assert.fail(String.format("Exception occurred: %s", 
e.getMessage()));
+        }
+    }
+}
diff --git a/utils/src/main/java/com/cloud/utils/FileUtil.java 
b/utils/src/main/java/com/cloud/utils/FileUtil.java
index d9bf0817687..fbb04e134dc 100644
--- a/utils/src/main/java/com/cloud/utils/FileUtil.java
+++ b/utils/src/main/java/com/cloud/utils/FileUtil.java
@@ -21,15 +21,20 @@ package com.cloud.utils;
 
 import java.io.File;
 import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
 import java.util.Arrays;
 import java.util.List;
 import java.util.stream.Collectors;
 
-import com.cloud.utils.exception.CloudRuntimeException;
-import com.cloud.utils.ssh.SshHelper;
 import org.apache.commons.io.FileUtils;
 import org.apache.log4j.Logger;
 
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.ssh.SshHelper;
+
 public class FileUtil {
     private static final Logger s_logger = Logger.getLogger(FileUtil.class);
 
@@ -57,4 +62,16 @@ public class FileUtil {
         }
         throw new CloudRuntimeException(finalErrMsg);
     }
+
+    public static boolean writeToFile(String fileName, String content) {
+        Path filePath = Paths.get(fileName);
+        try {
+            Files.write(filePath, content.getBytes(StandardCharsets.UTF_8));
+            s_logger.debug(String.format("Successfully wrote to the file: %s", 
fileName));
+            return true;
+        } catch (IOException e) {
+            s_logger.error(String.format("Error writing to the file: %s", 
fileName), e);
+        }
+        return false;
+    }
 }
diff --git a/utils/src/main/java/com/cloud/utils/script/Script.java 
b/utils/src/main/java/com/cloud/utils/script/Script.java
index 31585534cbd..27ec65a18a4 100644
--- a/utils/src/main/java/com/cloud/utils/script/Script.java
+++ b/utils/src/main/java/com/cloud/utils/script/Script.java
@@ -19,14 +19,6 @@
 
 package com.cloud.utils.script;
 
-import com.cloud.utils.PropertiesUtil;
-import com.cloud.utils.concurrency.NamedThreadFactory;
-import com.cloud.utils.script.OutputInterpreter.TimedOutLogger;
-import org.apache.cloudstack.utils.security.KeyStoreUtils;
-import org.apache.commons.io.IOUtils;
-import org.apache.log4j.Logger;
-import org.joda.time.Duration;
-
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.IOException;
@@ -40,10 +32,24 @@ import java.util.ArrayList;
 import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+
+import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.commons.io.IOUtils;
+import org.apache.log4j.Logger;
+import org.joda.time.Duration;
+
+import com.cloud.utils.Pair;
+import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.concurrency.NamedThreadFactory;
+import com.cloud.utils.script.OutputInterpreter.TimedOutLogger;
 
 public class Script implements Callable<String> {
     private static final Logger s_logger = Logger.getLogger(Script.class);
@@ -52,7 +58,7 @@ public class Script implements Callable<String> {
 
     public static final String ERR_EXECUTE = "execute.error";
     public static final String ERR_TIMEOUT = "timeout";
-    private int _defaultTimeout = 3600 * 1000; /* 1 hour */
+    private static final int DEFAULT_TIMEOUT = 3600 * 1000; /* 1 hour */
     private volatile boolean _isTimeOut = false;
 
     private boolean _passwordCommand = false;
@@ -84,7 +90,7 @@ public class Script implements Callable<String> {
         _timeout = timeout;
         if (_timeout == 0) {
             /* always using default timeout 1 hour to avoid thread hang */
-            _timeout = _defaultTimeout;
+            _timeout = DEFAULT_TIMEOUT;
         }
         _process = null;
         _logger = logger != null ? logger : s_logger;
@@ -493,16 +499,7 @@ public class Script implements Callable<String> {
         return null;
     }
 
-    public static String runSimpleBashScript(String command) {
-        return Script.runSimpleBashScript(command, 0);
-    }
-
-    public static String runSimpleBashScript(String command, int timeout) {
-
-        Script s = new Script("/bin/bash", timeout);
-        s.add("-c");
-        s.add(command);
-
+    private static String runScript(Script s) {
         OutputInterpreter.OneLineParser parser = new 
OutputInterpreter.OneLineParser();
         if (s.execute(parser) != null)
             return null;
@@ -514,16 +511,83 @@ public class Script implements Callable<String> {
             return result.trim();
     }
 
-    public static int runSimpleBashScriptForExitValue(String command) {
-        return runSimpleBashScriptForExitValue(command, 0);
-    }
-
-    public static int runSimpleBashScriptForExitValue(String command, int 
timeout) {
-
+    public static String runSimpleBashScript(String command, int timeout) {
         Script s = new Script("/bin/bash", timeout);
         s.add("-c");
         s.add(command);
+        return runScript(s);
+    }
 
+    public static String runSimpleBashScript(String command) {
+        return Script.runSimpleBashScript(command, 0);
+    }
+
+    public static String getExecutableAbsolutePath(String executable) {
+        for (String dirName : System.getenv("PATH").split(File.pathSeparator)) 
{
+            File file = new File(dirName, executable);
+            if (file.isFile() && file.canExecute()) {
+                return file.getAbsolutePath();
+            }
+        }
+        return executable;
+    }
+
+    private static Script getScriptForCommandRun(String... command) {
+        Script s = new Script(command[0], 0);
+        if (command.length > 1) {
+            for (int i = 1; i < command.length; ++i) {
+                s.add(command[i]);
+            }
+        }
+        return s;
+    }
+
+    public static String executeCommand(String... command) {
+        return runScript(getScriptForCommandRun(command));
+    }
+
+    public static int executeCommandForExitValue(String... command) {
+        return runScriptForExitValue(getScriptForCommandRun(command));
+    }
+
+    public static Pair<Integer, String> executePipedCommands(List<String[]> 
commands, long timeout) {
+        if (timeout <= 0) {
+            timeout = DEFAULT_TIMEOUT;
+        }
+        Callable<Pair<Integer, String>> commandRunner = () -> {
+            List<ProcessBuilder> builders = 
commands.stream().map(ProcessBuilder::new).collect(Collectors.toList());
+            List<Process> processes = ProcessBuilder.startPipeline(builders);
+            Process last = processes.get(processes.size()-1);
+            try (BufferedReader reader = new BufferedReader(new 
InputStreamReader(last.getInputStream()))) {
+                String line;
+                StringBuilder output = new StringBuilder();
+                while ((line = reader.readLine()) != null) {
+                    output.append(line).append(System.lineSeparator());
+                }
+                last.waitFor();
+                s_logger.debug("Piped commands executed successfully");
+                return new Pair<>(last.exitValue(), output.toString());
+            } catch (IOException | InterruptedException e) {
+                s_logger.error("Error executing piped commands", e);
+                return new Pair<>(-1, stackTraceAsString(e));
+            }
+        };
+
+        Future<Pair<Integer, String>> future = 
s_executors.submit(commandRunner);
+        Pair<Integer, String> result = new Pair<>(-1, ERR_EXECUTE);
+        try {
+            result = future.get(timeout, TimeUnit.MILLISECONDS);
+        } catch (TimeoutException e) {
+            s_logger.error("Piped command execution timed out, attempting to 
terminate the processes.");
+            future.cancel(true);
+            result.second(ERR_TIMEOUT);
+        } catch (InterruptedException | ExecutionException e) {
+            s_logger.error("Error executing piped commands", e);
+        }
+        return result;
+    }
+
+    private static int runScriptForExitValue(Script s) {
         String result = s.execute(null);
         if (result == null || result.trim().isEmpty())
             return -1;
@@ -536,4 +600,14 @@ public class Script implements Callable<String> {
         }
     }
 
+    public static int runSimpleBashScriptForExitValue(String command) {
+        return runSimpleBashScriptForExitValue(command, 0);
+    }
+
+    public static int runSimpleBashScriptForExitValue(String command, int 
timeout) {
+        Script s = new Script("/bin/bash", timeout);
+        s.add("-c");
+        s.add(command);
+        return runScriptForExitValue(s);
+    }
 }
diff --git a/utils/src/test/java/com/cloud/utils/script/ScriptTest.java 
b/utils/src/test/java/com/cloud/utils/script/ScriptTest.java
new file mode 100644
index 00000000000..cc6047959da
--- /dev/null
+++ b/utils/src/test/java/com/cloud/utils/script/ScriptTest.java
@@ -0,0 +1,81 @@
+// 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.utils.script;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.lang3.StringUtils;
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import com.cloud.utils.Pair;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ScriptTest {
+
+    @Test
+    public void testExecutePipedCommandsSingle() {
+        String keyword = "Hello World!";
+        List<String[]> commands = new ArrayList<>();
+        commands.add(new String[]{"echo", keyword});
+        Pair<Integer, String> result = Script.executePipedCommands(commands, 
0);
+        Assert.assertNotNull("Result should not be null", result);
+        Assert.assertEquals(0, result.first().intValue());
+        String output = result.second().trim();
+        Assert.assertTrue(StringUtils.isNotEmpty(output));
+        Assert.assertEquals(keyword, output);
+    }
+
+    @Test
+    public void testExecutePipedCommandsMultiple() {
+        String keyword = "Hello";
+        List<String[]> commands = Arrays.asList(
+                new String[]{"echo", String.format("%s\n World", keyword)},
+                new String[]{"grep", keyword}
+        );
+        Pair<Integer, String> result = Script.executePipedCommands(commands, 
0);
+        Assert.assertNotNull("Result should not be null", result);
+        Assert.assertEquals(0, result.first().intValue());
+        String output = result.second().trim();
+        Assert.assertTrue(StringUtils.isNotEmpty(output));
+        Assert.assertEquals(keyword, output);
+    }
+
+    @Test
+    public void testExecutePipedCommandsTimeout() {
+        List<String[]> commands = new ArrayList<>();
+        commands.add(new String[]{"sh", "-c", "sleep 10"});
+        Pair<Integer, String> result = Script.executePipedCommands(commands, 
TimeUnit.SECONDS.toMillis(1));
+        Assert.assertNotNull("Result should not be null", result);
+        Assert.assertEquals(-1, result.first().intValue());
+        Assert.assertEquals(Script.ERR_TIMEOUT, result.second());
+    }
+
+    @Test
+    public void testGetExecutableAbsolutePath() {
+        if (System.getProperty("os.name").startsWith("Windows")) {
+            return;
+        }
+        String result = Script.getExecutableAbsolutePath("ls");
+        Assert.assertTrue(List.of("/usr/bin/ls", "/bin/ls").contains(result));
+    }
+}

Reply via email to