Skip to content

Commit

Permalink
Configure Eureka Client without a properties file
Browse files Browse the repository at this point in the history
 - specify Eureka client properties directly in Hazelcast config
 - add new `use-classpath-eureka-client-props` property
 - update README.md
  • Loading branch information
googlielmo committed Feb 2, 2018
1 parent 3d727eb commit 8be84d8
Show file tree
Hide file tree
Showing 10 changed files with 861 additions and 32 deletions.
81 changes: 73 additions & 8 deletions README.md
Expand Up @@ -65,8 +65,8 @@ The following is an example declarative configuration.
```
* `self-registration`: Defines if the Discovery SPI plugin will register itself with the Eureka 1 service discovery.
It is optional. Default value is `true`.
* `namespace`: Definition for providing different namespaces in order to not to collide with other service registry clients
in eureka-client.properties file. It is optional. Default value is `hazelcast`.
* `namespace`: Definition for providing different namespaces in order not to collide with other service registry
clients in eureka-client.properties file. It is optional. Default value is `hazelcast`.

Below you can also find an example of Eureka client properties.

Expand All @@ -78,9 +78,42 @@ hazelcast.name=hazelcast-test
hazelcast.serviceUrl.default=http://<your-eureka-server-url>
```

> `IMPORTANT`: `hazelcast.name` property is crucial for cluster members to discover each other. Please give
> **IMPORTANT**: `hazelcast.name` property is crucial for cluster members to discover each other. Please give
identical names in regarding `eureka-client.properties` on EC2 hosts for building cluster of your choice properly.

#### Configuring Eureka Discovery without a properties file

In some environments adding the `eureka-client.properties` file to the classpath is not feasible.
To support this use case, it is possible to specify the Eureka client properties in the
Hazelcast configuration: Set the `use-classpath-eureka-client-props` property to `false`,
then add the Eureka client properties _without prepending the namespace_, as they will be applied
to the namespace specified with the `namespace` property.

**NOTE:** If `use-classpath-eureka-client-props` is `true` (its default value), all Eureka client properties
in the Hazelcast configuration will be ignored.

The following is an example declarative configuration, equivalent to the example given above.

```xml
<network>
...
<discovery-strategies>
<discovery-strategy class="com.hazelcast.eureka.one.EurekaOneDiscoveryStrategy" enabled="true">
<properties>
<property name="self-registration">true</property>
<property name="namespace">hazelcast</property>
<property name="use-classpath-eureka-client-props">false</property>
<property name="environment">prod</property>
<property name="shouldUseDns">false</property>
<property name="datacenter">cloud</property>
<property name="name">hazelcast-test</property>
<property name="serviceUrl.default">http://your-eureka-server-url</property>
</properties>
</discovery-strategy>
</discovery-strategies>
</network>
```

### Configuring Eureka Discovery for Hazelcast Client

- Add the *hazelcast-eureka-one.jar* dependency to your project.
Expand Down Expand Up @@ -111,10 +144,42 @@ hazelcast.name=hazelcast-test
hazelcast.serviceUrl.default=http://<your-eureka-server-url>/eureka/v2/
```

> `NOTE:` Hazelcast clients do not register themselves to Eureka server with given `namespace` or default namespace,
> **NOTE:** Hazelcast clients do not register themselves to Eureka server with given `namespace` or default namespace,
which is `hazelcast`. Therefore, `self-registration` property is overridden and it has no effect.

> `IMPORTANT`: `hazelcast.name` property is crucial for clients to discover cluster members.
> **IMPORTANT:** `hazelcast.name` property is crucial for clients to discover cluster members.
#### Configuring Eureka Discovery for Hazelcast Client without a properties file

In some environments adding the `eureka-client.properties` file to the Hazelcast Client classpath is not feasible.
To support this use case, it is possible to specify the Eureka client properties in the
Hazelcast Client configuration: Set the `use-classpath-eureka-client-props` property to `false`,
then add the Eureka client properties _without prepending the namespace_, as they will be applied
to the namespace specified with the `namespace` property.

**NOTE:** If `use-classpath-eureka-client-props` is `true` (its default value), all Eureka client properties
in the Hazelcast Client configuration will be ignored.

The following is an example declarative configuration, equivalent to the example given above.

```xml
<network>
...
<discovery-strategies>
<discovery-strategy class="com.hazelcast.eureka.one.EurekaOneDiscoveryStrategy" enabled="true">
<properties>
<property name="namespace">hazelcast</property>
<property name="use-classpath-eureka-client-props">false</property>
<property name="environment">prod</property>
<property name="shouldUseDns">false</property>
<property name="datacenter">cloud</property>
<property name="name">hazelcast-test</property>
<property name="serviceUrl.default">http://your-eureka-server-url/eureka/v2/</property>
</properties>
</discovery-strategy>
</discovery-strategies>
</network>
```

#### Reusing existing Eureka Client instance
If your application provides already configured `EurekaClient` instance e.g. if you are using Spring Cloud, you can reuse your existing client:
Expand All @@ -124,9 +189,9 @@ EurekaClient eurekaClient = ...
EurekaOneDiscoveryStrategyFactory.setEurekaClient(eurekaClient);
```

