Skip to content

Commit

Permalink
New API
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleg Kovalov committed May 28, 2019
1 parent 5d91f39 commit 324b9b2
Show file tree
Hide file tree
Showing 9 changed files with 234 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ public enum ErrorCode {
PERMISSION_DENIED(FORBIDDEN),
UNKNOWN_MIGRATION(NOT_FOUND),
INVALID_QUERY(BAD_REQUEST),
IMPLEMENTATION_ABSENT(NOT_FOUND),
READ_ONLY_MODE(INTERNAL_SERVER_ERROR);
IMPLEMENTATION_ABSENT(NOT_FOUND);

private final int httpCode;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package pl.allegro.tech.hermes.api.endpoints;

import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
import pl.allegro.tech.hermes.api.BlacklistStatus;

import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;

@Path("mode")
public interface ModeEndpoint {

@GET
@Produces(APPLICATION_JSON)
String getMode();

@POST
@Produces(APPLICATION_JSON)
Response setMode(String mode);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@
import org.springframework.stereotype.Component;
import pl.allegro.tech.hermes.management.api.auth.Roles;
import pl.allegro.tech.hermes.management.domain.mode.ModeService;
import pl.allegro.tech.hermes.management.domain.mode.ModeService.ManagementMode;
import static pl.allegro.tech.hermes.management.domain.mode.ModeService.ManagementMode.*;

import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.HttpMethod;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
Expand All @@ -37,13 +40,20 @@ public String getMode() {
}

@POST
@Consumes(APPLICATION_JSON)
@Produces(APPLICATION_JSON)
@ApiOperation(value = "Change management mode", response = String.class, httpMethod = HttpMethod.POST)
@ApiOperation(value = "Set management mode", response = String.class, httpMethod = HttpMethod.POST)
@RolesAllowed(Roles.ADMIN)
public Response switchMode(@Context SecurityContext securityContext) {
modeService.toggleMode();

public Response setMode(@PathParam("mode") String mode) {
switch (mode) {
case "readWrite":
modeService.setMode(READ_WRITE);
break;
case "readOnly":
modeService.setMode(READ_ONLY);
break;
default:
return Response.status(Response.Status.BAD_REQUEST).build();
}
return Response.status(Response.Status.OK).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package pl.allegro.tech.hermes.management.api;

import static javax.servlet.http.HttpServletResponse.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.GenericFilterBean;
import pl.allegro.tech.hermes.management.domain.mode.ModeService;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

@WebFilter(urlPatterns = "/*")
public class ReadOnlyFilter extends GenericFilterBean {

private static final Logger logger = LoggerFactory.getLogger(ReadOnlyFilter.class);
private static final String READ_ONLY_ERROR_MESSAGE = "Action forbidden due to read-only mode";
private static final Set<String> whitelist = new HashSet<>();

private final ModeService modeService;

public ReadOnlyFilter(ModeService modeService) {
this.modeService = modeService;

initWhitelist();
}

@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;

if (modeService.isReadOnlyEnabled()) {
if (!req.getMethod().equals("GET") && !isWhitelisted(req.getRequestURI())) {
HttpServletResponse resp = ((HttpServletResponse) response);
resp.sendError(SC_SERVICE_UNAVAILABLE, READ_ONLY_ERROR_MESSAGE);
return;
}
}
chain.doFilter(request, response);
}

private boolean isWhitelisted(String requestURI) {
if (requestURI.startsWith("/query")) {
return true;
}
if (requestURI.startsWith("/mode")) {
return true;
}
return false;
}

private void initWhitelist() {
whitelist.add("/topics/query");
whitelist.add("topics/{topicName}/subscriptions/query");
whitelist.add("query/groups");
whitelist.add("query/topics");
whitelist.add("query/topics");
whitelist.add("query/subscriptions");
whitelist.add("query/topics/metrics");
whitelist.add("query/subscriptions/metrics");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,21 @@
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import static javax.servlet.DispatcherType.REQUEST;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import pl.allegro.tech.hermes.common.clock.ClockFactory;
import pl.allegro.tech.hermes.management.api.ReadOnlyFilter;
import pl.allegro.tech.hermes.management.domain.mode.ModeService;
import pl.allegro.tech.hermes.management.domain.subscription.SubscriptionLagSource;
import pl.allegro.tech.hermes.management.infrastructure.metrics.NoOpSubscriptionLagSource;

import javax.servlet.DispatcherType;
import java.time.Clock;
import java.util.EnumSet;

@Configuration
@EnableConfigurationProperties({TopicProperties.class, MetricsProperties.class, HttpClientProperties.class})
Expand Down Expand Up @@ -46,4 +52,13 @@ public SubscriptionLagSource consumerLagSource() {
public Clock clock() {
return new ClockFactory().provide();
}

@Bean
public FilterRegistrationBean<ReadOnlyFilter> readOnlyFilter(ModeService modeService) {
FilterRegistrationBean<ReadOnlyFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.setDispatcherTypes(REQUEST);
registrationBean.setFilter(new ReadOnlyFilter(modeService));
registrationBean.addUrlPatterns("/*");
return registrationBean;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,29 +5,41 @@
@Component
public class ModeService {
public enum ManagementMode {
NORMAL, READ_ONLY
READ_WRITE("readWrite"), READ_ONLY("readOnly");

private final String text;

ManagementMode(String text) {
this.text = text;
}

public String getText() {
return text;
}
}

private volatile ManagementMode mode = ManagementMode.NORMAL;
private volatile ManagementMode mode = ManagementMode.READ_WRITE;

public ManagementMode getMode() {
return mode;
}

public void setMode(ManagementMode mode) {
this.mode = mode;
}

public void toggleMode() {
switch (mode) {
case NORMAL:
case READ_WRITE:
mode = ManagementMode.READ_ONLY;
break;
case READ_ONLY:
mode = ManagementMode.NORMAL;
mode = ManagementMode.READ_WRITE;
break;
}
}

public void mustBeNormal() {
if (mode != ManagementMode.NORMAL) {
throw new ReadOnlyModeException();
}
public boolean isReadOnlyEnabled() {
return mode == ManagementMode.READ_WRITE;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import pl.allegro.tech.hermes.api.endpoints.BlacklistEndpoint;
import pl.allegro.tech.hermes.api.endpoints.GroupEndpoint;
import pl.allegro.tech.hermes.api.endpoints.MigrationEndpoint;
import pl.allegro.tech.hermes.api.endpoints.ModeEndpoint;
import pl.allegro.tech.hermes.api.endpoints.OAuthProviderEndpoint;
import pl.allegro.tech.hermes.api.endpoints.SubscriptionOwnershipEndpoint;
import pl.allegro.tech.hermes.api.endpoints.OwnerEndpoint;
Expand Down Expand Up @@ -114,6 +115,10 @@ public UnhealthyEndpoint unhealthyEndpoint() {
return createProxy(url, UnhealthyEndpoint.class, managementConfig);
}

public ModeEndpoint modeEndpoint() {
return createProxy(url, ModeEndpoint.class, managementConfig);
}

public BlacklistEndpoint createBlacklistEndpoint() {
return createProxy(url, BlacklistEndpoint.class, managementConfig);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import pl.allegro.tech.hermes.api.endpoints.BlacklistEndpoint;
import pl.allegro.tech.hermes.api.endpoints.GroupEndpoint;
import pl.allegro.tech.hermes.api.endpoints.MigrationEndpoint;
import pl.allegro.tech.hermes.api.endpoints.ModeEndpoint;
import pl.allegro.tech.hermes.api.endpoints.OAuthProviderEndpoint;
import pl.allegro.tech.hermes.api.endpoints.OwnerEndpoint;
import pl.allegro.tech.hermes.api.endpoints.QueryEndpoint;
Expand Down Expand Up @@ -41,7 +42,9 @@ public class HermesEndpoints {

private final MigrationEndpoint migrationEndpoint;

public final UnhealthyEndpoint unhealthyEndpoint;
private final UnhealthyEndpoint unhealthyEndpoint;

private final ModeEndpoint modeEndpoint;

public HermesEndpoints(Hermes hermes) {
this.groupEndpoint = hermes.createGroupEndpoint();
Expand All @@ -56,6 +59,7 @@ public HermesEndpoints(Hermes hermes) {
this.ownerEndpoint = hermes.createOwnerEndpoint();
this.migrationEndpoint = hermes.createMigrationEndpoint();
this.unhealthyEndpoint = hermes.unhealthyEndpoint();
this.modeEndpoint = hermes.modeEndpoint();
}

public HermesEndpoints(String hermesFrontendUrl, String consumerUrl) {
Expand Down Expand Up @@ -108,6 +112,10 @@ public BlacklistEndpoint blacklist() {
return blacklistEndpoint;
}

public ModeEndpoint modeEndpoint() {
return modeEndpoint;
}

public List<String> findTopics(Topic topic, boolean tracking) {
return topicEndpoint.list(topic.getName().getGroupName(), tracking);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package pl.allegro.tech.hermes.integration.management;

import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import pl.allegro.tech.hermes.api.Group;
import pl.allegro.tech.hermes.integration.IntegrationTest;
import static pl.allegro.tech.hermes.integration.test.HermesAssertions.assertThat;
import static pl.allegro.tech.hermes.management.domain.mode.ModeService.ManagementMode.READ_ONLY;
import static pl.allegro.tech.hermes.management.domain.mode.ModeService.ManagementMode.READ_WRITE;
import static pl.allegro.tech.hermes.test.helper.builder.GroupBuilder.group;

import javax.ws.rs.core.Response;

public class ReadOnlyModeTest extends IntegrationTest {

@BeforeMethod
public void initialize() {
management.modeEndpoint().setMode(READ_WRITE.getText());
}

@Test
public void shouldAllowNonModifyingOperations() {
// given
management.modeEndpoint().setMode(READ_WRITE.getText());
String groupName = "kek";

// when
Response response = createGroup(groupName);

// then
assertThat(response).hasStatus(Response.Status.CREATED);
}

@Test
public void shouldRestrictModifyingOperations() {
// given
management.modeEndpoint().setMode(READ_ONLY.getText());
String groupName = "lol";

// when
Response response = createGroup(groupName);

// then
assertThat(response).hasStatus(Response.Status.SERVICE_UNAVAILABLE);
}

@Test
public void shouldSwitchModeBack() {
// given
management.modeEndpoint().setMode(READ_ONLY.getText());
String groupName = "mda";

// when
Response response = createGroup(groupName);

// then
assertThat(response).hasStatus(Response.Status.SERVICE_UNAVAILABLE);

// and
management.modeEndpoint().setMode(READ_WRITE.getText());

// when
response = createGroup(groupName);

// then
assertThat(response).hasStatus(Response.Status.CREATED);
}

public Response createGroup(String groupName) {
Group group = group(groupName).build();
return management.group().create(group);
}
}

0 comments on commit 324b9b2

Please sign in to comment.