Skip to content
This repository
Browse code

Merge pull request #103 from elandau/Governation

Full DiscoveryClient Governation
  • Loading branch information...
commit c38e6130184324348b6c5a2fec4b19d9546268fe 2 parents bcc3437 + e1ff7b0
Nitesh Kant authored April 16, 2014

Showing 26 changed files with 924 additions and 163 deletions. Show diff stats Hide diff stats

  1. 2  .gitignore
  2. 16  build.gradle
  3. 80  eureka-client/src/main/java/com/netflix/appinfo/ApplicationInfoManager.java
  4. 14  eureka-client/src/main/java/com/netflix/appinfo/CloudInstanceConfig.java
  5. 2  eureka-client/src/main/java/com/netflix/appinfo/EurekaInstanceConfig.java
  6. 11  eureka-client/src/main/java/com/netflix/appinfo/InstanceInfo.java
  7. 8  eureka-client/src/main/java/com/netflix/appinfo/MyDataCenterInstanceConfig.java
  8. 34  eureka-client/src/main/java/com/netflix/appinfo/providers/CloudInstanceConfigProvider.java
  9. 97  eureka-client/src/main/java/com/netflix/appinfo/providers/EurekaConfigBasedInstanceInfoProvider.java
  10. 25  eureka-client/src/main/java/com/netflix/appinfo/providers/MyDataCenterInstanceConfigProvider.java
  11. 3  eureka-client/src/main/java/com/netflix/discovery/DefaultEurekaClientConfig.java
  12. 80  eureka-client/src/main/java/com/netflix/discovery/DiscoveryClient.java
  13. 17  eureka-client/src/main/java/com/netflix/discovery/DiscoveryManager.java
  14. 3  eureka-client/src/main/java/com/netflix/discovery/EurekaClientConfig.java
  15. 15  eureka-client/src/main/java/com/netflix/discovery/EurekaNamespace.java
  16. 71  eureka-client/src/main/java/com/netflix/discovery/EurekaUpStatusResolver.java
  17. 78  eureka-client/src/main/java/com/netflix/discovery/InternalEurekaStatusModule.java
  18. 50  eureka-client/src/main/java/com/netflix/discovery/StatusChangeEvent.java
  19. 25  eureka-client/src/main/java/com/netflix/discovery/providers/DefaultEurekaClientConfigProvider.java
  20. 7  eureka-client/src/main/java/com/netflix/discovery/shared/Application.java
  21. 7  eureka-client/src/test/java/com/netflix/discovery/BackUpRegistryTest.java
  22. 12  eureka-client/src/test/java/com/netflix/discovery/DiscoveryClientDisableRegistryTest.java
  23. 53  eureka-client/src/test/java/com/netflix/discovery/DiscoveryClientRegistryTest.java
  24. 231  eureka-client/src/test/java/com/netflix/discovery/DiscoveryStatusCheckerTest.java
  25. 56  eureka-client/src/test/java/com/netflix/discovery/EurekaLifecycleTest.java
  26. 90  eureka-client/src/test/java/com/netflix/discovery/MockRemoteEurekaServer.java
2  .gitignore
@@ -46,6 +46,8 @@ Thumbs.db
46 46
 */target
47 47
 /build
48 48
 */build
  49
+/bin
  50
+*/bin
49 51
 #
50 52
 # # IntelliJ specific files/directories
51 53
 
16  build.gradle
... ...
@@ -1,5 +1,10 @@
1 1
 ext.githubProjectName = 'eureka'
2 2
 
  3
+ext {
  4
+    jerseyVersion="1.11"
  5
+    governatorVersion="1.2.7"
  6
+}
  7
