Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

custom load balancer declarative configuration #17482

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Expand Up @@ -131,6 +131,13 @@ protected BeanDefinitionBuilder createBeanBuilder(Class clazz) {
return builder;
}

protected BeanDefinitionBuilder createBeanBuilder(String className) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(className);
builder.setScope(configBuilder.getBeanDefinition().getScope());
builder.setLazyInit(configBuilder.getBeanDefinition().isLazyInit());
return builder;
}

protected BeanDefinitionBuilder createAndFillBeanBuilder(Node node, Class clazz, String propertyName,
BeanDefinitionBuilder parent, String... exceptPropertyNames) {
BeanDefinitionBuilder builder = createBeanBuilder(clazz);
Expand Down
Expand Up @@ -348,6 +348,20 @@ private void handleLoadBalancer(Node node) {
configBuilder.addPropertyValue("loadBalancer", new RandomLB());
} else if ("round-robin".equals(type)) {
configBuilder.addPropertyValue("loadBalancer", new RoundRobinLB());
} else if ("custom".equals(type)) {
NamedNodeMap attributes = node.getAttributes();
Node classNameNode = attributes.getNamedItem("class-name");
String className = classNameNode != null ? getTextContent(classNameNode) : null;
Node implNode = attributes.getNamedItem("implementation");
String implementation = implNode != null ? getTextContent(implNode) : null;
isTrue(className != null ^ implementation != null, "Exactly one of 'class-name' or 'implementation'"
+ " attributes is required to create LoadBalancer!");
if (className != null) {
BeanDefinitionBuilder loadBalancerBeanDefinition = createBeanBuilder(className);
configBuilder.addPropertyValue("loadBalancer", loadBalancerBeanDefinition.getBeanDefinition());
} else {
configBuilder.addPropertyReference("loadBalancer", implementation);
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions hazelcast-spring/src/main/resources/hazelcast-spring-4.0.xsd
Expand Up @@ -3090,9 +3090,11 @@
<xs:restriction base="non-space-string">
<xs:enumeration value="random"/>
<xs:enumeration value="round-robin"/>
<xs:enumeration value="custom"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attributeGroup ref="class-or-bean-name"/>
</xs:complexType>
<xs:complexType name="near-cache-client">
<xs:complexContent>
Expand Down
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hazelcast.spring;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class InvalidApplicationContextTest {

@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void shouldFailWhenLoadBalancerContainsClassNameAndImplementationAttribute() {
expectedException.expect(BeanDefinitionStoreException.class);
expectedException.expectMessage("Unexpected exception parsing XML document from class path resource [com/hazelcast/spring/"
+ "customLoadBalancer-invalidApplicationContext.xml]; nested exception is "
+ "java.lang.IllegalArgumentException: Exactly one of 'class-name' or 'implementation' attributes is "
+ "required to create LoadBalancer!");

new ClassPathXmlApplicationContext("com\\hazelcast\\spring\\customLoadBalancer-invalidApplicationContext.xml");
}
}
@@ -0,0 +1,68 @@
/*
* Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.hazelcast.spring;

import com.hazelcast.client.HazelcastClient;
import com.hazelcast.client.LoadBalancer;
import com.hazelcast.client.config.ClientConfig;
import com.hazelcast.client.impl.clientside.HazelcastClientProxy;
import com.hazelcast.client.test.CustomLoadBalancer;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.test.annotation.QuickTest;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.experimental.categories.Category;
import org.junit.runner.RunWith;
import org.springframework.test.context.ContextConfiguration;

import javax.annotation.Resource;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

@RunWith(CustomSpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"customLoadBalancer-applicationContext.xml"})
@Category(QuickTest.class)
public class TestCustomLoadBalancerContext {

@Resource(name = "client1")
private HazelcastClientProxy client1;

@Resource(name = "client2")
private HazelcastClientProxy client2;

@BeforeClass
@AfterClass
public static void start() {
HazelcastClient.shutdownAll();
Hazelcast.shutdownAll();
}

@Test
public void testCustomLoadBalancer() {
ClientConfig config1 = client1.getClientConfig();
LoadBalancer loadBalancer1 = config1.getLoadBalancer();
assertTrue(loadBalancer1 instanceof CustomLoadBalancer);
assertEquals("default-name", ((CustomLoadBalancer) loadBalancer1).getName());

ClientConfig config2 = client2.getClientConfig();
LoadBalancer loadBalancer2 = config2.getLoadBalancer();
assertTrue(loadBalancer2 instanceof CustomLoadBalancer);
assertEquals("custom-balancer-name", ((CustomLoadBalancer) loadBalancer2).getName());
}
}
@@ -0,0 +1,68 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
~
~ Licensed 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.
-->

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:hz="http://www.hazelcast.com/schema/spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.hazelcast.com/schema/spring
http://www.hazelcast.com/schema/spring/hazelcast-spring-4.0.xsd">

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"
p:systemPropertiesModeName="SYSTEM_PROPERTIES_MODE_OVERRIDE">
<property name="locations">
<list>
<value>classpath:/hazelcast-default.properties</value>
</list>
</property>
</bean>

<hz:hazelcast id="instance">
<hz:config>
<hz:network port="${cluster.port}" port-auto-increment="false">
<hz:join>
<hz:multicast enabled="false" />
<hz:tcp-ip enabled="true">
<hz:members>${cluster.members}</hz:members>
</hz:tcp-ip>
</hz:join>
</hz:network>
</hz:config>
</hz:hazelcast>

<hz:client id="client1">
<hz:network connection-timeout="1000" smart-routing="false">
<hz:member>127.0.0.1:5700</hz:member>
<hz:member>127.0.0.1:5701</hz:member>
</hz:network>
<hz:load-balancer type="custom" class-name="com.hazelcast.client.test.CustomLoadBalancer"/>
</hz:client>

<hz:client id="client2">
<hz:network connection-timeout="1000" smart-routing="false">
<hz:member>127.0.0.1:5700</hz:member>
<hz:member>127.0.0.1:5701</hz:member>
</hz:network>
<hz:load-balancer type="custom" implementation="customLoadBalancer"/>
</hz:client>

<bean id="customLoadBalancer" class="com.hazelcast.client.test.CustomLoadBalancer">
<property name="name" value="custom-balancer-name"/>
</bean>
</beans>
@@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2008-2020, Hazelcast, Inc. All Rights Reserved.
~
~ Licensed 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.
-->

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:hz="http://www.hazelcast.com/schema/spring"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.hazelcast.com/schema/spring
http://www.hazelcast.com/schema/spring/hazelcast-spring-4.0.xsd">

<hz:client id="client">
<hz:network connection-timeout="1000" smart-routing="false">
<hz:member>127.0.0.1:5700</hz:member>
<hz:member>127.0.0.1:5701</hz:member>
</hz:network>
<hz:load-balancer type="custom" class-name="com.hazelcast.client.test.CustomLoadBalancer"
implementation="customLoadBalancer"/>
</hz:client>

<bean id="customLoadBalancer" class="com.hazelcast.client.test.CustomLoadBalancer">
<property name="name" value="custom-balancer-name"/>
</bean>
</beans>
Expand Up @@ -53,6 +53,7 @@
import static com.hazelcast.internal.config.DeclarativeConfigUtil.SYSPROP_CLIENT_CONFIG;
import static com.hazelcast.internal.config.DeclarativeConfigUtil.validateSuffixInSystemProperty;
import static com.hazelcast.internal.util.Preconditions.checkFalse;
import static com.hazelcast.internal.util.Preconditions.checkHasText;
import static com.hazelcast.internal.util.Preconditions.isNotNull;
import static com.hazelcast.partition.strategy.StringPartitioningStrategy.getBaseName;

Expand Down Expand Up @@ -84,6 +85,11 @@ public class ClientConfig {
*/
private LoadBalancer loadBalancer;

/**
* Load balancer class name. Used internally with declarative configuration.
*/
private String loadBalancerClassName;

/**
* List of listeners that Hazelcast will automatically add as a part of initialization process.
* Currently only supports {@link com.hazelcast.core.LifecycleListener}.
Expand Down Expand Up @@ -128,6 +134,7 @@ public ClientConfig(ClientConfig config) {
securityConfig = new ClientSecurityConfig(config.securityConfig);
networkConfig = new ClientNetworkConfig(config.networkConfig);
loadBalancer = config.loadBalancer;
loadBalancerClassName = config.loadBalancerClassName;
listenerConfigs = new LinkedList<>();
for (ListenerConfig listenerConfig : config.listenerConfigs) {
listenerConfigs.add(new ListenerConfig(listenerConfig));
Expand Down Expand Up @@ -599,17 +606,46 @@ public LoadBalancer getLoadBalancer() {
}

/**
* Sets the {@link LoadBalancer}
* Sets the {@link LoadBalancer}.
* <p>
* If a load balancer class name was set, it will be removed.
*
* @param loadBalancer {@link LoadBalancer}
* @return configured {@link com.hazelcast.client.config.ClientConfig} for chaining
* @see com.hazelcast.client.LoadBalancer
*/
public ClientConfig setLoadBalancer(LoadBalancer loadBalancer) {
this.loadBalancer = loadBalancer;
this.loadBalancerClassName = null;
return this;
}

/**
* Gets load balancer class name
*
* @return load balancer class name
* @see com.hazelcast.client.LoadBalancer
*/
public String getLoadBalancerClassName() {
return loadBalancerClassName;
}

/**
* Sets load balancer class name.
* <p>
* If a load balancer implementation was set, it will be removed.
*
* @param loadBalancerClassName {@link LoadBalancer}
* @return configured {@link com.hazelcast.client.config.ClientConfig} for chaining
* @see com.hazelcast.client.LoadBalancer
*/
public ClientConfig setLoadBalancerClassName(@Nonnull String loadBalancerClassName) {
this.loadBalancerClassName = checkHasText(loadBalancerClassName, "Load balancer class name must contain text");
this.loadBalancer = null;
return this;
}


/**
* Gets the classLoader
*
Expand Down Expand Up @@ -923,7 +959,7 @@ public ClientConfig setMetricsConfig(@Nonnull ClientMetricsConfig metricsConfig)
@Override
public int hashCode() {
return Objects.hash(backupAckToClientEnabled, classLoader, clusterName, configPatternMatcher, connectionStrategyConfig,
flakeIdGeneratorConfigMap, instanceName, labels, listenerConfigs, loadBalancer,
flakeIdGeneratorConfigMap, instanceName, labels, listenerConfigs, loadBalancer, loadBalancerClassName,
managedContext, metricsConfig, nativeMemoryConfig, nearCacheConfigMap, networkConfig, properties,
proxyFactoryConfigs, queryCacheConfigs, reliableTopicConfigMap, securityConfig, serializationConfig,
userCodeDeploymentConfig, userContext);
Expand All @@ -949,6 +985,7 @@ public boolean equals(Object obj) {
&& Objects.equals(flakeIdGeneratorConfigMap, other.flakeIdGeneratorConfigMap)
&& Objects.equals(instanceName, other.instanceName) && Objects.equals(labels, other.labels)
&& Objects.equals(listenerConfigs, other.listenerConfigs) && Objects.equals(loadBalancer, other.loadBalancer)
&& Objects.equals(loadBalancerClassName, other.loadBalancerClassName)
&& Objects.equals(managedContext, other.managedContext) && Objects.equals(metricsConfig, other.metricsConfig)
&& Objects.equals(nativeMemoryConfig, other.nativeMemoryConfig)
&& Objects.equals(nearCacheConfigMap, other.nearCacheConfigMap)
Expand All @@ -970,6 +1007,7 @@ public String toString() {
+ ", securityConfig=" + securityConfig
+ ", networkConfig=" + networkConfig
+ ", loadBalancer=" + loadBalancer
+ ", loadBalancerClassName=" + loadBalancerClassName
+ ", listenerConfigs=" + listenerConfigs
+ ", instanceName='" + instanceName + '\''
+ ", configPatternMatcher=" + configPatternMatcher
Expand Down
Expand Up @@ -345,9 +345,14 @@ private static void loadBalancer(XmlGenerator gen, LoadBalancer loadBalancer) {
} else if (loadBalancer instanceof RoundRobinLB) {
type = "round-robin";
} else {
throw new IllegalArgumentException("Unknown load-balancer type: " + loadBalancer);
type = "custom";
}

if ("custom".equals(type)) {
gen.node("load-balancer", loadBalancer.getClass().getName(), "type", type);
} else {
gen.node("load-balancer", null, "type", type);
}
gen.node("load-balancer", null, "type", type);
}

private static void nearCaches(XmlGenerator gen, Map<String, NearCacheConfig> nearCacheMap) {
Expand Down