From af1434729106b11e1e9371b20aafc6a9230ac745 Mon Sep 17 00:00:00 2001 From: Nikhil-Ladha Date: Mon, 8 Jul 2024 17:39:18 +0530 Subject: [PATCH] internal: add sidecar service and client setup for volume group added sidecar service and client setup code for volume group feature Signed-off-by: Nikhil-Ladha --- internal/client/fake/volumegroup-client.go | 51 ++++++++ internal/client/volumegroup-client.go | 99 ++++++++++++++++ internal/client/volumegroup-client_test.go | 125 ++++++++++++++++++++ internal/sidecar/service/volumegroup.go | 129 +++++++++++++++++++++ 4 files changed, 404 insertions(+) create mode 100644 internal/client/fake/volumegroup-client.go create mode 100644 internal/client/volumegroup-client.go create mode 100644 internal/client/volumegroup-client_test.go create mode 100644 internal/sidecar/service/volumegroup.go diff --git a/internal/client/fake/volumegroup-client.go b/internal/client/fake/volumegroup-client.go new file mode 100644 index 000000000..ccfe80220 --- /dev/null +++ b/internal/client/fake/volumegroup-client.go @@ -0,0 +1,51 @@ +/* +Copyright 2024 The Kubernetes-CSI-Addons Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fake + +import "github.com/csi-addons/kubernetes-csi-addons/internal/proto" + +// VolumeGroupClient to fake grouping operations. +type VolumeGroupClient struct { + // CreateVolumeGroupMock mocks CreateVolumeGroup RPC call. + CreateVolumeGroupMock func(volumeGroupName string, volumeIDs []string) (*proto.CreateVolumeGroupResponse, error) + // ModifyVolumeGroupMembershipMock mock ModifyVolumeGroupMembership RPC call. + ModifyVolumeGroupMembershipMock func(volumeGroupID string, volumeIDs []string) (*proto.ModifyVolumeGroupMembershipResponse, error) + // DeleteVolumeGroupMock mocks DeleteVolumeGroup RPC call. + DeleteVolumeGroupMock func(volumeGroupID string) (*proto.DeleteVolumeGroupResponse, error) + // ControllerGetVolumeGroupMock mocks ControllerGetVolumeGroup RPC call. + ControllerGetVolumeGroupMock func(volumeGroupID string) (*proto.ControllerGetVolumeGroupResponse, error) +} + +// CreateVolumeGroup calls CreateVolumeGroupMock mock function. +func (vg *VolumeGroupClient) CreateVolumeGroup(volumeGroupName string, volumeIDs []string) (*proto.CreateVolumeGroupResponse, error) { + return vg.CreateVolumeGroupMock(volumeGroupName, volumeIDs) +} + +// ModifyVolumeGroupMembership calls ModifyVolumeGroupMembership mock function. +func (vg *VolumeGroupClient) ModifyVolumeGroupMembership(volumeGroupID string, volumeIDs []string) (*proto.ModifyVolumeGroupMembershipResponse, error) { + return vg.ModifyVolumeGroupMembershipMock(volumeGroupID, volumeIDs) +} + +// DeleteVolumeGroup calls DeleteVolumeGroup mock function. +func (vg *VolumeGroupClient) DeleteVolumeGroup(volumeGroupID string) (*proto.DeleteVolumeGroupResponse, error) { + return vg.DeleteVolumeGroupMock(volumeGroupID) +} + +// ControllerGetVolumeGroup calls ControllerGetVolumeGroup mock function. +func (vg *VolumeGroupClient) ControllerGetVolumeGroup(volumeGroupID string) (*proto.ControllerGetVolumeGroupResponse, error) { + return vg.ControllerGetVolumeGroupMock(volumeGroupID) +} diff --git a/internal/client/volumegroup-client.go b/internal/client/volumegroup-client.go new file mode 100644 index 000000000..635441c37 --- /dev/null +++ b/internal/client/volumegroup-client.go @@ -0,0 +1,99 @@ +/* +Copyright 2024 The Kubernetes-CSI-Addons Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "context" + "time" + + "github.com/csi-addons/kubernetes-csi-addons/internal/proto" + + "google.golang.org/grpc" +) + +type volumeGroupClient struct { + client proto.ControllerClient + timeout time.Duration +} + +// VolumeGroup holds the methods required for volume grouping. +type VolumeGroup interface { + // CreateVolumeGroup RPC call to create a volume group. + CreateVolumeGroup(volumeGroupName string, volumeIDs []string) (*proto.CreateVolumeGroupResponse, error) + // ModifyVolumeGroupMembership RPC call to modify the volume group. + ModifyVolumeGroupMembership(volumeGroupID string, volumeIDs []string) (*proto.ModifyVolumeGroupMembershipResponse, error) + // DeleteVolumeGroup RPC call to delete the volume group. + DeleteVolumeGroup(volumeGroupID string) (*proto.DeleteVolumeGroupResponse, error) + // ControllerGetVolumeGroup RPC call to fetch the volume group. + ControllerGetVolumeGroup(volumeGroupID string) (*proto.ControllerGetVolumeGroupResponse, error) +} + +// NewReplicationClient returns VolumeGroup interface which has the RPC +// calls for grouping. +func NewVolumeGroupClient(cc *grpc.ClientConn, timeout time.Duration) VolumeGroup { + return &volumeGroupClient{client: proto.NewControllerClient(cc), timeout: timeout} +} + +func (vg *volumeGroupClient) CreateVolumeGroup(volumeGroupName string, volumeIDs []string) (*proto.CreateVolumeGroupResponse, error) { + req := &proto.CreateVolumeGroupRequest{ + Name: volumeGroupName, + VolumeIds: volumeIDs, + } + + createCtx, cancel := context.WithTimeout(context.Background(), vg.timeout) + defer cancel() + resp, err := vg.client.CreateVolumeGroup(createCtx, req) + + return resp, err +} + +func (vg *volumeGroupClient) ModifyVolumeGroupMembership(volumeGroupID string, volumeIDs []string) (*proto.ModifyVolumeGroupMembershipResponse, error) { + req := &proto.ModifyVolumeGroupMembershipRequest{ + VolumeGroupId: volumeGroupID, + VolumeIds: volumeIDs, + } + + createCtx, cancel := context.WithTimeout(context.Background(), vg.timeout) + defer cancel() + resp, err := vg.client.ModifyVolumeGroupMembership(createCtx, req) + + return resp, err +} + +func (vg *volumeGroupClient) DeleteVolumeGroup(volumeGroupID string) (*proto.DeleteVolumeGroupResponse, error) { + req := &proto.DeleteVolumeGroupRequest{ + VolumeGroupId: volumeGroupID, + } + + createCtx, cancel := context.WithTimeout(context.Background(), vg.timeout) + defer cancel() + resp, err := vg.client.DeleteVolumeGroup(createCtx, req) + + return resp, err +} + +func (vg *volumeGroupClient) ControllerGetVolumeGroup(volumeGroupID string) (*proto.ControllerGetVolumeGroupResponse, error) { + req := &proto.ControllerGetVolumeGroupRequest{ + VolumeGroupId: volumeGroupID, + } + + createCtx, cancel := context.WithTimeout(context.Background(), vg.timeout) + defer cancel() + resp, err := vg.client.ControllerGetVolumeGroup(createCtx, req) + + return resp, err +} diff --git a/internal/client/volumegroup-client_test.go b/internal/client/volumegroup-client_test.go new file mode 100644 index 000000000..66659f3fa --- /dev/null +++ b/internal/client/volumegroup-client_test.go @@ -0,0 +1,125 @@ +/* +Copyright 2024 The Kubernetes-CSI-Addons Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package client + +import ( + "errors" + "testing" + + "github.com/csi-addons/kubernetes-csi-addons/internal/client/fake" + "github.com/csi-addons/kubernetes-csi-addons/internal/proto" + + "github.com/stretchr/testify/assert" +) + +func TestCreateVolumeGroup(t *testing.T) { + t.Parallel() + mockedCreateVolumeGroup := &fake.VolumeGroupClient{ + CreateVolumeGroupMock: func(volumeGroupName string, volumeIDs []string) (*proto.CreateVolumeGroupResponse, error) { + return &proto.CreateVolumeGroupResponse{}, nil + }, + } + client := mockedCreateVolumeGroup + resp, err := client.CreateVolumeGroup("", nil) + assert.Equal(t, &proto.CreateVolumeGroupResponse{}, resp) + assert.Nil(t, err) + + // return error + mockedCreateVolumeGroup = &fake.VolumeGroupClient{ + CreateVolumeGroupMock: func(volumeGroupName string, volumeIDs []string) (*proto.CreateVolumeGroupResponse, error) { + return nil, errors.New("failed to create volume group") + }, + } + client = mockedCreateVolumeGroup + resp, err = client.CreateVolumeGroup("", nil) + assert.Nil(t, resp) + assert.NotNil(t, err) +} + +func TestDeleteVolumeGroup(t *testing.T) { + t.Parallel() + mockedDeleteVolumeGroup := &fake.VolumeGroupClient{ + DeleteVolumeGroupMock: func(volumeGroupID string) (*proto.DeleteVolumeGroupResponse, error) { + return &proto.DeleteVolumeGroupResponse{}, nil + }, + } + client := mockedDeleteVolumeGroup + resp, err := client.DeleteVolumeGroup("") + assert.Equal(t, &proto.DeleteVolumeGroupResponse{}, resp) + assert.Nil(t, err) + + // return error + mockedDeleteVolumeGroup = &fake.VolumeGroupClient{ + DeleteVolumeGroupMock: func(volumeGroupID string) (*proto.DeleteVolumeGroupResponse, error) { + return nil, errors.New("failed to delete volume group") + }, + } + client = mockedDeleteVolumeGroup + resp, err = client.DeleteVolumeGroup("") + assert.Nil(t, resp) + assert.NotNil(t, err) +} + +func TestModifyVolumeGroupMembership(t *testing.T) { + t.Parallel() + // return success response + mockedModifyVolumeGroup := &fake.VolumeGroupClient{ + ModifyVolumeGroupMembershipMock: func(volumeGroupID string, volumeIDs []string) (*proto.ModifyVolumeGroupMembershipResponse, error) { + return &proto.ModifyVolumeGroupMembershipResponse{}, nil + }, + } + client := mockedModifyVolumeGroup + resp, err := client.ModifyVolumeGroupMembership("", nil) + assert.Equal(t, &proto.ModifyVolumeGroupMembershipResponse{}, resp) + assert.Nil(t, err) + + // return error + mockedModifyVolumeGroup = &fake.VolumeGroupClient{ + ModifyVolumeGroupMembershipMock: func(volumeGroupID string, volumeIDs []string) (*proto.ModifyVolumeGroupMembershipResponse, error) { + return nil, errors.New("failed to modify volume group") + }, + } + client = mockedModifyVolumeGroup + resp, err = client.ModifyVolumeGroupMembership("", nil) + assert.Nil(t, resp) + assert.NotNil(t, err) +} + +func TestControllerGetVolumeGroup(t *testing.T) { + t.Parallel() + // return success response + mockedGetVolumeGroup := &fake.VolumeGroupClient{ + ControllerGetVolumeGroupMock: func(volumeGroupID string) (*proto.ControllerGetVolumeGroupResponse, error) { + return &proto.ControllerGetVolumeGroupResponse{}, nil + }, + } + client := mockedGetVolumeGroup + resp, err := client.ControllerGetVolumeGroup("") + assert.Equal(t, &proto.ControllerGetVolumeGroupResponse{}, resp) + assert.Nil(t, err) + + // return error + mockedGetVolumeGroup = &fake.VolumeGroupClient{ + ControllerGetVolumeGroupMock: func(volumeGroupID string) (*proto.ControllerGetVolumeGroupResponse, error) { + return nil, errors.New("failed to get volume group") + }, + } + client = mockedGetVolumeGroup + resp, err = client.ControllerGetVolumeGroup("") + assert.Nil(t, resp) + assert.NotNil(t, err) +} diff --git a/internal/sidecar/service/volumegroup.go b/internal/sidecar/service/volumegroup.go new file mode 100644 index 000000000..9e232ff26 --- /dev/null +++ b/internal/sidecar/service/volumegroup.go @@ -0,0 +1,129 @@ +/* +Copyright 2024 The Kubernetes-CSI-Addons Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package service + +import ( + "context" + + "github.com/csi-addons/kubernetes-csi-addons/internal/proto" + + "google.golang.org/grpc" + "k8s.io/client-go/kubernetes" + "k8s.io/klog/v2" +) + +// VolumeGroupServer struct of sidecar with supported methods of proto +// volumegroup server spec and also containing volumegroup +// controller client to csi driver. +type VolumeGroupServer struct { + proto.UnimplementedControllerServer + controllerClient proto.ControllerClient + kubeClient *kubernetes.Clientset +} + +// NewVolumeGroupServer creates a new VolumeGroupServer which handles the proto.VolumeGroup +// Service requests. +func NewVolumeGroupServer(c *grpc.ClientConn, kc *kubernetes.Clientset) *VolumeGroupServer { + return &VolumeGroupServer{ + controllerClient: proto.NewControllerClient(c), + kubeClient: kc, + } +} + +// RegisterService registers service with the server. +func (vg *VolumeGroupServer) RegisterService(server grpc.ServiceRegistrar) { + proto.RegisterControllerServer(server, vg) +} + +// CreateVolumeGroup calls CSI-Addons CreateVolumeGroup service. +func (vg *VolumeGroupServer) CreateVolumeGroup( + ctx context.Context, + req *proto.CreateVolumeGroupRequest) (*proto.CreateVolumeGroupResponse, error) { + // Get the VolumeGroup name and volumeIds from the request + vgReq := &proto.CreateVolumeGroupRequest{ + Name: req.GetName(), + VolumeIds: req.GetVolumeIds(), + } + + vgResp, err := vg.controllerClient.CreateVolumeGroup(ctx, vgReq) + if err != nil { + klog.Errorf("Failed to create volume group: %v", err) + return nil, err + } + + return &proto.CreateVolumeGroupResponse{ + VolumeGroupId: vgResp.GetVolumeGroupId(), + }, nil +} + +// ModifyVolumeGroupMembership calls CSI-Addons ModifyVolumeGroupMembership service. +func (vg *VolumeGroupServer) ModifyVolumeGroupMembership( + ctx context.Context, + req *proto.ModifyVolumeGroupMembershipRequest) (*proto.ModifyVolumeGroupMembershipResponse, error) { + // Get the volumeGroup Id and volumeIds from the request + vgReq := &proto.ModifyVolumeGroupMembershipRequest{ + VolumeGroupId: req.GetVolumeGroupId(), + VolumeIds: req.GetVolumeIds(), + } + + vgResp, err := vg.controllerClient.ModifyVolumeGroupMembership(ctx, vgReq) + if err != nil { + klog.Errorf("Failed to modify volume group: %v", err) + return nil, err + } + + return &proto.ModifyVolumeGroupMembershipResponse{ + VolumeGroupId: vgResp.GetVolumeGroupId(), + }, nil +} + +// DeleteVolumeGroup calls CSI-Addons DeleteVolumeGroup service. +func (vg *VolumeGroupServer) DeleteVolumeGroup( + ctx context.Context, + req *proto.DeleteVolumeGroupRequest) (*proto.DeleteVolumeGroupResponse, error) { + // Get the volumeGroup Id from the request + vgReq := &proto.DeleteVolumeGroupRequest{ + VolumeGroupId: req.GetVolumeGroupId(), + } + + _, err := vg.controllerClient.DeleteVolumeGroup(ctx, vgReq) + if err != nil { + klog.Errorf("Failed to delete volume group: %v", err) + return nil, err + } + + return &proto.DeleteVolumeGroupResponse{}, nil +} + +// ControllerGetVolumeGroup calls CSI-Addons ControllerGetVolumeGroup service. +func (vg *VolumeGroupServer) ControllerGetVolumeGroup( + ctx context.Context, + req *proto.ControllerGetVolumeGroupRequest) (*proto.ControllerGetVolumeGroupResponse, error) { + // Get the volumeGroup Id from the request + vgReq := &proto.ControllerGetVolumeGroupRequest{ + VolumeGroupId: req.GetVolumeGroupId(), + } + + vgResp, err := vg.controllerClient.ControllerGetVolumeGroup(ctx, vgReq) + if err != nil { + klog.Errorf("Failed to get volume group: %v", err) + return nil, err + } + + return &proto.ControllerGetVolumeGroupResponse{ + VolumeGroupId: vgResp.GetVolumeGroupId(), + }, nil +}