+
3 8
 buildscript {
4 9
     repositories {
5 10
         mavenLocal()
@@ -32,15 +37,17 @@ subprojects {
32 37
 
33 38
 project(':eureka-client') {
34 39
     dependencies {
  40
+        compile "com.netflix.netflix-commons:netflix-eventbus:0.1.2"
35 41
         compile 'com.thoughtworks.xstream:xstream:1.4.2'
36 42
         compile 'com.netflix.archaius:archaius-core:0.3.3'
37 43
         compile 'javax.ws.rs:jsr311-api:1.1.1'
38 44
         compile 'com.netflix.servo:servo-core:0.4.36'
39  
-        compile 'com.sun.jersey:jersey-core:1.11'
40  
-        compile 'com.sun.jersey:jersey-client:1.11'
  45
+        compile "com.sun.jersey:jersey-core:$jerseyVersion"
  46
+        compile "com.sun.jersey:jersey-client:$jerseyVersion"
41 47
         compile 'com.sun.jersey.contribs:jersey-apache-client4:1.11'
42 48
         compile 'org.apache.httpcomponents:httpclient:4.2.1'
43 49
         compile 'com.netflix.ribbon:ribbon-httpclient:0.3.4'
  50
+        compile "com.netflix.governator:governator:$governatorVersion"
44 51
         runtime 'org.codehaus.jettison:jettison:1.2'
45 52
 
46 53
         testCompile 'junit:junit:4.10'
@@ -68,7 +75,6 @@ project(':eureka-core') {
68 75
 project(':eureka-resources') {
69 76
 }
70 77
 
71  
-
72 78
 project(':eureka-server') {
73 79
     apply plugin: 'war'
74 80
 
@@ -90,8 +96,8 @@ project(':eureka-server') {
90 96
         compile project(':eureka-core')
91 97
         runtime 'xerces:xercesImpl:2.4.0'
92 98
         runtime 'asm:asm:3.1'
93  
-        compile 'com.sun.jersey:jersey-server:1.11'
94  
-        compile 'com.sun.jersey:jersey-servlet:1.11'
  99
+        compile "com.sun.jersey:jersey-server:$jerseyVersion"
  100
+        compile "com.sun.jersey:jersey-servlet:$jerseyVersion"
95 101
         compile 'org.slf4j:slf4j-log4j12:1.6.1'
96 102
         runtime 'org.codehaus.jettison:jettison:1.2' 
97 103
         providedCompile 'javax.servlet:servlet-api:2.4'
80  eureka-client/src/main/java/com/netflix/appinfo/ApplicationInfoManager.java
@@ -18,11 +18,14 @@
18 18
 
19 19
 import java.util.Map;
20 20
 
  21
+import javax.inject.Inject;
  22
+import javax.inject.Singleton;
  23
+
21 24
 import org.slf4j.Logger;
22 25
 import org.slf4j.LoggerFactory;
23 26
 
24 27
 import com.netflix.appinfo.InstanceInfo.InstanceStatus;
25  
-import com.netflix.appinfo.InstanceInfo.PortType;
  28
+import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider;
26 29
 
27 30
 /**
28 31
  * The class that initializes information required for registration with
@@ -41,15 +44,25 @@
41 44
  * @author Karthik Ranganathan, Greg Kim
42 45
  * 
43 46
  */
  47
+@Singleton
44 48
 public class ApplicationInfoManager {
45  
-    private static final Logger logger = LoggerFactory
46  
-    .getLogger(ApplicationInfoManager.class);
47  
-    private static final ApplicationInfoManager instance = new ApplicationInfoManager();
  49
+    private static final Logger logger = LoggerFactory.getLogger(ApplicationInfoManager.class);
  50
+    private static ApplicationInfoManager instance = new ApplicationInfoManager();
  51
+    
48 52
     private InstanceInfo instanceInfo;
49 53
     private EurekaInstanceConfig config;
50 54
 
51 55
     private ApplicationInfoManager() {
52 56
     }
  57
+    
  58
+    @Inject
  59
+    ApplicationInfoManager(EurekaInstanceConfig config, InstanceInfo instanceInfo) {
  60
+        this.config = config;
  61
+        this.instanceInfo = instanceInfo;
  62
+        
  63
+        // Hack to allow for getInstance() to use the DI'd ApplicationInfoManager
  64
+        instance = this;
  65
+    }
53 66
 
54 67
     public static ApplicationInfoManager getInstance() {
55 68
         return instance;
@@ -58,62 +71,7 @@ public static ApplicationInfoManager getInstance() {
58 71
     public void initComponent(EurekaInstanceConfig config) {
59 72
         try {
60 73
             this.config = config;
61  
-            // Build the lease information to be passed to the server based
62  
-            // on config
63  
-            LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder
64  
-            .newBuilder()
65  
-            .setRenewalIntervalInSecs(
66  
-                    config.getLeaseRenewalIntervalInSeconds())
67  
-                    .setDurationInSecs(
68  
-                            config.getLeaseExpirationDurationInSeconds());
69  
-
70  
-            // Builder the instance information to be registered with eureka
71  
-            // server
72  
-            InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
73  
-
74  
-            builder.setNamespace(config.getNamespace())
75  
-            .setAppName(config.getAppname())
76  
-            .setAppGroupName(config.getAppGroupName())
77  
-            .setDataCenterInfo(config.getDataCenterInfo())
78  
-            .setIPAddr(config.getIpAddress())
79  
-            .setHostName(config.getHostName(false))
80  
-            .setPort(config.getNonSecurePort())
81  
-            .enablePort(PortType.UNSECURE,
82  
-                    config.isNonSecurePortEnabled())
83  
-            .setSecurePort(config.getSecurePort())
84  
-            .enablePort(PortType.SECURE, config.getSecurePortEnabled())
85  
-            .setVIPAddress(config.getVirtualHostName())
86  
-            .setSecureVIPAddress(config.getSecureVirtualHostName())
87  
-            .setHomePageUrl(config.getHomePageUrlPath(),
88  
-                            config.getHomePageUrl())
89  
-            .setStatusPageUrl(config.getStatusPageUrlPath(),
90  
-                              config.getStatusPageUrl())
91  
-            .setHealthCheckUrls(config.getHealthCheckUrlPath(),
92  
-                                config.getHealthCheckUrl(),
93  
-                                config.getSecureHealthCheckUrl())
94  
-            .setASGName(config.getASGName());
95  
-
96  
-            // Start off with the STARTING state to avoid traffic
97  
-            if (!config.isInstanceEnabledOnit()) {
98  
-                InstanceStatus initialStatus = InstanceStatus.STARTING;
99  
-                logger.info("Setting initial instance status as: " + initialStatus);
100  
-                builder.setStatus(initialStatus);
101  
-            } else {
102  
-                logger.info("Setting initial instance status as: " + InstanceStatus.UP +
103  
-                            ". This may be too early for the instance to advertise itself as available. " +
104  
-                            "You would instead want to control this via a healthcheck handler.");
105  
-            }
106  
-
107  
-            // Add any user-specific metadata information
108  
-            for (Map.Entry<String, String> mapEntry : config.getMetadataMap()
109  
-                    .entrySet()) {
110  
-                String key = mapEntry.getKey();
111  
-                String value = mapEntry.getValue();
112  
-                builder.add(key, value);
113  
-            }
114  
-            instanceInfo = builder.build();
115  
-            instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
116  
-
  74
+            this.instanceInfo = new EurekaConfigBasedInstanceInfoProvider(config).get();
117 75
         } catch (Throwable e) {
118 76
             throw new RuntimeException(
119 77
                     "Failed to initialize ApplicationInfoManager", e);
@@ -128,7 +86,7 @@ public void initComponent(EurekaInstanceConfig config) {
128 86
     public InstanceInfo getInfo() {
129 87
         return instanceInfo;
130 88
     }
131  
-
  89
+    
132 90
     /**
133 91
      * Register user-specific instance meta data. Application can send any other
134 92
      * additional meta data that need to be accessed for other reasons.The data
14  eureka-client/src/main/java/com/netflix/appinfo/CloudInstanceConfig.java
@@ -19,11 +19,15 @@
19 19
 import java.util.HashMap;
20 20
 import java.util.Map;
21 21
 
  22
+import javax.inject.Singleton;
  23
+
22 24
 import org.slf4j.Logger;
23 25
 import org.slf4j.LoggerFactory;
24 26
 
  27
+import com.google.inject.ProvidedBy;
25 28
 import com.netflix.appinfo.AmazonInfo.MetaDataKey;
26 29
 import com.netflix.appinfo.DataCenterInfo.Name;
  30
+import com.netflix.appinfo.providers.CloudInstanceConfigProvider;
27 31
 import com.netflix.config.DynamicBooleanProperty;
28 32
 import com.netflix.config.DynamicPropertyFactory;
29 33
 
@@ -41,17 +45,19 @@
41 45
  * @author Karthik Ranganathan
42 46
  * 
43 47
  */
  48
+@Singleton
  49
+@ProvidedBy(CloudInstanceConfigProvider.class)
44 50
 public class CloudInstanceConfig extends PropertiesInstanceConfig {
45  
-    private static final Logger logger = LoggerFactory
46  
-    .getLogger(CloudInstanceConfig.class);
47  
-    private static final DynamicPropertyFactory INSTANCE = com.netflix.config.DynamicPropertyFactory
48  
-    .getInstance();
  51
+    private static final Logger logger = LoggerFactory.getLogger(CloudInstanceConfig.class);
  52
+    private static final DynamicPropertyFactory INSTANCE = DynamicPropertyFactory.getInstance();
  53
+    
49 54
     private DynamicBooleanProperty propValidateInstanceId;
50 55
     private DataCenterInfo info;
51 56
     
52 57
     public CloudInstanceConfig() {
53 58
         initCloudInstanceConfig(namespace); 
54 59
     }
  60
+    
55 61
     public CloudInstanceConfig(String namespace) {
56 62
         super(namespace);
57 63
         initCloudInstanceConfig(namespace);
2  eureka-client/src/main/java/com/netflix/appinfo/EurekaInstanceConfig.java
@@ -18,6 +18,7 @@
18 18
 import java.net.URL;
19 19
 import java.util.Map;
20 20
 
  21
+import com.google.inject.ImplementedBy;
21 22
 import com.netflix.discovery.DiscoveryClient;
22 23
 
23 24
 /**
@@ -36,6 +37,7 @@
36 37
  * @author Karthik Ranganathan
37 38
  * 
38 39
  */
  40
+@ImplementedBy(CloudInstanceConfig.class)
39 41
 public interface EurekaInstanceConfig {
40 42
     /**
41 43
      * Get the name of the application to be registered with eureka.
11  eureka-client/src/main/java/com/netflix/appinfo/InstanceInfo.java
@@ -26,8 +26,8 @@
26 26
 import org.slf4j.Logger;
27 27
 import org.slf4j.LoggerFactory;
28 28
 
29  
-import com.netflix.appinfo.AmazonInfo.MetaDataKey;
30  
-import com.netflix.appinfo.DataCenterInfo.Name;
  29
+import com.google.inject.ProvidedBy;
  30
+import com.netflix.appinfo.providers.EurekaConfigBasedInstanceInfoProvider;
31 31
 import com.netflix.config.DynamicPropertyFactory;
32 32
 import com.netflix.discovery.DiscoveryClient;
33 33
 import com.netflix.discovery.converters.Auto;
@@ -46,13 +46,12 @@
46 46
  * @author Karthik Ranganathan, Greg Kim
47 47
  * 
48 48
  */
  49
+@ProvidedBy(EurekaConfigBasedInstanceInfoProvider.class)
49 50
 @Serializer("com.netflix.discovery.converters.EntityBodyConverter")
50 51
 @XStreamAlias("instance")
51 52
 public class InstanceInfo {
52  
-    private static final Logger logger = LoggerFactory
53  
-    .getLogger(InstanceInfo.class);
54  
-    private static final Pattern VIP_ATTRIBUTES_PATTERN = Pattern
55  
-    .compile("\\$\\{(.*?)\\}");
  53
+    private static final Logger logger = LoggerFactory.getLogger(InstanceInfo.class);
  54
+    private static final Pattern VIP_ATTRIBUTES_PATTERN = Pattern.compile("\\$\\{(.*?)\\}");
56 55
 
57 56
     public final static int DEFAULT_PORT = 7001;
58 57
     public final static int DEFAULT_SECURE_PORT = 7002;
8  eureka-client/src/main/java/com/netflix/appinfo/MyDataCenterInstanceConfig.java
@@ -15,12 +15,19 @@
15 15
  */
16 16
 package com.netflix.appinfo;
17 17
 
  18
+import javax.inject.Singleton;
  19
+
  20
+import com.google.inject.ProvidedBy;
  21
+import com.netflix.appinfo.providers.MyDataCenterInstanceConfigProvider;
  22
+
18 23
 /**
19 24
  * An {@link InstanceInfo} configuration for the non-AWS datacenter.
20 25
  * 
21 26
  * @author Karthik Ranganathan
22 27
  * 
23 28
  */
  29
+@Singleton
  30
+@ProvidedBy(MyDataCenterInstanceConfigProvider.class)
24 31
 public class MyDataCenterInstanceConfig extends PropertiesInstanceConfig
25 32
 implements EurekaInstanceConfig {
26 33
     
@@ -34,7 +41,6 @@ public MyDataCenterInstanceConfig(String namespace) {
34 41
     public MyDataCenterInstanceConfig(String namespace,
35 42
             DataCenterInfo dataCenterInfo) {
36 43
         super(namespace, dataCenterInfo);
37  
-
38 44
     }
39 45
 
40 46
 }
34  eureka-client/src/main/java/com/netflix/appinfo/providers/CloudInstanceConfigProvider.java
... ...
@@ -0,0 +1,34 @@
  1
+package com.netflix.appinfo.providers;
  2
+
  3
+import javax.inject.Singleton;
  4
+
  5
+import com.google.inject.Inject;
  6
+import com.google.inject.Provider;
  7
+import com.netflix.appinfo.CloudInstanceConfig;
  8
+import com.netflix.discovery.DiscoveryManager;
  9
+import com.netflix.discovery.EurekaNamespace;
  10
+
  11
+/**
  12
+ * This provider is necessary because the namespace is optional
  13
+ * @author elandau
  14
+ */
  15
+@Singleton
  16
+public class CloudInstanceConfigProvider implements Provider<CloudInstanceConfig> {
  17
+    @Inject(optional=true)
  18
+    @EurekaNamespace 
  19
+    private String namespace;
  20
+    
  21
+    @Override
  22
+    public CloudInstanceConfig get() {
  23
+        CloudInstanceConfig config;
  24
+        if (namespace == null)
  25
+            config = new CloudInstanceConfig();
  26
+        else
  27
+            config = new CloudInstanceConfig(namespace);
  28
+        
  29
+        // TOOD: Remove this when DiscoveryManager is finally no longer used
  30
+        DiscoveryManager.getInstance().setEurekaInstanceConfig(config);      
  31
+        return config;
  32
+    }
  33
+    
  34
+}
97  eureka-client/src/main/java/com/netflix/appinfo/providers/EurekaConfigBasedInstanceInfoProvider.java
... ...
@@ -0,0 +1,97 @@
  1
+package com.netflix.appinfo.providers;
  2
+
  3
+import java.util.Map;
  4
+
  5
+import javax.inject.Inject;
  6
+import javax.inject.Singleton;
  7
+
  8
+import org.slf4j.Logger;
  9
+import org.slf4j.LoggerFactory;
  10
+
  11
+import com.google.inject.Provider;
  12
+import com.netflix.appinfo.EurekaInstanceConfig;
  13
+import com.netflix.appinfo.InstanceInfo;
  14
+import com.netflix.appinfo.LeaseInfo;
  15
+import com.netflix.appinfo.InstanceInfo.InstanceStatus;
  16
+import com.netflix.appinfo.InstanceInfo.PortType;
  17
+
  18
+/**
  19
+ * InstanceInfo provider that constructs the InstanceInfo this this instance using
  20
+ * EurekaInstanceConfig
  21
+ * 
  22
+ * @author elandau
  23
+ *
  24
+ */
  25
+@Singleton
  26
+public class EurekaConfigBasedInstanceInfoProvider implements Provider<InstanceInfo> {
  27
+    private static final Logger LOG = LoggerFactory.getLogger(EurekaConfigBasedInstanceInfoProvider.class);
  28
+    
  29
+    private final EurekaInstanceConfig config;
  30
+    
  31
+    @Inject
  32
+    public EurekaConfigBasedInstanceInfoProvider(EurekaInstanceConfig config) {
  33
+        this.config = config;
  34
+    }
  35
+    
  36
+    @Override
  37
+    public InstanceInfo get() {
  38
+        // Build the lease information to be passed to the server based
  39
+        // on config
  40
+        LeaseInfo.Builder leaseInfoBuilder = LeaseInfo.Builder
  41
+        .newBuilder()
  42
+        .setRenewalIntervalInSecs(
  43
+                config.getLeaseRenewalIntervalInSeconds())
  44
+                .setDurationInSecs(
  45
+                        config.getLeaseExpirationDurationInSeconds());
  46
+
  47
+        // Builder the instance information to be registered with eureka
  48
+        // server
  49
+        InstanceInfo.Builder builder = InstanceInfo.Builder.newBuilder();
  50
+
  51
+        builder.setNamespace(config.getNamespace())
  52
+            .setAppName(config.getAppname())
  53
+            .setAppGroupName(config.getAppGroupName())
  54
+            .setDataCenterInfo(config.getDataCenterInfo())
  55
+            .setIPAddr(config.getIpAddress())
  56
+            .setHostName(config.getHostName(false))
  57
+            .setPort(config.getNonSecurePort())
  58
+            .enablePort(PortType.UNSECURE,
  59
+                    config.isNonSecurePortEnabled())
  60
+            .setSecurePort(config.getSecurePort())
  61
+            .enablePort(PortType.SECURE, config.getSecurePortEnabled())
  62
+            .setVIPAddress(config.getVirtualHostName())
  63
+            .setSecureVIPAddress(config.getSecureVirtualHostName())
  64
+            .setHomePageUrl(config.getHomePageUrlPath(),
  65
+                            config.getHomePageUrl())
  66
+            .setStatusPageUrl(config.getStatusPageUrlPath(),
  67
+                              config.getStatusPageUrl())
  68
+            .setHealthCheckUrls(config.getHealthCheckUrlPath(),
  69
+                                config.getHealthCheckUrl(),
  70
+                                config.getSecureHealthCheckUrl())
  71
+            .setASGName(config.getASGName());
  72
+
  73
+        // Start off with the STARTING state to avoid traffic
  74
+        if (!config.isInstanceEnabledOnit()) {
  75
+            InstanceStatus initialStatus = InstanceStatus.STARTING;
  76
+            LOG.info("Setting initial instance status as: " + initialStatus);
  77
+            builder.setStatus(initialStatus);
  78
+        } else {
  79
+            LOG.info("Setting initial instance status as: " + InstanceStatus.UP +
  80
+                        ". This may be too early for the instance to advertise itself as available. " +
  81
+                        "You would instead want to control this via a healthcheck handler.");
  82
+        }
  83
+
  84
+        // Add any user-specific metadata information
  85
+        for (Map.Entry<String, String> mapEntry : config.getMetadataMap()
  86
+                .entrySet()) {
  87
+            String key = mapEntry.getKey();
  88
+            String value = mapEntry.getValue();
  89
+            builder.add(key, value);
  90
+        }
  91
+        
  92
+        InstanceInfo instanceInfo = builder.build();
  93
+        instanceInfo.setLeaseInfo(leaseInfoBuilder.build());
  94
+        return instanceInfo;
  95
+    }
  96
+
  97
+}
25  eureka-client/src/main/java/com/netflix/appinfo/providers/MyDataCenterInstanceConfigProvider.java
... ...
@@ -0,0 +1,25 @@
  1
+package com.netflix.appinfo.providers;
  2
+
  3
+import com.google.inject.Inject;
  4
+import com.google.inject.Provider;
  5
+import com.netflix.appinfo.MyDataCenterInstanceConfig;
  6
+import com.netflix.discovery.DiscoveryManager;
  7
+import com.netflix.discovery.EurekaNamespace;
  8
+
  9
+public class MyDataCenterInstanceConfigProvider implements Provider<MyDataCenterInstanceConfig> {
  10
+    @Inject(optional=true)
  11
+    @EurekaNamespace 
  12
+    private String namespace;
  13
+    
  14
+    @Override
  15
+    public MyDataCenterInstanceConfig get() {
  16
+        MyDataCenterInstanceConfig config;
  17
+        if (namespace == null)
  18
+            config = new MyDataCenterInstanceConfig();
  19
+        else
  20
+            config = new MyDataCenterInstanceConfig(namespace);
  21
+        
  22
+        DiscoveryManager.getInstance().setEurekaInstanceConfig(config);      
  23
+        return config;
  24
+    }
  25
+}
3  eureka-client/src/main/java/com/netflix/discovery/DefaultEurekaClientConfig.java
@@ -24,9 +24,11 @@
24 24
 import org.slf4j.Logger;
25 25
 import org.slf4j.LoggerFactory;
26 26
 
  27
+import com.google.inject.ProvidedBy;
27 28
 import com.netflix.config.ConfigurationManager;
28 29
 import com.netflix.config.DynamicPropertyFactory;
29 30
 import com.netflix.config.DynamicStringProperty;
  31
+import com.netflix.discovery.providers.DefaultEurekaClientConfigProvider;
30 32
 
31 33
 import javax.annotation.Nullable;
32 34
 
@@ -53,6 +55,7 @@
53 55
  * @author Karthik Ranganathan
54 56
  * 
55 57
  */
  58
+@ProvidedBy(DefaultEurekaClientConfigProvider.class)
56 59
 public class DefaultEurekaClientConfig implements EurekaClientConfig {
57 60
     private static final String ARCHAIUS_DEPLOYMENT_ENVIRONMENT = "archaius.deployment.environment";
58 61
     private static final String TEST = "test";
80  eureka-client/src/main/java/com/netflix/discovery/DiscoveryClient.java
@@ -24,7 +24,6 @@
24 24
 import java.util.List;
25 25
 import java.util.Map;
26 26
 import java.util.Set;
27  
-import java.util.Timer;
28 27
 import java.util.TimerTask;
29 28
 import java.util.TreeMap;
30 29
 import java.util.TreeSet;
@@ -36,6 +35,8 @@
36 35
 import java.util.concurrent.atomic.AtomicReference;
37 36
 
38 37
 import javax.annotation.Nullable;
  38
+import javax.annotation.PreDestroy;
  39
+import javax.inject.Singleton;
39 40
 import javax.naming.directory.DirContext;
40 41
 import javax.ws.rs.core.MediaType;
41 42
 import javax.ws.rs.core.Response.Status;
@@ -43,6 +44,8 @@
43 44
 import org.slf4j.Logger;
44 45
 import org.slf4j.LoggerFactory;
45 46
 
  47
+import com.google.common.annotations.VisibleForTesting;
  48
+import com.google.inject.Inject;
46 49
 import com.netflix.appinfo.AmazonInfo;
47 50
 import com.netflix.appinfo.AmazonInfo.MetaDataKey;
48 51
 import com.netflix.appinfo.ApplicationInfoManager;
@@ -58,6 +61,7 @@
58 61
 import com.netflix.discovery.shared.EurekaJerseyClient;
59 62
 import com.netflix.discovery.shared.EurekaJerseyClient.JerseyClient;
60 63
 import com.netflix.discovery.shared.LookupService;
  64
+import com.netflix.eventbus.spi.EventBus;
61 65
 import com.netflix.servo.monitor.Counter;
62 66
 import com.netflix.servo.monitor.Monitors;
63 67
 import com.netflix.servo.monitor.Stopwatch;
@@ -91,12 +95,10 @@
91 95
  * @author Karthik Ranganathan, Greg Kim
92 96
  * 
93 97
  */
  98
+@Singleton
94 99
 public class DiscoveryClient implements LookupService {
95  
-    private static final Logger logger = LoggerFactory
96  
-            .getLogger(DiscoveryClient.class);
97  
-
98  
-    private static final DynamicPropertyFactory configInstance = DynamicPropertyFactory
99  
-            .getInstance();
  100
+    private static final Logger logger = LoggerFactory.getLogger(DiscoveryClient.class);
  101
+    private static final DynamicPropertyFactory configInstance = DynamicPropertyFactory.getInstance();
100 102
 
101 103
     // Constants
102 104
     private static final String DNS_PROVIDER_URL = "dns:";
@@ -148,17 +150,26 @@
148 150
     protected static EurekaClientConfig clientConfig;
149 151
     private final AtomicReference<String> remoteRegionsToFetch;
150 152
     private final InstanceRegionChecker instanceRegionChecker;
151  
-
  153
+    private volatile InstanceInfo.InstanceStatus lastRemoteInstanceStatus = InstanceInfo.InstanceStatus.UNKNOWN;
  154
+    
152 155
     private enum Action {
153 156
         Register, Cancel, Renew, Refresh, Refresh_Delta
154 157
     }
155 158
 
156 159
     private final ScheduledExecutorService scheduler;
157 160
 
  161
+    @Inject(optional=true)
  162
+    private EventBus eventBus;
  163
+    
  164
+    DiscoveryClient(InstanceInfo myInfo, EurekaClientConfig config, EventBus eventBus) {
  165
+        this(myInfo, config);
  166
+        this.eventBus = eventBus;
  167
+    }
  168
+    
  169
+    @Inject
158 170
     DiscoveryClient(InstanceInfo myInfo, EurekaClientConfig config) {
159 171
         try {
160 172
             scheduler = Executors.newScheduledThreadPool(4);
161  
-
162 173
             clientConfig = config;
163 174
             final String zone = getZone(myInfo);
164 175
             eurekaServiceUrls.set(getDiscoveryServiceUrls(zone));
@@ -226,6 +237,11 @@
226 237
         } catch (Throwable e) {
227 238
             logger.warn("Cannot register timers", e);
228 239
         }
  240
+        
  241
+        // This is a bit of hack to allow for existing code using DiscoveryManager.getInstance()
  242
+        // to work with DI'd DiscoveryClient
  243
+        DiscoveryManager.getInstance().setDiscoveryClient(this);
  244
+        DiscoveryManager.getInstance().setEurekaClientConfig(config);
229 245
     }
230 246
 
231 247
     /*
@@ -299,7 +315,7 @@ public void registerHealthCheckCallback(HealthCheckCallback callback) {
299 315
             healthCheckCallback = callback;
300 316
         }
301 317
     }
302  
-
  318
+    
303 319
     /**
304 320
      * Gets the list of instances matching the given VIP Address.
305 321
      * 
@@ -560,6 +576,7 @@ void register() {
560 576
      * Shuts down Eureka Client. Also sends a deregistration request to the
561 577
      * eureka server.
562 578
      */
  579
+    @PreDestroy
563 580
     public void shutdown() {
564 581
         cancelScheduledTasks();
565 582
 
@@ -648,15 +665,18 @@ private boolean fetchRegistry(boolean forceFullRegistryFetch) {
648 665
                 }
649 666
                 logTotalInstances();
650 667
             }
  668
+            
651 669
             logger.debug(PREFIX + appPathIdentifier + " -  refresh status: "
652 670
                     + response.getStatus());
  671
+            
  672
+            updateInstanceRemoteStatus();
  673
+
653 674
         } catch (Throwable e) {
654 675
             logger.error(
655 676
                     PREFIX + appPathIdentifier
656 677
                             + " - was unable to refresh its cache! status = "
657 678
                             + e.getMessage(), e);
658 679
             return false;
659  
-
660 680
         } finally {
661 681
             if (tracer != null) {
662 682
                 tracer.stop();
@@ -666,6 +686,43 @@ private boolean fetchRegistry(boolean forceFullRegistryFetch) {
666 686
         return true;
667 687
     }
668 688
 
  689
+    private synchronized void updateInstanceRemoteStatus() {
  690
+        // Determine this instance's status for this app and set to UNKNOWN if not found
  691
+        InstanceInfo.InstanceStatus currentRemoteInstanceStatus = null;
  692
+        if (instanceInfo.getAppName() != null) {
  693
+            Application app = getApplication(instanceInfo.getAppName());
  694
+            if (app != null) {
  695
+                InstanceInfo remoteInstanceInfo = app.getByInstanceId(instanceInfo.getId());
  696
+                if (remoteInstanceInfo != null) {
  697
+                    currentRemoteInstanceStatus = remoteInstanceInfo.getStatus();
  698
+                }
  699
+            }
  700
+        }
  701
+        if (currentRemoteInstanceStatus == null) {
  702
+            currentRemoteInstanceStatus = InstanceInfo.InstanceStatus.UNKNOWN;
  703
+        }
  704
+            
  705
+        // Notify if status changed
  706
+        if (lastRemoteInstanceStatus != currentRemoteInstanceStatus) {
  707
+            try {
  708
+                if (eventBus != null) {
  709
+                    StatusChangeEvent event = new StatusChangeEvent(lastRemoteInstanceStatus, currentRemoteInstanceStatus);
  710
+                    eventBus.publish(event);
  711
+                }
  712
+            }
  713
+            finally {
  714
+                lastRemoteInstanceStatus = currentRemoteInstanceStatus;
  715
+            }
  716
+        }
  717
+    }
  718
+    
  719
+    /**
  720
+     * @return Return he current instance status as seen on the Eureka server.
  721
+     */
  722
+    public InstanceInfo.InstanceStatus getInstanceRemoteStatus() {
  723
+        return lastRemoteInstanceStatus;
  724
+    }
  725
+    
669 726
     private String getReconcileHashCode(Applications applications) {
670 727
         TreeMap<String, AtomicInteger> instanceCountMap = new TreeMap<String, AtomicInteger>();
671 728
         if (isFetchingRemoteRegionRegistries()) {
@@ -768,8 +825,7 @@ private ClientResponse reconcileAndLogDifference(ClientResponse response,
768 825
      *            the delta information received from eureka server in the last
769 826
      *            poll cycle.
770 827
      */
771  
-    private void
772  
-    updateDelta(Applications delta) {
  828
+    private void updateDelta(Applications delta) {
773 829
         int deltaCount = 0;
774 830
         for (Application app : delta.getRegisteredApplications()) {
775 831
             for (InstanceInfo instance : app.getInstances()) {
17  eureka-client/src/main/java/com/netflix/discovery/DiscoveryManager.java
@@ -38,8 +38,7 @@
38 38
  * 
39 39
  */
40 40
 public class DiscoveryManager {
41  
-    private static final Logger logger = LoggerFactory
42  
-            .getLogger(DiscoveryManager.class);
  41
+    private static final Logger logger = LoggerFactory.getLogger(DiscoveryManager.class);
43 42
     private DiscoveryClient discoveryClient;
44 43
     
45 44
     private EurekaInstanceConfig eurekaInstanceConfig;
@@ -53,6 +52,18 @@ public static DiscoveryManager getInstance() {
53 52
         return s_instance;
54 53
     }
55 54
 
  55
+    public void setDiscoveryClient(DiscoveryClient discoveryClient) {
  56
+        this.discoveryClient = discoveryClient;
  57
+    }
  58
+    
  59
+    public void setEurekaClientConfig(EurekaClientConfig eurekaClientConfig) {
  60
+        this.eurekaClientConfig = eurekaClientConfig;
  61
+    }
  62
+    
  63
+    public void setEurekaInstanceConfig(EurekaInstanceConfig eurekaInstanceConfig) {
  64
+        this.eurekaInstanceConfig = eurekaInstanceConfig;
  65
+    }
  66
+    
56 67
     /**
57 68
      * Initializes the <tt>Discovery Client</tt> with the given configuration.
58 69
      * 
@@ -72,7 +83,7 @@ public void initComponent(EurekaInstanceConfig config,
72 83
         InstanceInfo info = ApplicationInfoManager.getInstance().getInfo();
73 84
         discoveryClient = new DiscoveryClient(info, eurekaConfig);
74 85
     }
75  
-
  86
+    
76 87
     /**
77 88
      * Shuts down the <tt>Discovery Client</tt> which unregisters the
78 89
      * information about this instance from the <tt>Discovery Server</tt>.
3  eureka-client/src/main/java/com/netflix/discovery/EurekaClientConfig.java
@@ -22,6 +22,8 @@
22 22
 
23 23
 import org.apache.http.client.HttpClient;
24 24
 
  25
+import com.google.inject.ImplementedBy;
  26
+import com.netflix.appinfo.CloudInstanceConfig;
25 27
 import com.netflix.appinfo.InstanceInfo.InstanceStatus;
26 28
 
27 29
 import javax.annotation.Nullable;
@@ -55,6 +57,7 @@
55 57
  * @author Karthik Ranganathan
56 58
  * 
57 59
  */
  60
+@ImplementedBy(DefaultEurekaClientConfig.class)
58 61
 public interface EurekaClientConfig {
59 62
 
60 63
     /**
15  eureka-client/src/main/java/com/netflix/discovery/EurekaNamespace.java
... ...
@@ -0,0 +1,15 @@
  1
+package com.netflix.discovery;
  2
+
  3
+import java.lang.annotation.ElementType;
  4
+import java.lang.annotation.Retention;
  5
+import java.lang.annotation.RetentionPolicy;
  6
+import java.lang.annotation.Target;
  7
+
  8
+import com.google.inject.BindingAnnotation;
  9
+
  10
+@BindingAnnotation
  11
+@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
  12
+@Retention(RetentionPolicy.RUNTIME)
  13
+public @interface EurekaNamespace {
  14
+
  15
+}
71  eureka-client/src/main/java/com/netflix/discovery/EurekaUpStatusResolver.java
... ...
@@ -0,0 +1,71 @@
  1
+package com.netflix.discovery;
  2
+
  3
+import javax.annotation.PostConstruct;
  4
+import javax.annotation.PreDestroy;
  5
+
  6
+import org.slf4j.Logger;
  7
+import org.slf4j.LoggerFactory;
  8
+
  9
+import com.google.inject.Inject;
  10
+import com.netflix.appinfo.InstanceInfo;
  11
+import com.netflix.eventbus.spi.EventBus;
  12
+import com.netflix.eventbus.spi.InvalidSubscriberException;
  13
+import com.netflix.eventbus.spi.Subscribe;
  14
+import com.netflix.governator.guice.lazy.LazySingleton;
  15
+
  16
+/**
  17
+ * Singleton that manages the state of @UpStatus/@DownStatus Supplier<Boolean>
  18
+ * and emits status changes to @UpStatus Observable<Boolean>.
  19
+ * 
  20
+ * @author elandau
  21
+ *
  22
+ */
  23
+@LazySingleton
  24
+public class EurekaUpStatusResolver  {
  25
+    private static Logger LOG = LoggerFactory.getLogger(EurekaUpStatusResolver.class);
  26
+    
  27
+    private volatile InstanceInfo.InstanceStatus currentStatus = InstanceInfo.InstanceStatus.UNKNOWN;
  28
+    private final EventBus eventBus;
  29
+    private final DiscoveryClient client;
  30
+    
  31
+    /**
  32
+     * @param executor
  33
+     * @param upStatus
  34
+     * @param discoveryClientProvider Provider that returns a discovery client.  We use a provider 
  35
+     *  because the DiscoveryClient reference may not exist at bootstrap time
  36
+     */
  37
+    @Inject
  38
+    public EurekaUpStatusResolver(DiscoveryClient client, EventBus eventBus) {
  39
+        this.eventBus = eventBus;
  40
+        this.client   = client;
  41
+    }
  42
+    
  43
+    @Subscribe
  44
+    public void onStatusChange(StatusChangeEvent event) {
  45
+        currentStatus = event.getStatus();
  46
+    }
  47
+    
  48
+    @PostConstruct
  49
+    public void init() {
  50
+        try {
  51
+            eventBus.registerSubscriber(this);
  52
+            
  53
+            // Must set the initial status
  54
+            currentStatus = client.getInstanceRemoteStatus();
  55
+        } catch (InvalidSubscriberException e) {
  56
+            LOG.error("Error registring for discovery status change events.", e);
  57
+        }
  58
+    }
  59
+    
  60
+    @PreDestroy
  61
+    public void shutdown() {
  62
+        eventBus.unregisterSubscriber(this);
  63
+    }
  64
+    
  65
+    /**
  66
+     * @return Get the current instance status
  67
+     */
  68
+    public InstanceInfo.InstanceStatus getStatus() {
  69
+        return currentStatus;
  70
+    }   
  71
+}
78  eureka-client/src/main/java/com/netflix/discovery/InternalEurekaStatusModule.java
... ...
@@ -0,0 +1,78 @@
  1
+package com.netflix.discovery;
  2
+
  3
+import javax.inject.Inject;
  4
+import javax.inject.Singleton;
  5
+
  6
+import com.google.common.base.Supplier;
  7
+import com.google.inject.AbstractModule;
  8
+import com.google.inject.Provider;
  9
+import com.google.inject.TypeLiteral;
  10
+import com.netflix.appinfo.InstanceInfo;
  11
+import com.netflix.appinfo.InstanceInfo.InstanceStatus;
  12
+import com.netflix.governator.annotations.binding.DownStatus;
  13
+import com.netflix.governator.annotations.binding.UpStatus;
  14
+import com.netflix.governator.guice.lazy.LazySingleton;
  15
+
  16
+/**
  17
+ * Specific bindings for eureka status checker.  
  18
+ * 
  19
+ * Note that this is an internal modules and ASSUMES that a binding for 
  20
+ * DiscoveryClient was already set.  
  21
+ * 
  22
+ * Exposed bindings,
  23
+ * 
  24
+ * @UpStatus   Supplier<Boolean>
  25
+ * @DownStatus Supplier<Boolean>
  26
+ * @UpStatus   Observable<Boolean>
  27
+ * 
  28
+ * @author elandau
  29
+ *
  30
+ */
  31
+@Singleton
  32
+class InternalEurekaStatusModule extends AbstractModule {
  33
+    
  34
+    @LazySingleton
  35
+    public static class UpStatusProvider implements Provider<Supplier<Boolean>> {
  36
+        @Inject
  37
+        private Provider<EurekaUpStatusResolver> upStatus;
  38
+        
  39
+        @Override
  40
+        public Supplier<Boolean> get() {
  41
+            final EurekaUpStatusResolver resolver = upStatus.get();
  42
+            return new Supplier<Boolean>() {
  43
+                @Override
  44
+                public Boolean get() {
  45
+                    return resolver.getStatus().equals(InstanceInfo.InstanceStatus.UP);
  46
+                }
  47
+            };
  48
+        }
  49
+    }
  50
+    
  51
+    @LazySingleton
  52
+    public static class DownStatusProvider implements Provider<Supplier<Boolean>> {
  53
+        @Inject
  54
+        private Provider<EurekaUpStatusResolver> upStatus;
  55
+        
  56
+        @Override
  57
+        public Supplier<Boolean> get() {
  58
+            final EurekaUpStatusResolver resolver = upStatus.get();
  59
+            return new Supplier<Boolean>() {
  60
+                @Override
  61
+                public Boolean get() {
  62
+                    return !resolver.getStatus().equals(InstanceInfo.InstanceStatus.UP);
  63
+                }
  64
+            };
  65
+        }
  66
+    }
  67
+
  68
+    @Override
  69
+    protected void configure() {
  70
+        bind(new TypeLiteral<Supplier<Boolean>>() {})
  71
+            .annotatedWith(UpStatus.class)
  72
+            .toProvider(UpStatusProvider.class);
  73
+        
  74
+        bind(new TypeLiteral<Supplier<Boolean>>() {})
  75
+            .annotatedWith(DownStatus.class)
  76
+            .toProvider(DownStatusProvider.class);
  77
+    }
  78
+}
50  eureka-client/src/main/java/com/netflix/discovery/StatusChangeEvent.java
... ...
@@ -0,0 +1,50 @@
  1
+package com.netflix.discovery;
  2
+
  3
+import com.netflix.appinfo.InstanceInfo;
  4
+
  5
+/**
  6
+ * Event containing the latest instance status information.  This event
  7
+ * is sent to the {@link EventBus} by {@link DiscoveryClient) whenever
  8
+ * a status change is identified from the remote Eureka server response.
  9
+ * 
  10
+ * @author elandau
  11
+ *
  12
+ */
  13
+public class StatusChangeEvent {
  14
+    private final InstanceInfo.InstanceStatus current;
  15
+    private final InstanceInfo.InstanceStatus previous;
  16
+    
  17
+    public StatusChangeEvent(InstanceInfo.InstanceStatus previous, InstanceInfo.InstanceStatus current) {
  18
+        this.current = current;
  19
+        this.previous = previous;
  20
+    }
  21
+    
  22
+    /**
  23
+     * Return the up current when the event was generated
  24
+     * @return true if current is up or false for ALL other current values
  25
+     */
  26
+    public boolean isUp() {
  27
+        return this.current.equals(InstanceInfo.InstanceStatus.UP);
  28
+    }
  29
+    
  30
+    /**
  31
+     * @return The current at the time the event is generated.  
  32
+     */
  33
+    public InstanceInfo.InstanceStatus getStatus() {
  34
+        return current;
  35
+    }
  36
+    
  37
+    /**
  38
+     * @return Return the client status immediately before the change
  39
+     */
  40
+    public InstanceInfo.InstanceStatus getPreviousStatus() {
  41
+        return previous;
  42
+    }
  43
+    
  44
+    @Override
  45
+    public String toString() {
  46
+        return "StatusChangeEvent [current=" + current + ", previous="
  47
+                + previous + "]";
  48
+    }
  49
+
  50
+}
25  eureka-client/src/main/java/com/netflix/discovery/providers/DefaultEurekaClientConfigProvider.java
... ...
@@ -0,0 +1,25 @@
  1
+package com.netflix.discovery.providers;
  2
+
  3
+import com.google.inject.Inject;
  4
+import com.google.inject.Provider;
  5
+import com.netflix.discovery.DefaultEurekaClientConfig;
  6
+import com.netflix.discovery.EurekaNamespace;
  7
+
  8
+/**
  9
+ * This provider is necessary because the namespace is optional
  10
+ * @author elandau
  11
+ */
  12
+public class DefaultEurekaClientConfigProvider implements Provider<DefaultEurekaClientConfig> {
  13
+
  14
+    @Inject(optional=true)
  15
+    @EurekaNamespace 
  16
+    private String namespace;
  17
+    
  18
+    @Override
  19
+    public DefaultEurekaClientConfig get() {
  20
+        if (namespace == null)
  21
+            return new DefaultEurekaClientConfig();
  22
+        else
  23
+            return new DefaultEurekaClientConfig(namespace);
  24
+    }
  25
+}
7  eureka-client/src/main/java/com/netflix/discovery/shared/Application.java
@@ -49,6 +49,13 @@
49 49
 @XStreamAlias("application")
50 50
 public class Application {
51 51
 
  52
+    @Override
  53
+    public String toString() {
  54
+        return "Application [name=" + name + ", isDirty=" + isDirty
  55
+                + ", instances=" + instances + ", shuffledInstances="
  56
+                + shuffledInstances + ", instancesMap=" + instancesMap + "]";
  57
+    }
  58
+
52 59
     private String name;
53 60