From 00cb1b6b456c74e5262d6add0e2c7cae72304dbe Mon Sep 17 00:00:00 2001 From: Rohit Yadav Date: Mon, 23 Oct 2017 01:40:31 -0300 Subject: [PATCH] APPLE FR22: Multiple Management Servers support for agents (FRO-72) FR12 introduce basic support for comma-separated list of management servers for agents, while makes an explicit LB unnecessary. On the agent side, the 'host' is saved as: @. Where the algorithm name is the name of the lb algorithm. The This FR introduces two new global settings: - indirect.agent.lb.algorithm: The algorithm for the indirect agent LB. - indirect.agent.lb.check.interval: The preferred host check interval for the agent's background task that checks and switches to agent's preferred host. Any changes to the above two global settings and the 'host' setting does not require restarting of the management server(s). The indirect.agent.lb.algorithm supports following algorithm options: - static: use the list as provided. - roundrobin: evenly spreads hosts across management servers. - shuffle: (pseudo) randomly sorts the list (not recommended for production). From the agent's perspective, the first address in the propagated list will be considered the preferred host. A new background task can be activated by configuring the indirect.agent.lb.check.interval which is a cluster level global setting from CloudStack or admins can override this by configuring the 'host.lb.check.interval' in the host's agent.properties file. Comma-separated management server list is propagated to agents on following cases: - Addition of a host (including ssvm, cpvm systevms). - Connection or reconnection by the agents to a management server. - After admin changes the 'host' and/or the 'indirect.agent.lb.algorithm' global settings. First the agent connects to the management server and sends its current management server list, which is compared by the management server and in case of failure a new/update list is sent for the agent to persist. Every time agent gets a ms-host list and the algorithm, the host specific background check interval is also sent and it dynamically reconfigures the background task without need to restart agents. The 'static' and 'roundrobin' algorithms, strictly checks for the order as expected by them, however, the 'shuffle' algorithm just checks for content and not the order of the comma separate ms host addresses. Signed-off-by: Rohit Yadav --- agent/conf/agent.properties | 11 + agent/src/com/cloud/agent/Agent.java | 124 ++++++++-- agent/src/com/cloud/agent/AgentShell.java | 58 ++++- agent/src/com/cloud/agent/IAgentShell.java | 45 ++-- .../test/com/cloud/agent/AgentShellTest.java | 8 +- .../config/ApiServiceConfiguration.java | 4 +- client/pom.xml | 5 + .../src/com/cloud/agent/api/ReadyCommand.java | 31 ++- .../com/cloud/agent/api/StartupCommand.java | 9 + .../agent/lb/SetupMSListAnswer.java | 30 +++ .../agent/lb/SetupMSListCommand.java | 56 +++++ engine/orchestration/pom.xml | 5 + .../cloud/agent/manager/AgentManagerImpl.java | 25 +- framework/agent-lb/pom.xml | 32 +++ .../cloudstack/agent/lb/IndirectAgentLB.java | 53 ++++ .../agent/lb/IndirectAgentLBAlgorithm.java | 45 ++++ framework/pom.xml | 1 + .../lb/ElasticLoadBalancerManagerImpl.java | 2 +- .../contrail/management/EventUtils.java | 2 +- server/pom.xml | 5 + .../spring-server-core-managers-context.xml | 2 + .../ConfigurationManagerImpl.java | 5 + .../consoleproxy/ConsoleProxyManagerImpl.java | 6 +- .../discoverer/LibvirtServerDiscoverer.java | 5 +- .../VirtualNetworkApplianceManagerImpl.java | 2 +- .../cloud/server/ConfigurationServerImpl.java | 4 +- .../agent/lb/IndirectAgentLBServiceImpl.java | 231 ++++++++++++++++++ .../IndirectAgentLBRoundRobinAlgorithm.java | 59 +++++ .../IndirectAgentLBShuffleAlgorithm.java | 44 ++++ .../IndirectAgentLBStaticAlgorithm.java | 40 +++ .../lb/IndirectAgentLBServiceImplTest.java | 208 ++++++++++++++++ ...ndirectAgentLBRoundRobinAlgorithmTest.java | 60 +++++ .../IndirectAgentLBShuffleAlgorithmTest.java | 44 ++++ .../IndirectAgentLBStaticAlgorithmTest.java | 33 +++ .../test/resources/createNetworkOffering.xml | 3 +- server/test/resources/testContext.xml | 3 +- .../SecondaryStorageManagerImpl.java | 14 +- utils/src/com/cloud/utils/StringUtils.java | 6 +- .../test/com/cloud/utils/StringUtilsTest.java | 17 +- 39 files changed, 1266 insertions(+), 71 deletions(-) create mode 100644 core/src/org/apache/cloudstack/agent/lb/SetupMSListAnswer.java create mode 100644 core/src/org/apache/cloudstack/agent/lb/SetupMSListCommand.java create mode 100644 framework/agent-lb/pom.xml create mode 100644 framework/agent-lb/src/org/apache/cloudstack/agent/lb/IndirectAgentLB.java create mode 100644 framework/agent-lb/src/org/apache/cloudstack/agent/lb/IndirectAgentLBAlgorithm.java create mode 100644 server/src/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImpl.java create mode 100644 server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithm.java create mode 100644 server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithm.java create mode 100644 server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithm.java create mode 100644 server/test/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImplTest.java create mode 100644 server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java create mode 100644 server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java create mode 100644 server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties index daad05f3b65..a1c36fdfe6f 100644 --- a/agent/conf/agent.properties +++ b/agent/conf/agent.properties @@ -30,6 +30,17 @@ 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 after agent +# connects to a management server. On connection, it receives admin configured +# cluster-level 'indirect.agent.lb.check.interval' setting that will be used by +# the agent as the preferred host check interval however the following setting +# if defined overrides the received value. The value 0 and lb algorithm 'shuffle' +# disables this background task. +#host.lb.check.interval=0 + #port = The port management server listening on, default is 8250 port=8250 diff --git a/agent/src/com/cloud/agent/Agent.java b/agent/src/com/cloud/agent/Agent.java index 4cdc4996629..ec27a1fbb67 100755 --- a/agent/src/com/cloud/agent/Agent.java +++ b/agent/src/com/cloud/agent/Agent.java @@ -21,6 +21,8 @@ import java.io.IOException; 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; @@ -37,12 +39,15 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.naming.ConfigurationException; +import org.apache.cloudstack.agent.lb.SetupMSListAnswer; +import org.apache.cloudstack.agent.lb.SetupMSListCommand; 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; @@ -64,6 +69,7 @@ import com.cloud.agent.transport.Response; 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; @@ -120,6 +126,7 @@ public class Agent implements HandlerFactory, IAgentControl { Long _id; Timer _timer = new Timer("Agent Timer"); + Timer hostLBTimer; List _watchList = new ArrayList(); long _sequence = 0; @@ -143,7 +150,7 @@ public class Agent implements HandlerFactory, IAgentControl { _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)); @@ -178,7 +185,7 @@ public class Agent implements HandlerFactory, IAgentControl { 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()); @@ -254,8 +261,10 @@ public class Agent implements HandlerFactory, IAgentControl { s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again..."); } while (!_connection.isStartup()) { + final String host = _shell.getNextHost(); _shell.getBackoffAlgorithm().waitBeforeRetry(); - _connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this); + _connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this); + s_logger.info("Connecting to host:" + host); try { _connection.start(); } catch (final NioConnectionException e) { @@ -263,6 +272,7 @@ public class Agent implements HandlerFactory, IAgentControl { s_logger.info("Attempted to connect to the server, but received an unexpected exception, trying again..."); } } + _shell.updateConnectedHost(); } public void stop(final String reason, final String detail) { @@ -307,6 +317,17 @@ public class Agent implements HandlerFactory, IAgentControl { _shell.setPersistentProperty(getResourceName(), "id", Long.toString(id)); } + private synchronized void scheduleHostLBCheckerTask(final long checkInterval) { + if (hostLBTimer != null) { + hostLBTimer.cancel(); + } + if (checkInterval > 0L) { + s_logger.info("Scheduling preferred host timer task with host.lb.interval=" + checkInterval + "ms"); + hostLBTimer = new Timer("Host LB Timer"); + 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()) { @@ -329,8 +350,8 @@ public class Agent implements HandlerFactory, IAgentControl { _watchList.clear(); } } - public synchronized void lockStartupTask(final Link link) - { + + public synchronized void lockStartupTask(final Link link) { _startup = new StartupTask(link); _timer.schedule(_startup, _startupWait); } @@ -338,9 +359,11 @@ public class Agent implements HandlerFactory, IAgentControl { public void sendStartup(final Link link) { final StartupCommand[] startup = _resource.initialize(); if (startup != null) { + final String msHostList = _shell.getPersistentProperty(null, "host"); final Command[] commands = new Command[startup.length]; for (int i = 0; i < startup.length; i++) { setupStartupCommand(startup[i]); + startup[i].setMSHostList(msHostList); commands[i] = startup[i]; } final Request request = new Request(_id != null ? _id : -1, -1, commands, false, false); @@ -399,19 +422,23 @@ public class Agent implements HandlerFactory, IAgentControl { } } - 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) { @@ -432,8 +459,9 @@ public class Agent implements HandlerFactory, IAgentControl { } do { - _connection = new NioClient("Agent", _shell.getHost(), _shell.getPort(), _shell.getWorkers(), this); - s_logger.info("Reconnecting..."); + String host = _shell.getNextHost(); + _connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this); + s_logger.info("Lost connection to host: " + lastConnectedHost + ". Connecting to next host: " + host); try { _connection.start(); } catch (final NioConnectionException e) { @@ -448,7 +476,8 @@ public class Agent implements HandlerFactory, IAgentControl { } _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) { @@ -548,6 +577,8 @@ public class Agent implements HandlerFactory, IAgentControl { answer = setupAgentKeystore((SetupKeyStoreCommand) cmd); } else if (cmd instanceof SetupCertificateCommand && ((SetupCertificateCommand) cmd).isHandleByAgent()) { answer = setupAgentCertificate((SetupCertificateCommand) cmd); + } else if (cmd instanceof SetupMSListCommand) { + answer = setupManagementServerList((SetupMSListCommand) cmd); } else { if (cmd instanceof ReadyCommand) { processReadyCommand(cmd); @@ -677,6 +708,30 @@ public class Agent implements HandlerFactory, IAgentControl { return new SetupCertificateAnswer(true); } + private void processManagementServerList(final List msList, final String lbAlgorithm, final Long lbCheckInterval) { + if (CollectionUtils.isNotEmpty(msList) && !Strings.isNullOrEmpty(lbAlgorithm)) { + try { + final String newMSHosts = String.format("%s%s%s", StringUtils.toCSVList(msList), IAgentShell.hostLbAlgorithmSeparator, lbAlgorithm); + _shell.setPersistentProperty(null, "host", newMSHosts); + _shell.setHosts(newMSHosts); + _shell.resetHostCounter(); + s_logger.info("Processed new management server list: " + newMSHosts); + } catch (final Exception e) { + throw new CloudRuntimeException("Could not persist received management servers list", e); + } + } + if ("shuffle".equals(lbAlgorithm)) { + scheduleHostLBCheckerTask(0); + } else { + scheduleHostLBCheckerTask(_shell.getLbCheckerInterval(lbCheckInterval)); + } + } + + private Answer setupManagementServerList(final SetupMSListCommand cmd) { + processManagementServerList(cmd.getMsList(), cmd.getLbAlgorithm(), cmd.getLbCheckInterval()); + return new SetupMSListAnswer(true); + } + public void processResponse(final Response response, final Link link) { final Answer answer = response.getAnswer(); if (s_logger.isDebugEnabled()) { @@ -697,15 +752,16 @@ public class Agent implements HandlerFactory, IAgentControl { } 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()); + processManagementServerList(ready.getMsHostList(), ready.getLbAlgorithm(), ready.getLbCheckInterval()); + + s_logger.info("Ready command is processed for agent id = " + getId()); } public void processOtherTask(final Task task) { @@ -987,4 +1043,44 @@ public class Agent implements HandlerFactory, IAgentControl { } } } + + public class PreferredHostCheckerTask extends ManagedContextTimerTask { + + @Override + protected void runInContext() { + try { + final String[] msList = _shell.getHosts(); + if (msList == null || msList.length < 1) { + return; + } + final String preferredHost = msList[0]; + 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 != null && !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.isTraceEnabled()) { + s_logger.trace("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/com/cloud/agent/AgentShell.java b/agent/src/com/cloud/agent/AgentShell.java index eb4bf8067a1..d06b157605f 100644 --- a/agent/src/com/cloud/agent/AgentShell.java +++ b/agent/src/com/cloud/agent/AgentShell.java @@ -50,6 +50,7 @@ import com.cloud.utils.PropertiesUtil; 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 @@ public class AgentShell implements IAgentShell, Daemon { private volatile boolean _exit = false; private int _pingRetries; private final List _agents = new ArrayList(); + private String hostToConnect; + private String connectedHost; + private Long preferredHostCheckInterval; public AgentShell() { } @@ -107,17 +111,53 @@ public class AgentShell implements IAgentShell, Daemon { } @Override - public String getHost() { - String[] hosts = _host.split(","); + public String getNextHost() { + final String[] hosts = getHosts(); if (_hostCounter >= hosts.length) { _hostCounter = 0; } - s_logger.info("Connecting to host: " + hosts[_hostCounter % hosts.length]); - return hosts[_hostCounter++ % hosts.length]; + hostToConnect = hosts[_hostCounter % hosts.length]; + _hostCounter++; + return hostToConnect; } - public void setHost(final String host) { - _host = host; + @Override + public String getConnectedHost() { + return connectedHost; + } + + @Override + public long getLbCheckerInterval(final Long receivedLbInterval) { + if (preferredHostCheckInterval != null) { + return preferredHostCheckInterval * 1000L; + } + if (receivedLbInterval != null) { + return receivedLbInterval * 1000L; + } + return 0L; + } + + @Override + public void updateConnectedHost() { + connectedHost = hostToConnect; + } + + @Override + public void setHosts(final String host) { + if (!Strings.isNullOrEmpty(host)) { + _host = host.split(hostLbAlgorithmSeparator)[0]; + resetHostCounter(); + } + } + + @Override + public void resetHostCounter() { + _hostCounter = 0; + } + + @Override + public String[] getHosts() { + return _host.split(","); } @Override @@ -250,7 +290,8 @@ public class AgentShell implements IAgentShell, Daemon { if (host == null) { host = "localhost"; } - _host = host; + + setHosts(host); if (zone != null) _zone = zone; @@ -290,6 +331,9 @@ public class AgentShell implements IAgentShell, Daemon { _properties.setProperty("guid", _guid); } + String val = getProperty(null, preferredHostIntervalKey); + preferredHostCheckInterval = (Strings.isNullOrEmpty(val) ? null : Long.valueOf(val)); + return true; } diff --git a/agent/src/com/cloud/agent/IAgentShell.java b/agent/src/com/cloud/agent/IAgentShell.java index dde67381a4a..5b52cee6361 100644 --- a/agent/src/com/cloud/agent/IAgentShell.java +++ b/agent/src/com/cloud/agent/IAgentShell.java @@ -22,33 +22,48 @@ import java.util.Properties; import com.cloud.utils.backoff.BackoffAlgorithm; public interface IAgentShell { - public Map getCmdLineProperties(); + String hostLbAlgorithmSeparator = "@"; + String preferredHostIntervalKey = "host.lb.check.interval"; - public Properties getProperties(); + Map getCmdLineProperties(); - public String getPersistentProperty(String prefix, String name); + Properties getProperties(); - public void setPersistentProperty(String prefix, String name, String value); + String getPersistentProperty(String prefix, String name); - public String getHost(); + void setPersistentProperty(String prefix, String name, String value); - public String getPrivateIp(); + String getNextHost(); - public int getPort(); + String getPrivateIp(); - public int getWorkers(); + int getPort(); - public int getProxyPort(); + int getWorkers(); - public String getGuid(); + int getProxyPort(); - public String getZone(); + String getGuid(); - public String getPod(); + String getZone(); - public BackoffAlgorithm getBackoffAlgorithm(); + String getPod(); - public int getPingRetries(); + BackoffAlgorithm getBackoffAlgorithm(); - public String getVersion(); + int getPingRetries(); + + String getVersion(); + + void setHosts(String hosts); + + void resetHostCounter(); + + String[] getHosts(); + + long getLbCheckerInterval(Long receivedLbInterval); + + void updateConnectedHost(); + + String getConnectedHost(); } diff --git a/agent/test/com/cloud/agent/AgentShellTest.java b/agent/test/com/cloud/agent/AgentShellTest.java index a3610c1712c..ec8b3655ee9 100644 --- a/agent/test/com/cloud/agent/AgentShellTest.java +++ b/agent/test/com/cloud/agent/AgentShellTest.java @@ -36,7 +36,7 @@ public class AgentShellTest { 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()); @@ -54,10 +54,10 @@ public class AgentShellTest { public void testGetHost() { AgentShell shell = new AgentShell(); List 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/org/apache/cloudstack/config/ApiServiceConfiguration.java b/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java index 701af625884..2d6242e357e 100644 --- a/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java +++ b/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java @@ -23,7 +23,7 @@ import org.apache.cloudstack.framework.config.Configurable; @Local(value = {ApiServiceConfiguration.class}) public class ApiServiceConfiguration implements Configurable { - public static final ConfigKey ManagementHostIPAdr = new ConfigKey("Advanced", String.class, "host", "localhost", "The ip address of management server", true); + public static final ConfigKey ManagementServerAddresses = new ConfigKey("Advanced", String.class, "host", "localhost", "The ip address of management server", true); public static final ConfigKey ApiServletPath = new ConfigKey("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 DefaultUIPageSize = new ConfigKey("Advanced", Long.class, "default.ui.page.size", "20", @@ -36,7 +36,7 @@ public class ApiServiceConfiguration implements Configurable { @Override public ConfigKey[] getConfigKeys() { - return new ConfigKey[] {ManagementHostIPAdr, ApiServletPath, DefaultUIPageSize}; + return new ConfigKey[] {ManagementServerAddresses, ApiServletPath, DefaultUIPageSize}; } } diff --git a/client/pom.xml b/client/pom.xml index a7f453c93bb..4eceaee0f4f 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -266,6 +266,11 @@ cloud-mom-inmemory ${project.version} + + org.apache.cloudstack + cloud-framework-agent-lb + ${project.version} + org.apache.cloudstack cloud-framework-ca diff --git a/core/src/com/cloud/agent/api/ReadyCommand.java b/core/src/com/cloud/agent/api/ReadyCommand.java index b02b004d6e5..06a435ad773 100644 --- a/core/src/com/cloud/agent/api/ReadyCommand.java +++ b/core/src/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,16 @@ public class ReadyCommand extends Command { private Long dcId; private Long hostId; + private List msHostList; + private String lbAlgorithm; + private Long lbCheckInterval; 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 +64,28 @@ public class ReadyCommand extends Command { public Long getHostId() { return hostId; } + + public List getMsHostList() { + return msHostList; + } + + public void setMsHostList(List msHostList) { + this.msHostList = msHostList; + } + + public String getLbAlgorithm() { + return lbAlgorithm; + } + + public void setLbAlgorithm(String lbAlgorithm) { + this.lbAlgorithm = lbAlgorithm; + } + + public Long getLbCheckInterval() { + return lbCheckInterval; + } + + public void setLbCheckInterval(Long lbCheckInterval) { + this.lbCheckInterval = lbCheckInterval; + } } diff --git a/core/src/com/cloud/agent/api/StartupCommand.java b/core/src/com/cloud/agent/api/StartupCommand.java index 1de51ad0f6f..5f2c00d0be6 100755 --- a/core/src/com/cloud/agent/api/StartupCommand.java +++ b/core/src/com/cloud/agent/api/StartupCommand.java @@ -46,6 +46,7 @@ public class StartupCommand extends Command { String agentTag; String resourceName; String gatewayIpAddress; + String msHostList; public StartupCommand(Host.Type type) { this.type = type; @@ -281,6 +282,14 @@ public class StartupCommand extends Command { this.gatewayIpAddress = gatewayIpAddress; } + public String getMsHostList() { + return msHostList; + } + + public void setMSHostList(String msHostList) { + this.msHostList = msHostList; + } + @Override public boolean executeInSequence() { return false; diff --git a/core/src/org/apache/cloudstack/agent/lb/SetupMSListAnswer.java b/core/src/org/apache/cloudstack/agent/lb/SetupMSListAnswer.java new file mode 100644 index 00000000000..e73d8e583e7 --- /dev/null +++ b/core/src/org/apache/cloudstack/agent/lb/SetupMSListAnswer.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.lb; + +import com.cloud.agent.api.Answer; + +public class SetupMSListAnswer extends Answer { + + public SetupMSListAnswer(final boolean result) { + super(null); + this.result = result; + } +} \ No newline at end of file diff --git a/core/src/org/apache/cloudstack/agent/lb/SetupMSListCommand.java b/core/src/org/apache/cloudstack/agent/lb/SetupMSListCommand.java new file mode 100644 index 00000000000..abc739f7d2f --- /dev/null +++ b/core/src/org/apache/cloudstack/agent/lb/SetupMSListCommand.java @@ -0,0 +1,56 @@ +// +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +// + +package org.apache.cloudstack.agent.lb; + +import java.util.List; + +import com.cloud.agent.api.Command; + +public class SetupMSListCommand extends Command { + + private List msList; + private String lbAlgorithm; + private Long lbCheckInterval; + + public SetupMSListCommand(final List msList, final String lbAlgorithm, final Long lbCheckInterval) { + super(); + this.msList = msList; + this.lbAlgorithm = lbAlgorithm; + this.lbCheckInterval = lbCheckInterval; + } + + public List getMsList() { + return msList; + } + + public String getLbAlgorithm() { + return lbAlgorithm; + } + + public Long getLbCheckInterval() { + return lbCheckInterval; + } + + @Override + public boolean executeInSequence() { + return false; + } + +} \ No newline at end of file diff --git a/engine/orchestration/pom.xml b/engine/orchestration/pom.xml index 23c0d1c5873..525b3d40e66 100755 --- a/engine/orchestration/pom.xml +++ b/engine/orchestration/pom.xml @@ -58,6 +58,11 @@ cloud-utils ${project.version} + + org.apache.cloudstack + cloud-framework-agent-lb + ${project.version} + + + 4.0.0 + Apache CloudStack Agent Management Servers Load Balancer + cloud-framework-agent-lb + + cloudstack-framework + org.apache.cloudstack + 4.5.3-SNAPSHOT + ../pom.xml + + + diff --git a/framework/agent-lb/src/org/apache/cloudstack/agent/lb/IndirectAgentLB.java b/framework/agent-lb/src/org/apache/cloudstack/agent/lb/IndirectAgentLB.java new file mode 100644 index 00000000000..627a5ee5f50 --- /dev/null +++ b/framework/agent-lb/src/org/apache/cloudstack/agent/lb/IndirectAgentLB.java @@ -0,0 +1,53 @@ +// 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.lb; + +import java.util.List; + +public interface IndirectAgentLB { + + /** + * Return list of management server addresses after applying configured lb algorithm + * for a host in a zone. + * @param hostId host id (if present) + * @param dcId zone id + * @param orderedHostIdList (optional) list of ordered host id list + * @return management servers string list + */ + List getManagementServerList(Long hostId, Long dcId, List orderedHostIdList); + + /** + * Compares received management server list against expected list for a host in a zone. + * @param hostId host id + * @param dcId zone id + * @param receivedMSHosts received management server list + * @return true if mgmtHosts is up to date, false if not + */ + boolean compareManagementServerList(Long hostId, Long dcId, List receivedMSHosts); + + /** + * Returns the configure LB algorithm + * @return returns algorithm name + */ + String getLBAlgorithmName(); + + /** + * Returns the configured LB preferred host check interval (if applicable at cluster scope) + * @return returns interval in seconds + */ + Long getLBPreferredHostCheckInterval(Long clusterId); +} \ No newline at end of file diff --git a/framework/agent-lb/src/org/apache/cloudstack/agent/lb/IndirectAgentLBAlgorithm.java b/framework/agent-lb/src/org/apache/cloudstack/agent/lb/IndirectAgentLBAlgorithm.java new file mode 100644 index 00000000000..a4a622f17ab --- /dev/null +++ b/framework/agent-lb/src/org/apache/cloudstack/agent/lb/IndirectAgentLBAlgorithm.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.lb; + +import java.util.List; + +public interface IndirectAgentLBAlgorithm { + /** + * Returns a sorted 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 sort(final List msList, final List 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 compare(final List msList, final List receivedMsList); +} \ No newline at end of file diff --git a/framework/pom.xml b/framework/pom.xml index 840268596c0..f64e1f46842 100644 --- a/framework/pom.xml +++ b/framework/pom.xml @@ -55,5 +55,6 @@ spring/lifecycle spring/module security + agent-lb diff --git a/plugins/network-elements/elastic-loadbalancer/src/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java b/plugins/network-elements/elastic-loadbalancer/src/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java index 11da736d1d0..f0542b23cc4 100644 --- a/plugins/network-elements/elastic-loadbalancer/src/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java +++ b/plugins/network-elements/elastic-loadbalancer/src/com/cloud/network/lb/ElasticLoadBalancerManagerImpl.java @@ -450,7 +450,7 @@ public class ElasticLoadBalancerManagerImpl extends ManagerBase implements Elast 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/plugins/network-elements/juniper-contrail/src/org/apache/cloudstack/network/contrail/management/EventUtils.java b/plugins/network-elements/juniper-contrail/src/org/apache/cloudstack/network/contrail/management/EventUtils.java index faeff1f5bd6..3dba5a01e8c 100644 --- a/plugins/network-elements/juniper-contrail/src/org/apache/cloudstack/network/contrail/management/EventUtils.java +++ b/plugins/network-elements/juniper-contrail/src/org/apache/cloudstack/network/contrail/management/EventUtils.java @@ -74,7 +74,7 @@ public class EventUtils { try { s_messageBus.publish(EventTypes.getEntityForEvent(eventType), eventType, null, event); } catch (Exception e) { - s_logger.warn("Failed to publish action event on the the event bus."); + s_logger.debug("Failed to publish action event on the the event bus."); } } diff --git a/server/pom.xml b/server/pom.xml index 97dd076a7db..592d7571a69 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -138,6 +138,11 @@ cloud-engine-components-api ${project.version} + + org.apache.cloudstack + cloud-framework-agent-lb + ${project.version} + org.opensaml opensaml diff --git a/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml b/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml index 64c77ada395..fa91ea3a8b4 100644 --- a/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml +++ b/server/resources/META-INF/cloudstack/core/spring-server-core-managers-context.xml @@ -256,4 +256,6 @@ + + diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 89e0428bad9..9de88e0ccb5 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -75,6 +75,8 @@ import org.apache.cloudstack.framework.config.ConfigDepot; import org.apache.cloudstack.framework.config.ConfigKey; 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; @@ -324,6 +326,8 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati AffinityGroupService _affinityGroupService; @Inject StorageManager _storageManager; + @Inject + MessageBus messageBus; // FIXME - why don't we have interface for DataCenterLinkLocalIpAddressDao? @Inject @@ -598,6 +602,7 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati } txn.commit(); + messageBus.publish(_name, EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, PublishScope.GLOBAL, name); return _configDao.getValue(name); } diff --git a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java index 3a53a5fd414..e93de8948a2 100755 --- a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -36,7 +36,7 @@ import org.apache.log4j.Logger; import com.google.gson.Gson; import com.google.gson.GsonBuilder; -import org.apache.cloudstack.config.ApiServiceConfiguration; +import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.framework.config.dao.ConfigurationDao; @@ -214,6 +214,8 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy private KeysManager _keysMgr; @Inject private VirtualMachineManager _itMgr; + @Inject + private IndirectAgentLB indirectAgentLB; private ConsoleProxyListener _listener; @@ -1303,7 +1305,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy 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(indirectAgentLB.getManagementServerList(dest.getHost().getId(), dest.getDataCenter().getId(), null))); buf.append(" port=").append(_mgmtPort); buf.append(" name=").append(profile.getVirtualMachine().getHostName()); if (_sslEnabled) { diff --git a/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java b/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java index 79b4f38c752..ac4aaac1a6d 100644 --- a/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java +++ b/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java @@ -63,6 +63,7 @@ import com.cloud.utils.PasswordGenerator; import com.cloud.utils.StringUtils; import com.cloud.utils.ssh.SSHCmdHelper; import com.trilead.ssh2.Connection; +import org.apache.cloudstack.agent.lb.IndirectAgentLB; public abstract class LibvirtServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter { private static final Logger s_logger = Logger.getLogger(LibvirtServerDiscoverer.class); @@ -75,6 +76,8 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements private AgentManager agentMgr; @Inject private CAManager caManager; + @Inject + private IndirectAgentLB indirectAgentLB; @Override public abstract Hypervisor.HypervisorType getHypervisorType(); @@ -279,7 +282,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements setupAgentSecurity(sshConnection, agentIp, hostname); - String parameters = " -m " + StringUtils.shuffleCSVList(_hostIp) + " -z " + dcId + " -p " + podId + " -c " + clusterId + " -g " + guid + " -a"; + String parameters = " -m " + StringUtils.toCSVList(indirectAgentLB.getManagementServerList(null, dcId, null)) + " -z " + dcId + " -p " + podId + " -c " + clusterId + " -g " + guid + " -a"; parameters += " --pubNic=" + kvmPublicNic; parameters += " --prvNic=" + kvmPrivateNic; diff --git a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java index 2b94aaff98b..d03376f003f 100755 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -2230,7 +2230,7 @@ VirtualMachineGuru, Listener, Configurable, StateListener IndirectAgentLBAlgorithm = new ConfigKey<>("Advanced", String.class, + "indirect.agent.lb.algorithm", "static", + "The algorithm to be 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); + + public static final ConfigKey IndirectAgentLBCheckInterval = new ConfigKey<>("Advanced", Long.class, + "indirect.agent.lb.check.interval", "0", + "The interval in seconds after which agent should check and try to connect to its preferred host. Set 0 to disable it.", + true, ConfigKey.Scope.Cluster); + + private static Map algorithmMap = new HashMap<>(); + + @Inject + private HostDao hostDao; + @Inject + private MessageBus messageBus; + @Inject + private AgentManager agentManager; + + ////////////////////////////////////////////////////// + /////////////// Agent MSLB Methods /////////////////// + ////////////////////////////////////////////////////// + + @Override + public List getManagementServerList(final Long hostId, final Long dcId, final List orderedHostIdList) { + 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 hostIdList = orderedHostIdList; + if (hostIdList == null) { + hostIdList = getOrderedHostIdList(dcId); + } + + final org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm algorithm = getAgentMSLBAlgorithm(); + final List msList = Arrays.asList(msServerAddresses.replace(" ", "").split(",")); + return algorithm.sort(msList, hostIdList, hostId); + } + + @Override + public boolean compareManagementServerList(final Long hostId, final Long dcId, final List receivedMSHosts) { + if (receivedMSHosts == null || receivedMSHosts.size() < 1) { + return false; + } + final List expectedMSList = getManagementServerList(hostId, dcId, null); + final org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm algorithm = getAgentMSLBAlgorithm(); + return algorithm.compare(expectedMSList, receivedMSHosts); + } + + @Override + public String getLBAlgorithmName() { + return IndirectAgentLBAlgorithm.value(); + } + + @Override + public Long getLBPreferredHostCheckInterval(final Long clusterId) { + return IndirectAgentLBCheckInterval.valueIn(clusterId); + } + + List getOrderedHostIdList(final Long dcId) { + final List hostIdList = new ArrayList<>(); + for (final Host host : getAllAgentBasedHosts()) { + if (host.getDataCenterId() == dcId) { + hostIdList.add(host.getId()); + } + } + Collections.sort(hostIdList, new Comparator() { + @Override + public int compare(Long x, Long y) { + return Long.compare(x,y); + } + }); + return hostIdList; + } + + private List getAllAgentBasedHosts() { + final List allHosts = hostDao.listAll(); + if (allHosts == null) { + return new ArrayList<>(); + } + final List agentBasedHosts = new ArrayList<>(); + for (final Host host : allHosts) { + if (host == null || host.getResourceState() != ResourceState.Enabled) { + continue; + } + if (host.getType() == Host.Type.Routing || host.getType() == Host.Type.ConsoleProxy || host.getType() == Host.Type.SecondaryStorage || host.getType() == Host.Type.SecondaryStorageVM) { + if (host.getHypervisorType() != null && host.getHypervisorType() != Hypervisor.HypervisorType.KVM && host.getHypervisorType() != Hypervisor.HypervisorType.LXC) { + continue; + } + agentBasedHosts.add(host); + } + } + return agentBasedHosts; + } + + private org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm getAgentMSLBAlgorithm() { + final String algorithm = getLBAlgorithmName(); + if (algorithmMap.containsKey(algorithm)) { + return algorithmMap.get(algorithm); + } + throw new CloudRuntimeException(String.format("Algorithm configured for '%s' not found, valid values are: %s", + IndirectAgentLBAlgorithm.key(), algorithmMap.keySet())); + } + + //////////////////////////////////////////////////////////// + /////////////// Agent MSLB Configuration /////////////////// + //////////////////////////////////////////////////////////// + + private void propagateMSListToAgents() { + LOG.debug("Propagating management server list update to agents"); + final String lbAlgorithm = getLBAlgorithmName(); + final Map> dcOrderedHostsMap = new HashMap<>(); + for (final Host host : getAllAgentBasedHosts()) { + final Long dcId = host.getDataCenterId(); + if (!dcOrderedHostsMap.containsKey(dcId)) { + dcOrderedHostsMap.put(dcId, getOrderedHostIdList(dcId)); + } + final List msList = getManagementServerList(host.getId(), host.getDataCenterId(), dcOrderedHostsMap.get(dcId)); + final Long lbCheckInterval = getLBPreferredHostCheckInterval(host.getClusterId()); + final SetupMSListCommand cmd = new SetupMSListCommand(msList, lbAlgorithm, lbCheckInterval); + final Answer answer = agentManager.easySend(host.getId(), cmd); + if (answer == null || !answer.getResult()) { + LOG.warn("Failed to setup management servers list to the agent of host id=" + host.getId()); + } + } + } + + private void configureMessageBusListener() { + messageBus.subscribe(EventTypes.EVENT_CONFIGURATION_VALUE_EDIT, new MessageSubscriber() { + @Override + public void onPublishMessage(final String senderAddress, String subject, Object args) { + final String globalSettingUpdated = (String) args; + if (Strings.isNullOrEmpty(globalSettingUpdated)) { + return; + } + if (globalSettingUpdated.equals(ApiServiceConfiguration.ManagementServerAddresses.key()) || + globalSettingUpdated.equals(IndirectAgentLBAlgorithm.key())) { + propagateMSListToAgents(); + } + } + }); + } + + private void configureAlgorithmMap() { + final List algorithms = new ArrayList<>(); + algorithms.add(new IndirectAgentLBStaticAlgorithm()); + algorithms.add(new IndirectAgentLBRoundRobinAlgorithm()); + algorithms.add(new IndirectAgentLBShuffleAlgorithm()); + algorithmMap.clear(); + for (org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm algorithm : algorithms) { + algorithmMap.put(algorithm.getName(), algorithm); + } + } + + @Override + public boolean configure(final String name, final Map params) throws ConfigurationException { + super.configure(name, params); + configureAlgorithmMap(); + configureMessageBusListener(); + return true; + } + + @Override + public String getConfigComponentName() { + return IndirectAgentLBServiceImpl.class.getSimpleName(); + } + + @Override + public ConfigKey[] getConfigKeys() { + return new ConfigKey[] { + IndirectAgentLBAlgorithm, + IndirectAgentLBCheckInterval + }; + } +} \ No newline at end of file diff --git a/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithm.java b/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithm.java new file mode 100644 index 00000000000..0c9044a4c57 --- /dev/null +++ b/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithm.java @@ -0,0 +1,59 @@ +// 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.lb.algorithm; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm; + +public class IndirectAgentLBRoundRobinAlgorithm implements IndirectAgentLBAlgorithm { + + private int findRRPivotIndex(final List msList, final List orderedHostList, final Long hostId) { + return orderedHostList.indexOf(hostId) % msList.size(); + } + + @Override + public List sort(final List msList, final List orderedHostList, final Long hostId) { + if (msList.size() < 2) { + return msList; + } + + final List hostList = new ArrayList<>(orderedHostList); + Long searchId = hostId; + if (hostId == null) { + searchId = -1L; + hostList.add(searchId); + } + + final int pivotIndex = findRRPivotIndex(msList, hostList, searchId); + final List roundRobin = new ArrayList<>(msList.subList(pivotIndex, msList.size())); + roundRobin.addAll(msList.subList(0, pivotIndex)); + + return roundRobin; + } + + @Override + public String getName() { + return "roundrobin"; + } + + @Override + public boolean compare(final List msList, final List receivedMsList) { + return msList != null && receivedMsList != null && msList.equals(receivedMsList); + } +} \ No newline at end of file diff --git a/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithm.java b/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithm.java new file mode 100644 index 00000000000..ccc1bfc96dd --- /dev/null +++ b/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithm.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.lb.algorithm; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm; +import org.apache.commons.collections.SetUtils; + +public class IndirectAgentLBShuffleAlgorithm implements IndirectAgentLBAlgorithm { + + @Override + public List sort(final List msList, final List orderedHostList, final Long hostId) { + final List randomList = new ArrayList<>(msList); + Collections.shuffle(randomList, new Random(System.currentTimeMillis())); + return randomList; + } + + @Override + public String getName() { + return "shuffle"; + } + + @Override + public boolean compare(List msList, List receivedMsList) { + return SetUtils.isEqualSet(msList, receivedMsList); + } +} \ No newline at end of file diff --git a/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithm.java b/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithm.java new file mode 100644 index 00000000000..b30a01019c3 --- /dev/null +++ b/server/src/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithm.java @@ -0,0 +1,40 @@ +// 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.lb.algorithm; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm; + +public class IndirectAgentLBStaticAlgorithm implements IndirectAgentLBAlgorithm { + + @Override + public List sort(final List msList, final List orderedHostList, final Long hostId) { + return new ArrayList<>(msList); + } + + @Override + public String getName() { + return "static"; + } + + @Override + public boolean compare(final List msList, final List receivedMsList) { + return msList != null && receivedMsList != null && msList.equals(receivedMsList); + } +} \ No newline at end of file diff --git a/server/test/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImplTest.java b/server/test/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImplTest.java new file mode 100644 index 00000000000..79b5421f9af --- /dev/null +++ b/server/test/org/apache/cloudstack/agent/lb/IndirectAgentLBServiceImplTest.java @@ -0,0 +1,208 @@ +// 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.lb; + +import static org.mockito.Mockito.when; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import org.apache.cloudstack.config.ApiServiceConfiguration; +import org.apache.cloudstack.framework.config.ConfigKey; +import org.apache.cloudstack.framework.messagebus.MessageBus; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.Spy; + +import com.cloud.agent.AgentManager; +import com.cloud.host.Host; +import com.cloud.host.HostVO; +import com.cloud.host.dao.HostDao; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.resource.ResourceState; +import com.cloud.utils.exception.CloudRuntimeException; + +public class IndirectAgentLBServiceImplTest { + + @Mock + HostDao hostDao; + @Mock + MessageBus messageBus; + @Mock + AgentManager agentManager; + + @Mock + HostVO host1; + @Mock + HostVO host2; + @Mock + HostVO host3; + @Mock + HostVO host4; + + @Spy + @InjectMocks + private IndirectAgentLBServiceImpl agentMSLB = new IndirectAgentLBServiceImpl(); + + private final String msCSVList = "192.168.10.10, 192.168.10.11, 192.168.10.12"; + private final List 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 addField(final IndirectAgentLBServiceImpl provider, final String name, final Object o) throws IllegalAccessException, NoSuchFieldException { + Field f = IndirectAgentLBServiceImpl.class.getDeclaredField(name); + f.setAccessible(true); + f.set(provider, o); + } + + private void configureMocks() throws NoSuchFieldException, IllegalAccessException { + 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.getType()).thenReturn(Host.Type.Routing); + when(h.getRemoved()).thenReturn(null); + when(h.getResourceState()).thenReturn(ResourceState.Enabled); + id++; + } + addField(agentMSLB, "hostDao", hostDao); + addField(agentMSLB, "messageBus", messageBus); + addField(agentMSLB, "agentManager", agentManager); + + when(hostDao.listAll()).thenReturn(Arrays.asList(host4, host2, host1, host3)); + } + + @Before + public void setUp() throws Exception { + MockitoAnnotations.initMocks(this); + configureMocks(); + agentMSLB.configure("someName", null); + overrideDefaultConfigValue(ApiServiceConfiguration.ManagementServerAddresses, "_defaultValue", msCSVList); + } + + @Test + public void testStaticLBSetting() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(IndirectAgentLBServiceImpl.IndirectAgentLBAlgorithm, "_defaultValue", "static"); + for (HostVO host : Arrays.asList(host1, host2, host3, host4)) { + List listToSend = agentMSLB.getManagementServerList(host.getId(), host.getDataCenterId(), null); + Assert.assertEquals(msList, listToSend); + } + } + + @Test + public void testStaticLBSettingNullHostId() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(IndirectAgentLBServiceImpl.IndirectAgentLBAlgorithm, "_defaultValue", "static"); + List listToSend = agentMSLB.getManagementServerList(host2.getId(), host2.getDataCenterId(), null); + Assert.assertEquals(listToSend, agentMSLB.getManagementServerList(null, DC_1_ID, null)); + } + + private void testRoundRobinForExistingHosts(List list) { + for (HostVO hostVO : Arrays.asList(host1, host2, host3, host4)) { + List listToSend = agentMSLB.getManagementServerList(hostVO.getId(), hostVO.getDataCenterId(), null); + Assert.assertEquals(list, listToSend); + Assert.assertEquals(list.get(0), listToSend.get(0)); + list.add(list.get(0)); + list.remove(0); + } + } + + @Test + public void testRoundRobinDeterministicOrder() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(IndirectAgentLBServiceImpl.IndirectAgentLBAlgorithm, "_defaultValue", "roundrobin"); + List listHost2 = agentMSLB.getManagementServerList(host2.getId(), host2.getDataCenterId(), null); + Assert.assertEquals(listHost2, agentMSLB.getManagementServerList(host2.getId(), host2.getDataCenterId(), null)); + } + + @Test + public void testRoundRobinLBSettingConnectedAgents() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(IndirectAgentLBServiceImpl.IndirectAgentLBAlgorithm, "_defaultValue", "roundrobin"); + List list = new ArrayList<>(msList); + testRoundRobinForExistingHosts(list); + } + + @Test + public void testRoundRobinLBSettingNullHostId() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(IndirectAgentLBServiceImpl.IndirectAgentLBAlgorithm, "_defaultValue", "roundrobin"); + List list = new ArrayList<>(msList); + testRoundRobinForExistingHosts(list); + List listToSend = agentMSLB.getManagementServerList(null, DC_1_ID, null); + 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(IndirectAgentLBServiceImpl.IndirectAgentLBAlgorithm, "_defaultValue", "shuffle"); + List shuffleListHost2 = agentMSLB.getManagementServerList(host2.getId(), host2.getDataCenterId(), null); + Assert.assertEquals(new HashSet<>(msList), new HashSet<>(shuffleListHost2)); + } + + @Test + public void testShuffleLBSettingNullHostId() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(IndirectAgentLBServiceImpl.IndirectAgentLBAlgorithm, "_defaultValue", "shuffle"); + Assert.assertEquals(new HashSet<>(msList), new HashSet<>(agentMSLB.getManagementServerList(null, DC_1_ID, null))); + } + + @Test(expected = CloudRuntimeException.class) + public void testInvalidAlgorithmSetting() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(IndirectAgentLBServiceImpl.IndirectAgentLBAlgorithm, "_defaultValue", "invalid-algo"); + agentMSLB.getManagementServerList(host1.getId(), host1.getDataCenterId(), null); + } + + @Test(expected = CloudRuntimeException.class) + public void testExceptionOnEmptyHostSetting() throws NoSuchFieldException, IllegalAccessException { + overrideDefaultConfigValue(ApiServiceConfiguration.ManagementServerAddresses, "_defaultValue", ""); + // This should throw exception + agentMSLB.getManagementServerList(host1.getId(), host1.getDataCenterId(), null); + } + + @Test + public void testGetOrderedRunningHostIdsNullList() { + when(hostDao.listAll()).thenReturn(null); + Assert.assertTrue(agentMSLB.getOrderedHostIdList(DC_1_ID).size() == 0); + } + + @Test + public void testGetOrderedRunningHostIdsOrderList() { + when(hostDao.listAll()).thenReturn(Arrays.asList(host4, host2, host1, host3)); + Assert.assertEquals(Arrays.asList(host1.getId(), host2.getId(), host3.getId(), host4.getId()), + agentMSLB.getOrderedHostIdList(DC_1_ID)); + } + + @Test + public void testGetHostsPerZoneNullHosts() { + when(hostDao.listAll()).thenReturn(null); + Assert.assertTrue(agentMSLB.getOrderedHostIdList(DC_2_ID).size() == 0); + } +} \ No newline at end of file diff --git a/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java new file mode 100644 index 00000000000..dbd935662de --- /dev/null +++ b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java @@ -0,0 +1,60 @@ +package org.apache.cloudstack.agent.lb.algorithm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm; +import org.junit.Assert; +import org.junit.Test; + +public class IndirectAgentLBRoundRobinAlgorithmTest { + private IndirectAgentLBAlgorithm algorithm = new IndirectAgentLBRoundRobinAlgorithm(); + + private List msList = Arrays.asList("10.1.1.1", "10.1.1.2", "10.1.1.3"); + private List hostList = new ArrayList<>(Arrays.asList(1L, 5L, 10L, 20L, 50L, 60L, 70L, 80L)); + + @Test + public void testGetMSListForNewHost() throws Exception { + List startList = algorithm.sort(msList, hostList, null); + Assert.assertNotEquals(msList, startList); + Assert.assertFalse(algorithm.compare(msList, startList)); + + hostList.add(100L); + List nextList = algorithm.sort(msList, hostList, null); + List expectedList = startList.subList(1, startList.size()); + expectedList.addAll(startList.subList(0, 1)); + Assert.assertEquals(nextList, expectedList); + } + + @Test + public void testGetMSListForExistingHost() throws Exception { + List startList = new ArrayList<>(msList); + for (Long hostId : hostList.subList(1, hostList.size())) { + List nextList = new ArrayList<>(startList.subList(1, msList.size())); + nextList.addAll(startList.subList(0, 1)); + List expectedList = algorithm.sort(msList, hostList, hostId); + Assert.assertEquals(expectedList, nextList); + startList = nextList; + } + } + + @Test + public void testName() throws Exception { + Assert.assertEquals(algorithm.getName(), "roundrobin"); + } + + @Test + public void testListComparison() throws Exception { + Assert.assertTrue(algorithm.compare(Collections.singletonList("10.1.1.1"), Collections.singletonList("10.1.1.1"))); + Assert.assertTrue(algorithm.compare(Arrays.asList("10.1.1.2", "10.1.1.1"), Arrays.asList("10.1.1.2", "10.1.1.1"))); + Assert.assertTrue(algorithm.compare(msList, Arrays.asList("10.1.1.1", "10.1.1.2", "10.1.1.3"))); + + Assert.assertFalse(algorithm.compare(msList, Arrays.asList("10.1.1.3", "10.1.1.2", "10.1.1.1"))); + Assert.assertFalse(algorithm.compare(msList, Arrays.asList("10.1.1.0", "10.2.2.2"))); + Assert.assertFalse(algorithm.compare(msList, new ArrayList())); + Assert.assertFalse(algorithm.compare(msList, null)); + } + +} \ No newline at end of file diff --git a/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java new file mode 100644 index 00000000000..82fa3767505 --- /dev/null +++ b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java @@ -0,0 +1,44 @@ +package org.apache.cloudstack.agent.lb.algorithm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm; +import org.junit.Assert; +import org.junit.Test; + +public class IndirectAgentLBShuffleAlgorithmTest { + private IndirectAgentLBAlgorithm algorithm = new IndirectAgentLBShuffleAlgorithm(); + + private List msList = Arrays.asList("10.1.1.1", "10.1.1.2", "10.1.1.3"); + + @Test + public void testGetMSList() throws Exception { + for (int i = 0; i < 100; i++) { + final List newList = algorithm.sort(msList, null, null); + if (!msList.equals(newList)) { + return; + } + Thread.sleep(10); + } + Assert.fail("Shuffle failed to produce a randomly sorted management server list"); + } + + @Test + public void testName() throws Exception { + Assert.assertEquals(algorithm.getName(), "shuffle"); + } + + @Test + public void testListComparison() throws Exception { + Assert.assertTrue(algorithm.compare(Collections.singletonList("10.1.1.1"), Collections.singletonList("10.1.1.1"))); + Assert.assertTrue(algorithm.compare(msList, Arrays.asList("10.1.1.1", "10.1.1.2", "10.1.1.3"))); + Assert.assertTrue(algorithm.compare(msList, Arrays.asList("10.1.1.3", "10.1.1.2", "10.1.1.1"))); + + Assert.assertFalse(algorithm.compare(msList, Arrays.asList("10.1.1.0", "10.2.2.2"))); + Assert.assertFalse(algorithm.compare(msList, new ArrayList())); + Assert.assertFalse(algorithm.compare(msList, null)); + } +} \ No newline at end of file diff --git a/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java new file mode 100644 index 00000000000..adaa9071d24 --- /dev/null +++ b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java @@ -0,0 +1,33 @@ +package org.apache.cloudstack.agent.lb.algorithm; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.cloudstack.agent.lb.IndirectAgentLBAlgorithm; +import org.junit.Assert; +import org.junit.Test; + +public class IndirectAgentLBStaticAlgorithmTest { + private IndirectAgentLBAlgorithm algorithm = new IndirectAgentLBStaticAlgorithm(); + + private List msList = Arrays.asList("10.1.1.1", "10.1.1.2", "10.1.1.3"); + + @Test + public void testGetMSList() throws Exception { + Assert.assertEquals(msList, algorithm.sort(msList, null, null)); + } + + @Test + public void testName() throws Exception { + Assert.assertEquals(algorithm.getName(), "static"); + } + + @Test + public void testListComparison() throws Exception { + Assert.assertTrue(algorithm.compare(msList, Arrays.asList("10.1.1.1", "10.1.1.2", "10.1.1.3"))); + Assert.assertFalse(algorithm.compare(msList, Arrays.asList("10.1.1.0", "10.2.2.2"))); + Assert.assertFalse(algorithm.compare(msList, new ArrayList())); + Assert.assertFalse(algorithm.compare(msList, null)); + } +} \ No newline at end of file diff --git a/server/test/resources/createNetworkOffering.xml b/server/test/resources/createNetworkOffering.xml index 13363675823..b20be49d697 100644 --- a/server/test/resources/createNetworkOffering.xml +++ b/server/test/resources/createNetworkOffering.xml @@ -51,5 +51,6 @@ - + + diff --git a/server/test/resources/testContext.xml b/server/test/resources/testContext.xml index 6a211981c6d..18899276bfc 100644 --- a/server/test/resources/testContext.xml +++ b/server/test/resources/testContext.xml @@ -81,7 +81,8 @@ + - + diff --git a/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java b/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java index 56d2c6aed5b..135980064c1 100755 --- a/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java +++ b/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java @@ -31,9 +31,7 @@ import javax.ejb.Local; import javax.inject.Inject; import javax.naming.ConfigurationException; -import com.cloud.configuration.ConfigurationManagerImpl; -import org.apache.cloudstack.config.ApiServiceConfiguration; - +import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.NetworkOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.DataStore; @@ -65,6 +63,7 @@ import com.cloud.agent.manager.Commands; import com.cloud.capacity.dao.CapacityDao; import com.cloud.cluster.ClusterManager; import com.cloud.configuration.Config; +import com.cloud.configuration.ConfigurationManagerImpl; import com.cloud.configuration.ZoneConfig; import com.cloud.consoleproxy.ConsoleProxyManager; import com.cloud.dc.DataCenter; @@ -88,12 +87,12 @@ import com.cloud.info.RunningHostInfoAgregator.ZoneHostInfo; 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; @@ -103,6 +102,7 @@ import com.cloud.resource.ServerResource; import com.cloud.resource.UnableDeleteHostException; import com.cloud.service.ServiceOfferingVO; import com.cloud.service.dao.ServiceOfferingDao; +import com.cloud.storage.Storage; import com.cloud.storage.UploadVO; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.SnapshotDao; @@ -113,7 +113,6 @@ import com.cloud.storage.secondary.SecStorageVmAlertEventArgs; import com.cloud.storage.secondary.SecondaryStorageListener; import com.cloud.storage.secondary.SecondaryStorageVmAllocator; import com.cloud.storage.secondary.SecondaryStorageVmManager; -import com.cloud.storage.Storage; import com.cloud.storage.template.TemplateConstants; import com.cloud.template.TemplateManager; import com.cloud.user.Account; @@ -243,6 +242,9 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar TemplateDataStoreDao _tmplStoreDao; @Inject VolumeDataStoreDao _volumeStoreDao; + @Inject + private IndirectAgentLB indirectAgentLB; + private long _capacityScanInterval = DEFAULT_CAPACITY_SCAN_INTERVAL; private int _secStorageVmMtuSize; @@ -1059,7 +1061,7 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar 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(indirectAgentLB.getManagementServerList(dest.getHost().getId(), dest.getDataCenter().getId(), null))); buf.append(" port=").append(_mgmtPort); buf.append(" name=").append(profile.getVirtualMachine().getHostName()); diff --git a/utils/src/com/cloud/utils/StringUtils.java b/utils/src/com/cloud/utils/StringUtils.java index 9ee73233d34..4d193120dd0 100644 --- a/utils/src/com/cloud/utils/StringUtils.java +++ b/utils/src/com/cloud/utils/StringUtils.java @@ -21,12 +21,10 @@ package com.cloud.utils; 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 class StringUtils { return listOfChunks; } - public static String shuffleCSVList(final String csvList) { - List list = csvTagsToList(csvList); - Collections.shuffle(list, new Random(System.nanoTime())); + public static String toCSVList(final List list) { return join(list, ","); } } diff --git a/utils/test/com/cloud/utils/StringUtilsTest.java b/utils/test/com/cloud/utils/StringUtilsTest.java index 37dbad60c35..17548f8f93f 100644 --- a/utils/test/com/cloud/utils/StringUtilsTest.java +++ b/utils/test/com/cloud/utils/StringUtilsTest.java @@ -20,9 +20,11 @@ package com.cloud.utils; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import junit.framework.Assert; @@ -232,13 +234,16 @@ public class StringUtilsTest { } @Test - public void testShuffleCSVList() { + public void testCSVList() { String input = "one,two,three,four,five,six,seven,eight,nine,ten"; - String output = StringUtils.shuffleCSVList(input); - Assert.assertFalse(input.equals(output)); + List list = Arrays.asList(input.split(",")); + assertTrue(input.equals(StringUtils.toCSVList(list))); + } - input = "only-one"; - output = StringUtils.shuffleCSVList("only-one"); - Assert.assertTrue(input.equals(output)); + @Test + public void testCSVListWithOneItem() { + String input = "singleitem"; + List list = Arrays.asList(input.split(",")); + assertTrue(input.equals(StringUtils.toCSVList(list))); } }