Author: ghenzler
Date: Thu Jan 17 20:52:13 2019
New Revision: 1851585
URL: http://svn.apache.org/viewvc?rev=1851585&view=rev
Log:
FELIX-6025 ScriptedHealthCheck initial version
Added:
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
(with props)
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
(with props)
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
(with props)
felix/trunk/healthcheck/generalchecks/src/test/resources/
felix/trunk/healthcheck/generalchecks/src/test/resources/org/
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy
Modified:
felix/trunk/healthcheck/generalchecks/pom.xml
Modified: felix/trunk/healthcheck/generalchecks/pom.xml
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/pom.xml?rev=1851585&r1=1851584&r2=1851585&view=diff
==============================================================================
--- felix/trunk/healthcheck/generalchecks/pom.xml (original)
+++ felix/trunk/healthcheck/generalchecks/pom.xml Thu Jan 17 20:52:13 2019
@@ -152,6 +152,12 @@
<version>1.9.5</version>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.codehaus.groovy</groupId>
+ <artifactId>groovy-all</artifactId>
+ <version>2.4.13</version>
+ <scope>test</scope>
+ </dependency>
<!-- END test scope dependencies -->
</dependencies>
Added:
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java?rev=1851585&view=auto
==============================================================================
---
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
(added)
+++
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
Thu Jan 17 20:52:13 2019
@@ -0,0 +1,276 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.generalchecks;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.StringWriter;
+import java.lang.reflect.Array;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import javax.script.Bindings;
+import javax.script.ScriptContext;
+import javax.script.ScriptEngine;
+import javax.script.SimpleBindings;
+import javax.script.SimpleScriptContext;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.felix.hc.api.FormattingResultLog;
+import org.apache.felix.hc.api.HealthCheck;
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog;
+import org.apache.felix.hc.generalchecks.util.ScriptEnginesTracker;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.ConfigurationPolicy;
+import org.osgi.service.component.annotations.Reference;
+import org.osgi.service.metatype.annotations.AttributeDefinition;
+import org.osgi.service.metatype.annotations.Designate;
+import org.osgi.service.metatype.annotations.ObjectClassDefinition;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** {@link HealthCheck} that runs an arbitrary script. */
+@Component(service = HealthCheck.class, configurationPolicy =
ConfigurationPolicy.REQUIRE)
+@Designate(ocd = ScriptedHealthCheck.Config.class, factory = true)
+public class ScriptedHealthCheck implements HealthCheck {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(ScriptedHealthCheck.class);
+
+ public static final String HC_LABEL = "Health Check: Script";
+
+ @ObjectClassDefinition(name = HC_LABEL, description = "Runs an arbitrary
script in given scriping language (via javax.script). "
+ + "The script has the following default bindings available: 'log',
'osgi' and 'bundleContext'. "
+ + "'log' is an instance of
org.apache.felix.hc.api.FormattingResultLog and is used to define the result of
the HC. "
+ + "'osgi.getService(classObj)' can be used as shortcut to retrieve
a service."
+ + "'osgi.getServices(classObj, filter)' used to retrieve multiple
services for a class using given filter. "
+ + "For all services retrieved via osgi binding, unget() is called
automatically at the end of the script execution."
+ + "'bundleContext' is available for advanced use cases. The script
does not need to return any value, but if it does and it is "
+ + "a org.apache.felix.hc.api.Result, that result and entries in
'log' are combined then).")
+ @interface Config {
+
+ @AttributeDefinition(name = "Name", description = "Name of this health
check.")
+ String hc_name() default "Scripted Health Check";
+
+ @AttributeDefinition(name = "Tags", description = "List of tags for
this health check, used to select subsets of health checks for execution e.g.
by a composite health check.")
+ String[] hc_tags() default {};
+
+ @AttributeDefinition(name = "Language", description = "The language
the script is written in. To use e.g. 'groovy', ensure osgi bundle 'groovy-all'
is available.")
+ String language() default "groovy";
+
+ @AttributeDefinition(name = "Script", description = "The script itself
(either use 'script' or 'scriptUrl').")
+ String script() default "log.info('ok'); log.warn('not so good');
log.critical('bad') // minimal example";
+
+ @AttributeDefinition(name = "Script Url", description = "Url to the
script to be used as alternative source (either use 'script' or 'scriptUrl').")
+ String scriptUrl() default "";
+
+ @AttributeDefinition
+ String webconsole_configurationFactory_nameHint() default "Scripted
HC: {hc.name} (tags: {hc.tags}) {scriptUrl} language: {language}";
+ }
+
+ private String language;
+ private String script;
+ private String scriptUrl;
+
+ private BundleContext bundleContext;
+
+ @Reference
+ private ScriptEnginesTracker scriptEnginesTracker;
+
+ @Activate
+ protected void activate(BundleContext context, Config config) {
+ this.bundleContext = context;
+ this.language = config.language().toLowerCase();
+ this.script = config.script();
+ this.scriptUrl = config.scriptUrl();
+
+ if(StringUtils.isNotBlank(script) &&
StringUtils.isNotBlank(scriptUrl)) {
+ LOG.info("Both 'script' and 'scriptUrl' (=()) are configured,
ignoring 'scriptUrl'", scriptUrl);
+ scriptUrl = null;
+ }
+
+ LOG.info("Activated Scripted HC "+config.hc_name()+" with "+
(StringUtils.isNotBlank(script)?"script "+script: "script url "+scriptUrl));
+
+ }
+
+ @Override
+ public Result execute() {
+ FormattingResultLog log = new FormattingResultLog();
+
+ ScriptEngine scriptEngine =
scriptEnginesTracker.getEngineByLanguage(language);
+ if(scriptEngine == null) {
+ log.healthCheckError("No ScriptEngineFactory found for language "+
language + " (available languages:
"+scriptEnginesTracker.getLanguagesByBundle()+")");
+ return new Result(log);
+ }
+
+ final Bindings bindings = new SimpleBindings();
+ final ScriptHelper osgi = new ScriptHelper(bundleContext);
+
+ StringWriter stdout = new StringWriter();
+ StringWriter stderr = new StringWriter();
+
+ bindings.put("osgi", osgi);
+ bindings.put("log", log);
+ bindings.put("bundleContext", bundleContext);
+
+ SimpleScriptContext scriptContext = new SimpleScriptContext();
+ scriptContext.setBindings(bindings, ScriptContext.ENGINE_SCOPE);
+ scriptContext.setWriter(stdout);
+ scriptContext.setErrorWriter(stderr);
+
+ try {
+ boolean urlIsUsed = StringUtils.isBlank(script);
+ String scriptToExecute = urlIsUsed ? getFileContents(scriptUrl):
script;
+ log.info("Executing script {} ({} lines)...",
(urlIsUsed?scriptUrl:" as configured"), scriptToExecute.split("\n").length);
+ log.debug(scriptToExecute);
+ Object scriptResult = scriptEngine.eval(scriptToExecute,
scriptContext);
+ appendStreamsToResult(log, stdout, stderr, scriptContext);
+
+ if(scriptResult instanceof Result) {
+ Result result = (Result) scriptResult;
+ for(ResultLog.Entry entry: result) {
+ log.add(entry);
+ }
+ } else if(scriptResult != null){
+ log.info("Script result: {}", scriptResult);
+ }
+
+
+ } catch (Exception e) {
+ log.healthCheckError("Exception during script execution: "+e, e);
+ } finally {
+ osgi.ungetServices();
+ }
+
+ return new Result(log);
+ }
+
+ private void appendStreamsToResult(FormattingResultLog log, StringWriter
stdout, StringWriter stderr, SimpleScriptContext scriptContext)
+ throws IOException {
+ scriptContext.getWriter().flush();
+ String stdoutStr = stdout.toString();
+ if(StringUtils.isNotBlank(stdoutStr)) {
+ log.info("stdout of script: {}", stdoutStr);
+ }
+
+ scriptContext.getErrorWriter().flush();
+ String stderrStr = stderr.toString();
+ if(StringUtils.isNotBlank(stderrStr)) {
+ log.critical("stderr of script: {}", stderrStr);
+ }
+ }
+
+ public String getFileContents(String url) {
+ String content;
+ try {
+ URLConnection conn = new URL(url).openConnection();
+ try (BufferedReader reader = new BufferedReader(new
InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
+ content = reader.lines().collect(Collectors.joining("\n"));
+ }
+ return content;
+ }catch(IOException e) {
+ throw new IllegalArgumentException("Could not read URL "+url+":
"+e, e);
+ }
+ }
+
+ // Script Helper for OSGi available as binding 'osgi'
+ class ScriptHelper {
+
+ private final BundleContext bundleContext;
+ private List<ServiceReference<?>> references;
+ private Map<String, Object> services;
+
+ public ScriptHelper(BundleContext bundleContext) {
+ this.bundleContext = bundleContext;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <ServiceType> ServiceType getService(Class<ServiceType> type) {
+ ServiceType service = (this.services == null ? null :
(ServiceType) this.services.get(type.getName()));
+ if (service == null) {
+ final ServiceReference<?> ref =
this.bundleContext.getServiceReference(type.getName());
+ if (ref != null) {
+ service = (ServiceType) this.bundleContext.getService(ref);
+ if (service != null) {
+ if (this.services == null) {
+ this.services = new HashMap<String, Object>();
+ }
+ if (this.references == null) {
+ this.references = new
ArrayList<ServiceReference<?>>();
+ }
+ this.references.add(ref);
+ this.services.put(type.getName(), service);
+ }
+ }
+ }
+ return service;
+ }
+
+ public <T> T[] getServices(Class<T> serviceType, String filter)
throws InvalidSyntaxException {
+ final ServiceReference<?>[] refs =
this.bundleContext.getServiceReferences(serviceType.getName(), filter);
+ T[] result = null;
+ if (refs != null) {
+ final List<T> objects = new ArrayList<T>();
+ for (int i = 0; i < refs.length; i++) {
+ @SuppressWarnings("unchecked")
+ final T service = (T)
this.bundleContext.getService(refs[i]);
+ if (service != null) {
+ if (this.references == null) {
+ this.references = new
ArrayList<ServiceReference<?>>();
+ }
+ this.references.add(refs[i]);
+ objects.add(service);
+ }
+ }
+ if (objects.size() > 0) {
+ @SuppressWarnings("unchecked")
+ T[] srv = (T[]) Array.newInstance(serviceType,
objects.size());
+ result = objects.toArray(srv);
+ }
+ }
+ return result;
+ }
+
+ public void ungetServices() {
+ if (this.references != null) {
+ final Iterator<ServiceReference<?>> i =
this.references.iterator();
+ while (i.hasNext()) {
+ final ServiceReference<?> ref = i.next();
+ this.bundleContext.ungetService(ref);
+ }
+ this.references.clear();
+ }
+ if (this.services != null) {
+ this.services.clear();
+ }
+ }
+ }
+
+}
Propchange:
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheck.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java?rev=1851585&view=auto
==============================================================================
---
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
(added)
+++
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
Thu Jan 17 20:52:13 2019
@@ -0,0 +1,194 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.generalchecks.util;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineFactory;
+
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleEvent;
+import org.osgi.framework.BundleListener;
+import org.osgi.service.component.annotations.Activate;
+import org.osgi.service.component.annotations.Component;
+import org.osgi.service.component.annotations.Deactivate;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/** Simple service to track script engines available via osgi bundles that
define META-INF/services/javax.script.ScriptEngineFactory, e.g. like
groovy-all. */
+@Component(immediate=true, service = ScriptEnginesTracker.class)
+public class ScriptEnginesTracker implements BundleListener {
+ private static final Logger LOG =
LoggerFactory.getLogger(ScriptEnginesTracker.class);
+
+ private static final String ENGINE_FACTORY_SERVICE = "META-INF/services/"
+ ScriptEngineFactory.class.getName();
+ private final Map<String, ScriptEngineFactory> enginesByLanguage = new
ConcurrentHashMap<String, ScriptEngineFactory>();
+ private final Map<Bundle, List<String>> languagesByBundle = new
ConcurrentHashMap<Bundle, List<String>>();
+
+ /** ServiceTracker for ScriptEngineFactory */
+ private BundleContext context;
+
+ @Activate
+ public void activate(BundleContext context) {
+ this.context = context;
+ this.context.addBundleListener(this);
+ registerInitialScriptEngineFactories();
+ }
+
+ @Deactivate
+ public void deactivate() {
+ this.context.removeBundleListener(this);
+
+ enginesByLanguage.clear();
+ languagesByBundle.clear();
+ }
+
+ public ScriptEngine getEngineByLanguage(String language) {
+ ScriptEngineFactory factory =
enginesByLanguage.get(language.toLowerCase());
+ if (factory == null) {
+ return null;
+ }
+
+ ScriptEngine engine = factory.getScriptEngine();
+ return engine;
+ }
+
+ public Map<Bundle, List<String>> getLanguagesByBundle() {
+ return languagesByBundle;
+ }
+
+
+ public void bundleChanged(BundleEvent event) {
+ if (event.getType() == BundleEvent.STARTED &&
event.getBundle().getEntry(ENGINE_FACTORY_SERVICE) != null) {
+ registerFactories(event.getBundle());
+ } else if (event.getType() == BundleEvent.STOPPED) {
+ unregisterFactories(event.getBundle());
+ }
+ }
+
+
+ private void registerInitialScriptEngineFactories() {
+ Bundle[] bundles = this.context.getBundles();
+ for (Bundle bundle : bundles) {
+ if ( bundle.getState() == Bundle.ACTIVE &&
bundle.getEntry(ENGINE_FACTORY_SERVICE)!=null) {
+ registerFactories(bundle);
+ }
+ }
+ }
+
+ private void registerFactories(Bundle bundle) {
+ List<ScriptEngineFactory> scriptEngineFactoriesForBundle =
getScriptEngineFactoriesForBundle(bundle);
+ for (ScriptEngineFactory scriptEngineFactory :
scriptEngineFactoriesForBundle) {
+ registerFactory(bundle, scriptEngineFactory);
+
+ }
+ }
+
+ private void unregisterFactories(Bundle bundle) {
+ List<String> languagesForBundle = languagesByBundle.get(bundle);
+ for (String lang : languagesForBundle) {
+ ScriptEngineFactory removed = enginesByLanguage.remove(lang);
+ LOG.info("Removing ScriptEngine {} for language {}", removed,
lang);
+ }
+ }
+
+
+ @SuppressWarnings("unchecked")
+ private List<ScriptEngineFactory> getScriptEngineFactoriesForBundle(final
Bundle bundle) {
+ URL url = bundle.getEntry(ENGINE_FACTORY_SERVICE);
+ InputStream ins = null;
+
+ List<ScriptEngineFactory> scriptEngineFactoriesInBundle = new
ArrayList<ScriptEngineFactory>();
+
+ try {
+ ins = url.openStream();
+ BufferedReader reader = new BufferedReader(new
InputStreamReader(ins));
+ for (String className : getClassNames(reader)) {
+ try {
+ Class<ScriptEngineFactory> clazz =
(Class<ScriptEngineFactory>) bundle.loadClass(className);
+ ScriptEngineFactory spi = clazz.newInstance();
+ scriptEngineFactoriesInBundle.add(spi);
+
+ } catch (Throwable t) {
+ LOG.error("Cannot register ScriptEngineFactory {}",
className, t);
+ }
+ }
+
+ } catch (IOException ioe) {
+ LOG.warn("Exception while trying to load factories as defined in
{}", ENGINE_FACTORY_SERVICE, ioe);
+ } finally {
+ closeQuietly(ins);
+ }
+
+ return scriptEngineFactoriesInBundle;
+ }
+
+ private void closeQuietly(InputStream ins) {
+ if (ins != null) {
+ try {
+ ins.close();
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+ }
+
+ private void registerFactory(Bundle bundle, final ScriptEngineFactory
factory) {
+ LOG.info("Adding ScriptEngine {}, {} for language {}, {}",
+ factory.getEngineName(), factory.getEngineVersion(),
+ factory.getLanguageName(), factory.getLanguageVersion());
+
+ String scriptLang = factory.getLanguageName().toLowerCase();
+
+ enginesByLanguage.put(scriptLang, factory);
+
+ List<String> languages = languagesByBundle.get(bundle);
+ if(languages==null) {
+ languages = new ArrayList<String>();
+ languagesByBundle.put(bundle, languages);
+ }
+ languages.add(scriptLang);
+ }
+
+
+ static List<String> getClassNames(BufferedReader reader) throws
IOException {
+ List<String> classNames = new ArrayList<String>();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ if (!line.startsWith("#") && line.trim().length() > 0) {
+ int indexOfHash = line.indexOf('#');
+ if (indexOfHash >= 0) {
+ line = line.substring(0, indexOfHash);
+ }
+ line = line.trim();
+ classNames.add(line);
+ }
+ }
+ return classNames;
+ }
+
+}
Propchange:
felix/trunk/healthcheck/generalchecks/src/main/java/org/apache/felix/hc/generalchecks/util/ScriptEnginesTracker.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java?rev=1851585&view=auto
==============================================================================
---
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
(added)
+++
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
Thu Jan 17 20:52:13 2019
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+package org.apache.felix.hc.generalchecks;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.mockito.MockitoAnnotations.initMocks;
+
+import java.lang.annotation.Annotation;
+import java.net.URL;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+
+import org.apache.felix.hc.api.Result;
+import org.apache.felix.hc.api.ResultLog.Entry;
+import org.apache.felix.hc.generalchecks.util.ScriptEnginesTracker;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.Spy;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
+
+public class ScriptedHealthCheckTest {
+
+ private static final String GROOVY = "Groovy";
+
+
+ @Spy
+ @InjectMocks
+ ScriptedHealthCheck scriptedHealthCheck;
+
+ @Spy
+ private ScriptEnginesTracker scriptEnginesTracker;
+
+ @Mock
+ private BundleContext bundleContext;
+
+ @Mock
+ private ServiceReference<TestService> testServiceReference;
+
+ @Mock
+ private TestService testService;
+
+ @Before
+ public void setup() {
+ initMocks(this);
+ ScriptEngineManager factory = new ScriptEngineManager();
+ // create JavaScript engine
+ ScriptEngine groovyEngine = factory.getEngineByName(GROOVY);
+
doReturn(groovyEngine).when(scriptEnginesTracker).getEngineByLanguage(GROOVY.toLowerCase());
+ }
+
+
+
+ @Test
+ public void testSimpleStatusValues() {
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
"log.info('good')", ""));
+ assertEquals(Result.Status.OK,
scriptedHealthCheck.execute().getStatus());
+
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
"log.warn('not so good')", ""));
+ assertEquals(Result.Status.WARN,
scriptedHealthCheck.execute().getStatus());
+
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
"log.critical('bad')", ""));
+ assertEquals(Result.Status.CRITICAL,
scriptedHealthCheck.execute().getStatus());
+
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
"log.temporarilyUnavailable('tmp away')", ""));
+ assertEquals(Result.Status.TEMPORARILY_UNAVAILABLE,
scriptedHealthCheck.execute().getStatus());
+
+ }
+
+ @Test
+ public void testExceptionInScript() {
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
"throw new IllegalStateException()", ""));
+ assertEquals(Result.Status.HEALTH_CHECK_ERROR,
scriptedHealthCheck.execute().getStatus());
+ }
+
+ @Test
+ public void testWithScriptUrl() {
+ URL scriptUrl = getClass().getResource("testHcScript.groovy");
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY, "",
scriptUrl.toString()));
+ Result result = scriptedHealthCheck.execute();
+ assertEquals(Result.Status.OK, result.getStatus());
+
+ List<Entry> entries = getEntries(result);
+ assertEquals(3, entries.size());
+ assertEquals("Test Script URL",
entries.get(entries.size()-1).getMessage());
+ }
+
+ @Test
+ public void testStdOut() {
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
"print 'test'", ""));
+ Result result = scriptedHealthCheck.execute();
+
+ List<Entry> entries = getEntries(result);
+ assertEquals(3, entries.size());
+ assertEquals("stdout of script: test",
entries.get(entries.size()-1).getMessage());
+ }
+
+ @Test
+ public void testCombinedResult() {
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
+ "import org.apache.felix.hc.api.*\n" +
+ "log.info('test1')\n" +
+ "return new Result(Result.Status.WARN, 'warn')",""));
+ Result result = scriptedHealthCheck.execute();
+ assertEquals(Result.Status.WARN, result.getStatus());
+
+ List<Entry> entries = getEntries(result);
+ assertEquals(4, entries.size());
+ assertEquals("test1", entries.get(entries.size()-2).getMessage());
+ assertEquals("warn", entries.get(entries.size()-1).getMessage());
+ }
+
+ @Test
+ public void testArbitraryResultLogged() {
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
+ "return 'ARBITRARY_RESULT_OBJECT'",""));
+ Result result = scriptedHealthCheck.execute();
+ assertEquals(Result.Status.OK, result.getStatus());
+
+ List<Entry> entries = getEntries(result);
+ assertEquals(3, entries.size());
+ assertEquals("Script result: ARBITRARY_RESULT_OBJECT",
entries.get(entries.size()-1).getMessage());
+ }
+
+ @Test
+ public void testOsgiBinding() {
+
+ TestService testService = new TestService();
+
doReturn(testServiceReference).when(bundleContext).getServiceReference(TestService.class.getName());
+
doReturn(testService).when(bundleContext).getService(testServiceReference);
+
+ scriptedHealthCheck.activate(bundleContext, new TestConfig(GROOVY,
+ "return
osgi.getService(org.apache.felix.hc.generalchecks.ScriptedHealthCheckTest.TestService.class)",""));
+
+ Result result = scriptedHealthCheck.execute();
+ assertEquals(Result.Status.OK, result.getStatus());
+ assertEquals("Script result: TestService",
getEntries(result).get(2).getMessage());
+
+ verify(bundleContext,
times(1)).getServiceReference(TestService.class.getName());
+ verify(bundleContext, times(1)).getService(testServiceReference);
+ verify(bundleContext, times(1)).ungetService(testServiceReference);
+ }
+
+
+ private List<Entry> getEntries(Result execute) {
+ return StreamSupport.stream(execute.spliterator(),
false).collect(Collectors.toList());
+ }
+
+ public class TestService {
+
+ @Override
+ public String toString() {
+ return "TestService";
+ }
+
+ }
+
+ private final class TestConfig implements ScriptedHealthCheck.Config {
+
+ private final String language;
+ private final String script;
+ private final String scriptUrl;
+
+ public TestConfig(String language, String script, String scriptUrl) {
+ super();
+ this.language = language;
+ this.script = script;
+ this.scriptUrl = scriptUrl;
+ }
+
+ @Override
+ public Class<? extends Annotation> annotationType() {
+ return ScriptedHealthCheck.Config.class;
+ }
+
+ @Override
+ public String webconsole_configurationFactory_nameHint() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String scriptUrl() {
+ return scriptUrl;
+ }
+
+ @Override
+ public String script() {
+ return script;
+ }
+
+ @Override
+ public String language() {
+ return language;
+ }
+
+ @Override
+ public String[] hc_tags() {
+ return new String[] {"test"};
+ }
+
+ @Override
+ public String hc_name() {
+ return "Test HC";
+ }
+ }
+
+}
Propchange:
felix/trunk/healthcheck/generalchecks/src/test/java/org/apache/felix/hc/generalchecks/ScriptedHealthCheckTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain
Added:
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy
URL:
http://svn.apache.org/viewvc/felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy?rev=1851585&view=auto
==============================================================================
---
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy
(added)
+++
felix/trunk/healthcheck/generalchecks/src/test/resources/org/apache/felix/hc/generalchecks/testHcScript.groovy
Thu Jan 17 20:52:13 2019
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The SF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations under the License.
+ */
+
+log.info('Test Script URL')
\ No newline at end of file