001/*
002 * (C) Copyright 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     mcedica
018 */
019package org.nuxeo.ecm.core.management.probes;
020
021import java.time.LocalDateTime;
022import java.time.ZoneId;
023import java.time.temporal.ChronoUnit;
024import java.util.Collection;
025import java.util.Collections;
026import java.util.Date;
027import java.util.HashMap;
028import java.util.HashSet;
029import java.util.Map;
030import java.util.Map.Entry;
031import java.util.Set;
032
033import org.apache.commons.logging.Log;
034import org.apache.commons.logging.LogFactory;
035import org.nuxeo.ecm.core.management.api.ProbeInfo;
036import org.nuxeo.ecm.core.management.api.ProbeManager;
037import org.nuxeo.ecm.core.management.statuses.HealthCheckResult;
038import org.nuxeo.runtime.api.Framework;
039import org.nuxeo.runtime.management.ManagementRuntimeException;
040import org.nuxeo.runtime.management.api.Probe;
041import org.nuxeo.runtime.management.api.ProbeStatus;
042
043public class ProbeManagerImpl implements ProbeManager {
044
045    protected static final Log log = LogFactory.getLog(ProbeManagerImpl.class);
046
047    protected final Map<Class<? extends Probe>, ProbeInfo> infosByTypes = new HashMap<>();
048
049    protected final Map<String, ProbeInfo> infosByShortcuts = new HashMap<>();
050
051    protected final Map<String, Probe> probesByShortcuts = new HashMap<>();
052
053    protected final Map<String, ProbeInfo> probesForHealthCheck = new HashMap<>();
054
055    protected final Set<ProbeInfo> failed = new HashSet<>();
056
057    protected final Set<ProbeInfo> succeed = new HashSet<>();
058
059    public static final String DEFAULT_HEALTH_CHECK_INTERVAL_SECONDS_PROPERTY = "nuxeo.healthcheck.refresh.interval.seconds";
060
061    public static final String DEFAULT_HEALTH_CHECK_INTERVAL_SECONDS = "20";
062
063    protected Set<String> doExtractProbesName(Collection<ProbeInfo> runners) {
064        Set<String> names = new HashSet<>();
065        for (ProbeInfo runner : runners) {
066            names.add(runner.getShortcutName());
067        }
068        return names;
069    }
070
071    @Override
072    public Collection<ProbeInfo> getAllProbeInfos() {
073        return Collections.unmodifiableCollection(infosByTypes.values());
074    }
075
076    @Override
077    public Collection<ProbeInfo> getInSuccessProbeInfos() {
078        return Collections.unmodifiableCollection(succeed);
079    }
080
081    @Override
082    public Collection<ProbeInfo> getInFailureProbeInfos() {
083        return Collections.unmodifiableCollection(failed);
084    }
085
086    @Override
087    public Collection<String> getProbeNames() {
088        return infosByShortcuts.keySet();
089    }
090
091    @Override
092    public int getProbesCount() {
093        return infosByTypes.size();
094    }
095
096    @Override
097    public Collection<String> getProbesInError() {
098        return doExtractProbesName(failed);
099    }
100
101    @Override
102    public int getProbesInErrorCount() {
103        return failed.size();
104    }
105
106    @Override
107    public Collection<String> getProbesInSuccess() {
108        return doExtractProbesName(succeed);
109    }
110
111    @Override
112    public int getProbesInSuccessCount() {
113        return succeed.size();
114    }
115
116    @Override
117    public ProbeInfo getProbeInfo(Class<? extends Probe> probeClass) {
118        ProbeInfo info = infosByTypes.get(probeClass);
119        if (info == null) {
120            throw new IllegalArgumentException("no probe registered for " + probeClass);
121        }
122        return info;
123    }
124
125    @Override
126    public boolean runAllProbes() {
127        doRun();
128        return getProbesInErrorCount() <= 0;
129    }
130
131    @Override
132    public ProbeInfo runProbe(ProbeInfo probe) {
133        doRunProbe(probe);
134        return probe;
135    }
136
137    @Override
138    public ProbeInfo runProbe(String name) {
139        ProbeInfo probeInfo = getProbeInfo(name);
140        if (probeInfo == null) {
141            log.warn("Probe " + name + " can not be found");
142            return null;
143        }
144        return runProbe(probeInfo);
145    }
146
147    @Override
148    public ProbeInfo getProbeInfo(String name) {
149        return infosByShortcuts.get(name);
150    }
151
152    public void registerProbe(ProbeDescriptor descriptor) {
153        Class<? extends Probe> probeClass = descriptor.getProbeClass();
154        Probe probe;
155        try {
156            probe = probeClass.getDeclaredConstructor().newInstance();
157        } catch (ReflectiveOperationException e) {
158            throw new ManagementRuntimeException("Cannot create management probe for " + descriptor);
159        }
160
161        ProbeInfoImpl info = new ProbeInfoImpl(descriptor);
162        infosByTypes.put(probeClass, info);
163        infosByShortcuts.put(descriptor.getShortcut(), info);
164        probesByShortcuts.put(descriptor.getShortcut(), probe);
165    }
166
167    public void unregisterProbe(ProbeDescriptor descriptor) {
168        Class<? extends Probe> probeClass = descriptor.getProbeClass();
169        infosByTypes.remove(probeClass);
170        infosByShortcuts.remove(descriptor.getShortcut());
171        probesForHealthCheck.remove(descriptor.getShortcut());
172    }
173
174    protected void doRun() {
175        for (ProbeInfo probe : infosByTypes.values()) {
176            doRunProbe(probe);
177        }
178    }
179
180    protected static Long doGetDuration(Date fromDate, Date toDate) {
181        return toDate.getTime() - fromDate.getTime();
182    }
183
184    protected void doRunProbe(ProbeInfo probe) {
185        if (!probe.isEnabled()) {
186            return;
187        }
188        boolean ok = false;
189        try {
190            ProbeInfoImpl probeInfoImpl = (ProbeInfoImpl) probe;
191            Thread currentThread = Thread.currentThread();
192            ClassLoader lastLoader = currentThread.getContextClassLoader();
193            currentThread.setContextClassLoader(ProbeInfoImpl.class.getClassLoader());
194            probeInfoImpl.lastRunnedDate = new Date();
195            probeInfoImpl.runnedCount += 1;
196            try {
197                Probe runnableProbe = probesByShortcuts.get(probe.getShortcutName());
198                probeInfoImpl.lastStatus = runnableProbe.run();
199                if (probeInfoImpl.lastStatus.isSuccess()) {
200                    probeInfoImpl.successCount += 1;
201                    probeInfoImpl.lastSucceedDate = probeInfoImpl.lastRunnedDate;
202                    probeInfoImpl.lastSuccessStatus = probeInfoImpl.lastStatus;
203                } else {
204                    probeInfoImpl.failureCount += 1;
205                    probeInfoImpl.lastFailureDate = probeInfoImpl.lastRunnedDate;
206                    probeInfoImpl.lastFailureStatus = probeInfoImpl.lastStatus;
207                }
208            } catch (RuntimeException e) {
209                probeInfoImpl.failureCount += 1;
210                probeInfoImpl.lastFailureDate = new Date();
211                probeInfoImpl.lastFailureStatus = ProbeStatus.newError(e);
212                // then swallow exception but wrap error to update status
213                probeInfoImpl.lastStatus = ProbeStatus.newError(e);
214            } finally {
215                probeInfoImpl.lastDuration = doGetDuration(probeInfoImpl.lastRunnedDate, new Date());
216                currentThread.setContextClassLoader(lastLoader);
217            }
218
219            if (probe.isInError()) {
220                succeed.remove(probe);
221                failed.add(probe);
222            } else {
223                failed.remove(probe);
224                succeed.add(probe);
225            }
226            ok = true;
227        } finally {
228            if (!ok) {
229                succeed.remove(probe);
230                failed.add(probe);
231            }
232        }
233    }
234
235    @Override
236    public void registerProbeForHealthCheck(HealthCheckProbesDescriptor descriptor) {
237        String name = descriptor.getName();
238        if (!descriptor.isEnabled()) {
239            probesForHealthCheck.remove(name);
240        } else if (infosByShortcuts.containsKey(name)) {
241            probesForHealthCheck.put(name, getProbeInfo(name));
242        }
243    }
244
245    @Override
246    public Collection<ProbeInfo> getHealthCheckProbes() {
247        return Collections.unmodifiableCollection(probesForHealthCheck.values());
248    }
249
250    @Override
251    public HealthCheckResult getOrRunHealthChecks() {
252        for (Entry<String, ProbeInfo> es : probesForHealthCheck.entrySet()) {
253            String probeName = es.getKey();
254            ProbeInfo probe = es.getValue();
255            if (probe == null) {
256                log.warn("Probe:" + probeName + " does not exist, skipping it for the health check");
257                continue;
258            }
259            getStatusOrRunProbe(probe, getDefaultCheckInterval());
260        }
261        return new HealthCheckResult(probesForHealthCheck.values());
262    }
263
264    @Override
265    public HealthCheckResult getOrRunHealthCheck(String name) throws IllegalArgumentException {
266
267        if (!probesForHealthCheck.containsKey(name)) {
268            throw new IllegalArgumentException("Probe:" + name + " does not exist, or not registed for the healthCheck");
269        }
270        ProbeInfo probe = probesForHealthCheck.get(name);
271        getStatusOrRunProbe(probe, getDefaultCheckInterval());
272        return new HealthCheckResult(Collections.singletonList(probe));
273    }
274
275    protected void getStatusOrRunProbe(ProbeInfo probe, int refreshSeconds) {
276        LocalDateTime now = LocalDateTime.now();
277        Date lastRunDate = probe.getLastRunnedDate();
278        LocalDateTime lastRunDateTime = lastRunDate != null ? LocalDateTime.ofInstant(lastRunDate.toInstant(),
279                ZoneId.systemDefault()) : LocalDateTime.MIN;
280        if (ChronoUnit.SECONDS.between(lastRunDateTime, now) > refreshSeconds) {
281            doRunProbe(probe);
282        }
283    }
284
285    private int getDefaultCheckInterval() {
286        return Integer.parseInt(Framework.getProperty(DEFAULT_HEALTH_CHECK_INTERVAL_SECONDS_PROPERTY,
287                DEFAULT_HEALTH_CHECK_INTERVAL_SECONDS));
288
289    }
290}