/* * 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. */ package org.apache.brooklyn.rest.filter; import java.util.Collections; import java.util.List; import java.util.Set; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState; import org.apache.brooklyn.rest.domain.ApiError; import org.apache.brooklyn.util.text.Strings; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableSet; import com.sun.jersey.api.model.AbstractMethod; import com.sun.jersey.spi.container.ContainerRequest; import com.sun.jersey.spi.container.ContainerRequestFilter; import com.sun.jersey.spi.container.ContainerResponseFilter; import com.sun.jersey.spi.container.ResourceFilter; import com.sun.jersey.spi.container.ResourceFilterFactory; /** * Checks that if the method or resource class corresponding to a request * has a {@link HaHotStateRequired} annotation, * that the server is in that state (and up). * Requests with {@link #SKIP_CHECK_HEADER} set as a header skip this check. * <p> * This follows a different pattern to {@link HaMasterCheckFilter} * as this needs to know the method being invoked. */ public class HaHotCheckResourceFilter implements ResourceFilterFactory { private static final Logger log = LoggerFactory.getLogger(HaHotCheckResourceFilter.class); private static final Set<ManagementNodeState> HOT_STATES = ImmutableSet.of( ManagementNodeState.MASTER, ManagementNodeState.HOT_STANDBY, ManagementNodeState.HOT_BACKUP); @Context private ManagementContext mgmt; public HaHotCheckResourceFilter() {} @VisibleForTesting public HaHotCheckResourceFilter(ManagementContext mgmt) { this.mgmt = mgmt; } private static class MethodFilter implements ResourceFilter, ContainerRequestFilter { private AbstractMethod am; private ManagementContext mgmt; public MethodFilter(AbstractMethod am, ManagementContext mgmt) { this.am = am; this.mgmt = mgmt; } @Override public ContainerRequestFilter getRequestFilter() { return this; } @Override public ContainerResponseFilter getResponseFilter() { return null; } private String lookForProblem(ContainerRequest request) { if (isSkipCheckHeaderSet(request)) return null; if (!isHaHotStateRequired(request)) return null; String problem = HaMasterCheckFilter.lookForProblemIfServerNotRunning(mgmt); if (Strings.isNonBlank(problem)) return problem; if (!isHaHotStatus()) return "server not in required HA hot state"; if (isStateNotYetValid()) return "server not yet completed loading data for required HA hot state"; return null; } @Override public ContainerRequest filter(ContainerRequest request) { String problem = lookForProblem(request); if (Strings.isNonBlank(problem)) { log.warn("Disallowing web request as "+problem+": "+request+"/"+am+" (caller should set '"+HaMasterCheckFilter.SKIP_CHECK_HEADER+"' to force)"); throw new WebApplicationException(ApiError.builder() .message("This request is only permitted against an active hot Brooklyn server") .errorCode(Response.Status.FORBIDDEN).build().asJsonResponse()); } return request; } // Maybe there should be a separate state to indicate that we have switched state // but still haven't finished rebinding. (Previously there was a time delay and an // isRebinding check, but introducing RebindManager#isAwaitingInitialRebind() seems cleaner.) private boolean isStateNotYetValid() { return mgmt.getRebindManager().isAwaitingInitialRebind(); } private boolean isHaHotStateRequired(ContainerRequest request) { return (am.getAnnotation(HaHotStateRequired.class) != null || am.getResource().getAnnotation(HaHotStateRequired.class) != null); } private boolean isSkipCheckHeaderSet(ContainerRequest request) { return "true".equalsIgnoreCase(request.getHeaderValue(HaMasterCheckFilter.SKIP_CHECK_HEADER)); } private boolean isHaHotStatus() { ManagementNodeState state = mgmt.getHighAvailabilityManager().getNodeState(); return HOT_STATES.contains(state); } } @Override public List<ResourceFilter> create(AbstractMethod am) { return Collections.<ResourceFilter>singletonList(new MethodFilter(am, mgmt)); } }