From c1b863215eb908a9a14acab323575c1fa71d3a59 Mon Sep 17 00:00:00 2001 From: Boris Glimcher Date: Fri, 14 Jul 2023 00:05:50 +0300 Subject: [PATCH] VRF: move from cloud APIs to evpngw APIs Signed-off-by: Boris Glimcher --- README.md | 8 +-- cmd/main.go | 2 + docker-compose.yml | 35 ++++++------ pkg/evpn/evpn.go | 6 +- pkg/evpn/evpn_test.go | 6 +- pkg/evpn/svi_subnet.go | 4 +- pkg/evpn/svi_subnet_test.go | 4 +- pkg/evpn/vrf.go | 88 +++++++++++++---------------- pkg/evpn/vrf_test.go | 110 +++++++++++++++++++----------------- 9 files changed, 135 insertions(+), 128 deletions(-) diff --git a/README.md b/README.md index 3854697f..f615f9d9 100644 --- a/README.md +++ b/README.md @@ -30,18 +30,18 @@ Run `docker-compose up -d` ## Manual gRPC example ```bash -docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"vpc" : {"spec" : {"v4_route_table_name_ref" : "1234"} }, "vpc_id" : "testvpc", "parent" : "todo" }' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateVpc -docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"subnet" : {"spec" : {"vpc_name_ref": "//network.opiproject.org/vpcs/blue", "virtual_router_mac": "qrvMAAAB", "v4_prefix": {"addr": 336860161, "len": 24} } }, "subnet_id" : "testbridge", "parent" : "todo" }' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateSubnet +docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"vrf" : {"spec" : {"vni" : 1234, "loopback_ip_prefix" : {} }}, "vrf_id" : "testvrf" }' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateVrf" +docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"subnet" : {"spec" : {"vpc_name_ref": "//network.opiproject.org/vrfs/blue", "virtual_router_mac": "qrvMAAAB", "v4_prefix": {"addr": 336860161, "len": 24} } }, "subnet_id" : "testbridge", "parent" : "todo" }' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateSubnet docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"interface" : {"spec" : {"ifid": 11, "l3_if_spec": {"vpc_name_ref": "//network.opiproject.org/subnets/testbridge", mac_address: "qrvMAAAB"}} }, "interface_id" : "testinterface", "parent" : "todo" }' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateInterface docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"tunnel" : {"spec" : {"vpc_name_ref": "//network.opiproject.org/subnets/testbridge", "local_ip": {"af": "IP_AF_INET", "v4_addr": 336860161}, "encap": {"type": "ENCAP_TYPE_VXLAN", "value": {"vnid": 100}} } }, "tunnel_id" : "testvxlan", "parent" : "todo" }' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateTunnel docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name": "//network.opiproject.org/interfaces/testinterface"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetInterface docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name": "//network.opiproject.org/subnets/testbridge"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetSubnet docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name": "//network.opiproject.org/tunnels/testvxlan"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetTunnel -docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name": "//network.opiproject.org/vpcs/testvpc"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetVpc +docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name": "//network.opiproject.org/vrfs/testvrf"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetVrf docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name": "//network.opiproject.org/interfaces/testinterface"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteInterface docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name": "//network.opiproject.org/subnets/testbridge"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteSubnet docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name" : "//network.opiproject.org/tunnels/testvxlan"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteTunnel -docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name" : "//network.opiproject.org/vpcs/testvpc"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteVpc +docker-compose exec opi-evpn-bridge grpcurl -plaintext -d '{"name" : "//network.opiproject.org/vrfs/testvrf"}' localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteVrf ``` ## Architecture Diagram diff --git a/cmd/main.go b/cmd/main.go index 13ae2beb..e062889a 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -12,6 +12,7 @@ import ( "net" pb "github.com/opiproject/opi-api/network/cloud/v1alpha1/gen/go" + pe "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" "github.com/opiproject/opi-evpn-bridge/pkg/evpn" "google.golang.org/grpc" @@ -33,6 +34,7 @@ func main() { // TODO: replace cloud -> evpn pb.RegisterCloudInfraServiceServer(s, opi) + pe.RegisterVrfServiceServer(s, opi) reflection.Register(s) diff --git a/docker-compose.yml b/docker-compose.yml index 1a1be458..c9817e90 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -270,27 +270,30 @@ services: /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateInterface -l && \ /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateSubnet -l && \ /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateTunnel -l && \ - /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.CreateVpc -l && \ /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetInterface -l && \ /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetSubnet -l && \ /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetTunnel -l && \ - /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.GetVpc -l && \ /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteInterface -l && \ /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteSubnet -l && \ /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteTunnel -l && \ - /entrypoint.sh ls localhost:50151 opi_api.network.cloud.v1alpha1.CloudInfraService.DeleteVpc -l && \ + /entrypoint.sh ls localhost:50151 opi_api.network.evpn_gw.v1alpha1.VrfService -l && \ + /entrypoint.sh ls localhost:50151 opi_api.network.evpn_gw.v1alpha1.VrfService.CreateVrf -l && \ + /entrypoint.sh ls localhost:50151 opi_api.network.evpn_gw.v1alpha1.VrfService.ListVrfs -l && \ + /entrypoint.sh ls localhost:50151 opi_api.network.evpn_gw.v1alpha1.VrfService.GetVrf -l && \ + /entrypoint.sh ls localhost:50151 opi_api.network.evpn_gw.v1alpha1.VrfService.DeleteVrf -l && \ + /entrypoint.sh ls localhost:50151 opi_api.network.evpn_gw.v1alpha1.VrfService.UpdateVrf -l && \ echo create && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateVpc "{\"vpc\" : {\"spec\" : {\"v4_route_table_name_ref\" : \"1000\"} }, \"vpc_id\" : \"blue\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateVpc "{\"vpc\" : {\"spec\" : {\"v4_route_table_name_ref\" : \"1001\"} }, \"vpc_id\" : \"green\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateVpc "{\"vpc\" : {\"spec\" : {\"v4_route_table_name_ref\" : \"1002\"} }, \"vpc_id\" : \"yellow\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vpcs/blue\", \"virtual_router_mac\": \"qrvMAAAB\" } }, \"subnet_id\" : \"br100\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vpcs/green\", \"virtual_router_mac\": \"qrvMAAAC\" } }, \"subnet_id\" : \"br101\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vpcs/yellow\", \"virtual_router_mac\": \"qrvMAAAD\" } }, \"subnet_id\" : \"br102\", \"parent\" : \"todo\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateVrf "{\"vrf\" : {\"spec\" : {\"vni\" : 1000, \"loopback_ip_prefix\": {} }}, \"vrf_id\" : \"blue\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateVrf "{\"vrf\" : {\"spec\" : {\"vni\" : 1001, \"loopback_ip_prefix\": {} }}, \"vrf_id\" : \"green\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateVrf "{\"vrf\" : {\"spec\" : {\"vni\" : 1002, \"loopback_ip_prefix\": {} }}, \"vrf_id\" : \"yellow\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vrfs/blue\", \"virtual_router_mac\": \"qrvMAAAB\" } }, \"subnet_id\" : \"br100\", \"parent\" : \"todo\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vrfs/green\", \"virtual_router_mac\": \"qrvMAAAC\" } }, \"subnet_id\" : \"br101\", \"parent\" : \"todo\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vrfs/yellow\", \"virtual_router_mac\": \"qrvMAAAD\" } }, \"subnet_id\" : \"br102\", \"parent\" : \"todo\" }" && \ /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : { \"virtual_router_mac\": \"qrvMAAAR\" } }, \"subnet_id\" : \"br10\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vpcs/blue\", \"virtual_router_mac\": \"qrvMAAAh\", \"v4_prefix\": {\"addr\": 336860161, \"len\": 24} } }, \"subnet_id\" : \"br20\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vpcs/blue\", \"virtual_router_mac\": \"qrvMAAAx\", \"v4_prefix\": {\"addr\": 505290241, \"len\": 24} } }, \"subnet_id\" : \"br30\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vpcs/green\", \"virtual_router_mac\": \"qrvMAABB\", \"v4_prefix\": {\"addr\": 673720321, \"len\": 24} } }, \"subnet_id\" : \"br40\", \"parent\" : \"todo\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vpcs/yellow\", \"virtual_router_mac\": \"qrvMAABC\", \"v4_prefix\": {\"addr\": 842150401, \"len\": 24} } }, \"subnet_id\" : \"br50\", \"parent\" : \"todo\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vrfs/blue\", \"virtual_router_mac\": \"qrvMAAAh\", \"v4_prefix\": {\"addr\": 336860161, \"len\": 24} } }, \"subnet_id\" : \"br20\", \"parent\" : \"todo\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vrfs/blue\", \"virtual_router_mac\": \"qrvMAAAx\", \"v4_prefix\": {\"addr\": 505290241, \"len\": 24} } }, \"subnet_id\" : \"br30\", \"parent\" : \"todo\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vrfs/green\", \"virtual_router_mac\": \"qrvMAABB\", \"v4_prefix\": {\"addr\": 673720321, \"len\": 24} } }, \"subnet_id\" : \"br40\", \"parent\" : \"todo\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 CreateSubnet "{\"subnet\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/vrfs/yellow\", \"virtual_router_mac\": \"qrvMAABC\", \"v4_prefix\": {\"addr\": 842150401, \"len\": 24} } }, \"subnet_id\" : \"br50\", \"parent\" : \"todo\" }" && \ /entrypoint.sh call --json_input --json_output localhost:50151 CreateTunnel "{\"tunnel\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/subnets/br100\", \"local_ip\": {\"af\": \"IP_AF_INET\", \"v4_addr\": 167772162}, \"encap\": {\"type\": \"ENCAP_TYPE_VXLAN\", \"value\": {\"vnid\": 100}} } }, \"tunnel_id\" : \"vni100\", \"parent\" : \"todo\" }" && \ /entrypoint.sh call --json_input --json_output localhost:50151 CreateTunnel "{\"tunnel\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/subnets/br101\", \"local_ip\": {\"af\": \"IP_AF_INET\", \"v4_addr\": 167772162}, \"encap\": {\"type\": \"ENCAP_TYPE_VXLAN\", \"value\": {\"vnid\": 101}} } }, \"tunnel_id\" : \"vni101\", \"parent\" : \"todo\" }" && \ /entrypoint.sh call --json_input --json_output localhost:50151 CreateTunnel "{\"tunnel\" : {\"spec\" : {\"vpc_name_ref\" : \"//network.opiproject.org/subnets/br102\", \"local_ip\": {\"af\": \"IP_AF_INET\", \"v4_addr\": 167772162}, \"encap\": {\"type\": \"ENCAP_TYPE_VXLAN\", \"value\": {\"vnid\": 102}} } }, \"tunnel_id\" : \"vni102\", \"parent\" : \"todo\" }" && \ @@ -304,9 +307,9 @@ services: /entrypoint.sh call --json_input --json_output localhost:50151 CreateInterface "{\"interface\" : {\"spec\" : {\"ifid\": 40, \"l3_if_spec\": {\"vpc_name_ref\" : \"//network.opiproject.org/subnets/br40\"}} }, \"interface_id\" : \"eth3\", \"parent\" : \"todo\" }" && \ /entrypoint.sh call --json_input --json_output localhost:50151 CreateInterface "{\"interface\" : {\"spec\" : {\"ifid\": 50, \"l3_if_spec\": {\"vpc_name_ref\" : \"//network.opiproject.org/subnets/br50\"}} }, \"interface_id\" : \"eth4\", \"parent\" : \"todo\" }" && \ echo get && \ - /entrypoint.sh call --json_input --json_output localhost:50151 GetVpc "{\"name\" : \"//network.opiproject.org/vpcs/blue\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 GetVpc "{\"name\" : \"//network.opiproject.org/vpcs/green\" }" && \ - /entrypoint.sh call --json_input --json_output localhost:50151 GetVpc "{\"name\" : \"//network.opiproject.org/vpcs/yellow\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 GetVrf "{\"name\" : \"//network.opiproject.org/vrfs/blue\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 GetVrf "{\"name\" : \"//network.opiproject.org/vrfs/green\" }" && \ + /entrypoint.sh call --json_input --json_output localhost:50151 GetVrf "{\"name\" : \"//network.opiproject.org/vrfs/yellow\" }" && \ /entrypoint.sh call --json_input --json_output localhost:50151 GetSubnet "{\"name\" : \"//network.opiproject.org/subnets/br100\" }" && \ /entrypoint.sh call --json_input --json_output localhost:50151 GetSubnet "{\"name\" : \"//network.opiproject.org/subnets/br101\" }" && \ /entrypoint.sh call --json_input --json_output localhost:50151 GetSubnet "{\"name\" : \"//network.opiproject.org/subnets/br102\" }" && \ diff --git a/pkg/evpn/evpn.go b/pkg/evpn/evpn.go index 27251cdf..63ef2a51 100644 --- a/pkg/evpn/evpn.go +++ b/pkg/evpn/evpn.go @@ -9,15 +9,17 @@ import ( "fmt" pb "github.com/opiproject/opi-api/network/cloud/v1alpha1/gen/go" + pe "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" ) // Server represents the Server object type Server struct { pb.UnimplementedCloudInfraServiceServer + pe.UnimplementedVrfServiceServer Subnets map[string]*pb.Subnet Interfaces map[string]*pb.Interface Tunnels map[string]*pb.Tunnel - Vpcs map[string]*pb.Vpc + Vrfs map[string]*pe.Vrf } // NewServer creates initialized instance of EVPN server @@ -26,7 +28,7 @@ func NewServer() *Server { Subnets: make(map[string]*pb.Subnet), Interfaces: make(map[string]*pb.Interface), Tunnels: make(map[string]*pb.Tunnel), - Vpcs: make(map[string]*pb.Vpc), + Vrfs: make(map[string]*pe.Vrf), } } diff --git a/pkg/evpn/evpn_test.go b/pkg/evpn/evpn_test.go index 706628dd..2be2cd49 100644 --- a/pkg/evpn/evpn_test.go +++ b/pkg/evpn/evpn_test.go @@ -14,14 +14,16 @@ import ( "google.golang.org/grpc/test/bufconn" pb "github.com/opiproject/opi-api/network/cloud/v1alpha1/gen/go" + pe "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" ) -// TODO: move test infrastructure code to a separate (test/server) package to avoid duplication - func dialer(opi *Server) func(context.Context, string) (net.Conn, error) { listener := bufconn.Listen(1024 * 1024) server := grpc.NewServer() + + // TODO: replace cloud -> evpn pb.RegisterCloudInfraServiceServer(server, opi) + pe.RegisterVrfServiceServer(server, opi) go func() { if err := server.Serve(listener); err != nil { diff --git a/pkg/evpn/svi_subnet.go b/pkg/evpn/svi_subnet.go index 71688714..79c60195 100644 --- a/pkg/evpn/svi_subnet.go +++ b/pkg/evpn/svi_subnet.go @@ -83,8 +83,8 @@ func (s *Server) CreateSubnet(_ context.Context, in *pb.CreateSubnetRequest) (*p log.Printf("error: %v", err) return nil, err } - // now get VRF/VPC to plug this bridge into - vpc, ok := s.Vpcs[in.Subnet.Spec.VpcNameRef] + // now get VRF to plug this bridge into + vpc, ok := s.Vrfs[in.Subnet.Spec.VpcNameRef] if !ok { err := status.Errorf(codes.NotFound, "unable to find key %s", in.Subnet.Spec.VpcNameRef) log.Printf("error: %v", err) diff --git a/pkg/evpn/svi_subnet_test.go b/pkg/evpn/svi_subnet_test.go index 8d5b83a0..e1be3da3 100644 --- a/pkg/evpn/svi_subnet_test.go +++ b/pkg/evpn/svi_subnet_test.go @@ -30,7 +30,7 @@ var ( testSubnetName = resourceIDToFullName("subnets", testSubnetID) testSubnet = pb.Subnet{ Spec: &pb.SubnetSpec{ - VpcNameRef: testVpcName, + VpcNameRef: testVrfName, VirtualRouterMac: []byte("qrvMAAAB"), V4Prefix: &pc.IPv4Prefix{ Addr: 336860161, @@ -205,7 +205,7 @@ func Test_DeleteSubnet(t *testing.T) { func Test_UpdateSubnet(t *testing.T) { spec := &pb.SubnetSpec{ - VpcNameRef: testVpcName, + VpcNameRef: testVrfName, VirtualRouterMac: []byte("qrvMAAAB"), V4Prefix: &pc.IPv4Prefix{ Addr: 336860161, diff --git a/pkg/evpn/vrf.go b/pkg/evpn/vrf.go index 1b8a99a7..268696ca 100644 --- a/pkg/evpn/vrf.go +++ b/pkg/evpn/vrf.go @@ -10,11 +10,10 @@ import ( "fmt" "log" "path" - "strconv" "github.com/vishvananda/netlink" - pb "github.com/opiproject/opi-api/network/cloud/v1alpha1/gen/go" + pb "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" "go.einride.tech/aip/fieldbehavior" "go.einride.tech/aip/fieldmask" @@ -26,9 +25,9 @@ import ( "google.golang.org/protobuf/types/known/emptypb" ) -// CreateVpc executes the creation of the VRF/VPC -func (s *Server) CreateVpc(_ context.Context, in *pb.CreateVpcRequest) (*pb.Vpc, error) { - log.Printf("CreateVpc: Received from client: %v", in) +// CreateVrf executes the creation of the VRF +func (s *Server) CreateVrf(_ context.Context, in *pb.CreateVrfRequest) (*pb.Vrf, error) { + log.Printf("CreateVrf: Received from client: %v", in) // check required fields if err := fieldbehavior.ValidateRequiredFields(in); err != nil { log.Printf("error: %v", err) @@ -36,31 +35,24 @@ func (s *Server) CreateVpc(_ context.Context, in *pb.CreateVpcRequest) (*pb.Vpc, } // see https://google.aip.dev/133#user-specified-ids resourceID := resourceid.NewSystemGenerated() - if in.VpcId != "" { - err := resourceid.ValidateUserSettable(in.VpcId) + if in.VrfId != "" { + err := resourceid.ValidateUserSettable(in.VrfId) if err != nil { log.Printf("error: %v", err) return nil, err } - log.Printf("client provided the ID of a resource %v, ignoring the name field %v", in.VpcId, in.Vpc.Name) - resourceID = in.VpcId + log.Printf("client provided the ID of a resource %v, ignoring the name field %v", in.VrfId, in.Vrf.Name) + resourceID = in.VrfId } - in.Vpc.Name = resourceIDToFullName("vpcs", resourceID) + in.Vrf.Name = resourceIDToFullName("vrfs", resourceID) // idempotent API when called with same key, should return same object - obj, ok := s.Vpcs[in.Vpc.Name] + obj, ok := s.Vrfs[in.Vrf.Name] if ok { - log.Printf("Already existing Vpc with id %v", in.Vpc.Name) + log.Printf("Already existing Vrf with id %v", in.Vrf.Name) return obj, nil } - // TODO: search DB for tables by this reference instead of reinterpreting this as integer - table, err := strconv.Atoi(in.Vpc.Spec.V4RouteTableNameRef) - if err != nil { - err := status.Error(codes.InvalidArgument, "could not convert V4RouteTableNameRef to integer") - log.Printf("error: %v", err) - return nil, err - } // not found, so create a new one - vrf := &netlink.Vrf{LinkAttrs: netlink.LinkAttrs{Name: resourceID}, Table: uint32(table)} + vrf := &netlink.Vrf{LinkAttrs: netlink.LinkAttrs{Name: resourceID}, Table: in.Vrf.Spec.Vni} if err := netlink.LinkAdd(vrf); err != nil { fmt.Printf("Failed to create link: %v", err) return nil, err @@ -69,17 +61,16 @@ func (s *Server) CreateVpc(_ context.Context, in *pb.CreateVpcRequest) (*pb.Vpc, fmt.Printf("Failed to up link: %v", err) return nil, err } - // TODO: replace cloud -> evpn - response := proto.Clone(in.Vpc).(*pb.Vpc) - response.Status = &pb.VpcStatus{SubnetCount: 4} - s.Vpcs[in.Vpc.Name] = response - log.Printf("CreateVpc: Sending to client: %v", response) + response := proto.Clone(in.Vrf).(*pb.Vrf) + response.Status = &pb.VrfStatus{LocalAs: 4} + s.Vrfs[in.Vrf.Name] = response + log.Printf("CreateVrf: Sending to client: %v", response) return response, nil } -// DeleteVpc deletes a VRF/VPC -func (s *Server) DeleteVpc(_ context.Context, in *pb.DeleteVpcRequest) (*emptypb.Empty, error) { - log.Printf("DeleteVpc: Received from client: %v", in) +// DeleteVrf deletes a VRF +func (s *Server) DeleteVrf(_ context.Context, in *pb.DeleteVrfRequest) (*emptypb.Empty, error) { + log.Printf("DeleteVrf: Received from client: %v", in) // check required fields if err := fieldbehavior.ValidateRequiredFields(in); err != nil { log.Printf("error: %v", err) @@ -91,7 +82,7 @@ func (s *Server) DeleteVpc(_ context.Context, in *pb.DeleteVpcRequest) (*emptypb return nil, err } // fetch object from the database - obj, ok := s.Vpcs[in.Name] + obj, ok := s.Vrfs[in.Name] if !ok { if in.AllowMissing { return &emptypb.Empty{}, nil @@ -101,7 +92,7 @@ func (s *Server) DeleteVpc(_ context.Context, in *pb.DeleteVpcRequest) (*emptypb return nil, err } resourceID := path.Base(obj.Name) - // use netlink to find VRF/VPC + // use netlink to find VRF vrf, err := netlink.LinkByName(resourceID) if err != nil { err := status.Errorf(codes.NotFound, "unable to find key %s", resourceID) @@ -113,39 +104,39 @@ func (s *Server) DeleteVpc(_ context.Context, in *pb.DeleteVpcRequest) (*emptypb fmt.Printf("Failed to up link: %v", err) return nil, err } - // use netlink to delete VRF/VPC + // use netlink to delete VRF if err := netlink.LinkDel(vrf); err != nil { fmt.Printf("Failed to delete link: %v", err) return nil, err } // remove from the Database - delete(s.Vpcs, obj.Name) + delete(s.Vrfs, obj.Name) return &emptypb.Empty{}, nil } -// UpdateVpc updates an VRF/VPC -func (s *Server) UpdateVpc(_ context.Context, in *pb.UpdateVpcRequest) (*pb.Vpc, error) { - log.Printf("UpdateVpc: Received from client: %v", in) +// UpdateVrf updates an VRF +func (s *Server) UpdateVrf(_ context.Context, in *pb.UpdateVrfRequest) (*pb.Vrf, error) { + log.Printf("UpdateVrf: Received from client: %v", in) // check required fields if err := fieldbehavior.ValidateRequiredFields(in); err != nil { log.Printf("error: %v", err) return nil, err } // Validate that a resource name conforms to the restrictions outlined in AIP-122. - if err := resourcename.Validate(in.Vpc.Name); err != nil { + if err := resourcename.Validate(in.Vrf.Name); err != nil { log.Printf("error: %v", err) return nil, err } // fetch object from the database - volume, ok := s.Vpcs[in.Vpc.Name] + volume, ok := s.Vrfs[in.Vrf.Name] if !ok { // TODO: introduce "in.AllowMissing" field. In case "true", create a new resource, don't return error - err := status.Errorf(codes.NotFound, "unable to find key %s", in.Vpc.Name) + err := status.Errorf(codes.NotFound, "unable to find key %s", in.Vrf.Name) log.Printf("error: %v", err) return nil, err } // update_mask = 2 - if err := fieldmask.Validate(in.UpdateMask, in.Vpc); err != nil { + if err := fieldmask.Validate(in.UpdateMask, in.Vrf); err != nil { log.Printf("error: %v", err) return nil, err } @@ -162,17 +153,16 @@ func (s *Server) UpdateVpc(_ context.Context, in *pb.UpdateVpcRequest) (*pb.Vpc, fmt.Printf("Failed to update link: %v", err) return nil, err } - // TODO: replace cloud -> evpn - response := proto.Clone(in.Vpc).(*pb.Vpc) - response.Status = &pb.VpcStatus{SubnetCount: 4} - s.Vpcs[in.Vpc.Name] = response - log.Printf("UpdateVpc: Sending to client: %v", response) + response := proto.Clone(in.Vrf).(*pb.Vrf) + response.Status = &pb.VrfStatus{LocalAs: 4} + s.Vrfs[in.Vrf.Name] = response + log.Printf("UpdateVrf: Sending to client: %v", response) return response, nil } -// GetVpc gets an VRF/VPC -func (s *Server) GetVpc(_ context.Context, in *pb.GetVpcRequest) (*pb.Vpc, error) { - log.Printf("GetVpc: Received from client: %v", in) +// GetVrf gets an VRF +func (s *Server) GetVrf(_ context.Context, in *pb.GetVrfRequest) (*pb.Vrf, error) { + log.Printf("GetVrf: Received from client: %v", in) // check required fields if err := fieldbehavior.ValidateRequiredFields(in); err != nil { log.Printf("error: %v", err) @@ -184,7 +174,7 @@ func (s *Server) GetVpc(_ context.Context, in *pb.GetVpcRequest) (*pb.Vpc, error return nil, err } // fetch object from the database - obj, ok := s.Vpcs[in.Name] + obj, ok := s.Vrfs[in.Name] if !ok { err := status.Errorf(codes.NotFound, "unable to find key %s", in.Name) log.Printf("error: %v", err) @@ -198,5 +188,5 @@ func (s *Server) GetVpc(_ context.Context, in *pb.GetVpcRequest) (*pb.Vpc, error return nil, err } // TODO - return &pb.Vpc{Name: in.Name, Spec: &pb.VpcSpec{V4RouteTableNameRef: obj.Spec.V4RouteTableNameRef, Tos: 11}, Status: &pb.VpcStatus{SubnetCount: 77}}, nil + return &pb.Vrf{Name: in.Name, Spec: &pb.VrfSpec{Vni: obj.Spec.Vni}, Status: &pb.VrfStatus{LocalAs: 77}}, nil } diff --git a/pkg/evpn/vrf_test.go b/pkg/evpn/vrf_test.go index 7748df3d..c6679a9f 100644 --- a/pkg/evpn/vrf_test.go +++ b/pkg/evpn/vrf_test.go @@ -21,40 +21,44 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/fieldmaskpb" - pb "github.com/opiproject/opi-api/network/cloud/v1alpha1/gen/go" + pb "github.com/opiproject/opi-api/network/evpn-gw/v1alpha1/gen/go" + pc "github.com/opiproject/opi-api/network/opinetcommon/v1alpha1/gen/go" ) var ( - testVpcID = "opi-vpc8" - testVpcName = resourceIDToFullName("vpcs", testVpcID) - testVpc = pb.Vpc{ - Spec: &pb.VpcSpec{ - V4RouteTableNameRef: "1000", + testVrfID = "opi-vrf8" + testVrfName = resourceIDToFullName("vrfs", testVrfID) + testVrf = pb.Vrf{ + Spec: &pb.VrfSpec{ + Vni: 1000, + LoopbackIpPrefix: &pc.IPPrefix{ + Len: 24, + }, }, } ) -func Test_CreateVpc(t *testing.T) { +func Test_CreateVrf(t *testing.T) { tests := map[string]struct { id string - in *pb.Vpc - out *pb.Vpc + in *pb.Vrf + out *pb.Vrf errCode codes.Code errMsg string exist bool }{ "illegal resource_id": { "CapitalLettersNotAllowed", - &testVpc, + &testVrf, nil, codes.Unknown, fmt.Sprintf("user-settable ID must only contain lowercase, numbers and hyphens (%v)", "got: 'C' in position 0"), false, }, "already exists": { - testVpcID, - &testVpc, - &testVpc, + testVrfID, + &testVrf, + &testVrf, codes.OK, "", true, @@ -80,17 +84,17 @@ func Test_CreateVpc(t *testing.T) { log.Fatal(err) } }(conn) - client := pb.NewCloudInfraServiceClient(conn) + client := pb.NewVrfServiceClient(conn) if tt.exist { - opi.Vpcs[testVpcName] = &testVpc + opi.Vrfs[testVrfName] = &testVrf } if tt.out != nil { - tt.out.Name = testVpcName + tt.out.Name = testVrfName } - request := &pb.CreateVpcRequest{Vpc: tt.in, VpcId: tt.id, Parent: "todo"} - response, err := client.CreateVpc(ctx, request) + request := &pb.CreateVrfRequest{Vrf: tt.in, VrfId: tt.id} + response, err := client.CreateVrf(ctx, request) if response != nil { // if !reflect.DeepEqual(response, tt.out) { mtt, _ := proto.Marshal(tt.out) @@ -114,7 +118,7 @@ func Test_CreateVpc(t *testing.T) { } } -func Test_DeleteVpc(t *testing.T) { +func Test_DeleteVrf(t *testing.T) { tests := map[string]struct { in string out *emptypb.Empty @@ -123,7 +127,7 @@ func Test_DeleteVpc(t *testing.T) { missing bool }{ // "valid request": { - // testVpcID, + // testVrfID, // &emptypb.Empty{}, // codes.OK, // "", @@ -133,7 +137,7 @@ func Test_DeleteVpc(t *testing.T) { "unknown-id", nil, codes.NotFound, - fmt.Sprintf("unable to find key %v", resourceIDToFullName("vpcs", "unknown-id")), + fmt.Sprintf("unable to find key %v", resourceIDToFullName("vrfs", "unknown-id")), false, }, "unknown key with missing allowed": { @@ -171,13 +175,13 @@ func Test_DeleteVpc(t *testing.T) { log.Fatal(err) } }(conn) - client := pb.NewCloudInfraServiceClient(conn) + client := pb.NewVrfServiceClient(conn) - fname1 := resourceIDToFullName("vpcs", tt.in) - opi.Vpcs[testVpcName] = &testVpc + fname1 := resourceIDToFullName("vrfs", tt.in) + opi.Vrfs[testVrfName] = &testVrf - request := &pb.DeleteVpcRequest{Name: fname1, AllowMissing: tt.missing} - response, err := client.DeleteVpc(ctx, request) + request := &pb.DeleteVrfRequest{Name: fname1, AllowMissing: tt.missing} + response, err := client.DeleteVrf(ctx, request) if er, ok := status.FromError(err); ok { if er.Code() != tt.errCode { @@ -197,14 +201,17 @@ func Test_DeleteVpc(t *testing.T) { } } -func Test_UpdateVpc(t *testing.T) { - spec := &pb.VpcSpec{ - V4RouteTableNameRef: "1000", +func Test_UpdateVrf(t *testing.T) { + spec := &pb.VrfSpec{ + Vni: 1000, + LoopbackIpPrefix: &pc.IPPrefix{ + Len: 24, + }, } tests := map[string]struct { mask *fieldmaskpb.FieldMask - in *pb.Vpc - out *pb.Vpc + in *pb.Vrf + out *pb.Vrf spdk []string errCode codes.Code errMsg string @@ -213,8 +220,8 @@ func Test_UpdateVpc(t *testing.T) { }{ "invalid fieldmask": { &fieldmaskpb.FieldMask{Paths: []string{"*", "author"}}, - &pb.Vpc{ - Name: testVpcName, + &pb.Vrf{ + Name: testVrfName, Spec: spec, }, nil, @@ -226,13 +233,14 @@ func Test_UpdateVpc(t *testing.T) { }, "valid request with unknown key": { nil, - &pb.Vpc{ - Name: resourceIDToFullName("vpcs", "unknown-id"), + &pb.Vrf{ + Name: resourceIDToFullName("vrfs", "unknown-id"), + Spec: spec, }, nil, []string{""}, codes.NotFound, - fmt.Sprintf("unable to find key %v", resourceIDToFullName("vpcs", "unknown-id")), + fmt.Sprintf("unable to find key %v", resourceIDToFullName("vrfs", "unknown-id")), false, true, }, @@ -257,17 +265,17 @@ func Test_UpdateVpc(t *testing.T) { log.Fatal(err) } }(conn) - client := pb.NewCloudInfraServiceClient(conn) + client := pb.NewVrfServiceClient(conn) if tt.exist { - opi.Vpcs[testVpcName] = &testVpc + opi.Vrfs[testVrfName] = &testVrf } if tt.out != nil { - tt.out.Name = testVpcName + tt.out.Name = testVrfName } - request := &pb.UpdateVpcRequest{Vpc: tt.in, UpdateMask: tt.mask} - response, err := client.UpdateVpc(ctx, request) + request := &pb.UpdateVrfRequest{Vrf: tt.in, UpdateMask: tt.mask} + response, err := client.UpdateVrf(ctx, request) if response != nil { // Marshall the request and response, so we can just compare the contained data mtt, _ := proto.Marshal(tt.out.Spec) @@ -296,18 +304,18 @@ func Test_UpdateVpc(t *testing.T) { } } -func Test_GetVpc(t *testing.T) { +func Test_GetVrf(t *testing.T) { tests := map[string]struct { in string - out *pb.Vpc + out *pb.Vrf errCode codes.Code errMsg string }{ // "valid request": { - // testVpcID, - // &pb.Vpc{ - // Name: testVpcName, - // Multipath: testVpc.Multipath, + // testVrfID, + // &pb.Vrf{ + // Name: testVrfName, + // Multipath: testVrf.Multipath, // }, // codes.OK, // "", @@ -345,12 +353,12 @@ func Test_GetVpc(t *testing.T) { log.Fatal(err) } }(conn) - client := pb.NewCloudInfraServiceClient(conn) + client := pb.NewVrfServiceClient(conn) - opi.Vpcs[testVpcID] = &testVpc + opi.Vrfs[testVrfID] = &testVrf - request := &pb.GetVpcRequest{Name: tt.in} - response, err := client.GetVpc(ctx, request) + request := &pb.GetVrfRequest{Name: tt.in} + response, err := client.GetVrf(ctx, request) if response != nil { // if !reflect.DeepEqual(response, tt.out) { mtt, _ := proto.Marshal(tt.out)