rhtyd closed pull request #2309: HOLD - CLOUDSTACK-10132: Multiple Management 
Servers Support for agents
URL: https://github.com/apache/cloudstack/pull/2309
 
 
   

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

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

diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties
index 0354aceadab..c01bef6703d 100644
--- a/agent/conf/agent.properties
+++ b/agent/conf/agent.properties
@@ -30,6 +30,14 @@ workers=5
 #host= The IP address of management server
 host=localhost
 
+# The time interval in seconds after which agent will check if connected host
+# is the preferred host (the first host in the comma-separated list is 
preferred
+# one) and will attempt to reconnect to the preferred host when it's connected
+# to one of the secondary/backup hosts.
+# The timer task is scheduled when agent starts for the first time, any change
+# in this setting requires restarting the agent.
+host.lb.interval=0
+
 #port = The port management server listening on, default is 8250
 port=8250
 
diff --git a/agent/src/main/java/com/cloud/agent/Agent.java 
b/agent/src/main/java/com/cloud/agent/Agent.java
index d2669c03aeb..44ae0ee4457 100644
--- a/agent/src/main/java/com/cloud/agent/Agent.java
+++ b/agent/src/main/java/com/cloud/agent/Agent.java
@@ -21,6 +21,8 @@
 import java.io.PrintWriter;
 import java.io.StringWriter;
 import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Socket;
 import java.net.UnknownHostException;
 import java.nio.channels.ClosedChannelException;
 import java.nio.charset.Charset;
@@ -38,12 +40,15 @@
 import javax.naming.ConfigurationException;
 
 import 
org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate;
+import org.apache.cloudstack.agent.mslb.SetupManagementServersListAnswer;
+import org.apache.cloudstack.agent.mslb.SetupManagementServersListCommand;
 import org.apache.cloudstack.ca.SetupCertificateAnswer;
 import org.apache.cloudstack.ca.SetupCertificateCommand;
 import org.apache.cloudstack.ca.SetupKeyStoreCommand;
 import org.apache.cloudstack.ca.SetupKeystoreAnswer;
 import org.apache.cloudstack.managed.context.ManagedContextTimerTask;
 import org.apache.cloudstack.utils.security.KeyStoreUtils;
+import org.apache.commons.collections.CollectionUtils;
 import org.apache.commons.io.FileUtils;
 import org.apache.log4j.Logger;
 import org.slf4j.MDC;
@@ -65,6 +70,7 @@
 import com.cloud.exception.AgentControlChannelException;
 import com.cloud.resource.ServerResource;
 import com.cloud.utils.PropertiesUtil;
+import com.cloud.utils.StringUtils;
 import com.cloud.utils.backoff.BackoffAlgorithm;
 import com.cloud.utils.concurrency.NamedThreadFactory;
 import com.cloud.utils.exception.CloudRuntimeException;
