diff --git a/Makefile b/Makefile index 7efef8fa..73d07912 100644 --- a/Makefile +++ b/Makefile @@ -34,7 +34,7 @@ coverage: test integration-test @cat unit.cov integration.cov > profile.cov .PHONY: bench -bench: install-deps +bench: install-deps prebuild @echo "+ $@" @go test -run=XXX -count=3 -bench=. ./... | tee bench.out @benchstat bench.out diff --git a/cache/cache.go b/cache/cache.go index 44cdf142..ecd79266 100644 --- a/cache/cache.go +++ b/cache/cache.go @@ -583,7 +583,7 @@ func (t *TableCache) Populate(tableUpdates ovsdb.TableUpdates) error { return err } if existing := tCache.Row(uuid); existing != nil { - if !reflect.DeepEqual(newModel, existing) { + if !model.Equal(newModel, existing) { logger.V(5).Info("updating row", "old:", fmt.Sprintf("%+v", existing), "new", fmt.Sprintf("%+v", newModel)) if err := tCache.Update(uuid, newModel, false); err != nil { return err @@ -660,7 +660,7 @@ func (t *TableCache) Populate2(tableUpdates ovsdb.TableUpdates2) error { if err != nil { return fmt.Errorf("unable to apply row modifications: %v", err) } - if !reflect.DeepEqual(modified, existing) { + if !model.Equal(modified, existing) { logger.V(5).Info("updating row", "old", fmt.Sprintf("%+v", existing), "new", fmt.Sprintf("%+v", modified)) if err := tCache.Update(uuid, modified, false); err != nil { return err diff --git a/client/api.go b/client/api.go index f486bd4b..ed8cf8cb 100644 --- a/client/api.go +++ b/client/api.go @@ -2,7 +2,6 @@ package client import ( "context" - "encoding/json" "errors" "fmt" "reflect" @@ -223,8 +222,7 @@ func (a api) Get(ctx context.Context, m model.Model) error { return ErrNotFound } - foundBytes, _ := json.Marshal(found) - _ = json.Unmarshal(foundBytes, m) + model.CloneInto(found, m) return nil } diff --git a/cmd/modelgen/main.go b/cmd/modelgen/main.go index 40ac3141..59883379 100644 --- a/cmd/modelgen/main.go +++ b/cmd/modelgen/main.go @@ -24,6 +24,7 @@ var ( outDirP = flag.String("o", ".", "Directory where the generated files shall be stored") pkgNameP = flag.String("p", "ovsmodel", "Package name") dryRun = flag.Bool("d", false, "Dry run") + extended = flag.Bool("extended", false, "Generates additional code like deep-copy methods, etc.") ) func main() { @@ -76,6 +77,7 @@ func main() { for name, table := range dbSchema.Tables { tmpl := modelgen.NewTableTemplate() args := modelgen.GetTableTemplateData(pkgName, name, &table) + args.WithExtendedGen(*extended) if err := gen.Generate(filepath.Join(outDir, modelgen.FileName(name)), tmpl, args); err != nil { log.Fatal(err) } diff --git a/example/vswitchd/gen.go b/example/vswitchd/gen.go index 3cef8860..2614300b 100644 --- a/example/vswitchd/gen.go +++ b/example/vswitchd/gen.go @@ -1,3 +1,3 @@ package vswitchd -//go:generate ../../bin/modelgen -p vswitchd -o . ovs.ovsschema +//go:generate ../../bin/modelgen --extended -p vswitchd -o . ovs.ovsschema diff --git a/model/model.go b/model/model.go index 453fd2ea..447e9db9 100644 --- a/model/model.go +++ b/model/model.go @@ -24,8 +24,21 @@ import ( //} type Model interface{} +type CloneableModel interface { + CloneModel() Model + CloneModelInto(Model) +} + +type ComparableModel interface { + EqualsModel(Model) bool +} + // Clone creates a deep copy of a model func Clone(a Model) Model { + if cloner, ok := a.(CloneableModel); ok { + return cloner.CloneModel() + } + val := reflect.Indirect(reflect.ValueOf(a)) b := reflect.New(val.Type()).Interface() aBytes, _ := json.Marshal(a) @@ -33,6 +46,25 @@ func Clone(a Model) Model { return b } +// CloneInto deep copies a model into another one +func CloneInto(src, dst Model) { + if cloner, ok := src.(CloneableModel); ok { + cloner.CloneModelInto(dst) + return + } + + aBytes, _ := json.Marshal(src) + _ = json.Unmarshal(aBytes, dst) +} + +func Equal(l, r Model) bool { + if comparator, ok := l.(ComparableModel); ok { + return comparator.EqualsModel(r) + } + + return reflect.DeepEqual(l, r) +} + func modelSetUUID(model Model, uuid string) error { modelVal := reflect.ValueOf(model).Elem() for i := 0; i < modelVal.NumField(); i++ { diff --git a/model/model_test.go b/model/model_test.go index e277d7f5..e75a4d45 100644 --- a/model/model_test.go +++ b/model/model_test.go @@ -3,6 +3,7 @@ package model import ( "encoding/json" "fmt" + "reflect" "testing" "github.com/ovn-org/libovsdb/ovsdb" @@ -335,7 +336,28 @@ func TestValidate(t *testing.T) { } -func TestClone(t *testing.T) { +type modelC struct { + modelB + NoClone string +} + +func (a *modelC) CloneModel() Model { + return &modelC{ + modelB: a.modelB, + } +} + +func (a *modelC) CloneModelInto(b Model) { + c := b.(*modelC) + c.modelB = a.modelB +} + +func (a *modelC) EqualsModel(b Model) bool { + c := b.(*modelC) + return reflect.DeepEqual(a.modelB, c.modelB) +} + +func TestCloneViaMarshalling(t *testing.T) { a := &modelB{UID: "foo", Foo: "bar", Bar: "baz"} b := Clone(a).(*modelB) assert.Equal(t, a, b) @@ -344,3 +366,73 @@ func TestClone(t *testing.T) { b.UID = "quux" assert.NotEqual(t, a, b) } + +func TestCloneIntoViaMarshalling(t *testing.T) { + a := &modelB{UID: "foo", Foo: "bar", Bar: "baz"} + b := &modelB{} + CloneInto(a, b) + assert.Equal(t, a, b) + a.UID = "baz" + assert.NotEqual(t, a, b) + b.UID = "quux" + assert.NotEqual(t, a, b) +} + +func TestCloneViaCloneable(t *testing.T) { + a := &modelC{modelB: modelB{UID: "foo", Foo: "bar", Bar: "baz"}, NoClone: "noClone"} + func(a interface{}) { + _, ok := a.(CloneableModel) + assert.True(t, ok, "is not cloneable") + }(a) + // test that Clone() uses the cloneable interface, in which + // case modelC.NoClone won't be copied + b := Clone(a).(*modelC) + assert.NotEqual(t, a, b) + b.NoClone = a.NoClone + assert.Equal(t, a, b) + a.UID = "baz" + assert.NotEqual(t, a, b) + b.UID = "quux" + assert.NotEqual(t, a, b) +} + +func TestCloneIntoViaCloneable(t *testing.T) { + a := &modelC{modelB: modelB{UID: "foo", Foo: "bar", Bar: "baz"}, NoClone: "noClone"} + func(a interface{}) { + _, ok := a.(CloneableModel) + assert.True(t, ok, "is not cloneable") + }(a) + // test that CloneInto() uses the cloneable interface, in which + // case modelC.NoClone won't be copied + b := &modelC{} + CloneInto(a, b) + assert.NotEqual(t, a, b) + b.NoClone = a.NoClone + assert.Equal(t, a, b) + a.UID = "baz" + assert.NotEqual(t, a, b) + b.UID = "quux" + assert.NotEqual(t, a, b) +} + +func TestEqualViaDeepEqual(t *testing.T) { + a := &modelB{UID: "foo", Foo: "bar", Bar: "baz"} + b := &modelB{UID: "foo", Foo: "bar", Bar: "baz"} + assert.True(t, Equal(a, b)) + a.UID = "baz" + assert.False(t, Equal(a, b)) +} + +func TestEqualViaComparable(t *testing.T) { + a := &modelC{modelB: modelB{UID: "foo", Foo: "bar", Bar: "baz"}, NoClone: "noClone"} + func(a interface{}) { + _, ok := a.(ComparableModel) + assert.True(t, ok, "is not comparable") + }(a) + b := a.CloneModel().(*modelC) + // test that Equal() uses the comparable interface, in which + // case the difference on modelC.NoClone won't be noticed + assert.True(t, Equal(a, b)) + a.UID = "baz" + assert.False(t, Equal(a, b)) +} diff --git a/modelgen/dbmodel.go b/modelgen/dbmodel.go index e7a1866f..d3189abb 100644 --- a/modelgen/dbmodel.go +++ b/modelgen/dbmodel.go @@ -8,12 +8,15 @@ import ( "github.com/ovn-org/libovsdb/ovsdb" ) -// NewDBTemplate return a new ClientDBModel template -// It includes the following other templates that can be overridden to customize the generated file -// "header" -// "preDBDefinitions" -// "postDBDefinitions" -// It is design to be used with a map[string] interface and some defined keys (see GetDBTemplateData) +// NewDBTemplate returns a new ClientDBModel template. It includes the following +// other templates that can be overridden to customize the generated file: +// +// - `header`: to include a comment as a header before package definition +// - `preDBDefinitions`: to include code after package definition +// - `postDBDefinitions`: to include code at the end +// +// It is designed to be used with a map[string] interface and some defined keys +// (see GetDBTemplateData) func NewDBTemplate() *template.Template { return template.Must(template.New("").Funcs( template.FuncMap{ @@ -62,16 +65,18 @@ func Schema() ovsdb.DatabaseSchema { `)) } -//TableInfo represents the information of a table needed by the Model template +// TableInfo represents the information of a table needed by the Model template type TableInfo struct { TableName string StructName string } -// GetDBTemplateData returns the map needed to execute the DBTemplate. It has the following keys: -// DatabaseName: (string) the database name -// PackageName : (string) the package name -// Tables: []Table list of Tables that form the Model +// GetDBTemplateData returns the map needed to execute the DBTemplate. It has +// the following keys: +// +// - `DatabaseName`: (string) the database name +// - `PackageName`: (string) the package name +// - `Tables`: []Table list of Tables that form the Model func GetDBTemplateData(pkg string, schema ovsdb.DatabaseSchema) map[string]interface{} { data := map[string]interface{}{} data["DatabaseName"] = schema.Name diff --git a/modelgen/table.go b/modelgen/table.go index bcbba96e..a6e07931 100644 --- a/modelgen/table.go +++ b/modelgen/table.go @@ -9,21 +9,170 @@ import ( "github.com/ovn-org/libovsdb/ovsdb" ) -// NewTableTemplate returns a new table template -// It includes the following other templates that can be overridden to customize the generated file -// "header" -// "preStructDefinitions" -// "structComment" -// "extraFields" -// "extraTags" -// "postStructDefinitions" -// It is design to be used with a map[string] interface and some defined keys (see GetTableTemplateData) -// In addition, the following functions can be used within the template: -// "PrintVal": prints a field value -// "FieldName": prints the name of a field based on its column -// "FieldType": prints the field name based on its column and schema -// "FieldTypeWithEnums": same as FieldType but with enum type expansion -// "OvsdbTag": prints the ovsdb tag +// extendedGenTemplate include additional code generation that is optional, like +// deep copy methods. +var extendedGenTemplate = ` +{{- define "deepCopyExtraFields" }}{{ end }} +{{- define "equalExtraFields" }}{{ end }} +{{- define "extendedGenImports" }} +{{- if index . "WithExtendedGen" }} +import "github.com/ovn-org/libovsdb/model" +{{- end }} +{{- end }} +{{- define "extendedGen" }} +{{- if index . "WithExtendedGen" }} +{{- $tableName := index . "TableName" }} +{{- $structName := index . "StructName" }} +{{- range $field := index . "Fields" }} +{{- $fieldName := FieldName $field.Column }} +{{- $type := "" }} +{{- if index $ "WithEnumTypes" }} +{{- $type = FieldTypeWithEnums $tableName $field.Column $field.Schema }} +{{- else }} +{{- $type = FieldType $tableName $field.Column $field.Schema }} +{{- end }} +{{- if or (eq (index $type 0) '*') (eq (slice $type 0 2) "[]") (eq (slice $type 0 3) "map") }} +func copy{{ $structName }}{{ $fieldName }}(a {{ $type }}) {{ $type }} { + if a == nil { + return nil + } + {{- if eq (index $type 0) '*' }} + b := *a + return &b + {{- else if eq (slice $type 0 2) "[]" }} + b := make({{ $type }}, len(a)) + copy(b, a) + return b + {{- else }} + b := make({{ $type }}, len(a)) + for k, v := range a { + b[k] = v + } + return b + {{- end }} +} + +func equal{{ $structName }}{{ $fieldName }}(a, b {{ $type }}) bool { + if (a == nil) != (b == nil) { + return false + } + {{- if eq (index $type 0) '*' }} + if a == b { + return true + } + return *a == *b + {{- else if eq (slice $type 0 2) "[]" }} + if len(a) != len(b) { + return false + } + for i, v := range a { + if b[i] != v { + return false + } + } + return true + {{- else }} + if len(a) != len(b) { + return false + } + for k, v := range a { + if w, ok := b[k]; !ok || v != w { + return false + } + } + return true + {{- end }} +} + +{{ end }} +{{- end }} + +func (a *{{ $structName }}) DeepCopyInto(b *{{ $structName }}) { + *b = *a + {{- range $field := index . "Fields" }} + {{- $fieldName := FieldName $field.Column }} + {{- $type := "" }} + {{- if index $ "WithEnumTypes" }} + {{- $type = FieldTypeWithEnums $tableName $field.Column $field.Schema }} + {{- else }} + {{- $type = FieldType $tableName $field.Column $field.Schema }} + {{- end }} + {{- if or (eq (index $type 0) '*') (eq (slice $type 0 2) "[]") (eq (slice $type 0 3) "map") }} + b.{{ $fieldName }} = copy{{ $structName }}{{ $fieldName }}(a.{{ $fieldName }}) + {{- end }} + {{- end }} + {{- template "deepCopyExtraFields" . }} +} + +func (a *{{ $structName }}) DeepCopy() *{{ $structName }} { + b := new({{ $structName }}) + a.DeepCopyInto(b) + return b +} + +func (a *{{ $structName }}) CloneModelInto(b model.Model) { + c := b.(*{{ $structName }}) + a.DeepCopyInto(c) +} + +func (a *{{ $structName }}) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *{{ $structName }}) Equals(b *{{ $structName }}) bool { + {{- range $i, $field := index . "Fields" }} + {{- $fieldName := FieldName $field.Column }} + {{- $type := "" }} + {{- if index $ "WithEnumTypes" }} + {{- $type = FieldTypeWithEnums $tableName $field.Column $field.Schema }} + {{- else }} + {{- $type = FieldType $tableName $field.Column $field.Schema }} + {{- end }} + {{- if $i }}&& + {{ else }}return {{ end }} + {{- if or (eq (index $type 0) '*') (eq (slice $type 0 2) "[]") (eq (slice $type 0 3) "map") -}} + equal{{ $structName }}{{ $fieldName }}(a.{{ $fieldName }}, b.{{ $fieldName }}) + {{- else -}} + a.{{ $fieldName }} == b.{{ $fieldName }} + {{- end }} + {{- end }} + {{- template "equalExtraFields" . }} +} + +func (a *{{ $structName }}) EqualsModel(b model.Model) bool { + c := b.(*{{ $structName }}) + return a.Equals(c) +} + +var _ model.CloneableModel = &{{ $structName }}{} +var _ model.ComparableModel = &{{ $structName }}{} +{{- end }} +{{- end }} +` + +// NewTableTemplate returns a new table template. It includes the following +// other templates that can be overridden to customize the generated file: +// +// - `header`: override the comment as header before package definition +// - `preStructDefinitions`: deprecated in favor of `extraImports` +// - `extraImports`: include additional imports +// - `structComment`: override the comment generated for the table +// - `extraFields`: add extra fields to the table +// - `extraTags`: add tags to the extra fields +// - `deepCopyExtraFields`: copy extra fields when copying a table +// - `equalExtraFields`: compare extra fields when comparing a table +// - `postStructDefinitions`: deprecated in favor of `extraDefinitions` +// - `extraDefinitions`: include additional definitions like functions etc. +// +// It is designed to be used with a map[string] interface and some defined keys +// (see GetTableTemplateData). In addition, the following functions can be used +// within the template: +// +// - `PrintVal`: prints a field value +// - `FieldName`: prints the name of a field based on its column +// - `FieldType`: prints the field type based on its column and schema +// - `FieldTypeWithEnums`: same as FieldType but with enum type expansion +// - `OvsdbTag`: prints the ovsdb tag func NewTableTemplate() *template.Template { return template.Must(template.New("").Funcs( template.FuncMap{ @@ -33,19 +182,21 @@ func NewTableTemplate() *template.Template { "FieldTypeWithEnums": FieldTypeWithEnums, "OvsdbTag": Tag, }, - ).Parse(` + ).Parse(extendedGenTemplate + ` {{- define "header" }} // Code generated by "libovsdb.modelgen" // DO NOT EDIT. {{- end }} +{{ define "extraImports" }}{{ end }} {{ define "preStructDefinitions" }}{{ end }} {{- define "structComment" }} // {{ index . "StructName" }} defines an object in {{ index . "TableName" }} table {{- end }} {{ define "extraTags" }}{{ end }} {{ define "extraFields" }}{{ end }} -{{ template "header" . }} +{{ define "extraDefinitions" }}{{ end }} {{ define "postStructDefinitions" }}{{ end }} +{{ template "header" . }} {{ define "enums" }} {{ if index . "WithEnumTypes" }} {{ if index . "Enums" }} @@ -67,7 +218,9 @@ var ( {{- end }} {{- end }} package {{ index . "PackageName" }} -{{ template "preStructDefinitions" }} +{{ template "extendedGenImports" . }} +{{ template "extraImports" . }} +{{ template "preStructDefinitions" . }} {{ template "enums" . }} {{ template "structComment" . }} type {{ index . "StructName" }} struct { @@ -81,8 +234,9 @@ type {{ index . "StructName" }} struct { {{ end }} {{ template "extraFields" . }} } - {{ template "postStructDefinitions" . }} +{{ template "extraDefinitions" . }} +{{ template "extendedGen" . }} `)) } @@ -109,11 +263,19 @@ func (t TableTemplateData) WithEnumTypes(val bool) { t["WithEnumTypes"] = val } -// GetTableTemplateData returns the TableTemplateData map. It has the following keys: -// TableName: (string) the table name -// PackageName : (string) the package name -// StructName: (string) the struct name -// Fields: []Field a list of Fields that the struct has +// WithExtendedGen configures whether the Template should generate code to deep +// copy models. +func (t TableTemplateData) WithExtendedGen(val bool) { + t["WithExtendedGen"] = val +} + +// GetTableTemplateData returns the TableTemplateData map. It has the following +// keys: +// +// - `TableName`: (string) the table name +// - `TPackageName`: (string) the package name +// - `TStructName`: (string) the struct name +// - `TFields`: []Field a list of Fields that the struct has func GetTableTemplateData(pkg, name string, table *ovsdb.TableSchema) TableTemplateData { data := map[string]interface{}{} data["TableName"] = name @@ -143,6 +305,7 @@ func GetTableTemplateData(pkg, name string, table *ovsdb.TableSchema) TableTempl data["Fields"] = Fields data["Enums"] = Enums data["WithEnumTypes"] = true + data["WithExtendedGen"] = false return data } diff --git a/modelgen/table_test.go b/modelgen/table_test.go index 703bcc04..0b81928a 100644 --- a/modelgen/table_test.go +++ b/modelgen/table_test.go @@ -3,9 +3,13 @@ package modelgen import ( "encoding/json" "fmt" + "reflect" "testing" "text/template" + "github.com/google/uuid" + "github.com/ovn-org/libovsdb/example/vswitchd" + "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -141,6 +145,338 @@ type AtomicTable struct { OtherProtocol *string OtherStr string } +`, + }, + { + name: "with deep copy code", + extend: func(tmpl *template.Template, data TableTemplateData) { + data.WithExtendedGen(true) + }, + expected: `// Code generated by "libovsdb.modelgen" +// DO NOT EDIT. + +package test + +import "github.com/ovn-org/libovsdb/model" + +type ( + AtomicTableEventType = string + AtomicTableProtocol = string +) + +var ( + AtomicTableEventTypeEmptyLbBackends AtomicTableEventType = "empty_lb_backends" + AtomicTableProtocolTCP AtomicTableProtocol = "tcp" + AtomicTableProtocolUDP AtomicTableProtocol = "udp" + AtomicTableProtocolSCTP AtomicTableProtocol = "sctp" +) + +// AtomicTable defines an object in atomicTable table +type AtomicTable struct { + UUID string ` + "`" + `ovsdb:"_uuid"` + "`" + ` + EventType AtomicTableEventType ` + "`" + `ovsdb:"event_type"` + "`" + ` + Float float64 ` + "`" + `ovsdb:"float"` + "`" + ` + Int int ` + "`" + `ovsdb:"int"` + "`" + ` + Protocol *AtomicTableProtocol ` + "`" + `ovsdb:"protocol"` + "`" + ` + Str string ` + "`" + `ovsdb:"str"` + "`" + ` +} + +func copyAtomicTableProtocol(a *AtomicTableProtocol) *AtomicTableProtocol { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalAtomicTableProtocol(a, b *AtomicTableProtocol) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func (a *AtomicTable) DeepCopyInto(b *AtomicTable) { + *b = *a + b.Protocol = copyAtomicTableProtocol(a.Protocol) +} + +func (a *AtomicTable) DeepCopy() *AtomicTable { + b := new(AtomicTable) + a.DeepCopyInto(b) + return b +} + +func (a *AtomicTable) CloneModelInto(b model.Model) { + c := b.(*AtomicTable) + a.DeepCopyInto(c) +} + +func (a *AtomicTable) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *AtomicTable) Equals(b *AtomicTable) bool { + return a.UUID == b.UUID && + a.EventType == b.EventType && + a.Float == b.Float && + a.Int == b.Int && + equalAtomicTableProtocol(a.Protocol, b.Protocol) && + a.Str == b.Str +} + +func (a *AtomicTable) EqualsModel(b model.Model) bool { + c := b.(*AtomicTable) + return a.Equals(c) +} + +var _ model.CloneableModel = &AtomicTable{} +var _ model.ComparableModel = &AtomicTable{} +`, + }, + { + name: "with deep copy code and extra fields", + extend: func(tmpl *template.Template, data TableTemplateData) { + data.WithExtendedGen(true) + extra := `{{ define "extraFields" }} {{- $tableName := index . "TableName" }} {{ range $field := index . "Fields" }} Other{{ FieldName $field.Column }} {{ FieldType $tableName $field.Column $field.Schema }} +{{ end }} +{{- end }} +{{ define "extraImports" }} +import "fmt" +{{ end }} +{{ define "extraDefinitions" }} +func copyAtomicTableOtherProtocol(a *AtomicTableProtocol) *AtomicTableProtocol { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalAtomicTableOtherProtocol(a, b *AtomicTableProtocol) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func (a *AtomicTable) PrintAtomicTableOtherProtocol() bool { + fmt.Printf(a.OtherProtocol) +} +{{ end }} +{{ define "deepCopyExtraFields" }} + b.OtherProtocol = copyAtomicTableOtherProtocol(a.OtherProtocol) +{{- end }} +{{ define "equalExtraFields" }} && + equalAtomicTableOtherProtocol(a.OtherProtocol, b.OtherProtocol) +{{- end }} +` + _, err := tmpl.Parse(extra) + if err != nil { + panic(err) + } + }, + expected: `// Code generated by "libovsdb.modelgen" +// DO NOT EDIT. + +package test + +import "github.com/ovn-org/libovsdb/model" + +import "fmt" + +type ( + AtomicTableEventType = string + AtomicTableProtocol = string +) + +var ( + AtomicTableEventTypeEmptyLbBackends AtomicTableEventType = "empty_lb_backends" + AtomicTableProtocolTCP AtomicTableProtocol = "tcp" + AtomicTableProtocolUDP AtomicTableProtocol = "udp" + AtomicTableProtocolSCTP AtomicTableProtocol = "sctp" +) + +// AtomicTable defines an object in atomicTable table +type AtomicTable struct { + UUID string ` + "`" + `ovsdb:"_uuid"` + "`" + ` + EventType AtomicTableEventType ` + "`" + `ovsdb:"event_type"` + "`" + ` + Float float64 ` + "`" + `ovsdb:"float"` + "`" + ` + Int int ` + "`" + `ovsdb:"int"` + "`" + ` + Protocol *AtomicTableProtocol ` + "`" + `ovsdb:"protocol"` + "`" + ` + Str string ` + "`" + `ovsdb:"str"` + "`" + ` + + OtherUUID string + OtherEventType string + OtherFloat float64 + OtherInt int + OtherProtocol *string + OtherStr string +} + +func copyAtomicTableOtherProtocol(a *AtomicTableProtocol) *AtomicTableProtocol { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalAtomicTableOtherProtocol(a, b *AtomicTableProtocol) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func (a *AtomicTable) PrintAtomicTableOtherProtocol() bool { + fmt.Printf(a.OtherProtocol) +} + +func copyAtomicTableProtocol(a *AtomicTableProtocol) *AtomicTableProtocol { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalAtomicTableProtocol(a, b *AtomicTableProtocol) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func (a *AtomicTable) DeepCopyInto(b *AtomicTable) { + *b = *a + b.Protocol = copyAtomicTableProtocol(a.Protocol) + b.OtherProtocol = copyAtomicTableOtherProtocol(a.OtherProtocol) +} + +func (a *AtomicTable) DeepCopy() *AtomicTable { + b := new(AtomicTable) + a.DeepCopyInto(b) + return b +} + +func (a *AtomicTable) CloneModelInto(b model.Model) { + c := b.(*AtomicTable) + a.DeepCopyInto(c) +} + +func (a *AtomicTable) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *AtomicTable) Equals(b *AtomicTable) bool { + return a.UUID == b.UUID && + a.EventType == b.EventType && + a.Float == b.Float && + a.Int == b.Int && + equalAtomicTableProtocol(a.Protocol, b.Protocol) && + a.Str == b.Str && + equalAtomicTableOtherProtocol(a.OtherProtocol, b.OtherProtocol) +} + +func (a *AtomicTable) EqualsModel(b model.Model) bool { + c := b.(*AtomicTable) + return a.Equals(c) +} + +var _ model.CloneableModel = &AtomicTable{} +var _ model.ComparableModel = &AtomicTable{} +`, + }, + { + name: "with deep copy code but no enums", + extend: func(tmpl *template.Template, data TableTemplateData) { + data.WithExtendedGen(true) + data.WithEnumTypes(false) + }, + expected: `// Code generated by "libovsdb.modelgen" +// DO NOT EDIT. + +package test + +import "github.com/ovn-org/libovsdb/model" + +// AtomicTable defines an object in atomicTable table +type AtomicTable struct { + UUID string ` + "`" + `ovsdb:"_uuid"` + "`" + ` + EventType string ` + "`" + `ovsdb:"event_type"` + "`" + ` + Float float64 ` + "`" + `ovsdb:"float"` + "`" + ` + Int int ` + "`" + `ovsdb:"int"` + "`" + ` + Protocol *string ` + "`" + `ovsdb:"protocol"` + "`" + ` + Str string ` + "`" + `ovsdb:"str"` + "`" + ` +} + +func copyAtomicTableProtocol(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalAtomicTableProtocol(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func (a *AtomicTable) DeepCopyInto(b *AtomicTable) { + *b = *a + b.Protocol = copyAtomicTableProtocol(a.Protocol) +} + +func (a *AtomicTable) DeepCopy() *AtomicTable { + b := new(AtomicTable) + a.DeepCopyInto(b) + return b +} + +func (a *AtomicTable) CloneModelInto(b model.Model) { + c := b.(*AtomicTable) + a.DeepCopyInto(c) +} + +func (a *AtomicTable) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *AtomicTable) Equals(b *AtomicTable) bool { + return a.UUID == b.UUID && + a.EventType == b.EventType && + a.Float == b.Float && + a.Int == b.Int && + equalAtomicTableProtocol(a.Protocol, b.Protocol) && + a.Str == b.Str +} + +func (a *AtomicTable) EqualsModel(b model.Model) bool { + c := b.(*AtomicTable) + return a.Equals(c) +} + +var _ model.CloneableModel = &AtomicTable{} +var _ model.ComparableModel = &AtomicTable{} `, }, { @@ -148,7 +484,7 @@ type AtomicTable struct { extend: func(tmpl *template.Template, data TableTemplateData) { extra := `{{ define "postStructDefinitions" }} func {{ index . "TestName" }} () string { - return "{{ index . "StructName" }}" + return "{{ index . "StructName" }}" } {{ end }} ` _, err := tmpl.Parse(extra) @@ -365,7 +701,7 @@ func ExampleNewTableTemplate() { // It can access the default data values plus any extra field that is added to data _, err = base.Parse(`{{define "postStructDefinitions"}} func (t {{ index . "StructName" }}) {{ index . "FuncName"}}() string { - return "bar" + return "bar" }{{end}}`) if err != nil { panic(err) @@ -381,3 +717,163 @@ func (t {{ index . "StructName" }}) {{ index . "FuncName"}}() string { panic(err) } } + +func TestExtendedGenCloneableModel(t *testing.T) { + a := &vswitchd.Bridge{} + func(a interface{}) { + _, ok := a.(model.CloneableModel) + assert.True(t, ok, "is not cloneable") + }(a) +} + +func TestExtendedGenComparableModel(t *testing.T) { + a := &vswitchd.Bridge{} + func(a interface{}) { + _, ok := a.(model.ComparableModel) + assert.True(t, ok, "is not comparable") + }(a) +} + +func doGenDeepCopy(data model.CloneableModel, b *testing.B) { + _ = data.CloneModel() +} + +func doJSONDeepCopy(data model.CloneableModel, b *testing.B) { + aBytes, err := json.Marshal(data) + if err != nil { + b.Fatal(err) + } + err = json.Unmarshal(aBytes, data) + if err != nil { + b.Fatal(err) + } +} + +func buildRandStr() *string { + str := uuid.New().String() + return &str +} + +func buildTestBridge() *vswitchd.Bridge { + return &vswitchd.Bridge{ + UUID: *buildRandStr(), + AutoAttach: buildRandStr(), + Controller: []string{*buildRandStr(), *buildRandStr()}, + DatapathID: buildRandStr(), + DatapathType: *buildRandStr(), + DatapathVersion: *buildRandStr(), + ExternalIDs: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + FailMode: &vswitchd.BridgeFailModeSecure, + FloodVLANs: [4096]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + FlowTables: map[int]string{1: *buildRandStr(), 2: *buildRandStr()}, + IPFIX: buildRandStr(), + McastSnoopingEnable: false, + Mirrors: []string{*buildRandStr(), *buildRandStr()}, + Name: *buildRandStr(), + Netflow: buildRandStr(), + OtherConfig: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + Ports: []string{*buildRandStr(), *buildRandStr()}, + Protocols: []string{*buildRandStr(), *buildRandStr()}, + RSTPEnable: true, + RSTPStatus: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + Sflow: buildRandStr(), + Status: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + STPEnable: false, + } +} + +func buildTestInterface() *vswitchd.Interface { + aBool := false + aInt := 0 + return &vswitchd.Interface{ + UUID: *buildRandStr(), + AdminState: buildRandStr(), + BFD: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + BFDStatus: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + CFMFault: &aBool, + CFMFaultStatus: []string{*buildRandStr(), *buildRandStr()}, + CFMFlapCount: &aInt, + CFMHealth: &aInt, + CFMMpid: &aInt, + CFMRemoteMpids: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, + CFMRemoteOpstate: buildRandStr(), + Duplex: buildRandStr(), + Error: buildRandStr(), + ExternalIDs: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + Ifindex: &aInt, + IngressPolicingBurst: aInt, + IngressPolicingKpktsBurst: aInt, + IngressPolicingKpktsRate: aInt, + IngressPolicingRate: aInt, + LACPCurrent: &aBool, + LinkResets: &aInt, + LinkSpeed: &aInt, + LinkState: buildRandStr(), + LLDP: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + MAC: buildRandStr(), + MACInUse: buildRandStr(), + MTU: &aInt, + MTURequest: &aInt, + Name: *buildRandStr(), + Ofport: &aInt, + OfportRequest: &aInt, + Options: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + OtherConfig: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + Statistics: map[string]int{*buildRandStr(): 0, *buildRandStr(): 1}, + Status: map[string]string{*buildRandStr(): *buildRandStr(), *buildRandStr(): *buildRandStr()}, + Type: *buildRandStr(), + } +} + +func BenchmarkDeepCopy(b *testing.B) { + bridge := buildTestBridge() + intf := buildTestInterface() + benchmarks := []struct { + name string + data model.CloneableModel + deepCopier func(model.CloneableModel, *testing.B) + }{ + {"modelgen Bridge", bridge, doGenDeepCopy}, + {"json Bridge", bridge, doJSONDeepCopy}, + {"modelgen Interface", intf, doGenDeepCopy}, + {"json Interface", intf, doJSONDeepCopy}, + } + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + bm.deepCopier(bm.data, b) + } + }) + } +} + +func doGenEquals(l, r model.ComparableModel, b *testing.B) { + l.EqualsModel(r) +} + +func doDeepEqual(l, r model.ComparableModel, b *testing.B) { + reflect.DeepEqual(l, r) +} + +func BenchmarkDeepEqual(b *testing.B) { + bridge := buildTestBridge() + intf := buildTestInterface() + benchmarks := []struct { + name string + left model.ComparableModel + right model.ComparableModel + comparator func(model.ComparableModel, model.ComparableModel, *testing.B) + }{ + {"modelgen Bridge", bridge, bridge.DeepCopy(), doGenEquals}, + {"reflect Bridge", bridge, bridge.DeepCopy(), doDeepEqual}, + {"modelgen Interface", intf, intf.DeepCopy(), doGenEquals}, + {"reflect Interface", intf, intf.DeepCopy(), doDeepEqual}, + } + for _, bm := range benchmarks { + b.Run(bm.name, func(b *testing.B) { + for i := 0; i < b.N; i++ { + bm.comparator(bm.left, bm.right, b) + } + }) + } +} diff --git a/ovsdb/serverdb/database.go b/ovsdb/serverdb/database.go index dda0f7e6..aef6c284 100644 --- a/ovsdb/serverdb/database.go +++ b/ovsdb/serverdb/database.go @@ -3,6 +3,8 @@ package serverdb +import "github.com/ovn-org/libovsdb/model" + type ( DatabaseModel = string ) @@ -25,3 +27,118 @@ type Database struct { Schema *string `ovsdb:"schema"` Sid *string `ovsdb:"sid"` } + +func copyDatabaseCid(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalDatabaseCid(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyDatabaseIndex(a *int) *int { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalDatabaseIndex(a, b *int) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyDatabaseSchema(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalDatabaseSchema(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func copyDatabaseSid(a *string) *string { + if a == nil { + return nil + } + b := *a + return &b +} + +func equalDatabaseSid(a, b *string) bool { + if (a == nil) != (b == nil) { + return false + } + if a == b { + return true + } + return *a == *b +} + +func (a *Database) DeepCopyInto(b *Database) { + *b = *a + b.Cid = copyDatabaseCid(a.Cid) + b.Index = copyDatabaseIndex(a.Index) + b.Schema = copyDatabaseSchema(a.Schema) + b.Sid = copyDatabaseSid(a.Sid) +} + +func (a *Database) DeepCopy() *Database { + b := new(Database) + a.DeepCopyInto(b) + return b +} + +func (a *Database) CloneModelInto(b model.Model) { + c := b.(*Database) + a.DeepCopyInto(c) +} + +func (a *Database) CloneModel() model.Model { + return a.DeepCopy() +} + +func (a *Database) Equals(b *Database) bool { + return a.UUID == b.UUID && + equalDatabaseCid(a.Cid, b.Cid) && + a.Connected == b.Connected && + equalDatabaseIndex(a.Index, b.Index) && + a.Leader == b.Leader && + a.Model == b.Model && + a.Name == b.Name && + equalDatabaseSchema(a.Schema, b.Schema) && + equalDatabaseSid(a.Sid, b.Sid) +} + +func (a *Database) EqualsModel(b model.Model) bool { + c := b.(*Database) + return a.Equals(c) +} + +var _ model.CloneableModel = &Database{} +var _ model.ComparableModel = &Database{} diff --git a/ovsdb/serverdb/gen.go b/ovsdb/serverdb/gen.go index 87aace3b..5923af60 100644 --- a/ovsdb/serverdb/gen.go +++ b/ovsdb/serverdb/gen.go @@ -3,4 +3,4 @@ package serverdb // server_model is a database model for the special _Server database that all // ovsdb instances export. It reports back status of the server process itself. -//go:generate ../../bin/modelgen -p serverdb -o . _server.ovsschema +//go:generate ../../bin/modelgen --extended -p serverdb -o . _server.ovsschema