Skip to content

Commit

Permalink
Add parameter to GET /accounts API to ignore containers information (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
Arun-LinkedIn authored Jan 30, 2025
1 parent e0d84fa commit f7d2c88
Show file tree
Hide file tree
Showing 7 changed files with 51 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,16 @@ abstract class AccountMixIn {

/**
* Serialize a collection of accounts to json bytes that can be used in requests/responses.
* @param accounts the {@link Account}s to serialize.
* @param accounts the {@link Account}s to serialize.
* @param ignoreContainers {@code true} to ignore containers in the serialization, {@code false} otherwise.
* @return the serialized bytes in json format.
*/
public static byte[] serializeAccountsInJson(Collection<Account> accounts) throws IOException {
public static byte[] serializeAccountsInJson(Collection<Account> accounts, boolean ignoreContainers)
throws IOException {
Map<String, Collection<Account>> resultObj = new HashMap<>();
resultObj.put(ACCOUNTS_KEY, accounts);
return objectMapper.writeValueAsBytes(resultObj);
return ignoreContainers ? objectMapperWithoutContainer.writeValueAsBytes(resultObj)
: objectMapper.writeValueAsBytes(resultObj);
}

/**
Expand Down
5 changes: 5 additions & 0 deletions ambry-api/src/main/java/com/github/ambry/rest/RestUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,11 @@ public static final class Headers {
* Request header to carry hostname (with port);
*/
public final static String HOSTNAME = "x-ambry-hostname";

/**
* Boolean field set to "true" for ignoring containers when fetching accounts information via GET /accounts API.
*/
public static final String IGNORE_CONTAINERS = "x-ambry-ignore-containers";
}

public static final class TrackingHeaders {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1825,7 +1825,7 @@ private void verifyCommonRequestCostHeaders(HttpResponse response, double expect
* @param accounts the accounts to replace or add using the {@code POST /accounts} call.
*/
void updateAccountsAndVerify(AccountService accountService, Account... accounts) throws Exception {
byte[] accountUpdateJson = AccountCollectionSerde.serializeAccountsInJson(Arrays.asList(accounts));
byte[] accountUpdateJson = AccountCollectionSerde.serializeAccountsInJson(Arrays.asList(accounts), false);
FullHttpRequest request =
buildRequest(HttpMethod.POST, Operations.ACCOUNTS, null, ByteBuffer.wrap(accountUpdateJson));
NettyClient.ResponseParts responseParts = nettyClient.sendRequest(request, null, null).get();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import com.github.ambry.account.AccountService;
import com.github.ambry.account.AccountServiceException;
import com.github.ambry.account.Container;
import com.github.ambry.account.Dataset;
import com.github.ambry.commons.ByteBufferReadableStreamChannel;
import com.github.ambry.commons.Callback;
import com.github.ambry.rest.RestRequest;
Expand Down Expand Up @@ -121,16 +120,19 @@ private Callback<Void> securityProcessRequestCallback() {
*/
private Callback<Void> securityPostProcessRequestCallback() {
return buildCallback(frontendMetrics.getAccountsSecurityPostProcessRequestMetrics, securityCheckResult -> {
byte[] serialized;
byte[] serializedAccountsOrContainers;
if (RestUtils.getRequestPath(restRequest).matchesOperation(ACCOUNTS_CONTAINERS)) {
LOGGER.debug("Received request for getting single container with arguments: {}", restRequest.getArgs());
Container container = getContainer();
serialized = AccountCollectionSerde.serializeContainersInJson(Collections.singletonList(container));
serializedAccountsOrContainers = AccountCollectionSerde.serializeContainersInJson(Collections.singletonList(container));
restResponseChannel.setHeader(RestUtils.Headers.TARGET_ACCOUNT_ID, container.getParentAccountId());
} else {
serialized = AccountCollectionSerde.serializeAccountsInJson(getAccounts());
boolean ignoreContainers =
RestUtils.getBooleanHeader(restRequest.getArgs(), RestUtils.Headers.IGNORE_CONTAINERS, false);
serializedAccountsOrContainers = AccountCollectionSerde.serializeAccountsInJson(getAccounts(), ignoreContainers);
}
ReadableStreamChannel channel = new ByteBufferReadableStreamChannel(ByteBuffer.wrap(serialized));
ReadableStreamChannel channel = new ByteBufferReadableStreamChannel(ByteBuffer.wrap(
serializedAccountsOrContainers));
restResponseChannel.setHeader(RestUtils.Headers.DATE, new GregorianCalendar().getTime());
restResponseChannel.setHeader(RestUtils.Headers.CONTENT_TYPE, RestUtils.JSON_CONTENT_TYPE);
restResponseChannel.setHeader(RestUtils.Headers.CONTENT_LENGTH, channel.getSize());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2349,7 +2349,8 @@ public void addGetAndDeleteDatasetTest() throws Exception {
public void postAccountsTest() throws Exception {
Account accountToAdd = accountService.generateRandomAccount();
List<ByteBuffer> body = new LinkedList<>();
body.add(ByteBuffer.wrap(AccountCollectionSerde.serializeAccountsInJson(Collections.singleton(accountToAdd))));
body.add(
ByteBuffer.wrap(AccountCollectionSerde.serializeAccountsInJson(Collections.singleton(accountToAdd), false)));
body.add(null);
RestRequest restRequest = createRestRequest(RestMethod.POST, Operations.ACCOUNTS, null, body);
MockRestResponseChannel restResponseChannel = new MockRestResponseChannel();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,33 @@ public void getSingleContainerSuccessTest() throws Exception {
existingContainer);
}

@Test
public void getAccountsWithoutContainersTest() throws Exception {
Account account = accountService.createAndAddRandomAccount();
ThrowingConsumer<RestRequest> testAction = (request) -> {
// Set the header to ignore containers
request.setArg(RestUtils.Headers.IGNORE_CONTAINERS, true);
RestResponseChannel restResponseChannel = new MockRestResponseChannel();
ReadableStreamChannel channel = sendRequestGetResponse(request, restResponseChannel);
assertNotNull("There should be a response", channel);
assertNotNull("Date has not been set", restResponseChannel.getHeader(RestUtils.Headers.DATE));
assertEquals("Content-type is not as expected", RestUtils.JSON_CONTENT_TYPE,
restResponseChannel.getHeader(RestUtils.Headers.CONTENT_TYPE));
assertEquals("Content-length is not as expected", channel.getSize(),
Integer.parseInt((String) restResponseChannel.getHeader(RestUtils.Headers.CONTENT_LENGTH)));
RetainingAsyncWritableChannel asyncWritableChannel = new RetainingAsyncWritableChannel((int) channel.getSize());
channel.readInto(asyncWritableChannel, null).get();
Account receivedAccount =
AccountCollectionSerde.accountsFromInputStreamInJson(asyncWritableChannel.consumeContentAsInputStream())
.iterator()
.next();
assertTrue("Accounts do not match", account.equalsWithoutContainers(receivedAccount));
assertTrue("Containers should not be present", receivedAccount.getAllContainers().isEmpty());
};
testAction.accept(createRestRequest(account.getName(), null, null, Operations.ACCOUNTS));
testAction.accept(createRestRequest(null, Short.toString(account.getId()), null, Operations.ACCOUNTS));
}

/**
* Test failure case of getting single container.
* @throws Exception
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public PostAccountsHandlerTest() {
@Test
public void validRequestsTest() throws Exception {
ThrowingConsumer<Collection<Account>> testAction = accountsToUpdate -> {
String requestBody = new String(AccountCollectionSerde.serializeAccountsInJson(accountsToUpdate));
String requestBody = new String(AccountCollectionSerde.serializeAccountsInJson(accountsToUpdate, false));
RestResponseChannel restResponseChannel = new MockRestResponseChannel();
sendRequestGetResponse(requestBody, restResponseChannel);
assertNotNull("Date has not been set", restResponseChannel.getHeader(RestUtils.Headers.DATE));
Expand Down Expand Up @@ -111,7 +111,7 @@ public void badRequestsTest() throws Exception {
testAction.accept(new JSONObject().append("accounts", "ABC").toString(), RestServiceErrorCode.BadRequest);
// AccountService update failure
accountService.setShouldUpdateSucceed(false);
testAction.accept(new String(AccountCollectionSerde.serializeAccountsInJson(Collections.emptyList())),
testAction.accept(new String(AccountCollectionSerde.serializeAccountsInJson(Collections.emptyList(), false)),
RestServiceErrorCode.InternalServerError);
}

Expand All @@ -123,7 +123,7 @@ public void badRequestsTest() throws Exception {
public void securityServiceDenialTest() throws Exception {
IllegalStateException injectedException = new IllegalStateException("@@expected");
TestUtils.ThrowingRunnable testAction = () -> sendRequestGetResponse(
new String(AccountCollectionSerde.serializeAccountsInJson(Collections.emptyList())),
new String(AccountCollectionSerde.serializeAccountsInJson(Collections.emptyList(), false)),
new MockRestResponseChannel());
ThrowingConsumer<IllegalStateException> errorChecker = e -> assertEquals("Wrong exception", injectedException, e);
securityServiceFactory.exceptionToReturn = injectedException;
Expand Down

0 comments on commit f7d2c88

Please sign in to comment.