When using reused client as above, discovery implementation will **not** send Eureka Server any status changes regarding
application state. Also, if you need to inject `Eureka client` externally, you have to configure discovery programmatically
as shown above code snippet.
When using reused client as above, discovery implementation will **not** send Eureka Server any status changes regarding
application state. Also, if you need to inject `Eureka client` externally, you have to configure discovery
programmatically as shown above code snippet.

## Debugging

Expand Down
6 changes: 6 additions & 0 deletions checkstyle/suppressions.xml
Expand Up @@ -20,4 +20,10 @@
<!-- Exclude Clover instrumented sources -->
<suppress checks="" files="/src-instrumented/"/>

<!-- Exclude some checks for Eureka code -->
<suppress checks="MethodCount" files="com/hazelcast/eureka/one/PropertyBasedEurekaClientConfig.java" />
<suppress checks="MagicNumber" files="com/hazelcast/eureka/one/PropertyBasedEurekaClientConfig.java" />
<suppress checks="AvoidStarImport" files="com/hazelcast/eureka/one/PropertyBasedEurekaClientConfig.java" />
<suppress checks="AvoidStarImport" files="com/hazelcast/eureka/one/EurekaOneProperties.java" />

</suppressions>
5 changes: 5 additions & 0 deletions pom.xml
Expand Up @@ -155,6 +155,11 @@
<artifactId>guava</artifactId>
<version>${guava.version}</version>
</dependency>
<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
<version>1.8</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
Expand Down
Expand Up @@ -16,22 +16,20 @@

package com.hazelcast.eureka.one;

import static com.hazelcast.eureka.one.EurekaOneProperties.EUREKA_ONE_SYSTEM_PREFIX;
import static com.hazelcast.eureka.one.EurekaOneProperties.NAMESPACE;
import static com.hazelcast.eureka.one.EurekaOneProperties.SELF_REGISTRATION;

import java.io.IOException;
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.TimeUnit;

import com.google.common.annotations.VisibleForTesting;
import com.hazelcast.config.properties.PropertyDefinition;
import com.hazelcast.logging.ILogger;
import com.hazelcast.logging.NoLogFactory;
import com.hazelcast.nio.Address;
Expand All @@ -50,8 +48,15 @@
import com.netflix.discovery.DefaultEurekaClientConfig;
import com.netflix.discovery.DiscoveryClient;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.EurekaClientConfig;
import com.netflix.discovery.shared.Application;

import static com.hazelcast.eureka.one.EurekaOneProperties.EUREKA_ONE_SYSTEM_PREFIX;
import static com.hazelcast.eureka.one.EurekaOneProperties.HZ_PROPERTY_DEFINITIONS;
import static com.hazelcast.eureka.one.EurekaOneProperties.NAMESPACE;
import static com.hazelcast.eureka.one.EurekaOneProperties.SELF_REGISTRATION;
import static com.hazelcast.eureka.one.EurekaOneProperties.USE_CLASSPATH_EUREKA_CLIENT_PROPS;

