From 73bcd41b92a0dbf173099da9e8c8b604a45d7899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jaime=20Caama=C3=B1o=20Ruiz?= Date: Tue, 9 Jan 2024 11:08:24 +0000 Subject: [PATCH] Add referential integrity integration tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While these tests could have been purely server tests, setting them up as integration tests against both the test server and the real OVSDB server allows us to contrast the implemented behavior. Also provides coverage for how the references are updated in the database as well as for how referential integrity updates are sent back to the client. Signed-off-by: Jaime CaamaƱo Ruiz --- server/server_integration_test.go | 346 +++++++++++++++++++++++++++ test/ovs/ovs_integration_test.go | 380 ++++++++++++++++++++++++++++++ test/test_data.go | 56 ++++- 3 files changed, 780 insertions(+), 2 deletions(-) diff --git a/server/server_integration_test.go b/server/server_integration_test.go index e9f3d8dd..e4273df9 100644 --- a/server/server_integration_test.go +++ b/server/server_integration_test.go @@ -10,11 +10,13 @@ import ( "testing" "time" + "github.com/google/uuid" "github.com/ovn-org/libovsdb/cache" "github.com/ovn-org/libovsdb/client" "github.com/ovn-org/libovsdb/database/inmemory" "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-org/libovsdb/test" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -516,3 +518,347 @@ func TestMultipleOpsSameRow(t *testing.T) { require.Equal(t, map[string]string{"key1": "value1", "keyA": "valueA"}, br.ExternalIds) require.Nil(t, br.DatapathID) } + +func TestReferentialIntegrity(t *testing.T) { + // UUIDs to use throughout the tests + ovsUUID := uuid.New().String() + bridgeUUID := uuid.New().String() + port1UUID := uuid.New().String() + port2UUID := uuid.New().String() + mirrorUUID := uuid.New().String() + + // the test adds an additional op to initialOps to set a reference to + // the bridge in OVS table + // the test deletes expectModels at the end + tests := []struct { + name string + initialOps []ovsdb.Operation + testOps func(client.Client) ([]ovsdb.Operation, error) + expectModels []model.Model + dontExpectModels []model.Model + expectErr bool + }{ + { + name: "strong reference is garbage collected", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + "mirrors": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: mirrorUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port1UUID, + Row: ovsdb.Row{ + "name": port1UUID, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Mirror", + UUID: mirrorUUID, + Row: ovsdb.Row{ + "name": mirrorUUID, + "select_src_port": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // remove the mirror reference + b := &test.BridgeType{UUID: bridgeUUID} + return c.Where(b).Update(b, &b.Mirrors) + }, + expectModels: []model.Model{ + &test.BridgeType{UUID: bridgeUUID, Name: bridgeUUID, Ports: []string{port1UUID}}, + &test.PortType{UUID: port1UUID, Name: port1UUID}, + }, + dontExpectModels: []model.Model{ + // mirror should have been garbage collected + &test.MirrorType{UUID: mirrorUUID}, + }, + }, + { + name: "adding non-root row that is not strongly reference is a noop", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // add a mirror + m := &test.MirrorType{UUID: mirrorUUID, Name: mirrorUUID} + return c.Create(m) + }, + expectModels: []model.Model{ + &test.BridgeType{UUID: bridgeUUID, Name: bridgeUUID}, + }, + dontExpectModels: []model.Model{ + // mirror should have not been added as is not referenced from anywhere + &test.MirrorType{UUID: mirrorUUID}, + }, + }, + { + name: "adding non-existent strong reference fails", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // add a mirror + b := &test.BridgeType{UUID: bridgeUUID, Mirrors: []string{mirrorUUID}} + return c.Where(b).Update(b, &b.Mirrors) + }, + expectModels: []model.Model{ + &test.BridgeType{UUID: bridgeUUID, Name: bridgeUUID}, + }, + expectErr: true, + }, + { + name: "weak reference is garbage collected", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}, ovsdb.UUID{GoUUID: port2UUID}}}, + "mirrors": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: mirrorUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port1UUID, + Row: ovsdb.Row{ + "name": port1UUID, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port2UUID, + Row: ovsdb.Row{ + "name": port2UUID, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Mirror", + UUID: mirrorUUID, + Row: ovsdb.Row{ + "name": mirrorUUID, + "select_src_port": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}, ovsdb.UUID{GoUUID: port2UUID}}}, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // remove port1 + p := &test.PortType{UUID: port1UUID} + ops, err := c.Where(p).Delete() + if err != nil { + return nil, err + } + b := &test.BridgeType{UUID: bridgeUUID, Ports: []string{port2UUID}} + op, err := c.Where(b).Update(b, &b.Ports) + if err != nil { + return nil, err + } + return append(ops, op...), nil + }, + expectModels: []model.Model{ + &test.BridgeType{UUID: bridgeUUID, Name: bridgeUUID, Ports: []string{port2UUID}, Mirrors: []string{mirrorUUID}}, + &test.PortType{UUID: port2UUID, Name: port2UUID}, + // mirror reference to port1 should have been garbage collected + &test.MirrorType{UUID: mirrorUUID, Name: mirrorUUID, SelectSrcPort: []string{port2UUID}}, + }, + dontExpectModels: []model.Model{ + &test.PortType{UUID: port1UUID}, + }, + }, + { + name: "adding a weak reference to a non-existent row is a noop", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + "mirrors": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: mirrorUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port1UUID, + Row: ovsdb.Row{ + "name": port1UUID, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Mirror", + UUID: mirrorUUID, + Row: ovsdb.Row{ + "name": mirrorUUID, + "select_src_port": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // add reference to non-existent port2 + m := &test.MirrorType{UUID: mirrorUUID, SelectSrcPort: []string{port1UUID, port2UUID}} + return c.Where(m).Update(m, &m.SelectSrcPort) + }, + expectModels: []model.Model{ + &test.BridgeType{UUID: bridgeUUID, Name: bridgeUUID, Ports: []string{port1UUID}, Mirrors: []string{mirrorUUID}}, + &test.PortType{UUID: port1UUID, Name: port1UUID}, + // mirror reference to port2 should have been garbage collected resulting in noop + &test.MirrorType{UUID: mirrorUUID, Name: mirrorUUID, SelectSrcPort: []string{port1UUID}}, + }, + }, + { + name: "garbage collecting a weak reference on a column lowering it below the min length fails", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + "mirrors": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: mirrorUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port1UUID, + Row: ovsdb.Row{ + "name": port1UUID, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Mirror", + UUID: mirrorUUID, + Row: ovsdb.Row{ + "name": mirrorUUID, + "select_src_port": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // remove port 1 + return c.Where(&test.PortType{UUID: port1UUID}).Delete() + }, + expectModels: []model.Model{ + &test.BridgeType{UUID: bridgeUUID, Name: bridgeUUID, Ports: []string{port1UUID}, Mirrors: []string{mirrorUUID}}, + &test.PortType{UUID: port1UUID, Name: port1UUID}, + &test.MirrorType{UUID: mirrorUUID, Name: mirrorUUID, SelectSrcPort: []string{port1UUID}}, + }, + expectErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c, close := buildTestServerAndClient(t) + defer close() + _, err := c.MonitorAll(context.Background()) + require.NoError(t, err) + + // add the bridge reference to the initial ops + ops := append(tt.initialOps, ovsdb.Operation{ + Op: ovsdb.OperationInsert, + Table: "Open_vSwitch", + UUID: ovsUUID, + Row: ovsdb.Row{ + "bridges": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: bridgeUUID}}}, + }, + }) + + results, err := c.Transact(context.Background(), ops...) + require.NoError(t, err) + require.Len(t, results, len(ops)) + + errors, err := ovsdb.CheckOperationResults(results, ops) + require.Nil(t, errors) + require.NoError(t, err) + + ops, err = tt.testOps(c) + require.NoError(t, err) + + results, err = c.Transact(context.Background(), ops...) + require.NoError(t, err) + + errors, err = ovsdb.CheckOperationResults(results, ops) + require.Nil(t, errors) + if tt.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + for _, m := range tt.expectModels { + actual := model.Clone(m) + err := c.Get(context.Background(), actual) + require.NoError(t, err, "when expecting model %v", m) + require.Equal(t, m, actual) + } + + for _, m := range tt.dontExpectModels { + err := c.Get(context.Background(), m) + require.ErrorIs(t, err, client.ErrNotFound, "when not expecting model %v", m) + } + + ops = []ovsdb.Operation{} + for _, m := range tt.expectModels { + op, err := c.Where(m).Delete() + require.NoError(t, err) + require.Len(t, op, 1) + ops = append(ops, op...) + } + + // remove the bridge reference + ops = append(ops, ovsdb.Operation{ + Op: ovsdb.OperationDelete, + Table: "Open_vSwitch", + Where: []ovsdb.Condition{ + { + Column: "_uuid", + Function: ovsdb.ConditionEqual, + Value: ovsdb.UUID{GoUUID: ovsUUID}, + }, + }, + }) + + results, err = c.Transact(context.Background(), ops...) + require.NoError(t, err) + require.Len(t, results, len(ops)) + + errors, err = ovsdb.CheckOperationResults(results, ops) + require.Nil(t, errors) + require.NoError(t, err) + }) + } +} diff --git a/test/ovs/ovs_integration_test.go b/test/ovs/ovs_integration_test.go index daafc926..88da6419 100644 --- a/test/ovs/ovs_integration_test.go +++ b/test/ovs/ovs_integration_test.go @@ -10,6 +10,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" + "github.com/google/uuid" "github.com/ory/dockertest/v3" "github.com/ory/dockertest/v3/docker" "github.com/ovn-org/libovsdb/cache" @@ -164,6 +165,7 @@ type bridgeType struct { BridgeFailMode *BridgeFailMode `ovsdb:"fail_mode"` IPFIX *string `ovsdb:"ipfix"` DatapathID *string `ovsdb:"datapath_id"` + Mirrors []string `ovsdb:"mirrors"` } // ovsType is the ORM model of the OVS table @@ -200,11 +202,31 @@ type queueType struct { DSCP *int `ovsdb:"dscp"` } +type portType struct { + UUID string `ovsdb:"_uuid"` + Name string `ovsdb:"name"` + Interfaces []string `ovsdb:"interfaces"` +} + +type interfaceType struct { + UUID string `ovsdb:"_uuid"` + Name string `ovsdb:"name"` +} + +type mirrorType struct { + UUID string `ovsdb:"_uuid"` + Name string `ovsdb:"name"` + SelectSrcPort []string `ovsdb:"select_src_port"` +} + var defDB, _ = model.NewClientDBModel("Open_vSwitch", map[string]model.Model{ "Open_vSwitch": &ovsType{}, "Bridge": &bridgeType{}, "IPFIX": &ipfixType{}, "Queue": &queueType{}, + "Port": &portType{}, + "Mirror": &mirrorType{}, + "Interface": &interfaceType{}, }) func (suite *OVSIntegrationSuite) TestConnectReconnect() { @@ -1243,3 +1265,361 @@ func (suite *OVSIntegrationSuite) TestMultipleOpsSameRow() { require.Equal(suite.T(), map[string]string{"key1": "value1", "keyA": "valueA"}, br.ExternalIds) require.Nil(suite.T(), br.DatapathID) } + +func (suite *OVSIntegrationSuite) TestReferentialIntegrity() { + t := suite.Suite.T() + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // fetch the OVS UUID + var ovs []*ovsType + err := suite.clientWithoutInactvityCheck.WhereCache(func(*ovsType) bool { return true }).List(ctx, &ovs) + require.NoError(t, err) + require.Len(t, ovs, 1) + + // UUIDs to use throughout the tests + ovsUUID := ovs[0].UUID + bridgeUUID := uuid.New().String() + port1UUID := uuid.New().String() + port2UUID := uuid.New().String() + interfaceUUID := uuid.New().String() + mirrorUUID := uuid.New().String() + + // monitor additional table specific to this test + _, err = suite.clientWithoutInactvityCheck.Monitor(ctx, + suite.clientWithoutInactvityCheck.NewMonitor( + client.WithTable(&portType{}), + client.WithTable(&interfaceType{}), + client.WithTable(&mirrorType{}), + ), + ) + require.NoError(t, err) + + // the test adds an additional op to initialOps to set a reference to + // the bridge in OVS table + // the test deletes expectModels at the end + tests := []struct { + name string + initialOps []ovsdb.Operation + testOps func(client.Client) ([]ovsdb.Operation, error) + expectModels []model.Model + dontExpectModels []model.Model + expectErr bool + }{ + { + name: "strong reference is garbage collected", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + "mirrors": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: mirrorUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port1UUID, + Row: ovsdb.Row{ + "name": port1UUID, + "interfaces": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: interfaceUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Interface", + UUID: interfaceUUID, + Row: ovsdb.Row{ + "name": interfaceUUID, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Mirror", + UUID: mirrorUUID, + Row: ovsdb.Row{ + "name": mirrorUUID, + "select_src_port": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // remove the mirror reference + b := &bridgeType{UUID: bridgeUUID} + return c.Where(b).Update(b, &b.Mirrors) + }, + expectModels: []model.Model{ + &bridgeType{UUID: bridgeUUID, Name: bridgeUUID, Ports: []string{port1UUID}}, + &portType{UUID: port1UUID, Name: port1UUID, Interfaces: []string{interfaceUUID}}, + &interfaceType{UUID: interfaceUUID, Name: interfaceUUID}, + }, + dontExpectModels: []model.Model{ + // mirror should have been garbage collected + &mirrorType{UUID: mirrorUUID}, + }, + }, + { + name: "adding non-root row that is not strongly reference is a noop", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // add a mirror + m := &mirrorType{UUID: mirrorUUID, Name: mirrorUUID} + return c.Create(m) + }, + expectModels: []model.Model{ + &bridgeType{UUID: bridgeUUID, Name: bridgeUUID}, + }, + dontExpectModels: []model.Model{ + // mirror should have not been added as is not referenced from anywhere + &mirrorType{UUID: mirrorUUID}, + }, + }, + { + name: "adding non-existent strong reference fails", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // add a mirror + b := &bridgeType{UUID: bridgeUUID, Mirrors: []string{mirrorUUID}} + return c.Where(b).Update(b, &b.Mirrors) + }, + expectModels: []model.Model{ + &bridgeType{UUID: bridgeUUID, Name: bridgeUUID}, + }, + expectErr: true, + }, + { + name: "weak reference is garbage collected", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}, ovsdb.UUID{GoUUID: port2UUID}}}, + "mirrors": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: mirrorUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port1UUID, + Row: ovsdb.Row{ + "name": port1UUID, + "interfaces": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: interfaceUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port2UUID, + Row: ovsdb.Row{ + "name": port2UUID, + "interfaces": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: interfaceUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Interface", + UUID: interfaceUUID, + Row: ovsdb.Row{ + "name": interfaceUUID, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Mirror", + UUID: mirrorUUID, + Row: ovsdb.Row{ + "name": mirrorUUID, + "select_src_port": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}, ovsdb.UUID{GoUUID: port2UUID}}}, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // remove port1 + b := &bridgeType{UUID: bridgeUUID, Ports: []string{port2UUID}} + return c.Where(b).Update(b, &b.Ports) + }, + expectModels: []model.Model{ + &bridgeType{UUID: bridgeUUID, Name: bridgeUUID, Ports: []string{port2UUID}, Mirrors: []string{mirrorUUID}}, + &portType{UUID: port2UUID, Name: port2UUID, Interfaces: []string{interfaceUUID}}, + // mirror reference to port1 should have been garbage collected + &mirrorType{UUID: mirrorUUID, Name: mirrorUUID, SelectSrcPort: []string{port2UUID}}, + }, + dontExpectModels: []model.Model{ + &portType{UUID: port1UUID}, + }, + }, + { + name: "adding a weak reference to a non-existent row is a noop", + initialOps: []ovsdb.Operation{ + { + Op: ovsdb.OperationInsert, + Table: "Bridge", + UUID: bridgeUUID, + Row: ovsdb.Row{ + "name": bridgeUUID, + "ports": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + "mirrors": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: mirrorUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Port", + UUID: port1UUID, + Row: ovsdb.Row{ + "name": port1UUID, + "interfaces": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: interfaceUUID}}}, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Interface", + UUID: interfaceUUID, + Row: ovsdb.Row{ + "name": interfaceUUID, + }, + }, + { + Op: ovsdb.OperationInsert, + Table: "Mirror", + UUID: mirrorUUID, + Row: ovsdb.Row{ + "name": mirrorUUID, + "select_src_port": ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: port1UUID}}}, + }, + }, + }, + testOps: func(c client.Client) ([]ovsdb.Operation, error) { + // add reference to non-existent port2 + m := &mirrorType{UUID: mirrorUUID, SelectSrcPort: []string{port1UUID, port2UUID}} + return c.Where(m).Update(m, &m.SelectSrcPort) + }, + expectModels: []model.Model{ + &bridgeType{UUID: bridgeUUID, Name: bridgeUUID, Ports: []string{port1UUID}, Mirrors: []string{mirrorUUID}}, + &portType{UUID: port1UUID, Name: port1UUID, Interfaces: []string{interfaceUUID}}, + &interfaceType{UUID: interfaceUUID, Name: interfaceUUID}, + // mirror reference to port2 should have been garbage collected resulting in noop + &mirrorType{UUID: mirrorUUID, Name: mirrorUUID, SelectSrcPort: []string{port1UUID}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := suite.clientWithoutInactvityCheck + + // add the bridge reference to the initial ops + ops := append(tt.initialOps, ovsdb.Operation{ + Op: ovsdb.OperationMutate, + Table: "Open_vSwitch", + Mutations: []ovsdb.Mutation{ + { + Mutator: ovsdb.MutateOperationInsert, + Column: "bridges", + Value: ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: bridgeUUID}}}, + }, + }, + Where: []ovsdb.Condition{ + { + Column: "_uuid", + Function: ovsdb.ConditionEqual, + Value: ovsdb.UUID{GoUUID: ovsUUID}, + }, + }, + }) + + results, err := c.Transact(ctx, ops...) + require.NoError(t, err) + require.Len(t, results, len(ops)) + + errors, err := ovsdb.CheckOperationResults(results, ops) + require.Nil(t, errors) + require.NoError(t, err) + + ops, err = tt.testOps(c) + require.NoError(t, err) + + results, err = c.Transact(ctx, ops...) + require.NoError(t, err) + + errors, err = ovsdb.CheckOperationResults(results, ops) + require.Nil(t, errors) + if tt.expectErr { + require.Error(t, err) + } else { + require.NoError(t, err) + } + + for _, m := range tt.expectModels { + actual := model.Clone(m) + err := c.Get(ctx, actual) + require.NoError(t, err, "when expecting model %v", m) + require.Equal(t, m, actual) + } + + for _, m := range tt.dontExpectModels { + err := c.Get(ctx, m) + require.ErrorIs(t, err, client.ErrNotFound, "when expecting model %v", m) + } + + ops = []ovsdb.Operation{} + for _, m := range tt.expectModels { + op, err := c.Where(m).Delete() + require.NoError(t, err) + require.Len(t, op, 1) + ops = append(ops, op...) + } + + // remove the bridge reference + ops = append(ops, ovsdb.Operation{ + Op: ovsdb.OperationMutate, + Table: "Open_vSwitch", + Mutations: []ovsdb.Mutation{ + { + Mutator: ovsdb.MutateOperationDelete, + Column: "bridges", + Value: ovsdb.OvsSet{GoSet: []interface{}{ovsdb.UUID{GoUUID: bridgeUUID}}}, + }, + }, + Where: []ovsdb.Condition{ + { + Column: "_uuid", + Function: ovsdb.ConditionEqual, + Value: ovsdb.UUID{GoUUID: ovsUUID}, + }, + }, + }) + + results, err = c.Transact(context.Background(), ops...) + require.NoError(t, err) + require.Len(t, results, len(ops)) + + errors, err = ovsdb.CheckOperationResults(results, ops) + require.Nil(t, errors) + require.NoError(t, err) + }) + } +} diff --git a/test/test_data.go b/test/test_data.go index 562e6d92..e4435108 100644 --- a/test/test_data.go +++ b/test/test_data.go @@ -66,6 +66,16 @@ const schema = ` "max": "unlimited" } }, + "mirrors": { + "type": { + "key": { + "type": "uuid", + "refTable": "Mirror" + }, + "min": 0, + "max": "unlimited" + } + }, "status": { "type": { "key": "string", @@ -145,6 +155,34 @@ const schema = ` } }, "indexes": [["target"]] + }, + "Mirror": { + "columns": { + "name": { + "type": "string" + }, + "select_src_port": { + "type": { + "key": { + "type": "uuid", + "refTable": "Port", + "refType": "weak" + }, + "min": 1, + "max": "unlimited" + } + } + } + }, + "Port": { + "columns": { + "name": { + "type": "string", + "mutable": false + } + }, + "isRoot": true, + "indexes": [["name"]] } } } @@ -160,6 +198,7 @@ type BridgeType struct { ExternalIds map[string]string `ovsdb:"external_ids"` Ports []string `ovsdb:"ports"` Status map[string]string `ovsdb:"status"` + Mirrors []string `ovsdb:"mirrors"` } // OvsType is the simplified ORM model of the Bridge table @@ -177,11 +216,22 @@ type FlowSampleCollectorSetType struct { IPFIX *string // `ovsdb:"ipfix"` } -type Manager struct { +type ManagerType struct { UUID string `ovsdb:"_uuid"` Target string `ovsdb:"target"` } +type PortType struct { + UUID string `ovsdb:"_uuid"` + Name string `ovsdb:"name"` +} + +type MirrorType struct { + UUID string `ovsdb:"_uuid"` + Name string `ovsdb:"name"` + SelectSrcPort []string `ovsdb:"select_src_port"` +} + func GetModel() (model.DatabaseModel, error) { client, err := model.NewClientDBModel( "Open_vSwitch", @@ -189,7 +239,9 @@ func GetModel() (model.DatabaseModel, error) { "Open_vSwitch": &OvsType{}, "Bridge": &BridgeType{}, "Flow_Sample_Collector_Set": &FlowSampleCollectorSetType{}, - "Manager": &Manager{}, + "Manager": &ManagerType{}, + "Mirror": &MirrorType{}, + "Port": &PortType{}, }, ) if err != nil {