2222import org .elasticsearch .cluster .service .ClusterService ;
2323import org .elasticsearch .common .file .MasterNodeFileWatchingService ;
2424import org .elasticsearch .env .Environment ;
25+ import org .elasticsearch .health .HealthIndicatorDetails ;
26+ import org .elasticsearch .health .HealthIndicatorImpact ;
27+ import org .elasticsearch .health .HealthIndicatorResult ;
28+ import org .elasticsearch .health .HealthIndicatorService ;
29+ import org .elasticsearch .health .SimpleHealthIndicatorDetails ;
30+ import org .elasticsearch .health .node .HealthInfo ;
2531import org .elasticsearch .xcontent .XContentParseException ;
2632import org .elasticsearch .xcontent .XContentParserConfiguration ;
2733
2834import java .io .BufferedInputStream ;
2935import java .io .IOException ;
3036import java .nio .file .Files ;
37+ import java .util .List ;
38+ import java .util .Map ;
3139import java .util .concurrent .ExecutionException ;
40+ import java .util .concurrent .atomic .AtomicLong ;
41+ import java .util .concurrent .atomic .AtomicReference ;
3242
43+ import static org .elasticsearch .health .HealthStatus .GREEN ;
44+ import static org .elasticsearch .health .HealthStatus .YELLOW ;
45+ import static org .elasticsearch .health .ImpactArea .DEPLOYMENT_MANAGEMENT ;
3346import static org .elasticsearch .reservedstate .service .ReservedStateVersionCheck .HIGHER_OR_SAME_VERSION ;
3447import static org .elasticsearch .reservedstate .service .ReservedStateVersionCheck .HIGHER_VERSION_ONLY ;
3548import static org .elasticsearch .xcontent .XContentType .JSON ;
@@ -53,17 +66,29 @@ public class FileSettingsService extends MasterNodeFileWatchingService implement
5366 public static final String NAMESPACE = "file_settings" ;
5467 public static final String OPERATOR_DIRECTORY = "operator" ;
5568 private final ReservedClusterStateService stateService ;
69+ private final FileSettingsHealthIndicatorService healthIndicatorService ;
5670
5771 /**
5872 * Constructs the {@link FileSettingsService}
5973 *
6074 * @param clusterService so we can register ourselves as a cluster state change listener
6175 * @param stateService an instance of the immutable cluster state controller, so we can perform the cluster state changes
6276 * @param environment we need the environment to pull the location of the config and operator directories
77+ * @param healthIndicatorService tracks the success or failure of file-based settings
6378 */
64- public FileSettingsService (ClusterService clusterService , ReservedClusterStateService stateService , Environment environment ) {
79+ public FileSettingsService (
80+ ClusterService clusterService ,
81+ ReservedClusterStateService stateService ,
82+ Environment environment ,
83+ FileSettingsHealthIndicatorService healthIndicatorService
84+ ) {
6585 super (clusterService , environment .configFile ().toAbsolutePath ().resolve (OPERATOR_DIRECTORY ).resolve (SETTINGS_FILE_NAME ));
6686 this .stateService = stateService ;
87+ this .healthIndicatorService = healthIndicatorService ;
88+ }
89+
90+ public FileSettingsHealthIndicatorService healthIndicatorService () {
91+ return healthIndicatorService ;
6792 }
6893
6994 /**
@@ -121,6 +146,7 @@ protected boolean shouldRefreshFileState(ClusterState clusterState) {
121146 @ Override
122147 protected void processFileChanges () throws ExecutionException , InterruptedException , IOException {
123148 logger .info ("processing path [{}] for [{}]" , watchedFile (), NAMESPACE );
149+ healthIndicatorService .changeOccurred ();
124150 processFileChanges (HIGHER_VERSION_ONLY );
125151 }
126152
@@ -131,6 +157,7 @@ protected void processFileChanges() throws ExecutionException, InterruptedExcept
131157 @ Override
132158 protected void processFileOnServiceStart () throws IOException , ExecutionException , InterruptedException {
133159 logger .info ("processing path [{}] for [{}] on service start" , watchedFile (), NAMESPACE );
160+ healthIndicatorService .changeOccurred ();
134161 processFileChanges (HIGHER_OR_SAME_VERSION );
135162 }
136163
@@ -146,6 +173,16 @@ private void processFileChanges(ReservedStateVersionCheck versionCheck) throws I
146173 completion .get ();
147174 }
148175
176+ private void completeProcessing (Exception e , PlainActionFuture <Void > completion ) {
177+ if (e != null ) {
178+ healthIndicatorService .failureOccurred (e .toString ());
179+ completion .onFailure (e );
180+ } else {
181+ completion .onResponse (null );
182+ healthIndicatorService .successOccurred ();
183+ }
184+ }
185+
149186 @ Override
150187 protected void onProcessFileChangesException (Exception e ) {
151188 if (e instanceof ExecutionException ) {
@@ -172,11 +209,61 @@ protected void processInitialFileMissing() throws ExecutionException, Interrupte
172209 completion .get ();
173210 }
174211
175- private static void completeProcessing (Exception e , PlainActionFuture <Void > completion ) {
176- if (e != null ) {
177- completion .onFailure (e );
178- } else {
179- completion .onResponse (null );
212+ public static class FileSettingsHealthIndicatorService implements HealthIndicatorService {
213+ static final String NAME = "file_settings" ;
214+ static final String NO_CHANGES_SYMPTOM = "No file-based setting changes have occurred" ;
215+ static final String SUCCESS_SYMPTOM = "The most recent file-based settings were applied successfully" ;
216+ static final String FAILURE_SYMPTOM = "The most recent file-based settings encountered an error" ;
217+
218+ static final List <HealthIndicatorImpact > STALE_SETTINGS_IMPACT = List .of (
219+ new HealthIndicatorImpact (
220+ NAME ,
221+ "stale" ,
222+ 3 ,
223+ "The most recent file-based settings changes have not been applied." ,
224+ List .of (DEPLOYMENT_MANAGEMENT )
225+ )
226+ );
227+
228+ private final AtomicLong changeCount = new AtomicLong (0 );
229+ private final AtomicLong failureStreak = new AtomicLong (0 );
230+ private final AtomicReference <String > mostRecentFailure = new AtomicReference <>();
231+
232+ public void changeOccurred () {
233+ changeCount .incrementAndGet ();
234+ }
235+
236+ public void successOccurred () {
237+ failureStreak .set (0 );
238+ }
239+
240+ public void failureOccurred (String description ) {
241+ failureStreak .incrementAndGet ();
242+ mostRecentFailure .set (description );
243+ }
244+
245+ @ Override
246+ public String name () {
247+ return NAME ;
248+ }
249+
250+ @ Override
251+ public HealthIndicatorResult calculate (boolean verbose , int maxAffectedResourcesCount , HealthInfo healthInfo ) {
252+ if (0 == changeCount .get ()) {
253+ return createIndicator (GREEN , NO_CHANGES_SYMPTOM , HealthIndicatorDetails .EMPTY , List .of (), List .of ());
254+ }
255+ long numFailures = failureStreak .get ();
256+ if (0 == numFailures ) {
257+ return createIndicator (GREEN , SUCCESS_SYMPTOM , HealthIndicatorDetails .EMPTY , List .of (), List .of ());
258+ } else {
259+ return createIndicator (
260+ YELLOW ,
261+ FAILURE_SYMPTOM ,
262+ new SimpleHealthIndicatorDetails (Map .of ("failure_streak" , numFailures , "most_recent_failure" , mostRecentFailure .get ())),
263+ STALE_SETTINGS_IMPACT ,
264+ List .of ()
265+ );
266+ }
180267 }
181268 }
182269}
0 commit comments