This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-hc-support.git
commit 1cffc3b369990f2c6ea9226f78b550d55b620be7 Author: Bertrand Delacretaz <[email protected]> AuthorDate: Thu Sep 26 13:17:13 2013 +0000 SLING-3127 - prepare Health Check tools release git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1526476 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 115 ++++++++ .../hc/support/impl/DefaultLoginsHealthCheck.java | 119 ++++++++ .../sling/hc/support/impl/InternalRequest.java | 319 +++++++++++++++++++++ .../sling/hc/support/impl/InternalResponse.java | 185 ++++++++++++ .../impl/SlingRequestStatusHealthCheck.java | 139 +++++++++ .../OSGI-INF/metatype/metatype.properties | 52 ++++ src/test/java/org/apache/sling/hc/healthcheck | 44 +++ .../healthchecks/DefaultLoginsHealthCheckTest.java | 70 +++++ .../org/apache/sling/hc/healthchecks/SetField.java | 30 ++ .../SlingRequestStatusHealthCheckTest.java | 96 +++++++ 10 files changed, 1169 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..ce31b77 --- /dev/null +++ b/pom.xml @@ -0,0 +1,115 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project + xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>17</version> + <relativePath/> + </parent> + + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.hc.support</artifactId> + <packaging>bundle</packaging> + <version>0.0.2-SNAPSHOT</version> + + <name>Sling Health Check Support Components</name> + <inceptionYear>2013</inceptionYear> + + <description> + Default Sling Health Check Support Components + </description> + + <properties> + <sling.java.version>6</sling.java.version> + </properties> + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.apache.felix.scr.annotations</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.hc.core</artifactId> + <version>0.0.2-SNAPSHOT</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.commons.osgi</artifactId> + <version>2.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.api</artifactId> + <version>2.1.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.jcr.api</artifactId> + <version>2.0.4</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.engine</artifactId> + <version>2.2.0</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + <version>1.6.2</version> + </dependency> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + </dependency> + <dependency> + <groupId>javax.jcr</groupId> + <artifactId>jcr</artifactId> + </dependency> + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-simple</artifactId> + <version>1.6.2</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + <dependency> + <groupId>org.mockito</groupId> + <artifactId>mockito-core</artifactId> + <version>1.9.5</version> + <scope>test</scope> + </dependency> + </dependencies> +</project> diff --git a/src/main/java/org/apache/sling/hc/support/impl/DefaultLoginsHealthCheck.java b/src/main/java/org/apache/sling/hc/support/impl/DefaultLoginsHealthCheck.java new file mode 100644 index 0000000..adb2b23 --- /dev/null +++ b/src/main/java/org/apache/sling/hc/support/impl/DefaultLoginsHealthCheck.java @@ -0,0 +1,119 @@ +/* + * 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.sling.hc.support.impl; + +import java.util.Arrays; +import java.util.List; + +import javax.jcr.Credentials; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.PropertyUnbounded; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.commons.osgi.PropertiesUtil; +import org.apache.sling.hc.api.HealthCheck; +import org.apache.sling.hc.api.Result; +import org.apache.sling.hc.util.FormattingResultLog; +import org.apache.sling.jcr.api.SlingRepository; +import org.osgi.service.component.ComponentContext; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** {@link HealthCheck} that checks that Sling default logins fail. + * Used to verify that those logins are disabled on production systems */ +@Component( + name="org.apache.sling.hc.support.DefaultLoginsHealthCheck", + configurationFactory=true, + policy=ConfigurationPolicy.REQUIRE, + metatype=true) +@Properties({ + @Property(name=HealthCheck.NAME), + @Property(name=HealthCheck.TAGS, unbounded=PropertyUnbounded.ARRAY), + @Property(name=HealthCheck.MBEAN_NAME) +}) +@Service(value=HealthCheck.class) +public class DefaultLoginsHealthCheck implements HealthCheck { + + private final Logger log = LoggerFactory.getLogger(getClass()); + + @Property(unbounded=PropertyUnbounded.ARRAY) + private static final String PROP_LOGINS = "logins"; + + private List<String> logins; + + @Reference + private SlingRepository repository; + + @Activate + public void activate(ComponentContext ctx) { + logins = Arrays.asList(PropertiesUtil.toStringArray(ctx.getProperties().get(PROP_LOGINS), new String[] {})); + log.info("Activated, logins={}", logins); + } + + @Override + public Result execute() { + final FormattingResultLog resultLog = new FormattingResultLog(); + int checked=0; + int failures=0; + + for(String login : logins) { + final String [] parts = login.split(":"); + if(parts.length != 2) { + resultLog.warn("Expected login in the form username:password, got [{}]", login); + continue; + } + checked++; + final String username = parts[0].trim(); + final String password = parts[1].trim(); + final Credentials creds = new SimpleCredentials(username, password.toCharArray()); + Session s = null; + try { + s = repository.login(creds); + if(s != null) { + failures++; + resultLog.warn("Login as [{}] succeeded, was expecting it to fail", username); + } else { + resultLog.debug("Login as [{}] didn't throw an Exception but returned null Session", username); + } + } catch(RepositoryException re) { + resultLog.debug("Login as [{}] failed, as expected", username); + } finally { + if(s != null) { + s.logout(); + } + } + } + + if(checked==0) { + resultLog.warn("Did not check any logins, configured logins={}", logins); + } else if(failures != 0){ + resultLog.warn("Checked {} logins, {} failures", checked, failures); + } else { + resultLog.debug("Checked {} logins, all successful", checked, failures); + } + return new Result(resultLog); + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/hc/support/impl/InternalRequest.java b/src/main/java/org/apache/sling/hc/support/impl/InternalRequest.java new file mode 100644 index 0000000..b79fdfe --- /dev/null +++ b/src/main/java/org/apache/sling/hc/support/impl/InternalRequest.java @@ -0,0 +1,319 @@ +/* + * 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.sling.hc.support.impl; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.StringReader; +import java.io.UnsupportedEncodingException; +import java.security.Principal; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Vector; + +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletInputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +public class InternalRequest implements HttpServletRequest { + + private final String path; + private final Map<String, Object> attributes = new HashMap<String, Object>(); + + public InternalRequest(String path) { + this.path = path; + } + + @Override + public Object getAttribute(String key) { + return attributes.get(key); + } + + @Override + public Enumeration<?> getAttributeNames() { + return new Vector<String>(attributes.keySet()).elements(); + } + + @Override + public String getCharacterEncoding() { + return "UTF-8"; + } + + @Override + public int getContentLength() { + return 0; + } + + @Override + public String getContentType() { + return "text/plain"; + } + + @Override + public ServletInputStream getInputStream() throws IOException { + return new ServletInputStream() { + @Override + public int read() throws IOException { + return 0; + } + }; + } + + @Override + public String getLocalAddr() { + return "127.0.0.1"; + } + + @Override + public String getLocalName() { + return "localhost"; + } + + @Override + public int getLocalPort() { + return 0; + } + + @Override + public Locale getLocale() { + return Locale.getDefault(); + } + + @Override + public Enumeration<?> getLocales() { + return new Vector<Locale>().elements(); + } + + @Override + public String getParameter(String arg0) { + return null; + } + + @Override + public Map<?,?> getParameterMap() { + return new HashMap<String, Object>(); + } + + @Override + public Enumeration<?> getParameterNames() { + return new Vector<String>().elements(); + } + + @Override + public String[] getParameterValues(String arg0) { + return null; + } + + @Override + public String getProtocol() { + return "http"; + } + + @Override + public BufferedReader getReader() throws IOException { + return new BufferedReader(new StringReader("")); + } + + @Override + public String getRealPath(String arg0) { + return path; + } + + @Override + public String getRemoteAddr() { + return "127.0.0.1"; + } + + @Override + public String getRemoteHost() { + return "localhost"; + } + + @Override + public int getRemotePort() { + return 1234; + } + + @Override + public RequestDispatcher getRequestDispatcher(String arg0) { + return null; + } + + @Override + public String getScheme() { + return "http"; + } + + @Override + public String getServerName() { + return "localhost"; + } + + @Override + public int getServerPort() { + return 80; + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public void removeAttribute(String arg0) { + } + + @Override + public void setAttribute(String key, Object value) { + attributes.put(key, value); + } + + @Override + public void setCharacterEncoding(String arg0) + throws UnsupportedEncodingException { + } + + @Override + public String getAuthType() { + return null; + } + + @Override + public String getContextPath() { + return ""; + } + + @Override + public Cookie[] getCookies() { + return null; + } + + @Override + public long getDateHeader(String arg0) { + return 0; + } + + @Override + public String getHeader(String arg0) { + return null; + } + + @Override + public Enumeration<?> getHeaderNames() { + return new Vector<String>().elements(); + } + + @Override + public Enumeration<?> getHeaders(String arg0) { + return new Vector<String>().elements(); + } + + @Override + public int getIntHeader(String arg0) { + return 0; + } + + @Override + public String getMethod() { + return "GET"; + } + + @Override + public String getPathInfo() { + return path; + } + + @Override + public String getPathTranslated() { + return path; + } + + @Override + public String getQueryString() { + return ""; + } + + @Override + public String getRemoteUser() { + return "remoteuser"; + } + + @Override + public String getRequestURI() { + return "http://localhost" + path; + } + + @Override + public StringBuffer getRequestURL() { + return new StringBuffer(getRequestURI()); + } + + @Override + public String getRequestedSessionId() { + return ""; + } + + @Override + public String getServletPath() { + return ""; + } + + @Override + public HttpSession getSession() { + return null; + } + + @Override + public HttpSession getSession(boolean arg0) { + return null; + } + + @Override + public Principal getUserPrincipal() { + return null; + } + + @Override + public boolean isRequestedSessionIdFromCookie() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromURL() { + return false; + } + + @Override + public boolean isRequestedSessionIdFromUrl() { + return false; + } + + @Override + public boolean isRequestedSessionIdValid() { + return false; + } + + @Override + public boolean isUserInRole(String arg0) { + return false; + } +} diff --git a/src/main/java/org/apache/sling/hc/support/impl/InternalResponse.java b/src/main/java/org/apache/sling/hc/support/impl/InternalResponse.java new file mode 100644 index 0000000..8752628 --- /dev/null +++ b/src/main/java/org/apache/sling/hc/support/impl/InternalResponse.java @@ -0,0 +1,185 @@ +/* + * 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.sling.hc.support.impl; + +import java.io.IOException; +import java.io.PrintWriter; +import java.util.Locale; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; + +public class InternalResponse implements HttpServletResponse { + + private int status = 200; + + private static class DevNullOutputStream extends ServletOutputStream { + @Override + public void write(int b) { + } + } + + @Override + public void flushBuffer() throws IOException { + } + + @Override + public int getBufferSize() { + return 0; + } + + @Override + public String getCharacterEncoding() { + return "UTF-8"; + } + + @Override + public String getContentType() { + return "text/plain"; + } + + @Override + public Locale getLocale() { + return Locale.getDefault(); + } + + @Override + public ServletOutputStream getOutputStream() throws IOException { + return new DevNullOutputStream(); + } + + @Override + public PrintWriter getWriter() throws IOException { + return new PrintWriter(new DevNullOutputStream()); + } + + @Override + public boolean isCommitted() { + return false; + } + + @Override + public void reset() { + } + + @Override + public void resetBuffer() { + } + + @Override + public void setBufferSize(int arg0) { + } + + @Override + public void setCharacterEncoding(String arg0) { + } + + @Override + public void setContentLength(int arg0) { + } + + @Override + public void setContentType(String arg0) { + } + + @Override + public void setLocale(Locale arg0) { + } + + @Override + public void addCookie(Cookie arg0) { + } + + @Override + public void addDateHeader(String arg0, long arg1) { + } + + @Override + public void addHeader(String arg0, String arg1) { + } + + @Override + public void addIntHeader(String arg0, int arg1) { + } + + @Override + public boolean containsHeader(String arg0) { + return false; + } + + @Override + public String encodeRedirectURL(String url) { + return url; + } + + @Override + public String encodeRedirectUrl(String url) { + return url; + } + + @Override + public String encodeURL(String url) { + return url; + } + + @Override + public String encodeUrl(String url) { + return url; + } + + @Override + public void sendError(int s, String arg1) throws IOException { + status = s; + } + + @Override + public void sendError(int s) throws IOException { + status = s; + } + + @Override + public void sendRedirect(String arg0) throws IOException { + } + + @Override + public void setDateHeader(String arg0, long arg1) { + } + + @Override + public void setHeader(String arg0, String arg1) { + } + + @Override + public void setIntHeader(String arg0, int arg1) { + } + + @Override + public void setStatus(int s, String arg1) { + status = s; + } + + @Override + public void setStatus(int s) { + status = s; + } + + public int getStatus() { + return status; + } +} diff --git a/src/main/java/org/apache/sling/hc/support/impl/SlingRequestStatusHealthCheck.java b/src/main/java/org/apache/sling/hc/support/impl/SlingRequestStatusHealthCheck.java new file mode 100644 index 0000000..968174e --- /dev/null +++ b/src/main/java/org/apache/sling/hc/support/impl/SlingRequestStatusHealthCheck.java @@ -0,0 +1,139 @@ +/* + * 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.sling.hc.support.impl; + +import java.util.Arrays; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.apache.felix.scr.annotations.Properties; +import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.PropertyUnbounded; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.commons.osgi.PropertiesUtil; +import org.apache.sling.engine.SlingRequestProcessor; +import org.apache.sling.hc.api.HealthCheck; +import org.apache.sling.hc.api.Result; +import org.apache.sling.hc.util.FormattingResultLog; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** {@link HealthCheck} that checks the HTTP status of Sling requests. + * Typically used to check that a freshly installed Sling-based system + * is in good shape, contains all required content etc. */ +@Component( + name="org.apache.sling.hc.support.SlingRequestStatusHealthCheck", + configurationFactory=true, + policy=ConfigurationPolicy.REQUIRE, + metatype=true) +@Properties({ + @Property(name=HealthCheck.NAME), + @Property(name=HealthCheck.TAGS, unbounded=PropertyUnbounded.ARRAY), + @Property(name=HealthCheck.MBEAN_NAME) +}) +@Service(value=HealthCheck.class) +public class SlingRequestStatusHealthCheck implements HealthCheck { + + private static final Logger log = LoggerFactory.getLogger(SlingRequestStatusHealthCheck.class); + private String [] paths; + + static class PathSpec { + int status; + String path; + + PathSpec(String configuredPath, FormattingResultLog resultLog) { + path = configuredPath; + status = 200; + + final String [] parts = configuredPath.split(":"); + if(parts.length == 2) { + try { + status = Integer.valueOf(parts[1].trim()); + path = parts[0].trim(); + } catch(NumberFormatException nfe) { + resultLog.healthCheckError("NumberFormatException while parsing [{}], invalid status value?", configuredPath); + } + } + } + } + + @Property(unbounded=PropertyUnbounded.ARRAY) + private static final String PROP_PATH = "path"; + + @Reference + private SlingRequestProcessor requestProcessor; + + @Reference + private ResourceResolverFactory resolverFactory; + + @Activate + public void activate(final Map<String, Object> properties) { + paths = PropertiesUtil.toStringArray(properties.get(PROP_PATH), new String [] {}); + log.info("Activated, paths={}", Arrays.asList(paths)); + } + + @Override + public Result execute() { + final FormattingResultLog resultLog = new FormattingResultLog(); + + ResourceResolver resolver = null; + int checked = 0; + int failed = 0; + String lastPath = null; + + try { + resolver = resolverFactory.getAdministrativeResourceResolver(null); + for(String p : paths) { + lastPath = p; + final PathSpec ps = new PathSpec(p, resultLog); + final HttpServletRequest request = new InternalRequest(ps.path); + final InternalResponse response = new InternalResponse(); + requestProcessor.processRequest(request, response, resolver); + final int status = response.getStatus(); + if(status != ps.status) { + failed++; + resultLog.warn("[{}] returns status {}, expected {}", new Object[] { ps.path, status, ps.status }); + } else { + resultLog.debug("[{}] returns status {} as expected", ps.path, status); + } + checked++; + } + } catch(Exception e) { + resultLog.warn("Exception while executing request [{}]: {}", lastPath, e); + } finally { + if(resolver != null) { + resolver.close(); + } + } + + if(checked == 0) { + resultLog.warn("No paths checked, empty paths list?"); + } else { + resultLog.debug("{} paths checked, {} failures", checked, failed); + } + + return new Result(resultLog); + } +} \ No newline at end of file diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties new file mode 100644 index 0000000..193b63b --- /dev/null +++ b/src/main/resources/OSGI-INF/metatype/metatype.properties @@ -0,0 +1,52 @@ +# +# 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 ASF 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. +# + +# +# This file contains localization strings for configuration labels and +# descriptions as used in the metatype.xml descriptor generated by the +# the Sling SCR plugin + +org.apache.sling.hc.DefaultLoginsHealthCheck.name = Apache Sling Default Logins Health Check +org.apache.sling.hc.DefaultLoginsHealthCheck.description = Expects default logins to fail, used to verify \ + that they are disabled on production systems + +org.apache.sling.hc.SlingRequestStatusHealthCheck.name = Apache Sling Sling Request Status Health Check +org.apache.sling.hc.SlingRequestStatusHealthCheck.description = Checks the HTTP status of Sling requests. + +hc.mbean.name.name = MBean name +hc.mbean.name.description = Name of the MBean to create for this Health Check. + +hc.tags.name = Health Check tags +hc.tags.description = List of tags for this Health Check service, used to select \ + subsets of Health Check services for execution. + +hc.name.name = Health Check Name +hc.name.description = Name of this Health Check service. Used for the MBean that's created \ + for this Health Check as well, unless a specific MBean name is configured. + +logins.name = Login credentials +logins.description = Which credentials to check. Each one is in the format "user:password" \ + like "admin:admin" for example. Do *not* put any confidential passwords here, the goal \ + is just to check that the default/demo logins, which passwords are known anyway, are \ + disabled. + +path.name = Paths to check +path.description = The list of paths to check, optionally with expected HTTP status responses. \ + An entry like "/tmp/test.txt:301", for example, checks that /tmp/test.txt returns a \ + 301 response. diff --git a/src/test/java/org/apache/sling/hc/healthcheck b/src/test/java/org/apache/sling/hc/healthcheck new file mode 100644 index 0000000..954f535 --- /dev/null +++ b/src/test/java/org/apache/sling/hc/healthcheck @@ -0,0 +1,44 @@ +package org.apache.sling.hc.impl.healthchecks; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.List; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; + +@RunWith(Parameterized.class) +public class PathSpecTest { + private final String pathSpec; + private final String expectedPath; + private final int expectedStatus; + + @Parameters(name="{1}") + public static List<Object[]> data() { + final List<Object[]> result = new ArrayList<Object[]>(); + + result.add(new Object[] { "/one.html", "/one.html", 200 } ); + result.add(new Object[] { "/two.html:404", "/two.html", 404 } ); + result.add(new Object[] { "three.html : 404 ", "three.html", 404 } ); + result.add(new Object[] { "four.html:not an integer", "four.html:not an integer", 200 } ); + result.add(new Object[] { "", "", 200 } ); + + return result; + } + + public PathSpecTest(String pathSpec, String expectedPath, int expectedStatus) { + this.pathSpec = pathSpec; + this.expectedPath = expectedPath; + this.expectedStatus = expectedStatus; + } + + @Test + public void testParsing() { + final SlingRequestStatusHealthCheck.PathSpec ps = new SlingRequestStatusHealthCheck.PathSpec(pathSpec); + assertEquals(expectedPath, ps.path); + assertEquals(expectedStatus, ps.status); + } +} diff --git a/src/test/java/org/apache/sling/hc/healthchecks/DefaultLoginsHealthCheckTest.java b/src/test/java/org/apache/sling/hc/healthchecks/DefaultLoginsHealthCheckTest.java new file mode 100644 index 0000000..862b6f8 --- /dev/null +++ b/src/test/java/org/apache/sling/hc/healthchecks/DefaultLoginsHealthCheckTest.java @@ -0,0 +1,70 @@ +/* + * 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.sling.hc.healthchecks; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; + +import javax.jcr.Credentials; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.sling.hc.api.Result; +import org.apache.sling.hc.support.impl.DefaultLoginsHealthCheck; +import org.apache.sling.jcr.api.SlingRepository; +import org.junit.Test; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +public class DefaultLoginsHealthCheckTest { + + private Result getTestResult(String login) throws Exception { + final DefaultLoginsHealthCheck c = new DefaultLoginsHealthCheck(); + SetField.set(c, "logins", Arrays.asList(new String[] { login })); + + final SlingRepository repo = Mockito.mock(SlingRepository.class); + SetField.set(c, "repository", repo); + final Session s = Mockito.mock(Session.class); + Mockito.when(repo.login(Matchers.any(Credentials.class))).thenAnswer(new Answer<Session>() { + @Override + public Session answer(InvocationOnMock invocation) { + final SimpleCredentials c = (SimpleCredentials)invocation.getArguments()[0]; + if("admin".equals(c.getUserID())) { + return s; + } + return null; + } + }); + + return c.execute(); + } + + @Test + public void testHealthCheckFails() throws Exception { + assertFalse("Expecting failed check", getTestResult("admin:admin").isOk()); + } + + @Test + public void testHealthCheckSucceeds() throws Exception { + assertTrue("Expecting successful check", getTestResult("FOO:bar").isOk()); + } +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/hc/healthchecks/SetField.java b/src/test/java/org/apache/sling/hc/healthchecks/SetField.java new file mode 100644 index 0000000..c4577a3 --- /dev/null +++ b/src/test/java/org/apache/sling/hc/healthchecks/SetField.java @@ -0,0 +1,30 @@ +/* + * 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.sling.hc.healthchecks; + +import java.lang.reflect.Field; + +public class SetField { + + public static void set(Object o, String name, Object value) throws Exception { + final Field f = o.getClass().getDeclaredField(name); + f.setAccessible(true); + f.set(o, value); + } + +} \ No newline at end of file diff --git a/src/test/java/org/apache/sling/hc/healthchecks/SlingRequestStatusHealthCheckTest.java b/src/test/java/org/apache/sling/hc/healthchecks/SlingRequestStatusHealthCheckTest.java new file mode 100644 index 0000000..6731465 --- /dev/null +++ b/src/test/java/org/apache/sling/hc/healthchecks/SlingRequestStatusHealthCheckTest.java @@ -0,0 +1,96 @@ +package org.apache.sling.hc.healthchecks; + +import static org.junit.Assert.assertEquals; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ResourceResolverFactory; +import org.apache.sling.engine.SlingRequestProcessor; +import org.apache.sling.hc.api.Result; +import org.apache.sling.hc.support.impl.SlingRequestStatusHealthCheck; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.mockito.Matchers; +import org.mockito.Mockito; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +@RunWith(Parameterized.class) +public class SlingRequestStatusHealthCheckTest { + private SlingRequestStatusHealthCheck hc; + private final Result.Status expectedStatus; + private final String [] paths; + + @Parameters(name="{0}") + public static List<Object[]> data() { + final List<Object[]> result = new ArrayList<Object[]>(); + result.add(new Object[] { "200.html:200,502.html:1234,436.json:436", Result.Status.WARN }); + + result.add(new Object[] { "", Result.Status.OK }); + result.add(new Object[] { "200.x", Result.Status.OK }); + result.add(new Object[] { "404.html:404", Result.Status.OK }); + result.add(new Object[] { "200.html:200,502.html:502,436.json:436" , Result.Status.OK }); + result.add(new Object[] { "200.html:1234,502.html:502,436.json:436" , Result.Status.WARN }); + result.add(new Object[] { "200.html:200,502.html:1234,436.json:436" , Result.Status.WARN }); + result.add(new Object[] { "200.html:200,502.html:502,436.json:1234" , Result.Status.WARN }); + result.add(new Object[] { "200.html:1234,502.html:1234,436.json:1234" , Result.Status.WARN }); + return result; + } + + @Before + public void setup() throws Exception { + hc = new SlingRequestStatusHealthCheck(); + + final ResourceResolverFactory rrf = Mockito.mock(ResourceResolverFactory.class); + SetField.set(hc, "resolverFactory", rrf); + + final Answer<Void> a = new Answer<Void> () { + @Override + public Void answer(InvocationOnMock invocation) { + final HttpServletRequest request = (HttpServletRequest)invocation.getArguments()[0]; + final HttpServletResponse response = (HttpServletResponse)invocation.getArguments()[1]; + final String path = request.getPathInfo(); + if(path.length() > 0) { + final String status = path.substring(0, path.indexOf('.')); + response.setStatus(Integer.valueOf(status)); + } + return null; + } + + }; + + final SlingRequestProcessor srp = Mockito.mock(SlingRequestProcessor.class); + SetField.set(hc, "requestProcessor", srp); + Mockito.doAnswer(a).when(srp).processRequest( + Matchers.any(HttpServletRequest.class), + Matchers.any(HttpServletResponse.class), + Matchers.any(ResourceResolver.class)); + + + final Map<String, Object> properties = new HashMap<String, Object>(); + properties.put("path", paths); + hc.activate(properties); + } + + public SlingRequestStatusHealthCheckTest(String paths, Result.Status expectedStatus) { + this.paths = paths.split(","); + this.expectedStatus = expectedStatus; + + } + + @Test + public void testResult() { + assertEquals("Expecting result " + expectedStatus + " for paths=" + paths, + expectedStatus, hc.execute().getStatus()); + } +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
