diff --git a/agent/conf/agent.properties b/agent/conf/agent.properties index 0354aceadab..ad35b96bab6 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 1c5417bf767..32112540c1c 100644 --- 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; @@ -38,12 +40,15 @@ import java.util.concurrent.atomic.AtomicInteger; import javax.naming.ConfigurationException; import org.apache.cloudstack.agent.directdownload.SetupDirectDownloadCertificate; +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; @@ -65,6 +70,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; @@ -121,6 +127,7 @@ public class Agent implements HandlerFactory, IAgentControl { Long _id; Timer _timer = new Timer("Agent Timer"); + Timer hostLBTimer; List _watchList = new ArrayList(); long _sequence = 0; @@ -144,7 +151,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)); @@ -179,7 +186,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()); @@ -255,7 +262,7 @@ 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.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,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) { @@ -310,6 +318,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()) { @@ -332,8 +351,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); } @@ -341,9 +360,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); @@ -402,19 +423,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) { @@ -434,7 +459,7 @@ public class Agent implements HandlerFactory, IAgentControl { _shell.getBackoffAlgorithm().waitBeforeRetry(); } - final String host = _shell.getHost(); + final String host = _shell.getNextHost(); do { _connection = new NioClient("Agent", host, _shell.getPort(), _shell.getWorkers(), this); s_logger.info("Reconnecting to host:" + host); @@ -452,7 +477,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) { @@ -554,6 +580,8 @@ public class Agent implements HandlerFactory, IAgentControl { answer = setupAgentCertificate((SetupCertificateCommand) cmd); } else if (cmd instanceof SetupDirectDownloadCertificate) { answer = setupDirectDownloadCertificate((SetupDirectDownloadCertificate) cmd); + } else if (cmd instanceof SetupMSListCommand) { + answer = setupManagementServerList((SetupMSListCommand) cmd); } else { if (cmd instanceof ReadyCommand) { processReadyCommand(cmd); @@ -708,6 +736,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()) { @@ -728,15 +780,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) { @@ -1018,4 +1071,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 5950bc78e61..13b6c65a351 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,18 +111,54 @@ public class AgentShell implements IAgentShell, Daemon { } @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 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 resetHostCounter() { + _hostCounter = 0; + } + + @Override + public String[] getHosts() { + return _host.split(","); + } + + @Override + public void setHosts(final String host) { + if (!Strings.isNullOrEmpty(host)) { + _host = host.split(hostLbAlgorithmSeparator)[0]; + resetHostCounter(); + } } @Override @@ -251,7 +291,8 @@ public class AgentShell implements IAgentShell, Daemon { if (host == null) { host = "localhost"; } - _host = host; + + setHosts(host); if (zone != null) _zone = zone; @@ -291,6 +332,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 8ceba4531d1..868293c8977 100644 --- a/agent/test/com/cloud/agent/AgentShellTest.java +++ b/agent/test/com/cloud/agent/AgentShellTest.java @@ -35,7 +35,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()); @@ -53,10 +53,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 485688a3769..55e8202ff48 100644 --- a/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java +++ b/api/src/org/apache/cloudstack/config/ApiServiceConfiguration.java @@ -20,7 +20,7 @@ import org.apache.cloudstack.framework.config.ConfigKey; import org.apache.cloudstack.framework.config.Configurable; 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. This can also accept comma separated addresses.", 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, ApiSourceCidrChecksEnabled, ApiAllowedSourceCidrList}; + return new ConfigKey[] {ManagementServerAddresses, ApiServletPath, DefaultUIPageSize, ApiSourceCidrChecksEnabled, ApiAllowedSourceCidrList}; } } diff --git a/client/pom.xml b/client/pom.xml index ba3ba5d983f..9907d8cc2ac 100644 --- a/client/pom.xml +++ b/client/pom.xml @@ -313,6 +313,11 @@ cloud-mom-kafka ${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 100644 --- 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 6ad6806a5d8..5bc8697f28f 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} + org.apache.cloudstack cloud-server diff --git a/engine/orchestration/src/com/cloud/agent/manager/AgentManagerImpl.java b/engine/orchestration/src/com/cloud/agent/manager/AgentManagerImpl.java index b7357756c4c..9626385c0fd 100644 --- a/engine/orchestration/src/com/cloud/agent/manager/AgentManagerImpl.java +++ b/engine/orchestration/src/com/cloud/agent/manager/AgentManagerImpl.java @@ -20,6 +20,7 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.channels.ClosedChannelException; import java.util.ArrayList; +import java.util.Arrays; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -37,6 +38,7 @@ import java.util.concurrent.locks.ReentrantLock; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.agent.lb.IndirectAgentLB; import org.apache.cloudstack.ca.CAManager; import com.cloud.configuration.ManagementServiceConfiguration; import org.apache.cloudstack.framework.config.ConfigKey; @@ -116,6 +118,7 @@ import com.cloud.utils.nio.Link; import com.cloud.utils.nio.NioServer; import com.cloud.utils.nio.Task; import com.cloud.utils.time.InaccurateClock; +import com.google.common.base.Strings; /** * Implementation of the Agent Manager. This class controls the connection to the agents. @@ -163,6 +166,9 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl @Inject protected HypervisorGuruManager _hvGuruMgr; + @Inject + protected IndirectAgentLB indirectAgentLB; + protected int _retry = 2; protected long _nodeId = -1; @@ -1081,14 +1087,31 @@ public class AgentManagerImpl extends ManagerBase implements AgentManager, Handl AgentAttache attache = null; ReadyCommand ready = null; try { + final List agentMSHostList = new ArrayList<>(); + if (startup != null && startup.length > 0) { + final String agentMSHosts = startup[0].getMsHostList(); + if (!Strings.isNullOrEmpty(agentMSHosts)) { + agentMSHostList.addAll(Arrays.asList(agentMSHosts.split(","))); + } + } + final HostVO host = _resourceMgr.createHostVOForConnectedAgent(startup); if (host != null) { ready = new ReadyCommand(host.getDataCenterId(), host.getId()); + + if (!indirectAgentLB.compareManagementServerList(host.getId(), host.getDataCenterId(), agentMSHostList)) { + final List newMSList = indirectAgentLB.getManagementServerList(host.getId(), host.getDataCenterId(), null); + ready.setMsHostList(newMSList); + ready.setLbAlgorithm(indirectAgentLB.getLBAlgorithmName()); + ready.setLbCheckInterval(indirectAgentLB.getLBPreferredHostCheckInterval(host.getClusterId())); + s_logger.debug("Agent's management server host list is not up to date, sending list update:" + newMSList); + } + attache = createAttacheForConnect(host, link); attache = notifyMonitorsOfConnection(attache, startup, false); } } catch (final Exception e) { - s_logger.debug("Failed to handle host connection: " + e.toString()); + s_logger.debug("Failed to handle host connection: ", e); ready = new ReadyCommand(null); ready.setDetails(e.toString()); } finally { diff --git a/framework/agent-lb/pom.xml b/framework/agent-lb/pom.xml new file mode 100644 index 00000000000..7d14a3a4a7e --- /dev/null +++ b/framework/agent-lb/pom.xml @@ -0,0 +1,32 @@ + + + 4.0.0 + Apache CloudStack Agent Management Servers Load Balancer + cloud-framework-agent-lb + + cloudstack-framework + org.apache.cloudstack + 4.11.1.0-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 b09acd34823..4ac14230cc8 100644 --- a/framework/pom.xml +++ b/framework/pom.xml @@ -55,7 +55,8 @@ managed-context spring/lifecycle spring/module - security + security + agent-lb direct-download 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 d9175548649..78e04b5dc53 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 @@ -447,7 +447,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/server/pom.xml b/server/pom.xml index fa3f75cf95c..534b1a34c70 100644 --- a/server/pom.xml +++ b/server/pom.xml @@ -136,6 +136,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 7d19b1e2f46..c7715a866c4 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 @@ -295,5 +295,7 @@ + + diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 1632da95f95..80642f51375 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -76,6 +76,8 @@ import org.apache.cloudstack.framework.config.ConfigKey; 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 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati ImageStoreDao _imageStoreDao; @Inject ImageStoreDetailsDao _imageStoreDetailsDao; + @Inject + MessageBus messageBus; // FIXME - why don't we have interface for DataCenterLinkLocalIpAddressDao? @@ -660,6 +664,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 28fff3c7219..f131714ecaf 100644 --- a/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java +++ b/server/src/com/cloud/consoleproxy/ConsoleProxyManagerImpl.java @@ -29,7 +29,7 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; -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; @@ -211,6 +211,8 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy private KeysManager _keysMgr; @Inject private VirtualMachineManager _itMgr; + @Inject + private IndirectAgentLB indirectAgentLB; private ConsoleProxyListener _listener; @@ -1355,7 +1357,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 c1afc9a6f88..ef69fdcb436 100644 --- a/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java +++ b/server/src/com/cloud/hypervisor/kvm/discoverer/LibvirtServerDiscoverer.java @@ -27,9 +27,9 @@ import java.util.UUID; import javax.inject.Inject; import javax.naming.ConfigurationException; +import org.apache.cloudstack.agent.lb.IndirectAgentLB; 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; @@ -76,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(); @@ -288,7 +290,7 @@ public abstract class LibvirtServerDiscoverer extends DiscovererBase implements 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(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 f3035d05e61..1985deaefa8 100644 --- a/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java +++ b/server/src/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java @@ -1373,7 +1373,7 @@ 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..c38f7332ac9 --- /dev/null +++ b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBRoundRobinAlgorithmTest.java @@ -0,0 +1,76 @@ +// 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.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..59894b33fc2 --- /dev/null +++ b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBShuffleAlgorithmTest.java @@ -0,0 +1,60 @@ +// 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.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..7ba5c90b7dd --- /dev/null +++ b/server/test/org/apache/cloudstack/agent/lb/algorithm/IndirectAgentLBStaticAlgorithmTest.java @@ -0,0 +1,49 @@ +// 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.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 126e265682b..68ff007cdbf 100644 --- a/server/test/resources/createNetworkOffering.xml +++ b/server/test/resources/createNetworkOffering.xml @@ -54,4 +54,5 @@ + diff --git a/server/test/resources/testContext.xml b/server/test/resources/testContext.xml index e84244880e0..c267648da05 100644 --- a/server/test/resources/testContext.xml +++ b/server/test/resources/testContext.xml @@ -79,9 +79,10 @@ - - - + + + + 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 a158c9c0e68..66c436b15c3 100644 --- a/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java +++ b/services/secondary-storage/controller/src/org/apache/cloudstack/secondarystorage/SecondaryStorageManagerImpl.java @@ -30,7 +30,7 @@ import java.util.Map; import javax.inject.Inject; import javax.naming.ConfigurationException; -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; @@ -89,12 +89,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; @@ -246,6 +246,9 @@ public class SecondaryStorageManagerImpl extends ManagerBase implements Secondar VolumeDataStoreDao _volumeStoreDao; @Inject private ImageStoreDetailsUtil imageStoreDetailsUtil; + @Inject + private IndirectAgentLB indirectAgentLB; + private long _capacityScanInterval = DEFAULT_CAPACITY_SCAN_INTERVAL; private int _secStorageVmMtuSize; @@ -1119,7 +1122,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/main/java/com/cloud/utils/StringUtils.java b/utils/src/main/java/com/cloud/utils/StringUtils.java index 6ada2ad60bd..f7ef0bc75e7 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 @@ 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())); - return join(list, ","); + public static String toCSVList(final List csvList) { + return join(csvList, ","); } } diff --git a/utils/src/test/java/com/cloud/utils/StringUtilsTest.java b/utils/src/test/java/com/cloud/utils/StringUtilsTest.java index e8e62b0a75e..09a6c71bcf8 100644 --- a/utils/src/test/java/com/cloud/utils/StringUtilsTest.java +++ b/utils/src/test/java/com/cloud/utils/StringUtilsTest.java @@ -20,9 +20,8 @@ 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; @@ -254,13 +253,9 @@ public class StringUtilsTest { } @Test - public void testShuffleCSVList() { + public void testToCSVList() { String input = "one,two,three,four,five,six,seven,eight,nine,ten"; - String output = StringUtils.shuffleCSVList(input); - assertFalse(input.equals(output)); - - input = "only-one"; - output = StringUtils.shuffleCSVList("only-one"); + String output = StringUtils.toCSVList(Arrays.asList(input.split(","))); assertTrue(input.equals(output)); } }