diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml new file mode 100644 index 0000000..77c21f7 --- /dev/null +++ b/.github/workflows/main.yaml @@ -0,0 +1,116 @@ +name: Build and Deploy to GKE + +on: + push: + branches: + - main + +env: + PROJECT_ID: threadit-api + CLUSTER_NAME: threadit-cluster + ZONE: europe-west1-b + GCS_KEY: gcs-key + SERVICES: db community thread comment vote search popular + +jobs: + setup-build-publish-deploy: + name: Setup, Build, Publish, and Deploy + runs-on: ubuntu-latest + environment: production + + permissions: + contents: read + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Authenticate to Google Cloud + uses: google-github-actions/auth@v2 + with: + credentials_json: ${{ secrets.GCP_SA_KEY }} + + - name: Set up Google Cloud SDK + uses: google-github-actions/setup-gcloud@v2 + with: + project_id: ${{ env.PROJECT_ID }} + + - name: Check if GKE cluster exists + id: check-cluster + run: | + if gcloud container clusters describe $CLUSTER_NAME --zone $ZONE --project $PROJECT_ID; then + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "Cluster does not exist" + echo "exists=false" >> $GITHUB_OUTPUT + fi + + - name: Set up GKE credentials + if: steps.check-cluster.outputs.exists == 'true' + uses: google-github-actions/get-gke-credentials@v2 + with: + project_id: ${{ env.PROJECT_ID }} + cluster_name: ${{ env.CLUSTER_NAME }} + location: ${{ env.ZONE }} + + - name: Cluster not created. Skip deployment + if: steps.check-cluster.outputs.exists == 'false' + run: | + echo "Cluster doesn't exist — skipping deployment." + exit 0 + + - name: Configure Docker for GCR + run: | + gcloud auth configure-docker --quiet + + - name: Build and push images to GCR + working-directory: code/kubernetes + run: | + for SERVICE in $SERVICES; do + docker build -t gcr.io/$PROJECT_ID/${SERVICE}-service:latest -f services/${SERVICE}-service/Dockerfile . + docker push gcr.io/$PROJECT_ID/${SERVICE}-service:latest + done + + docker build -t gcr.io/$PROJECT_ID/grpc-gateway:latest -f grpc-gateway/Dockerfile . + docker push gcr.io/$PROJECT_ID/grpc-gateway:latest + + - name: Deploy Traefik + working-directory: code/kubernetes + run: | + helm repo add traefik https://traefik.github.io/charts + helm repo update + helm upgrade --install traefik traefik/traefik -n $CLUSTER_NAME -f traefik/values.yaml + + kubectl apply -n $CLUSTER_NAME -f traefik/cors.yaml + kubectl apply -n $CLUSTER_NAME -f traefik/strip-prefix.yaml + + - name: Create Kubernetes secrets + run: | + BUCKET_SECRET=$(gcloud secrets versions access latest --secret=$GCS_KEY) + MONGO_USER=$(gcloud secrets versions access latest --secret="mongo-user") + MONGO_PASS=$(gcloud secrets versions access latest --secret="mongo-pass") + + kubectl create secret generic "bucket-secret" \ + --from-literal="$GCS_KEY.json=$BUCKET_SECRET" \ + -n $CLUSTER_NAME --dry-run=client -o yaml | kubectl apply -f - + + kubectl create secret generic "mongo-secret" \ + --from-literal="MONGO_INITDB_ROOT_USERNAME=$MONGO_USER" \ + --from-literal="MONGO_INITDB_ROOT_PASSWORD=$MONGO_PASS" \ + -n $CLUSTER_NAME --dry-run=client -o yaml | kubectl apply -f - + + - name: Deploy configuration and Mongo + working-directory: code/kubernetes + run: | + kubectl apply -n $CLUSTER_NAME -f config.yaml + kubectl apply -n $CLUSTER_NAME -f mongo/ + + - name: Deploy services + working-directory: code/kubernetes + run: | + for SERVICE in $SERVICES; do + kubectl apply -n $CLUSTER_NAME -f services/${SERVICE}-service/ + done + + kubectl apply -n $CLUSTER_NAME -f grpc-gateway/ \ No newline at end of file diff --git a/code/gen/comment-service/pb/comment-service.pb.go b/code/gen/comment-service/pb/comment-service.pb.go index 7732cdc..36b53cd 100644 --- a/code/gen/comment-service/pb/comment-service.pb.go +++ b/code/gen/comment-service/pb/comment-service.pb.go @@ -434,8 +434,9 @@ const file_comment_service_proto_rawDesc = "" + "\f_vote_offsetB\x16\n" + "\x14_num_comments_offset\"&\n" + "\x14DeleteCommentRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id2\xec\x03\n" + - "\x0eCommentService\x12^\n" + + "\x02id\x18\x01 \x01(\tR\x02id2\xab\x04\n" + + "\x0eCommentService\x12=\n" + + "\vCheckHealth\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12^\n" + "\fListComments\x12\x1c.comment.ListCommentsRequest\x1a\x1d.comment.ListCommentsResponse\"\x11\x82\xd3\xe4\x93\x02\v\x12\t/comments\x12d\n" + "\rCreateComment\x12\x1d.comment.CreateCommentRequest\x1a\x1e.comment.CreateCommentResponse\"\x14\x82\xd3\xe4\x93\x02\x0e:\x01*\"\t/comments\x12Q\n" + "\n" + @@ -471,18 +472,20 @@ var file_comment_service_proto_goTypes = []any{ var file_comment_service_proto_depIdxs = []int32{ 7, // 0: comment.ListCommentsResponse.comments:type_name -> models.Comment 8, // 1: comment.CreateCommentRequest.parent_type:type_name -> models.CommentParentType - 0, // 2: comment.CommentService.ListComments:input_type -> comment.ListCommentsRequest - 2, // 3: comment.CommentService.CreateComment:input_type -> comment.CreateCommentRequest - 4, // 4: comment.CommentService.GetComment:input_type -> comment.GetCommentRequest - 5, // 5: comment.CommentService.UpdateComment:input_type -> comment.UpdateCommentRequest - 6, // 6: comment.CommentService.DeleteComment:input_type -> comment.DeleteCommentRequest - 1, // 7: comment.CommentService.ListComments:output_type -> comment.ListCommentsResponse - 3, // 8: comment.CommentService.CreateComment:output_type -> comment.CreateCommentResponse - 7, // 9: comment.CommentService.GetComment:output_type -> models.Comment - 9, // 10: comment.CommentService.UpdateComment:output_type -> google.protobuf.Empty - 9, // 11: comment.CommentService.DeleteComment:output_type -> google.protobuf.Empty - 7, // [7:12] is the sub-list for method output_type - 2, // [2:7] is the sub-list for method input_type + 9, // 2: comment.CommentService.CheckHealth:input_type -> google.protobuf.Empty + 0, // 3: comment.CommentService.ListComments:input_type -> comment.ListCommentsRequest + 2, // 4: comment.CommentService.CreateComment:input_type -> comment.CreateCommentRequest + 4, // 5: comment.CommentService.GetComment:input_type -> comment.GetCommentRequest + 5, // 6: comment.CommentService.UpdateComment:input_type -> comment.UpdateCommentRequest + 6, // 7: comment.CommentService.DeleteComment:input_type -> comment.DeleteCommentRequest + 9, // 8: comment.CommentService.CheckHealth:output_type -> google.protobuf.Empty + 1, // 9: comment.CommentService.ListComments:output_type -> comment.ListCommentsResponse + 3, // 10: comment.CommentService.CreateComment:output_type -> comment.CreateCommentResponse + 7, // 11: comment.CommentService.GetComment:output_type -> models.Comment + 9, // 12: comment.CommentService.UpdateComment:output_type -> google.protobuf.Empty + 9, // 13: comment.CommentService.DeleteComment:output_type -> google.protobuf.Empty + 8, // [8:14] is the sub-list for method output_type + 2, // [2:8] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name diff --git a/code/gen/comment-service/pb/comment-service_grpc.pb.go b/code/gen/comment-service/pb/comment-service_grpc.pb.go index 5ce5698..549d86b 100644 --- a/code/gen/comment-service/pb/comment-service_grpc.pb.go +++ b/code/gen/comment-service/pb/comment-service_grpc.pb.go @@ -21,6 +21,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( + CommentService_CheckHealth_FullMethodName = "/comment.CommentService/CheckHealth" CommentService_ListComments_FullMethodName = "/comment.CommentService/ListComments" CommentService_CreateComment_FullMethodName = "/comment.CommentService/CreateComment" CommentService_GetComment_FullMethodName = "/comment.CommentService/GetComment" @@ -32,6 +33,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CommentServiceClient interface { + CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) ListComments(ctx context.Context, in *ListCommentsRequest, opts ...grpc.CallOption) (*ListCommentsResponse, error) CreateComment(ctx context.Context, in *CreateCommentRequest, opts ...grpc.CallOption) (*CreateCommentResponse, error) GetComment(ctx context.Context, in *GetCommentRequest, opts ...grpc.CallOption) (*pb.Comment, error) @@ -47,6 +49,16 @@ func NewCommentServiceClient(cc grpc.ClientConnInterface) CommentServiceClient { return &commentServiceClient{cc} } +func (c *commentServiceClient) CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CommentService_CheckHealth_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *commentServiceClient) ListComments(ctx context.Context, in *ListCommentsRequest, opts ...grpc.CallOption) (*ListCommentsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListCommentsResponse) @@ -101,6 +113,7 @@ func (c *commentServiceClient) DeleteComment(ctx context.Context, in *DeleteComm // All implementations must embed UnimplementedCommentServiceServer // for forward compatibility. type CommentServiceServer interface { + CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) ListComments(context.Context, *ListCommentsRequest) (*ListCommentsResponse, error) CreateComment(context.Context, *CreateCommentRequest) (*CreateCommentResponse, error) GetComment(context.Context, *GetCommentRequest) (*pb.Comment, error) @@ -116,6 +129,9 @@ type CommentServiceServer interface { // pointer dereference when methods are called. type UnimplementedCommentServiceServer struct{} +func (UnimplementedCommentServiceServer) CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented") +} func (UnimplementedCommentServiceServer) ListComments(context.Context, *ListCommentsRequest) (*ListCommentsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListComments not implemented") } @@ -152,6 +168,24 @@ func RegisterCommentServiceServer(s grpc.ServiceRegistrar, srv CommentServiceSer s.RegisterService(&CommentService_ServiceDesc, srv) } +func _CommentService_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CommentServiceServer).CheckHealth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CommentService_CheckHealth_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CommentServiceServer).CheckHealth(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _CommentService_ListComments_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListCommentsRequest) if err := dec(in); err != nil { @@ -249,6 +283,10 @@ var CommentService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "comment.CommentService", HandlerType: (*CommentServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "CheckHealth", + Handler: _CommentService_CheckHealth_Handler, + }, { MethodName: "ListComments", Handler: _CommentService_ListComments_Handler, diff --git a/code/gen/community-service/pb/community-service.pb.go b/code/gen/community-service/pb/community-service.pb.go index d386cb6..54d8c03 100644 --- a/code/gen/community-service/pb/community-service.pb.go +++ b/code/gen/community-service/pb/community-service.pb.go @@ -391,8 +391,9 @@ const file_community_service_proto_rawDesc = "" + "\x05_nameB\x15\n" + "\x13_num_threads_offset\"(\n" + "\x16DeleteCommunityRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id2\xa8\x04\n" + - "\x10CommunityService\x12n\n" + + "\x02id\x18\x01 \x01(\tR\x02id2\xe7\x04\n" + + "\x10CommunityService\x12=\n" + + "\vCheckHealth\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12n\n" + "\x0fListCommunities\x12!.community.ListCommunitiesRequest\x1a\".community.ListCommunitiesResponse\"\x14\x82\xd3\xe4\x93\x02\x0e\x12\f/communities\x12q\n" + "\x0fCreateCommunity\x12!.community.CreateCommunityRequest\x1a\".community.CreateCommunityResponse\"\x17\x82\xd3\xe4\x93\x02\x11:\x01*\"\f/communities\x12\\\n" + "\fGetCommunity\x12\x1e.community.GetCommunityRequest\x1a\x11.models.Community\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/communities/{id}\x12j\n" + @@ -425,18 +426,20 @@ var file_community_service_proto_goTypes = []any{ } var file_community_service_proto_depIdxs = []int32{ 7, // 0: community.ListCommunitiesResponse.communities:type_name -> models.Community - 0, // 1: community.CommunityService.ListCommunities:input_type -> community.ListCommunitiesRequest - 2, // 2: community.CommunityService.CreateCommunity:input_type -> community.CreateCommunityRequest - 4, // 3: community.CommunityService.GetCommunity:input_type -> community.GetCommunityRequest - 5, // 4: community.CommunityService.UpdateCommunity:input_type -> community.UpdateCommunityRequest - 6, // 5: community.CommunityService.DeleteCommunity:input_type -> community.DeleteCommunityRequest - 1, // 6: community.CommunityService.ListCommunities:output_type -> community.ListCommunitiesResponse - 3, // 7: community.CommunityService.CreateCommunity:output_type -> community.CreateCommunityResponse - 7, // 8: community.CommunityService.GetCommunity:output_type -> models.Community - 8, // 9: community.CommunityService.UpdateCommunity:output_type -> google.protobuf.Empty - 8, // 10: community.CommunityService.DeleteCommunity:output_type -> google.protobuf.Empty - 6, // [6:11] is the sub-list for method output_type - 1, // [1:6] is the sub-list for method input_type + 8, // 1: community.CommunityService.CheckHealth:input_type -> google.protobuf.Empty + 0, // 2: community.CommunityService.ListCommunities:input_type -> community.ListCommunitiesRequest + 2, // 3: community.CommunityService.CreateCommunity:input_type -> community.CreateCommunityRequest + 4, // 4: community.CommunityService.GetCommunity:input_type -> community.GetCommunityRequest + 5, // 5: community.CommunityService.UpdateCommunity:input_type -> community.UpdateCommunityRequest + 6, // 6: community.CommunityService.DeleteCommunity:input_type -> community.DeleteCommunityRequest + 8, // 7: community.CommunityService.CheckHealth:output_type -> google.protobuf.Empty + 1, // 8: community.CommunityService.ListCommunities:output_type -> community.ListCommunitiesResponse + 3, // 9: community.CommunityService.CreateCommunity:output_type -> community.CreateCommunityResponse + 7, // 10: community.CommunityService.GetCommunity:output_type -> models.Community + 8, // 11: community.CommunityService.UpdateCommunity:output_type -> google.protobuf.Empty + 8, // 12: community.CommunityService.DeleteCommunity:output_type -> google.protobuf.Empty + 7, // [7:13] is the sub-list for method output_type + 1, // [1:7] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name diff --git a/code/gen/community-service/pb/community-service_grpc.pb.go b/code/gen/community-service/pb/community-service_grpc.pb.go index f75f528..088a329 100644 --- a/code/gen/community-service/pb/community-service_grpc.pb.go +++ b/code/gen/community-service/pb/community-service_grpc.pb.go @@ -21,6 +21,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( + CommunityService_CheckHealth_FullMethodName = "/community.CommunityService/CheckHealth" CommunityService_ListCommunities_FullMethodName = "/community.CommunityService/ListCommunities" CommunityService_CreateCommunity_FullMethodName = "/community.CommunityService/CreateCommunity" CommunityService_GetCommunity_FullMethodName = "/community.CommunityService/GetCommunity" @@ -32,6 +33,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type CommunityServiceClient interface { + CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) ListCommunities(ctx context.Context, in *ListCommunitiesRequest, opts ...grpc.CallOption) (*ListCommunitiesResponse, error) CreateCommunity(ctx context.Context, in *CreateCommunityRequest, opts ...grpc.CallOption) (*CreateCommunityResponse, error) GetCommunity(ctx context.Context, in *GetCommunityRequest, opts ...grpc.CallOption) (*pb.Community, error) @@ -47,6 +49,16 @@ func NewCommunityServiceClient(cc grpc.ClientConnInterface) CommunityServiceClie return &communityServiceClient{cc} } +func (c *communityServiceClient) CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, CommunityService_CheckHealth_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *communityServiceClient) ListCommunities(ctx context.Context, in *ListCommunitiesRequest, opts ...grpc.CallOption) (*ListCommunitiesResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListCommunitiesResponse) @@ -101,6 +113,7 @@ func (c *communityServiceClient) DeleteCommunity(ctx context.Context, in *Delete // All implementations must embed UnimplementedCommunityServiceServer // for forward compatibility. type CommunityServiceServer interface { + CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) ListCommunities(context.Context, *ListCommunitiesRequest) (*ListCommunitiesResponse, error) CreateCommunity(context.Context, *CreateCommunityRequest) (*CreateCommunityResponse, error) GetCommunity(context.Context, *GetCommunityRequest) (*pb.Community, error) @@ -116,6 +129,9 @@ type CommunityServiceServer interface { // pointer dereference when methods are called. type UnimplementedCommunityServiceServer struct{} +func (UnimplementedCommunityServiceServer) CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented") +} func (UnimplementedCommunityServiceServer) ListCommunities(context.Context, *ListCommunitiesRequest) (*ListCommunitiesResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListCommunities not implemented") } @@ -152,6 +168,24 @@ func RegisterCommunityServiceServer(s grpc.ServiceRegistrar, srv CommunityServic s.RegisterService(&CommunityService_ServiceDesc, srv) } +func _CommunityService_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(CommunityServiceServer).CheckHealth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: CommunityService_CheckHealth_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(CommunityServiceServer).CheckHealth(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _CommunityService_ListCommunities_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListCommunitiesRequest) if err := dec(in); err != nil { @@ -249,6 +283,10 @@ var CommunityService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "community.CommunityService", HandlerType: (*CommunityServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "CheckHealth", + Handler: _CommunityService_CheckHealth_Handler, + }, { MethodName: "ListCommunities", Handler: _CommunityService_ListCommunities_Handler, diff --git a/code/gen/popular-service/pb/popular-service.pb.go b/code/gen/popular-service/pb/popular-service.pb.go index afa8b4e..79e095a 100644 --- a/code/gen/popular-service/pb/popular-service.pb.go +++ b/code/gen/popular-service/pb/popular-service.pb.go @@ -11,6 +11,7 @@ import ( _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -219,7 +220,7 @@ var File_popular_service_proto protoreflect.FileDescriptor const file_popular_service_proto_rawDesc = "" + "\n" + - "\x15popular-service.proto\x12\apopular\x1a\x1cgoogle/api/annotations.proto\x1a\fmodels.proto\"g\n" + + "\x15popular-service.proto\x12\apopular\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/api/annotations.proto\x1a\fmodels.proto\"g\n" + "\x18GetPopularThreadsRequest\x12\x1b\n" + "\x06offset\x18\x01 \x01(\x05H\x00R\x06offset\x88\x01\x01\x12\x19\n" + "\x05limit\x18\x02 \x01(\x05H\x01R\x05limit\x88\x01\x01B\t\n" + @@ -233,8 +234,9 @@ const file_popular_service_proto_rawDesc = "" + "\a_offsetB\b\n" + "\x06_limit\"I\n" + "\x1aGetPopularCommentsResponse\x12+\n" + - "\bcomments\x18\x01 \x03(\v2\x0f.models.CommentR\bcomments2\x80\x02\n" + - "\x0ePopularService\x12t\n" + + "\bcomments\x18\x01 \x03(\v2\x0f.models.CommentR\bcomments2\xbf\x02\n" + + "\x0ePopularService\x12=\n" + + "\vCheckHealth\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12t\n" + "\x11GetPopularThreads\x12!.popular.GetPopularThreadsRequest\x1a\".popular.GetPopularThreadsResponse\"\x18\x82\xd3\xe4\x93\x02\x12\x12\x10/popular/threads\x12x\n" + "\x12GetPopularComments\x12\".popular.GetPopularCommentsRequest\x1a#.popular.GetPopularCommentsResponse\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/popular/commentsB\x1bZ\x19gen/popular-service/pb;pbb\x06proto3" @@ -258,16 +260,19 @@ var file_popular_service_proto_goTypes = []any{ (*GetPopularCommentsResponse)(nil), // 3: popular.GetPopularCommentsResponse (*pb.Thread)(nil), // 4: models.Thread (*pb.Comment)(nil), // 5: models.Comment + (*emptypb.Empty)(nil), // 6: google.protobuf.Empty } var file_popular_service_proto_depIdxs = []int32{ 4, // 0: popular.GetPopularThreadsResponse.threads:type_name -> models.Thread 5, // 1: popular.GetPopularCommentsResponse.comments:type_name -> models.Comment - 0, // 2: popular.PopularService.GetPopularThreads:input_type -> popular.GetPopularThreadsRequest - 2, // 3: popular.PopularService.GetPopularComments:input_type -> popular.GetPopularCommentsRequest - 1, // 4: popular.PopularService.GetPopularThreads:output_type -> popular.GetPopularThreadsResponse - 3, // 5: popular.PopularService.GetPopularComments:output_type -> popular.GetPopularCommentsResponse - 4, // [4:6] is the sub-list for method output_type - 2, // [2:4] is the sub-list for method input_type + 6, // 2: popular.PopularService.CheckHealth:input_type -> google.protobuf.Empty + 0, // 3: popular.PopularService.GetPopularThreads:input_type -> popular.GetPopularThreadsRequest + 2, // 4: popular.PopularService.GetPopularComments:input_type -> popular.GetPopularCommentsRequest + 6, // 5: popular.PopularService.CheckHealth:output_type -> google.protobuf.Empty + 1, // 6: popular.PopularService.GetPopularThreads:output_type -> popular.GetPopularThreadsResponse + 3, // 7: popular.PopularService.GetPopularComments:output_type -> popular.GetPopularCommentsResponse + 5, // [5:8] is the sub-list for method output_type + 2, // [2:5] is the sub-list for method input_type 2, // [2:2] is the sub-list for extension type_name 2, // [2:2] is the sub-list for extension extendee 0, // [0:2] is the sub-list for field type_name diff --git a/code/gen/popular-service/pb/popular-service_grpc.pb.go b/code/gen/popular-service/pb/popular-service_grpc.pb.go index 10fe1c6..48c75e5 100644 --- a/code/gen/popular-service/pb/popular-service_grpc.pb.go +++ b/code/gen/popular-service/pb/popular-service_grpc.pb.go @@ -11,6 +11,7 @@ import ( grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" ) // This is a compile-time assertion to ensure that this generated file @@ -19,6 +20,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( + PopularService_CheckHealth_FullMethodName = "/popular.PopularService/CheckHealth" PopularService_GetPopularThreads_FullMethodName = "/popular.PopularService/GetPopularThreads" PopularService_GetPopularComments_FullMethodName = "/popular.PopularService/GetPopularComments" ) @@ -27,6 +29,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type PopularServiceClient interface { + CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) GetPopularThreads(ctx context.Context, in *GetPopularThreadsRequest, opts ...grpc.CallOption) (*GetPopularThreadsResponse, error) GetPopularComments(ctx context.Context, in *GetPopularCommentsRequest, opts ...grpc.CallOption) (*GetPopularCommentsResponse, error) } @@ -39,6 +42,16 @@ func NewPopularServiceClient(cc grpc.ClientConnInterface) PopularServiceClient { return &popularServiceClient{cc} } +func (c *popularServiceClient) CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, PopularService_CheckHealth_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *popularServiceClient) GetPopularThreads(ctx context.Context, in *GetPopularThreadsRequest, opts ...grpc.CallOption) (*GetPopularThreadsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetPopularThreadsResponse) @@ -63,6 +76,7 @@ func (c *popularServiceClient) GetPopularComments(ctx context.Context, in *GetPo // All implementations must embed UnimplementedPopularServiceServer // for forward compatibility. type PopularServiceServer interface { + CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) GetPopularThreads(context.Context, *GetPopularThreadsRequest) (*GetPopularThreadsResponse, error) GetPopularComments(context.Context, *GetPopularCommentsRequest) (*GetPopularCommentsResponse, error) mustEmbedUnimplementedPopularServiceServer() @@ -75,6 +89,9 @@ type PopularServiceServer interface { // pointer dereference when methods are called. type UnimplementedPopularServiceServer struct{} +func (UnimplementedPopularServiceServer) CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented") +} func (UnimplementedPopularServiceServer) GetPopularThreads(context.Context, *GetPopularThreadsRequest) (*GetPopularThreadsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GetPopularThreads not implemented") } @@ -102,6 +119,24 @@ func RegisterPopularServiceServer(s grpc.ServiceRegistrar, srv PopularServiceSer s.RegisterService(&PopularService_ServiceDesc, srv) } +func _PopularService_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(PopularServiceServer).CheckHealth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: PopularService_CheckHealth_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(PopularServiceServer).CheckHealth(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _PopularService_GetPopularThreads_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(GetPopularThreadsRequest) if err := dec(in); err != nil { @@ -145,6 +180,10 @@ var PopularService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "popular.PopularService", HandlerType: (*PopularServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "CheckHealth", + Handler: _PopularService_CheckHealth_Handler, + }, { MethodName: "GetPopularThreads", Handler: _PopularService_GetPopularThreads_Handler, diff --git a/code/gen/search-service/pb/search-service.pb.go b/code/gen/search-service/pb/search-service.pb.go index 2235d50..7b8ab6c 100644 --- a/code/gen/search-service/pb/search-service.pb.go +++ b/code/gen/search-service/pb/search-service.pb.go @@ -11,6 +11,7 @@ import ( _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" + emptypb "google.golang.org/protobuf/types/known/emptypb" reflect "reflect" sync "sync" unsafe "unsafe" @@ -227,7 +228,7 @@ var File_search_service_proto protoreflect.FileDescriptor const file_search_service_proto_rawDesc = "" + "\n" + - "\x14search-service.proto\x12\x06search\x1a\x1cgoogle/api/annotations.proto\x1a\fmodels.proto\"r\n" + + "\x14search-service.proto\x12\x06search\x1a\x1bgoogle/protobuf/empty.proto\x1a\x1cgoogle/api/annotations.proto\x1a\fmodels.proto\"r\n" + "\rSearchRequest\x12\x14\n" + "\x05query\x18\x01 \x01(\tR\x05query\x12\x1b\n" + "\x06offset\x18\x02 \x01(\x05H\x00R\x06offset\x88\x01\x01\x12\x19\n" + @@ -240,8 +241,9 @@ const file_search_service_proto_rawDesc = "" + "\x17CommunitySearchResponse\x12+\n" + "\aresults\x18\x01 \x03(\v2\x11.models.CommunityR\aresults\"@\n" + "\x14ThreadSearchResponse\x12(\n" + - "\aresults\x18\x01 \x03(\v2\x0e.models.ThreadR\aresults2\xa8\x02\n" + - "\rSearchService\x12T\n" + + "\aresults\x18\x01 \x03(\v2\x0e.models.ThreadR\aresults2\xe7\x02\n" + + "\rSearchService\x12=\n" + + "\vCheckHealth\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12T\n" + "\fGlobalSearch\x12\x15.search.SearchRequest\x1a\x1c.search.GlobalSearchResponse\"\x0f\x82\xd3\xe4\x93\x02\t\x12\a/search\x12d\n" + "\x0fCommunitySearch\x12\x15.search.SearchRequest\x1a\x1f.search.CommunitySearchResponse\"\x19\x82\xd3\xe4\x93\x02\x13\x12\x11/search/community\x12[\n" + "\fThreadSearch\x12\x15.search.SearchRequest\x1a\x1c.search.ThreadSearchResponse\"\x16\x82\xd3\xe4\x93\x02\x10\x12\x0e/search/threadB\x1aZ\x18gen/search-service/pb;pbb\x06proto3" @@ -266,20 +268,23 @@ var file_search_service_proto_goTypes = []any{ (*ThreadSearchResponse)(nil), // 3: search.ThreadSearchResponse (*pb.Thread)(nil), // 4: models.Thread (*pb.Community)(nil), // 5: models.Community + (*emptypb.Empty)(nil), // 6: google.protobuf.Empty } var file_search_service_proto_depIdxs = []int32{ 4, // 0: search.GlobalSearchResponse.thread_results:type_name -> models.Thread 5, // 1: search.GlobalSearchResponse.community_results:type_name -> models.Community 5, // 2: search.CommunitySearchResponse.results:type_name -> models.Community 4, // 3: search.ThreadSearchResponse.results:type_name -> models.Thread - 0, // 4: search.SearchService.GlobalSearch:input_type -> search.SearchRequest - 0, // 5: search.SearchService.CommunitySearch:input_type -> search.SearchRequest - 0, // 6: search.SearchService.ThreadSearch:input_type -> search.SearchRequest - 1, // 7: search.SearchService.GlobalSearch:output_type -> search.GlobalSearchResponse - 2, // 8: search.SearchService.CommunitySearch:output_type -> search.CommunitySearchResponse - 3, // 9: search.SearchService.ThreadSearch:output_type -> search.ThreadSearchResponse - 7, // [7:10] is the sub-list for method output_type - 4, // [4:7] is the sub-list for method input_type + 6, // 4: search.SearchService.CheckHealth:input_type -> google.protobuf.Empty + 0, // 5: search.SearchService.GlobalSearch:input_type -> search.SearchRequest + 0, // 6: search.SearchService.CommunitySearch:input_type -> search.SearchRequest + 0, // 7: search.SearchService.ThreadSearch:input_type -> search.SearchRequest + 6, // 8: search.SearchService.CheckHealth:output_type -> google.protobuf.Empty + 1, // 9: search.SearchService.GlobalSearch:output_type -> search.GlobalSearchResponse + 2, // 10: search.SearchService.CommunitySearch:output_type -> search.CommunitySearchResponse + 3, // 11: search.SearchService.ThreadSearch:output_type -> search.ThreadSearchResponse + 8, // [8:12] is the sub-list for method output_type + 4, // [4:8] is the sub-list for method input_type 4, // [4:4] is the sub-list for extension type_name 4, // [4:4] is the sub-list for extension extendee 0, // [0:4] is the sub-list for field type_name diff --git a/code/gen/search-service/pb/search-service_grpc.pb.go b/code/gen/search-service/pb/search-service_grpc.pb.go index 96cc60b..a80e8a4 100644 --- a/code/gen/search-service/pb/search-service_grpc.pb.go +++ b/code/gen/search-service/pb/search-service_grpc.pb.go @@ -11,6 +11,7 @@ import ( grpc "google.golang.org/grpc" codes "google.golang.org/grpc/codes" status "google.golang.org/grpc/status" + emptypb "google.golang.org/protobuf/types/known/emptypb" ) // This is a compile-time assertion to ensure that this generated file @@ -19,6 +20,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( + SearchService_CheckHealth_FullMethodName = "/search.SearchService/CheckHealth" SearchService_GlobalSearch_FullMethodName = "/search.SearchService/GlobalSearch" SearchService_CommunitySearch_FullMethodName = "/search.SearchService/CommunitySearch" SearchService_ThreadSearch_FullMethodName = "/search.SearchService/ThreadSearch" @@ -28,6 +30,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type SearchServiceClient interface { + CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) GlobalSearch(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*GlobalSearchResponse, error) CommunitySearch(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*CommunitySearchResponse, error) ThreadSearch(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*ThreadSearchResponse, error) @@ -41,6 +44,16 @@ func NewSearchServiceClient(cc grpc.ClientConnInterface) SearchServiceClient { return &searchServiceClient{cc} } +func (c *searchServiceClient) CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, SearchService_CheckHealth_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *searchServiceClient) GlobalSearch(ctx context.Context, in *SearchRequest, opts ...grpc.CallOption) (*GlobalSearchResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GlobalSearchResponse) @@ -75,6 +88,7 @@ func (c *searchServiceClient) ThreadSearch(ctx context.Context, in *SearchReques // All implementations must embed UnimplementedSearchServiceServer // for forward compatibility. type SearchServiceServer interface { + CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) GlobalSearch(context.Context, *SearchRequest) (*GlobalSearchResponse, error) CommunitySearch(context.Context, *SearchRequest) (*CommunitySearchResponse, error) ThreadSearch(context.Context, *SearchRequest) (*ThreadSearchResponse, error) @@ -88,6 +102,9 @@ type SearchServiceServer interface { // pointer dereference when methods are called. type UnimplementedSearchServiceServer struct{} +func (UnimplementedSearchServiceServer) CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented") +} func (UnimplementedSearchServiceServer) GlobalSearch(context.Context, *SearchRequest) (*GlobalSearchResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method GlobalSearch not implemented") } @@ -118,6 +135,24 @@ func RegisterSearchServiceServer(s grpc.ServiceRegistrar, srv SearchServiceServe s.RegisterService(&SearchService_ServiceDesc, srv) } +func _SearchService_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(SearchServiceServer).CheckHealth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: SearchService_CheckHealth_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(SearchServiceServer).CheckHealth(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _SearchService_GlobalSearch_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(SearchRequest) if err := dec(in); err != nil { @@ -179,6 +214,10 @@ var SearchService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "search.SearchService", HandlerType: (*SearchServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "CheckHealth", + Handler: _SearchService_CheckHealth_Handler, + }, { MethodName: "GlobalSearch", Handler: _SearchService_GlobalSearch_Handler, diff --git a/code/gen/thread-service/pb/thread-service.pb.go b/code/gen/thread-service/pb/thread-service.pb.go index c180d50..e0e60a9 100644 --- a/code/gen/thread-service/pb/thread-service.pb.go +++ b/code/gen/thread-service/pb/thread-service.pb.go @@ -452,8 +452,9 @@ const file_thread_service_proto_rawDesc = "" + "\f_vote_offsetB\x16\n" + "\x14_num_comments_offset\"%\n" + "\x13DeleteThreadRequest\x12\x0e\n" + - "\x02id\x18\x01 \x01(\tR\x02id2\xd2\x03\n" + - "\rThreadService\x12X\n" + + "\x02id\x18\x01 \x01(\tR\x02id2\x91\x04\n" + + "\rThreadService\x12=\n" + + "\vCheckHealth\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12X\n" + "\vListThreads\x12\x1a.thread.ListThreadsRequest\x1a\x1b.thread.ListThreadsResponse\"\x10\x82\xd3\xe4\x93\x02\n" + "\x12\b/threads\x12^\n" + "\fCreateThread\x12\x1b.thread.CreateThreadRequest\x1a\x1c.thread.CreateThreadResponse\"\x13\x82\xd3\xe4\x93\x02\r:\x01*\"\b/threads\x12L\n" + @@ -487,18 +488,20 @@ var file_thread_service_proto_goTypes = []any{ } var file_thread_service_proto_depIdxs = []int32{ 7, // 0: thread.ListThreadsResponse.threads:type_name -> models.Thread - 0, // 1: thread.ThreadService.ListThreads:input_type -> thread.ListThreadsRequest - 2, // 2: thread.ThreadService.CreateThread:input_type -> thread.CreateThreadRequest - 4, // 3: thread.ThreadService.GetThread:input_type -> thread.GetThreadRequest - 5, // 4: thread.ThreadService.UpdateThread:input_type -> thread.UpdateThreadRequest - 6, // 5: thread.ThreadService.DeleteThread:input_type -> thread.DeleteThreadRequest - 1, // 6: thread.ThreadService.ListThreads:output_type -> thread.ListThreadsResponse - 3, // 7: thread.ThreadService.CreateThread:output_type -> thread.CreateThreadResponse - 7, // 8: thread.ThreadService.GetThread:output_type -> models.Thread - 8, // 9: thread.ThreadService.UpdateThread:output_type -> google.protobuf.Empty - 8, // 10: thread.ThreadService.DeleteThread:output_type -> google.protobuf.Empty - 6, // [6:11] is the sub-list for method output_type - 1, // [1:6] is the sub-list for method input_type + 8, // 1: thread.ThreadService.CheckHealth:input_type -> google.protobuf.Empty + 0, // 2: thread.ThreadService.ListThreads:input_type -> thread.ListThreadsRequest + 2, // 3: thread.ThreadService.CreateThread:input_type -> thread.CreateThreadRequest + 4, // 4: thread.ThreadService.GetThread:input_type -> thread.GetThreadRequest + 5, // 5: thread.ThreadService.UpdateThread:input_type -> thread.UpdateThreadRequest + 6, // 6: thread.ThreadService.DeleteThread:input_type -> thread.DeleteThreadRequest + 8, // 7: thread.ThreadService.CheckHealth:output_type -> google.protobuf.Empty + 1, // 8: thread.ThreadService.ListThreads:output_type -> thread.ListThreadsResponse + 3, // 9: thread.ThreadService.CreateThread:output_type -> thread.CreateThreadResponse + 7, // 10: thread.ThreadService.GetThread:output_type -> models.Thread + 8, // 11: thread.ThreadService.UpdateThread:output_type -> google.protobuf.Empty + 8, // 12: thread.ThreadService.DeleteThread:output_type -> google.protobuf.Empty + 7, // [7:13] is the sub-list for method output_type + 1, // [1:7] is the sub-list for method input_type 1, // [1:1] is the sub-list for extension type_name 1, // [1:1] is the sub-list for extension extendee 0, // [0:1] is the sub-list for field type_name diff --git a/code/gen/thread-service/pb/thread-service_grpc.pb.go b/code/gen/thread-service/pb/thread-service_grpc.pb.go index 38c4a47..1a29600 100644 --- a/code/gen/thread-service/pb/thread-service_grpc.pb.go +++ b/code/gen/thread-service/pb/thread-service_grpc.pb.go @@ -21,6 +21,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( + ThreadService_CheckHealth_FullMethodName = "/thread.ThreadService/CheckHealth" ThreadService_ListThreads_FullMethodName = "/thread.ThreadService/ListThreads" ThreadService_CreateThread_FullMethodName = "/thread.ThreadService/CreateThread" ThreadService_GetThread_FullMethodName = "/thread.ThreadService/GetThread" @@ -32,6 +33,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type ThreadServiceClient interface { + CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) ListThreads(ctx context.Context, in *ListThreadsRequest, opts ...grpc.CallOption) (*ListThreadsResponse, error) CreateThread(ctx context.Context, in *CreateThreadRequest, opts ...grpc.CallOption) (*CreateThreadResponse, error) GetThread(ctx context.Context, in *GetThreadRequest, opts ...grpc.CallOption) (*pb.Thread, error) @@ -47,6 +49,16 @@ func NewThreadServiceClient(cc grpc.ClientConnInterface) ThreadServiceClient { return &threadServiceClient{cc} } +func (c *threadServiceClient) CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, ThreadService_CheckHealth_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *threadServiceClient) ListThreads(ctx context.Context, in *ListThreadsRequest, opts ...grpc.CallOption) (*ListThreadsResponse, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListThreadsResponse) @@ -101,6 +113,7 @@ func (c *threadServiceClient) DeleteThread(ctx context.Context, in *DeleteThread // All implementations must embed UnimplementedThreadServiceServer // for forward compatibility. type ThreadServiceServer interface { + CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) ListThreads(context.Context, *ListThreadsRequest) (*ListThreadsResponse, error) CreateThread(context.Context, *CreateThreadRequest) (*CreateThreadResponse, error) GetThread(context.Context, *GetThreadRequest) (*pb.Thread, error) @@ -116,6 +129,9 @@ type ThreadServiceServer interface { // pointer dereference when methods are called. type UnimplementedThreadServiceServer struct{} +func (UnimplementedThreadServiceServer) CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented") +} func (UnimplementedThreadServiceServer) ListThreads(context.Context, *ListThreadsRequest) (*ListThreadsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method ListThreads not implemented") } @@ -152,6 +168,24 @@ func RegisterThreadServiceServer(s grpc.ServiceRegistrar, srv ThreadServiceServe s.RegisterService(&ThreadService_ServiceDesc, srv) } +func _ThreadService_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ThreadServiceServer).CheckHealth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: ThreadService_CheckHealth_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ThreadServiceServer).CheckHealth(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _ThreadService_ListThreads_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(ListThreadsRequest) if err := dec(in); err != nil { @@ -249,6 +283,10 @@ var ThreadService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "thread.ThreadService", HandlerType: (*ThreadServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "CheckHealth", + Handler: _ThreadService_CheckHealth_Handler, + }, { MethodName: "ListThreads", Handler: _ThreadService_ListThreads_Handler, diff --git a/code/gen/vote-service/pb/vote-service.pb.go b/code/gen/vote-service/pb/vote-service.pb.go index 1d61b9c..aea8bc4 100644 --- a/code/gen/vote-service/pb/vote-service.pb.go +++ b/code/gen/vote-service/pb/vote-service.pb.go @@ -120,8 +120,9 @@ const file_vote_service_proto_rawDesc = "" + "\tthread_id\x18\x01 \x01(\tR\bthreadId\"3\n" + "\x12VoteCommentRequest\x12\x1d\n" + "\n" + - "comment_id\x18\x01 \x01(\tR\tcommentId2\xc5\x03\n" + - "\vVoteService\x12h\n" + + "comment_id\x18\x01 \x01(\tR\tcommentId2\x84\x04\n" + + "\vVoteService\x12=\n" + + "\vCheckHealth\x12\x16.google.protobuf.Empty\x1a\x16.google.protobuf.Empty\x12h\n" + "\fUpvoteThread\x12\x17.vote.VoteThreadRequest\x1a\x16.google.protobuf.Empty\"'\x82\xd3\xe4\x93\x02!:\x01*\"\x1c/votes/thread/{thread_id}/up\x12l\n" + "\x0eDownvoteThread\x12\x17.vote.VoteThreadRequest\x1a\x16.google.protobuf.Empty\")\x82\xd3\xe4\x93\x02#:\x01*\"\x1e/votes/thread/{thread_id}/down\x12l\n" + "\rUpvoteComment\x12\x18.vote.VoteCommentRequest\x1a\x16.google.protobuf.Empty\")\x82\xd3\xe4\x93\x02#:\x01*\"\x1e/votes/comment/{comment_id}/up\x12p\n" + @@ -146,16 +147,18 @@ var file_vote_service_proto_goTypes = []any{ (*emptypb.Empty)(nil), // 2: google.protobuf.Empty } var file_vote_service_proto_depIdxs = []int32{ - 0, // 0: vote.VoteService.UpvoteThread:input_type -> vote.VoteThreadRequest - 0, // 1: vote.VoteService.DownvoteThread:input_type -> vote.VoteThreadRequest - 1, // 2: vote.VoteService.UpvoteComment:input_type -> vote.VoteCommentRequest - 1, // 3: vote.VoteService.DownvoteComment:input_type -> vote.VoteCommentRequest - 2, // 4: vote.VoteService.UpvoteThread:output_type -> google.protobuf.Empty - 2, // 5: vote.VoteService.DownvoteThread:output_type -> google.protobuf.Empty - 2, // 6: vote.VoteService.UpvoteComment:output_type -> google.protobuf.Empty - 2, // 7: vote.VoteService.DownvoteComment:output_type -> google.protobuf.Empty - 4, // [4:8] is the sub-list for method output_type - 0, // [0:4] is the sub-list for method input_type + 2, // 0: vote.VoteService.CheckHealth:input_type -> google.protobuf.Empty + 0, // 1: vote.VoteService.UpvoteThread:input_type -> vote.VoteThreadRequest + 0, // 2: vote.VoteService.DownvoteThread:input_type -> vote.VoteThreadRequest + 1, // 3: vote.VoteService.UpvoteComment:input_type -> vote.VoteCommentRequest + 1, // 4: vote.VoteService.DownvoteComment:input_type -> vote.VoteCommentRequest + 2, // 5: vote.VoteService.CheckHealth:output_type -> google.protobuf.Empty + 2, // 6: vote.VoteService.UpvoteThread:output_type -> google.protobuf.Empty + 2, // 7: vote.VoteService.DownvoteThread:output_type -> google.protobuf.Empty + 2, // 8: vote.VoteService.UpvoteComment:output_type -> google.protobuf.Empty + 2, // 9: vote.VoteService.DownvoteComment:output_type -> google.protobuf.Empty + 5, // [5:10] is the sub-list for method output_type + 0, // [0:5] is the sub-list for method input_type 0, // [0:0] is the sub-list for extension type_name 0, // [0:0] is the sub-list for extension extendee 0, // [0:0] is the sub-list for field type_name diff --git a/code/gen/vote-service/pb/vote-service_grpc.pb.go b/code/gen/vote-service/pb/vote-service_grpc.pb.go index 9802f62..5a345ec 100644 --- a/code/gen/vote-service/pb/vote-service_grpc.pb.go +++ b/code/gen/vote-service/pb/vote-service_grpc.pb.go @@ -20,6 +20,7 @@ import ( const _ = grpc.SupportPackageIsVersion9 const ( + VoteService_CheckHealth_FullMethodName = "/vote.VoteService/CheckHealth" VoteService_UpvoteThread_FullMethodName = "/vote.VoteService/UpvoteThread" VoteService_DownvoteThread_FullMethodName = "/vote.VoteService/DownvoteThread" VoteService_UpvoteComment_FullMethodName = "/vote.VoteService/UpvoteComment" @@ -30,6 +31,7 @@ const ( // // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. type VoteServiceClient interface { + CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) UpvoteThread(ctx context.Context, in *VoteThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) DownvoteThread(ctx context.Context, in *VoteThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) UpvoteComment(ctx context.Context, in *VoteCommentRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) @@ -44,6 +46,16 @@ func NewVoteServiceClient(cc grpc.ClientConnInterface) VoteServiceClient { return &voteServiceClient{cc} } +func (c *voteServiceClient) CheckHealth(ctx context.Context, in *emptypb.Empty, opts ...grpc.CallOption) (*emptypb.Empty, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) + out := new(emptypb.Empty) + err := c.cc.Invoke(ctx, VoteService_CheckHealth_FullMethodName, in, out, cOpts...) + if err != nil { + return nil, err + } + return out, nil +} + func (c *voteServiceClient) UpvoteThread(ctx context.Context, in *VoteThreadRequest, opts ...grpc.CallOption) (*emptypb.Empty, error) { cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(emptypb.Empty) @@ -88,6 +100,7 @@ func (c *voteServiceClient) DownvoteComment(ctx context.Context, in *VoteComment // All implementations must embed UnimplementedVoteServiceServer // for forward compatibility. type VoteServiceServer interface { + CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) UpvoteThread(context.Context, *VoteThreadRequest) (*emptypb.Empty, error) DownvoteThread(context.Context, *VoteThreadRequest) (*emptypb.Empty, error) UpvoteComment(context.Context, *VoteCommentRequest) (*emptypb.Empty, error) @@ -102,6 +115,9 @@ type VoteServiceServer interface { // pointer dereference when methods are called. type UnimplementedVoteServiceServer struct{} +func (UnimplementedVoteServiceServer) CheckHealth(context.Context, *emptypb.Empty) (*emptypb.Empty, error) { + return nil, status.Errorf(codes.Unimplemented, "method CheckHealth not implemented") +} func (UnimplementedVoteServiceServer) UpvoteThread(context.Context, *VoteThreadRequest) (*emptypb.Empty, error) { return nil, status.Errorf(codes.Unimplemented, "method UpvoteThread not implemented") } @@ -135,6 +151,24 @@ func RegisterVoteServiceServer(s grpc.ServiceRegistrar, srv VoteServiceServer) { s.RegisterService(&VoteService_ServiceDesc, srv) } +func _VoteService_CheckHealth_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(emptypb.Empty) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(VoteServiceServer).CheckHealth(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: VoteService_CheckHealth_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(VoteServiceServer).CheckHealth(ctx, req.(*emptypb.Empty)) + } + return interceptor(ctx, in, info, handler) +} + func _VoteService_UpvoteThread_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { in := new(VoteThreadRequest) if err := dec(in); err != nil { @@ -214,6 +248,10 @@ var VoteService_ServiceDesc = grpc.ServiceDesc{ ServiceName: "vote.VoteService", HandlerType: (*VoteServiceServer)(nil), Methods: []grpc.MethodDesc{ + { + MethodName: "CheckHealth", + Handler: _VoteService_CheckHealth_Handler, + }, { MethodName: "UpvoteThread", Handler: _VoteService_UpvoteThread_Handler, diff --git a/code/grpc-gateway/main.go b/code/grpc-gateway/main.go index 71e7394..3f5bbfe 100644 --- a/code/grpc-gateway/main.go +++ b/code/grpc-gateway/main.go @@ -2,6 +2,7 @@ package main import ( "context" + "encoding/json" "fmt" commentpb "gen/comment-service/pb" communitypb "gen/community-service/pb" @@ -16,6 +17,7 @@ import ( "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" + "google.golang.org/protobuf/types/known/emptypb" ) func getGrpcServerAddress(hostEnvVar string, portEnvVar string) string { @@ -30,6 +32,81 @@ func getGrpcServerAddress(hostEnvVar string, portEnvVar string) string { return fmt.Sprintf("%s:%s", host, port) } +func connectGrpcClient(hostEnvVar string, portEnvVar string) *grpc.ClientConn { + addr := getGrpcServerAddress(hostEnvVar, portEnvVar) + conn, err := grpc.NewClient(addr, + grpc.WithTransportCredentials(insecure.NewCredentials()), + grpc.WithDefaultCallOptions( + grpc.MaxCallRecvMsgSize(1024*1024*500), // 500MB + grpc.MaxCallSendMsgSize(1024*1024*500), // 500MB + ), + ) + if err != nil { + log.Fatalf("failed to connect to %s: %v", addr, err) + } + return conn +} + +func handleHealthCheck(w http.ResponseWriter, r *http.Request) { + ctx := context.Background() + + health := map[string]bool{ + "community-service": false, + "thread-service": false, + "comment-service": false, + "vote-service": false, + "search-service": false, + "popular-service": false, + } + + communityConn := connectGrpcClient("COMMUNITY_SERVICE_HOST", "COMMUNITY_SERVICE_PORT") + defer communityConn.Close() + communityClient := communitypb.NewCommunityServiceClient(communityConn) + _, err := communityClient.CheckHealth(ctx, &emptypb.Empty{}) + health["community-service"] = err == nil + + threadConn := connectGrpcClient("THREAD_SERVICE_HOST", "THREAD_SERVICE_PORT") + defer threadConn.Close() + threadClient := threadpb.NewThreadServiceClient(threadConn) + _, err = threadClient.CheckHealth(ctx, &emptypb.Empty{}) + health["thread-service"] = err == nil + + commentConn := connectGrpcClient("COMMENT_SERVICE_HOST", "COMMENT_SERVICE_PORT") + defer commentConn.Close() + commentClient := commentpb.NewCommentServiceClient(commentConn) + _, err = commentClient.CheckHealth(ctx, &emptypb.Empty{}) + health["comment-service"] = err == nil + + voteConn := connectGrpcClient("VOTE_SERVICE_HOST", "VOTE_SERVICE_PORT") + defer voteConn.Close() + voteClient := votepb.NewVoteServiceClient(voteConn) + _, err = voteClient.CheckHealth(ctx, &emptypb.Empty{}) + health["vote-service"] = err == nil + + searchConn := connectGrpcClient("SEARCH_SERVICE_HOST", "SEARCH_SERVICE_PORT") + defer searchConn.Close() + searchClient := searchpb.NewSearchServiceClient(searchConn) + _, err = searchClient.CheckHealth(ctx, &emptypb.Empty{}) + health["search-service"] = err == nil + + popularConn := connectGrpcClient("POPULAR_SERVICE_HOST", "POPULAR_SERVICE_PORT") + defer popularConn.Close() + popularClient := popularpb.NewPopularServiceClient(popularConn) + _, err = popularClient.CheckHealth(ctx, &emptypb.Empty{}) + health["popular-service"] = err == nil + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + jsonResponse, err := json.Marshal(health) + if err != nil { + http.Error(w, "Failed to marshal JSON response", http.StatusInternalServerError) + return + } + + w.Write(jsonResponse) +} + func main() { gwmux := runtime.NewServeMux() @@ -71,6 +148,8 @@ func main() { log.Fatalf("Failed to register gRPC gateway: %v", err) } + http.HandleFunc("/health", handleHealthCheck) + http.Handle("/", gwmux) port := os.Getenv("GRPC_GATEWAY_PORT") diff --git a/code/kubernetes/grpc-gateway/deployment.yaml b/code/kubernetes/grpc-gateway/deployment.yaml index 7ee716b..529ff7f 100644 --- a/code/kubernetes/grpc-gateway/deployment.yaml +++ b/code/kubernetes/grpc-gateway/deployment.yaml @@ -18,6 +18,13 @@ spec: imagePullPolicy: Always ports: - containerPort: 8080 + resources: + requests: + cpu: 40m + memory: 80Mi + limits: + cpu: 120m + memory: 240Mi env: - name: GRPC_GATEWAY_PORT valueFrom: @@ -65,4 +72,35 @@ spec: valueFrom: configMapKeyRef: name: threadit-config - key: POPULAR_SERVICE_PORT \ No newline at end of file + key: POPULAR_SERVICE_PORT + readinessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 10 + timeoutSeconds: 5 + livenessProbe: + httpGet: + path: /health + port: 8080 + initialDelaySeconds: 30 + timeoutSeconds: 5 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: grpc-gateway-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: grpc-gateway + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/code/kubernetes/mongo/deployment.yaml b/code/kubernetes/mongo/deployment.yaml index ab16f15..3810484 100644 --- a/code/kubernetes/mongo/deployment.yaml +++ b/code/kubernetes/mongo/deployment.yaml @@ -17,6 +17,13 @@ spec: image: mongo:latest ports: - containerPort: 27017 + resources: + requests: + cpu: 200m + memory: 700Mi + limits: + cpu: 500m + memory: 1Gi env: - name: MONGO_INITDB_DATABASE valueFrom: @@ -36,6 +43,22 @@ spec: volumeMounts: - name: mongodb-data mountPath: /data/db + livenessProbe: + exec: + command: + - mongosh + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: 60 + timeoutSeconds: 5 + readinessProbe: + exec: + command: + - mongosh + - --eval + - "db.adminCommand('ping')" + initialDelaySeconds: 20 + timeoutSeconds: 5 volumes: - name: mongodb-data persistentVolumeClaim: diff --git a/code/kubernetes/scripts/README.md b/code/kubernetes/scripts/README.md index ad025b0..c44a3d7 100644 --- a/code/kubernetes/scripts/README.md +++ b/code/kubernetes/scripts/README.md @@ -43,8 +43,9 @@ $ ./cluster-info.sh - `--pods` Shows the status and details of all pods running in the specified namespace. - `--services` Lists all services deployed in the specified namespace. - `--deployments` Shows deployment configurations and statuses for the namespace. -- `--resource-pods` Displays real-time CPU and memory usage metrics for each pod. -- `--resource-nodes` Displays real-time CPU and memory usage metrics for each node in the cluster. +- `--resources-pods` Displays real-time CPU and memory usage metrics for each pod. +- `--resources-nodes` Displays real-time CPU and memory usage metrics for each node in the cluster. +- `--hpa` Shows all Horizontal Pod Autoscalers in the namespace, including current and target scaling metrics. - `--all` Runs all of the above commands to display full cluster info. ### 4. Delete Cluster diff --git a/code/kubernetes/scripts/cluster-info.sh b/code/kubernetes/scripts/cluster-info.sh index 20723d5..5d56aa5 100644 --- a/code/kubernetes/scripts/cluster-info.sh +++ b/code/kubernetes/scripts/cluster-info.sh @@ -11,6 +11,7 @@ SHOW_SERVICES=false SHOW_DEPLOYMENTS=false SHOW_RESOURCES_PODS=false SHOW_RESOURCES_NODES=false +SHOW_HPA=false if [[ $# -eq 0 ]]; then SHOW_NAMESPACES=true @@ -19,6 +20,7 @@ if [[ $# -eq 0 ]]; then SHOW_DEPLOYMENTS=true SHOW_RESOURCES_PODS=true SHOW_RESOURCES_NODES=true + SHOW_HPA=true fi # Parse flags @@ -30,6 +32,7 @@ while [[ "$#" -gt 0 ]]; do --deployments) SHOW_DEPLOYMENTS=true ;; --resources-pods) SHOW_RESOURCES_PODS=true ;; --resources-nodes) SHOW_RESOURCES_NODES=true ;; + --hpa) SHOW_HPA=true ;; --all) SHOW_NAMESPACES=true SHOW_PODS=true @@ -37,6 +40,7 @@ while [[ "$#" -gt 0 ]]; do SHOW_DEPLOYMENTS=true SHOW_RESOURCES_PODS=true SHOW_RESOURCES_NODES=true + SHOW_HPA=true ;; *) echo "āŒ Unknown flag: $1"; exit 1 ;; esac @@ -53,3 +57,4 @@ $SHOW_SERVICES && echo -e "\nšŸ” Services:" && kubectl get svc -n $CLUSTER_NAME $SHOW_DEPLOYMENTS && echo -e "\nšŸ“‚ Deployments:" && kubectl get deployments -n $CLUSTER_NAME $SHOW_RESOURCES_PODS && echo -e "\nšŸ“Š Resource Usage (Pods):" && kubectl top pods -n $CLUSTER_NAME $SHOW_RESOURCES_NODES && echo -e "\nšŸ–„ļø Resource Usage (Nodes):" && kubectl top nodes +$SHOW_HPA && echo -e "\nšŸ“Œ Horizontal Pod Autoscalers:" && kubectl get hpa -n $CLUSTER_NAME \ No newline at end of file diff --git a/code/kubernetes/scripts/create-cluster.sh b/code/kubernetes/scripts/create-cluster.sh index e20ed38..a5b0b19 100644 --- a/code/kubernetes/scripts/create-cluster.sh +++ b/code/kubernetes/scripts/create-cluster.sh @@ -3,7 +3,7 @@ set -e PROJECT_ID="threadit-api" CLUSTER_NAME="threadit-cluster" -MACHINE_TYPE="e2-standard-4" +MACHINE_TYPE="e2-standard-2" ZONE="europe-west1-b" gcloud config set project $PROJECT_ID @@ -11,12 +11,12 @@ gcloud config set project $PROJECT_ID gcloud container clusters create $CLUSTER_NAME \ --num-nodes=3 \ --enable-autoscaling \ - --min-nodes=0 \ - --max-nodes=4 \ + --min-nodes=1 \ + --max-nodes=5 \ --machine-type=$MACHINE_TYPE \ --zone=$ZONE \ --disk-type=pd-standard \ - --disk-size=20 + --disk-size=25 gcloud container clusters get-credentials $CLUSTER_NAME --zone=$ZONE diff --git a/code/kubernetes/services/comment-service/deployment.yaml b/code/kubernetes/services/comment-service/deployment.yaml index 22f4dc4..9bd770e 100644 --- a/code/kubernetes/services/comment-service/deployment.yaml +++ b/code/kubernetes/services/comment-service/deployment.yaml @@ -18,6 +18,13 @@ spec: imagePullPolicy: Always ports: - containerPort: 50054 + resources: + requests: + cpu: 20m + memory: 40Mi + limits: + cpu: 60m + memory: 120Mi env: - name: SERVICE_PORT valueFrom: @@ -37,4 +44,33 @@ spec: valueFrom: configMapKeyRef: name: threadit-config - key: THREAD_SERVICE_PORT \ No newline at end of file + key: THREAD_SERVICE_PORT + readinessProbe: + tcpSocket: + port: 50054 + initialDelaySeconds: 5 + timeoutSeconds: 3 + livenessProbe: + tcpSocket: + port: 50054 + initialDelaySeconds: 15 + timeoutSeconds: 3 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: comment-service-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: comment-service + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/code/kubernetes/services/community-service/deployment.yaml b/code/kubernetes/services/community-service/deployment.yaml index b9817eb..598daed 100644 --- a/code/kubernetes/services/community-service/deployment.yaml +++ b/code/kubernetes/services/community-service/deployment.yaml @@ -18,6 +18,13 @@ spec: imagePullPolicy: Always ports: - containerPort: 50052 + resources: + requests: + cpu: 20m + memory: 40Mi + limits: + cpu: 60m + memory: 120Mi env: - name: SERVICE_PORT valueFrom: @@ -37,4 +44,33 @@ spec: valueFrom: configMapKeyRef: name: threadit-config - key: THREAD_SERVICE_PORT \ No newline at end of file + key: THREAD_SERVICE_PORT + readinessProbe: + tcpSocket: + port: 50052 + initialDelaySeconds: 5 + timeoutSeconds: 3 + livenessProbe: + tcpSocket: + port: 50052 + initialDelaySeconds: 15 + timeoutSeconds: 3 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: community-service-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: community-service + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/code/kubernetes/services/db-service/deployment.yaml b/code/kubernetes/services/db-service/deployment.yaml index 0ca80e8..cbf045f 100644 --- a/code/kubernetes/services/db-service/deployment.yaml +++ b/code/kubernetes/services/db-service/deployment.yaml @@ -18,6 +18,13 @@ spec: imagePullPolicy: Always ports: - containerPort: 50051 + resources: + requests: + cpu: 100m + memory: 250Mi + limits: + cpu: 300m + memory: 2Gi env: - name: SERVICE_PORT valueFrom: @@ -35,6 +42,21 @@ spec: - mountPath: /var/secret/gcp/ name: bucket-credentials readOnly: true + livenessProbe: + tcpSocket: + port: 50051 + initialDelaySeconds: 60 + timeoutSeconds: 4 + readinessProbe: + tcpSocket: + port: 50051 + initialDelaySeconds: 30 + timeoutSeconds: 4 + startupProbe: + tcpSocket: + port: 50051 + periodSeconds: 2 + failureThreshold: 90 volumes: - name: bucket-credentials secret: @@ -42,3 +64,22 @@ spec: items: - key: gcs-key.json path: gcs-key.json +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: db-service-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: db-service + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/code/kubernetes/services/popular-service/deployment.yaml b/code/kubernetes/services/popular-service/deployment.yaml index 32ee19f..e6daa04 100644 --- a/code/kubernetes/services/popular-service/deployment.yaml +++ b/code/kubernetes/services/popular-service/deployment.yaml @@ -18,6 +18,13 @@ spec: imagePullPolicy: Always ports: - containerPort: 50057 + resources: + requests: + cpu: 20m + memory: 40Mi + limits: + cpu: 60m + memory: 120Mi env: - name: SERVICE_PORT valueFrom: @@ -37,4 +44,33 @@ spec: valueFrom: configMapKeyRef: name: threadit-config - key: COMMENT_SERVICE_PORT \ No newline at end of file + key: COMMENT_SERVICE_PORT + readinessProbe: + tcpSocket: + port: 50057 + initialDelaySeconds: 5 + timeoutSeconds: 3 + livenessProbe: + tcpSocket: + port: 50057 + initialDelaySeconds: 15 + timeoutSeconds: 3 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: popular-service-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: popular-service + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/code/kubernetes/services/search-service/deployment.yaml b/code/kubernetes/services/search-service/deployment.yaml index 32b5aaf..e51dae7 100644 --- a/code/kubernetes/services/search-service/deployment.yaml +++ b/code/kubernetes/services/search-service/deployment.yaml @@ -18,6 +18,13 @@ spec: imagePullPolicy: Always ports: - containerPort: 50056 + resources: + requests: + cpu: 20m + memory: 40Mi + limits: + cpu: 60m + memory: 120Mi env: - name: SERVICE_PORT valueFrom: @@ -37,4 +44,33 @@ spec: valueFrom: configMapKeyRef: name: threadit-config - key: THREAD_SERVICE_PORT \ No newline at end of file + key: THREAD_SERVICE_PORT + readinessProbe: + tcpSocket: + port: 50056 + initialDelaySeconds: 5 + timeoutSeconds: 3 + livenessProbe: + tcpSocket: + port: 50056 + initialDelaySeconds: 15 + timeoutSeconds: 3 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: search-service-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: search-service + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/code/kubernetes/services/thread-service/deployment.yaml b/code/kubernetes/services/thread-service/deployment.yaml index 86800ac..11b9ee9 100644 --- a/code/kubernetes/services/thread-service/deployment.yaml +++ b/code/kubernetes/services/thread-service/deployment.yaml @@ -18,6 +18,13 @@ spec: imagePullPolicy: Always ports: - containerPort: 50053 + resources: + requests: + cpu: 20m + memory: 40Mi + limits: + cpu: 60m + memory: 120Mi env: - name: SERVICE_PORT valueFrom: @@ -37,4 +44,33 @@ spec: valueFrom: configMapKeyRef: name: threadit-config - key: COMMUNITY_SERVICE_PORT \ No newline at end of file + key: COMMUNITY_SERVICE_PORT + readinessProbe: + tcpSocket: + port: 50053 + initialDelaySeconds: 5 + timeoutSeconds: 3 + livenessProbe: + tcpSocket: + port: 50053 + initialDelaySeconds: 15 + timeoutSeconds: 3 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: thread-service-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: thread-service + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/code/kubernetes/services/vote-service/deployment.yaml b/code/kubernetes/services/vote-service/deployment.yaml index 8480665..44b329e 100644 --- a/code/kubernetes/services/vote-service/deployment.yaml +++ b/code/kubernetes/services/vote-service/deployment.yaml @@ -18,6 +18,13 @@ spec: imagePullPolicy: Always ports: - containerPort: 50055 + resources: + requests: + cpu: 20m + memory: 40Mi + limits: + cpu: 60m + memory: 120Mi env: - name: SERVICE_PORT valueFrom: @@ -37,4 +44,33 @@ spec: valueFrom: configMapKeyRef: name: threadit-config - key: COMMENT_SERVICE_PORT \ No newline at end of file + key: COMMENT_SERVICE_PORT + readinessProbe: + tcpSocket: + port: 50055 + initialDelaySeconds: 5 + timeoutSeconds: 3 + livenessProbe: + tcpSocket: + port: 50055 + initialDelaySeconds: 15 + timeoutSeconds: 3 +--- +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: vote-service-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: vote-service + minReplicas: 1 + maxReplicas: 3 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 80 \ No newline at end of file diff --git a/code/kubernetes/traefik/values.yaml b/code/kubernetes/traefik/values.yaml index 9f28b52..56f8925 100644 --- a/code/kubernetes/traefik/values.yaml +++ b/code/kubernetes/traefik/values.yaml @@ -1,12 +1,23 @@ -# https://github.com/traefik/traefik-helm-chart/blob/master/traefik/VALUES.md +# traefik/values.yaml -# autoscaling: # TODO: If needed, uncomment and configure HPA settings -# enabled: true -# maxReplicas: 2 -# metrics: -# - type: Resource -# resource: -# name: cpu -# target: -# type: Utilization -# averageUtilization: 60 +replicaCount: 1 + +resources: + requests: + cpu: 40m + memory: 80Mi + limits: + cpu: 120m + memory: 240Mi + +# TODO: If needed, uncomment and configure HPA settings +#autoscaling: +# enabled: true +# maxReplicas: 2 +# metrics: +# - type: Resource +# resource: +# name: cpu +# target: +# type: Utilization +# averageUtilization: 60 \ No newline at end of file diff --git a/code/proto/comment-service.proto b/code/proto/comment-service.proto index 8a0b13c..39653bb 100644 --- a/code/proto/comment-service.proto +++ b/code/proto/comment-service.proto @@ -9,6 +9,8 @@ import "google/api/annotations.proto"; import "models.proto"; service CommentService { + rpc CheckHealth(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc ListComments(ListCommentsRequest) returns (ListCommentsResponse) { option (google.api.http) = { get: "/comments" diff --git a/code/proto/community-service.proto b/code/proto/community-service.proto index efcce95..093ff78 100644 --- a/code/proto/community-service.proto +++ b/code/proto/community-service.proto @@ -9,6 +9,8 @@ import "google/api/annotations.proto"; import "models.proto"; service CommunityService { + rpc CheckHealth(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc ListCommunities(ListCommunitiesRequest) returns (ListCommunitiesResponse) { option (google.api.http) = { get: "/communities" diff --git a/code/proto/popular-service.proto b/code/proto/popular-service.proto index a228286..9a66d9d 100644 --- a/code/proto/popular-service.proto +++ b/code/proto/popular-service.proto @@ -4,15 +4,19 @@ package popular; option go_package = "gen/popular-service/pb;pb"; +import "google/protobuf/empty.proto"; import "google/api/annotations.proto"; import "models.proto"; service PopularService { + rpc CheckHealth(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc GetPopularThreads(GetPopularThreadsRequest) returns (GetPopularThreadsResponse) { option (google.api.http) = { get: "/popular/threads" }; } + rpc GetPopularComments(GetPopularCommentsRequest) returns (GetPopularCommentsResponse) { option (google.api.http) = { get: "/popular/comments" diff --git a/code/proto/search-service.proto b/code/proto/search-service.proto index 5756c05..81f94ed 100644 --- a/code/proto/search-service.proto +++ b/code/proto/search-service.proto @@ -4,10 +4,13 @@ package search; option go_package = "gen/search-service/pb;pb"; +import "google/protobuf/empty.proto"; import "google/api/annotations.proto"; import "models.proto"; service SearchService { + rpc CheckHealth(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc GlobalSearch (SearchRequest) returns (GlobalSearchResponse) { option (google.api.http) = { get: "/search" diff --git a/code/proto/thread-service.proto b/code/proto/thread-service.proto index e069bac..320610f 100644 --- a/code/proto/thread-service.proto +++ b/code/proto/thread-service.proto @@ -9,6 +9,8 @@ import "google/api/annotations.proto"; import "models.proto"; service ThreadService { + rpc CheckHealth(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc ListThreads (ListThreadsRequest) returns (ListThreadsResponse) { option (google.api.http) = { get: "/threads" diff --git a/code/proto/vote-service.proto b/code/proto/vote-service.proto index f634eaf..27a4a88 100644 --- a/code/proto/vote-service.proto +++ b/code/proto/vote-service.proto @@ -8,6 +8,8 @@ import "google/protobuf/empty.proto"; import "google/api/annotations.proto"; service VoteService { + rpc CheckHealth(google.protobuf.Empty) returns (google.protobuf.Empty); + rpc UpvoteThread (VoteThreadRequest) returns (google.protobuf.Empty) { option (google.api.http) = { post: "/votes/thread/{thread_id}/up" diff --git a/code/services/comment-service/src/server.go b/code/services/comment-service/src/server.go index e906b70..6eabede 100644 --- a/code/services/comment-service/src/server.go +++ b/code/services/comment-service/src/server.go @@ -23,6 +23,10 @@ const ( MaxCommentLength = 500 ) +func (s *CommentServer) CheckHealth(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil +} + func (s *CommentServer) ListComments(ctx context.Context, req *commentpb.ListCommentsRequest) (*commentpb.ListCommentsResponse, error) { // validate inputs if req.ThreadId != nil && req.GetThreadId() == "" { diff --git a/code/services/community-service/src/server.go b/code/services/community-service/src/server.go index fe5e330..17eee01 100644 --- a/code/services/community-service/src/server.go +++ b/code/services/community-service/src/server.go @@ -6,10 +6,11 @@ import ( dbpb "gen/db-service/pb" models "gen/models/pb" threadpb "gen/thread-service/pb" + "math" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" - "math" ) type CommunityServer struct { @@ -23,6 +24,10 @@ const ( MaxLength = 50 ) +func (s *CommunityServer) CheckHealth(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil +} + func (s *CommunityServer) ListCommunities(ctx context.Context, req *communitypb.ListCommunitiesRequest) (*communitypb.ListCommunitiesResponse, error) { // validate inputs if req.Offset != nil && req.GetOffset() < 0 { diff --git a/code/services/popular-service/src/server.go b/code/services/popular-service/src/server.go index 2274701..a7e0788 100644 --- a/code/services/popular-service/src/server.go +++ b/code/services/popular-service/src/server.go @@ -8,6 +8,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" ) type PopularServer struct { @@ -16,6 +17,10 @@ type PopularServer struct { CommentClient commentpb.CommentServiceClient } +func (s *PopularServer) CheckHealth(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil +} + func (s *PopularServer) GetPopularThreads(ctx context.Context, req *popularpb.GetPopularThreadsRequest) (*popularpb.GetPopularThreadsResponse, error) { // validate inputs if req.Offset != nil && req.GetOffset() < 0 { diff --git a/code/services/search-service/src/server.go b/code/services/search-service/src/server.go index 8ccfaa6..b0248f1 100644 --- a/code/services/search-service/src/server.go +++ b/code/services/search-service/src/server.go @@ -6,8 +6,10 @@ import ( models "gen/models/pb" searchpb "gen/search-service/pb" threadpb "gen/thread-service/pb" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/emptypb" ) type SearchServer struct { @@ -16,6 +18,10 @@ type SearchServer struct { ThreadClient threadpb.ThreadServiceClient } +func (s *SearchServer) CheckHealth(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil +} + func (s *SearchServer) GlobalSearch(ctx context.Context, req *searchpb.SearchRequest) (*searchpb.GlobalSearchResponse, error) { // validate inputs reqErr := validateSearchRequest(req) diff --git a/code/services/thread-service/src/server.go b/code/services/thread-service/src/server.go index 6b2c1cd..2e334a7 100644 --- a/code/services/thread-service/src/server.go +++ b/code/services/thread-service/src/server.go @@ -26,6 +26,10 @@ const ( MinContentLength = 3 ) +func (s *ThreadServer) CheckHealth(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil +} + func (s *ThreadServer) ListThreads(ctx context.Context, req *threadpb.ListThreadsRequest) (*threadpb.ListThreadsResponse, error) { // validate inputs if req.CommunityId != nil && req.GetCommunityId() == "" { diff --git a/code/services/vote-service/src/server.go b/code/services/vote-service/src/server.go index 610841c..966db00 100644 --- a/code/services/vote-service/src/server.go +++ b/code/services/vote-service/src/server.go @@ -5,6 +5,7 @@ import ( commentpb "gen/comment-service/pb" threadpb "gen/thread-service/pb" votepb "gen/vote-service/pb" + "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/types/known/emptypb" @@ -16,6 +17,10 @@ type VoteServer struct { CommentClient commentpb.CommentServiceClient } +func (s *VoteServer) CheckHealth(ctx context.Context, req *emptypb.Empty) (*emptypb.Empty, error) { + return &emptypb.Empty{}, nil +} + func (s *VoteServer) UpvoteThread(ctx context.Context, req *votepb.VoteThreadRequest) (*emptypb.Empty, error) { return s.updateThreadVote(ctx, req, 1) } diff --git a/code/traefik/dynamic.yml b/code/traefik/dynamic.yml index c98bbe5..d679a0f 100644 --- a/code/traefik/dynamic.yml +++ b/code/traefik/dynamic.yml @@ -61,6 +61,14 @@ http: - "cors" - "api-strip-prefix" + grpc-gateway: + rule: "PathPrefix(`/api`)" + service: "gprc-gateway" + entryPoints: ["web"] + middlewares: + - "cors" + - "api-strip-prefix" + services: community-service: loadBalancer: @@ -91,3 +99,8 @@ http: loadBalancer: servers: - url: "http://grpc-gateway:8080/popular" + + gprc-gateway: + loadBalancer: + servers: + - url: "http://grpc-gateway:8080"