@@ -121,6 +127,7 @@ public int value() {
     Long _id;
 
     Timer _timer = new Timer("Agent Timer");
+    Timer hostLBTimer = new Timer("Host LB Timer");
 
     List<WatchTask> _watchList = new ArrayList<WatchTask>();
     long _sequence = 0;
@@ -144,7 +151,7 @@ public Agent(final IAgentShell shell) {
         _shell = shell;
         _link = null;
 
-        _connection = new NioClient("Agent", _shell.getHost(), 
_shell.getPort(), _shell.getWorkers(), this);
+        _connection = new NioClient("Agent", _shell.getNextHost(), 
_shell.getPort(), _shell.getWorkers(), this);
 
         Runtime.getRuntime().addShutdownHook(new ShutdownThread(this));
 
@@ -179,7 +186,7 @@ public Agent(final IAgentShell shell, final int 
localAgentId, final ServerResour
             throw new ConfigurationException("Unable to configure " + 
_resource.getName());
         }
 
-        final String host = _shell.getHost();
+        final String host = _shell.getNextHost();
         _connection = new NioClient("Agent", host, _shell.getPort(), 
_shell.getWorkers(), this);
 
         // ((NioClient)_connection).setBindAddress(_shell.getPrivateIp());
@@ -255,7 +262,7 @@ public void start() {
             s_logger.info("Attempted to connect to the server, but received an 
unexpected exception, trying again...");
         }
         while (!_connection.isStartup()) {
-            final String host = _shell.getHost();
+            final String host = _shell.getNextHost();
             _shell.getBackoffAlgorithm().waitBeforeRetry();
             _connection = new NioClient("Agent", host, _shell.getPort(), 
_shell.getWorkers(), this);
             s_logger.info("Connecting to host:" + host);
@@ -266,6 +273,8 @@ public void start() {
                 s_logger.info("Attempted to connect to the server, but 
received an unexpected exception, trying again...");
             }
         }
+        _shell.updateConnectedHost();
+        scheduleHostLBCheckerTask();
     }
 
     public void stop(final String reason, final String detail) {
@@ -310,6 +319,14 @@ public void setId(final Long id) {
         _shell.setPersistentProperty(getResourceName(), "id", 
Long.toString(id));
     }
 
+    private synchronized void scheduleHostLBCheckerTask() {
+        final long checkInterval = _shell.getHostLBTimerInterval();
+        if (checkInterval > 0L) {
+            s_logger.info("Scheduling preferred host timer task with 
host.lb.interval=" + checkInterval + "ms");
+            hostLBTimer.scheduleAtFixedRate(new PreferredHostCheckerTask(), 
checkInterval, checkInterval);
+        }
+    }
+
     public void scheduleWatch(final Link link, final Request request, final 
long delay, final long period) {
         synchronized (_watchList) {
             if (s_logger.isDebugEnabled()) {
@@ -332,8 +349,7 @@ protected void cancelTasks() {
             _watchList.clear();
         }
     }
-    public synchronized void lockStartupTask(final Link link)
-    {
+    public synchronized void lockStartupTask(final Link link) {
         _startup = new StartupTask(link);
         _timer.schedule(_startup, _startupWait);
     }
@@ -402,19 +418,23 @@ protected void reconnect(final Link link) {
             }
         }
 
-        link.close();
-        link.terminated();
+        if (link != null) {
+            link.close();
+            link.terminated();
+        }
 
         setLink(null);
         cancelTasks();
 
         _resource.disconnected();
 
+        final String lastConnectedHost = _shell.getConnectedHost();
+
         int inProgress = 0;
         do {
             _shell.getBackoffAlgorithm().waitBeforeRetry();
 
-            s_logger.info("Lost connection to the server. Dealing with the 
remaining commands...");
+            s_logger.info("Lost connection to host: " + lastConnectedHost + ". 
Dealing with the remaining commands...");
 
             inProgress = _inProgress.get();
             if (inProgress > 0) {
@@ -434,10 +454,10 @@ protected void reconnect(final Link link) {
             _shell.getBackoffAlgorithm().waitBeforeRetry();
         }
 
-        final String host = _shell.getHost();
         do {
+            String host = _shell.getNextHost();
             _connection = new NioClient("Agent", host, _shell.getPort(), 
_shell.getWorkers(), this);
-            s_logger.info("Reconnecting to host:" + host);
+            s_logger.info("Lost connection to host: " + lastConnectedHost + ". 
Connecting to next host: " + host);
             try {
                 _connection.start();
             } catch (final NioConnectionException e) {
@@ -452,7 +472,8 @@ protected void reconnect(final Link link) {
             }
             _shell.getBackoffAlgorithm().waitBeforeRetry();
         } while (!_connection.isStartup());
-        s_logger.info("Connected to the server");
+        _shell.updateConnectedHost();
+        s_logger.info("Connected to the host: " + _shell.getConnectedHost());
     }
 
     public void processStartupAnswer(final Answer answer, final Response 
response, final Link link) {
@@ -554,6 +575,8 @@ protected void processRequest(final Request request, final 
Link link) {
                         answer = 
setupAgentCertificate((SetupCertificateCommand) cmd);
                     } else if (cmd instanceof SetupDirectDownloadCertificate) {
                         answer = 
setupDirectDownloadCertificate((SetupDirectDownloadCertificate) cmd);
+                    } else if (cmd instanceof 
SetupManagementServersListCommand) {
+                        answer = 
setupManagementServersList((SetupManagementServersListCommand) cmd);
                     } else {
                         if (cmd instanceof ReadyCommand) {
                             processReadyCommand(cmd);
@@ -628,6 +651,41 @@ private Answer 
setupDirectDownloadCertificate(SetupDirectDownloadCertificate cmd
         return new Answer(cmd, true, "Certificate " + certificateName + " 
imported");
     }
 
+    /**
+     * Persist newly received management servers list
+     * @param msList management servers list
+     */
+    private void persistNewManagementServersList(List<String> msList) {
+        final String newHosts = StringUtils.toCSVList(msList);
+        _shell.setHosts(newHosts);
+        _shell.setPersistentProperty(null, "host", newHosts);
+        s_logger.info("Saved new management servers list: " + msList);
+    }
+
+    /**
+     * Persist mgmt hosts list if it is not empty
+     * @param msList management hosts list
+     */
+    private void setupMgmtHostsList(List<String> msList) {
+        if (CollectionUtils.isNotEmpty(msList)) {
+            try {
+                persistNewManagementServersList(msList);
+                _shell.resetHostCounter();
+            } catch (Exception e) {
+                throw new CloudRuntimeException("Couldnt persist received 
management servers list", e);
+            }
+        }
+    }
+
+    /**
+     * Process SetupManagementServersListCommand
+     */
+    private Answer 
setupManagementServersList(SetupManagementServersListCommand cmd) {
+        final List<String> msList = cmd.getMsList();
+        setupMgmtHostsList(msList);
+        return new SetupManagementServersListAnswer(true);
+    }
+
     public Answer setupAgentKeystore(final SetupKeyStoreCommand cmd) {
         final String keyStorePassword = cmd.getKeystorePassword();
         final long validityDays = cmd.getValidityDays();
@@ -728,15 +786,15 @@ public void processResponse(final Response response, 
final Link link) {
     }
 
     public void processReadyCommand(final Command cmd) {
-
         final ReadyCommand ready = (ReadyCommand)cmd;
 
-        s_logger.info("Proccess agent ready command, agent id = " + 
ready.getHostId());
+        s_logger.info("Processing agent ready command, agent id = " + 
ready.getHostId());
         if (ready.getHostId() != null) {
             setId(ready.getHostId());
         }
-        s_logger.info("Ready command is processed: agent id = " + getId());
+        setupMgmtHostsList(ready.getMgmtHosts());
 
+        s_logger.info("Ready command is processed for agent id = " + getId());
     }
 
     public void processOtherTask(final Task task) {
@@ -1018,4 +1076,41 @@ public void doTask(final Task task) throws 
TaskExecutionException {
             }
         }
     }
+
+    public class PreferredHostCheckerTask extends ManagedContextTimerTask {
+
+         @Override
+         protected void runInContext() {
+             try {
+                 int index = 0;
+                 final String[] msList = _shell.getHosts();
+                 String preferredHost  = msList[index];
+                 final String connectedHost = _shell.getConnectedHost();
+                 if (s_logger.isTraceEnabled()) {
+                     s_logger.trace("Running preferred host checker task, 
connected host=" + connectedHost + ", preferred host=" + preferredHost);
+                 }
+                 if (!preferredHost.equals(connectedHost) && _link != null) {
+                     boolean isHostUp = true;
+                     try (final Socket socket = new Socket()) {
+                         socket.connect(new InetSocketAddress(preferredHost, 
_shell.getPort()), 5000);
+                     } catch (final IOException e) {
+                         isHostUp = false;
+                         if (s_logger.isDebugEnabled()) {
+                             s_logger.debug("Host: " + preferredHost + " is 
not reachable");
+                         }
+                     }
+                     if (isHostUp && _link != null && _inProgress.get() == 0) {
+                         if (s_logger.isDebugEnabled()) {
+                             s_logger.debug("Preferred host " + preferredHost 
+ " is found to be reachable, trying to reconnect");
+                         }
+                         _shell.resetHostCounter();
+                         reconnect(_link);
+                     }
+                 }
+             } catch (Throwable t) {
+                 s_logger.error("Error caught while attempting to connect to 
preferred host", t);
+             }
+         }
+
+    }
 }
diff --git a/agent/src/main/java/com/cloud/agent/AgentShell.java 
b/agent/src/main/java/com/cloud/agent/AgentShell.java
index 5950bc78e61..734ae33c129 100644
--- a/agent/src/main/java/com/cloud/agent/AgentShell.java
+++ b/agent/src/main/java/com/cloud/agent/AgentShell.java
@@ -50,6 +50,7 @@
 import com.cloud.utils.backoff.BackoffAlgorithm;
 import com.cloud.utils.backoff.impl.ConstantTimeBackoff;
 import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.common.base.Strings;
 
 public class AgentShell implements IAgentShell, Daemon {
     private static final Logger s_logger = 
Logger.getLogger(AgentShell.class.getName());
@@ -72,6 +73,9 @@
     private volatile boolean _exit = false;
     private int _pingRetries;
     private final List<Agent> _agents = new ArrayList<Agent>();
+    private String hostToConnect;
+    private String connectedHost;
+    private long hostLBTimerInterval;
 
     public AgentShell() {
     }
@@ -107,18 +111,47 @@ public String getPod() {
     }
 
     @Override
-    public String getHost() {
-        final String[] hosts = _host.split(",");
+    public String getNextHost() {
+        final String[] hosts = getHosts();
         if (_hostCounter >= hosts.length) {
             _hostCounter = 0;
         }
-        final String host = hosts[_hostCounter % hosts.length];
+        hostToConnect = hosts[_hostCounter % hosts.length];
         _hostCounter++;
-        return host;
+        return hostToConnect;
     }
 
-    public void setHost(final String host) {
-        _host = host;
+    @Override
+    public String getConnectedHost() {
+        return connectedHost;
+    }
+
+    @Override
+    public long getHostLBTimerInterval() {
+        return hostLBTimerInterval * 1000L;
+    }
+
+    @Override
+    public void updateConnectedHost() {
+        connectedHost = hostToConnect;
+    }
+
+    @Override
+    public void setHosts(final String host) {
+        if (Strings.isNullOrEmpty(_host) || !_host.equals(host)) {
+            _host = host;
+            resetHostCounter();
+        }
+    }
+
+    @Override
+    public void resetHostCounter() {
+        _hostCounter = 0;
+    }
+
+    @Override
+    public String[] getHosts() {
+        return _host.split(",");
     }
 
     @Override
@@ -291,6 +324,9 @@ protected boolean parseCommand(final String[] args) throws 
ConfigurationExceptio
             _properties.setProperty("guid", _guid);
         }
 
+        String val = getProperty(null, "host.lb.interval");
+        hostLBTimerInterval = (Strings.isNullOrEmpty(val) ? 0L : 
Long.valueOf(val));
+
         return true;
     }
 
diff --git a/agent/src/main/java/com/cloud/agent/IAgentShell.java 
b/agent/src/main/java/com/cloud/agent/IAgentShell.java
index dde67381a4a..b0edd52e7eb 100644
--- a/agent/src/main/java/com/cloud/agent/IAgentShell.java
+++ b/agent/src/main/java/com/cloud/agent/IAgentShell.java
@@ -22,33 +22,45 @@
 import com.cloud.utils.backoff.BackoffAlgorithm;
 
 public interface IAgentShell {
-    public Map<String, Object> getCmdLineProperties();
+    Map<String, Object> getCmdLineProperties();
 
-    public Properties getProperties();
+    Properties getProperties();
 
-    public String getPersistentProperty(String prefix, String name);
+    String getPersistentProperty(String prefix, String name);
 
-    public void setPersistentProperty(String prefix, String name, String 
value);
+    void setPersistentProperty(String prefix, String name, String value);
 
-    public String getHost();
+    String getNextHost();
 
-    public String getPrivateIp();
+    String getPrivateIp();
 
-    public int getPort();
+    int getPort();
 
-    public int getWorkers();
+    int getWorkers();
 
-    public int getProxyPort();
+    int getProxyPort();
 
-    public String getGuid();
+    String getGuid();
 
-    public String getZone();
+    String getZone();
 
-    public String getPod();
+    String getPod();
 
-    public BackoffAlgorithm getBackoffAlgorithm();
+    BackoffAlgorithm getBackoffAlgorithm();
 
-    public int getPingRetries();
+    int getPingRetries();
 
-    public String getVersion();
+    String getVersion();
+
+    void setHosts(String hosts);
+
+    void resetHostCounter();
+
+    String[] getHosts();
+
+    long getHostLBTimerInterval();
+
+    void updateConnectedHost();
+
+    String getConnectedHost();
 }
diff --git a/agent/src/test/java/com/cloud/agent/AgentShellTest.java 
b/agent/src/test/java/com/cloud/agent/AgentShellTest.java
index 8ceba4531d1..868293c8977 100644
--- a/agent/src/test/java/com/cloud/agent/AgentShellTest.java
+++ b/agent/src/test/java/com/cloud/agent/AgentShellTest.java
@@ -35,7 +35,7 @@ public void parseCommand() throws ConfigurationException {
         shell.parseCommand(new String[] {"port=55555", "threads=4", 
"host=localhost", "pod=pod1", "guid=" + anyUuid, "zone=zone1"});
         Assert.assertEquals(55555, shell.getPort());
         Assert.assertEquals(4, shell.getWorkers());
-        Assert.assertEquals("localhost", shell.getHost());
+        Assert.assertEquals("localhost", shell.getNextHost());
         Assert.assertEquals(anyUuid.toString(), shell.getGuid());
         Assert.assertEquals("pod1", shell.getPod());
         Assert.assertEquals("zone1", shell.getZone());
@@ -53,10 +53,10 @@ public void loadProperties() throws ConfigurationException {
     public void testGetHost() {
         AgentShell shell = new AgentShell();
         List<String> hosts = Arrays.asList("10.1.1.1", "20.2.2.2", "30.3.3.3", 
"2001:db8::1");
-        shell.setHost(StringUtils.listToCsvTags(hosts));
+        shell.setHosts(StringUtils.listToCsvTags(hosts));
         for (String host : hosts) {
-            Assert.assertEquals(host, shell.getHost());
+            Assert.assertEquals(host, shell.getNextHost());
         }
-        Assert.assertEquals(shell.getHost(), hosts.get(0));
+        Assert.assertEquals(shell.getNextHost(), hosts.get(0));
     }
 }
diff --git 
a/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java 
b/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java
index 485688a3769..33a08d7933f 100644
--- 
a/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java
+++ 
b/api/src/main/java/org/apache/cloudstack/config/ApiServiceConfiguration.java
@@ -20,7 +20,7 @@
 import org.apache.cloudstack.framework.config.Configurable;
 
 public class ApiServiceConfiguration implements Configurable {
-    public static final ConfigKey<String> ManagementHostIPAdr = new 
ConfigKey<String>("Advanced", String.class, "host", "localhost", "The ip 
address of management server", true);
+    public static final ConfigKey<String> ManagementServerAddresses = new 
ConfigKey<String>("Advanced", String.class, "host", "localhost", "The ip 
address of management server", true);
     public static final ConfigKey<String> ApiServletPath = new 
ConfigKey<String>("Advanced", String.class, "endpointe.url", 
"http://localhost:8080/client/api";,
             "API end point. Can be used by CS components/services deployed 
remotely, for sending CS API requests", true);
     public static final ConfigKey<Long> DefaultUIPageSize = new 
ConfigKey<Long>("Advanced", Long.class, "default.ui.page.size", "20",
@@ -36,7 +36,7 @@ public String getConfigComponentName() {
 
     @Override
     public ConfigKey<?>[] getConfigKeys() {
-        return new ConfigKey<?>[] {ManagementHostIPAdr, ApiServletPath, 
DefaultUIPageSize, ApiSourceCidrChecksEnabled, ApiAllowedSourceCidrList};
+        return new ConfigKey<?>[] {ManagementServerAddresses, ApiServletPath, 
DefaultUIPageSize, ApiSourceCidrChecksEnabled, ApiAllowedSourceCidrList};
     }
 
 }
diff --git a/core/src/main/java/com/cloud/agent/api/ReadyCommand.java 
b/core/src/main/java/com/cloud/agent/api/ReadyCommand.java
index b02b004d6e5..05c282ab0be 100644
--- a/core/src/main/java/com/cloud/agent/api/ReadyCommand.java
+++ b/core/src/main/java/com/cloud/agent/api/ReadyCommand.java
@@ -19,6 +19,8 @@
 
 package com.cloud.agent.api;
 
+import java.util.List;
+
 public class ReadyCommand extends Command {
     private String _details;
 
@@ -28,13 +30,14 @@ public ReadyCommand() {
 
     private Long dcId;
     private Long hostId;
+    private List<String> mgmtHosts;
 
     public ReadyCommand(Long dcId) {
         super();
         this.dcId = dcId;
     }
 
-    public ReadyCommand(Long dcId, Long hostId) {
+    public ReadyCommand(final Long dcId, final Long hostId) {
         this(dcId);
         this.hostId = hostId;
     }
@@ -59,4 +62,13 @@ public boolean executeInSequence() {
     public Long getHostId() {
         return hostId;
     }
+
+    public List<String> getMgmtHosts() {
+        return mgmtHosts;
+    }
+
+    public void setMgmtHosts(List<String> mgmtHosts) {
+        this.mgmtHosts = mgmtHosts;
+    }
+
 }
diff --git 
a/core/src/main/java/com/cloud/agent/api/StartupMgmtHostsCommand.java 
b/core/src/main/java/com/cloud/agent/api/StartupMgmtHostsCommand.java
new file mode 100644
index 00000000000..efe6603bc8d
--- /dev/null
+++ b/core/src/main/java/com/cloud/agent/api/StartupMgmtHostsCommand.java
@@ -0,0 +1,36 @@
+//
+// 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.agent.api;
+
+import com.cloud.host.Host;
+
+public class StartupMgmtHostsCommand extends StartupCommand {
+
+    private String hosts;
+
+    public StartupMgmtHostsCommand(String hosts) {
+        super(Host.Type.Routing);
+        this.hosts = hosts;
+    }
+
+    public String getHosts() {
+        return hosts;
+    }
+}
\ No newline at end of file
diff --git 
a/core/src/main/java/org/apache/cloudstack/agent/mslb/SetupManagementServersListAnswer.java
 
b/core/src/main/java/org/apache/cloudstack/agent/mslb/SetupManagementServersListAnswer.java
new file mode 100644
index 00000000000..45a1e27b7f1
--- /dev/null
+++ 
b/core/src/main/java/org/apache/cloudstack/agent/mslb/SetupManagementServersListAnswer.java
@@ -0,0 +1,30 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.agent.mslb;
+
+import com.cloud.agent.api.Answer;
+
+public class SetupManagementServersListAnswer extends Answer {
+
+    public SetupManagementServersListAnswer(final boolean result) {
+        super(null);
+        this.result = result;
+    }
+}
diff --git 
a/core/src/main/java/org/apache/cloudstack/agent/mslb/SetupManagementServersListCommand.java
 
b/core/src/main/java/org/apache/cloudstack/agent/mslb/SetupManagementServersListCommand.java
new file mode 100644
index 00000000000..847b6a5abdf
--- /dev/null
+++ 
b/core/src/main/java/org/apache/cloudstack/agent/mslb/SetupManagementServersListCommand.java
@@ -0,0 +1,42 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package org.apache.cloudstack.agent.mslb;
+
+import com.cloud.agent.api.Command;
+import java.util.List;
+
+public class SetupManagementServersListCommand extends Command {
+
+    private List<String> msList;
+
+    public SetupManagementServersListCommand(final List<String> msList) {
+        super();
+        this.msList = msList;
+    }
+
+    public List<String> getMsList() {
+        return msList;
+    }
+
+    @Override
+    public boolean executeInSequence() {
+        return false;
+    }
+}
diff --git a/engine/orchestration/pom.xml b/engine/orchestration/pom.xml
index 80adccbe30d..353366c73b4 100755
--- a/engine/orchestration/pom.xml
+++ b/engine/orchestration/pom.xml
@@ -58,6 +58,11 @@
       <artifactId>cloud-utils</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-framework-agent-mslb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.apache.cloudstack</groupId>
       <artifactId>cloud-server</artifactId>
diff --git 
a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java
 
b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java
index b7357756c4c..d22c200d838 100644
--- 
a/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java
+++ 
b/engine/orchestration/src/main/java/com/cloud/agent/manager/AgentManagerImpl.java
@@ -24,6 +24,7 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Arrays;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.LinkedBlockingQueue;
@@ -37,6 +38,8 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import com.cloud.utils.StringUtils;
+import org.apache.cloudstack.agent.mslb.AgentMSLB;
 import org.apache.cloudstack.ca.CAManager;
 import com.cloud.configuration.ManagementServiceConfiguration;
 import org.apache.cloudstack.framework.config.ConfigKey;
@@ -67,6 +70,7 @@
 import com.cloud.agent.api.ShutdownCommand;
 import com.cloud.agent.api.StartupAnswer;
 import com.cloud.agent.api.StartupCommand;
+import com.cloud.agent.api.StartupMgmtHostsCommand;
 import com.cloud.agent.api.StartupProxyCommand;
 import com.cloud.agent.api.StartupRoutingCommand;
 import com.cloud.agent.api.StartupSecondaryStorageCommand;
@@ -184,6 +188,9 @@
     @Inject
     ManagementServiceConfiguration mgmtServiceConf;
 
+    @Inject
+    AgentMSLB agentMSLB;
+
     protected final ConfigKey<Integer> Workers = new 
ConfigKey<Integer>("Advanced", Integer.class, "workers", "5",
                     "Number of worker threads handling remote agent 
connections.", false);
     protected final ConfigKey<Integer> Port = new 
ConfigKey<Integer>("Advanced", Integer.class, "port", "8250", "Port to listen 
on for remote agent connections.", false);
@@ -1081,9 +1088,20 @@ private AgentAttache handleConnectedAgent(final Link 
link, final StartupCommand[
         AgentAttache attache = null;
         ReadyCommand ready = null;
         try {
+            String mgmtHostsFromAgent = 
getMgmtHostsFromAgent(request.getCommands());
+            List<String> mgmtHostsList = null;
+            if (StringUtils.isNotBlank(mgmtHostsFromAgent)) {
+                mgmtHostsList = Arrays.asList(mgmtHostsFromAgent.split(","));
+            }
             final HostVO host = 
_resourceMgr.createHostVOForConnectedAgent(startup);
             if (host != null) {
                 ready = new ReadyCommand(host.getDataCenterId(), host.getId());
+
+                if (mgmtHostsList != null && 
!agentMSLB.isManagementServerListUpToDate(host.getId(), host.getDataCenterId(), 
mgmtHostsList)) {
+                    //Send the latest mgmt hosts list if it is not up to date 
on agent side
+                    
ready.setMgmtHosts(agentMSLB.getManagementServerList(host.getId(), 
host.getDataCenterId()));
+                }
+
                 attache = createAttacheForConnect(host, link);
                 attache = notifyMonitorsOfConnection(attache, startup, false);
             }
@@ -1199,6 +1217,18 @@ protected void connectAgent(final Link link, final 
Command[] cmds, final Request
         _connectExecutor.execute(new HandleAgentConnectTask(link, cmds, 
request));
     }
 
+    /**
+     * Get mgmt hosts from agent (if any StartupMgmtHostsCommand on cmds), 
null if no host received
+     */
+    private String getMgmtHostsFromAgent(Command[] cmds) {
+        for (Command cmd : cmds) {
+            if (cmd instanceof StartupMgmtHostsCommand) {
+                return ((StartupMgmtHostsCommand) cmd).getHosts();
+            }
+        }
+        return null;
+    }
+
     public class AgentHandler extends Task {
         public AgentHandler(final Task.Type type, final Link link, final 
byte[] data) {
             super(type, link, data);
diff --git a/framework/agent-mslb/pom.xml b/framework/agent-mslb/pom.xml
new file mode 100644
index 00000000000..800b723c190
--- /dev/null
+++ b/framework/agent-mslb/pom.xml
@@ -0,0 +1,30 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements. See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership. The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License. You may obtain a copy of the License at
+  http://www.apache.org/licenses/LICENSE-2.0
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied. See the License for the
+  specific language governing permissions and limitations
+  under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+    <modelVersion>4.0.0</modelVersion>
+    <name>Apache CloudStack Agent Management Servers Load Balancer</name>
+    <artifactId>cloud-framework-agent-mslb</artifactId>
+    <parent>
+        <artifactId>cloudstack-framework</artifactId>
+        <groupId>org.apache.cloudstack</groupId>
+        <version>4.12.0.0-SNAPSHOT</version>
+        <relativePath>../pom.xml</relativePath>
+    </parent>
+
+</project>
diff --git 
a/framework/agent-mslb/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLB.java
 
b/framework/agent-mslb/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLB.java
new file mode 100644
index 00000000000..07f35ee435a
--- /dev/null
+++ 
b/framework/agent-mslb/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLB.java
@@ -0,0 +1,39 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.mslb;
+
+import java.util.List;
+
+public interface AgentMSLB {
+
+    /**
+     * Return list of management server addresses after applying configured 
MSLB algorithm
+     * @param hostId host id (if present)
+     * @param dcId zone id
+     * @return management servers string list
+     */
+    List<String> getManagementServerList(Long hostId, Long dcId);
+
+    /**
+     * Return true if received management server list is up to date for hostId 
on dcId
+     * @param hostId host id
+     * @param dcId zone id
+     * @param receivedMgmtHosts received management server list
+     * @return true if mgmtHosts is up to date, false if not
+     */
+    boolean isManagementServerListUpToDate(Long hostId, Long dcId, 
List<String> receivedMgmtHosts);
+}
\ No newline at end of file
diff --git 
a/framework/agent-mslb/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLBAlgorithm.java
 
b/framework/agent-mslb/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLBAlgorithm.java
new file mode 100644
index 00000000000..f32dba4b24b
--- /dev/null
+++ 
b/framework/agent-mslb/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLBAlgorithm.java
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package org.apache.cloudstack.agent.mslb;
+
+import java.util.List;
+
+public interface AgentMSLBAlgorithm {
+    /**
+     * Returns the management server list to send to host after applying the 
algorithm
+     * @param msList management server list
+     * @param orderedHostList ordered host list
+     * @param hostId host id
+     * @return returns the list of management server addresses which will be 
sent to host id
+     */
+    List<String> getMSList(final List<String> msList, final List<Long> 
orderedHostList, final Long hostId);
+
+    /**
+     * Gets the unique name of the algorithm
+     * @return returns the name of the Agent MSLB algorithm
+     */
+    String getName();
+
+    /**
+     * Compares and return if received mgmt server list is equal to the actual 
mgmt server lists
+     * @param msList current mgmt server list
+     * @param receivedMsList received mgmt server list
+     * @return true if the lists are equal, false if not
+     */
+    boolean isMSListEqual(final List<String> msList, final List<String> 
receivedMsList);
+}
diff --git a/framework/pom.xml b/framework/pom.xml
index b7322fe3540..3104c40fc20 100644
--- a/framework/pom.xml
+++ b/framework/pom.xml
@@ -43,6 +43,7 @@
     </plugins>
   </build>
   <modules>
+    <module>agent-mslb</module>
     <module>ipc</module>
     <module>ca</module>
     <module>rest</module>
diff --git 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
index 9b7fb2ea013..445d679348d 100644
--- 
a/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
+++ 
b/plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/LibvirtComputingResource.java
@@ -85,6 +85,7 @@
 import com.cloud.agent.api.PingRoutingWithNwGroupsCommand;
 import com.cloud.agent.api.SetupGuestNetworkCommand;
 import com.cloud.agent.api.StartupCommand;
+import com.cloud.agent.api.StartupMgmtHostsCommand;
 import com.cloud.agent.api.StartupRoutingCommand;
 import com.cloud.agent.api.StartupStorageCommand;
 import com.cloud.agent.api.VmDiskStatsEntry;
@@ -2593,13 +2594,49 @@ public Type getType() {
             s_logger.debug("Unable to initialize local storage pool: " + e);
         }
 
-        if (sscmd != null) {
-            return new StartupCommand[] {cmd, sscmd};
+        StartupMgmtHostsCommand mgmtHostsCommand = null;
+        try {
+            mgmtHostsCommand = getMgmtHostCommand();
+        } catch (ConfigurationException e) {
+            s_logger.error("Error with agent.properties file: " + e);
+        } catch (IOException e) {
+            s_logger.error("Error retrieving properties from agent.properties 
due to: " + e);
+        }
+
+        if (sscmd != null && mgmtHostsCommand != null) {
+            return new StartupCommand[]{cmd, sscmd, mgmtHostsCommand};
+        } else if (sscmd != null && mgmtHostsCommand == null) {
+            return new StartupCommand[]{cmd, sscmd};
+        } else if (sscmd == null && mgmtHostsCommand != null) {
+            return new StartupCommand[]{cmd, mgmtHostsCommand};
         } else {
             return new StartupCommand[] {cmd};
         }
     }
 
+    /**
+     * Create a StartupMgmtHostsCommand using 'host' property on 
agent.properties file to be sent to the connected mgmt host
+     * @return StartUpMgmtHostsCommand
+     * @throws ConfigurationException if agent.properties file cannot be found 
or no hosts specified on agent.properties file
+     * @throws IOException if properties cannot be retrieved from 
agent.properties file
+     */
+    private StartupMgmtHostsCommand getMgmtHostCommand() throws 
ConfigurationException, IOException {
+        final File file = 
PropertiesUtil.findConfigFile("/etc/cloudstack/agent/agent.properties");
+        if (file == null) {
+            throw new ConfigurationException("Unable to find 
agent.properties.");
+        }
+
+        s_logger.debug("agent.properties found at " + file.getAbsolutePath());
+
+        final Properties properties = PropertiesUtil.loadFromFile(file);
+
+        final String mgmtHosts = (String)properties.get("host");
+        if (mgmtHosts == null) {
+            throw new ConfigurationException("No mgmt hosts specified on 
agent.properties file");
+        }
+        return new StartupMgmtHostsCommand(mgmtHosts);
+    }
+
     public String diskUuidToSerial(String uuid) {
         String uuidWithoutHyphen = uuid.replace("-","");
         return uuidWithoutHyphen.substring(0, 
Math.min(uuidWithoutHyphen.length(), 20));
diff --git 
a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java
 
b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java
index d9175548649..78e04b5dc53 100644
--- 
a/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java
+++ 
b/plugins/network-elements/elastic-loadbalancer/src/main/java/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java
@@ -447,7 +447,7 @@ public boolean 
finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl
                     if (s_logger.isInfoEnabled()) {
                         s_logger.info("Check if we need to add management 
server explicit route to ELB vm. pod cidr: " + dest.getPod().getCidrAddress() + 
"/"
                                 + dest.getPod().getCidrSize() + ", pod 
gateway: " + dest.getPod().getGateway() + ", management host: "
-                                + 
ApiServiceConfiguration.ManagementHostIPAdr.value());
+                                + 
ApiServiceConfiguration.ManagementServerAddresses.value());
                     }
 
                     if (s_logger.isDebugEnabled()) {
diff --git a/server/pom.xml b/server/pom.xml
index 8641703f562..d0f2b813b4d 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -136,6 +136,11 @@
       <artifactId>cloud-engine-components-api</artifactId>
       <version>${project.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.cloudstack</groupId>
+      <artifactId>cloud-framework-agent-mslb</artifactId>
+      <version>${project.version}</version>
+    </dependency>
     <dependency>
       <groupId>org.opensaml</groupId>
       <artifactId>opensaml</artifactId>
diff --git 
a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java 
b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
index 1632da95f95..80642f51375 100755
--- a/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
+++ b/server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java
@@ -76,6 +76,8 @@
 import org.apache.cloudstack.framework.config.Configurable;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
 import org.apache.cloudstack.framework.config.impl.ConfigurationVO;
+import org.apache.cloudstack.framework.messagebus.MessageBus;
+import org.apache.cloudstack.framework.messagebus.PublishScope;
 import org.apache.cloudstack.region.PortableIp;
 import org.apache.cloudstack.region.PortableIpDao;
 import org.apache.cloudstack.region.PortableIpRange;
@@ -352,6 +354,8 @@
     ImageStoreDao _imageStoreDao;
     @Inject
     ImageStoreDetailsDao _imageStoreDetailsDao;
+    @Inject
+    MessageBus messageBus;
 
 
     // FIXME - why don't we have interface for DataCenterLinkLocalIpAddressDao?
@@ -660,6 +664,7 @@ public String updateConfiguration(final long userId, final 
String name, final St
         }
 
         txn.commit();
+        messageBus.publish(_name, EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, 
PublishScope.GLOBAL, name);
         return _configDao.getValue(name);
     }
 
diff --git 
a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java 
b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
index 28fff3c7219..c64bca10013 100644
--- a/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
+++ b/server/src/main/java/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java
@@ -29,7 +29,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
-import org.apache.cloudstack.config.ApiServiceConfiguration;
+import org.apache.cloudstack.agent.mslb.AgentMSLB;
 import org.apache.cloudstack.context.CallContext;
 import 
org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.framework.config.dao.ConfigurationDao;
@@ -211,6 +211,8 @@
     private KeysManager _keysMgr;
     @Inject
     private VirtualMachineManager _itMgr;
+    @Inject
+    private AgentMSLB agentMSLB;
 
     private ConsoleProxyListener _listener;
 
@@ -1355,7 +1357,7 @@ public boolean 
finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl
 
         StringBuilder buf = profile.getBootArgsBuilder();
         buf.append(" template=domP type=consoleproxy");
-        buf.append(" 
host=").append(StringUtils.shuffleCSVList(ApiServiceConfiguration.ManagementHostIPAdr.value()));
+        buf.append(" 
host=").append(StringUtils.toCSVList(agentMSLB.getManagementServerList(dest.getHost().getId(),
 dest.getDataCenter().getId())));
         buf.append(" port=").append(_mgmtPort);
         buf.append(" name=").append(profile.getVirtualMachine().getHostName());
         if (_sslEnabled) {
diff --git 
a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
 
b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
index 63a44b83518..2f99aa7dc73 100644
--- 
a/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
+++ 
b/server/src/main/java/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java
@@ -27,9 +27,9 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
+import org.apache.cloudstack.agent.mslb.AgentMSLB;
 import org.apache.cloudstack.ca.CAManager;
 import org.apache.cloudstack.ca.SetupCertificateCommand;
-import org.apache.cloudstack.config.ApiServiceConfiguration;
 import org.apache.cloudstack.framework.ca.Certificate;
 import org.apache.cloudstack.utils.security.KeyStoreUtils;
 import org.apache.log4j.Logger;
@@ -75,6 +75,8 @@
     private AgentManager agentMgr;
     @Inject
     private CAManager caManager;
+    @Inject
+    private AgentMSLB agentMSLB;
 
     @Override
     public abstract Hypervisor.HypervisorType getHypervisorType();
@@ -291,7 +293,7 @@ private void setupAgentSecurity(final Connection 
sshConnection, final String age
 
             setupAgentSecurity(sshConnection, agentIp, hostname);
 
-            String parameters = " -m " + 
StringUtils.shuffleCSVList(ApiServiceConfiguration.ManagementHostIPAdr.value()) 
+ " -z " + dcId + " -p " + podId + " -c " + clusterId + " -g " + guid + " -a";
+            String parameters = " -m " + 
StringUtils.toCSVList(agentMSLB.getManagementServerList(null, dcId)) + " -z " + 
dcId + " -p " + podId + " -c " + clusterId + " -g " + guid + " -a";
 
             parameters += " --pubNic=" + kvmPublicNic;
             parameters += " --prvNic=" + kvmPrivateNic;
diff --git 
a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java
 
b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java
index f3035d05e61..1985deaefa8 100644
--- 
a/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java
+++ 
b/server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java
@@ -1373,7 +1373,7 @@ public boolean finalizeVirtualMachineProfile(final 
VirtualMachineProfile profile
                 if (dest.getHost().getHypervisorType() == 
HypervisorType.VMware || dest.getHost().getHypervisorType() == 
HypervisorType.Hyperv) {
                     s_logger.info("Check if we need to add management server 
explicit route to DomR. pod cidr: " + dest.getPod().getCidrAddress() + "/"
                             + dest.getPod().getCidrSize() + ", pod gateway: " 
+ dest.getPod().getGateway() + ", management host: "
-                            + 
ApiServiceConfiguration.ManagementHostIPAdr.value());
+                            + 
ApiServiceConfiguration.ManagementServerAddresses.value());
 
                     if (s_logger.isInfoEnabled()) {
                         s_logger.info("Add management server explicit route to 
DomR.");
@@ -1484,7 +1484,7 @@ public boolean finalizeVirtualMachineProfile(final 
VirtualMachineProfile profile
             } else {
                 buf.append(String.format(" 
baremetalnotificationsecuritykey=%s", user.getSecretKey()));
                 buf.append(String.format(" baremetalnotificationapikey=%s", 
user.getApiKey()));
-                buf.append(" 
host=").append(ApiServiceConfiguration.ManagementHostIPAdr.value());
+                buf.append(" 
host=").append(ApiServiceConfiguration.ManagementServerAddresses.value());
                 buf.append(" 
port=").append(_configDao.getValue(Config.BaremetalProvisionDoneNotificationPort.key()));
             }
         }
diff --git a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java 
b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
index 2966d41d8bf..0f8bd49d231 100755
--- a/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
+++ b/server/src/main/java/com/cloud/resource/ResourceManagerImpl.java
@@ -253,6 +253,7 @@ public void setDiscoverers(final List<? extends Discoverer> 
discoverers) {
     @Inject
     private ClusterVSMMapDao _clusterVSMMapDao;
 
+
     private final long _nodeId = ManagementServerNode.getManagementServerId();
 
     private final HashMap<String, ResourceStateAdapter> _resourceStateAdapters 
= new HashMap<String, ResourceStateAdapter>();
diff --git a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java 
b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
index e348051cb0d..197ebed3e69 100644
--- a/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
+++ b/server/src/main/java/com/cloud/server/ConfigurationServerImpl.java
@@ -246,14 +246,14 @@ public void persistDefaultValues() throws 
InternalErrorException {
             if (hostIpAdr != null) {
                 Boolean devel = 
Boolean.valueOf(_configDao.getValue("developer"));
                 if (devel) {
-                    String value = 
_configDao.getValue(ApiServiceConfiguration.ManagementHostIPAdr.key());
+                    String value = 
_configDao.getValue(ApiServiceConfiguration.ManagementServerAddresses.key());
                     if (value != null && !value.equals("localhost")) {
                         needUpdateHostIp = false;
                     }
                 }
 
                 if (needUpdateHostIp) {
-                    
_configDepot.createOrUpdateConfigObject(ApiServiceConfiguration.class.getSimpleName(),
 ApiServiceConfiguration.ManagementHostIPAdr, hostIpAdr);
+                    
_configDepot.createOrUpdateConfigObject(ApiServiceConfiguration.class.getSimpleName(),
 ApiServiceConfiguration.ManagementServerAddresses, hostIpAdr);
                     s_logger.debug("ConfigurationServer saved \"" + hostIpAdr 
+ "\" as host.");
                 }
             }
diff --git 
a/server/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLBServiceImpl.java
 
b/server/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLBServiceImpl.java
new file mode 100644
index 00000000000..86b1b3fd1e9
--- /dev/null
+++ 
b/server/src/main/java/org/apache/cloudstack/agent/mslb/AgentMSLBServiceImpl.java
@@ -0,0 +1,236 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.mslb;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.agent.api.Answer;
+import com.cloud.event.EventTypes;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.resource.ResourceListener;
+import com.cloud.resource.ResourceManager;
+import com.cloud.resource.ResourceState;
+import com.cloud.resource.ServerResource;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import java.util.stream.Collectors;
+import javax.inject.Inject;
+import javax.naming.ConfigurationException;
+
+import org.apache.cloudstack.agent.mslb.algorithm.AgentMSLBRoundRobinAlgorithm;
+import org.apache.cloudstack.agent.mslb.algorithm.AgentMSLBShuffleAlgorithm;
+import org.apache.cloudstack.agent.mslb.algorithm.AgentMSLBStaticAlgorithm;
+import org.apache.cloudstack.config.ApiServiceConfiguration;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.config.Configurable;
+import org.apache.cloudstack.framework.messagebus.MessageBus;
+import org.apache.log4j.Logger;
+
+import com.cloud.utils.component.ComponentLifecycleBase;
+import com.cloud.utils.exception.CloudRuntimeException;
+import com.google.common.base.Strings;
+
+public class AgentMSLBServiceImpl extends ComponentLifecycleBase implements 
AgentMSLB, Configurable, ResourceListener {
+    public static final Logger LOG = 
Logger.getLogger(AgentMSLBServiceImpl.class);
+
+    public static final ConfigKey<String> ConnectedAgentLBAlgorithm = new 
ConfigKey<>("Advanced", String.class,
+            "connected.agent.mslb.algorithm", "static",
+            "The algorithm to applied on the provided 'host' management server 
list that is sent to indirect agents. Allowed values are: static, roundrobin 
and shuffle.",
+            true, ConfigKey.Scope.Global);
+
+    private static Map<String, AgentMSLBAlgorithm> algorithmMap = new 
HashMap<>();
+
+    @Inject
+    ResourceManager resourceManager;
+    @Inject
+    MessageBus messageBus;
+    @Inject
+    AgentManager agentManager;
+
+    /**
+     * Return a map of (zoneid, list of host ids)
+     * @return map
+     */
+    protected Map<Long, List<Long>> getHostsPerZone() {
+        List<HostVO> allHosts = 
resourceManager.listAllHostsInAllZonesByType(Host.Type.Routing);
+        if (allHosts == null) {
+            return null;
+        }
+        return allHosts.stream()
+                .collect(
+                        Collectors.groupingBy(
+                               HostVO::getDataCenterId,
+                               Collectors.mapping(HostVO::getId, 
Collectors.toList()
+                        )));
+    }
+
+    /**
+     * Propagate management servers lists to agents
+     */
+    private void propagateListToAgents() {
+        LOG.debug("Propagating management lists to agents");
+        Map<Long, List<Long>> hostsPerZone = getHostsPerZone();
+        for (Long zoneId : hostsPerZone.keySet()) {
+            List<Long> hostIds = hostsPerZone.get(zoneId);
+            for (Long hostId : hostIds) {
+                List<String> msList = getManagementServerList(hostId, zoneId);
+                SetupManagementServersListCommand cmd = new 
SetupManagementServersListCommand(msList);
+                Answer answer = agentManager.easySend(hostId, cmd);
+                if (answer == null || !answer.getResult()) {
+                    LOG.warn("Error sending management servers list to agent" 
+ hostId);
+                }
+            }
+        }
+    }
+
+    private void initMessageBusListener() {
+        messageBus.subscribe(EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, 
(senderAddress, subject, args) -> {
+            String globalSettingUpdated = (String) args;
+            if 
(globalSettingUpdated.equals(ApiServiceConfiguration.ManagementServerAddresses.key())
 ||
+                    
globalSettingUpdated.equals(ConnectedAgentLBAlgorithm.key())) {
+                propagateListToAgents();
+            }
+        });
+    }
+
+    @Override
+    public List<String> getManagementServerList(Long hostId, Long dcId) {
+        final String msServerAddresses = 
ApiServiceConfiguration.ManagementServerAddresses.value();
+        if (Strings.isNullOrEmpty(msServerAddresses)) {
+            throw new CloudRuntimeException(String.format("No management 
server addresses are defined in '%s' setting",
+                    ApiServiceConfiguration.ManagementServerAddresses.key()));
+        }
+
+        List<Long> orderedHostIds = getOrderedRunningHostIds(dcId);
+        List<String> msList = Arrays.asList(msServerAddresses.replace(" ", 
"").split(","));
+        AgentMSLBAlgorithm algorithm = getAgentMSLBAlgorithm();
+        return algorithm.getMSList(msList, orderedHostIds, hostId);
+    }
+
+    @Override
+    public boolean isManagementServerListUpToDate(Long hostId, Long dcId, 
List<String> receivedMgmtHosts) {
+        List<String> managementServerList = getManagementServerList(hostId, 
dcId);
+        AgentMSLBAlgorithm algorithm = getAgentMSLBAlgorithm();
+        return algorithm.isMSListEqual(managementServerList, 
receivedMgmtHosts);
+    }
+
+    protected List<Long> getOrderedRunningHostIds(Long dcId) {
+        List<HostVO> hosts = 
resourceManager.listAllHostsInOneZoneByType(Host.Type.Routing, dcId);
+        if (hosts != null) {
+            return hosts.stream()
+                    .filter(x -> 
!x.getHypervisorType().equals(Hypervisor.HypervisorType.VMware) &&
+                                    
!x.getHypervisorType().equals(Hypervisor.HypervisorType.XenServer) &&
+                                    
!x.getHypervisorType().equals(Hypervisor.HypervisorType.Hyperv) &&
+                                    x.getRemoved() == null && 
x.getResourceState().equals(ResourceState.Enabled))
+                    .map(x -> x.getId())
+                    .sorted((x,y) -> Long.compare(x, y))
+                    .collect(Collectors.toList());
+        }
+        return null;
+    }
+
+    private AgentMSLBAlgorithm getAgentMSLBAlgorithm() {
+        final String algorithm = ConnectedAgentLBAlgorithm.value();
+        if (algorithmMap.containsKey(algorithm)) {
+            return algorithmMap.get(algorithm);
+        }
+        throw new CloudRuntimeException(String.format("Algorithm configured 
for '%s' not found, valid values are: %s",
+                ConnectedAgentLBAlgorithm.key(), algorithmMap.keySet()));
+    }
+
+    private void initAlgorithmMap() {
+        final List<AgentMSLBAlgorithm> algorithms = new ArrayList<>();
+        algorithms.add(new AgentMSLBStaticAlgorithm());
+        algorithms.add(new AgentMSLBRoundRobinAlgorithm());
+        algorithms.add(new AgentMSLBShuffleAlgorithm());
+        algorithmMap.clear();
+        for (AgentMSLBAlgorithm algorithm : algorithms) {
+            algorithmMap.put(algorithm.getName(), algorithm);
+        }
+    }
+
+    @Override
+    public boolean configure(final String name, final Map<String, Object> 
params) throws ConfigurationException {
+        super.configure(name, params);
+        initAlgorithmMap();
+        initMessageBusListener();
+        return true;
+    }
+
+    @Override
+    public boolean start() {
+        
resourceManager.registerResourceEvent(ResourceListener.EVENT_DISCOVER_AFTER, 
this);
+        return true;
+    }
+
+    @Override
+    public boolean stop() {
+        resourceManager.unregisterResourceEvent(this);
+        return true;
+    }
+
+    @Override
+    public String getConfigComponentName() {
+        return AgentMSLBServiceImpl.class.getSimpleName();
+    }
+
+    @Override
+    public ConfigKey<?>[] getConfigKeys() {
+        return new ConfigKey<?>[] {
+                ConnectedAgentLBAlgorithm
+        };
+    }
+
+    @Override
+    public void processDiscoverEventBefore(Long dcid, Long podId, Long 
clusterId, URI uri, String username, String password, List<String> hostTags) {
+    }
+
+    @Override
+    public void processDiscoverEventAfter(Map<? extends ServerResource, 
Map<String, String>> resources) {
+        propagateListToAgents();
+    }
+
+    @Override
+    public void processDeleteHostEventBefore(Host host) {
+    }
+
+    @Override
+    public void processDeletHostEventAfter(Host host) {
+    }
+
+    @Override
+    public void processCancelMaintenaceEventBefore(Long hostId) {
+    }
+
+    @Override
+    public void processCancelMaintenaceEventAfter(Long hostId) {
+    }
+
+    @Override
+    public void processPrepareMaintenaceEventBefore(Long hostId) {
+    }
+
+    @Override
+    public void processPrepareMaintenaceEventAfter(Long hostId) {
+    }
+}
\ No newline at end of file
diff --git 
a/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBRoundRobinAlgorithm.java
 
b/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBRoundRobinAlgorithm.java
new file mode 100644
index 00000000000..53eb557d2e8
--- /dev/null
+++ 
b/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBRoundRobinAlgorithm.java
@@ -0,0 +1,94 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.mslb.algorithm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cloudstack.agent.mslb.AgentMSLBAlgorithm;
+
+public class AgentMSLBRoundRobinAlgorithm implements AgentMSLBAlgorithm {
+
+    private static final long LAST_ID_FOR_NOT_EXISTING_HOSTS = 0l;
+
+    /**
+     * Get RR index for a hostId depending on its order in the hosts list
+     * @param msList
+     * @param orderedHostList
+     * @param hostId
+     * @return
+     */
+    private int findRoundRobinIndexForHost(final List<String> msList, final 
List<Long> orderedHostList, final Long hostId) {
+        return orderedHostList.indexOf(hostId) % msList.size();
+    }
+
+    /**
+     * When discovering new hosts there is no host id on DB, in order to apply 
RR algorithm an entry is added at the end of the hosts list
+     * @param hostId host id
+     * @param orderedHostList hosts list
+     * @return added id on the list if hostId is null, hostId if not
+     */
+    private Long getHostForRoundRobinAlgorithm(Long hostId, List<Long> 
orderedHostList) {
+        if (hostId == null) {
+            orderedHostList.add(LAST_ID_FOR_NOT_EXISTING_HOSTS);
+            return LAST_ID_FOR_NOT_EXISTING_HOSTS;
+        }
+        return hostId;
+    }
+
+    /**
+     * If an entry was added at the end of the list it is removed (when hostId 
is null)
+     * @param hostId host id
+     * @param orderedHostList hosts list
+     */
+    private void revertChangesAfterApplyingRoundRobinAlgorithm(Long hostId, 
List<Long> orderedHostList) {
+        if (hostId == null) {
+            orderedHostList.remove(orderedHostList.size()-1);
+        }
+    }
+
+    @Override
+    public List<String> getMSList(final List<String> msList, final List<Long> 
orderedHostList, final Long hostId) {
+        if (msList.size() < 2) {
+            return msList;
+        }
+        Long host = getHostForRoundRobinAlgorithm(hostId, orderedHostList);
+
+        final int currentRRIndex = findRoundRobinIndexForHost(msList, 
orderedHostList, host);
+        final List<String> roundRobin = new 
ArrayList<>(msList.subList(currentRRIndex, msList.size()));
+        roundRobin.addAll(msList.subList(0, currentRRIndex));
+
+        revertChangesAfterApplyingRoundRobinAlgorithm(hostId, orderedHostList);
+        return roundRobin;
+    }
+
+    @Override
+    public String getName() {
+        return "roundrobin";
+    }
+
+    @Override
+    public boolean isMSListEqual(List<String> msList, List<String> 
receivedMsList) {
+        if (msList.size() != receivedMsList.size()) return false;
+        for (int i = 0; i < msList.size(); i++) {
+            if (!msList.get(i).equals(receivedMsList.get(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git 
a/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBShuffleAlgorithm.java
 
b/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBShuffleAlgorithm.java
new file mode 100644
index 00000000000..bf639d360c2
--- /dev/null
+++ 
b/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBShuffleAlgorithm.java
@@ -0,0 +1,44 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.mslb.algorithm;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import org.apache.cloudstack.agent.mslb.AgentMSLBAlgorithm;
+import org.apache.commons.collections.SetUtils;
+
+public class AgentMSLBShuffleAlgorithm implements AgentMSLBAlgorithm {
+
+    @Override
+    public List<String> getMSList(final List<String> msList, final List<Long> 
orderedHostList, final Long hostId) {
+        final List<String> randomList = new ArrayList<>(msList);
+        Collections.shuffle(randomList, new 
Random(System.currentTimeMillis()));
+        return randomList;
+    }
+
+    @Override
+    public String getName() {
+        return "shuffle";
+    }
+
+    @Override
+    public boolean isMSListEqual(List<String> msList, List<String> 
receivedMsList) {
+        return SetUtils.isEqualSet(msList, receivedMsList);
+    }
+}
\ No newline at end of file
diff --git 
a/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBStaticAlgorithm.java
 
b/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBStaticAlgorithm.java
new file mode 100644
index 00000000000..4beeca1aefb
--- /dev/null
+++ 
b/server/src/main/java/org/apache/cloudstack/agent/mslb/algorithm/AgentMSLBStaticAlgorithm.java
@@ -0,0 +1,46 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.mslb.algorithm;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.cloudstack.agent.mslb.AgentMSLBAlgorithm;
+
+public class AgentMSLBStaticAlgorithm implements AgentMSLBAlgorithm {
+
+    @Override
+    public List<String> getMSList(final List<String> msList, final List<Long> 
orderedHostList, final Long hostId) {
+        return new ArrayList<>(msList);
+    }
+
+    @Override
+    public String getName() {
+        return "static";
+    }
+
+    @Override
+    public boolean isMSListEqual(List<String> msList, List<String> 
receivedMsList) {
+        if (msList.size() != receivedMsList.size()) return false;
+        for (int i = 0; i < msList.size(); i++) {
+            if (!msList.get(i).equals(receivedMsList.get(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+}
\ No newline at end of file
diff --git 
a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
 
b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
index 7d19b1e2f46..8834bd03fde 100644
--- 
a/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
+++ 
b/server/src/main/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml
@@ -296,4 +296,7 @@
     <bean id="annotationService" 
class="org.apache.cloudstack.annotation.AnnotationManagerImpl" />
 
     <bean id="directDownloadManager" 
class="org.apache.cloudstack.direct.download.DirectDownloadManagerImpl" />
+
+    <bean id="agentMSLB" 
class="org.apache.cloudstack.agent.mslb.AgentMSLBServiceImpl" />
+
 </beans>
diff --git a/server/src/test/resources/createNetworkOffering.xml 
b/server/src/test/resources/createNetworkOffering.xml
index 126e265682b..1a3bad166b4 100644
--- a/server/src/test/resources/createNetworkOffering.xml
+++ b/server/src/test/resources/createNetworkOffering.xml
@@ -54,4 +54,5 @@
     <bean id="userIpAddressDetailsDao" 
class="org.apache.cloudstack.resourcedetail.dao.UserIpAddressDetailsDaoImpl" />
     <bean id="loadBalancerVMMapDaoImpl" 
class="com.cloud.network.dao.LoadBalancerVMMapDaoImpl" />
     <bean id="imageStoreDaoImpl" 
class="org.apache.cloudstack.storage.datastore.db.ImageStoreDaoImpl" />
+    <bean id="messageBus" 
class="org.apache.cloudstack.framework.messagebus.MessageBusBase" />
 </beans>
diff --git 
a/server/test/org/apache/cloudstack/agent/mslb/AgentMSLBServiceImplTest.java 
b/server/test/org/apache/cloudstack/agent/mslb/AgentMSLBServiceImplTest.java
new file mode 100644
index 00000000000..d064a7efd6a
--- /dev/null
+++ b/server/test/org/apache/cloudstack/agent/mslb/AgentMSLBServiceImplTest.java
@@ -0,0 +1,243 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+package org.apache.cloudstack.agent.mslb;
+
+import com.cloud.agent.AgentManager;
+import com.cloud.host.Host;
+import com.cloud.host.HostVO;
+import com.cloud.hypervisor.Hypervisor;
+import com.cloud.resource.ResourceManager;
+import com.cloud.resource.ResourceState;
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.cloudstack.config.ApiServiceConfiguration;
+import org.apache.cloudstack.framework.config.ConfigKey;
+import org.apache.cloudstack.framework.messagebus.MessageBus;
+import org.mockito.Mock;
+import org.mockito.Spy;
+import org.mockito.InjectMocks;
+import org.mockito.MockitoAnnotations;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.mockito.Mockito.when;
+
+import com.cloud.utils.exception.CloudRuntimeException;
+
+public class AgentMSLBServiceImplTest {
+
+    @Mock
+    ResourceManager resourceManager;
+    @Mock
+    MessageBus messageBus;
+    @Mock
+    AgentManager agentManager;
+
+    @Mock
+    HostVO host1;
+    @Mock
+    HostVO host2;
+    @Mock
+    HostVO host3;
+    @Mock
+    HostVO host4;
+
+    @Spy
+    @InjectMocks
+    private AgentMSLBServiceImpl agentMgmtLB = new AgentMSLBServiceImpl();
+
+    private final String msCSVList = "192.168.10.10, 192.168.10.11, 
192.168.10.12";
+    private final List<String> msList = Arrays.asList(msCSVList.replace(" 
","").split(","));
+
+    private static final long DC_1_ID = 1L;
+    private static final long DC_2_ID = 2L;
+
+    private void overrideDefaultConfigValue(final ConfigKey configKey, final 
String name, final Object o) throws IllegalAccessException, 
NoSuchFieldException {
+        final Field f = ConfigKey.class.getDeclaredField(name);
+        f.setAccessible(true);
+        f.set(configKey, o);
+    }
+
+    private void configureMocks() {
+        long id = 1;
+        for (HostVO h : Arrays.asList(host1, host2, host3, host4)) {
+            when(h.getId()).thenReturn(id);
+            when(h.getDataCenterId()).thenReturn(DC_1_ID);
+            
when(h.getHypervisorType()).thenReturn(Hypervisor.HypervisorType.KVM);
+            when(h.getRemoved()).thenReturn(null);
+            when(h.getResourceState()).thenReturn(ResourceState.Enabled);
+            id++;
+        }
+        when(resourceManager.listAllHostsInOneZoneByType(Host.Type.Routing, 
DC_1_ID))
+                .thenReturn(Arrays.asList(host4, host2, host1, host3));
+        
when(resourceManager.listAllHostsInAllZonesByType(Host.Type.Routing)).thenReturn(Arrays.asList(host4,
 host3, host1, host2));
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+        configureMocks();
+        agentMgmtLB.configure("someName", null);
+        
overrideDefaultConfigValue(ApiServiceConfiguration.ManagementServerAddresses, 
"_defaultValue", msCSVList);
+    }
+
+    @Test
+    public void testStaticLBSetting() throws NoSuchFieldException, 
IllegalAccessException {
+        
overrideDefaultConfigValue(AgentMSLBServiceImpl.ConnectedAgentLBAlgorithm, 
"_defaultValue", "static");
+        //Assert that each host receives the same list when algorithm is 
static, therefore, the same primary host
+        for (HostVO hostVO : Arrays.asList(host1, host2, host3, host4)) {
+            List<String> listToSend = 
agentMgmtLB.getManagementServerList(hostVO.getId(), hostVO.getDataCenterId());
+            Assert.assertEquals(msList, listToSend);
+            Assert.assertEquals(msList.get(0), listToSend.get(0));
+        }
+    }
+
+    @Test
+    public void testStaticLBSettingNullHostId() throws NoSuchFieldException, 
IllegalAccessException {
+        
overrideDefaultConfigValue(AgentMSLBServiceImpl.ConnectedAgentLBAlgorithm, 
"_defaultValue", "static");
+        //Assert new agents will be setup with the same list as existing hosts
+        List<String> listToSend = 
agentMgmtLB.getManagementServerList(host2.getId(), host2.getDataCenterId());
+        Assert.assertEquals(listToSend, 
agentMgmtLB.getManagementServerList(null, DC_1_ID));
+    }
+
+    private void testRoundRobinForExistingHosts(List<String> list) {
+        for (HostVO hostVO : Arrays.asList(host1, host2, host3, host4)) {
+            List<String> listToSend = 
agentMgmtLB.getManagementServerList(hostVO.getId(), hostVO.getDataCenterId());
+            Assert.assertEquals(list, listToSend);
+            Assert.assertEquals(list.get(0), listToSend.get(0));
+            list.add(list.get(0));
+            list.remove(0);
+        }
+    }
+    @Test
+    public void testRoundRobinLBSettingConnectedAgents() throws 
NoSuchFieldException, IllegalAccessException {
+        
overrideDefaultConfigValue(AgentMSLBServiceImpl.ConnectedAgentLBAlgorithm, 
"_defaultValue", "roundrobin");
+        List<String> list = new ArrayList<>(msList);
+        testRoundRobinForExistingHosts(list);
+    }
+
+    @Test
+    public void testRoundRobinDeterministicOrder() throws 
NoSuchFieldException, IllegalAccessException {
+        
overrideDefaultConfigValue(AgentMSLBServiceImpl.ConnectedAgentLBAlgorithm, 
"_defaultValue", "roundrobin");
+        //Assert that host order is deterministic for round robin. i.e. a 
given host receives the same list (order is given by host id)
+        List<String> listHost2 = 
agentMgmtLB.getManagementServerList(host2.getId(), host2.getDataCenterId());
+        Assert.assertEquals(listHost2, 
agentMgmtLB.getManagementServerList(host2.getId(), host2.getDataCenterId()));
+    }
+
+    @Test
+    public void testRoundRobinLBSettingNullHostId() throws 
NoSuchFieldException, IllegalAccessException {
+        
overrideDefaultConfigValue(AgentMSLBServiceImpl.ConnectedAgentLBAlgorithm, 
"_defaultValue", "roundrobin");
+        //Assert new agents will be setup with list assuming new host has the 
greatest id
+        List<String> list = new ArrayList<>(msList);
+        testRoundRobinForExistingHosts(list);
+        List<String> listToSend = agentMgmtLB.getManagementServerList(null, 
DC_1_ID);
+        Assert.assertEquals(list, listToSend);
+        Assert.assertEquals(list.get(0), listToSend.get(0));
+        list.add(list.get(0));
+        list.remove(0);
+    }
+
+    @Test
+    public void testShuffleLBSetting() throws NoSuchFieldException, 
IllegalAccessException {
+        
overrideDefaultConfigValue(AgentMSLBServiceImpl.ConnectedAgentLBAlgorithm, 
"_defaultValue", "shuffle");
+        List<String> shuffleListHost2 = 
agentMgmtLB.getManagementServerList(host2.getId(), host2.getDataCenterId());
+        Assert.assertEquals(new HashSet<>(msList), new 
HashSet<>(shuffleListHost2));
+    }
+
+    @Test
+    public void testShuffleLBSettingNullHostId() throws NoSuchFieldException, 
IllegalAccessException {
+        
overrideDefaultConfigValue(AgentMSLBServiceImpl.ConnectedAgentLBAlgorithm, 
"_defaultValue", "shuffle");
+        Assert.assertEquals(new HashSet<>(msList), new 
HashSet<>(agentMgmtLB.getManagementServerList(null, DC_1_ID)));
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testInvalidAlgorithmSetting() throws NoSuchFieldException, 
IllegalAccessException {
+        
overrideDefaultConfigValue(AgentMSLBServiceImpl.ConnectedAgentLBAlgorithm, 
"_defaultValue", "invalid-algo");
+        agentMgmtLB.getManagementServerList(host1.getId(), 
host1.getDataCenterId());
+    }
+
+    @Test(expected = CloudRuntimeException.class)
+    public void testExceptionOnEmptyHostSetting() throws NoSuchFieldException, 
IllegalAccessException {
+        
overrideDefaultConfigValue(ApiServiceConfiguration.ManagementServerAddresses, 
"_defaultValue", "");
+        // This should throw exception
+        agentMgmtLB.getManagementServerList(host1.getId(), 
host1.getDataCenterId());
+    }
+
+    @Test
+    public void testGetOrderedRunningHostIdsNullList() {
+        when(resourceManager.listAllHostsInOneZoneByType(Host.Type.Routing, 
DC_1_ID)).thenReturn(null);
+        Assert.assertNull(agentMgmtLB.getOrderedRunningHostIds(DC_1_ID));
+    }
+
+    @Test
+    public void testGetOrderedRunningHostIdsEmptyList() {
+        when(resourceManager.listAllHostsInOneZoneByType(Host.Type.Routing, 
DC_1_ID))
+                .thenReturn(new ArrayList<HostVO>());
+        Assert.assertEquals(new ArrayList<>(), 
agentMgmtLB.getOrderedRunningHostIds(DC_1_ID));
+    }
+
+    @Test
+    public void testGetOrderedRunningHostIdsOrderList() {
+        when(resourceManager.listAllHostsInOneZoneByType(Host.Type.Routing, 
DC_1_ID))
+                .thenReturn(Arrays.asList(host4, host2, host1, host3));
+        Assert.assertEquals(Arrays.asList(host1.getId(), host2.getId(), 
host3.getId(), host4.getId()),
+                agentMgmtLB.getOrderedRunningHostIds(DC_1_ID));
+    }
+
+    @Test
+    public void testGetHostsPerZoneAllHostsInOneZone() {
+        Map<Long, List<Long>> hostsPerZone = agentMgmtLB.getHostsPerZone();
+        Assert.assertEquals(1, hostsPerZone.keySet().size());
+        Assert.assertTrue(hostsPerZone.containsKey(DC_1_ID));
+        Assert.assertEquals(new HashSet<>(Arrays.asList(host1.getId(), 
host2.getId(), host3.getId(), host4.getId())),
+                new HashSet<>(hostsPerZone.get(DC_1_ID)));
+    }
+
+    @Test
+    public void testGetHostsPerZoneHostsInDifferentZones() {
+        when(host2.getDataCenterId()).thenReturn(DC_2_ID);
+        when(host4.getDataCenterId()).thenReturn(DC_2_ID);
+        Map<Long, List<Long>> hostsPerZone = agentMgmtLB.getHostsPerZone();
+        Assert.assertEquals(2, hostsPerZone.keySet().size());
+        Assert.assertTrue(hostsPerZone.containsKey(DC_1_ID));
+        Assert.assertTrue(hostsPerZone.containsKey(DC_2_ID));
+        Assert.assertEquals(new HashSet<>(Arrays.asList(host1.getId(), 
host3.getId())),
+                new HashSet<>(hostsPerZone.get(DC_1_ID)));
+        Assert.assertEquals(new HashSet<>(Arrays.asList(host2.getId(), 
host4.getId())),
+                new HashSet<>(hostsPerZone.get(DC_2_ID)));
+    }
+
+    @Test
+    public void testGetHostsPerZoneNullHosts() {
+        
when(resourceManager.listAllHostsInAllZonesByType(Host.Type.Routing)).thenReturn(null);
+        Assert.assertNull(agentMgmtLB.getHostsPerZone());
+    }
+
+    @Test
+    public void testGetHostsPerZoneEmptyHosts() {
+        
when(resourceManager.listAllHostsInAllZonesByType(Host.Type.Routing)).thenReturn(new
 ArrayList<>());
+        Map<Long, List<Long>> map = agentMgmtLB.getHostsPerZone();
+        Assert.assertNotNull(map);
+        Assert.assertTrue(map.isEmpty());
+    }
+}
\ No newline at end of file
diff --git 
a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java
 
b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java
index a158c9c0e68..5a6eec4a6b2 100644
--- 
a/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java
+++ 
b/services/secondary-storage/controller/src/main/java/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java
@@ -30,7 +30,7 @@
 import javax.inject.Inject;
 import javax.naming.ConfigurationException;
 
-import org.apache.cloudstack.config.ApiServiceConfiguration;
+import org.apache.cloudstack.agent.mslb.AgentMSLB;
 import org.apache.cloudstack.context.CallContext;
 import 
org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService;
 import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
@@ -89,12 +89,12 @@
 import com.cloud.network.Network;
 import com.cloud.network.NetworkModel;
 import com.cloud.network.Networks.TrafficType;
+import com.cloud.network.StorageNetworkManager;
 import com.cloud.network.dao.IPAddressDao;
 import com.cloud.network.dao.IPAddressVO;
 import com.cloud.network.dao.NetworkDao;
 import com.cloud.network.dao.NetworkVO;
 import com.cloud.network.rules.RulesManager;
-import com.cloud.network.StorageNetworkManager;
 import com.cloud.offering.NetworkOffering;
 import com.cloud.offering.ServiceOffering;
 import com.cloud.offerings.dao.NetworkOfferingDao;
@@ -246,6 +246,9 @@
     VolumeDataStoreDao _volumeStoreDao;
     @Inject
     private ImageStoreDetailsUtil imageStoreDetailsUtil;
+    @Inject
+    private AgentMSLB agentMSLB;
+
     private long _capacityScanInterval = DEFAULT_CAPACITY_SCAN_INTERVAL;
     private int _secStorageVmMtuSize;
 
@@ -1119,7 +1122,7 @@ public boolean 
finalizeVirtualMachineProfile(VirtualMachineProfile profile, Depl
 
         StringBuilder buf = profile.getBootArgsBuilder();
         buf.append(" template=domP type=secstorage");
-        buf.append(" 
host=").append(StringUtils.shuffleCSVList(ApiServiceConfiguration.ManagementHostIPAdr.value()));
+        buf.append(" 
host=").append(StringUtils.toCSVList(agentMSLB.getManagementServerList(dest.getHost().getId(),
 dest.getDataCenter().getId())));
         buf.append(" port=").append(_mgmtPort);
         buf.append(" name=").append(profile.getVirtualMachine().getHostName());
 
diff --git a/utils/src/main/java/com/cloud/utils/StringUtils.java 
b/utils/src/main/java/com/cloud/utils/StringUtils.java
index 6ada2ad60bd..42dcf3c4204 100644
--- a/utils/src/main/java/com/cloud/utils/StringUtils.java
+++ b/utils/src/main/java/com/cloud/utils/StringUtils.java
@@ -21,12 +21,10 @@
 
 import java.nio.charset.Charset;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
-import java.util.Random;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -323,9 +321,7 @@ public static String mapToString(final Map<String, String> 
map) {
         return listOfChunks;
     }
 
-    public static String shuffleCSVList(final String csvList) {
-        List<String> list = csvTagsToList(csvList);
-        Collections.shuffle(list, new Random(System.nanoTime()));
+    public static String toCSVList(final List<String> list) {
         return join(list, ",");
     }
 }
diff --git a/utils/src/test/java/com/cloud/utils/StringUtilsTest.java 
b/utils/src/test/java/com/cloud/utils/StringUtilsTest.java
index e8e62b0a75e..ea5f5d10b04 100644
--- a/utils/src/test/java/com/cloud/utils/StringUtilsTest.java
+++ b/utils/src/test/java/com/cloud/utils/StringUtilsTest.java
@@ -20,13 +20,13 @@
 package com.cloud.utils;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
 
 import java.nio.charset.Charset;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.List;
 
 import org.junit.Test;
 
@@ -254,13 +254,16 @@ public void listToCsvTags() {
     }
 
     @Test
-    public void testShuffleCSVList() {
+    public void testCSVList() {
         String input = "one,two,three,four,five,six,seven,eight,nine,ten";
-        String output = StringUtils.shuffleCSVList(input);
-        assertFalse(input.equals(output));
+        List<String> list = Arrays.asList(input.split(","));
+        assertTrue(input.equals(StringUtils.toCSVList(list)));
+    }
 
-        input = "only-one";
-        output = StringUtils.shuffleCSVList("only-one");
-        assertTrue(input.equals(output));
+    @Test
+    public void testCSVListWithOneItem() {
+        String input = "singleitem";
+        List<String> list = Arrays.asList(input.split(","));
+        assertTrue(input.equals(StringUtils.toCSVList(list)));
     }
 }


 

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


With regards,
Apache Git Services

Reply via email to