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 {