Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[controller] Add support for gRPC in venice-controller - Part I #1396

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

sushantmane
Copy link
Collaborator

@sushantmane sushantmane commented Dec 16, 2024

Add support for gRPC in venice-controller - Part I

This commit introduces foundational server-side code to support gRPC-based endpoints in the Venice Controller. To keep the change set manageable and facilitate incremental progress, this PR includes only 3 out of the 15 endpoints implemented in #1244. The remaining endpoints will be added in the subsequent PRs.

How was this PR tested?

UTs and E2E tests

Does this PR introduce any user-facing changes?

  • No. You can skip the rest of this section.
  • Yes. Make sure to explain your proposed changes and call out the behavior change.

sushantmane and others added 2 commits December 17, 2024 00:05
- Add controller handler service and gRPC server with secure mode
- Implement create store, get store, getAllStores, list stores APIs
- Add proto definitions for Stores and empty push
- Enable configuration to toggle gRPC server on/off
- Fix error handling and transport in ControllerClient for test compatibility
- Add additional routes: AdminCommandExecution, AdminTopicMetadata, Cluster, Version
- Update Docker setup for gRPC compatibility

Add experimental changes

Add integration test
@sushantmane sushantmane force-pushed the grpc-in-venice-control-plane-basics branch from fd9b817 to 405b106 Compare December 17, 2024 10:40
@sushantmane sushantmane force-pushed the grpc-in-venice-control-plane-basics branch from 405b106 to 3c79933 Compare December 17, 2024 10:45
@sushantmane sushantmane requested a review from FelixGV December 18, 2024 05:26
Copy link
Contributor

@FelixGV FelixGV left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finished my first pass on this PR... left a few questions and comments. Thanks for the hard work on this! Exciting stuff!

}

public boolean isTerminated() {
return server.isTerminated();
}

private boolean isSecure() {
return !(credentials instanceof InsecureServerCredentials);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that null instanceof Anything is always false. Right now, it doesn't seem like credentials can be null, but if they could be (e.g., due to some regression), then isSecure would return true (which would probably just result in a NPE sometime later, but in any event, it could be confusing?).

IDK what types can credentials take so IDK if a positive instanceof check is possible, or else maybe having a dedicated boolean flag for this, set in the same code path which sets the credentials prop. Or we can just leave it like this if you think it's fine. I don't have a strong opinion either way.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here credentials can not be null as always set it to some value in the constructor of VeniceGrpcServer. But I agree this could be confusing hence I'v updated the code to use dedicated boolean flag.

string methodType = 50001; // Assign a unique field number
}

service VeniceEchoService {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this for? Just some kind of hello world? Or a health check?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ya, this is hello world kind of service that is only used for testing of VeniceGrpcServer class

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right, np, but can we move this class to the test directory then?

map<string, ViewConfigGrpc> viewConfigs = 56;
}

message VersionGrpc {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a data modeling thought... it's not strictly gRPC-related, and also applies to the JSON stuff we shove into Zookeeper, but since we're building this one from scratch, we may have the option to reconsider some past decisions and do better (while leaving the JSON/ZK stuff unchanged -- at least for now)...

So the thing is that we have a lot of properties duplicated across store-version configs and store configs. And when that is the case, we implicitly understand it to mean that changing this config in the store config will affect future versions, and that we cannot change it in an existing version as those are immutable.

I wonder if we could codify this within the data model itself, and by the same token eliminate some duplication as well. How? By eliminating all of these props from the store config, and instead having a futureVersion record as one of the fields of the store config, which would house all of these configs.

IDK if the above idea is clear, please LMK either way. I'd like to know WDYT.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this idea a lot. I can update the PR to reflect this

Comment on lines -135 to +163
createServices();
this.pubSubTopicRepository = new PubSubTopicRepository();
this.controllerService = createControllerService();
this.adminServer = createAdminServer(false);
this.secureAdminServer = sslEnabled ? createAdminServer(true) : null;
this.topicCleanupService = createTopicCleanupService();
this.storeBackupVersionCleanupService = createStoreBackupVersionCleanupService();
this.disabledPartitionEnablerService = createDisabledPartitionEnablerService();
this.unusedValueSchemaCleanupService = createUnusedValueSchemaCleanupService();
this.storeGraveyardCleanupService = createStoreGraveyardCleanupService();
this.systemStoreRepairService = createSystemStoreRepairService();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like this kind of change. It should allow us to mark all/most of these properties final. Could you do that?

// Controller is SSL Enabled
responseObject.setSecureUrl(leaderController.getUrl(true));
}
requestHandler.getLeaderController(new ControllerRequest(cluster), responseObject);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So this function will populate the responseObject? Can we not call it a getter if that is the case? e.g., populateLeaderController, perhaps?

Comment on lines +133 to +162
public void createStore(
CreateStoreGrpcRequest grpcRequest,
StreamObserver<CreateStoreGrpcResponse> responseObserver) {
String clusterName = grpcRequest.getClusterStoreInfo().getClusterName();
String storeName = grpcRequest.getClusterStoreInfo().getStoreName();
LOGGER.info("Received gRPC request to create store: {} in cluster: {}", storeName, clusterName);
try {
// TODO (sushantmane) : Add the ACL check for allowlist users here

// Convert the gRPC request to the internal request object
CreateNewStoreRequest request = new CreateNewStoreRequest(
grpcRequest.getClusterStoreInfo().getClusterName(),
grpcRequest.getClusterStoreInfo().getStoreName(),
grpcRequest.hasOwner() ? grpcRequest.getOwner() : null,
grpcRequest.getKeySchema(),
grpcRequest.getValueSchema(),
grpcRequest.hasAccessPermission() ? grpcRequest.getAccessPermission() : null,
grpcRequest.getIsSystemStore());

// Create the store using the internal request object
NewStoreResponse response = new NewStoreResponse();
requestHandler.createStore(request, response);

// Convert the internal response object to the gRPC response object and send the gRPC response
responseObserver.onNext(
CreateStoreGrpcResponse.newBuilder()
.setClusterStoreInfo(getClusterStoreGrpcInfo(response))
.setOwner(response.getOwner())
.build());
responseObserver.onCompleted();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if instead of having an intermediate POJO representation in the form of CreateNewStoreRequest and NewStoreResponse, could we use the gRPC generated classes, CreateStoreGrpcRequest and CreateStoreGrpcResponse , directly as our POJOs?

I think the intermediate representations can make sense if in the long-term we plan to not migrate fully to gRPC, and/or support additional protocols (i.e., besides gRPC and REST, some third one).

But since the long-term direction is to go all in on gRPC, I think it would be fine to use the gRPC classes as our basic POJOs, and do this kind of marshalling only in the REST bridging code (as a temporary stop gap, during the migration). Then, when we're done with the migration we could delete the REST code and the remainder would be very lean, no marshalling into separate objects, etc.

In a nutshell, this means:

  1. requestHandler.createStore(request, response); would take the gRPC generated classes as param types.
  2. This class here should be greatly simplified.
  3. The REST class should be similar in size as in this PR, but transferring state into the gRPC POJOs instead of our own POJOs.

WDYT?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We discussed this during the AR, and Nisarg also suggested the same approach as you. However, my personal preference is to avoid mixing gRPC-generated classes throughout the codebase for two reasons:
1. If we decide to continue supporting HTTP/REST in the future, we would need to maintain HTTP ↔️ gRPC conversion logic
2. Using gRPC-generated classes directly in handlers would make unit testing more challenging since most of these generated classes are final

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants