diff --git a/lib/api/bgppeer.go b/lib/api/bgppeer.go index becb87e88..94a7db7e3 100644 --- a/lib/api/bgppeer.go +++ b/lib/api/bgppeer.go @@ -37,6 +37,10 @@ func (t BGPPeer) GetResourceMetadata() unversioned.ResourceMetadata { return t.Metadata } +func (t BGPPeer) GetResourceID() string { + return t.Metadata.PeerIP.IP.String() + "_" + string(t.Metadata.Scope) + "_" + t.Metadata.Node +} + // BGPPeerMetadata contains the metadata for a BGPPeer resource. type BGPPeerMetadata struct { unversioned.ObjectMetadata diff --git a/lib/api/hostendpoint.go b/lib/api/hostendpoint.go index b7e4975ff..0f12a2d07 100644 --- a/lib/api/hostendpoint.go +++ b/lib/api/hostendpoint.go @@ -31,6 +31,10 @@ func (t HostEndpoint) GetResourceMetadata() unversioned.ResourceMetadata { return t.Metadata } +func (t HostEndpoint) GetResourceID() string { + return t.Metadata.Node + "_" + t.Metadata.Name +} + // HostEndpointMetadata contains the Metadata for a HostEndpoint resource. type HostEndpointMetadata struct { unversioned.ObjectMetadata diff --git a/lib/api/ippool.go b/lib/api/ippool.go index b9c138e1e..044aa03f7 100644 --- a/lib/api/ippool.go +++ b/lib/api/ippool.go @@ -37,6 +37,10 @@ func (t IPPool) GetResourceMetadata() unversioned.ResourceMetadata { return t.Metadata } +func (t IPPool) GetResourceID() string { + return t.Metadata.CIDR.String() +} + // IPPoolMetadata contains the metadata for an IP pool resource. type IPPoolMetadata struct { unversioned.ObjectMetadata diff --git a/lib/api/node.go b/lib/api/node.go index 05efeacd0..3226f872e 100644 --- a/lib/api/node.go +++ b/lib/api/node.go @@ -44,6 +44,10 @@ func (t Node) GetResourceMetadata() unversioned.ResourceMetadata { return t.Metadata } +func (t Node) GetResourceID() string { + return t.Metadata.Name +} + // NodeMetadata contains the metadata for a Calico Node resource. type NodeMetadata struct { unversioned.ObjectMetadata diff --git a/lib/api/policy.go b/lib/api/policy.go index 5a8dae432..820248a28 100644 --- a/lib/api/policy.go +++ b/lib/api/policy.go @@ -46,6 +46,10 @@ func (t Policy) GetResourceMetadata() unversioned.ResourceMetadata { return t.Metadata } +func (t Policy) GetResourceID() string { + return t.Metadata.Name +} + // PolicyMetadata contains the metadata for a selector-based security Policy resource. type PolicyMetadata struct { unversioned.ObjectMetadata diff --git a/lib/api/profile.go b/lib/api/profile.go index 94a25cdd6..80de7b66f 100644 --- a/lib/api/profile.go +++ b/lib/api/profile.go @@ -33,6 +33,10 @@ func (t Profile) GetResourceMetadata() unversioned.ResourceMetadata { return t.Metadata } +func (t Profile) GetResourceID() string { + return t.Metadata.Name +} + // ProfileMetadata contains the metadata for a security Profile resource. type ProfileMetadata struct { unversioned.ObjectMetadata diff --git a/lib/api/unversioned/types.go b/lib/api/unversioned/types.go index 655a8da19..c56e5bcc6 100644 --- a/lib/api/unversioned/types.go +++ b/lib/api/unversioned/types.go @@ -23,6 +23,7 @@ type Resource interface { type ResourceObject interface { Resource GetResourceMetadata() ResourceMetadata + GetResourceID() string } // Define available versions. diff --git a/lib/api/workloadendpoint.go b/lib/api/workloadendpoint.go index 347cc134b..7acce2406 100644 --- a/lib/api/workloadendpoint.go +++ b/lib/api/workloadendpoint.go @@ -31,6 +31,10 @@ func (t WorkloadEndpoint) GetResourceMetadata() unversioned.ResourceMetadata { return t.Metadata } +func (t WorkloadEndpoint) GetResourceID() string { + return t.Metadata.Node + "_" + t.Metadata.Orchestrator + "_" + t.Metadata.Workload + "_" + t.Metadata.Name + "_" + t.Metadata.ActiveInstanceID +} + // WorkloadEndpointMetadata contains the Metadata for a WorkloadEndpoint resource. type WorkloadEndpointMetadata struct { unversioned.ObjectMetadata diff --git a/lib/validator/common.go b/lib/validator/common.go index 2c026ca12..106be16b5 100644 --- a/lib/validator/common.go +++ b/lib/validator/common.go @@ -17,6 +17,8 @@ package validator import ( "fmt" "log" + "reflect" + "strings" "github.com/projectcalico/libcalico-go/lib/api" "github.com/projectcalico/libcalico-go/lib/api/unversioned" @@ -80,3 +82,39 @@ func ValidateMetadataIDsAssigned(rm unversioned.ResourceMetadata) error { return nil } + +// ValidateIDFieldsSame is used to validate that the Resource Objects +// supplied in one slice reference the same objects in the other slice +// by comparing the ID fields. +func ValidateIDFieldsSame(original, changed []unversioned.ResourceObject) error { + toCompare := make(map[string]int) + for i, obj := range original { + toCompare[obj.GetResourceID()] = i + } + + for _, obj := range changed { + if _, ok := toCompare[obj.GetResourceID()]; !ok { + return fmt.Errorf("Cannot change metadata fields: %v, resource will not be found", GetIDFields(obj.GetResourceMetadata())) + } + } + + return nil +} + +// GetIDFields is a helper function that returns the field labels +// on the metadata objects that are ID fields excluding Labels, +// Annotations, and Tags +func GetIDFields(rm unversioned.ResourceMetadata) []string { + labels := make([]string, 0, 4) + val := reflect.ValueOf(rm) + for i := 0; i < val.Type().NumField(); i++ { + label, ok := val.Type().Field(i).Tag.Lookup("json") + // strip omitempty and other modifiers + label = strings.Split(label, ",")[0] + if ok && label != "annotations" && label != "labels" && label != "tags" { + labels = append(labels, label) + } + } + + return labels +} diff --git a/lib/validator/common_test.go b/lib/validator/common_test.go index 25817b60c..446880475 100644 --- a/lib/validator/common_test.go +++ b/lib/validator/common_test.go @@ -20,6 +20,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/projectcalico/libcalico-go/lib/api" + "github.com/projectcalico/libcalico-go/lib/api/unversioned" "github.com/projectcalico/libcalico-go/lib/net" "github.com/projectcalico/libcalico-go/lib/scope" ) @@ -181,3 +182,117 @@ var _ = Describe("Test ValidateMetadataIDsAssigned function", func() { }) }) }) + +var _ = Describe("Test getIDFields helper function", func() { + Context("with BGPPeer Metadata", func() { + md := api.BGPPeerMetadata{} + idFields := validator.GetIDFields(md) + It("should return 3 fields", func() { + Expect(len(idFields)).To(Equal(3)) + }) + It("should contain scope, node, and peerIP", func() { + for _, field := range idFields { + check := field == "scope" || field == "node" || field == "peerIP" + Expect(check).To(Equal(true)) + } + }) + }) + + Context("with Host Endpoint Metadata", func() { + md := api.HostEndpointMetadata{} + idFields := validator.GetIDFields(md) + It("should return 2 fields", func() { + Expect(len(idFields)).To(Equal(2)) + }) + It("should contain node and name", func() { + for _, field := range idFields { + check := field == "name" || field == "node" + Expect(check).To(Equal(true)) + } + }) + }) + + Context("with IP Pool Metadata", func() { + md := api.IPPoolMetadata{} + idFields := validator.GetIDFields(md) + It("should return 1 field", func() { + Expect(len(idFields)).To(Equal(1)) + }) + It("should contain cidr", func() { + Expect(idFields[0]).To(Equal("cidr")) + }) + }) + + Context("with Node Metadata", func() { + md := api.NodeMetadata{} + idFields := validator.GetIDFields(md) + It("should return 1 field", func() { + Expect(len(idFields)).To(Equal(1)) + }) + It("should contain name", func() { + Expect(idFields[0]).To(Equal("name")) + }) + }) + + Context("with Policy Metadata", func() { + md := api.PolicyMetadata{} + idFields := validator.GetIDFields(md) + It("should return 1 field", func() { + Expect(len(idFields)).To(Equal(1)) + }) + It("should contain name", func() { + Expect(idFields[0]).To(Equal("name")) + }) + }) + + Context("with Profile Metadata", func() { + md := api.ProfileMetadata{} + idFields := validator.GetIDFields(md) + It("should return 1 field", func() { + Expect(len(idFields)).To(Equal(1)) + }) + It("should contain name", func() { + Expect(idFields[0]).To(Equal("name")) + }) + }) + + Context("with Workload Endpoint Metadata", func() { + md := api.WorkloadEndpointMetadata{} + idFields := validator.GetIDFields(md) + It("should return 5 fields", func() { + Expect(len(idFields)).To(Equal(5)) + }) + It("should contain name, workload, orchestrator, node, activeInstanceID", func() { + for _, field := range idFields { + check := field == "name" || field == "workload" || field == "orchestrator" || field == "node" || field == "activeInstanceID" + Expect(check).To(Equal(true)) + } + }) + }) +}) + +var _ = Describe("Test ValidateIDFieldsSame", func() { + Context("with Profile Metadata", func() { + var profile1 *api.Profile + var profile2 *api.Profile + BeforeEach(func() { + profile1 = api.NewProfile() + profile1.Metadata.Name = "testProfile1" + profile2 = api.NewProfile() + profile2.Metadata.Name = "testProfile1" + }) + It("should not error out if the name is not changed", func() { + before := []unversioned.ResourceObject{profile1} + after := []unversioned.ResourceObject{profile2} + err := validator.ValidateIDFieldsSame(before, after) + Expect(err).NotTo(HaveOccurred()) + }) + It("should return an error if the name is changed", func() { + profile2.Metadata.Name = "testName2" + before := []unversioned.ResourceObject{profile1} + after := []unversioned.ResourceObject{profile2} + err := validator.ValidateIDFieldsSame(before, after) + Expect(err).To(HaveOccurred()) + }) + }) +})