final class EurekaOneDiscoveryStrategy
extends AbstractDiscoveryStrategy {

Expand Down Expand Up @@ -122,6 +127,7 @@ EurekaOneDiscoveryStrategy build() {
private final EurekaClient eurekaClient;
private final ApplicationInfoManager applicationInfoManager;

private final Boolean useClasspathEurekaClientProps;
private final String namespace;
private StatusChangeStrategy statusChangeStrategy;

Expand All @@ -130,6 +136,7 @@ private EurekaOneDiscoveryStrategy(final EurekaOneDiscoveryStrategyBuilder build

this.namespace = getOrDefault(EUREKA_ONE_SYSTEM_PREFIX, NAMESPACE, "hazelcast");
boolean selfRegistration = getOrDefault(EUREKA_ONE_SYSTEM_PREFIX, SELF_REGISTRATION, true);
this.useClasspathEurekaClientProps = getOrDefault(EUREKA_ONE_SYSTEM_PREFIX, USE_CLASSPATH_EUREKA_CLIENT_PROPS, true);
// override registration if requested
if (!selfRegistration) {
statusChangeStrategy = new NoopUpdater();
Expand All @@ -144,12 +151,31 @@ private EurekaOneDiscoveryStrategy(final EurekaOneDiscoveryStrategyBuilder build
}

if (builder.eurekaClient == null) {
this.eurekaClient = new DiscoveryClient(applicationInfoManager, new EurekaOneAwareConfig(this.namespace));
EurekaClientConfig eurekaClientConfig;
if (useClasspathEurekaClientProps) {
eurekaClientConfig = new EurekaOneAwareConfig(this.namespace);
} else {
eurekaClientConfig = new PropertyBasedEurekaClientConfig(
this.namespace,
getEurekaClientProperties(this.namespace, this.getProperties()));
}
this.eurekaClient = new DiscoveryClient(applicationInfoManager, eurekaClientConfig);
} else {
this.eurekaClient = builder.eurekaClient;
}
}

private Map<String, Object> getEurekaClientProperties(String namespace, Map<String, Comparable> properties) {
Map<String, Object> result = new HashMap<String, Object>();
for (Map.Entry<String, Comparable> e : properties.entrySet()) {
result.put(namespace + "." + e.getKey(), e.getValue());
}
for (PropertyDefinition p : HZ_PROPERTY_DEFINITIONS) {
result.remove(namespace + "." + p.key());
}
return result;
}

private ApplicationInfoManager initializeApplicationInfoManager(DiscoveryNode localNode) {
EurekaInstanceConfig instanceConfig = buildInstanceConfig(localNode);

Expand All @@ -162,23 +188,26 @@ private ApplicationInfoManager initializeApplicationInfoManager(DiscoveryNode lo

private EurekaInstanceConfig buildInstanceConfig(DiscoveryNode localNode) {
try {
String value;
if (this.useClasspathEurekaClientProps) {
String configProperty = DynamicPropertyFactory
.getInstance()
.getStringProperty("eureka.client.props", "eureka-client").get();

String eurekaPropertyFile = String.format("%s.properties", configProperty);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = loader.getResource(eurekaPropertyFile);
if (url == null) {
throw new IllegalStateException("Cannot locate " + eurekaPropertyFile + " as a classpath resource.");
}
Properties props = new Properties();
props.load(url.openStream());

String configProperty = DynamicPropertyFactory
.getInstance()
.getStringProperty("eureka.client.props", "eureka-client")
.get();

String eurekaPropertyFile = String.format("%s.properties", configProperty);
ClassLoader loader = Thread.currentThread().getContextClassLoader();
URL url = loader.getResource(eurekaPropertyFile);
if (url == null) {
throw new IllegalStateException("Cannot locate " + eurekaPropertyFile + " as a classpath resource.");
String key = String.format("%s.datacenter", this.namespace);
value = props.getProperty(key, "");
} else {
value = String.valueOf(getProperties().get("datacenter"));
}
Properties props = new Properties();
props.load(url.openStream());

String key = String.format("%s.datacenter", this.namespace);
String value = props.getProperty(key, "");
if ("cloud".equals(value.trim().toLowerCase())) {
return new DelegatingInstanceConfig(new CloudInstanceConfig(this.namespace), localNode);
}
Expand Down Expand Up @@ -275,6 +304,11 @@ void verifyEurekaRegistration() {
} while (true);
}

@VisibleForTesting
EurekaClient getEurekaClient() {
return eurekaClient;
}

private class EurekaOneAwareConfig extends DefaultEurekaClientConfig {
EurekaOneAwareConfig(String namespace) {
super(namespace);
Expand Down
Expand Up @@ -35,9 +35,12 @@
public class EurekaOneDiscoveryStrategyFactory
implements DiscoveryStrategyFactory {

private static final Collection<PropertyDefinition> PROPERTY_DEFINITIONS = Lists.newArrayList(
EurekaOneProperties.SELF_REGISTRATION,
EurekaOneProperties.NAMESPACE);
static final Collection<PropertyDefinition> PROPERTY_DEFINITIONS = Lists.newArrayList();

static {
PROPERTY_DEFINITIONS.addAll(EurekaOneProperties.HZ_PROPERTY_DEFINITIONS);
PROPERTY_DEFINITIONS.addAll(EurekaOneProperties.EUREKA_CLIENT_PROPERTY_DEFINITIONS);
}

private static EurekaClient eurekaClient;

Expand Down

0 comments on commit 8be84d8

Please sign in to comment.