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.Probe;
036import org.nuxeo.ecm.core.management.api.ProbeInfo;
037import org.nuxeo.ecm.core.management.api.ProbeManager;
038import org.nuxeo.ecm.core.management.api.ProbeStatus;
039import org.nuxeo.ecm.core.management.statuses.HealthCheckResult;
040import org.nuxeo.runtime.api.Framework;
041import org.nuxeo.runtime.management.ManagementRuntimeException;
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<Class<? extends Probe>, ProbeInfo>();
048
049    protected final Map<String, ProbeInfo> infosByShortcuts = new HashMap<String, ProbeInfo>();
050
051    protected final Map<String, Probe> probesByShortcuts = new HashMap<String, Probe>();
052
053    protected final Map<String, ProbeInfo> probesForHealthCheck = new HashMap<String, ProbeInfo>();
054
055    protected final Set<ProbeInfo> failed = new HashSet<ProbeInfo>();
056
057    protected final Set<ProbeInfo> succeed = new HashSet<ProbeInfo>();
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<String>();
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.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.lastSucceedDate = probeInfoImpl.lastRunnedDate;
201                    probeInfoImpl.lastSuccessStatus = probeInfoImpl.lastStatus;
202                    probeInfoImpl.successCount += 1;
203                } else {
204                    probeInfoImpl.lastFailureStatus = probeInfoImpl.lastStatus;
205                    probeInfoImpl.failureCount += 1;
206                    probeInfoImpl.lastFailureDate = probeInfoImpl.lastRunnedDate;
207                }
208            } catch (RuntimeException e) {
209                probeInfoImpl.failureCount += 1;
210                probeInfoImpl.lastFailureDate = new Date();
211                probeInfoImpl.lastFailureStatus = ProbeStatus.newError(e);
212                // then swallow exception
213            } finally {
214                probeInfoImpl.lastDuration = doGetDuration(probeInfoImpl.lastRunnedDate, new Date());
215                currentThread.setContextClassLoader(lastLoader);
216            }
217
218            if (probe.isInError()) {
219                succeed.remove(probe);
220                failed.add(probe);
221            } else {
222                failed.remove(probe);
223                succeed.add(probe);
224            }
225            ok = true;
226        } finally {
227            if (!ok) {
228                succeed.remove(probe);
229                failed.add(probe);
230            }
231        }
232    }
233
234    @Override
235    public void registerProbeForHealthCheck(HealthCheckProbesDescriptor descriptor) {
236        String name = descriptor.getName();
237        if (!descriptor.isEnabled()) {
238            if (probesForHealthCheck.containsKey(name)) {
239                probesForHealthCheck.remove(name);
240                return;
241            }
242        }
243        if (infosByShortcuts.containsKey(name)) {
244            probesForHealthCheck.put(name, getProbeInfo(name));
245        }
246    }
247
248    @Override
249    public Collection<ProbeInfo> getHealthCheckProbes() {
250        return Collections.unmodifiableCollection(probesForHealthCheck.values());
251    }
252
253    @Override
254    public HealthCheckResult getOrRunHealthChecks() {
255        for (Entry<String, ProbeInfo> es : probesForHealthCheck.entrySet()) {
256            String probeName = es.getKey();
257            ProbeInfo probe = es.getValue();
258            if (probe == null) {
259                log.warn("Probe:" + probeName + " does not exist, skipping it for the health check");
260                continue;
261            }
262            getStatusOrRunProbe(probe, getDefaultCheckInterval());
263        }
264        return new HealthCheckResult(probesForHealthCheck.values());
265    }
266
267    @Override
268    public HealthCheckResult getOrRunHealthCheck(String name) throws IllegalArgumentException {
269
270        if (!probesForHealthCheck.containsKey(name)) {
271            throw new IllegalArgumentException("Probe:" + name + " does not exist, or not registed for the healthCheck");
272        }
273        ProbeInfo probe = probesForHealthCheck.get(name);
274        getStatusOrRunProbe(probe, getDefaultCheckInterval());
275        return new HealthCheckResult(Collections.singletonList(probe));
276    }
277
278    protected void getStatusOrRunProbe(ProbeInfo probe, int refreshSeconds) {
279        LocalDateTime now = LocalDateTime.now();
280        Date lastRunDate = probe.getLastRunnedDate();
281        LocalDateTime lastRunDateTime = lastRunDate != null ? LocalDateTime.ofInstant(lastRunDate.toInstant(),
282                ZoneId.systemDefault()) : LocalDateTime.MIN;
283        if (ChronoUnit.SECONDS.between(lastRunDateTime, now) > refreshSeconds) {
284            doRunProbe(probe);
285        }
286    }
287
288    private int getDefaultCheckInterval() {
289        return Integer.parseInt(Framework.getProperty(DEFAULT_HEALTH_CHECK_INTERVAL_SECONDS_PROPERTY,
290                DEFAULT_HEALTH_CHECK_INTERVAL_SECONDS));
291
292    }
293}