From 38d8f83ca848181930c3e5330a1daf95a244784f Mon Sep 17 00:00:00 2001 From: Gemma Hou Date: Thu, 26 Sep 2024 02:42:59 +0000 Subject: [PATCH] mockGCP for compute firewall policy --- config/tests/samples/create/harness.go | 1 + mockgcp/mockcompute/firewallpoliciesv1.go | 249 ++++++++++++++++++ .../globalorganizationoperationsv1.go | 36 +++ mockgcp/mockcompute/operations.go | 16 ++ mockgcp/mockcompute/service.go | 10 + ...d_object_computefirewallpolicy.golden.yaml | 4 +- .../v1beta1/computefirewallpolicy/_http.log | 17 +- 7 files changed, 322 insertions(+), 11 deletions(-) create mode 100644 mockgcp/mockcompute/firewallpoliciesv1.go create mode 100644 mockgcp/mockcompute/globalorganizationoperationsv1.go diff --git a/config/tests/samples/create/harness.go b/config/tests/samples/create/harness.go index dfb441f5cf..5312286030 100644 --- a/config/tests/samples/create/harness.go +++ b/config/tests/samples/create/harness.go @@ -666,6 +666,7 @@ func MaybeSkip(t *testing.T, name string, resources []*unstructured.Unstructured case schema.GroupKind{Group: "compute.cnrm.cloud.google.com", Kind: "ComputeAddress"}: case schema.GroupKind{Group: "compute.cnrm.cloud.google.com", Kind: "ComputeBackendService"}: case schema.GroupKind{Group: "compute.cnrm.cloud.google.com", Kind: "ComputeDisk"}: + case schema.GroupKind{Group: "compute.cnrm.cloud.google.com", Kind: "ComputeFirewallPolicy"}: case schema.GroupKind{Group: "compute.cnrm.cloud.google.com", Kind: "ComputeForwardingRule"}: case schema.GroupKind{Group: "compute.cnrm.cloud.google.com", Kind: "ComputeHealthCheck"}: case schema.GroupKind{Group: "compute.cnrm.cloud.google.com", Kind: "ComputeInstance"}: diff --git a/mockgcp/mockcompute/firewallpoliciesv1.go b/mockgcp/mockcompute/firewallpoliciesv1.go new file mode 100644 index 0000000000..d1652bde39 --- /dev/null +++ b/mockgcp/mockcompute/firewallpoliciesv1.go @@ -0,0 +1,249 @@ +// Copyright 2024 Google LLC +// +// 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 mockcompute + +import ( + "context" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + "google.golang.org/protobuf/proto" + "strconv" + "strings" + + pb "github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/generated/mockgcp/cloud/compute/v1" +) + +type FirewallPoliciesV1 struct { + *MockService + pb.UnimplementedFirewallPoliciesServer +} + +func (s *FirewallPoliciesV1) Get(ctx context.Context, req *pb.GetFirewallPolicyRequest) (*pb.FirewallPolicy, error) { + reqName := "locations/global/firewallPolicies/" + req.GetFirewallPolicy() + name, err := s.parseFirewallPolicyName(reqName) + if err != nil { + return nil, err + } + + fqn := name.String() + + obj := &pb.FirewallPolicy{} + if err := s.storage.Get(ctx, fqn, obj); err != nil { + if status.Code(err) == codes.NotFound { + return nil, status.Errorf(codes.NotFound, "The resource '%s' was not found", fqn) + } + return nil, err + } + + return obj, nil +} + +func (s *FirewallPoliciesV1) Insert(ctx context.Context, req *pb.InsertFirewallPolicyRequest) (*pb.Operation, error) { + policyId := req.GetFirewallPolicyResource().GetName() + id := s.generateID() + if policyId == "" { + policyId = strconv.FormatUint(id, 10) + } + reqName := "locations/global/firewallPolicies/" + policyId + policyName, err := s.parseFirewallPolicyName(reqName) + if err != nil { + return nil, err + } + + fqn := policyName.String() + + obj := proto.Clone(req.GetFirewallPolicyResource()).(*pb.FirewallPolicy) + obj.SelfLink = PtrTo("https://www.googleapis.com/compute/v1/" + policyName.String()) + obj.SelfLinkWithId = PtrTo("https://www.googleapis.com/compute/v1/" + policyName.String() + "/" + policyId) + obj.Parent = PtrTo(req.ParentId) + obj.RuleTupleCount = PtrTo(int32(8)) + obj.Id = PtrTo(id) + obj.Name = PtrTo(policyId) + obj.CreationTimestamp = PtrTo(s.nowString()) + obj.Kind = PtrTo("compute#firewallPolicy") + + if obj.Fingerprint == nil { + obj.Fingerprint = PtrTo(computeFingerprint(obj)) + } + + // Use default rules + if obj.Rules == nil { + obj.Rules = []*pb.FirewallPolicyRule{ + { + Action: PtrTo("goto_next"), + Description: PtrTo("default egress rule ipv6"), + Direction: PtrTo("EGRESS"), + EnableLogging: PtrTo(false), + Kind: PtrTo("compute#firewallPolicyRule"), + Match: &pb.FirewallPolicyRuleMatcher{ + DestIpRanges: []string{"::/0"}, + Layer4Configs: []*pb.FirewallPolicyRuleMatcherLayer4Config{ + { + IpProtocol: PtrTo("all"), + }, + }, + }, + Priority: PtrTo(int32(2147483644)), + RuleTupleCount: PtrTo(int32(2)), + }, + { + Action: PtrTo("goto_next"), + Description: PtrTo("default ingress rule ipv6"), + Direction: PtrTo("INGRESS"), + EnableLogging: PtrTo(false), + Kind: PtrTo("compute#firewallPolicyRule"), + Match: &pb.FirewallPolicyRuleMatcher{ + DestIpRanges: []string{"::/0"}, + Layer4Configs: []*pb.FirewallPolicyRuleMatcherLayer4Config{ + { + IpProtocol: PtrTo("all"), + }, + }, + }, + Priority: PtrTo(int32(2147483645)), + RuleTupleCount: PtrTo(int32(2)), + }, + { + Action: PtrTo("goto_next"), + Description: PtrTo("default egress rule"), + Direction: PtrTo("EGRESS"), + EnableLogging: PtrTo(false), + Kind: PtrTo("compute#firewallPolicyRule"), + Match: &pb.FirewallPolicyRuleMatcher{ + DestIpRanges: []string{"0.0.0.0/0"}, + Layer4Configs: []*pb.FirewallPolicyRuleMatcherLayer4Config{ + { + IpProtocol: PtrTo("all"), + }, + }, + }, + Priority: PtrTo(int32(2147483646)), + RuleTupleCount: PtrTo(int32(2)), + }, + { + Action: PtrTo("goto_next"), + Description: PtrTo("default ingress rule"), + Direction: PtrTo("INGRESS"), + EnableLogging: PtrTo(false), + Kind: PtrTo("compute#firewallPolicyRule"), + Match: &pb.FirewallPolicyRuleMatcher{ + SrcIpRanges: []string{"0.0.0.0/0"}, + Layer4Configs: []*pb.FirewallPolicyRuleMatcherLayer4Config{ + { + IpProtocol: PtrTo("all"), + }, + }, + }, + Priority: PtrTo(int32(2147483647)), + RuleTupleCount: PtrTo(int32(2)), + }, + } + } + + if err := s.storage.Create(ctx, fqn, obj); err != nil { + return nil, err + } + + op := &pb.Operation{ + TargetId: obj.Id, + TargetLink: obj.SelfLink, + OperationType: PtrTo("createFirewallPolicy"), + User: PtrTo("user@example.com"), + } + return s.startGlobalOrganizationLRO(ctx, op, func() (proto.Message, error) { + return obj, nil + }) +} + +func (s *FirewallPoliciesV1) Patch(ctx context.Context, req *pb.PatchFirewallPolicyRequest) (*pb.Operation, error) { + reqName := "locations/global/firewallPolicies/" + req.GetFirewallPolicy() + + name, err := s.parseFirewallPolicyName(reqName) + if err != nil { + return nil, err + } + + fqn := name.String() + obj := &pb.FirewallPolicy{} + if err := s.storage.Get(ctx, fqn, obj); err != nil { + return nil, err + } + + proto.Merge(obj, req.GetFirewallPolicyResource()) + + if err := s.storage.Update(ctx, fqn, obj); err != nil { + return nil, err + } + + op := &pb.Operation{ + TargetId: obj.Id, + TargetLink: obj.SelfLink, + OperationType: PtrTo("updateFirewallPolicy"), + User: PtrTo("user@example.com"), + // patch operation finished super fast + Progress: PtrTo(int32(100)), + Status: PtrTo(pb.Operation_DONE), + } + return s.startGlobalOrganizationLRO(ctx, op, func() (proto.Message, error) { + return obj, nil + }) +} +func (s *FirewallPoliciesV1) Delete(ctx context.Context, req *pb.DeleteFirewallPolicyRequest) (*pb.Operation, error) { + reqName := "locations/global/firewallPolicies/" + req.GetFirewallPolicy() + name, err := s.parseFirewallPolicyName(reqName) + if err != nil { + return nil, err + } + + fqn := name.String() + + deleted := &pb.FirewallPolicy{} + if err := s.storage.Delete(ctx, fqn, deleted); err != nil { + return nil, err + } + + op := &pb.Operation{ + TargetId: deleted.Id, + TargetLink: deleted.SelfLink, + OperationType: PtrTo("deleteFirewallPolicy"), + User: PtrTo("user@example.com"), + } + return s.startGlobalOrganizationLRO(ctx, op, func() (proto.Message, error) { + return deleted, nil + }) +} + +type firewallPolicyName struct { + Name string +} + +func (n *firewallPolicyName) String() string { + return "locations/global/firewallPolicies/" + n.Name +} + +// parseFirewallPolicyName parses a string into a firewallPolicyName. +// The expected form is `locations/global/firewallPolicies/*`. +func (s *MockService) parseFirewallPolicyName(name string) (*firewallPolicyName, error) { + tokens := strings.Split(name, "/") + + if len(tokens) == 4 && tokens[2] == "firewallPolicies" { + name := &firewallPolicyName{ + Name: tokens[3], + } + return name, nil + } else { + return nil, status.Errorf(codes.InvalidArgument, "name %q is not valid", name) + } +} diff --git a/mockgcp/mockcompute/globalorganizationoperationsv1.go b/mockgcp/mockcompute/globalorganizationoperationsv1.go new file mode 100644 index 0000000000..b885c89979 --- /dev/null +++ b/mockgcp/mockcompute/globalorganizationoperationsv1.go @@ -0,0 +1,36 @@ +// Copyright 2024 Google LLC +// +// 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 mockcompute + +import ( + "context" + + pb "github.com/GoogleCloudPlatform/k8s-config-connector/mockgcp/generated/mockgcp/cloud/compute/v1" +) + +type GlobalOrganizationOperationsV1 struct { + *MockService + pb.UnimplementedGlobalOrganizationOperationsServer +} + +func (s *GlobalOrganizationOperationsV1) Get(ctx context.Context, req *pb.GetGlobalOrganizationOperationRequest) (*pb.Operation, error) { + fqn := s.globalOrganizationOperationFQN(req.Operation) + lro, err := s.getOperation(ctx, fqn) + if err != nil { + return nil, err + } + + return lro, nil +} diff --git a/mockgcp/mockcompute/operations.go b/mockgcp/mockcompute/operations.go index c1b16baaf1..292a0809bb 100644 --- a/mockgcp/mockcompute/operations.go +++ b/mockgcp/mockcompute/operations.go @@ -41,6 +41,10 @@ func (s *computeOperations) globalOperationFQN(projectID string, name string) st return "projects/" + projectID + "/global/operations/" + name } +func (s *computeOperations) globalOrganizationOperationFQN(name string) string { + return "locations/global/operations/" + name +} + func (s *computeOperations) regionalOperationFQN(projectID string, region string, name string) string { return "projects/" + projectID + "/regions/" + region + "/operations/" + name } @@ -158,6 +162,18 @@ func (s *computeOperations) startGlobalLRO(ctx context.Context, projectID string return s.startLRO0(ctx, op, fqn, callback) } +func (s *computeOperations) startGlobalOrganizationLRO(ctx context.Context, op *pb.Operation, callback func() (proto.Message, error)) (*pb.Operation, error) { + now := time.Now() + millis := now.UnixMilli() + id := uuid.NewUUID() + + name := fmt.Sprintf("operation-%d-%s", millis, id) + fqn := s.globalOrganizationOperationFQN(name) + + op.Name = PtrTo(name) + return s.startLRO0(ctx, op, fqn, callback) +} + // Gets the latest state of a long-running operation. Clients can use this // method to poll the operation result at intervals as recommended by the API // service. diff --git a/mockgcp/mockcompute/service.go b/mockgcp/mockcompute/service.go index 0dc6a97fc9..1da66aae44 100644 --- a/mockgcp/mockcompute/service.go +++ b/mockgcp/mockcompute/service.go @@ -76,6 +76,7 @@ func (s *MockService) Register(grpcServer *grpc.Server) { pb.RegisterRegionOperationsServer(grpcServer, &RegionalOperationsV1{MockService: s}) pb.RegisterGlobalOperationsServer(grpcServer, &GlobalOperationsV1{MockService: s}) + pb.RegisterGlobalOrganizationOperationsServer(grpcServer, &GlobalOrganizationOperationsV1{MockService: s}) pb.RegisterNodeGroupsServer(grpcServer, &NodeGroupsV1{MockService: s}) pb.RegisterNodeTemplatesServer(grpcServer, &NodeTemplatesV1{MockService: s}) @@ -89,6 +90,8 @@ func (s *MockService) Register(grpcServer *grpc.Server) { pb.RegisterServiceAttachmentsServer(grpcServer, &RegionalServiceAttachmentV1{MockService: s}) + pb.RegisterFirewallPoliciesServer(grpcServer, &FirewallPoliciesV1{MockService: s}) + pb.RegisterGlobalForwardingRulesServer(grpcServer, &GlobalForwardingRulesV1{MockService: s}) pb.RegisterForwardingRulesServer(grpcServer, &RegionalForwardingRulesV1{MockService: s}) @@ -170,6 +173,10 @@ func (s *MockService) NewHTTPMux(ctx context.Context, conn *grpc.ClientConn) (ht return nil, err } + if err := pb.RegisterFirewallPoliciesHandler(ctx, mux.ServeMux, conn); err != nil { + return nil, err + } + if err := pb.RegisterForwardingRulesHandler(ctx, mux.ServeMux, conn); err != nil { return nil, err } @@ -183,6 +190,9 @@ func (s *MockService) NewHTTPMux(ctx context.Context, conn *grpc.ClientConn) (ht if err := pb.RegisterGlobalOperationsHandler(ctx, mux.ServeMux, conn); err != nil { return nil, err } + if err := pb.RegisterGlobalOrganizationOperationsHandler(ctx, mux.ServeMux, conn); err != nil { + return nil, err + } if err := pb.RegisterAddressesHandler(ctx, mux.ServeMux, conn); err != nil { return nil, err diff --git a/pkg/test/resourcefixture/testdata/basic/compute/v1beta1/computefirewallpolicy/_generated_object_computefirewallpolicy.golden.yaml b/pkg/test/resourcefixture/testdata/basic/compute/v1beta1/computefirewallpolicy/_generated_object_computefirewallpolicy.golden.yaml index 878982a301..a0f32121fb 100644 --- a/pkg/test/resourcefixture/testdata/basic/compute/v1beta1/computefirewallpolicy/_generated_object_computefirewallpolicy.golden.yaml +++ b/pkg/test/resourcefixture/testdata/basic/compute/v1beta1/computefirewallpolicy/_generated_object_computefirewallpolicy.golden.yaml @@ -30,5 +30,5 @@ status: id: 1111111111111111 observedGeneration: 3 ruleTupleCount: 8 - selfLink: https://www.googleapis.com/compute/beta/locations/global/firewallPolicies/${firewallPolicyID} - selfLinkWithId: https://www.googleapis.com/compute/beta/locations/global/firewallPolicies/${firewallPolicyID}/${firewallPolicyID} + selfLink: https://www.googleapis.com/compute/v1/locations/global/firewallPolicies/${firewallPolicyID} + selfLinkWithId: https://www.googleapis.com/compute/v1/locations/global/firewallPolicies/${firewallPolicyID}/${firewallPolicyID} diff --git a/pkg/test/resourcefixture/testdata/basic/compute/v1beta1/computefirewallpolicy/_http.log b/pkg/test/resourcefixture/testdata/basic/compute/v1beta1/computefirewallpolicy/_http.log index e2a8c5212a..99ea0b3f9c 100644 --- a/pkg/test/resourcefixture/testdata/basic/compute/v1beta1/computefirewallpolicy/_http.log +++ b/pkg/test/resourcefixture/testdata/basic/compute/v1beta1/computefirewallpolicy/_http.log @@ -29,6 +29,8 @@ X-Xss-Protection: 0 "selfLink": "https://www.googleapis.com/compute/v1/locations/global/operations/${operationID}", "startTime": "2024-04-01T12:34:56.123456Z", "status": "RUNNING", + "targetId": "${firewallPolicyId}", + "targetLink": "https://www.googleapis.com/compute/v1/locations/global/firewallPolicies/${firewallPolicyId}", "user": "user@example.com" } @@ -85,7 +87,6 @@ X-Xss-Protection: 0 { "creationTimestamp": "2024-04-01T12:34:56.123456Z", "description": "A basic organization firewall policy", - "displayName": "firewallpolicy-${uniqueId}", "fingerprint": "abcdef0123A=", "id": "000000000000000000000", "kind": "compute#firewallPolicy", @@ -119,13 +120,13 @@ X-Xss-Protection: 0 "enableLogging": false, "kind": "compute#firewallPolicyRule", "match": { + "destIpRanges": [ + "::/0" + ], "layer4Configs": [ { "ipProtocol": "all" } - ], - "srcIpRanges": [ - "::/0" ] }, "priority": 2147483645, @@ -198,7 +199,6 @@ X-Frame-Options: SAMEORIGIN X-Xss-Protection: 0 { - "endTime": "2024-04-01T12:34:56.123456Z", "id": "000000000000000000000", "insertTime": "2024-04-01T12:34:56.123456Z", "kind": "compute#operation", @@ -266,7 +266,6 @@ X-Xss-Protection: 0 { "creationTimestamp": "2024-04-01T12:34:56.123456Z", "description": "An updated basic organization firewall policy", - "displayName": "firewallpolicy-${uniqueId}", "fingerprint": "abcdef0123A=", "id": "000000000000000000000", "kind": "compute#firewallPolicy", @@ -300,13 +299,13 @@ X-Xss-Protection: 0 "enableLogging": false, "kind": "compute#firewallPolicyRule", "match": { + "destIpRanges": [ + "::/0" + ], "layer4Configs": [ { "ipProtocol": "all" } - ], - "srcIpRanges": [ - "::/0" ] }, "priority": 2147483645,