From 3259b9120bfa26742245369e1bbad11d472535d7 Mon Sep 17 00:00:00 2001 From: gugu <404200721@qq.com> Date: Wed, 8 Mar 2023 11:01:44 +0800 Subject: [PATCH] prepare for libovsdb replacement (#1978) --- Makefile | 1 + go.mod | 4 +- hack/mockgen.sh | 8 + mocks/doc.go | 8 + mocks/pkg/ovs/interface.go | 3228 ++++++++++++++++++ pkg/controller/config.go | 2 +- pkg/controller/controller.go | 17 +- pkg/controller/external-gw.go | 1 + pkg/controller/gc.go | 2 +- pkg/controller/ovn-ic.go | 33 +- pkg/controller/ovn-ic_test.go | 1 + pkg/controller/pod.go | 10 +- pkg/controller/security_group.go | 70 +- pkg/controller/security_group_test.go | 77 + pkg/controller/vpc.go | 7 +- pkg/ovs/interface.go | 166 + pkg/ovs/ovn-nb-acl.go | 825 +++++ pkg/ovs/ovn-nb-acl_test.go | 1433 ++++++++ pkg/ovs/ovn-nb-address_set.go | 187 + pkg/ovs/ovn-nb-address_set_test.go | 241 ++ pkg/ovs/ovn-nb-dhcp_options.go | 334 ++ pkg/ovs/ovn-nb-dhcp_options_test.go | 467 +++ pkg/ovs/ovn-nb-gateway_chassis.go | 163 + pkg/ovs/ovn-nb-gateway_chassis_test.go | 123 + pkg/ovs/ovn-nb-load_balancer.go | 265 ++ pkg/ovs/ovn-nb-load_balancer_test.go | 361 ++ pkg/ovs/ovn-nb-logical_router.go | 248 +- pkg/ovs/ovn-nb-logical_router_policy.go | 333 +- pkg/ovs/ovn-nb-logical_router_policy_test.go | 393 +++ pkg/ovs/ovn-nb-logical_router_port.go | 342 +- pkg/ovs/ovn-nb-logical_router_port_test.go | 610 ++++ pkg/ovs/ovn-nb-logical_router_route.go | 333 +- pkg/ovs/ovn-nb-logical_router_route_test.go | 373 ++ pkg/ovs/ovn-nb-logical_router_test.go | 496 +++ pkg/ovs/ovn-nb-logical_switch.go | 338 ++ pkg/ovs/ovn-nb-logical_switch_port.go | 633 +++- pkg/ovs/ovn-nb-logical_switch_port_test.go | 1528 +++++++++ pkg/ovs/ovn-nb-logical_switch_test.go | 602 ++++ pkg/ovs/ovn-nb-nat.go | 369 ++ pkg/ovs/ovn-nb-nat_test.go | 627 ++++ pkg/ovs/ovn-nb-port_group.go | 267 +- pkg/ovs/ovn-nb-port_group_test.go | 500 +++ pkg/ovs/ovn-nb-suite_test.go | 738 ++++ pkg/ovs/ovn-nb.go | 218 ++ pkg/ovs/ovn-nb_global.go | 164 + pkg/ovs/ovn-nb_global_test.go | 223 ++ pkg/ovs/ovn-nb_test.go | 280 ++ pkg/ovs/ovn-nbctl-legacy.go | 5 - pkg/ovs/ovn-nbctl-legacy_test.go | 1 + pkg/ovs/ovn.go | 54 +- pkg/ovs/util.go | 192 ++ pkg/ovs/util_test.go | 233 ++ pkg/ovsdb/client/client.go | 9 + pkg/util/const.go | 2 + 54 files changed, 17941 insertions(+), 204 deletions(-) create mode 100755 hack/mockgen.sh create mode 100644 mocks/doc.go create mode 100644 mocks/pkg/ovs/interface.go create mode 100644 pkg/controller/ovn-ic_test.go create mode 100644 pkg/controller/security_group_test.go create mode 100644 pkg/ovs/interface.go create mode 100644 pkg/ovs/ovn-nb-acl.go create mode 100644 pkg/ovs/ovn-nb-acl_test.go create mode 100644 pkg/ovs/ovn-nb-address_set.go create mode 100644 pkg/ovs/ovn-nb-address_set_test.go create mode 100644 pkg/ovs/ovn-nb-dhcp_options.go create mode 100644 pkg/ovs/ovn-nb-dhcp_options_test.go create mode 100644 pkg/ovs/ovn-nb-gateway_chassis.go create mode 100644 pkg/ovs/ovn-nb-gateway_chassis_test.go create mode 100644 pkg/ovs/ovn-nb-load_balancer.go create mode 100644 pkg/ovs/ovn-nb-load_balancer_test.go create mode 100644 pkg/ovs/ovn-nb-logical_router_policy_test.go create mode 100644 pkg/ovs/ovn-nb-logical_router_port_test.go create mode 100644 pkg/ovs/ovn-nb-logical_router_route_test.go create mode 100644 pkg/ovs/ovn-nb-logical_router_test.go create mode 100644 pkg/ovs/ovn-nb-logical_switch.go create mode 100644 pkg/ovs/ovn-nb-logical_switch_port_test.go create mode 100644 pkg/ovs/ovn-nb-logical_switch_test.go create mode 100644 pkg/ovs/ovn-nb-nat.go create mode 100644 pkg/ovs/ovn-nb-nat_test.go create mode 100644 pkg/ovs/ovn-nb-port_group_test.go create mode 100644 pkg/ovs/ovn-nb-suite_test.go create mode 100644 pkg/ovs/ovn-nb.go create mode 100644 pkg/ovs/ovn-nb_global.go create mode 100644 pkg/ovs/ovn-nb_global_test.go create mode 100644 pkg/ovs/ovn-nb_test.go create mode 100644 pkg/ovs/util_test.go diff --git a/Makefile b/Makefile index df1f9bfd11f..7ca944da081 100644 --- a/Makefile +++ b/Makefile @@ -657,6 +657,7 @@ scan: .PHONY: ut ut: ginkgo -mod=mod -progress --always-emit-ginkgo-writer --slow-spec-threshold=60s test/unittest + go test ./pkg/... .PHONY: ipam-bench ipam-bench: diff --git a/go.mod b/go.mod index 696c8840017..c3521c75903 100644 --- a/go.mod +++ b/go.mod @@ -17,6 +17,8 @@ require ( github.com/docker/docker v23.0.1+incompatible github.com/emicklei/go-restful/v3 v3.10.1 github.com/evanphx/json-patch/v5 v5.6.0 + github.com/go-logr/stdr v1.2.2 + github.com/golang/mock v1.6.0 github.com/greenpau/ovsdb v1.0.3 github.com/k8snetworkplumbingwg/network-attachment-definition-client v1.4.0 github.com/mdlayher/arp v0.0.0-20220512170110-6706a2966875 @@ -105,7 +107,6 @@ require ( github.com/go-kit/log v0.2.1 // indirect github.com/go-logfmt/logfmt v0.6.0 // indirect github.com/go-logr/logr v1.2.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect github.com/go-openapi/jsonpointer v0.19.6 // indirect github.com/go-openapi/jsonreference v0.20.2 // indirect github.com/go-openapi/swag v0.22.3 // indirect @@ -115,7 +116,6 @@ require ( github.com/golang-jwt/jwt/v4 v4.5.0 // indirect github.com/golang/glog v1.0.0 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/mock v1.6.0 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/google/cadvisor v0.46.0 // indirect github.com/google/cel-go v0.13.0 // indirect diff --git a/hack/mockgen.sh b/hack/mockgen.sh new file mode 100755 index 00000000000..a4657a8d651 --- /dev/null +++ b/hack/mockgen.sh @@ -0,0 +1,8 @@ +#! /usr/bin/env bash + +set -o errexit +set -o nounset +set -o pipefail + +# require mockgen v1.6.0+ +go generate ./mocks \ No newline at end of file diff --git a/mocks/doc.go b/mocks/doc.go new file mode 100644 index 00000000000..7fa05169667 --- /dev/null +++ b/mocks/doc.go @@ -0,0 +1,8 @@ +package ovs + +/* +Package mocks will have all the mocks +*/ + +// OvnClient mocks +//go:generate mockgen -source=../pkg/ovs/interface.go -destination=./pkg/ovs/interface.go -package=ovs diff --git a/mocks/pkg/ovs/interface.go b/mocks/pkg/ovs/interface.go new file mode 100644 index 00000000000..0313c639e32 --- /dev/null +++ b/mocks/pkg/ovs/interface.go @@ -0,0 +1,3228 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ../pkg/ovs/interface.go + +// Package ovs is a generated GoMock package. +package ovs + +import ( + reflect "reflect" + + gomock "github.com/golang/mock/gomock" + v1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + ovs "github.com/kubeovn/kube-ovn/pkg/ovs" + ovnnb "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + ovsdb "github.com/ovn-org/libovsdb/ovsdb" + v10 "k8s.io/api/networking/v1" +) + +// MockNbGlobal is a mock of NbGlobal interface. +type MockNbGlobal struct { + ctrl *gomock.Controller + recorder *MockNbGlobalMockRecorder +} + +// MockNbGlobalMockRecorder is the mock recorder for MockNbGlobal. +type MockNbGlobalMockRecorder struct { + mock *MockNbGlobal +} + +// NewMockNbGlobal creates a new mock instance. +func NewMockNbGlobal(ctrl *gomock.Controller) *MockNbGlobal { + mock := &MockNbGlobal{ctrl: ctrl} + mock.recorder = &MockNbGlobalMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNbGlobal) EXPECT() *MockNbGlobalMockRecorder { + return m.recorder +} + +// GetNbGlobal mocks base method. +func (m *MockNbGlobal) GetNbGlobal() (*ovnnb.NBGlobal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNbGlobal") + ret0, _ := ret[0].(*ovnnb.NBGlobal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNbGlobal indicates an expected call of GetNbGlobal. +func (mr *MockNbGlobalMockRecorder) GetNbGlobal() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNbGlobal", reflect.TypeOf((*MockNbGlobal)(nil).GetNbGlobal)) +} + +// SetAzName mocks base method. +func (m *MockNbGlobal) SetAzName(azName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetAzName", azName) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetAzName indicates an expected call of SetAzName. +func (mr *MockNbGlobalMockRecorder) SetAzName(azName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAzName", reflect.TypeOf((*MockNbGlobal)(nil).SetAzName), azName) +} + +// SetICAutoRoute mocks base method. +func (m *MockNbGlobal) SetICAutoRoute(enable bool, blackList []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetICAutoRoute", enable, blackList) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetICAutoRoute indicates an expected call of SetICAutoRoute. +func (mr *MockNbGlobalMockRecorder) SetICAutoRoute(enable, blackList interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetICAutoRoute", reflect.TypeOf((*MockNbGlobal)(nil).SetICAutoRoute), enable, blackList) +} + +// SetLBCIDR mocks base method. +func (m *MockNbGlobal) SetLBCIDR(serviceCIDR string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLBCIDR", serviceCIDR) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLBCIDR indicates an expected call of SetLBCIDR. +func (mr *MockNbGlobalMockRecorder) SetLBCIDR(serviceCIDR interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLBCIDR", reflect.TypeOf((*MockNbGlobal)(nil).SetLBCIDR), serviceCIDR) +} + +// SetLsDnatModDlDst mocks base method. +func (m *MockNbGlobal) SetLsDnatModDlDst(enabled bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLsDnatModDlDst", enabled) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLsDnatModDlDst indicates an expected call of SetLsDnatModDlDst. +func (mr *MockNbGlobalMockRecorder) SetLsDnatModDlDst(enabled interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLsDnatModDlDst", reflect.TypeOf((*MockNbGlobal)(nil).SetLsDnatModDlDst), enabled) +} + +// SetUseCtInvMatch mocks base method. +func (m *MockNbGlobal) SetUseCtInvMatch() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetUseCtInvMatch") + ret0, _ := ret[0].(error) + return ret0 +} + +// SetUseCtInvMatch indicates an expected call of SetUseCtInvMatch. +func (mr *MockNbGlobalMockRecorder) SetUseCtInvMatch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUseCtInvMatch", reflect.TypeOf((*MockNbGlobal)(nil).SetUseCtInvMatch)) +} + +// UpdateNbGlobal mocks base method. +func (m *MockNbGlobal) UpdateNbGlobal(nbGlobal *ovnnb.NBGlobal, fields ...interface{}) error { + m.ctrl.T.Helper() + varargs := []interface{}{nbGlobal} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateNbGlobal", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNbGlobal indicates an expected call of UpdateNbGlobal. +func (mr *MockNbGlobalMockRecorder) UpdateNbGlobal(nbGlobal interface{}, fields ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{nbGlobal}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNbGlobal", reflect.TypeOf((*MockNbGlobal)(nil).UpdateNbGlobal), varargs...) +} + +// MockLogicalRouter is a mock of LogicalRouter interface. +type MockLogicalRouter struct { + ctrl *gomock.Controller + recorder *MockLogicalRouterMockRecorder +} + +// MockLogicalRouterMockRecorder is the mock recorder for MockLogicalRouter. +type MockLogicalRouterMockRecorder struct { + mock *MockLogicalRouter +} + +// NewMockLogicalRouter creates a new mock instance. +func NewMockLogicalRouter(ctrl *gomock.Controller) *MockLogicalRouter { + mock := &MockLogicalRouter{ctrl: ctrl} + mock.recorder = &MockLogicalRouterMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogicalRouter) EXPECT() *MockLogicalRouterMockRecorder { + return m.recorder +} + +// CreateLogicalRouter mocks base method. +func (m *MockLogicalRouter) CreateLogicalRouter(lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogicalRouter", lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalRouter indicates an expected call of CreateLogicalRouter. +func (mr *MockLogicalRouterMockRecorder) CreateLogicalRouter(lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalRouter", reflect.TypeOf((*MockLogicalRouter)(nil).CreateLogicalRouter), lrName) +} + +// DeleteLogicalRouter mocks base method. +func (m *MockLogicalRouter) DeleteLogicalRouter(lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouter", lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouter indicates an expected call of DeleteLogicalRouter. +func (mr *MockLogicalRouterMockRecorder) DeleteLogicalRouter(lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouter", reflect.TypeOf((*MockLogicalRouter)(nil).DeleteLogicalRouter), lrName) +} + +// GetLogicalRouter mocks base method. +func (m *MockLogicalRouter) GetLogicalRouter(lrName string, ignoreNotFound bool) (*ovnnb.LogicalRouter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalRouter", lrName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LogicalRouter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalRouter indicates an expected call of GetLogicalRouter. +func (mr *MockLogicalRouterMockRecorder) GetLogicalRouter(lrName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalRouter", reflect.TypeOf((*MockLogicalRouter)(nil).GetLogicalRouter), lrName, ignoreNotFound) +} + +// ListLogicalRouter mocks base method. +func (m *MockLogicalRouter) ListLogicalRouter(needVendorFilter bool, filter func(*ovnnb.LogicalRouter) bool) ([]ovnnb.LogicalRouter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalRouter", needVendorFilter, filter) + ret0, _ := ret[0].([]ovnnb.LogicalRouter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalRouter indicates an expected call of ListLogicalRouter. +func (mr *MockLogicalRouterMockRecorder) ListLogicalRouter(needVendorFilter, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalRouter", reflect.TypeOf((*MockLogicalRouter)(nil).ListLogicalRouter), needVendorFilter, filter) +} + +// LogicalRouterExists mocks base method. +func (m *MockLogicalRouter) LogicalRouterExists(name string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalRouterExists", name) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalRouterExists indicates an expected call of LogicalRouterExists. +func (mr *MockLogicalRouterMockRecorder) LogicalRouterExists(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalRouterExists", reflect.TypeOf((*MockLogicalRouter)(nil).LogicalRouterExists), name) +} + +// MockLogicalRouterPort is a mock of LogicalRouterPort interface. +type MockLogicalRouterPort struct { + ctrl *gomock.Controller + recorder *MockLogicalRouterPortMockRecorder +} + +// MockLogicalRouterPortMockRecorder is the mock recorder for MockLogicalRouterPort. +type MockLogicalRouterPortMockRecorder struct { + mock *MockLogicalRouterPort +} + +// NewMockLogicalRouterPort creates a new mock instance. +func NewMockLogicalRouterPort(ctrl *gomock.Controller) *MockLogicalRouterPort { + mock := &MockLogicalRouterPort{ctrl: ctrl} + mock.recorder = &MockLogicalRouterPortMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogicalRouterPort) EXPECT() *MockLogicalRouterPortMockRecorder { + return m.recorder +} + +// CreateLogicalRouterPort mocks base method. +func (m *MockLogicalRouterPort) CreateLogicalRouterPort(lrName, lrpName, mac string, networks []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogicalRouterPort", lrName, lrpName, mac, networks) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalRouterPort indicates an expected call of CreateLogicalRouterPort. +func (mr *MockLogicalRouterPortMockRecorder) CreateLogicalRouterPort(lrName, lrpName, mac, networks interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalRouterPort", reflect.TypeOf((*MockLogicalRouterPort)(nil).CreateLogicalRouterPort), lrName, lrpName, mac, networks) +} + +// CreatePeerRouterPort mocks base method. +func (m *MockLogicalRouterPort) CreatePeerRouterPort(localRouter, remoteRouter, localRouterPortIP string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatePeerRouterPort", localRouter, remoteRouter, localRouterPortIP) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreatePeerRouterPort indicates an expected call of CreatePeerRouterPort. +func (mr *MockLogicalRouterPortMockRecorder) CreatePeerRouterPort(localRouter, remoteRouter, localRouterPortIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePeerRouterPort", reflect.TypeOf((*MockLogicalRouterPort)(nil).CreatePeerRouterPort), localRouter, remoteRouter, localRouterPortIP) +} + +// DeleteLogicalRouterPort mocks base method. +func (m *MockLogicalRouterPort) DeleteLogicalRouterPort(lrpName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPort", lrpName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPort indicates an expected call of DeleteLogicalRouterPort. +func (mr *MockLogicalRouterPortMockRecorder) DeleteLogicalRouterPort(lrpName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPort", reflect.TypeOf((*MockLogicalRouterPort)(nil).DeleteLogicalRouterPort), lrpName) +} + +// DeleteLogicalRouterPorts mocks base method. +func (m *MockLogicalRouterPort) DeleteLogicalRouterPorts(externalIDs map[string]string, filter func(*ovnnb.LogicalRouterPort) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPorts", externalIDs, filter) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPorts indicates an expected call of DeleteLogicalRouterPorts. +func (mr *MockLogicalRouterPortMockRecorder) DeleteLogicalRouterPorts(externalIDs, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPorts", reflect.TypeOf((*MockLogicalRouterPort)(nil).DeleteLogicalRouterPorts), externalIDs, filter) +} + +// GetLogicalRouterPort mocks base method. +func (m *MockLogicalRouterPort) GetLogicalRouterPort(lrpName string, ignoreNotFound bool) (*ovnnb.LogicalRouterPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalRouterPort", lrpName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LogicalRouterPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalRouterPort indicates an expected call of GetLogicalRouterPort. +func (mr *MockLogicalRouterPortMockRecorder) GetLogicalRouterPort(lrpName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalRouterPort", reflect.TypeOf((*MockLogicalRouterPort)(nil).GetLogicalRouterPort), lrpName, ignoreNotFound) +} + +// ListLogicalRouterPorts mocks base method. +func (m *MockLogicalRouterPort) ListLogicalRouterPorts(externalIDs map[string]string, filter func(*ovnnb.LogicalRouterPort) bool) ([]ovnnb.LogicalRouterPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalRouterPorts", externalIDs, filter) + ret0, _ := ret[0].([]ovnnb.LogicalRouterPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalRouterPorts indicates an expected call of ListLogicalRouterPorts. +func (mr *MockLogicalRouterPortMockRecorder) ListLogicalRouterPorts(externalIDs, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalRouterPorts", reflect.TypeOf((*MockLogicalRouterPort)(nil).ListLogicalRouterPorts), externalIDs, filter) +} + +// LogicalRouterPortExists mocks base method. +func (m *MockLogicalRouterPort) LogicalRouterPortExists(lrpName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalRouterPortExists", lrpName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalRouterPortExists indicates an expected call of LogicalRouterPortExists. +func (mr *MockLogicalRouterPortMockRecorder) LogicalRouterPortExists(lrpName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalRouterPortExists", reflect.TypeOf((*MockLogicalRouterPort)(nil).LogicalRouterPortExists), lrpName) +} + +// UpdateLogicalRouterPortRA mocks base method. +func (m *MockLogicalRouterPort) UpdateLogicalRouterPortRA(lrpName, ipv6RAConfigsStr string, enableIPv6RA bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLogicalRouterPortRA", lrpName, ipv6RAConfigsStr, enableIPv6RA) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateLogicalRouterPortRA indicates an expected call of UpdateLogicalRouterPortRA. +func (mr *MockLogicalRouterPortMockRecorder) UpdateLogicalRouterPortRA(lrpName, ipv6RAConfigsStr, enableIPv6RA interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLogicalRouterPortRA", reflect.TypeOf((*MockLogicalRouterPort)(nil).UpdateLogicalRouterPortRA), lrpName, ipv6RAConfigsStr, enableIPv6RA) +} + +// MockLogicalSwitch is a mock of LogicalSwitch interface. +type MockLogicalSwitch struct { + ctrl *gomock.Controller + recorder *MockLogicalSwitchMockRecorder +} + +// MockLogicalSwitchMockRecorder is the mock recorder for MockLogicalSwitch. +type MockLogicalSwitchMockRecorder struct { + mock *MockLogicalSwitch +} + +// NewMockLogicalSwitch creates a new mock instance. +func NewMockLogicalSwitch(ctrl *gomock.Controller) *MockLogicalSwitch { + mock := &MockLogicalSwitch{ctrl: ctrl} + mock.recorder = &MockLogicalSwitchMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogicalSwitch) EXPECT() *MockLogicalSwitchMockRecorder { + return m.recorder +} + +// CreateBareLogicalSwitch mocks base method. +func (m *MockLogicalSwitch) CreateBareLogicalSwitch(lsName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBareLogicalSwitch", lsName) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateBareLogicalSwitch indicates an expected call of CreateBareLogicalSwitch. +func (mr *MockLogicalSwitchMockRecorder) CreateBareLogicalSwitch(lsName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBareLogicalSwitch", reflect.TypeOf((*MockLogicalSwitch)(nil).CreateBareLogicalSwitch), lsName) +} + +// CreateLogicalSwitch mocks base method. +func (m *MockLogicalSwitch) CreateLogicalSwitch(lsName, lrName, cidrBlock, gateway string, needRouter, randomAllocateGW bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogicalSwitch", lsName, lrName, cidrBlock, gateway, needRouter, randomAllocateGW) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalSwitch indicates an expected call of CreateLogicalSwitch. +func (mr *MockLogicalSwitchMockRecorder) CreateLogicalSwitch(lsName, lrName, cidrBlock, gateway, needRouter, randomAllocateGW interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalSwitch", reflect.TypeOf((*MockLogicalSwitch)(nil).CreateLogicalSwitch), lsName, lrName, cidrBlock, gateway, needRouter, randomAllocateGW) +} + +// DeleteLogicalSwitch mocks base method. +func (m *MockLogicalSwitch) DeleteLogicalSwitch(lsName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalSwitch", lsName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalSwitch indicates an expected call of DeleteLogicalSwitch. +func (mr *MockLogicalSwitchMockRecorder) DeleteLogicalSwitch(lsName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalSwitch", reflect.TypeOf((*MockLogicalSwitch)(nil).DeleteLogicalSwitch), lsName) +} + +// ListLogicalSwitch mocks base method. +func (m *MockLogicalSwitch) ListLogicalSwitch(needVendorFilter bool, filter func(*ovnnb.LogicalSwitch) bool) ([]ovnnb.LogicalSwitch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalSwitch", needVendorFilter, filter) + ret0, _ := ret[0].([]ovnnb.LogicalSwitch) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalSwitch indicates an expected call of ListLogicalSwitch. +func (mr *MockLogicalSwitchMockRecorder) ListLogicalSwitch(needVendorFilter, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalSwitch", reflect.TypeOf((*MockLogicalSwitch)(nil).ListLogicalSwitch), needVendorFilter, filter) +} + +// LogicalSwitchExists mocks base method. +func (m *MockLogicalSwitch) LogicalSwitchExists(lsName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalSwitchExists", lsName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalSwitchExists indicates an expected call of LogicalSwitchExists. +func (mr *MockLogicalSwitchMockRecorder) LogicalSwitchExists(lsName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalSwitchExists", reflect.TypeOf((*MockLogicalSwitch)(nil).LogicalSwitchExists), lsName) +} + +// LogicalSwitchUpdateLoadBalancers mocks base method. +func (m *MockLogicalSwitch) LogicalSwitchUpdateLoadBalancers(lsName string, op ovsdb.Mutator, lbNames ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{lsName, op} + for _, a := range lbNames { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "LogicalSwitchUpdateLoadBalancers", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// LogicalSwitchUpdateLoadBalancers indicates an expected call of LogicalSwitchUpdateLoadBalancers. +func (mr *MockLogicalSwitchMockRecorder) LogicalSwitchUpdateLoadBalancers(lsName, op interface{}, lbNames ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{lsName, op}, lbNames...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalSwitchUpdateLoadBalancers", reflect.TypeOf((*MockLogicalSwitch)(nil).LogicalSwitchUpdateLoadBalancers), varargs...) +} + +// MockLogicalSwitchPort is a mock of LogicalSwitchPort interface. +type MockLogicalSwitchPort struct { + ctrl *gomock.Controller + recorder *MockLogicalSwitchPortMockRecorder +} + +// MockLogicalSwitchPortMockRecorder is the mock recorder for MockLogicalSwitchPort. +type MockLogicalSwitchPortMockRecorder struct { + mock *MockLogicalSwitchPort +} + +// NewMockLogicalSwitchPort creates a new mock instance. +func NewMockLogicalSwitchPort(ctrl *gomock.Controller) *MockLogicalSwitchPort { + mock := &MockLogicalSwitchPort{ctrl: ctrl} + mock.recorder = &MockLogicalSwitchPortMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogicalSwitchPort) EXPECT() *MockLogicalSwitchPortMockRecorder { + return m.recorder +} + +// CreateBareLogicalSwitchPort mocks base method. +func (m *MockLogicalSwitchPort) CreateBareLogicalSwitchPort(lsName, lspName, ip, mac string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBareLogicalSwitchPort", lsName, lspName, ip, mac) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateBareLogicalSwitchPort indicates an expected call of CreateBareLogicalSwitchPort. +func (mr *MockLogicalSwitchPortMockRecorder) CreateBareLogicalSwitchPort(lsName, lspName, ip, mac interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBareLogicalSwitchPort", reflect.TypeOf((*MockLogicalSwitchPort)(nil).CreateBareLogicalSwitchPort), lsName, lspName, ip, mac) +} + +// CreateLocalnetLogicalSwitchPort mocks base method. +func (m *MockLogicalSwitchPort) CreateLocalnetLogicalSwitchPort(lsName, lspName, provider string, vlanID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLocalnetLogicalSwitchPort", lsName, lspName, provider, vlanID) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLocalnetLogicalSwitchPort indicates an expected call of CreateLocalnetLogicalSwitchPort. +func (mr *MockLogicalSwitchPortMockRecorder) CreateLocalnetLogicalSwitchPort(lsName, lspName, provider, vlanID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLocalnetLogicalSwitchPort", reflect.TypeOf((*MockLogicalSwitchPort)(nil).CreateLocalnetLogicalSwitchPort), lsName, lspName, provider, vlanID) +} + +// CreateLogicalSwitchPort mocks base method. +func (m *MockLogicalSwitchPort) CreateLogicalSwitchPort(lsName, lspName, ip, mac, podName, namespace string, portSecurity bool, securityGroups, vips string, enableDHCP bool, dhcpOptions *ovs.DHCPOptionsUUIDs, vpc string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogicalSwitchPort", lsName, lspName, ip, mac, podName, namespace, portSecurity, securityGroups, vips, enableDHCP, dhcpOptions, vpc) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalSwitchPort indicates an expected call of CreateLogicalSwitchPort. +func (mr *MockLogicalSwitchPortMockRecorder) CreateLogicalSwitchPort(lsName, lspName, ip, mac, podName, namespace, portSecurity, securityGroups, vips, enableDHCP, dhcpOptions, vpc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalSwitchPort", reflect.TypeOf((*MockLogicalSwitchPort)(nil).CreateLogicalSwitchPort), lsName, lspName, ip, mac, podName, namespace, portSecurity, securityGroups, vips, enableDHCP, dhcpOptions, vpc) +} + +// CreateVirtualLogicalSwitchPorts mocks base method. +func (m *MockLogicalSwitchPort) CreateVirtualLogicalSwitchPorts(lsName string, ips ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{lsName} + for _, a := range ips { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateVirtualLogicalSwitchPorts", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateVirtualLogicalSwitchPorts indicates an expected call of CreateVirtualLogicalSwitchPorts. +func (mr *MockLogicalSwitchPortMockRecorder) CreateVirtualLogicalSwitchPorts(lsName interface{}, ips ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{lsName}, ips...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVirtualLogicalSwitchPorts", reflect.TypeOf((*MockLogicalSwitchPort)(nil).CreateVirtualLogicalSwitchPorts), varargs...) +} + +// DeleteLogicalSwitchPort mocks base method. +func (m *MockLogicalSwitchPort) DeleteLogicalSwitchPort(lspName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalSwitchPort", lspName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalSwitchPort indicates an expected call of DeleteLogicalSwitchPort. +func (mr *MockLogicalSwitchPortMockRecorder) DeleteLogicalSwitchPort(lspName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalSwitchPort", reflect.TypeOf((*MockLogicalSwitchPort)(nil).DeleteLogicalSwitchPort), lspName) +} + +// EnablePortLayer2forward mocks base method. +func (m *MockLogicalSwitchPort) EnablePortLayer2forward(lspName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnablePortLayer2forward", lspName) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnablePortLayer2forward indicates an expected call of EnablePortLayer2forward. +func (mr *MockLogicalSwitchPortMockRecorder) EnablePortLayer2forward(lspName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnablePortLayer2forward", reflect.TypeOf((*MockLogicalSwitchPort)(nil).EnablePortLayer2forward), lspName) +} + +// GetLogicalSwitchPort mocks base method. +func (m *MockLogicalSwitchPort) GetLogicalSwitchPort(lspName string, ignoreNotFound bool) (*ovnnb.LogicalSwitchPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalSwitchPort", lspName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LogicalSwitchPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalSwitchPort indicates an expected call of GetLogicalSwitchPort. +func (mr *MockLogicalSwitchPortMockRecorder) GetLogicalSwitchPort(lspName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalSwitchPort", reflect.TypeOf((*MockLogicalSwitchPort)(nil).GetLogicalSwitchPort), lspName, ignoreNotFound) +} + +// ListLogicalSwitchPorts mocks base method. +func (m *MockLogicalSwitchPort) ListLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string, filter func(*ovnnb.LogicalSwitchPort) bool) ([]ovnnb.LogicalSwitchPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalSwitchPorts", needVendorFilter, externalIDs, filter) + ret0, _ := ret[0].([]ovnnb.LogicalSwitchPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalSwitchPorts indicates an expected call of ListLogicalSwitchPorts. +func (mr *MockLogicalSwitchPortMockRecorder) ListLogicalSwitchPorts(needVendorFilter, externalIDs, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalSwitchPorts", reflect.TypeOf((*MockLogicalSwitchPort)(nil).ListLogicalSwitchPorts), needVendorFilter, externalIDs, filter) +} + +// ListNormalLogicalSwitchPorts mocks base method. +func (m *MockLogicalSwitchPort) ListNormalLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.LogicalSwitchPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNormalLogicalSwitchPorts", needVendorFilter, externalIDs) + ret0, _ := ret[0].([]ovnnb.LogicalSwitchPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListNormalLogicalSwitchPorts indicates an expected call of ListNormalLogicalSwitchPorts. +func (mr *MockLogicalSwitchPortMockRecorder) ListNormalLogicalSwitchPorts(needVendorFilter, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNormalLogicalSwitchPorts", reflect.TypeOf((*MockLogicalSwitchPort)(nil).ListNormalLogicalSwitchPorts), needVendorFilter, externalIDs) +} + +// LogicalSwitchPortExists mocks base method. +func (m *MockLogicalSwitchPort) LogicalSwitchPortExists(name string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalSwitchPortExists", name) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalSwitchPortExists indicates an expected call of LogicalSwitchPortExists. +func (mr *MockLogicalSwitchPortMockRecorder) LogicalSwitchPortExists(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalSwitchPortExists", reflect.TypeOf((*MockLogicalSwitchPort)(nil).LogicalSwitchPortExists), name) +} + +// SetLogicalSwitchPortExternalIds mocks base method. +func (m *MockLogicalSwitchPort) SetLogicalSwitchPortExternalIds(lspName string, externalIds map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPortExternalIds", lspName, externalIds) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortExternalIds indicates an expected call of SetLogicalSwitchPortExternalIds. +func (mr *MockLogicalSwitchPortMockRecorder) SetLogicalSwitchPortExternalIds(lspName, externalIds interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortExternalIds", reflect.TypeOf((*MockLogicalSwitchPort)(nil).SetLogicalSwitchPortExternalIds), lspName, externalIds) +} + +// SetLogicalSwitchPortSecurity mocks base method. +func (m *MockLogicalSwitchPort) SetLogicalSwitchPortSecurity(portSecurity bool, lspName, mac, ips, vips string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPortSecurity", portSecurity, lspName, mac, ips, vips) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortSecurity indicates an expected call of SetLogicalSwitchPortSecurity. +func (mr *MockLogicalSwitchPortMockRecorder) SetLogicalSwitchPortSecurity(portSecurity, lspName, mac, ips, vips interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortSecurity", reflect.TypeOf((*MockLogicalSwitchPort)(nil).SetLogicalSwitchPortSecurity), portSecurity, lspName, mac, ips, vips) +} + +// SetLogicalSwitchPortVirtualParents mocks base method. +func (m *MockLogicalSwitchPort) SetLogicalSwitchPortVirtualParents(lsName, parents string, ips ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{lsName, parents} + for _, a := range ips { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SetLogicalSwitchPortVirtualParents", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortVirtualParents indicates an expected call of SetLogicalSwitchPortVirtualParents. +func (mr *MockLogicalSwitchPortMockRecorder) SetLogicalSwitchPortVirtualParents(lsName, parents interface{}, ips ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{lsName, parents}, ips...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortVirtualParents", reflect.TypeOf((*MockLogicalSwitchPort)(nil).SetLogicalSwitchPortVirtualParents), varargs...) +} + +// SetLogicalSwitchPortVlanTag mocks base method. +func (m *MockLogicalSwitchPort) SetLogicalSwitchPortVlanTag(lspName string, vlanID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPortVlanTag", lspName, vlanID) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortVlanTag indicates an expected call of SetLogicalSwitchPortVlanTag. +func (mr *MockLogicalSwitchPortMockRecorder) SetLogicalSwitchPortVlanTag(lspName, vlanID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortVlanTag", reflect.TypeOf((*MockLogicalSwitchPort)(nil).SetLogicalSwitchPortVlanTag), lspName, vlanID) +} + +// SetLogicalSwitchPortsSecurityGroup mocks base method. +func (m *MockLogicalSwitchPort) SetLogicalSwitchPortsSecurityGroup(sgName, op string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPortsSecurityGroup", sgName, op) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortsSecurityGroup indicates an expected call of SetLogicalSwitchPortsSecurityGroup. +func (mr *MockLogicalSwitchPortMockRecorder) SetLogicalSwitchPortsSecurityGroup(sgName, op interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortsSecurityGroup", reflect.TypeOf((*MockLogicalSwitchPort)(nil).SetLogicalSwitchPortsSecurityGroup), sgName, op) +} + +// UpdateLogicalSwitchAcl mocks base method. +func (m *MockLogicalSwitchPort) UpdateLogicalSwitchAcl(lsName string, subnetAcls []v1.Acl) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLogicalSwitchAcl", lsName, subnetAcls) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateLogicalSwitchAcl indicates an expected call of UpdateLogicalSwitchAcl. +func (mr *MockLogicalSwitchPortMockRecorder) UpdateLogicalSwitchAcl(lsName, subnetAcls interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLogicalSwitchAcl", reflect.TypeOf((*MockLogicalSwitchPort)(nil).UpdateLogicalSwitchAcl), lsName, subnetAcls) +} + +// MockLoadBalancer is a mock of LoadBalancer interface. +type MockLoadBalancer struct { + ctrl *gomock.Controller + recorder *MockLoadBalancerMockRecorder +} + +// MockLoadBalancerMockRecorder is the mock recorder for MockLoadBalancer. +type MockLoadBalancerMockRecorder struct { + mock *MockLoadBalancer +} + +// NewMockLoadBalancer creates a new mock instance. +func NewMockLoadBalancer(ctrl *gomock.Controller) *MockLoadBalancer { + mock := &MockLoadBalancer{ctrl: ctrl} + mock.recorder = &MockLoadBalancerMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLoadBalancer) EXPECT() *MockLoadBalancerMockRecorder { + return m.recorder +} + +// CreateLoadBalancer mocks base method. +func (m *MockLoadBalancer) CreateLoadBalancer(lbName, protocol, selectFields string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLoadBalancer", lbName, protocol, selectFields) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLoadBalancer indicates an expected call of CreateLoadBalancer. +func (mr *MockLoadBalancerMockRecorder) CreateLoadBalancer(lbName, protocol, selectFields interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLoadBalancer", reflect.TypeOf((*MockLoadBalancer)(nil).CreateLoadBalancer), lbName, protocol, selectFields) +} + +// DeleteLoadBalancers mocks base method. +func (m *MockLoadBalancer) DeleteLoadBalancers(filter func(*ovnnb.LoadBalancer) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLoadBalancers", filter) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLoadBalancers indicates an expected call of DeleteLoadBalancers. +func (mr *MockLoadBalancerMockRecorder) DeleteLoadBalancers(filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLoadBalancers", reflect.TypeOf((*MockLoadBalancer)(nil).DeleteLoadBalancers), filter) +} + +// GetLoadBalancer mocks base method. +func (m *MockLoadBalancer) GetLoadBalancer(lbName string, ignoreNotFound bool) (*ovnnb.LoadBalancer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLoadBalancer", lbName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LoadBalancer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLoadBalancer indicates an expected call of GetLoadBalancer. +func (mr *MockLoadBalancerMockRecorder) GetLoadBalancer(lbName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoadBalancer", reflect.TypeOf((*MockLoadBalancer)(nil).GetLoadBalancer), lbName, ignoreNotFound) +} + +// ListLoadBalancers mocks base method. +func (m *MockLoadBalancer) ListLoadBalancers(filter func(*ovnnb.LoadBalancer) bool) ([]ovnnb.LoadBalancer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLoadBalancers", filter) + ret0, _ := ret[0].([]ovnnb.LoadBalancer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLoadBalancers indicates an expected call of ListLoadBalancers. +func (mr *MockLoadBalancerMockRecorder) ListLoadBalancers(filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLoadBalancers", reflect.TypeOf((*MockLoadBalancer)(nil).ListLoadBalancers), filter) +} + +// LoadBalancerAddVips mocks base method. +func (m *MockLoadBalancer) LoadBalancerAddVips(lbName string, vips map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadBalancerAddVips", lbName, vips) + ret0, _ := ret[0].(error) + return ret0 +} + +// LoadBalancerAddVips indicates an expected call of LoadBalancerAddVips. +func (mr *MockLoadBalancerMockRecorder) LoadBalancerAddVips(lbName, vips interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerAddVips", reflect.TypeOf((*MockLoadBalancer)(nil).LoadBalancerAddVips), lbName, vips) +} + +// LoadBalancerDeleteVips mocks base method. +func (m *MockLoadBalancer) LoadBalancerDeleteVips(lbName string, vips map[string]struct{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadBalancerDeleteVips", lbName, vips) + ret0, _ := ret[0].(error) + return ret0 +} + +// LoadBalancerDeleteVips indicates an expected call of LoadBalancerDeleteVips. +func (mr *MockLoadBalancerMockRecorder) LoadBalancerDeleteVips(lbName, vips interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerDeleteVips", reflect.TypeOf((*MockLoadBalancer)(nil).LoadBalancerDeleteVips), lbName, vips) +} + +// LoadBalancerExists mocks base method. +func (m *MockLoadBalancer) LoadBalancerExists(lbName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadBalancerExists", lbName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadBalancerExists indicates an expected call of LoadBalancerExists. +func (mr *MockLoadBalancerMockRecorder) LoadBalancerExists(lbName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerExists", reflect.TypeOf((*MockLoadBalancer)(nil).LoadBalancerExists), lbName) +} + +// SetLoadBalancerAffinityTimeout mocks base method. +func (m *MockLoadBalancer) SetLoadBalancerAffinityTimeout(lbName string, timeout int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLoadBalancerAffinityTimeout", lbName, timeout) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLoadBalancerAffinityTimeout indicates an expected call of SetLoadBalancerAffinityTimeout. +func (mr *MockLoadBalancerMockRecorder) SetLoadBalancerAffinityTimeout(lbName, timeout interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLoadBalancerAffinityTimeout", reflect.TypeOf((*MockLoadBalancer)(nil).SetLoadBalancerAffinityTimeout), lbName, timeout) +} + +// MockPortGroup is a mock of PortGroup interface. +type MockPortGroup struct { + ctrl *gomock.Controller + recorder *MockPortGroupMockRecorder +} + +// MockPortGroupMockRecorder is the mock recorder for MockPortGroup. +type MockPortGroupMockRecorder struct { + mock *MockPortGroup +} + +// NewMockPortGroup creates a new mock instance. +func NewMockPortGroup(ctrl *gomock.Controller) *MockPortGroup { + mock := &MockPortGroup{ctrl: ctrl} + mock.recorder = &MockPortGroupMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockPortGroup) EXPECT() *MockPortGroupMockRecorder { + return m.recorder +} + +// CreatePortGroup mocks base method. +func (m *MockPortGroup) CreatePortGroup(pgName string, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatePortGroup", pgName, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreatePortGroup indicates an expected call of CreatePortGroup. +func (mr *MockPortGroupMockRecorder) CreatePortGroup(pgName, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePortGroup", reflect.TypeOf((*MockPortGroup)(nil).CreatePortGroup), pgName, externalIDs) +} + +// DeletePortGroup mocks base method. +func (m *MockPortGroup) DeletePortGroup(pgName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePortGroup", pgName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePortGroup indicates an expected call of DeletePortGroup. +func (mr *MockPortGroupMockRecorder) DeletePortGroup(pgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePortGroup", reflect.TypeOf((*MockPortGroup)(nil).DeletePortGroup), pgName) +} + +// GetPortGroup mocks base method. +func (m *MockPortGroup) GetPortGroup(pgName string, ignoreNotFound bool) (*ovnnb.PortGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPortGroup", pgName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.PortGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPortGroup indicates an expected call of GetPortGroup. +func (mr *MockPortGroupMockRecorder) GetPortGroup(pgName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPortGroup", reflect.TypeOf((*MockPortGroup)(nil).GetPortGroup), pgName, ignoreNotFound) +} + +// ListPortGroups mocks base method. +func (m *MockPortGroup) ListPortGroups(externalIDs map[string]string) ([]ovnnb.PortGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListPortGroups", externalIDs) + ret0, _ := ret[0].([]ovnnb.PortGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListPortGroups indicates an expected call of ListPortGroups. +func (mr *MockPortGroupMockRecorder) ListPortGroups(externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPortGroups", reflect.TypeOf((*MockPortGroup)(nil).ListPortGroups), externalIDs) +} + +// PortGroupAddPorts mocks base method. +func (m *MockPortGroup) PortGroupAddPorts(pgName string, lspNames ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{pgName} + for _, a := range lspNames { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PortGroupAddPorts", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// PortGroupAddPorts indicates an expected call of PortGroupAddPorts. +func (mr *MockPortGroupMockRecorder) PortGroupAddPorts(pgName interface{}, lspNames ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{pgName}, lspNames...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortGroupAddPorts", reflect.TypeOf((*MockPortGroup)(nil).PortGroupAddPorts), varargs...) +} + +// PortGroupExists mocks base method. +func (m *MockPortGroup) PortGroupExists(pgName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortGroupExists", pgName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PortGroupExists indicates an expected call of PortGroupExists. +func (mr *MockPortGroupMockRecorder) PortGroupExists(pgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortGroupExists", reflect.TypeOf((*MockPortGroup)(nil).PortGroupExists), pgName) +} + +// PortGroupRemovePorts mocks base method. +func (m *MockPortGroup) PortGroupRemovePorts(pgName string, lspNames ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{pgName} + for _, a := range lspNames { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PortGroupRemovePorts", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// PortGroupRemovePorts indicates an expected call of PortGroupRemovePorts. +func (mr *MockPortGroupMockRecorder) PortGroupRemovePorts(pgName interface{}, lspNames ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{pgName}, lspNames...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortGroupRemovePorts", reflect.TypeOf((*MockPortGroup)(nil).PortGroupRemovePorts), varargs...) +} + +// PortGroupResetPorts mocks base method. +func (m *MockPortGroup) PortGroupResetPorts(pgName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortGroupResetPorts", pgName) + ret0, _ := ret[0].(error) + return ret0 +} + +// PortGroupResetPorts indicates an expected call of PortGroupResetPorts. +func (mr *MockPortGroupMockRecorder) PortGroupResetPorts(pgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortGroupResetPorts", reflect.TypeOf((*MockPortGroup)(nil).PortGroupResetPorts), pgName) +} + +// MockACL is a mock of ACL interface. +type MockACL struct { + ctrl *gomock.Controller + recorder *MockACLMockRecorder +} + +// MockACLMockRecorder is the mock recorder for MockACL. +type MockACLMockRecorder struct { + mock *MockACL +} + +// NewMockACL creates a new mock instance. +func NewMockACL(ctrl *gomock.Controller) *MockACL { + mock := &MockACL{ctrl: ctrl} + mock.recorder = &MockACLMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockACL) EXPECT() *MockACLMockRecorder { + return m.recorder +} + +// CreateEgressAcl mocks base method. +func (m *MockACL) CreateEgressAcl(pgName, asEgressName, asExceptName, protocol string, npp []v10.NetworkPolicyPort) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateEgressAcl", pgName, asEgressName, asExceptName, protocol, npp) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateEgressAcl indicates an expected call of CreateEgressAcl. +func (mr *MockACLMockRecorder) CreateEgressAcl(pgName, asEgressName, asExceptName, protocol, npp interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEgressAcl", reflect.TypeOf((*MockACL)(nil).CreateEgressAcl), pgName, asEgressName, asExceptName, protocol, npp) +} + +// CreateGatewayAcl mocks base method. +func (m *MockACL) CreateGatewayAcl(pgName, gateway string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateGatewayAcl", pgName, gateway) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateGatewayAcl indicates an expected call of CreateGatewayAcl. +func (mr *MockACLMockRecorder) CreateGatewayAcl(pgName, gateway interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGatewayAcl", reflect.TypeOf((*MockACL)(nil).CreateGatewayAcl), pgName, gateway) +} + +// CreateIngressAcl mocks base method. +func (m *MockACL) CreateIngressAcl(pgName, asIngressName, asExceptName, protocol string, npp []v10.NetworkPolicyPort) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateIngressAcl", pgName, asIngressName, asExceptName, protocol, npp) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateIngressAcl indicates an expected call of CreateIngressAcl. +func (mr *MockACLMockRecorder) CreateIngressAcl(pgName, asIngressName, asExceptName, protocol, npp interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateIngressAcl", reflect.TypeOf((*MockACL)(nil).CreateIngressAcl), pgName, asIngressName, asExceptName, protocol, npp) +} + +// CreateNodeAcl mocks base method. +func (m *MockACL) CreateNodeAcl(pgName, nodeIp string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNodeAcl", pgName, nodeIp) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNodeAcl indicates an expected call of CreateNodeAcl. +func (mr *MockACLMockRecorder) CreateNodeAcl(pgName, nodeIp interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNodeAcl", reflect.TypeOf((*MockACL)(nil).CreateNodeAcl), pgName, nodeIp) +} + +// CreateSgDenyAllAcl mocks base method. +func (m *MockACL) CreateSgDenyAllAcl(sgName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSgDenyAllAcl", sgName) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateSgDenyAllAcl indicates an expected call of CreateSgDenyAllAcl. +func (mr *MockACLMockRecorder) CreateSgDenyAllAcl(sgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSgDenyAllAcl", reflect.TypeOf((*MockACL)(nil).CreateSgDenyAllAcl), sgName) +} + +// DeleteAcls mocks base method. +func (m *MockACL) DeleteAcls(parentName, parentType, direction string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAcls", parentName, parentType, direction) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAcls indicates an expected call of DeleteAcls. +func (mr *MockACLMockRecorder) DeleteAcls(parentName, parentType, direction interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAcls", reflect.TypeOf((*MockACL)(nil).DeleteAcls), parentName, parentType, direction) +} + +// SetAclLog mocks base method. +func (m *MockACL) SetAclLog(pgName string, logEnable, isIngress bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetAclLog", pgName, logEnable, isIngress) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetAclLog indicates an expected call of SetAclLog. +func (mr *MockACLMockRecorder) SetAclLog(pgName, logEnable, isIngress interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAclLog", reflect.TypeOf((*MockACL)(nil).SetAclLog), pgName, logEnable, isIngress) +} + +// SetLogicalSwitchPrivate mocks base method. +func (m *MockACL) SetLogicalSwitchPrivate(lsName, cidrBlock string, allowSubnets []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPrivate", lsName, cidrBlock, allowSubnets) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPrivate indicates an expected call of SetLogicalSwitchPrivate. +func (mr *MockACLMockRecorder) SetLogicalSwitchPrivate(lsName, cidrBlock, allowSubnets interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPrivate", reflect.TypeOf((*MockACL)(nil).SetLogicalSwitchPrivate), lsName, cidrBlock, allowSubnets) +} + +// UpdateSgAcl mocks base method. +func (m *MockACL) UpdateSgAcl(sg *v1.SecurityGroup, direction string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateSgAcl", sg, direction) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateSgAcl indicates an expected call of UpdateSgAcl. +func (mr *MockACLMockRecorder) UpdateSgAcl(sg, direction interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSgAcl", reflect.TypeOf((*MockACL)(nil).UpdateSgAcl), sg, direction) +} + +// MockAddressSet is a mock of AddressSet interface. +type MockAddressSet struct { + ctrl *gomock.Controller + recorder *MockAddressSetMockRecorder +} + +// MockAddressSetMockRecorder is the mock recorder for MockAddressSet. +type MockAddressSetMockRecorder struct { + mock *MockAddressSet +} + +// NewMockAddressSet creates a new mock instance. +func NewMockAddressSet(ctrl *gomock.Controller) *MockAddressSet { + mock := &MockAddressSet{ctrl: ctrl} + mock.recorder = &MockAddressSetMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockAddressSet) EXPECT() *MockAddressSetMockRecorder { + return m.recorder +} + +// AddressSetUpdateAddress mocks base method. +func (m *MockAddressSet) AddressSetUpdateAddress(asName string, addresses ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{asName} + for _, a := range addresses { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddressSetUpdateAddress", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddressSetUpdateAddress indicates an expected call of AddressSetUpdateAddress. +func (mr *MockAddressSetMockRecorder) AddressSetUpdateAddress(asName interface{}, addresses ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{asName}, addresses...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddressSetUpdateAddress", reflect.TypeOf((*MockAddressSet)(nil).AddressSetUpdateAddress), varargs...) +} + +// CreateAddressSet mocks base method. +func (m *MockAddressSet) CreateAddressSet(asName string, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateAddressSet", asName, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateAddressSet indicates an expected call of CreateAddressSet. +func (mr *MockAddressSetMockRecorder) CreateAddressSet(asName, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAddressSet", reflect.TypeOf((*MockAddressSet)(nil).CreateAddressSet), asName, externalIDs) +} + +// DeleteAddressSet mocks base method. +func (m *MockAddressSet) DeleteAddressSet(asName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAddressSet", asName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAddressSet indicates an expected call of DeleteAddressSet. +func (mr *MockAddressSetMockRecorder) DeleteAddressSet(asName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAddressSet", reflect.TypeOf((*MockAddressSet)(nil).DeleteAddressSet), asName) +} + +// DeleteAddressSets mocks base method. +func (m *MockAddressSet) DeleteAddressSets(externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAddressSets", externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAddressSets indicates an expected call of DeleteAddressSets. +func (mr *MockAddressSetMockRecorder) DeleteAddressSets(externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAddressSets", reflect.TypeOf((*MockAddressSet)(nil).DeleteAddressSets), externalIDs) +} + +// ListAddressSets mocks base method. +func (m *MockAddressSet) ListAddressSets(externalIDs map[string]string) ([]ovnnb.AddressSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAddressSets", externalIDs) + ret0, _ := ret[0].([]ovnnb.AddressSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListAddressSets indicates an expected call of ListAddressSets. +func (mr *MockAddressSetMockRecorder) ListAddressSets(externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAddressSets", reflect.TypeOf((*MockAddressSet)(nil).ListAddressSets), externalIDs) +} + +// MockLogicalRouterStaticRoute is a mock of LogicalRouterStaticRoute interface. +type MockLogicalRouterStaticRoute struct { + ctrl *gomock.Controller + recorder *MockLogicalRouterStaticRouteMockRecorder +} + +// MockLogicalRouterStaticRouteMockRecorder is the mock recorder for MockLogicalRouterStaticRoute. +type MockLogicalRouterStaticRouteMockRecorder struct { + mock *MockLogicalRouterStaticRoute +} + +// NewMockLogicalRouterStaticRoute creates a new mock instance. +func NewMockLogicalRouterStaticRoute(ctrl *gomock.Controller) *MockLogicalRouterStaticRoute { + mock := &MockLogicalRouterStaticRoute{ctrl: ctrl} + mock.recorder = &MockLogicalRouterStaticRouteMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogicalRouterStaticRoute) EXPECT() *MockLogicalRouterStaticRouteMockRecorder { + return m.recorder +} + +// AddLogicalRouterStaticRoute mocks base method. +func (m *MockLogicalRouterStaticRoute) AddLogicalRouterStaticRoute(lrName, policy, cidrBlock, nextHops, routeType string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddLogicalRouterStaticRoute", lrName, policy, cidrBlock, nextHops, routeType) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddLogicalRouterStaticRoute indicates an expected call of AddLogicalRouterStaticRoute. +func (mr *MockLogicalRouterStaticRouteMockRecorder) AddLogicalRouterStaticRoute(lrName, policy, cidrBlock, nextHops, routeType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLogicalRouterStaticRoute", reflect.TypeOf((*MockLogicalRouterStaticRoute)(nil).AddLogicalRouterStaticRoute), lrName, policy, cidrBlock, nextHops, routeType) +} + +// ClearLogicalRouterStaticRoute mocks base method. +func (m *MockLogicalRouterStaticRoute) ClearLogicalRouterStaticRoute(lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClearLogicalRouterStaticRoute", lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClearLogicalRouterStaticRoute indicates an expected call of ClearLogicalRouterStaticRoute. +func (mr *MockLogicalRouterStaticRouteMockRecorder) ClearLogicalRouterStaticRoute(lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearLogicalRouterStaticRoute", reflect.TypeOf((*MockLogicalRouterStaticRoute)(nil).ClearLogicalRouterStaticRoute), lrName) +} + +// DeleteLogicalRouterStaticRoute mocks base method. +func (m *MockLogicalRouterStaticRoute) DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterStaticRoute", lrName, policy, prefix, nextHop, routeType) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterStaticRoute indicates an expected call of DeleteLogicalRouterStaticRoute. +func (mr *MockLogicalRouterStaticRouteMockRecorder) DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterStaticRoute", reflect.TypeOf((*MockLogicalRouterStaticRoute)(nil).DeleteLogicalRouterStaticRoute), lrName, policy, prefix, nextHop, routeType) +} + +// GetLogicalRouterRouteByOpts mocks base method. +func (m *MockLogicalRouterStaticRoute) GetLogicalRouterRouteByOpts(key, value string) ([]ovnnb.LogicalRouterStaticRoute, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalRouterRouteByOpts", key, value) + ret0, _ := ret[0].([]ovnnb.LogicalRouterStaticRoute) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalRouterRouteByOpts indicates an expected call of GetLogicalRouterRouteByOpts. +func (mr *MockLogicalRouterStaticRouteMockRecorder) GetLogicalRouterRouteByOpts(key, value interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalRouterRouteByOpts", reflect.TypeOf((*MockLogicalRouterStaticRoute)(nil).GetLogicalRouterRouteByOpts), key, value) +} + +// ListLogicalRouterStaticRoutes mocks base method. +func (m *MockLogicalRouterStaticRoute) ListLogicalRouterStaticRoutes(externalIDs map[string]string) ([]ovnnb.LogicalRouterStaticRoute, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalRouterStaticRoutes", externalIDs) + ret0, _ := ret[0].([]ovnnb.LogicalRouterStaticRoute) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalRouterStaticRoutes indicates an expected call of ListLogicalRouterStaticRoutes. +func (mr *MockLogicalRouterStaticRouteMockRecorder) ListLogicalRouterStaticRoutes(externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalRouterStaticRoutes", reflect.TypeOf((*MockLogicalRouterStaticRoute)(nil).ListLogicalRouterStaticRoutes), externalIDs) +} + +// LogicalRouterStaticRouteExists mocks base method. +func (m *MockLogicalRouterStaticRoute) LogicalRouterStaticRouteExists(lrName, policy, prefix, nextHop, routeType string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalRouterStaticRouteExists", lrName, policy, prefix, nextHop, routeType) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalRouterStaticRouteExists indicates an expected call of LogicalRouterStaticRouteExists. +func (mr *MockLogicalRouterStaticRouteMockRecorder) LogicalRouterStaticRouteExists(lrName, policy, prefix, nextHop, routeType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalRouterStaticRouteExists", reflect.TypeOf((*MockLogicalRouterStaticRoute)(nil).LogicalRouterStaticRouteExists), lrName, policy, prefix, nextHop, routeType) +} + +// MockLogicalRouterPolicy is a mock of LogicalRouterPolicy interface. +type MockLogicalRouterPolicy struct { + ctrl *gomock.Controller + recorder *MockLogicalRouterPolicyMockRecorder +} + +// MockLogicalRouterPolicyMockRecorder is the mock recorder for MockLogicalRouterPolicy. +type MockLogicalRouterPolicyMockRecorder struct { + mock *MockLogicalRouterPolicy +} + +// NewMockLogicalRouterPolicy creates a new mock instance. +func NewMockLogicalRouterPolicy(ctrl *gomock.Controller) *MockLogicalRouterPolicy { + mock := &MockLogicalRouterPolicy{ctrl: ctrl} + mock.recorder = &MockLogicalRouterPolicyMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockLogicalRouterPolicy) EXPECT() *MockLogicalRouterPolicyMockRecorder { + return m.recorder +} + +// AddLogicalRouterPolicy mocks base method. +func (m *MockLogicalRouterPolicy) AddLogicalRouterPolicy(lrName string, priority int, match, action string, nextHops []string, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddLogicalRouterPolicy", lrName, priority, match, action, nextHops, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddLogicalRouterPolicy indicates an expected call of AddLogicalRouterPolicy. +func (mr *MockLogicalRouterPolicyMockRecorder) AddLogicalRouterPolicy(lrName, priority, match, action, nextHops, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLogicalRouterPolicy", reflect.TypeOf((*MockLogicalRouterPolicy)(nil).AddLogicalRouterPolicy), lrName, priority, match, action, nextHops, externalIDs) +} + +// ClearLogicalRouterPolicy mocks base method. +func (m *MockLogicalRouterPolicy) ClearLogicalRouterPolicy(lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClearLogicalRouterPolicy", lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClearLogicalRouterPolicy indicates an expected call of ClearLogicalRouterPolicy. +func (mr *MockLogicalRouterPolicyMockRecorder) ClearLogicalRouterPolicy(lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearLogicalRouterPolicy", reflect.TypeOf((*MockLogicalRouterPolicy)(nil).ClearLogicalRouterPolicy), lrName) +} + +// DeleteLogicalRouterPolicies mocks base method. +func (m *MockLogicalRouterPolicy) DeleteLogicalRouterPolicies(lrName string, priority int, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPolicies", lrName, priority, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPolicies indicates an expected call of DeleteLogicalRouterPolicies. +func (mr *MockLogicalRouterPolicyMockRecorder) DeleteLogicalRouterPolicies(lrName, priority, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPolicies", reflect.TypeOf((*MockLogicalRouterPolicy)(nil).DeleteLogicalRouterPolicies), lrName, priority, externalIDs) +} + +// DeleteLogicalRouterPolicy mocks base method. +func (m *MockLogicalRouterPolicy) DeleteLogicalRouterPolicy(lrName string, priority int, match string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPolicy", lrName, priority, match) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPolicy indicates an expected call of DeleteLogicalRouterPolicy. +func (mr *MockLogicalRouterPolicyMockRecorder) DeleteLogicalRouterPolicy(lrName, priority, match interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPolicy", reflect.TypeOf((*MockLogicalRouterPolicy)(nil).DeleteLogicalRouterPolicy), lrName, priority, match) +} + +// DeleteLogicalRouterPolicyByUUID mocks base method. +func (m *MockLogicalRouterPolicy) DeleteLogicalRouterPolicyByUUID(lrName, uuid string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPolicyByUUID", lrName, uuid) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPolicyByUUID indicates an expected call of DeleteLogicalRouterPolicyByUUID. +func (mr *MockLogicalRouterPolicyMockRecorder) DeleteLogicalRouterPolicyByUUID(lrName, uuid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPolicyByUUID", reflect.TypeOf((*MockLogicalRouterPolicy)(nil).DeleteLogicalRouterPolicyByUUID), lrName, uuid) +} + +// GetLogicalRouterPolicy mocks base method. +func (m *MockLogicalRouterPolicy) GetLogicalRouterPolicy(lrName string, priority int, match string, ignoreNotFound bool) (*ovnnb.LogicalRouterPolicy, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalRouterPolicy", lrName, priority, match, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LogicalRouterPolicy) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalRouterPolicy indicates an expected call of GetLogicalRouterPolicy. +func (mr *MockLogicalRouterPolicyMockRecorder) GetLogicalRouterPolicy(lrName, priority, match, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalRouterPolicy", reflect.TypeOf((*MockLogicalRouterPolicy)(nil).GetLogicalRouterPolicy), lrName, priority, match, ignoreNotFound) +} + +// ListLogicalRouterPolicies mocks base method. +func (m *MockLogicalRouterPolicy) ListLogicalRouterPolicies(priority int, externalIDs map[string]string) ([]ovnnb.LogicalRouterPolicy, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalRouterPolicies", priority, externalIDs) + ret0, _ := ret[0].([]ovnnb.LogicalRouterPolicy) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalRouterPolicies indicates an expected call of ListLogicalRouterPolicies. +func (mr *MockLogicalRouterPolicyMockRecorder) ListLogicalRouterPolicies(priority, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalRouterPolicies", reflect.TypeOf((*MockLogicalRouterPolicy)(nil).ListLogicalRouterPolicies), priority, externalIDs) +} + +// MockNAT is a mock of NAT interface. +type MockNAT struct { + ctrl *gomock.Controller + recorder *MockNATMockRecorder +} + +// MockNATMockRecorder is the mock recorder for MockNAT. +type MockNATMockRecorder struct { + mock *MockNAT +} + +// NewMockNAT creates a new mock instance. +func NewMockNAT(ctrl *gomock.Controller) *MockNAT { + mock := &MockNAT{ctrl: ctrl} + mock.recorder = &MockNATMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockNAT) EXPECT() *MockNATMockRecorder { + return m.recorder +} + +// DeleteNat mocks base method. +func (m *MockNAT) DeleteNat(lrName, natType, externalIP, logicalIP string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteNat", lrName, natType, externalIP, logicalIP) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteNat indicates an expected call of DeleteNat. +func (mr *MockNATMockRecorder) DeleteNat(lrName, natType, externalIP, logicalIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNat", reflect.TypeOf((*MockNAT)(nil).DeleteNat), lrName, natType, externalIP, logicalIP) +} + +// DeleteNats mocks base method. +func (m *MockNAT) DeleteNats(lrName, natType, logicalIP string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteNats", lrName, natType, logicalIP) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteNats indicates an expected call of DeleteNats. +func (mr *MockNATMockRecorder) DeleteNats(lrName, natType, logicalIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNats", reflect.TypeOf((*MockNAT)(nil).DeleteNats), lrName, natType, logicalIP) +} + +// ListNats mocks base method. +func (m *MockNAT) ListNats(natType, logicalIP string, externalIDs map[string]string) ([]ovnnb.NAT, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNats", natType, logicalIP, externalIDs) + ret0, _ := ret[0].([]ovnnb.NAT) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListNats indicates an expected call of ListNats. +func (mr *MockNATMockRecorder) ListNats(natType, logicalIP, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNats", reflect.TypeOf((*MockNAT)(nil).ListNats), natType, logicalIP, externalIDs) +} + +// NatExists mocks base method. +func (m *MockNAT) NatExists(lrName, natType, externalIP, logicalIP string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NatExists", lrName, natType, externalIP, logicalIP) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NatExists indicates an expected call of NatExists. +func (mr *MockNATMockRecorder) NatExists(lrName, natType, externalIP, logicalIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NatExists", reflect.TypeOf((*MockNAT)(nil).NatExists), lrName, natType, externalIP, logicalIP) +} + +// UpdateDnatAndSnat mocks base method. +func (m *MockNAT) UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, gatewayType string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDnatAndSnat", lrName, externalIP, logicalIP, lspName, externalMac, gatewayType) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateDnatAndSnat indicates an expected call of UpdateDnatAndSnat. +func (mr *MockNATMockRecorder) UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, gatewayType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDnatAndSnat", reflect.TypeOf((*MockNAT)(nil).UpdateDnatAndSnat), lrName, externalIP, logicalIP, lspName, externalMac, gatewayType) +} + +// UpdateSnat mocks base method. +func (m *MockNAT) UpdateSnat(lrName, externalIP, logicalIP string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateSnat", lrName, externalIP, logicalIP) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateSnat indicates an expected call of UpdateSnat. +func (mr *MockNATMockRecorder) UpdateSnat(lrName, externalIP, logicalIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSnat", reflect.TypeOf((*MockNAT)(nil).UpdateSnat), lrName, externalIP, logicalIP) +} + +// MockDHCPOptions is a mock of DHCPOptions interface. +type MockDHCPOptions struct { + ctrl *gomock.Controller + recorder *MockDHCPOptionsMockRecorder +} + +// MockDHCPOptionsMockRecorder is the mock recorder for MockDHCPOptions. +type MockDHCPOptionsMockRecorder struct { + mock *MockDHCPOptions +} + +// NewMockDHCPOptions creates a new mock instance. +func NewMockDHCPOptions(ctrl *gomock.Controller) *MockDHCPOptions { + mock := &MockDHCPOptions{ctrl: ctrl} + mock.recorder = &MockDHCPOptionsMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockDHCPOptions) EXPECT() *MockDHCPOptionsMockRecorder { + return m.recorder +} + +// DeleteDHCPOptions mocks base method. +func (m *MockDHCPOptions) DeleteDHCPOptions(lsName, protocol string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDHCPOptions", lsName, protocol) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteDHCPOptions indicates an expected call of DeleteDHCPOptions. +func (mr *MockDHCPOptionsMockRecorder) DeleteDHCPOptions(lsName, protocol interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPOptions", reflect.TypeOf((*MockDHCPOptions)(nil).DeleteDHCPOptions), lsName, protocol) +} + +// DeleteDHCPOptionsByUUIDs mocks base method. +func (m *MockDHCPOptions) DeleteDHCPOptionsByUUIDs(uuidList ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range uuidList { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteDHCPOptionsByUUIDs", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteDHCPOptionsByUUIDs indicates an expected call of DeleteDHCPOptionsByUUIDs. +func (mr *MockDHCPOptionsMockRecorder) DeleteDHCPOptionsByUUIDs(uuidList ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPOptionsByUUIDs", reflect.TypeOf((*MockDHCPOptions)(nil).DeleteDHCPOptionsByUUIDs), uuidList...) +} + +// ListDHCPOptions mocks base method. +func (m *MockDHCPOptions) ListDHCPOptions(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.DHCPOptions, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListDHCPOptions", needVendorFilter, externalIDs) + ret0, _ := ret[0].([]ovnnb.DHCPOptions) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListDHCPOptions indicates an expected call of ListDHCPOptions. +func (mr *MockDHCPOptionsMockRecorder) ListDHCPOptions(needVendorFilter, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDHCPOptions", reflect.TypeOf((*MockDHCPOptions)(nil).ListDHCPOptions), needVendorFilter, externalIDs) +} + +// UpdateDHCPOptions mocks base method. +func (m *MockDHCPOptions) UpdateDHCPOptions(subnet *v1.Subnet) (*ovs.DHCPOptionsUUIDs, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDHCPOptions", subnet) + ret0, _ := ret[0].(*ovs.DHCPOptionsUUIDs) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateDHCPOptions indicates an expected call of UpdateDHCPOptions. +func (mr *MockDHCPOptionsMockRecorder) UpdateDHCPOptions(subnet interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDHCPOptions", reflect.TypeOf((*MockDHCPOptions)(nil).UpdateDHCPOptions), subnet) +} + +// MockOvnClient is a mock of OvnClient interface. +type MockOvnClient struct { + ctrl *gomock.Controller + recorder *MockOvnClientMockRecorder +} + +// MockOvnClientMockRecorder is the mock recorder for MockOvnClient. +type MockOvnClientMockRecorder struct { + mock *MockOvnClient +} + +// NewMockOvnClient creates a new mock instance. +func NewMockOvnClient(ctrl *gomock.Controller) *MockOvnClient { + mock := &MockOvnClient{ctrl: ctrl} + mock.recorder = &MockOvnClientMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockOvnClient) EXPECT() *MockOvnClientMockRecorder { + return m.recorder +} + +// AddLogicalRouterPolicy mocks base method. +func (m *MockOvnClient) AddLogicalRouterPolicy(lrName string, priority int, match, action string, nextHops []string, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddLogicalRouterPolicy", lrName, priority, match, action, nextHops, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddLogicalRouterPolicy indicates an expected call of AddLogicalRouterPolicy. +func (mr *MockOvnClientMockRecorder) AddLogicalRouterPolicy(lrName, priority, match, action, nextHops, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLogicalRouterPolicy", reflect.TypeOf((*MockOvnClient)(nil).AddLogicalRouterPolicy), lrName, priority, match, action, nextHops, externalIDs) +} + +// AddLogicalRouterStaticRoute mocks base method. +func (m *MockOvnClient) AddLogicalRouterStaticRoute(lrName, policy, cidrBlock, nextHops, routeType string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "AddLogicalRouterStaticRoute", lrName, policy, cidrBlock, nextHops, routeType) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddLogicalRouterStaticRoute indicates an expected call of AddLogicalRouterStaticRoute. +func (mr *MockOvnClientMockRecorder) AddLogicalRouterStaticRoute(lrName, policy, cidrBlock, nextHops, routeType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddLogicalRouterStaticRoute", reflect.TypeOf((*MockOvnClient)(nil).AddLogicalRouterStaticRoute), lrName, policy, cidrBlock, nextHops, routeType) +} + +// AddressSetUpdateAddress mocks base method. +func (m *MockOvnClient) AddressSetUpdateAddress(asName string, addresses ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{asName} + for _, a := range addresses { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "AddressSetUpdateAddress", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// AddressSetUpdateAddress indicates an expected call of AddressSetUpdateAddress. +func (mr *MockOvnClientMockRecorder) AddressSetUpdateAddress(asName interface{}, addresses ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{asName}, addresses...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AddressSetUpdateAddress", reflect.TypeOf((*MockOvnClient)(nil).AddressSetUpdateAddress), varargs...) +} + +// ClearLogicalRouterPolicy mocks base method. +func (m *MockOvnClient) ClearLogicalRouterPolicy(lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClearLogicalRouterPolicy", lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClearLogicalRouterPolicy indicates an expected call of ClearLogicalRouterPolicy. +func (mr *MockOvnClientMockRecorder) ClearLogicalRouterPolicy(lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearLogicalRouterPolicy", reflect.TypeOf((*MockOvnClient)(nil).ClearLogicalRouterPolicy), lrName) +} + +// ClearLogicalRouterStaticRoute mocks base method. +func (m *MockOvnClient) ClearLogicalRouterStaticRoute(lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ClearLogicalRouterStaticRoute", lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// ClearLogicalRouterStaticRoute indicates an expected call of ClearLogicalRouterStaticRoute. +func (mr *MockOvnClientMockRecorder) ClearLogicalRouterStaticRoute(lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ClearLogicalRouterStaticRoute", reflect.TypeOf((*MockOvnClient)(nil).ClearLogicalRouterStaticRoute), lrName) +} + +// CreateAddressSet mocks base method. +func (m *MockOvnClient) CreateAddressSet(asName string, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateAddressSet", asName, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateAddressSet indicates an expected call of CreateAddressSet. +func (mr *MockOvnClientMockRecorder) CreateAddressSet(asName, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateAddressSet", reflect.TypeOf((*MockOvnClient)(nil).CreateAddressSet), asName, externalIDs) +} + +// CreateBareLogicalSwitch mocks base method. +func (m *MockOvnClient) CreateBareLogicalSwitch(lsName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBareLogicalSwitch", lsName) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateBareLogicalSwitch indicates an expected call of CreateBareLogicalSwitch. +func (mr *MockOvnClientMockRecorder) CreateBareLogicalSwitch(lsName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBareLogicalSwitch", reflect.TypeOf((*MockOvnClient)(nil).CreateBareLogicalSwitch), lsName) +} + +// CreateBareLogicalSwitchPort mocks base method. +func (m *MockOvnClient) CreateBareLogicalSwitchPort(lsName, lspName, ip, mac string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateBareLogicalSwitchPort", lsName, lspName, ip, mac) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateBareLogicalSwitchPort indicates an expected call of CreateBareLogicalSwitchPort. +func (mr *MockOvnClientMockRecorder) CreateBareLogicalSwitchPort(lsName, lspName, ip, mac interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateBareLogicalSwitchPort", reflect.TypeOf((*MockOvnClient)(nil).CreateBareLogicalSwitchPort), lsName, lspName, ip, mac) +} + +// CreateEgressAcl mocks base method. +func (m *MockOvnClient) CreateEgressAcl(pgName, asEgressName, asExceptName, protocol string, npp []v10.NetworkPolicyPort) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateEgressAcl", pgName, asEgressName, asExceptName, protocol, npp) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateEgressAcl indicates an expected call of CreateEgressAcl. +func (mr *MockOvnClientMockRecorder) CreateEgressAcl(pgName, asEgressName, asExceptName, protocol, npp interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateEgressAcl", reflect.TypeOf((*MockOvnClient)(nil).CreateEgressAcl), pgName, asEgressName, asExceptName, protocol, npp) +} + +// CreateGatewayAcl mocks base method. +func (m *MockOvnClient) CreateGatewayAcl(pgName, gateway string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateGatewayAcl", pgName, gateway) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateGatewayAcl indicates an expected call of CreateGatewayAcl. +func (mr *MockOvnClientMockRecorder) CreateGatewayAcl(pgName, gateway interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGatewayAcl", reflect.TypeOf((*MockOvnClient)(nil).CreateGatewayAcl), pgName, gateway) +} + +// CreateGatewayLogicalSwitch mocks base method. +func (m *MockOvnClient) CreateGatewayLogicalSwitch(lsName, lrName, provider, ip, mac string, vlanID int, chassises ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{lsName, lrName, provider, ip, mac, vlanID} + for _, a := range chassises { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateGatewayLogicalSwitch", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateGatewayLogicalSwitch indicates an expected call of CreateGatewayLogicalSwitch. +func (mr *MockOvnClientMockRecorder) CreateGatewayLogicalSwitch(lsName, lrName, provider, ip, mac, vlanID interface{}, chassises ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{lsName, lrName, provider, ip, mac, vlanID}, chassises...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGatewayLogicalSwitch", reflect.TypeOf((*MockOvnClient)(nil).CreateGatewayLogicalSwitch), varargs...) +} + +// CreateIngressAcl mocks base method. +func (m *MockOvnClient) CreateIngressAcl(pgName, asIngressName, asExceptName, protocol string, npp []v10.NetworkPolicyPort) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateIngressAcl", pgName, asIngressName, asExceptName, protocol, npp) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateIngressAcl indicates an expected call of CreateIngressAcl. +func (mr *MockOvnClientMockRecorder) CreateIngressAcl(pgName, asIngressName, asExceptName, protocol, npp interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateIngressAcl", reflect.TypeOf((*MockOvnClient)(nil).CreateIngressAcl), pgName, asIngressName, asExceptName, protocol, npp) +} + +// CreateLoadBalancer mocks base method. +func (m *MockOvnClient) CreateLoadBalancer(lbName, protocol, selectFields string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLoadBalancer", lbName, protocol, selectFields) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLoadBalancer indicates an expected call of CreateLoadBalancer. +func (mr *MockOvnClientMockRecorder) CreateLoadBalancer(lbName, protocol, selectFields interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLoadBalancer", reflect.TypeOf((*MockOvnClient)(nil).CreateLoadBalancer), lbName, protocol, selectFields) +} + +// CreateLocalnetLogicalSwitchPort mocks base method. +func (m *MockOvnClient) CreateLocalnetLogicalSwitchPort(lsName, lspName, provider string, vlanID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLocalnetLogicalSwitchPort", lsName, lspName, provider, vlanID) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLocalnetLogicalSwitchPort indicates an expected call of CreateLocalnetLogicalSwitchPort. +func (mr *MockOvnClientMockRecorder) CreateLocalnetLogicalSwitchPort(lsName, lspName, provider, vlanID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLocalnetLogicalSwitchPort", reflect.TypeOf((*MockOvnClient)(nil).CreateLocalnetLogicalSwitchPort), lsName, lspName, provider, vlanID) +} + +// CreateLogicalPatchPort mocks base method. +func (m *MockOvnClient) CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, ip, mac string, chassises ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{lsName, lrName, lspName, lrpName, ip, mac} + for _, a := range chassises { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateLogicalPatchPort", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalPatchPort indicates an expected call of CreateLogicalPatchPort. +func (mr *MockOvnClientMockRecorder) CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, ip, mac interface{}, chassises ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{lsName, lrName, lspName, lrpName, ip, mac}, chassises...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalPatchPort", reflect.TypeOf((*MockOvnClient)(nil).CreateLogicalPatchPort), varargs...) +} + +// CreateLogicalRouter mocks base method. +func (m *MockOvnClient) CreateLogicalRouter(lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogicalRouter", lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalRouter indicates an expected call of CreateLogicalRouter. +func (mr *MockOvnClientMockRecorder) CreateLogicalRouter(lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalRouter", reflect.TypeOf((*MockOvnClient)(nil).CreateLogicalRouter), lrName) +} + +// CreateLogicalRouterPort mocks base method. +func (m *MockOvnClient) CreateLogicalRouterPort(lrName, lrpName, mac string, networks []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogicalRouterPort", lrName, lrpName, mac, networks) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalRouterPort indicates an expected call of CreateLogicalRouterPort. +func (mr *MockOvnClientMockRecorder) CreateLogicalRouterPort(lrName, lrpName, mac, networks interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalRouterPort", reflect.TypeOf((*MockOvnClient)(nil).CreateLogicalRouterPort), lrName, lrpName, mac, networks) +} + +// CreateLogicalSwitch mocks base method. +func (m *MockOvnClient) CreateLogicalSwitch(lsName, lrName, cidrBlock, gateway string, needRouter, randomAllocateGW bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogicalSwitch", lsName, lrName, cidrBlock, gateway, needRouter, randomAllocateGW) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalSwitch indicates an expected call of CreateLogicalSwitch. +func (mr *MockOvnClientMockRecorder) CreateLogicalSwitch(lsName, lrName, cidrBlock, gateway, needRouter, randomAllocateGW interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalSwitch", reflect.TypeOf((*MockOvnClient)(nil).CreateLogicalSwitch), lsName, lrName, cidrBlock, gateway, needRouter, randomAllocateGW) +} + +// CreateLogicalSwitchPort mocks base method. +func (m *MockOvnClient) CreateLogicalSwitchPort(lsName, lspName, ip, mac, podName, namespace string, portSecurity bool, securityGroups, vips string, enableDHCP bool, dhcpOptions *ovs.DHCPOptionsUUIDs, vpc string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateLogicalSwitchPort", lsName, lspName, ip, mac, podName, namespace, portSecurity, securityGroups, vips, enableDHCP, dhcpOptions, vpc) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateLogicalSwitchPort indicates an expected call of CreateLogicalSwitchPort. +func (mr *MockOvnClientMockRecorder) CreateLogicalSwitchPort(lsName, lspName, ip, mac, podName, namespace, portSecurity, securityGroups, vips, enableDHCP, dhcpOptions, vpc interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateLogicalSwitchPort", reflect.TypeOf((*MockOvnClient)(nil).CreateLogicalSwitchPort), lsName, lspName, ip, mac, podName, namespace, portSecurity, securityGroups, vips, enableDHCP, dhcpOptions, vpc) +} + +// CreateNodeAcl mocks base method. +func (m *MockOvnClient) CreateNodeAcl(pgName, nodeIp string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateNodeAcl", pgName, nodeIp) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateNodeAcl indicates an expected call of CreateNodeAcl. +func (mr *MockOvnClientMockRecorder) CreateNodeAcl(pgName, nodeIp interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateNodeAcl", reflect.TypeOf((*MockOvnClient)(nil).CreateNodeAcl), pgName, nodeIp) +} + +// CreatePeerRouterPort mocks base method. +func (m *MockOvnClient) CreatePeerRouterPort(localRouter, remoteRouter, localRouterPortIP string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatePeerRouterPort", localRouter, remoteRouter, localRouterPortIP) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreatePeerRouterPort indicates an expected call of CreatePeerRouterPort. +func (mr *MockOvnClientMockRecorder) CreatePeerRouterPort(localRouter, remoteRouter, localRouterPortIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePeerRouterPort", reflect.TypeOf((*MockOvnClient)(nil).CreatePeerRouterPort), localRouter, remoteRouter, localRouterPortIP) +} + +// CreatePortGroup mocks base method. +func (m *MockOvnClient) CreatePortGroup(pgName string, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreatePortGroup", pgName, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreatePortGroup indicates an expected call of CreatePortGroup. +func (mr *MockOvnClientMockRecorder) CreatePortGroup(pgName, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreatePortGroup", reflect.TypeOf((*MockOvnClient)(nil).CreatePortGroup), pgName, externalIDs) +} + +// CreateSgDenyAllAcl mocks base method. +func (m *MockOvnClient) CreateSgDenyAllAcl(sgName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateSgDenyAllAcl", sgName) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateSgDenyAllAcl indicates an expected call of CreateSgDenyAllAcl. +func (mr *MockOvnClientMockRecorder) CreateSgDenyAllAcl(sgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateSgDenyAllAcl", reflect.TypeOf((*MockOvnClient)(nil).CreateSgDenyAllAcl), sgName) +} + +// CreateVirtualLogicalSwitchPorts mocks base method. +func (m *MockOvnClient) CreateVirtualLogicalSwitchPorts(lsName string, ips ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{lsName} + for _, a := range ips { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "CreateVirtualLogicalSwitchPorts", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateVirtualLogicalSwitchPorts indicates an expected call of CreateVirtualLogicalSwitchPorts. +func (mr *MockOvnClientMockRecorder) CreateVirtualLogicalSwitchPorts(lsName interface{}, ips ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{lsName}, ips...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateVirtualLogicalSwitchPorts", reflect.TypeOf((*MockOvnClient)(nil).CreateVirtualLogicalSwitchPorts), varargs...) +} + +// DeleteAcls mocks base method. +func (m *MockOvnClient) DeleteAcls(parentName, parentType, direction string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAcls", parentName, parentType, direction) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAcls indicates an expected call of DeleteAcls. +func (mr *MockOvnClientMockRecorder) DeleteAcls(parentName, parentType, direction interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAcls", reflect.TypeOf((*MockOvnClient)(nil).DeleteAcls), parentName, parentType, direction) +} + +// DeleteAddressSet mocks base method. +func (m *MockOvnClient) DeleteAddressSet(asName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAddressSet", asName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAddressSet indicates an expected call of DeleteAddressSet. +func (mr *MockOvnClientMockRecorder) DeleteAddressSet(asName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAddressSet", reflect.TypeOf((*MockOvnClient)(nil).DeleteAddressSet), asName) +} + +// DeleteAddressSets mocks base method. +func (m *MockOvnClient) DeleteAddressSets(externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteAddressSets", externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteAddressSets indicates an expected call of DeleteAddressSets. +func (mr *MockOvnClientMockRecorder) DeleteAddressSets(externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteAddressSets", reflect.TypeOf((*MockOvnClient)(nil).DeleteAddressSets), externalIDs) +} + +// DeleteDHCPOptions mocks base method. +func (m *MockOvnClient) DeleteDHCPOptions(lsName, protocol string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteDHCPOptions", lsName, protocol) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteDHCPOptions indicates an expected call of DeleteDHCPOptions. +func (mr *MockOvnClientMockRecorder) DeleteDHCPOptions(lsName, protocol interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPOptions", reflect.TypeOf((*MockOvnClient)(nil).DeleteDHCPOptions), lsName, protocol) +} + +// DeleteDHCPOptionsByUUIDs mocks base method. +func (m *MockOvnClient) DeleteDHCPOptionsByUUIDs(uuidList ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{} + for _, a := range uuidList { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "DeleteDHCPOptionsByUUIDs", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteDHCPOptionsByUUIDs indicates an expected call of DeleteDHCPOptionsByUUIDs. +func (mr *MockOvnClientMockRecorder) DeleteDHCPOptionsByUUIDs(uuidList ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPOptionsByUUIDs", reflect.TypeOf((*MockOvnClient)(nil).DeleteDHCPOptionsByUUIDs), uuidList...) +} + +// DeleteLoadBalancers mocks base method. +func (m *MockOvnClient) DeleteLoadBalancers(filter func(*ovnnb.LoadBalancer) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLoadBalancers", filter) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLoadBalancers indicates an expected call of DeleteLoadBalancers. +func (mr *MockOvnClientMockRecorder) DeleteLoadBalancers(filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLoadBalancers", reflect.TypeOf((*MockOvnClient)(nil).DeleteLoadBalancers), filter) +} + +// DeleteLogicalGatewaySwitch mocks base method. +func (m *MockOvnClient) DeleteLogicalGatewaySwitch(lsName, lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalGatewaySwitch", lsName, lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalGatewaySwitch indicates an expected call of DeleteLogicalGatewaySwitch. +func (mr *MockOvnClientMockRecorder) DeleteLogicalGatewaySwitch(lsName, lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalGatewaySwitch", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalGatewaySwitch), lsName, lrName) +} + +// DeleteLogicalRouter mocks base method. +func (m *MockOvnClient) DeleteLogicalRouter(lrName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouter", lrName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouter indicates an expected call of DeleteLogicalRouter. +func (mr *MockOvnClientMockRecorder) DeleteLogicalRouter(lrName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouter", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalRouter), lrName) +} + +// DeleteLogicalRouterPolicies mocks base method. +func (m *MockOvnClient) DeleteLogicalRouterPolicies(lrName string, priority int, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPolicies", lrName, priority, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPolicies indicates an expected call of DeleteLogicalRouterPolicies. +func (mr *MockOvnClientMockRecorder) DeleteLogicalRouterPolicies(lrName, priority, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPolicies", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalRouterPolicies), lrName, priority, externalIDs) +} + +// DeleteLogicalRouterPolicy mocks base method. +func (m *MockOvnClient) DeleteLogicalRouterPolicy(lrName string, priority int, match string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPolicy", lrName, priority, match) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPolicy indicates an expected call of DeleteLogicalRouterPolicy. +func (mr *MockOvnClientMockRecorder) DeleteLogicalRouterPolicy(lrName, priority, match interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPolicy", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalRouterPolicy), lrName, priority, match) +} + +// DeleteLogicalRouterPolicyByUUID mocks base method. +func (m *MockOvnClient) DeleteLogicalRouterPolicyByUUID(lrName, uuid string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPolicyByUUID", lrName, uuid) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPolicyByUUID indicates an expected call of DeleteLogicalRouterPolicyByUUID. +func (mr *MockOvnClientMockRecorder) DeleteLogicalRouterPolicyByUUID(lrName, uuid interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPolicyByUUID", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalRouterPolicyByUUID), lrName, uuid) +} + +// DeleteLogicalRouterPort mocks base method. +func (m *MockOvnClient) DeleteLogicalRouterPort(lrpName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPort", lrpName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPort indicates an expected call of DeleteLogicalRouterPort. +func (mr *MockOvnClientMockRecorder) DeleteLogicalRouterPort(lrpName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPort", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalRouterPort), lrpName) +} + +// DeleteLogicalRouterPorts mocks base method. +func (m *MockOvnClient) DeleteLogicalRouterPorts(externalIDs map[string]string, filter func(*ovnnb.LogicalRouterPort) bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterPorts", externalIDs, filter) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterPorts indicates an expected call of DeleteLogicalRouterPorts. +func (mr *MockOvnClientMockRecorder) DeleteLogicalRouterPorts(externalIDs, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterPorts", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalRouterPorts), externalIDs, filter) +} + +// DeleteLogicalRouterStaticRoute mocks base method. +func (m *MockOvnClient) DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalRouterStaticRoute", lrName, policy, prefix, nextHop, routeType) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalRouterStaticRoute indicates an expected call of DeleteLogicalRouterStaticRoute. +func (mr *MockOvnClientMockRecorder) DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalRouterStaticRoute", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalRouterStaticRoute), lrName, policy, prefix, nextHop, routeType) +} + +// DeleteLogicalSwitch mocks base method. +func (m *MockOvnClient) DeleteLogicalSwitch(lsName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalSwitch", lsName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalSwitch indicates an expected call of DeleteLogicalSwitch. +func (mr *MockOvnClientMockRecorder) DeleteLogicalSwitch(lsName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalSwitch", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalSwitch), lsName) +} + +// DeleteLogicalSwitchPort mocks base method. +func (m *MockOvnClient) DeleteLogicalSwitchPort(lspName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteLogicalSwitchPort", lspName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteLogicalSwitchPort indicates an expected call of DeleteLogicalSwitchPort. +func (mr *MockOvnClientMockRecorder) DeleteLogicalSwitchPort(lspName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteLogicalSwitchPort", reflect.TypeOf((*MockOvnClient)(nil).DeleteLogicalSwitchPort), lspName) +} + +// DeleteNat mocks base method. +func (m *MockOvnClient) DeleteNat(lrName, natType, externalIP, logicalIP string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteNat", lrName, natType, externalIP, logicalIP) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteNat indicates an expected call of DeleteNat. +func (mr *MockOvnClientMockRecorder) DeleteNat(lrName, natType, externalIP, logicalIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNat", reflect.TypeOf((*MockOvnClient)(nil).DeleteNat), lrName, natType, externalIP, logicalIP) +} + +// DeleteNats mocks base method. +func (m *MockOvnClient) DeleteNats(lrName, natType, logicalIP string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteNats", lrName, natType, logicalIP) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteNats indicates an expected call of DeleteNats. +func (mr *MockOvnClientMockRecorder) DeleteNats(lrName, natType, logicalIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteNats", reflect.TypeOf((*MockOvnClient)(nil).DeleteNats), lrName, natType, logicalIP) +} + +// DeletePortGroup mocks base method. +func (m *MockOvnClient) DeletePortGroup(pgName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeletePortGroup", pgName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeletePortGroup indicates an expected call of DeletePortGroup. +func (mr *MockOvnClientMockRecorder) DeletePortGroup(pgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeletePortGroup", reflect.TypeOf((*MockOvnClient)(nil).DeletePortGroup), pgName) +} + +// DeleteSecurityGroup mocks base method. +func (m *MockOvnClient) DeleteSecurityGroup(sgName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteSecurityGroup", sgName) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteSecurityGroup indicates an expected call of DeleteSecurityGroup. +func (mr *MockOvnClientMockRecorder) DeleteSecurityGroup(sgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteSecurityGroup", reflect.TypeOf((*MockOvnClient)(nil).DeleteSecurityGroup), sgName) +} + +// EnablePortLayer2forward mocks base method. +func (m *MockOvnClient) EnablePortLayer2forward(lspName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "EnablePortLayer2forward", lspName) + ret0, _ := ret[0].(error) + return ret0 +} + +// EnablePortLayer2forward indicates an expected call of EnablePortLayer2forward. +func (mr *MockOvnClientMockRecorder) EnablePortLayer2forward(lspName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "EnablePortLayer2forward", reflect.TypeOf((*MockOvnClient)(nil).EnablePortLayer2forward), lspName) +} + +// GetEntityInfo mocks base method. +func (m *MockOvnClient) GetEntityInfo(entity interface{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetEntityInfo", entity) + ret0, _ := ret[0].(error) + return ret0 +} + +// GetEntityInfo indicates an expected call of GetEntityInfo. +func (mr *MockOvnClientMockRecorder) GetEntityInfo(entity interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntityInfo", reflect.TypeOf((*MockOvnClient)(nil).GetEntityInfo), entity) +} + +// GetLoadBalancer mocks base method. +func (m *MockOvnClient) GetLoadBalancer(lbName string, ignoreNotFound bool) (*ovnnb.LoadBalancer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLoadBalancer", lbName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LoadBalancer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLoadBalancer indicates an expected call of GetLoadBalancer. +func (mr *MockOvnClientMockRecorder) GetLoadBalancer(lbName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLoadBalancer", reflect.TypeOf((*MockOvnClient)(nil).GetLoadBalancer), lbName, ignoreNotFound) +} + +// GetLogicalRouter mocks base method. +func (m *MockOvnClient) GetLogicalRouter(lrName string, ignoreNotFound bool) (*ovnnb.LogicalRouter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalRouter", lrName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LogicalRouter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalRouter indicates an expected call of GetLogicalRouter. +func (mr *MockOvnClientMockRecorder) GetLogicalRouter(lrName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalRouter", reflect.TypeOf((*MockOvnClient)(nil).GetLogicalRouter), lrName, ignoreNotFound) +} + +// GetLogicalRouterPolicy mocks base method. +func (m *MockOvnClient) GetLogicalRouterPolicy(lrName string, priority int, match string, ignoreNotFound bool) (*ovnnb.LogicalRouterPolicy, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalRouterPolicy", lrName, priority, match, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LogicalRouterPolicy) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalRouterPolicy indicates an expected call of GetLogicalRouterPolicy. +func (mr *MockOvnClientMockRecorder) GetLogicalRouterPolicy(lrName, priority, match, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalRouterPolicy", reflect.TypeOf((*MockOvnClient)(nil).GetLogicalRouterPolicy), lrName, priority, match, ignoreNotFound) +} + +// GetLogicalRouterPort mocks base method. +func (m *MockOvnClient) GetLogicalRouterPort(lrpName string, ignoreNotFound bool) (*ovnnb.LogicalRouterPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalRouterPort", lrpName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LogicalRouterPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalRouterPort indicates an expected call of GetLogicalRouterPort. +func (mr *MockOvnClientMockRecorder) GetLogicalRouterPort(lrpName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalRouterPort", reflect.TypeOf((*MockOvnClient)(nil).GetLogicalRouterPort), lrpName, ignoreNotFound) +} + +// GetLogicalRouterRouteByOpts mocks base method. +func (m *MockOvnClient) GetLogicalRouterRouteByOpts(key, value string) ([]ovnnb.LogicalRouterStaticRoute, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalRouterRouteByOpts", key, value) + ret0, _ := ret[0].([]ovnnb.LogicalRouterStaticRoute) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalRouterRouteByOpts indicates an expected call of GetLogicalRouterRouteByOpts. +func (mr *MockOvnClientMockRecorder) GetLogicalRouterRouteByOpts(key, value interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalRouterRouteByOpts", reflect.TypeOf((*MockOvnClient)(nil).GetLogicalRouterRouteByOpts), key, value) +} + +// GetLogicalSwitchPort mocks base method. +func (m *MockOvnClient) GetLogicalSwitchPort(lspName string, ignoreNotFound bool) (*ovnnb.LogicalSwitchPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetLogicalSwitchPort", lspName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.LogicalSwitchPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetLogicalSwitchPort indicates an expected call of GetLogicalSwitchPort. +func (mr *MockOvnClientMockRecorder) GetLogicalSwitchPort(lspName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetLogicalSwitchPort", reflect.TypeOf((*MockOvnClient)(nil).GetLogicalSwitchPort), lspName, ignoreNotFound) +} + +// GetNbGlobal mocks base method. +func (m *MockOvnClient) GetNbGlobal() (*ovnnb.NBGlobal, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetNbGlobal") + ret0, _ := ret[0].(*ovnnb.NBGlobal) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetNbGlobal indicates an expected call of GetNbGlobal. +func (mr *MockOvnClientMockRecorder) GetNbGlobal() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetNbGlobal", reflect.TypeOf((*MockOvnClient)(nil).GetNbGlobal)) +} + +// GetPortGroup mocks base method. +func (m *MockOvnClient) GetPortGroup(pgName string, ignoreNotFound bool) (*ovnnb.PortGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetPortGroup", pgName, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.PortGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetPortGroup indicates an expected call of GetPortGroup. +func (mr *MockOvnClientMockRecorder) GetPortGroup(pgName, ignoreNotFound interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetPortGroup", reflect.TypeOf((*MockOvnClient)(nil).GetPortGroup), pgName, ignoreNotFound) +} + +// ListAddressSets mocks base method. +func (m *MockOvnClient) ListAddressSets(externalIDs map[string]string) ([]ovnnb.AddressSet, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListAddressSets", externalIDs) + ret0, _ := ret[0].([]ovnnb.AddressSet) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListAddressSets indicates an expected call of ListAddressSets. +func (mr *MockOvnClientMockRecorder) ListAddressSets(externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListAddressSets", reflect.TypeOf((*MockOvnClient)(nil).ListAddressSets), externalIDs) +} + +// ListDHCPOptions mocks base method. +func (m *MockOvnClient) ListDHCPOptions(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.DHCPOptions, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListDHCPOptions", needVendorFilter, externalIDs) + ret0, _ := ret[0].([]ovnnb.DHCPOptions) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListDHCPOptions indicates an expected call of ListDHCPOptions. +func (mr *MockOvnClientMockRecorder) ListDHCPOptions(needVendorFilter, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListDHCPOptions", reflect.TypeOf((*MockOvnClient)(nil).ListDHCPOptions), needVendorFilter, externalIDs) +} + +// ListLoadBalancers mocks base method. +func (m *MockOvnClient) ListLoadBalancers(filter func(*ovnnb.LoadBalancer) bool) ([]ovnnb.LoadBalancer, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLoadBalancers", filter) + ret0, _ := ret[0].([]ovnnb.LoadBalancer) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLoadBalancers indicates an expected call of ListLoadBalancers. +func (mr *MockOvnClientMockRecorder) ListLoadBalancers(filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLoadBalancers", reflect.TypeOf((*MockOvnClient)(nil).ListLoadBalancers), filter) +} + +// ListLogicalRouter mocks base method. +func (m *MockOvnClient) ListLogicalRouter(needVendorFilter bool, filter func(*ovnnb.LogicalRouter) bool) ([]ovnnb.LogicalRouter, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalRouter", needVendorFilter, filter) + ret0, _ := ret[0].([]ovnnb.LogicalRouter) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalRouter indicates an expected call of ListLogicalRouter. +func (mr *MockOvnClientMockRecorder) ListLogicalRouter(needVendorFilter, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalRouter", reflect.TypeOf((*MockOvnClient)(nil).ListLogicalRouter), needVendorFilter, filter) +} + +// ListLogicalRouterPolicies mocks base method. +func (m *MockOvnClient) ListLogicalRouterPolicies(priority int, externalIDs map[string]string) ([]ovnnb.LogicalRouterPolicy, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalRouterPolicies", priority, externalIDs) + ret0, _ := ret[0].([]ovnnb.LogicalRouterPolicy) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalRouterPolicies indicates an expected call of ListLogicalRouterPolicies. +func (mr *MockOvnClientMockRecorder) ListLogicalRouterPolicies(priority, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalRouterPolicies", reflect.TypeOf((*MockOvnClient)(nil).ListLogicalRouterPolicies), priority, externalIDs) +} + +// ListLogicalRouterPorts mocks base method. +func (m *MockOvnClient) ListLogicalRouterPorts(externalIDs map[string]string, filter func(*ovnnb.LogicalRouterPort) bool) ([]ovnnb.LogicalRouterPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalRouterPorts", externalIDs, filter) + ret0, _ := ret[0].([]ovnnb.LogicalRouterPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalRouterPorts indicates an expected call of ListLogicalRouterPorts. +func (mr *MockOvnClientMockRecorder) ListLogicalRouterPorts(externalIDs, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalRouterPorts", reflect.TypeOf((*MockOvnClient)(nil).ListLogicalRouterPorts), externalIDs, filter) +} + +// ListLogicalRouterStaticRoutes mocks base method. +func (m *MockOvnClient) ListLogicalRouterStaticRoutes(externalIDs map[string]string) ([]ovnnb.LogicalRouterStaticRoute, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalRouterStaticRoutes", externalIDs) + ret0, _ := ret[0].([]ovnnb.LogicalRouterStaticRoute) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalRouterStaticRoutes indicates an expected call of ListLogicalRouterStaticRoutes. +func (mr *MockOvnClientMockRecorder) ListLogicalRouterStaticRoutes(externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalRouterStaticRoutes", reflect.TypeOf((*MockOvnClient)(nil).ListLogicalRouterStaticRoutes), externalIDs) +} + +// ListLogicalSwitch mocks base method. +func (m *MockOvnClient) ListLogicalSwitch(needVendorFilter bool, filter func(*ovnnb.LogicalSwitch) bool) ([]ovnnb.LogicalSwitch, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalSwitch", needVendorFilter, filter) + ret0, _ := ret[0].([]ovnnb.LogicalSwitch) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalSwitch indicates an expected call of ListLogicalSwitch. +func (mr *MockOvnClientMockRecorder) ListLogicalSwitch(needVendorFilter, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalSwitch", reflect.TypeOf((*MockOvnClient)(nil).ListLogicalSwitch), needVendorFilter, filter) +} + +// ListLogicalSwitchPorts mocks base method. +func (m *MockOvnClient) ListLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string, filter func(*ovnnb.LogicalSwitchPort) bool) ([]ovnnb.LogicalSwitchPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListLogicalSwitchPorts", needVendorFilter, externalIDs, filter) + ret0, _ := ret[0].([]ovnnb.LogicalSwitchPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListLogicalSwitchPorts indicates an expected call of ListLogicalSwitchPorts. +func (mr *MockOvnClientMockRecorder) ListLogicalSwitchPorts(needVendorFilter, externalIDs, filter interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListLogicalSwitchPorts", reflect.TypeOf((*MockOvnClient)(nil).ListLogicalSwitchPorts), needVendorFilter, externalIDs, filter) +} + +// ListNats mocks base method. +func (m *MockOvnClient) ListNats(natType, logicalIP string, externalIDs map[string]string) ([]ovnnb.NAT, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNats", natType, logicalIP, externalIDs) + ret0, _ := ret[0].([]ovnnb.NAT) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListNats indicates an expected call of ListNats. +func (mr *MockOvnClientMockRecorder) ListNats(natType, logicalIP, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNats", reflect.TypeOf((*MockOvnClient)(nil).ListNats), natType, logicalIP, externalIDs) +} + +// ListNormalLogicalSwitchPorts mocks base method. +func (m *MockOvnClient) ListNormalLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.LogicalSwitchPort, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListNormalLogicalSwitchPorts", needVendorFilter, externalIDs) + ret0, _ := ret[0].([]ovnnb.LogicalSwitchPort) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListNormalLogicalSwitchPorts indicates an expected call of ListNormalLogicalSwitchPorts. +func (mr *MockOvnClientMockRecorder) ListNormalLogicalSwitchPorts(needVendorFilter, externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListNormalLogicalSwitchPorts", reflect.TypeOf((*MockOvnClient)(nil).ListNormalLogicalSwitchPorts), needVendorFilter, externalIDs) +} + +// ListPortGroups mocks base method. +func (m *MockOvnClient) ListPortGroups(externalIDs map[string]string) ([]ovnnb.PortGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ListPortGroups", externalIDs) + ret0, _ := ret[0].([]ovnnb.PortGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// ListPortGroups indicates an expected call of ListPortGroups. +func (mr *MockOvnClientMockRecorder) ListPortGroups(externalIDs interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ListPortGroups", reflect.TypeOf((*MockOvnClient)(nil).ListPortGroups), externalIDs) +} + +// LoadBalancerAddVips mocks base method. +func (m *MockOvnClient) LoadBalancerAddVips(lbName string, vips map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadBalancerAddVips", lbName, vips) + ret0, _ := ret[0].(error) + return ret0 +} + +// LoadBalancerAddVips indicates an expected call of LoadBalancerAddVips. +func (mr *MockOvnClientMockRecorder) LoadBalancerAddVips(lbName, vips interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerAddVips", reflect.TypeOf((*MockOvnClient)(nil).LoadBalancerAddVips), lbName, vips) +} + +// LoadBalancerDeleteVips mocks base method. +func (m *MockOvnClient) LoadBalancerDeleteVips(lbName string, vips map[string]struct{}) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadBalancerDeleteVips", lbName, vips) + ret0, _ := ret[0].(error) + return ret0 +} + +// LoadBalancerDeleteVips indicates an expected call of LoadBalancerDeleteVips. +func (mr *MockOvnClientMockRecorder) LoadBalancerDeleteVips(lbName, vips interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerDeleteVips", reflect.TypeOf((*MockOvnClient)(nil).LoadBalancerDeleteVips), lbName, vips) +} + +// LoadBalancerExists mocks base method. +func (m *MockOvnClient) LoadBalancerExists(lbName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LoadBalancerExists", lbName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LoadBalancerExists indicates an expected call of LoadBalancerExists. +func (mr *MockOvnClientMockRecorder) LoadBalancerExists(lbName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadBalancerExists", reflect.TypeOf((*MockOvnClient)(nil).LoadBalancerExists), lbName) +} + +// LogicalRouterExists mocks base method. +func (m *MockOvnClient) LogicalRouterExists(name string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalRouterExists", name) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalRouterExists indicates an expected call of LogicalRouterExists. +func (mr *MockOvnClientMockRecorder) LogicalRouterExists(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalRouterExists", reflect.TypeOf((*MockOvnClient)(nil).LogicalRouterExists), name) +} + +// LogicalRouterPortExists mocks base method. +func (m *MockOvnClient) LogicalRouterPortExists(lrpName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalRouterPortExists", lrpName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalRouterPortExists indicates an expected call of LogicalRouterPortExists. +func (mr *MockOvnClientMockRecorder) LogicalRouterPortExists(lrpName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalRouterPortExists", reflect.TypeOf((*MockOvnClient)(nil).LogicalRouterPortExists), lrpName) +} + +// LogicalRouterStaticRouteExists mocks base method. +func (m *MockOvnClient) LogicalRouterStaticRouteExists(lrName, policy, prefix, nextHop, routeType string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalRouterStaticRouteExists", lrName, policy, prefix, nextHop, routeType) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalRouterStaticRouteExists indicates an expected call of LogicalRouterStaticRouteExists. +func (mr *MockOvnClientMockRecorder) LogicalRouterStaticRouteExists(lrName, policy, prefix, nextHop, routeType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalRouterStaticRouteExists", reflect.TypeOf((*MockOvnClient)(nil).LogicalRouterStaticRouteExists), lrName, policy, prefix, nextHop, routeType) +} + +// LogicalSwitchExists mocks base method. +func (m *MockOvnClient) LogicalSwitchExists(lsName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalSwitchExists", lsName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalSwitchExists indicates an expected call of LogicalSwitchExists. +func (mr *MockOvnClientMockRecorder) LogicalSwitchExists(lsName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalSwitchExists", reflect.TypeOf((*MockOvnClient)(nil).LogicalSwitchExists), lsName) +} + +// LogicalSwitchPortExists mocks base method. +func (m *MockOvnClient) LogicalSwitchPortExists(name string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "LogicalSwitchPortExists", name) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// LogicalSwitchPortExists indicates an expected call of LogicalSwitchPortExists. +func (mr *MockOvnClientMockRecorder) LogicalSwitchPortExists(name interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalSwitchPortExists", reflect.TypeOf((*MockOvnClient)(nil).LogicalSwitchPortExists), name) +} + +// LogicalSwitchUpdateLoadBalancers mocks base method. +func (m *MockOvnClient) LogicalSwitchUpdateLoadBalancers(lsName string, op ovsdb.Mutator, lbNames ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{lsName, op} + for _, a := range lbNames { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "LogicalSwitchUpdateLoadBalancers", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// LogicalSwitchUpdateLoadBalancers indicates an expected call of LogicalSwitchUpdateLoadBalancers. +func (mr *MockOvnClientMockRecorder) LogicalSwitchUpdateLoadBalancers(lsName, op interface{}, lbNames ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{lsName, op}, lbNames...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalSwitchUpdateLoadBalancers", reflect.TypeOf((*MockOvnClient)(nil).LogicalSwitchUpdateLoadBalancers), varargs...) +} + +// NatExists mocks base method. +func (m *MockOvnClient) NatExists(lrName, natType, externalIP, logicalIP string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "NatExists", lrName, natType, externalIP, logicalIP) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// NatExists indicates an expected call of NatExists. +func (mr *MockOvnClientMockRecorder) NatExists(lrName, natType, externalIP, logicalIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NatExists", reflect.TypeOf((*MockOvnClient)(nil).NatExists), lrName, natType, externalIP, logicalIP) +} + +// PortGroupAddPorts mocks base method. +func (m *MockOvnClient) PortGroupAddPorts(pgName string, lspNames ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{pgName} + for _, a := range lspNames { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PortGroupAddPorts", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// PortGroupAddPorts indicates an expected call of PortGroupAddPorts. +func (mr *MockOvnClientMockRecorder) PortGroupAddPorts(pgName interface{}, lspNames ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{pgName}, lspNames...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortGroupAddPorts", reflect.TypeOf((*MockOvnClient)(nil).PortGroupAddPorts), varargs...) +} + +// PortGroupExists mocks base method. +func (m *MockOvnClient) PortGroupExists(pgName string) (bool, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortGroupExists", pgName) + ret0, _ := ret[0].(bool) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// PortGroupExists indicates an expected call of PortGroupExists. +func (mr *MockOvnClientMockRecorder) PortGroupExists(pgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortGroupExists", reflect.TypeOf((*MockOvnClient)(nil).PortGroupExists), pgName) +} + +// PortGroupRemovePorts mocks base method. +func (m *MockOvnClient) PortGroupRemovePorts(pgName string, lspNames ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{pgName} + for _, a := range lspNames { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "PortGroupRemovePorts", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// PortGroupRemovePorts indicates an expected call of PortGroupRemovePorts. +func (mr *MockOvnClientMockRecorder) PortGroupRemovePorts(pgName interface{}, lspNames ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{pgName}, lspNames...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortGroupRemovePorts", reflect.TypeOf((*MockOvnClient)(nil).PortGroupRemovePorts), varargs...) +} + +// PortGroupResetPorts mocks base method. +func (m *MockOvnClient) PortGroupResetPorts(pgName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "PortGroupResetPorts", pgName) + ret0, _ := ret[0].(error) + return ret0 +} + +// PortGroupResetPorts indicates an expected call of PortGroupResetPorts. +func (mr *MockOvnClientMockRecorder) PortGroupResetPorts(pgName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PortGroupResetPorts", reflect.TypeOf((*MockOvnClient)(nil).PortGroupResetPorts), pgName) +} + +// RemoveLogicalPatchPort mocks base method. +func (m *MockOvnClient) RemoveLogicalPatchPort(lspName, lrpName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "RemoveLogicalPatchPort", lspName, lrpName) + ret0, _ := ret[0].(error) + return ret0 +} + +// RemoveLogicalPatchPort indicates an expected call of RemoveLogicalPatchPort. +func (mr *MockOvnClientMockRecorder) RemoveLogicalPatchPort(lspName, lrpName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoveLogicalPatchPort", reflect.TypeOf((*MockOvnClient)(nil).RemoveLogicalPatchPort), lspName, lrpName) +} + +// SetAclLog mocks base method. +func (m *MockOvnClient) SetAclLog(pgName string, logEnable, isIngress bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetAclLog", pgName, logEnable, isIngress) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetAclLog indicates an expected call of SetAclLog. +func (mr *MockOvnClientMockRecorder) SetAclLog(pgName, logEnable, isIngress interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAclLog", reflect.TypeOf((*MockOvnClient)(nil).SetAclLog), pgName, logEnable, isIngress) +} + +// SetAzName mocks base method. +func (m *MockOvnClient) SetAzName(azName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetAzName", azName) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetAzName indicates an expected call of SetAzName. +func (mr *MockOvnClientMockRecorder) SetAzName(azName interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetAzName", reflect.TypeOf((*MockOvnClient)(nil).SetAzName), azName) +} + +// SetICAutoRoute mocks base method. +func (m *MockOvnClient) SetICAutoRoute(enable bool, blackList []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetICAutoRoute", enable, blackList) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetICAutoRoute indicates an expected call of SetICAutoRoute. +func (mr *MockOvnClientMockRecorder) SetICAutoRoute(enable, blackList interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetICAutoRoute", reflect.TypeOf((*MockOvnClient)(nil).SetICAutoRoute), enable, blackList) +} + +// SetLBCIDR mocks base method. +func (m *MockOvnClient) SetLBCIDR(serviceCIDR string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLBCIDR", serviceCIDR) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLBCIDR indicates an expected call of SetLBCIDR. +func (mr *MockOvnClientMockRecorder) SetLBCIDR(serviceCIDR interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLBCIDR", reflect.TypeOf((*MockOvnClient)(nil).SetLBCIDR), serviceCIDR) +} + +// SetLoadBalancerAffinityTimeout mocks base method. +func (m *MockOvnClient) SetLoadBalancerAffinityTimeout(lbName string, timeout int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLoadBalancerAffinityTimeout", lbName, timeout) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLoadBalancerAffinityTimeout indicates an expected call of SetLoadBalancerAffinityTimeout. +func (mr *MockOvnClientMockRecorder) SetLoadBalancerAffinityTimeout(lbName, timeout interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLoadBalancerAffinityTimeout", reflect.TypeOf((*MockOvnClient)(nil).SetLoadBalancerAffinityTimeout), lbName, timeout) +} + +// SetLogicalSwitchPortExternalIds mocks base method. +func (m *MockOvnClient) SetLogicalSwitchPortExternalIds(lspName string, externalIds map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPortExternalIds", lspName, externalIds) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortExternalIds indicates an expected call of SetLogicalSwitchPortExternalIds. +func (mr *MockOvnClientMockRecorder) SetLogicalSwitchPortExternalIds(lspName, externalIds interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortExternalIds", reflect.TypeOf((*MockOvnClient)(nil).SetLogicalSwitchPortExternalIds), lspName, externalIds) +} + +// SetLogicalSwitchPortSecurity mocks base method. +func (m *MockOvnClient) SetLogicalSwitchPortSecurity(portSecurity bool, lspName, mac, ips, vips string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPortSecurity", portSecurity, lspName, mac, ips, vips) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortSecurity indicates an expected call of SetLogicalSwitchPortSecurity. +func (mr *MockOvnClientMockRecorder) SetLogicalSwitchPortSecurity(portSecurity, lspName, mac, ips, vips interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortSecurity", reflect.TypeOf((*MockOvnClient)(nil).SetLogicalSwitchPortSecurity), portSecurity, lspName, mac, ips, vips) +} + +// SetLogicalSwitchPortVirtualParents mocks base method. +func (m *MockOvnClient) SetLogicalSwitchPortVirtualParents(lsName, parents string, ips ...string) error { + m.ctrl.T.Helper() + varargs := []interface{}{lsName, parents} + for _, a := range ips { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "SetLogicalSwitchPortVirtualParents", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortVirtualParents indicates an expected call of SetLogicalSwitchPortVirtualParents. +func (mr *MockOvnClientMockRecorder) SetLogicalSwitchPortVirtualParents(lsName, parents interface{}, ips ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{lsName, parents}, ips...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortVirtualParents", reflect.TypeOf((*MockOvnClient)(nil).SetLogicalSwitchPortVirtualParents), varargs...) +} + +// SetLogicalSwitchPortVlanTag mocks base method. +func (m *MockOvnClient) SetLogicalSwitchPortVlanTag(lspName string, vlanID int) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPortVlanTag", lspName, vlanID) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortVlanTag indicates an expected call of SetLogicalSwitchPortVlanTag. +func (mr *MockOvnClientMockRecorder) SetLogicalSwitchPortVlanTag(lspName, vlanID interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortVlanTag", reflect.TypeOf((*MockOvnClient)(nil).SetLogicalSwitchPortVlanTag), lspName, vlanID) +} + +// SetLogicalSwitchPortsSecurityGroup mocks base method. +func (m *MockOvnClient) SetLogicalSwitchPortsSecurityGroup(sgName, op string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPortsSecurityGroup", sgName, op) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPortsSecurityGroup indicates an expected call of SetLogicalSwitchPortsSecurityGroup. +func (mr *MockOvnClientMockRecorder) SetLogicalSwitchPortsSecurityGroup(sgName, op interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPortsSecurityGroup", reflect.TypeOf((*MockOvnClient)(nil).SetLogicalSwitchPortsSecurityGroup), sgName, op) +} + +// SetLogicalSwitchPrivate mocks base method. +func (m *MockOvnClient) SetLogicalSwitchPrivate(lsName, cidrBlock string, allowSubnets []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalSwitchPrivate", lsName, cidrBlock, allowSubnets) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalSwitchPrivate indicates an expected call of SetLogicalSwitchPrivate. +func (mr *MockOvnClientMockRecorder) SetLogicalSwitchPrivate(lsName, cidrBlock, allowSubnets interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalSwitchPrivate", reflect.TypeOf((*MockOvnClient)(nil).SetLogicalSwitchPrivate), lsName, cidrBlock, allowSubnets) +} + +// SetLsDnatModDlDst mocks base method. +func (m *MockOvnClient) SetLsDnatModDlDst(enabled bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLsDnatModDlDst", enabled) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLsDnatModDlDst indicates an expected call of SetLsDnatModDlDst. +func (mr *MockOvnClientMockRecorder) SetLsDnatModDlDst(enabled interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLsDnatModDlDst", reflect.TypeOf((*MockOvnClient)(nil).SetLsDnatModDlDst), enabled) +} + +// SetUseCtInvMatch mocks base method. +func (m *MockOvnClient) SetUseCtInvMatch() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetUseCtInvMatch") + ret0, _ := ret[0].(error) + return ret0 +} + +// SetUseCtInvMatch indicates an expected call of SetUseCtInvMatch. +func (mr *MockOvnClientMockRecorder) SetUseCtInvMatch() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetUseCtInvMatch", reflect.TypeOf((*MockOvnClient)(nil).SetUseCtInvMatch)) +} + +// UpdateDHCPOptions mocks base method. +func (m *MockOvnClient) UpdateDHCPOptions(subnet *v1.Subnet) (*ovs.DHCPOptionsUUIDs, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDHCPOptions", subnet) + ret0, _ := ret[0].(*ovs.DHCPOptionsUUIDs) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// UpdateDHCPOptions indicates an expected call of UpdateDHCPOptions. +func (mr *MockOvnClientMockRecorder) UpdateDHCPOptions(subnet interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDHCPOptions", reflect.TypeOf((*MockOvnClient)(nil).UpdateDHCPOptions), subnet) +} + +// UpdateDnatAndSnat mocks base method. +func (m *MockOvnClient) UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, gatewayType string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateDnatAndSnat", lrName, externalIP, logicalIP, lspName, externalMac, gatewayType) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateDnatAndSnat indicates an expected call of UpdateDnatAndSnat. +func (mr *MockOvnClientMockRecorder) UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, gatewayType interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateDnatAndSnat", reflect.TypeOf((*MockOvnClient)(nil).UpdateDnatAndSnat), lrName, externalIP, logicalIP, lspName, externalMac, gatewayType) +} + +// UpdateLogicalRouterPortRA mocks base method. +func (m *MockOvnClient) UpdateLogicalRouterPortRA(lrpName, ipv6RAConfigsStr string, enableIPv6RA bool) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLogicalRouterPortRA", lrpName, ipv6RAConfigsStr, enableIPv6RA) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateLogicalRouterPortRA indicates an expected call of UpdateLogicalRouterPortRA. +func (mr *MockOvnClientMockRecorder) UpdateLogicalRouterPortRA(lrpName, ipv6RAConfigsStr, enableIPv6RA interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLogicalRouterPortRA", reflect.TypeOf((*MockOvnClient)(nil).UpdateLogicalRouterPortRA), lrpName, ipv6RAConfigsStr, enableIPv6RA) +} + +// UpdateLogicalSwitchAcl mocks base method. +func (m *MockOvnClient) UpdateLogicalSwitchAcl(lsName string, subnetAcls []v1.Acl) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLogicalSwitchAcl", lsName, subnetAcls) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateLogicalSwitchAcl indicates an expected call of UpdateLogicalSwitchAcl. +func (mr *MockOvnClientMockRecorder) UpdateLogicalSwitchAcl(lsName, subnetAcls interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLogicalSwitchAcl", reflect.TypeOf((*MockOvnClient)(nil).UpdateLogicalSwitchAcl), lsName, subnetAcls) +} + +// UpdateNbGlobal mocks base method. +func (m *MockOvnClient) UpdateNbGlobal(nbGlobal *ovnnb.NBGlobal, fields ...interface{}) error { + m.ctrl.T.Helper() + varargs := []interface{}{nbGlobal} + for _, a := range fields { + varargs = append(varargs, a) + } + ret := m.ctrl.Call(m, "UpdateNbGlobal", varargs...) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateNbGlobal indicates an expected call of UpdateNbGlobal. +func (mr *MockOvnClientMockRecorder) UpdateNbGlobal(nbGlobal interface{}, fields ...interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + varargs := append([]interface{}{nbGlobal}, fields...) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateNbGlobal", reflect.TypeOf((*MockOvnClient)(nil).UpdateNbGlobal), varargs...) +} + +// UpdateSgAcl mocks base method. +func (m *MockOvnClient) UpdateSgAcl(sg *v1.SecurityGroup, direction string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateSgAcl", sg, direction) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateSgAcl indicates an expected call of UpdateSgAcl. +func (mr *MockOvnClientMockRecorder) UpdateSgAcl(sg, direction interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSgAcl", reflect.TypeOf((*MockOvnClient)(nil).UpdateSgAcl), sg, direction) +} + +// UpdateSnat mocks base method. +func (m *MockOvnClient) UpdateSnat(lrName, externalIP, logicalIP string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateSnat", lrName, externalIP, logicalIP) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateSnat indicates an expected call of UpdateSnat. +func (mr *MockOvnClientMockRecorder) UpdateSnat(lrName, externalIP, logicalIP interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateSnat", reflect.TypeOf((*MockOvnClient)(nil).UpdateSnat), lrName, externalIP, logicalIP) +} diff --git a/pkg/controller/config.go b/pkg/controller/config.go index 28565aae5a8..9b2ab48e465 100644 --- a/pkg/controller/config.go +++ b/pkg/controller/config.go @@ -154,7 +154,7 @@ func ParseFlags() (*Configuration, error) { argEnableEipSnat = pflag.Bool("enable-eip-snat", true, "Enable EIP and SNAT") argEnableExternalVpc = pflag.Bool("enable-external-vpc", true, "Enable external vpc support") argEnableEcmp = pflag.Bool("enable-ecmp", false, "Enable ecmp route for centralized subnet") - argKeepVmIP = pflag.Bool("keep-vm-ip", false, "Whether to keep ip for kubevirt pod when pod is rebuild") + argKeepVmIP = pflag.Bool("keep-vm-ip", true, "Whether to keep ip for kubevirt pod when pod is rebuild") argEnableLbSvc = pflag.Bool("enable-lb-svc", false, "Whether to support loadbalancer service") argEnableMetrics = pflag.Bool("enable-metrics", true, "Whether to support metrics query") diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 7c6ab724353..01f86173ecc 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -32,6 +32,16 @@ import ( const controllerAgentName = "kube-ovn-controller" +const ( + logicalSwitchKey = "ls" + logicalRouterKey = "lr" + portGroupKey = "pg" + networkPolicyKey = "np" + sgKey = "sg" + associatedSgKeyPrefix = "associated_sg_" + sgsKey = "security_groups" +) + // Controller is kube-ovn main controller that watch ns/pod/node/svc/ep and operate ovn type Controller struct { config *Configuration @@ -42,9 +52,12 @@ type Controller struct { namedPort *NamedPort ovnLegacyClient *ovs.LegacyClient - ovnClient *ovs.OvnClient + ovnClient ovs.OvnClient ovnPgKeyMutex *keymutex.KeyMutex + // ExternalGatewayType define external gateway type, centralized + ExternalGatewayType string + podsLister v1.PodLister podsSynced cache.InformerSynced addPodQueue workqueue.RateLimitingInterface @@ -391,7 +404,7 @@ func NewController(config *Configuration) *Controller { } var err error - if controller.ovnClient, err = ovs.NewOvnClient(config.OvnNbAddr, config.OvnTimeout); err != nil { + if controller.ovnClient, err = ovs.NewOvnClient(config.OvnNbAddr, config.OvnTimeout, config.NodeSwitchCIDR); err != nil { util.LogFatalAndExit(err, "failed to create ovn client") } diff --git a/pkg/controller/external-gw.go b/pkg/controller/external-gw.go index 090530306b3..0b0130a6ea6 100644 --- a/pkg/controller/external-gw.go +++ b/pkg/controller/external-gw.go @@ -62,6 +62,7 @@ func (c *Controller) resyncExternalGateway() { exGwEnabled = "true" lastExGwCM = cm.Data c.ovnLegacyClient.ExternalGatewayType = cm.Data["type"] + c.ExternalGatewayType = cm.Data["type"] klog.Info("finish establishing ovn external gw") cachedVpc, err := c.vpcsLister.Get(c.config.ClusterRouter) diff --git a/pkg/controller/gc.go b/pkg/controller/gc.go index 630dce7886b..a1069c25ee3 100644 --- a/pkg/controller/gc.go +++ b/pkg/controller/gc.go @@ -340,7 +340,7 @@ func (c *Controller) markAndCleanLSP() error { ipMap[vmLsp] = struct{}{} } - lsps, err := c.ovnClient.ListLogicalSwitchPorts(c.config.EnableExternalVpc, nil) + lsps, err := c.ovnClient.ListNormalLogicalSwitchPorts(c.config.EnableExternalVpc, nil) if err != nil { klog.Errorf("failed to list logical switch port, %v", err) return err diff --git a/pkg/controller/ovn-ic.go b/pkg/controller/ovn-ic.go index 12fcede35f0..d34826910d7 100644 --- a/pkg/controller/ovn-ic.go +++ b/pkg/controller/ovn-ic.go @@ -4,13 +4,14 @@ import ( "context" "encoding/json" "fmt" - "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" "os" "os/exec" "reflect" "strings" "time" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" @@ -172,7 +173,7 @@ func (c *Controller) removeInterConnection(azName string) error { } } - if err := c.stopOVNIC(); err != nil { + if err := c.stopOvnIC(); err != nil { klog.Errorf("failed to stop ovn-ic, %v", err) return err } @@ -181,7 +182,7 @@ func (c *Controller) removeInterConnection(azName string) error { } func (c *Controller) establishInterConnection(config map[string]string) error { - if err := c.startOVNIC(config["ic-db-host"], config["ic-nb-port"], config["ic-sb-port"]); err != nil { + if err := c.startOvnIC(config["ic-db-host"], config["ic-nb-port"], config["ic-sb-port"]); err != nil { klog.Errorf("failed to start ovn-ic, %v", err) return err } @@ -197,7 +198,7 @@ func (c *Controller) establishInterConnection(config map[string]string) error { return nil } - if err := c.ovnLegacyClient.SetAzName(config["az-name"]); err != nil { + if err := c.ovnClient.SetAzName(config["az-name"]); err != nil { klog.Errorf("failed to set az name. %v", err) return err } @@ -284,7 +285,7 @@ func (c *Controller) acquireLrpAddress(ts string) (string, error) { } } -func (c *Controller) startOVNIC(icHost, icNbPort, icSbPort string) error { +func (c *Controller) startOvnIC(icHost, icNbPort, icSbPort string) error { cmd := exec.Command("/usr/share/ovn/scripts/ovn-ctl", fmt.Sprintf("--ovn-ic-nb-db=%s", genHostAddress(icHost, icNbPort)), fmt.Sprintf("--ovn-ic-sb-db=%s", genHostAddress(icHost, icSbPort)), @@ -309,7 +310,7 @@ func (c *Controller) startOVNIC(icHost, icNbPort, icSbPort string) error { return nil } -func (c *Controller) stopOVNIC() error { +func (c *Controller) stopOvnIC() error { cmd := exec.Command("/usr/share/ovn/scripts/ovn-ctl", "stop_ic") output, err := cmd.CombinedOutput() if err != nil { @@ -446,22 +447,17 @@ func (c *Controller) syncOneRouteToPolicy(key, value string) { return } if len(lrRouteList) == 0 { - klog.V(5).Info(" lr ovn-ic route does not exist") - lrPolicyList, err := c.ovnClient.GetLogicalRouterPoliciesByExtID(key, value) + klog.V(5).Info("lr ovn-ic route does not exist") + err := c.ovnClient.DeleteLogicalRouterPolicies(lr.Name, util.OvnICPolicyPriority, map[string]string{key: value}) if err != nil { - klog.Errorf("failed to list ovn-ic lr policy ", err) + klog.Errorf("delete ovn-ic lr policy", err) return } - for _, lrPolicy := range lrPolicyList { - if err := c.ovnClient.DeleteRouterPolicy(lr, lrPolicy.UUID); err != nil { - klog.Errorf("deleting router policy failed %v", err) - } - } return } policyMap := map[string]string{} - lrPolicyList, err := c.ovnClient.GetLogicalRouterPoliciesByExtID(key, value) + lrPolicyList, err := c.ovnClient.ListLogicalRouterPolicies(util.OvnICPolicyPriority, map[string]string{key: value}) if err != nil { klog.Errorf("failed to list ovn-ic lr policy ", err) return @@ -479,16 +475,13 @@ func (c *Controller) syncOneRouteToPolicy(key, value string) { delete(policyMap, lrRoute.IPPrefix) } else { matchFiled := util.MatchV4Dst + " == " + lrRoute.IPPrefix - if err := c.ovnClient.AddRouterPolicy(lr, matchFiled, ovnnb.LogicalRouterPolicyActionAllow, - map[string]string{}, - map[string]string{key: value, "vendor": util.CniTypeName}, - util.OvnICPolicyPriority); err != nil { + if err := c.ovnClient.AddLogicalRouterPolicy(util.DefaultVpc, util.OvnICPolicyPriority, matchFiled, ovnnb.LogicalRouterPolicyActionAllow, nil, map[string]string{key: value, "vendor": util.CniTypeName}); err != nil { klog.Errorf("adding router policy failed %v", err) } } } for _, uuid := range policyMap { - if err := c.ovnClient.DeleteRouterPolicy(lr, uuid); err != nil { + if err := c.ovnClient.DeleteLogicalRouterPolicyByUUID(lr.Name, uuid); err != nil { klog.Errorf("deleting router policy failed %v", err) } } diff --git a/pkg/controller/ovn-ic_test.go b/pkg/controller/ovn-ic_test.go new file mode 100644 index 00000000000..b0b429f8999 --- /dev/null +++ b/pkg/controller/ovn-ic_test.go @@ -0,0 +1 @@ +package controller diff --git a/pkg/controller/pod.go b/pkg/controller/pod.go index fdba27364ec..4b8491f215b 100644 --- a/pkg/controller/pod.go +++ b/pkg/controller/pod.go @@ -13,6 +13,7 @@ import ( "gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/logging" multustypes "gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types" + v1 "k8s.io/api/core/v1" k8serrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -712,7 +713,7 @@ func (c *Controller) handleDeletePod(pod *v1.Pod) error { return nil } - ports, err := c.ovnClient.ListPodLogicalSwitchPorts(key) + ports, err := c.ovnClient.ListNormalLogicalSwitchPorts(true, map[string]string{"pod": key}) if err != nil { klog.Errorf("failed to list lsps of pod '%s', %v", pod.Name, err) return err @@ -943,7 +944,7 @@ func (c *Controller) handleUpdatePod(key string) error { // remove lsp from port group to make EIP/SNAT work portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName) c.ovnPgKeyMutex.Lock(pgName) - if err = c.ovnClient.PortGroupRemovePort(pgName, portName); err != nil { + if err = c.ovnClient.PortGroupRemovePorts(pgName, portName); err != nil { c.ovnPgKeyMutex.Unlock(pgName) return err } @@ -965,10 +966,13 @@ func (c *Controller) handleUpdatePod(key string) error { portName := ovs.PodNameToPortName(podName, pod.Namespace, podNet.ProviderName) c.ovnPgKeyMutex.Lock(pgName) - if err = c.ovnClient.PortGroupAddPort(pgName, portName); err != nil { + + if err := c.ovnClient.PortGroupAddPorts(pgName, portName); err != nil { c.ovnPgKeyMutex.Unlock(pgName) + klog.Errorf("add port to port group %s: %v", pgName, err) return err } + c.ovnPgKeyMutex.Unlock(pgName) added = true diff --git a/pkg/controller/security_group.go b/pkg/controller/security_group.go index a8e6dca6562..799428cf755 100644 --- a/pkg/controller/security_group.go +++ b/pkg/controller/security_group.go @@ -177,20 +177,56 @@ func (c *Controller) initDenyAllSecurityGroup() error { return nil } +// updateDenyAllSgPorts set lsp to deny which security_groups is not empty func (c *Controller) updateDenyAllSgPorts() error { - results, err := c.ovnLegacyClient.CustomFindEntity("logical_switch_port", []string{"_uuid", "name", "port_security"}, "external_ids:security_groups!=\"\"") + // list all lsp which security_groups is not empty + lsps, err := c.ovnClient.ListNormalLogicalSwitchPorts(true, map[string]string{sgsKey: ""}) if err != nil { klog.Errorf("failed to find logical port, %v", err) return err } - var ports []string - for _, ret := range results { - if len(ret["port_security"]) < 2 { + + addPorts := make([]string, 0, len(lsps)) + for _, lsp := range lsps { + // skip lsp which only have mac addresses,address is in port.PortSecurity[0] + if len(strings.Split(lsp.PortSecurity[0], " ")) < 2 { continue } - ports = append(ports, ret["name"][0]) + + /* skip lsp which security_group does not exist */ + // sgs format: sg1/sg2/sg3 + sgs := strings.Split(lsp.ExternalIDs[sgsKey], "/") + allNotExist, err := c.securityGroupAllNotExist(sgs) + if err != nil { + return err + } + + if allNotExist { + continue + } + + addPorts = append(addPorts, lsp.Name) + } + pgName := ovs.GetSgPortGroupName(util.DenyAllSecurityGroup) + + // reset pg ports + if err := c.ovnClient.PortGroupResetPorts(pgName); err != nil { + klog.Error(err) + return err } - return c.ovnLegacyClient.SetPortsToPortGroup(ovs.GetSgPortGroupName(util.DenyAllSecurityGroup), ports) + + // add port + if len(addPorts) == 0 { + return nil + } + + klog.V(6).Infof("add ports %v to port group %s", addPorts, pgName) + if err := c.ovnClient.PortGroupAddPorts(pgName, addPorts...); err != nil { + klog.Errorf("add ports to port group %s: %v", pgName, err) + return err + } + + return nil } func (c *Controller) handleAddOrUpdateSg(key string) error { @@ -447,3 +483,25 @@ func (c *Controller) reconcilePortSg(portName, securityGroups string) error { } return nil } + +// securityGroupAllNotExist return true if all sgs does not exist +func (c *Controller) securityGroupAllNotExist(sgs []string) (bool, error) { + if len(sgs) == 0 { + return true, nil + } + + notExistsCount := 0 + // sgs format: sg1/sg2/sg3 + for _, sg := range sgs { + ok, err := c.ovnClient.PortGroupExists(ovs.GetSgPortGroupName(sg)) + if err != nil { + return true, err + } + + if !ok { + notExistsCount++ + } + } + + return notExistsCount == len(sgs), nil +} diff --git a/pkg/controller/security_group_test.go b/pkg/controller/security_group_test.go new file mode 100644 index 00000000000..c417eb9b7ba --- /dev/null +++ b/pkg/controller/security_group_test.go @@ -0,0 +1,77 @@ +package controller + +import ( + "testing" + + "github.com/golang/mock/gomock" + "github.com/stretchr/testify/require" + + mockovs "github.com/kubeovn/kube-ovn/mocks/pkg/ovs" + "github.com/kubeovn/kube-ovn/pkg/ovs" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +func mockLsp() *ovnnb.LogicalSwitchPort { + return &ovnnb.LogicalSwitchPort{ + ExternalIDs: map[string]string{ + "associated_sg_default-securitygroup": "false", + "associated_sg_sg": "true", + "security_groups": "sg", + }, + } +} + +func Test_getPortSg(t *testing.T) { + t.Run("only have one sg", func(t *testing.T) { + c := &Controller{} + port := mockLsp() + out, err := c.getPortSg(port) + require.NoError(t, err) + require.Equal(t, []string{"sg"}, out) + }) + + t.Run("have two and more sgs", func(t *testing.T) { + c := &Controller{} + port := mockLsp() + port.ExternalIDs["associated_sg_default-securitygroup"] = "true" + out, err := c.getPortSg(port) + require.NoError(t, err) + require.ElementsMatch(t, []string{"sg", "default-securitygroup"}, out) + }) +} + +func Test_securityGroupALLNotExist(t *testing.T) { + t.Parallel() + + mockCtrl := gomock.NewController(t) + mockOvnClient := mockovs.NewMockOvnClient(mockCtrl) + ctrl := &Controller{ + ovnClient: mockOvnClient, + } + + sgName := "sg" + pgName := ovs.GetSgPortGroupName(sgName) + + t.Run("should return false when some port group exist", func(t *testing.T) { + mockOvnClient.EXPECT().PortGroupExists(gomock.Eq(pgName)).Return(true, nil) + mockOvnClient.EXPECT().PortGroupExists(gomock.Not(pgName)).Return(false, nil).Times(3) + + exist, err := ctrl.securityGroupAllNotExist([]string{sgName, "sg1", "sg2", "sg3"}) + require.NoError(t, err) + require.False(t, exist) + }) + + t.Run("should return true when all port group does't exist", func(t *testing.T) { + mockOvnClient.EXPECT().PortGroupExists(gomock.Any()).Return(false, nil).Times(3) + + exist, err := ctrl.securityGroupAllNotExist([]string{"sg1", "sg2", "sg3"}) + require.NoError(t, err) + require.True(t, exist) + }) + + t.Run("should return true when sgs is empty", func(t *testing.T) { + exist, err := ctrl.securityGroupAllNotExist([]string{}) + require.NoError(t, err) + require.True(t, exist) + }) +} diff --git a/pkg/controller/vpc.go b/pkg/controller/vpc.go index 4c2e3090ab6..95558b3e56b 100644 --- a/pkg/controller/vpc.go +++ b/pkg/controller/vpc.go @@ -205,8 +205,9 @@ func (c *Controller) reconcileRouterPorts(vpc *kubeovnv1.Vpc) error { } networks := util.GetIpAddrWithMask(gateway, subnet.Spec.CIDRBlock) klog.Infof("add vpc lrp %s, networks %s", routerPortName, networks) - if err := c.ovnClient.AddLogicalRouterPort(router, routerPortName, "", networks); err != nil { - klog.ErrorS(err, "unable to create router port", "vpc", vpc.Name, "subnet", subnetName) + + if err := c.ovnClient.CreateLogicalRouterPort(router, routerPortName, "", strings.Split(networks, ",")); err != nil { + klog.Errorf("failed to create router port %s, err %v", routerPortName, err) return err } } @@ -239,7 +240,7 @@ func (c *Controller) reconcileRouterPortBySubnet(vpc *kubeovnv1.Vpc, subnet *kub networks := util.GetIpAddrWithMask(gateway, subnet.Spec.CIDRBlock) klog.Infof("router port does not exist, trying to create %s with ip %s", routerPortName, networks) - if err := c.ovnClient.AddLogicalRouterPort(router, routerPortName, "", networks); err != nil { + if err := c.ovnClient.CreateLogicalRouterPort(router, routerPortName, "", strings.Split(networks, ",")); err != nil { klog.Errorf("failed to create router port %s, err %v", routerPortName, err) return err } diff --git a/pkg/ovs/interface.go b/pkg/ovs/interface.go new file mode 100644 index 00000000000..b79bdcf04fa --- /dev/null +++ b/pkg/ovs/interface.go @@ -0,0 +1,166 @@ +package ovs + +import ( + netv1 "k8s.io/api/networking/v1" + + "github.com/ovn-org/libovsdb/ovsdb" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +type NbGlobal interface { + UpdateNbGlobal(nbGlobal *ovnnb.NBGlobal, fields ...interface{}) error + SetAzName(azName string) error + SetUseCtInvMatch() error + SetICAutoRoute(enable bool, blackList []string) error + SetLBCIDR(serviceCIDR string) error + SetLsDnatModDlDst(enabled bool) error + GetNbGlobal() (*ovnnb.NBGlobal, error) +} + +type LogicalRouter interface { + CreateLogicalRouter(lrName string) error + DeleteLogicalRouter(lrName string) error + GetLogicalRouter(lrName string, ignoreNotFound bool) (*ovnnb.LogicalRouter, error) + ListLogicalRouter(needVendorFilter bool, filter func(lr *ovnnb.LogicalRouter) bool) ([]ovnnb.LogicalRouter, error) + LogicalRouterExists(name string) (bool, error) +} + +type LogicalRouterPort interface { + CreatePeerRouterPort(localRouter, remoteRouter, localRouterPortIP string) error + CreateLogicalRouterPort(lrName string, lrpName, mac string, networks []string) error + UpdateLogicalRouterPortRA(lrpName, ipv6RAConfigsStr string, enableIPv6RA bool) error + DeleteLogicalRouterPort(lrpName string) error + DeleteLogicalRouterPorts(externalIDs map[string]string, filter func(lrp *ovnnb.LogicalRouterPort) bool) error + GetLogicalRouterPort(lrpName string, ignoreNotFound bool) (*ovnnb.LogicalRouterPort, error) + ListLogicalRouterPorts(externalIDs map[string]string, filter func(lrp *ovnnb.LogicalRouterPort) bool) ([]ovnnb.LogicalRouterPort, error) + LogicalRouterPortExists(lrpName string) (bool, error) +} + +type LogicalSwitch interface { + CreateLogicalSwitch(lsName, lrName, cidrBlock, gateway string, needRouter, randomAllocateGW bool) error + CreateBareLogicalSwitch(lsName string) error + LogicalSwitchUpdateLoadBalancers(lsName string, op ovsdb.Mutator, lbNames ...string) error + DeleteLogicalSwitch(lsName string) error + ListLogicalSwitch(needVendorFilter bool, filter func(lr *ovnnb.LogicalSwitch) bool) ([]ovnnb.LogicalSwitch, error) + LogicalSwitchExists(lsName string) (bool, error) +} + +type LogicalSwitchPort interface { + CreateLogicalSwitchPort(lsName, lspName, ip, mac, podName, namespace string, portSecurity bool, securityGroups string, vips string, enableDHCP bool, dhcpOptions *DHCPOptionsUUIDs, vpc string) error + CreateBareLogicalSwitchPort(lsName, lspName, ip, mac string) error + CreateLocalnetLogicalSwitchPort(lsName, lspName, provider string, vlanID int) error + CreateVirtualLogicalSwitchPorts(lsName string, ips ...string) error + SetLogicalSwitchPortSecurity(portSecurity bool, lspName, mac, ips, vips string) error + SetLogicalSwitchPortVirtualParents(lsName, parents string, ips ...string) error + SetLogicalSwitchPortExternalIds(lspName string, externalIds map[string]string) error + SetLogicalSwitchPortVlanTag(lspName string, vlanID int) error + SetLogicalSwitchPortsSecurityGroup(sgName string, op string) error + UpdateLogicalSwitchAcl(lsName string, subnetAcls []kubeovnv1.Acl) error + EnablePortLayer2forward(lspName string) error + DeleteLogicalSwitchPort(lspName string) error + ListLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string, filter func(lsp *ovnnb.LogicalSwitchPort) bool) ([]ovnnb.LogicalSwitchPort, error) + ListNormalLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.LogicalSwitchPort, error) + GetLogicalSwitchPort(lspName string, ignoreNotFound bool) (*ovnnb.LogicalSwitchPort, error) + LogicalSwitchPortExists(name string) (bool, error) +} + +type LoadBalancer interface { + CreateLoadBalancer(lbName, protocol, selectFields string) error + LoadBalancerAddVips(lbName string, vips map[string]string) error + LoadBalancerDeleteVips(lbName string, vips map[string]struct{}) error + SetLoadBalancerAffinityTimeout(lbName string, timeout int) error + DeleteLoadBalancers(filter func(lb *ovnnb.LoadBalancer) bool) error + GetLoadBalancer(lbName string, ignoreNotFound bool) (*ovnnb.LoadBalancer, error) + ListLoadBalancers(filter func(lb *ovnnb.LoadBalancer) bool) ([]ovnnb.LoadBalancer, error) + LoadBalancerExists(lbName string) (bool, error) +} + +type PortGroup interface { + CreatePortGroup(pgName string, externalIDs map[string]string) error + PortGroupAddPorts(pgName string, lspNames ...string) error + PortGroupRemovePorts(pgName string, lspNames ...string) error + PortGroupResetPorts(pgName string) error + DeletePortGroup(pgName string) error + ListPortGroups(externalIDs map[string]string) ([]ovnnb.PortGroup, error) + GetPortGroup(pgName string, ignoreNotFound bool) (*ovnnb.PortGroup, error) + PortGroupExists(pgName string) (bool, error) +} + +type ACL interface { + CreateIngressAcl(pgName, asIngressName, asExceptName, protocol string, npp []netv1.NetworkPolicyPort) error + CreateEgressAcl(pgName, asEgressName, asExceptName, protocol string, npp []netv1.NetworkPolicyPort) error + CreateGatewayAcl(pgName, gateway string) error + CreateNodeAcl(pgName, nodeIp string) error + CreateSgDenyAllAcl(sgName string) error + UpdateSgAcl(sg *kubeovnv1.SecurityGroup, direction string) error + SetAclLog(pgName string, logEnable, isIngress bool) error + SetLogicalSwitchPrivate(lsName, cidrBlock string, allowSubnets []string) error + DeleteAcls(parentName, parentType string, direction string) error +} + +type AddressSet interface { + CreateAddressSet(asName string, externalIDs map[string]string) error + AddressSetUpdateAddress(asName string, addresses ...string) error + DeleteAddressSet(asName string) error + DeleteAddressSets(externalIDs map[string]string) error + ListAddressSets(externalIDs map[string]string) ([]ovnnb.AddressSet, error) +} + +type LogicalRouterStaticRoute interface { + AddLogicalRouterStaticRoute(lrName, policy, cidrBlock, nextHops, routeType string) error + ClearLogicalRouterStaticRoute(lrName string) error + DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType string) error + GetLogicalRouterRouteByOpts(key, value string) ([]ovnnb.LogicalRouterStaticRoute, error) + ListLogicalRouterStaticRoutes(externalIDs map[string]string) ([]ovnnb.LogicalRouterStaticRoute, error) + LogicalRouterStaticRouteExists(lrName, policy, prefix, nextHop, routeType string) (bool, error) +} + +type LogicalRouterPolicy interface { + AddLogicalRouterPolicy(lrName string, priority int, match, action string, nextHops []string, externalIDs map[string]string) error + DeleteLogicalRouterPolicy(lrName string, priority int, match string) error + DeleteLogicalRouterPolicies(lrName string, priority int, externalIDs map[string]string) error + DeleteLogicalRouterPolicyByUUID(lrName string, uuid string) error + ClearLogicalRouterPolicy(lrName string) error + ListLogicalRouterPolicies(priority int, externalIDs map[string]string) ([]ovnnb.LogicalRouterPolicy, error) + GetLogicalRouterPolicy(lrName string, priority int, match string, ignoreNotFound bool) (*ovnnb.LogicalRouterPolicy, error) +} + +type NAT interface { + UpdateSnat(lrName, externalIP, logicalIP string) error + UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, gatewayType string) error + DeleteNats(lrName, natType, logicalIP string) error + DeleteNat(lrName, natType, externalIP, logicalIP string) error + NatExists(lrName, natType, externalIP, logicalIP string) (bool, error) + ListNats(natType, logicalIP string, externalIDs map[string]string) ([]ovnnb.NAT, error) +} + +type DHCPOptions interface { + UpdateDHCPOptions(subnet *kubeovnv1.Subnet) (*DHCPOptionsUUIDs, error) + DeleteDHCPOptions(lsName string, protocol string) error + DeleteDHCPOptionsByUUIDs(uuidList ...string) error + ListDHCPOptions(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.DHCPOptions, error) +} + +type OvnClient interface { + NbGlobal + LogicalRouter + LogicalRouterPort + LogicalSwitch + LogicalSwitchPort + LoadBalancer + PortGroup + ACL + AddressSet + LogicalRouterStaticRoute + LogicalRouterPolicy + NAT + DHCPOptions + CreateGatewayLogicalSwitch(lsName, lrName, provider, ip, mac string, vlanID int, chassises ...string) error + CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, ip, mac string, chassises ...string) error + RemoveLogicalPatchPort(lspName, lrpName string) error + DeleteLogicalGatewaySwitch(lsName, lrName string) error + DeleteSecurityGroup(sgName string) error + GetEntityInfo(entity interface{}) error +} diff --git a/pkg/ovs/ovn-nb-acl.go b/pkg/ovs/ovn-nb-acl.go new file mode 100644 index 00000000000..add8f91eed4 --- /dev/null +++ b/pkg/ovs/ovn-nb-acl.go @@ -0,0 +1,825 @@ +package ovs + +import ( + "context" + "fmt" + "strconv" + "strings" + + netv1 "k8s.io/api/networking/v1" + + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +// CreateIngressACL creates an ingress ACL +func (c *ovnClient) CreateIngressAcl(pgName, asIngressName, asExceptName, protocol string, npp []netv1.NetworkPolicyPort) error { + acls := make([]*ovnnb.ACL, 0) + + /* default drop acl */ + AllIpMatch := NewAndAclMatch( + NewAclMatch("outport", "==", "@"+pgName, ""), + NewAclMatch("ip", "", "", ""), + ) + options := func(acl *ovnnb.ACL) { + acl.Name = &pgName + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + } + + defaultDropAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressDefaultDrop, AllIpMatch.String(), ovnnb.ACLActionDrop, options) + if err != nil { + return fmt.Errorf("new default drop ingress acl for port group %s: %v", pgName, err) + } + + acls = append(acls, defaultDropAcl) + + /* allow acl */ + matches := newNetworkPolicyAclMatch(pgName, asIngressName, asExceptName, protocol, ovnnb.ACLDirectionToLport, npp) + for _, m := range matches { + allowAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressAllowPriority, m, ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new allow ingress acl for port group %s: %v", pgName, err) + } + + acls = append(acls, allowAcl) + } + + if err := c.CreateAcls(pgName, portGroupKey, acls...); err != nil { + return fmt.Errorf("add ingress acls to port group %s: %v", pgName, err) + } + + return nil +} + +// CreateIngressACL creates an egress ACL +func (c *ovnClient) CreateEgressAcl(pgName, asEgressName, asExceptName, protocol string, npp []netv1.NetworkPolicyPort) error { + acls := make([]*ovnnb.ACL, 0) + + /* default drop acl */ + AllIpMatch := NewAndAclMatch( + NewAclMatch("inport", "==", "@"+pgName, ""), + NewAclMatch("ip", "", "", ""), + ) + options := func(acl *ovnnb.ACL) { + acl.Name = &pgName + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + } + + defaultDropAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressDefaultDrop, AllIpMatch.String(), ovnnb.ACLActionDrop, options) + if err != nil { + return fmt.Errorf("new default drop egress acl for port group %s: %v", pgName, err) + } + + acls = append(acls, defaultDropAcl) + + /* allow acl */ + matches := newNetworkPolicyAclMatch(pgName, asEgressName, asExceptName, protocol, ovnnb.ACLDirectionFromLport, npp) + for _, m := range matches { + allowAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressAllowPriority, m, ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new allow egress acl for port group %s: %v", pgName, err) + } + + acls = append(acls, allowAcl) + } + + if err := c.CreateAcls(pgName, portGroupKey, acls...); err != nil { + return fmt.Errorf("add egress acls to port group %s: %v", pgName, err) + } + + return nil +} + +// CreateGatewayACL create allow acl for subnet gateway +func (c *ovnClient) CreateGatewayAcl(pgName, gateway string) error { + acls := make([]*ovnnb.ACL, 0) + + for _, gw := range strings.Split(gateway, ",") { + protocol := util.CheckProtocol(gw) + ipSuffix := "ip4" + if protocol == kubeovnv1.ProtocolIPv6 { + ipSuffix = "ip6" + } + + allowIngressAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressAllowPriority, fmt.Sprintf("%s.src == %s", ipSuffix, gw), ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new allow ingress acl for port group %s: %v", pgName, err) + } + + allowEgressAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressAllowPriority, fmt.Sprintf("%s.dst == %s", ipSuffix, gw), ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new allow egress acl for port group %s: %v", pgName, err) + } + + acls = append(acls, allowIngressAcl, allowEgressAcl) + } + + if err := c.CreateAcls(pgName, portGroupKey, acls...); err != nil { + return fmt.Errorf("add gateway acls to port group %s: %v", pgName, err) + } + + return nil +} + +// CreateGatewayACL create allow acl for node join ip +func (c *ovnClient) CreateNodeAcl(pgName, nodeIp string) error { + acls := make([]*ovnnb.ACL, 0) + for _, ip := range strings.Split(nodeIp, ",") { + protocol := util.CheckProtocol(ip) + ipSuffix := "ip4" + if protocol == kubeovnv1.ProtocolIPv6 { + ipSuffix = "ip6" + } + pgAs := fmt.Sprintf("%s_%s", pgName, ipSuffix) + + allowIngressAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionToLport, util.NodeAllowPriority, fmt.Sprintf("%s.src == %s && %s.dst == $%s", ipSuffix, ip, ipSuffix, pgAs), ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new allow ingress acl for port group %s: %v", pgName, err) + } + + allowEgressAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionFromLport, util.NodeAllowPriority, fmt.Sprintf("%s.dst == %s && %s.src == $%s", ipSuffix, ip, ipSuffix, pgAs), ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new allow egress acl for port group %s: %v", pgName, err) + } + + acls = append(acls, allowIngressAcl, allowEgressAcl) + } + + if err := c.CreateAcls(pgName, portGroupKey, acls...); err != nil { + return fmt.Errorf("add node acls to port group %s: %v", pgName, err) + } + + return nil +} + +func (c *ovnClient) CreateSgDenyAllAcl(sgName string) error { + pgName := GetSgPortGroupName(sgName) + + ingressAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionToLport, util.SecurityGroupDropPriority, fmt.Sprintf("outport == @%s && ip", pgName), ovnnb.ACLActionDrop) + if err != nil { + return fmt.Errorf("new deny all ingress acl for security group %s: %v", sgName, err) + } + + egressAcl, err := c.newAcl(pgName, ovnnb.ACLDirectionFromLport, util.SecurityGroupDropPriority, fmt.Sprintf("inport == @%s && ip", pgName), ovnnb.ACLActionDrop) + if err != nil { + return fmt.Errorf("new deny all egress acl for security group %s: %v", sgName, err) + } + + if err := c.CreateAcls(pgName, portGroupKey, ingressAcl, egressAcl); err != nil { + return fmt.Errorf("add deny all acl to port group %s: %v", pgName, err) + } + + return nil +} + +func (c *ovnClient) UpdateSgAcl(sg *kubeovnv1.SecurityGroup, direction string) error { + pgName := GetSgPortGroupName(sg.Name) + + // clear acl + if err := c.DeleteAcls(pgName, portGroupKey, direction); err != nil { + return fmt.Errorf("delete direction '%s' acls from port group %s: %v", direction, pgName, err) + } + + acls := make([]*ovnnb.ACL, 0, 2) + + // ingress rule + srcOrDst, portDirection, sgRules := "src", "outport", sg.Spec.IngressRules + if direction == ovnnb.ACLDirectionFromLport { // egress rule + srcOrDst = "dst" + portDirection = "inport" + sgRules = sg.Spec.EgressRules + } + + /* create port_group associated acl */ + if sg.Spec.AllowSameGroupTraffic { + asName := GetSgV4AssociatedName(sg.Name) + for _, ipSuffix := range []string{"ip4", "ip6"} { + if ipSuffix == "ip6" { + asName = GetSgV6AssociatedName(sg.Name) + } + + match := NewAndAclMatch( + NewAclMatch(portDirection, "==", "@"+pgName, ""), + NewAclMatch(ipSuffix, "", "", ""), + NewAclMatch(ipSuffix+"."+srcOrDst, "==", "$"+asName, ""), + ) + acl, err := c.newAcl(pgName, direction, util.SecurityGroupAllowPriority, match.String(), ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new allow acl for security group %s: %v", sg.Name, err) + } + + acls = append(acls, acl) + } + } + + /* create rule acl */ + for _, rule := range sgRules { + acl, err := c.newSgRuleACL(sg.Name, direction, rule) + if err != nil { + return fmt.Errorf("new rule acl for security group %s: %v", sg.Name, err) + } + acls = append(acls, acl) + } + + if err := c.CreateAcls(pgName, portGroupKey, acls...); err != nil { + return fmt.Errorf("add acl to port group %s: %v", pgName, err) + } + + return nil +} + +func (c *ovnClient) UpdateLogicalSwitchAcl(lsName string, subnetAcls []kubeovnv1.Acl) error { + if err := c.DeleteAcls(lsName, logicalSwitchKey, ""); err != nil { + return fmt.Errorf("clear logical switch %s acls: %v", lsName, err) + } + + if len(subnetAcls) == 0 { + return nil + } + acls := make([]*ovnnb.ACL, 0, len(subnetAcls)) + + /* recreate logical switch acl */ + for _, subnetAcl := range subnetAcls { + acl, err := c.newAcl(lsName, subnetAcl.Direction, strconv.Itoa(subnetAcl.Priority), subnetAcl.Match, subnetAcl.Action) + if err != nil { + return fmt.Errorf("new acl for logical switch %s: %v", lsName, err) + } + acls = append(acls, acl) + } + + if err := c.CreateAcls(lsName, logicalSwitchKey, acls...); err != nil { + return fmt.Errorf("add acls to logical switch %s: %v", lsName, err) + } + + return nil +} + +// UpdateAcl update acl +func (c *ovnClient) UpdateAcl(acl *ovnnb.ACL, fields ...interface{}) error { + if acl == nil { + return fmt.Errorf("address_set is nil") + } + + op, err := c.Where(acl).Update(acl, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating acl with 'direction %s priority %d match %s': %v", acl.Direction, acl.Priority, acl.Match, err) + } + + if err = c.Transact("acl-update", op); err != nil { + return fmt.Errorf("update acl with 'direction %s priority %d match %s': %v", acl.Direction, acl.Priority, acl.Match, err) + } + + return nil +} + +// SetLogicalSwitchPrivate will drop all ingress traffic except allow subnets, same subnet and node subnet +func (c *ovnClient) SetLogicalSwitchPrivate(lsName, cidrBlock string, allowSubnets []string) error { + // clear acls + if err := c.DeleteAcls(lsName, logicalSwitchKey, ""); err != nil { + return fmt.Errorf("clear logical switch %s acls: %v", lsName, err) + } + + acls := make([]*ovnnb.ACL, 0) + + /* default drop acl */ + AllIpMatch := NewAclMatch("ip", "", "", "") + + options := func(acl *ovnnb.ACL) { + acl.Name = &lsName + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + } + + defaultDropAcl, err := c.newAcl(lsName, ovnnb.ACLDirectionToLport, util.DefaultDropPriority, AllIpMatch.String(), ovnnb.ACLActionDrop, options) + if err != nil { + return fmt.Errorf("new default drop ingress acl for logical switch %s: %v", lsName, err) + } + + acls = append(acls, defaultDropAcl) + + nodeSubnetAclFunc := func(protocol, ipSuffix string) error { + for _, nodeCidr := range strings.Split(c.NodeSwitchCIDR, ",") { + // skip different address family + if protocol != util.CheckProtocol(nodeCidr) { + continue + } + + match := NewAndAclMatch( + NewAclMatch(ipSuffix+".src", "==", nodeCidr, ""), + ) + + acl, err := c.newAcl(lsName, ovnnb.ACLDirectionToLport, util.NodeAllowPriority, match.String(), ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new node subnet ingress acl for logical switch %s: %v", lsName, err) + } + + acls = append(acls, acl) + } + + return nil + } + + allowSubnetAclFunc := func(protocol, ipSuffix, cidr string) error { + for _, allowSubnet := range allowSubnets { + subnet := strings.TrimSpace(allowSubnet) + // skip empty subnet + if len(subnet) == 0 { + continue + } + + // skip different address family + if util.CheckProtocol(subnet) != protocol { + continue + } + + match := NewOrAclMatch( + NewAndAclMatch( + NewAclMatch(ipSuffix+".src", "==", cidr, ""), + NewAclMatch(ipSuffix+".dst", "==", subnet, ""), + ), + NewAndAclMatch( + NewAclMatch(ipSuffix+".src", "==", subnet, ""), + NewAclMatch(ipSuffix+".dst", "==", cidr, ""), + ), + ) + + acl, err := c.newAcl(lsName, ovnnb.ACLDirectionToLport, util.SubnetAllowPriority, match.String(), ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new allow subnet ingress acl for logical switch %s: %v", lsName, err) + } + + acls = append(acls, acl) + } + return nil + } + + for _, cidr := range strings.Split(cidrBlock, ",") { + protocol := util.CheckProtocol(cidr) + + ipSuffix := "ip4" + if protocol == kubeovnv1.ProtocolIPv6 { + ipSuffix = "ip6" + } + + /* same subnet acl */ + sameSubnetMatch := NewAndAclMatch( + NewAclMatch(ipSuffix+".src", "==", cidr, ""), + NewAclMatch(ipSuffix+".dst", "==", cidr, ""), + ) + + sameSubnetAcl, err := c.newAcl(lsName, ovnnb.ACLDirectionToLport, util.SubnetAllowPriority, sameSubnetMatch.String(), ovnnb.ACLActionAllowRelated) + if err != nil { + return fmt.Errorf("new same subnet ingress acl for logical switch %s: %v", lsName, err) + } + + acls = append(acls, sameSubnetAcl) + + // node subnet acl + if err := nodeSubnetAclFunc(protocol, ipSuffix); err != nil { + return err + } + + // allow subnet acl + if err := allowSubnetAclFunc(protocol, ipSuffix, cidr); err != nil { + return err + } + } + + if err := c.CreateAcls(lsName, logicalSwitchKey, acls...); err != nil { + return fmt.Errorf("add ingress acls to logical switch %s: %v", lsName, err) + } + + return nil +} + +func (c *ovnClient) SetAclLog(pgName string, logEnable, isIngress bool) error { + direction := ovnnb.ACLDirectionToLport + portDirection := "outport" + if !isIngress { + direction = ovnnb.ACLDirectionFromLport + portDirection = "inport" + } + + // match all traffic to or from pgName + AllIpMatch := NewAndAclMatch( + NewAclMatch(portDirection, "==", "@"+pgName, ""), + NewAclMatch("ip", "", "", ""), + ) + + acl, err := c.GetAcl(pgName, direction, util.IngressDefaultDrop, AllIpMatch.String(), false) + if err != nil { + return err + } + + acl.Log = logEnable + + err = c.UpdateAcl(acl, &acl.Log) + if err != nil { + return fmt.Errorf("update acl: %v", err) + } + + return nil +} + +// CreateAcls create several acl once +// parentType is 'ls' or 'pg' +func (c *ovnClient) CreateAcls(parentName, parentType string, acls ...*ovnnb.ACL) error { + if parentType != portGroupKey && parentType != logicalSwitchKey { + return fmt.Errorf("acl parent type must be '%s' or '%s'", portGroupKey, logicalSwitchKey) + } + + if len(acls) == 0 { + return nil + } + + models := make([]model.Model, 0, len(acls)) + aclUUIDs := make([]string, 0, len(acls)) + for _, acl := range acls { + if acl != nil { + models = append(models, model.Model(acl)) + aclUUIDs = append(aclUUIDs, acl.UUID) + } + } + + createAclsOp, err := c.ovnNbClient.Create(models...) + if err != nil { + return fmt.Errorf("generate operations for creating acls: %v", err) + } + + var aclAddOp []ovsdb.Operation + if parentType == portGroupKey { // acl attach to port group + aclAddOp, err = c.portGroupUpdateAclOp(parentName, aclUUIDs, ovsdb.MutateOperationInsert) + if err != nil { + return fmt.Errorf("generate operations for adding acls to port group %s: %v", parentName, err) + } + } else { // acl attach to logical switch + aclAddOp, err = c.logicalSwitchUpdateAclOp(parentName, aclUUIDs, ovsdb.MutateOperationInsert) + if err != nil { + return fmt.Errorf("generate operations for adding acls to logical switch %s: %v", parentName, err) + } + } + + ops := make([]ovsdb.Operation, 0, len(createAclsOp)+len(aclAddOp)) + ops = append(ops, createAclsOp...) + ops = append(ops, aclAddOp...) + + if err = c.Transact("acls-add", ops); err != nil { + return fmt.Errorf("add acls to type %s %s: %v", parentType, parentName, err) + } + + return nil +} + +func (c *ovnClient) CreateBareAcl(parentName, direction, priority, match, action string) error { + acl, err := c.newAcl(parentName, direction, priority, match, action) + if err != nil { + return fmt.Errorf("new acl direction %s priority %s match %s action %s: %v", direction, priority, match, action, err) + } + + op, err := c.ovnNbClient.Create(acl) + if err != nil { + return fmt.Errorf("generate operations for creating acl direction %s priority %s match %s action %s: %v", direction, priority, match, action, err) + } + + if err = c.Transact("acl-create", op); err != nil { + return fmt.Errorf("create acl direction %s priority %s match %s action %s: %v", direction, priority, match, action, err) + } + + return nil +} + +// DeleteAcls delete several acl once, +// delete to-lport and from-lport direction acl when direction is empty, otherwise one-way +// parentType is 'ls' or 'pg' +func (c *ovnClient) DeleteAcls(parentName, parentType string, direction string) error { + externalIDs := map[string]string{aclParentKey: parentName} + + /* delete acls from port group or logical switch */ + acls, err := c.ListAcls(direction, externalIDs) + if err != nil { + return fmt.Errorf("list type %s %s acls: %v", parentType, parentName, err) + } + + aclUUIDs := make([]string, 0, len(acls)) + for _, acl := range acls { + aclUUIDs = append(aclUUIDs, acl.UUID) + } + + var removeAclOp []ovsdb.Operation + if parentType == portGroupKey { // remove acl from port group + removeAclOp, err = c.portGroupUpdateAclOp(parentName, aclUUIDs, ovsdb.MutateOperationDelete) + if err != nil { + return fmt.Errorf("generate operations for deleting acls from port group %s: %v", parentName, err) + } + } else { // remove acl from logical switch + removeAclOp, err = c.logicalSwitchUpdateAclOp(parentName, aclUUIDs, ovsdb.MutateOperationDelete) + if err != nil { + return fmt.Errorf("generate operations for deleting acls from logical switch %s: %v", parentName, err) + } + } + + // delete acls + delAclsOp, err := c.WhereCache(aclFilter(direction, externalIDs)).Delete() + if err != nil { + return fmt.Errorf("generate operation for deleting acls: %v", err) + } + + ops := make([]ovsdb.Operation, 0, len(removeAclOp)+len(delAclsOp)) + ops = append(ops, removeAclOp...) + ops = append(ops, delAclsOp...) + + if err = c.Transact("acls-del", ops); err != nil { + return fmt.Errorf("del acls from type %s %s: %v", parentType, parentName, err) + } + + return nil +} + +// GetAcl get acl by direction, priority and match, +// be consistent with ovn-nbctl which direction, priority and match determine one acl in port group or logical switch +func (c *ovnClient) GetAcl(parent, direction, priority, match string, ignoreNotFound bool) (*ovnnb.ACL, error) { + // this is necessary because may exist same direction, priority and match acl in different port group or logical switch + if len(parent) == 0 { + return nil, fmt.Errorf("the parent name is required") + } + + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + intPriority, _ := strconv.Atoi(priority) + + aclList := make([]ovnnb.ACL, 0) + if err := c.ovnNbClient.WhereCache(func(acl *ovnnb.ACL) bool { + return len(acl.ExternalIDs) != 0 && acl.ExternalIDs[aclParentKey] == parent && acl.Direction == direction && acl.Priority == intPriority && acl.Match == match + }).List(ctx, &aclList); err != nil { + return nil, fmt.Errorf("get acl with 'parent %s direction %s priority %s match %s': %v", parent, direction, priority, match, err) + } + + // not found + if len(aclList) == 0 { + if ignoreNotFound { + return nil, nil + } + return nil, fmt.Errorf("not found acl with 'parent %s direction %s priority %s match %s'", parent, direction, priority, match) + } + + if len(aclList) > 1 { + return nil, fmt.Errorf("more than one acl with same 'parent %s direction %s priority %s match %s'", parent, direction, priority, match) + } + + return &aclList[0], nil +} + +// ListAcls list acls which match the given externalIDs, +// result should include all to-lport and from-lport acls when direction is empty, +// result should include all acls when externalIDs is empty, +// result should include all acls which externalIDs[key] is not empty when externalIDs[key] is "" +// TODO: maybe add other filter conditions(priority or match) +func (c *ovnClient) ListAcls(direction string, externalIDs map[string]string) ([]ovnnb.ACL, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + aclList := make([]ovnnb.ACL, 0) + + if err := c.WhereCache(aclFilter(direction, externalIDs)).List(ctx, &aclList); err != nil { + return nil, fmt.Errorf("list acls: %v", err) + } + + return aclList, nil +} + +func (c *ovnClient) AclExists(parent, direction, priority, match string) (bool, error) { + acl, err := c.GetAcl(parent, direction, priority, match, true) + return acl != nil, err +} + +// newAcl return acl with basic information +func (c *ovnClient) newAcl(parent, direction, priority, match, action string, options ...func(acl *ovnnb.ACL)) (*ovnnb.ACL, error) { + if len(parent) == 0 { + return nil, fmt.Errorf("the parent name is required") + } + + if len(direction) == 0 || len(priority) == 0 || len(match) == 0 || len(action) == 0 { + return nil, fmt.Errorf("acl 'direction %s' and 'priority %s' and 'match %s' and 'action %s' is required", direction, priority, match, action) + } + + exists, err := c.AclExists(parent, direction, priority, match) + if err != nil { + return nil, fmt.Errorf("get parent %s acl: %v", parent, err) + } + + // found, ignore + if exists { + return nil, nil + } + + intPriority, _ := strconv.Atoi(priority) + + acl := &ovnnb.ACL{ + UUID: ovsclient.NamedUUID(), + Action: action, + Direction: direction, + Match: match, + Priority: intPriority, + ExternalIDs: map[string]string{ + aclParentKey: parent, + }, + } + + for _, option := range options { + option(acl) + } + + return acl, nil +} + +// createSgRuleACL create security group rule acl +func (c *ovnClient) newSgRuleACL(sgName string, direction string, rule *kubeovnv1.SgRule) (*ovnnb.ACL, error) { + ipSuffix := "ip4" + if rule.IPVersion == "ipv6" { + ipSuffix = "ip6" + } + + pgName := GetSgPortGroupName(sgName) + + // ingress rule + srcOrDst, portDirection := "src", "outport" + if direction == ovnnb.ACLDirectionFromLport { // egress rule + srcOrDst = "dst" + portDirection = "inport" + } + + ipKey := ipSuffix + "." + srcOrDst + + /* match all traffic to or from pgName */ + AllIpMatch := NewAndAclMatch( + NewAclMatch(portDirection, "==", "@"+pgName, ""), + NewAclMatch(ipSuffix, "", "", ""), + ) + + /* allow allowed ip traffic */ + // type address + allowedIpMatch := NewAndAclMatch( + AllIpMatch, + NewAclMatch(ipKey, "==", rule.RemoteAddress, ""), + ) + + // type securityGroup + if rule.RemoteType == kubeovnv1.SgRemoteTypeSg { + allowedIpMatch = NewAndAclMatch( + AllIpMatch, + NewAclMatch(ipKey, "==", "$"+rule.RemoteSecurityGroup, ""), + ) + } + + /* allow layer 4 traffic */ + // allow all layer 4 traffic + match := allowedIpMatch + + switch rule.Protocol { + case kubeovnv1.ProtocolICMP: + match = NewAndAclMatch( + allowedIpMatch, + NewAclMatch("icmp4", "", "", ""), + ) + if ipSuffix == "ip6" { + match = NewAndAclMatch( + allowedIpMatch, + NewAclMatch("icmp6", "", "", ""), + ) + } + case kubeovnv1.ProtocolTCP, kubeovnv1.ProtocolUDP: + match = NewAndAclMatch( + allowedIpMatch, + NewAclMatch(string(rule.Protocol)+".dst", "<=", strconv.Itoa(rule.PortRangeMin), strconv.Itoa(rule.PortRangeMax)), + ) + } + + action := ovnnb.ACLActionDrop + if rule.Policy == kubeovnv1.PolicyAllow { + action = ovnnb.ACLActionAllowRelated + } + + highestPriority, _ := strconv.Atoi(util.SecurityGroupHighestPriority) + + acl, err := c.newAcl(pgName, direction, strconv.Itoa(highestPriority-rule.Priority), match.String(), action) + if err != nil { + return nil, fmt.Errorf("new security group acl for port group %s: %v", pgName, err) + } + + return acl, nil +} + +func newNetworkPolicyAclMatch(pgName, asAllowName, asExceptName, protocol, direction string, npp []netv1.NetworkPolicyPort) []string { + ipSuffix := "ip4" + if protocol == kubeovnv1.ProtocolIPv6 { + ipSuffix = "ip6" + } + + // ingress rule + srcOrDst, portDirection := "src", "outport" + if direction == ovnnb.ACLDirectionFromLport { // egress rule + srcOrDst = "dst" + portDirection = "inport" + } + + ipKey := ipSuffix + "." + srcOrDst + + // match all traffic to or from pgName + AllIpMatch := NewAndAclMatch( + NewAclMatch(portDirection, "==", "@"+pgName, ""), + NewAclMatch("ip", "", "", ""), + ) + + allowedIpMatch := NewAndAclMatch( + AllIpMatch, + NewAclMatch(ipKey, "==", "$"+asAllowName, ""), + NewAclMatch(ipKey, "!=", "$"+asExceptName, ""), + ) + + matches := make([]string, 0) + + // allow allowed ip traffic but except + if len(npp) == 0 { + return []string{allowedIpMatch.String()} + } + + for _, port := range npp { + protocol := strings.ToLower(string(*port.Protocol)) + + // allow all tcp or udp traffic + if port.Port == nil { + allLayer4Match := NewAndAclMatch( + allowedIpMatch, + NewAclMatch(protocol, "", "", ""), + ) + + matches = append(matches, allLayer4Match.String()) + continue + } + + // allow one tcp or udp port traffic + if port.EndPort == nil { + tcpKey := protocol + ".dst" + oneTcpMatch := NewAndAclMatch( + allowedIpMatch, + NewAclMatch(tcpKey, "==", fmt.Sprintf("%d", port.Port.IntVal), ""), + ) + + matches = append(matches, oneTcpMatch.String()) + continue + } + + // allow several tcp or udp port traffic + tcpKey := protocol + ".dst" + severalTcpMatch := NewAndAclMatch( + allowedIpMatch, + NewAclMatch(tcpKey, "<=", fmt.Sprintf("%d", port.Port.IntVal), fmt.Sprintf("%d", *port.EndPort)), + ) + matches = append(matches, severalTcpMatch.String()) + } + + return matches +} + +// aclFilter filter acls which match the given externalIDs, +// result should include all to-lport and from-lport acls when direction is empty, +// result should include all acls when externalIDs is empty, +// result should include all acls which externalIDs[key] is not empty when externalIDs[key] is "" +// TODO: maybe add other filter conditions(priority or match) +func aclFilter(direction string, externalIDs map[string]string) func(acl *ovnnb.ACL) bool { + return func(acl *ovnnb.ACL) bool { + if len(acl.ExternalIDs) < len(externalIDs) { + return false + } + + if len(acl.ExternalIDs) != 0 { + for k, v := range externalIDs { + // if only key exist but not value in externalIDs, we should include this lsp, + // it's equal to shell command `ovn-nbctl --columns=xx find acl external_ids:key!=\"\"` + if len(v) == 0 { + if len(acl.ExternalIDs[k]) == 0 { + return false + } + } else { + if acl.ExternalIDs[k] != v { + return false + } + } + } + } + + if len(direction) != 0 && acl.Direction != direction { + return false + } + + return true + } +} diff --git a/pkg/ovs/ovn-nb-acl_test.go b/pkg/ovs/ovn-nb-acl_test.go new file mode 100644 index 00000000000..8d5a6ba2d28 --- /dev/null +++ b/pkg/ovs/ovn-nb-acl_test.go @@ -0,0 +1,1433 @@ +package ovs + +import ( + "fmt" + "strconv" + "strings" + "testing" + + "github.com/stretchr/testify/require" + + v1 "k8s.io/api/core/v1" + netv1 "k8s.io/api/networking/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func mockNetworkPolicyPort() []netv1.NetworkPolicyPort { + protocolTcp := v1.ProtocolTCP + var endPort int32 = 20000 + return []netv1.NetworkPolicyPort{ + { + Port: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 12345, + }, + Protocol: &protocolTcp, + }, + { + Port: &intstr.IntOrString{ + Type: intstr.Int, + IntVal: 12346, + }, + EndPort: &endPort, + Protocol: &protocolTcp, + }, + } +} + +func newAcl(parentName, direction, priority, match, action string, options ...func(acl *ovnnb.ACL)) *ovnnb.ACL { + intPriority, _ := strconv.Atoi(priority) + + acl := &ovnnb.ACL{ + UUID: ovsclient.NamedUUID(), + Action: action, + Direction: direction, + Match: match, + Priority: intPriority, + ExternalIDs: map[string]string{ + aclParentKey: parentName, + }, + } + + for _, option := range options { + option(acl) + } + + return acl +} + +func (suite *OvnClientTestSuite) testCreateIngressAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + + t.Run("ipv4 acl", func(t *testing.T) { + t.Parallel() + + pgName := "test_create_v4_ingress_acl_pg" + asIngressName := "test.default.ingress.allow.ipv4" + asExceptName := "test.default.ingress.except.ipv4" + protocol := kubeovnv1.ProtocolIPv4 + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + npp := mockNetworkPolicyPort() + + err = ovnClient.CreateIngressAcl(pgName, asIngressName, asExceptName, protocol, npp) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 3) + + match := fmt.Sprintf("outport == @%s && ip", pgName) + defaultDropAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressDefaultDrop, match, false) + require.NoError(t, err) + + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressDefaultDrop, match, ovnnb.ACLActionDrop, func(acl *ovnnb.ACL) { + acl.Name = &pgName + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + acl.UUID = defaultDropAcl.UUID + }) + + require.Equal(t, expect, defaultDropAcl) + require.Contains(t, pg.ACLs, defaultDropAcl.UUID) + + matches := newNetworkPolicyAclMatch(pgName, asIngressName, asExceptName, protocol, ovnnb.ACLDirectionToLport, npp) + for _, m := range matches { + allowAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressAllowPriority, m, false) + require.NoError(t, err) + + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressAllowPriority, m, ovnnb.ACLActionAllowRelated) + expect.UUID = allowAcl.UUID + require.Equal(t, expect, allowAcl) + + require.Contains(t, pg.ACLs, allowAcl.UUID) + } + }) + + t.Run("ipv6 acl", func(t *testing.T) { + t.Parallel() + + pgName := "test_create_v6_ingress_acl_pg" + asIngressName := "test.default.ingress.allow.ipv6" + asExceptName := "test.default.ingress.except.ipv6" + protocol := kubeovnv1.ProtocolIPv6 + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + err = ovnClient.CreateIngressAcl(pgName, asIngressName, asExceptName, protocol, nil) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 2) + + match := fmt.Sprintf("outport == @%s && ip", pgName) + defaultDropAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressDefaultDrop, match, false) + require.NoError(t, err) + + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressDefaultDrop, match, ovnnb.ACLActionDrop, func(acl *ovnnb.ACL) { + acl.Name = &pgName + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + acl.UUID = defaultDropAcl.UUID + }) + + require.Equal(t, expect, defaultDropAcl) + require.Contains(t, pg.ACLs, defaultDropAcl.UUID) + + matches := newNetworkPolicyAclMatch(pgName, asIngressName, asExceptName, protocol, ovnnb.ACLDirectionToLport, nil) + for _, m := range matches { + allowAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressAllowPriority, m, false) + require.NoError(t, err) + + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressAllowPriority, m, ovnnb.ACLActionAllowRelated) + expect.UUID = allowAcl.UUID + require.Equal(t, expect, allowAcl) + + require.Contains(t, pg.ACLs, allowAcl.UUID) + } + }) +} + +func (suite *OvnClientTestSuite) testCreateEgressAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + + t.Run("ipv4 acl", func(t *testing.T) { + t.Parallel() + + pgName := "test_create_v4_egress_acl_pg" + asEgressName := "test.default.egress.allow.ipv4" + asExceptName := "test.default.egress.except.ipv4" + protocol := kubeovnv1.ProtocolIPv4 + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + npp := mockNetworkPolicyPort() + + err = ovnClient.CreateEgressAcl(pgName, asEgressName, asExceptName, protocol, npp) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 3) + + match := fmt.Sprintf("inport == @%s && ip", pgName) + defaultDropAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressDefaultDrop, match, false) + require.NoError(t, err) + + expect := newAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressDefaultDrop, match, ovnnb.ACLActionDrop, func(acl *ovnnb.ACL) { + acl.Name = &pgName + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + acl.UUID = defaultDropAcl.UUID + }) + + require.Equal(t, expect, defaultDropAcl) + require.Contains(t, pg.ACLs, defaultDropAcl.UUID) + + matches := newNetworkPolicyAclMatch(pgName, asEgressName, asExceptName, protocol, ovnnb.ACLDirectionFromLport, npp) + for _, m := range matches { + allowAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressAllowPriority, m, false) + require.NoError(t, err) + + expect := newAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressAllowPriority, m, ovnnb.ACLActionAllowRelated) + expect.UUID = allowAcl.UUID + require.Equal(t, expect, allowAcl) + + require.Contains(t, pg.ACLs, allowAcl.UUID) + } + }) + + t.Run("ipv6 acl", func(t *testing.T) { + t.Parallel() + + pgName := "test_create_v6_egress_acl_pg" + asEgressName := "test.default.egress.allow.ipv6" + asExceptName := "test.default.egress.except.ipv6" + protocol := kubeovnv1.ProtocolIPv6 + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + err = ovnClient.CreateEgressAcl(pgName, asEgressName, asExceptName, protocol, nil) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 2) + + match := fmt.Sprintf("inport == @%s && ip", pgName) + defaultDropAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressDefaultDrop, match, false) + require.NoError(t, err) + + expect := newAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressDefaultDrop, match, ovnnb.ACLActionDrop, func(acl *ovnnb.ACL) { + acl.Name = &pgName + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + acl.UUID = defaultDropAcl.UUID + }) + + require.Equal(t, expect, defaultDropAcl) + require.Contains(t, pg.ACLs, defaultDropAcl.UUID) + + matches := newNetworkPolicyAclMatch(pgName, asEgressName, asExceptName, protocol, ovnnb.ACLDirectionFromLport, nil) + for _, m := range matches { + allowAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressAllowPriority, m, false) + require.NoError(t, err) + + expect := newAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressAllowPriority, m, ovnnb.ACLActionAllowRelated) + expect.UUID = allowAcl.UUID + require.Equal(t, expect, allowAcl) + + require.Contains(t, pg.ACLs, allowAcl.UUID) + } + }) +} + +func (suite *OvnClientTestSuite) testCreateGatewayAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test_create_gw_acl_pg" + gateway := "10.244.0.1,fc00::0af4:01" + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + err = ovnClient.CreateGatewayAcl(pgName, gateway) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 4) + + for _, gw := range strings.Split(gateway, ",") { + protocol := util.CheckProtocol(gw) + ipSuffix := "ip4" + if protocol == kubeovnv1.ProtocolIPv6 { + ipSuffix = "ip6" + } + + match := fmt.Sprintf("%s.src == %s", ipSuffix, gw) + allowIngressAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressAllowPriority, match, false) + require.NoError(t, err) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressAllowPriority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = allowIngressAcl.UUID + require.Equal(t, expect, allowIngressAcl) + require.Contains(t, pg.ACLs, allowIngressAcl.UUID) + + match = fmt.Sprintf("%s.dst == %s", ipSuffix, gw) + allowEgressAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressAllowPriority, match, false) + require.NoError(t, err) + expect = newAcl(pgName, ovnnb.ACLDirectionFromLport, util.EgressAllowPriority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = allowEgressAcl.UUID + require.Equal(t, expect, allowEgressAcl) + require.Contains(t, pg.ACLs, allowEgressAcl.UUID) + } +} + +func (suite *OvnClientTestSuite) testCreateNodeAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test_create_node_acl_pg" + nodeIp := "100.64.0.2,fd00:100:64::2" + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + err = ovnClient.CreateNodeAcl(pgName, nodeIp) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 4) + + for _, ip := range strings.Split(nodeIp, ",") { + protocol := util.CheckProtocol(ip) + ipSuffix := "ip4" + if protocol == kubeovnv1.ProtocolIPv6 { + ipSuffix = "ip6" + } + + pgAs := fmt.Sprintf("%s_%s", pgName, ipSuffix) + + match := fmt.Sprintf("%s.src == %s && %s.dst == $%s", ipSuffix, ip, ipSuffix, pgAs) + allowIngressAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.NodeAllowPriority, match, false) + require.NoError(t, err) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, util.NodeAllowPriority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = allowIngressAcl.UUID + require.Equal(t, expect, allowIngressAcl) + require.Contains(t, pg.ACLs, allowIngressAcl.UUID) + + match = fmt.Sprintf("%s.dst == %s && %s.src == $%s", ipSuffix, ip, ipSuffix, pgAs) + allowEgressAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.NodeAllowPriority, match, false) + require.NoError(t, err) + expect = newAcl(pgName, ovnnb.ACLDirectionFromLport, util.NodeAllowPriority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = allowEgressAcl.UUID + require.Equal(t, expect, allowEgressAcl) + require.Contains(t, pg.ACLs, allowEgressAcl.UUID) + } +} + +func (suite *OvnClientTestSuite) testCreateSgDenyAllAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + sgName := "test_create_deny_all_acl_pg" + pgName := GetSgPortGroupName(sgName) + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + err = ovnClient.CreateSgDenyAllAcl(sgName) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + + // ingress acl + match := fmt.Sprintf("outport == @%s && ip", pgName) + ingressAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.SecurityGroupDropPriority, match, false) + require.NoError(t, err) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, util.SecurityGroupDropPriority, match, ovnnb.ACLActionDrop) + expect.UUID = ingressAcl.UUID + require.Equal(t, expect, ingressAcl) + require.Contains(t, pg.ACLs, ingressAcl.UUID) + + // egress acl + match = fmt.Sprintf("inport == @%s && ip", pgName) + egressAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.SecurityGroupDropPriority, match, false) + require.NoError(t, err) + expect = newAcl(pgName, ovnnb.ACLDirectionFromLport, util.SecurityGroupDropPriority, match, ovnnb.ACLActionDrop) + expect.UUID = egressAcl.UUID + require.Equal(t, expect, egressAcl) + require.Contains(t, pg.ACLs, egressAcl.UUID) +} + +func (suite *OvnClientTestSuite) testUpdateSgAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + sgName := "test_update_sg_acl_pg" + v4AsName := GetSgV4AssociatedName(sgName) + v6AsName := GetSgV6AssociatedName(sgName) + pgName := GetSgPortGroupName(sgName) + + sg := &kubeovnv1.SecurityGroup{ + ObjectMeta: metav1.ObjectMeta{ + Name: sgName, + }, + Spec: kubeovnv1.SecurityGroupSpec{ + AllowSameGroupTraffic: true, + IngressRules: []*kubeovnv1.SgRule{ + { + IPVersion: "ipv4", + RemoteType: kubeovnv1.SgRemoteTypeAddress, + RemoteAddress: "0.0.0.0/0", + Protocol: "icmp", + Priority: 12, + Policy: "allow", + }, + }, + EgressRules: []*kubeovnv1.SgRule{ + { + IPVersion: "ipv4", + RemoteType: kubeovnv1.SgRemoteTypeAddress, + RemoteAddress: "0.0.0.0/0", + Protocol: "all", + Priority: 10, + Policy: "allow", + }, + }, + }, + } + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + t.Run("update securityGroup ingress acl", func(t *testing.T) { + err = ovnClient.UpdateSgAcl(sg, ovnnb.ACLDirectionToLport) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + + // ipv4 acl + match := fmt.Sprintf("outport == @%s && ip4 && ip4.src == $%s", pgName, v4AsName) + v4Acl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.SecurityGroupAllowPriority, match, false) + require.NoError(t, err) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, util.SecurityGroupAllowPriority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = v4Acl.UUID + require.Equal(t, expect, v4Acl) + require.Contains(t, pg.ACLs, v4Acl.UUID) + + // ipv6 acl + match = fmt.Sprintf("outport == @%s && ip6 && ip6.src == $%s", pgName, v6AsName) + v6Acl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.SecurityGroupAllowPriority, match, false) + require.NoError(t, err) + expect = newAcl(pgName, ovnnb.ACLDirectionToLport, util.SecurityGroupAllowPriority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = v6Acl.UUID + require.Equal(t, expect, v6Acl) + require.Contains(t, pg.ACLs, v6Acl.UUID) + + // rule acl + match = fmt.Sprintf("outport == @%s && ip4 && ip4.src == 0.0.0.0/0 && icmp4", pgName) + rulAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, "2288", match, false) + require.NoError(t, err) + expect = newAcl(pgName, ovnnb.ACLDirectionToLport, "2288", match, ovnnb.ACLActionAllowRelated) + expect.UUID = rulAcl.UUID + require.Equal(t, expect, rulAcl) + require.Contains(t, pg.ACLs, rulAcl.UUID) + }) + + t.Run("update securityGroup egress acl", func(t *testing.T) { + err = ovnClient.UpdateSgAcl(sg, ovnnb.ACLDirectionFromLport) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + + // ipv4 acl + match := fmt.Sprintf("inport == @%s && ip4 && ip4.dst == $%s", pgName, v4AsName) + v4Acl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.SecurityGroupAllowPriority, match, false) + require.NoError(t, err) + expect := newAcl(pgName, ovnnb.ACLDirectionFromLport, util.SecurityGroupAllowPriority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = v4Acl.UUID + require.Equal(t, expect, v4Acl) + require.Contains(t, pg.ACLs, v4Acl.UUID) + + // ipv6 acl + match = fmt.Sprintf("inport == @%s && ip6 && ip6.dst == $%s", pgName, v6AsName) + v6Acl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.SecurityGroupAllowPriority, match, false) + require.NoError(t, err) + expect = newAcl(pgName, ovnnb.ACLDirectionFromLport, util.SecurityGroupAllowPriority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = v6Acl.UUID + require.Equal(t, expect, v6Acl) + require.Contains(t, pg.ACLs, v6Acl.UUID) + + // rule acl + match = fmt.Sprintf("inport == @%s && ip4 && ip4.dst == 0.0.0.0/0", pgName) + rulAcl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, "2290", match, false) + require.NoError(t, err) + expect = newAcl(pgName, ovnnb.ACLDirectionFromLport, "2290", match, ovnnb.ACLActionAllowRelated) + expect.UUID = rulAcl.UUID + require.Equal(t, expect, rulAcl) + require.Contains(t, pg.ACLs, rulAcl.UUID) + }) +} + +func (suite *OvnClientTestSuite) testUpdateLogicalSwitchAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test_update_acl_ls" + + subnetAcls := []kubeovnv1.Acl{ + { + Direction: ovnnb.ACLDirectionToLport, + Priority: 1111, + Match: "ip4.src == 192.168.111.5", + Action: ovnnb.ACLActionAllowRelated, + }, + { + Direction: ovnnb.ACLDirectionFromLport, + Priority: 1111, + Match: "ip4.dst == 192.168.111.50", + Action: ovnnb.ACLActionDrop, + }, + } + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.UpdateLogicalSwitchAcl(lsName, subnetAcls) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + for _, subnetAcl := range subnetAcls { + acl, err := ovnClient.GetAcl(lsName, subnetAcl.Direction, strconv.Itoa(subnetAcl.Priority), subnetAcl.Match, false) + require.NoError(t, err) + expect := newAcl(lsName, subnetAcl.Direction, strconv.Itoa(subnetAcl.Priority), subnetAcl.Match, subnetAcl.Action) + expect.UUID = acl.UUID + require.Equal(t, expect, acl) + require.Contains(t, ls.ACLs, acl.UUID) + } +} + +func (suite *OvnClientTestSuite) testSetAclLog() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := GetSgPortGroupName("test_set_acl_log") + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + t.Run("set ingress acl log to false", func(t *testing.T) { + match := fmt.Sprintf("outport == @%s && ip", pgName) + acl := newAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressDefaultDrop, match, ovnnb.ACLActionDrop, func(acl *ovnnb.ACL) { + acl.Name = &pgName + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + }) + + err = ovnClient.CreateAcls(pgName, portGroupKey, acl) + require.NoError(t, err) + + err = ovnClient.SetAclLog(pgName, false, true) + require.NoError(t, err) + + acl, err = ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, util.IngressDefaultDrop, match, false) + require.NoError(t, err) + require.False(t, acl.Log) + }) + + t.Run("set egress acl log to false", func(t *testing.T) { + match := fmt.Sprintf("inport == @%s && ip", pgName) + acl := newAcl(pgName, ovnnb.ACLDirectionFromLport, util.IngressDefaultDrop, match, ovnnb.ACLActionDrop, func(acl *ovnnb.ACL) { + acl.Name = &pgName + acl.Log = false + acl.Severity = &ovnnb.ACLSeverityWarning + }) + + err = ovnClient.CreateAcls(pgName, portGroupKey, acl) + require.NoError(t, err) + + err = ovnClient.SetAclLog(pgName, true, false) + require.NoError(t, err) + + acl, err = ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, util.IngressDefaultDrop, match, false) + require.NoError(t, err) + require.True(t, acl.Log) + }) + +} + +func (suite *OvnClientTestSuite) testSetLogicalSwitchPrivate() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test_set_private_ls" + cidrBlock := "10.244.0.0/16,fc00::af4:0/112" + allowSubnets := []string{ + "10.230.0.0/16", + "10.240.0.0/16", + "fc00::af9:0/112", + "fc00::afa:0/112", + } + direction := ovnnb.ACLDirectionToLport + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("subnet protocol is dual", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPrivate(lsName, cidrBlock, allowSubnets) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Len(t, ls.ACLs, 9) + + // default drop acl + match := "ip" + acl, err := ovnClient.GetAcl(lsName, direction, util.DefaultDropPriority, match, false) + require.NoError(t, err) + require.Contains(t, ls.ACLs, acl.UUID) + + // same subnet acl + for _, cidr := range strings.Split(cidrBlock, ",") { + protocol := util.CheckProtocol(cidr) + + match := fmt.Sprintf(`ip4.src == %s && ip4.dst == %s`, cidr, cidr) + if protocol == kubeovnv1.ProtocolIPv6 { + match = fmt.Sprintf(`ip6.src == %s && ip6.dst == %s`, cidr, cidr) + } + + acl, err = ovnClient.GetAcl(lsName, direction, util.SubnetAllowPriority, match, false) + require.NoError(t, err) + require.Contains(t, ls.ACLs, acl.UUID) + + // allow subnet acl + for _, subnet := range allowSubnets { + protocol := util.CheckProtocol(cidr) + + allowProtocol := util.CheckProtocol(subnet) + if allowProtocol != protocol { + continue + } + + match = fmt.Sprintf("(ip4.src == %s && ip4.dst == %s) || (ip4.src == %s && ip4.dst == %s)", cidr, subnet, subnet, cidr) + if protocol == kubeovnv1.ProtocolIPv6 { + match = fmt.Sprintf("(ip6.src == %s && ip6.dst == %s) || (ip6.src == %s && ip6.dst == %s)", cidr, subnet, subnet, cidr) + } + + acl, err = ovnClient.GetAcl(lsName, direction, util.SubnetAllowPriority, match, false) + require.NoError(t, err) + require.Contains(t, ls.ACLs, acl.UUID) + } + } + + // node subnet acl + for _, cidr := range strings.Split(ovnClient.NodeSwitchCIDR, ",") { + protocol := util.CheckProtocol(cidr) + + match := fmt.Sprintf(`ip4.src == %s`, cidr) + if protocol == kubeovnv1.ProtocolIPv6 { + match = fmt.Sprintf(`ip6.src == %s`, cidr) + } + + acl, err = ovnClient.GetAcl(lsName, direction, util.NodeAllowPriority, match, false) + require.NoError(t, err) + require.Contains(t, ls.ACLs, acl.UUID) + } + }) + + t.Run("subnet protocol is ipv4", func(t *testing.T) { + cidrBlock := "10.244.0.0/16" + err = ovnClient.SetLogicalSwitchPrivate(lsName, cidrBlock, allowSubnets) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Len(t, ls.ACLs, 5) + + // default drop acl + match := "ip" + acl, err := ovnClient.GetAcl(lsName, direction, util.DefaultDropPriority, match, false) + require.NoError(t, err) + require.Contains(t, ls.ACLs, acl.UUID) + + // same subnet acl + for _, cidr := range strings.Split(cidrBlock, ",") { + protocol := util.CheckProtocol(cidr) + + match := fmt.Sprintf(`ip4.src == %s && ip4.dst == %s`, cidr, cidr) + if protocol == kubeovnv1.ProtocolIPv6 { + match = fmt.Sprintf(`ip6.src == %s && ip6.dst == %s`, cidr, cidr) + } + + acl, err = ovnClient.GetAcl(lsName, direction, util.SubnetAllowPriority, match, false) + require.NoError(t, err) + require.Contains(t, ls.ACLs, acl.UUID) + + // allow subnet acl + for _, subnet := range allowSubnets { + protocol := util.CheckProtocol(cidr) + + allowProtocol := util.CheckProtocol(subnet) + if allowProtocol != protocol { + continue + } + + match = fmt.Sprintf("(ip4.src == %s && ip4.dst == %s) || (ip4.src == %s && ip4.dst == %s)", cidr, subnet, subnet, cidr) + if protocol == kubeovnv1.ProtocolIPv6 { + match = fmt.Sprintf("(ip6.src == %s && ip6.dst == %s) || (ip6.src == %s && ip6.dst == %s)", cidr, subnet, subnet, cidr) + } + + acl, err = ovnClient.GetAcl(lsName, direction, util.SubnetAllowPriority, match, false) + require.NoError(t, err) + require.Contains(t, ls.ACLs, acl.UUID) + } + } + + // node subnet acl + for _, cidr := range strings.Split(ovnClient.NodeSwitchCIDR, ",") { + protocol := util.CheckProtocol(cidr) + + match := fmt.Sprintf(`ip4.src == %s`, cidr) + if protocol == kubeovnv1.ProtocolIPv6 { + match = fmt.Sprintf(`ip6.src == %s`, cidr) + } + + acl, err = ovnClient.GetAcl(lsName, direction, util.NodeAllowPriority, match, false) + if protocol == kubeovnv1.ProtocolIPv4 { + require.NoError(t, err) + require.Contains(t, ls.ACLs, acl.UUID) + } else { + require.ErrorContains(t, err, "not found acl") + } + } + }) +} + +func (suite *OvnClientTestSuite) test_newSgRuleACL() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + sgName := "test_create_sg_acl_pg" + pgName := GetSgPortGroupName(sgName) + highestPriority, _ := strconv.Atoi(util.SecurityGroupHighestPriority) + + t.Run("create securityGroup type sg acl", func(t *testing.T) { + t.Parallel() + + sgRule := &kubeovnv1.SgRule{ + IPVersion: "ipv4", + RemoteType: kubeovnv1.SgRemoteTypeSg, + RemoteSecurityGroup: "ovn.sg.test_sg", + Protocol: "icmp", + Priority: 12, + Policy: "allow", + } + priority := strconv.Itoa(highestPriority - sgRule.Priority) + + acl, err := ovnClient.newSgRuleACL(sgName, ovnnb.ACLDirectionToLport, sgRule) + require.NoError(t, err) + + match := fmt.Sprintf("outport == @%s && ip4 && ip4.src == $%s && icmp4", pgName, sgRule.RemoteSecurityGroup) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = acl.UUID + require.Equal(t, expect, acl) + }) + + t.Run("create address type sg acl", func(t *testing.T) { + t.Parallel() + + sgRule := &kubeovnv1.SgRule{ + IPVersion: "ipv4", + RemoteType: kubeovnv1.SgRemoteTypeAddress, + RemoteAddress: "10.10.10.12/24", + Protocol: "icmp", + Priority: 12, + Policy: "allow", + } + priority := strconv.Itoa(highestPriority - sgRule.Priority) + + acl, err := ovnClient.newSgRuleACL(sgName, ovnnb.ACLDirectionToLport, sgRule) + require.NoError(t, err) + + match := fmt.Sprintf("outport == @%s && ip4 && ip4.src == %s && icmp4", pgName, sgRule.RemoteAddress) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = acl.UUID + require.Equal(t, expect, acl) + }) + + t.Run("create ipv6 acl", func(t *testing.T) { + t.Parallel() + + sgRule := &kubeovnv1.SgRule{ + IPVersion: "ipv6", + RemoteType: kubeovnv1.SgRemoteTypeAddress, + RemoteAddress: "fe80::200:ff:fe04:2611/64", + Protocol: "icmp", + Priority: 12, + Policy: "allow", + } + priority := strconv.Itoa(highestPriority - sgRule.Priority) + + acl, err := ovnClient.newSgRuleACL(sgName, ovnnb.ACLDirectionToLport, sgRule) + require.NoError(t, err) + + match := fmt.Sprintf("outport == @%s && ip6 && ip6.src == %s && icmp6", pgName, sgRule.RemoteAddress) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = acl.UUID + require.Equal(t, expect, acl) + }) + + t.Run("create egress sg acl", func(t *testing.T) { + t.Parallel() + + sgRule := &kubeovnv1.SgRule{ + IPVersion: "ipv4", + RemoteType: kubeovnv1.SgRemoteTypeAddress, + RemoteAddress: "10.10.10.12/24", + Protocol: "icmp", + Priority: 12, + Policy: "allow", + } + priority := strconv.Itoa(highestPriority - sgRule.Priority) + + acl, err := ovnClient.newSgRuleACL(sgName, ovnnb.ACLDirectionFromLport, sgRule) + require.NoError(t, err) + + match := fmt.Sprintf("inport == @%s && ip4 && ip4.dst == %s && icmp4", pgName, sgRule.RemoteAddress) + expect := newAcl(pgName, ovnnb.ACLDirectionFromLport, priority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = acl.UUID + require.Equal(t, expect, acl) + }) + + t.Run("create drop sg acl", func(t *testing.T) { + t.Parallel() + + sgRule := &kubeovnv1.SgRule{ + IPVersion: "ipv4", + RemoteType: kubeovnv1.SgRemoteTypeAddress, + RemoteAddress: "10.10.10.12/24", + Protocol: "icmp", + Priority: 21, + Policy: "drop", + } + priority := strconv.Itoa(highestPriority - sgRule.Priority) + + acl, err := ovnClient.newSgRuleACL(sgName, ovnnb.ACLDirectionToLport, sgRule) + require.NoError(t, err) + + match := fmt.Sprintf("outport == @%s && ip4 && ip4.src == %s && icmp4", pgName, sgRule.RemoteAddress) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionDrop) + expect.UUID = acl.UUID + require.Equal(t, expect, acl) + }) + + t.Run("create tcp sg acl", func(t *testing.T) { + t.Parallel() + + sgRule := &kubeovnv1.SgRule{ + IPVersion: "ipv4", + RemoteType: kubeovnv1.SgRemoteTypeAddress, + RemoteAddress: "10.10.10.12/24", + Protocol: "tcp", + Priority: 12, + Policy: "allow", + PortRangeMin: 12345, + PortRangeMax: 12360, + } + priority := strconv.Itoa(highestPriority - sgRule.Priority) + + acl, err := ovnClient.newSgRuleACL(sgName, ovnnb.ACLDirectionToLport, sgRule) + require.NoError(t, err) + + match := fmt.Sprintf("outport == @%s && ip4 && ip4.src == %s && %d <= tcp.dst <= %d", pgName, sgRule.RemoteAddress, sgRule.PortRangeMin, sgRule.PortRangeMax) + expect := newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + expect.UUID = acl.UUID + require.Equal(t, expect, acl) + }) +} + +func (suite *OvnClientTestSuite) testCreateAcls() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-create-acls-pg" + priority := "5000" + basePort := 12300 + matchPrefix := "outport == @ovn.sg.test_create_acl_pg && ip" + acls := make([]*ovnnb.ACL, 0, 3) + + t.Run("add acl to port group", func(t *testing.T) { + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + err = ovnClient.CreateAcls(pgName, portGroupKey, append(acls, nil)...) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, false) + require.NoError(t, err) + require.Equal(t, match, acl.Match) + + require.Contains(t, pg.ACLs, acl.UUID) + } + }) + + t.Run("add acl to logical switch", func(t *testing.T) { + lsName := "test-create-acls-ls" + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && udp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(lsName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + err = ovnClient.CreateAcls(lsName, logicalSwitchKey, append(acls, nil)...) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && udp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.GetAcl(lsName, ovnnb.ACLDirectionToLport, priority, match, false) + require.NoError(t, err) + require.Equal(t, match, acl.Match) + + require.Contains(t, ls.ACLs, acl.UUID) + } + }) + + t.Run("acl parent type is wrong", func(t *testing.T) { + err := ovnClient.CreateAcls(pgName, "", nil) + require.ErrorContains(t, err, "acl parent type must be 'pg' or 'ls'") + + err = ovnClient.CreateAcls(pgName, "wrong_key", nil) + require.ErrorContains(t, err, "acl parent type must be 'pg' or 'ls'") + }) +} + +func (suite *OvnClientTestSuite) testDeleteAcls() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-del-acls-pg" + lsName := "test-del-acls-ls" + matchPrefix := "outport == @ovn.sg.test_del_acl_pg && ip" + + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("delete all direction acls from port group", func(t *testing.T) { + priority := "5601" + basePort := 5601 + acls := make([]*ovnnb.ACL, 0, 5) + + // to-lport + for i := 0; i < 2; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + // from-lport + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(pgName, ovnnb.ACLDirectionFromLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + err = ovnClient.CreateAcls(pgName, portGroupKey, acls...) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 5) + + err = ovnClient.DeleteAcls(pgName, portGroupKey, "") + require.NoError(t, err) + + pg, err = ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Empty(t, pg.ACLs) + }) + + t.Run("delete one-way acls from port group", func(t *testing.T) { + priority := "5701" + basePort := 5701 + acls := make([]*ovnnb.ACL, 0, 5) + + // to-lport + for i := 0; i < 2; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + // from-lport + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(pgName, ovnnb.ACLDirectionFromLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + err = ovnClient.CreateAcls(pgName, portGroupKey, acls...) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 5) + + /* delete to-lport direction acl */ + err = ovnClient.DeleteAcls(pgName, portGroupKey, ovnnb.ACLDirectionToLport) + require.NoError(t, err) + + pg, err = ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Len(t, pg.ACLs, 3) + + /* delete from-lport direction acl */ + err = ovnClient.DeleteAcls(pgName, portGroupKey, ovnnb.ACLDirectionFromLport) + require.NoError(t, err) + + pg, err = ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Empty(t, pg.ACLs) + }) + + t.Run("delete all direction acls from logical switch", func(t *testing.T) { + priority := "5601" + basePort := 5601 + acls := make([]*ovnnb.ACL, 0, 5) + + // to-lport + for i := 0; i < 2; i++ { + match := fmt.Sprintf("%s && udp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(lsName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + // from-lport + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && udp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(lsName, ovnnb.ACLDirectionFromLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + err = ovnClient.CreateAcls(lsName, logicalSwitchKey, acls...) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Len(t, ls.ACLs, 5) + + err = ovnClient.DeleteAcls(lsName, logicalSwitchKey, "") + require.NoError(t, err) + + ls, err = ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Empty(t, ls.ACLs) + }) + + t.Run("delete one-way acls from logical switch", func(t *testing.T) { + priority := "5701" + basePort := 5701 + acls := make([]*ovnnb.ACL, 0, 5) + + // to-lport + for i := 0; i < 2; i++ { + match := fmt.Sprintf("%s && udp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(lsName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + // from-lport + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && udp.dst == %d", matchPrefix, basePort+i) + acl, err := ovnClient.newAcl(lsName, ovnnb.ACLDirectionFromLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + acls = append(acls, acl) + } + + err = ovnClient.CreateAcls(lsName, logicalSwitchKey, acls...) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Len(t, ls.ACLs, 5) + + /* delete to-lport direction acl */ + err = ovnClient.DeleteAcls(lsName, logicalSwitchKey, ovnnb.ACLDirectionToLport) + require.NoError(t, err) + + ls, err = ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Len(t, ls.ACLs, 3) + + /* delete from-lport direction acl */ + err = ovnClient.DeleteAcls(lsName, logicalSwitchKey, ovnnb.ACLDirectionFromLport) + require.NoError(t, err) + + ls, err = ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Empty(t, ls.ACLs) + }) +} + +func (suite *OvnClientTestSuite) testGetAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test_get_acl_pg" + priority := "2000" + match := "ip4.dst == 100.64.0.0/16" + + err := ovnClient.CreateBareAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + + t.Run("direction, priority and match are same", func(t *testing.T) { + t.Parallel() + acl, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, false) + require.NoError(t, err) + require.Equal(t, ovnnb.ACLDirectionToLport, acl.Direction) + require.Equal(t, 2000, acl.Priority) + require.Equal(t, match, acl.Match) + require.Equal(t, ovnnb.ACLActionAllowRelated, acl.Action) + }) + + t.Run("direction, priority and match are not all same", func(t *testing.T) { + t.Parallel() + + _, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, priority, match, false) + require.ErrorContains(t, err, "not found acl") + + _, err = ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, "1010", match, false) + require.ErrorContains(t, err, "not found acl") + + _, err = ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, priority, match+" && tcp", false) + require.ErrorContains(t, err, "not found acl") + }) + + t.Run("should no err when direction, priority and match are not all same but ignoreNotFound=true", func(t *testing.T) { + t.Parallel() + + _, err := ovnClient.GetAcl(pgName, ovnnb.ACLDirectionFromLport, priority, match, true) + require.NoError(t, err) + }) + + t.Run("no acl belongs to parent exist", func(t *testing.T) { + t.Parallel() + + _, err := ovnClient.GetAcl(pgName+"_1", ovnnb.ACLDirectionFromLport, priority, match, false) + require.ErrorContains(t, err, "not found acl") + }) +} + +func (suite *OvnClientTestSuite) testListAcls() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-list-acl-pg" + basePort := 50000 + + matchPrefix := "outport == @ovn.sg.test_list_acl_pg && ip" + // create two to-lport acl + for i := 0; i < 2; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + err := ovnClient.CreateBareAcl(pgName, ovnnb.ACLDirectionToLport, "9999", match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + } + + // create two from-lport acl + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + err := ovnClient.CreateBareAcl(pgName, ovnnb.ACLDirectionFromLport, "9999", match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + } + + /* list all direction acl */ + out, err := ovnClient.ListAcls("", nil) + require.NoError(t, err) + count := 0 + for _, v := range out { + if strings.Contains(v.Match, matchPrefix) { + count++ + } + } + require.Equal(t, count, 5) +} + +func (suite *OvnClientTestSuite) test_newAcl() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-new-acl-pg" + priority := "1000" + match := "outport==@ovn.sg.test_create_acl_pg && ip" + options := func(acl *ovnnb.ACL) { + acl.Log = true + acl.Severity = &ovnnb.ACLSeverityWarning + acl.Name = &pgName + } + + expect := &ovnnb.ACL{ + Name: &pgName, + Action: ovnnb.ACLActionAllowRelated, + Direction: ovnnb.ACLDirectionToLport, + Match: match, + Priority: 1000, + ExternalIDs: map[string]string{ + aclParentKey: pgName, + }, + Log: true, + Severity: &ovnnb.ACLSeverityWarning, + } + + acl, err := ovnClient.newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated, options) + require.NoError(t, err) + expect.UUID = acl.UUID + require.Equal(t, expect, acl) +} + +func (suite *OvnClientTestSuite) testnewNetworkPolicyAclMatch() { + t := suite.T() + t.Parallel() + + pgName := "test-new-acl-m-pg" + asAllowName := "test.default.xx.allow.ipv4" + asExceptName := "test.default.xx.except.ipv4" + + t.Run("has ingress network policy port", func(t *testing.T) { + t.Parallel() + + npp := mockNetworkPolicyPort() + matches := newNetworkPolicyAclMatch(pgName, asAllowName, asExceptName, kubeovnv1.ProtocolIPv4, ovnnb.ACLDirectionToLport, npp) + require.Equal(t, []string{ + fmt.Sprintf("outport == @%s && ip && ip4.src == $%s && ip4.src != $%s && tcp.dst == %d", pgName, asAllowName, asExceptName, npp[0].Port.IntVal), + fmt.Sprintf("outport == @%s && ip && ip4.src == $%s && ip4.src != $%s && %d <= tcp.dst <= %d", pgName, asAllowName, asExceptName, npp[1].Port.IntVal, *npp[1].EndPort), + }, matches) + }) + + t.Run("has egress network policy port", func(t *testing.T) { + t.Parallel() + + npp := mockNetworkPolicyPort() + + matches := newNetworkPolicyAclMatch(pgName, asAllowName, asExceptName, kubeovnv1.ProtocolIPv4, ovnnb.ACLDirectionFromLport, npp) + require.Equal(t, []string{ + fmt.Sprintf("inport == @%s && ip && ip4.dst == $%s && ip4.dst != $%s && tcp.dst == %d", pgName, asAllowName, asExceptName, npp[0].Port.IntVal), + fmt.Sprintf("inport == @%s && ip && ip4.dst == $%s && ip4.dst != $%s && %d <= tcp.dst <= %d", pgName, asAllowName, asExceptName, npp[1].Port.IntVal, *npp[1].EndPort), + }, matches) + }) + + t.Run("network policy port is nil", func(t *testing.T) { + t.Parallel() + + matches := newNetworkPolicyAclMatch(pgName, asAllowName, asExceptName, kubeovnv1.ProtocolIPv4, ovnnb.ACLDirectionToLport, nil) + require.Equal(t, []string{ + fmt.Sprintf("outport == @%s && ip && ip4.src == $%s && ip4.src != $%s", pgName, asAllowName, asExceptName), + }, matches) + }) + + t.Run("has network policy port but port is not set", func(t *testing.T) { + t.Parallel() + + npp := mockNetworkPolicyPort() + npp[1].Port = nil + + matches := newNetworkPolicyAclMatch(pgName, asAllowName, asExceptName, kubeovnv1.ProtocolIPv4, ovnnb.ACLDirectionToLport, npp) + require.Equal(t, []string{ + fmt.Sprintf("outport == @%s && ip && ip4.src == $%s && ip4.src != $%s && tcp.dst == %d", pgName, asAllowName, asExceptName, npp[0].Port.IntVal), + fmt.Sprintf("outport == @%s && ip && ip4.src == $%s && ip4.src != $%s && tcp", pgName, asAllowName, asExceptName), + }, matches) + }) + + t.Run("has network policy port but endPort is not set", func(t *testing.T) { + t.Parallel() + + npp := mockNetworkPolicyPort() + npp[1].EndPort = nil + + matches := newNetworkPolicyAclMatch(pgName, asAllowName, asExceptName, kubeovnv1.ProtocolIPv4, ovnnb.ACLDirectionToLport, npp) + require.Equal(t, []string{ + fmt.Sprintf("outport == @%s && ip && ip4.src == $%s && ip4.src != $%s && tcp.dst == %d", pgName, asAllowName, asExceptName, npp[0].Port.IntVal), + fmt.Sprintf("outport == @%s && ip && ip4.src == $%s && ip4.src != $%s && tcp.dst == %d", pgName, asAllowName, asExceptName, npp[1].Port.IntVal), + }, matches) + }) +} + +func (suite *OvnClientTestSuite) test_aclFilter() { + t := suite.T() + t.Parallel() + + pgName := "test-filter-acl-pg" + + acls := make([]*ovnnb.ACL, 0) + + t.Run("filter acl", func(t *testing.T) { + t.Parallel() + + match := "outport == @ovn.sg.test_list_acl_pg && ip" + // create two to-lport acl + for i := 0; i < 2; i++ { + acl := newAcl(pgName, ovnnb.ACLDirectionToLport, "9999", match, ovnnb.ACLActionAllowRelated) + acls = append(acls, acl) + } + + // create two to-lport acl without acl parent key + for i := 0; i < 2; i++ { + acl := newAcl(pgName, ovnnb.ACLDirectionToLport, "9999", match, ovnnb.ACLActionAllowRelated) + acl.ExternalIDs = nil + acls = append(acls, acl) + } + + // create two from-lport acl + for i := 0; i < 3; i++ { + acl := newAcl(pgName, ovnnb.ACLDirectionFromLport, "9999", match, ovnnb.ACLActionAllowRelated) + acls = append(acls, acl) + } + + // create four from-lport acl with other acl parent key + for i := 0; i < 4; i++ { + acl := newAcl(pgName, ovnnb.ACLDirectionFromLport, "9999", match, ovnnb.ACLActionAllowRelated) + acl.ExternalIDs[aclParentKey] = pgName + "-test" + acls = append(acls, acl) + } + + /* include all direction acl */ + filterFunc := aclFilter("", nil) + count := 0 + for _, acl := range acls { + if filterFunc(acl) { + count++ + } + } + require.Equal(t, count, 11) + + /* include all direction acl with external ids */ + filterFunc = aclFilter("", map[string]string{aclParentKey: pgName}) + count = 0 + for _, acl := range acls { + if filterFunc(acl) { + count++ + } + } + require.Equal(t, count, 5) + + /* include to-lport acl */ + filterFunc = aclFilter(ovnnb.ACLDirectionToLport, nil) + count = 0 + for _, acl := range acls { + if filterFunc(acl) { + count++ + } + } + require.Equal(t, count, 4) + + /* include to-lport acl with external ids */ + filterFunc = aclFilter(ovnnb.ACLDirectionToLport, map[string]string{aclParentKey: pgName}) + count = 0 + for _, acl := range acls { + if filterFunc(acl) { + count++ + } + } + require.Equal(t, count, 2) + + /* include from-lport acl */ + filterFunc = aclFilter(ovnnb.ACLDirectionFromLport, nil) + count = 0 + for _, acl := range acls { + if filterFunc(acl) { + count++ + } + } + require.Equal(t, count, 7) + + /* include all from-lport acl with acl parent key*/ + filterFunc = aclFilter(ovnnb.ACLDirectionFromLport, map[string]string{aclParentKey: ""}) + count = 0 + for _, acl := range acls { + if filterFunc(acl) { + count++ + } + } + require.Equal(t, count, 7) + }) + + t.Run("result should exclude acl when externalIDs's length is not equal", func(t *testing.T) { + t.Parallel() + + match := "outport == @ovn.sg.test_filter_acl_pg && ip" + acl := newAcl(pgName, ovnnb.ACLDirectionToLport, "9999", match, ovnnb.ACLActionAllowRelated) + + filterFunc := aclFilter("", map[string]string{ + aclParentKey: pgName, + "key": "value", + }) + + require.False(t, filterFunc(acl)) + }) +} diff --git a/pkg/ovs/ovn-nb-address_set.go b/pkg/ovs/ovn-nb-address_set.go new file mode 100644 index 00000000000..a3881fe08ab --- /dev/null +++ b/pkg/ovs/ovn-nb-address_set.go @@ -0,0 +1,187 @@ +package ovs + +import ( + "context" + "fmt" + + "github.com/ovn-org/libovsdb/client" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +// CreateAddressSet create address set with external ids +func (c *ovnClient) CreateAddressSet(asName string, externalIDs map[string]string) error { + // ovn acl doesn't support address_set name with '-' + if matched := matchAddressSetName(asName); !matched { + return fmt.Errorf("address set %s must match `[a-zA-Z_.][a-zA-Z_.0-9]*`", asName) + } + + exists, err := c.AddressSetExists(asName) + if err != nil { + return err + } + + // found, ignore + if exists { + return nil + } + + as := &ovnnb.AddressSet{ + Name: asName, + ExternalIDs: externalIDs, + } + + ops, err := c.ovnNbClient.Create(as) + if err != nil { + return fmt.Errorf("generate operations for creating address set %s: %v", asName, err) + } + + if err = c.Transact("as-add", ops); err != nil { + return fmt.Errorf("create address set %s: %v", asName, err) + } + + return nil +} + +// AddressSetUpdateAddress update addresses, +// clear addresses when addresses is empty +func (c *ovnClient) AddressSetUpdateAddress(asName string, addresses ...string) error { + as, err := c.GetAddressSet(asName, false) + if err != nil { + return fmt.Errorf("get address set %s: %v", asName, err) + } + + // clear addresses when addresses is empty + as.Addresses = addresses + + if err := c.UpdateAddressSet(as, &as.Addresses); err != nil { + return fmt.Errorf("set address set %s addresses %v: %v", asName, addresses, err) + } + + return nil +} + +// UpdateAddressSet update address set +func (c *ovnClient) UpdateAddressSet(as *ovnnb.AddressSet, fields ...interface{}) error { + if as == nil { + return fmt.Errorf("address_set is nil") + } + + op, err := c.Where(as).Update(as, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating address set %s: %v", as.Name, err) + } + + if err = c.Transact("as-update", op); err != nil { + return fmt.Errorf("update address set %s: %v", as.Name, err) + } + + return nil +} + +func (c *ovnClient) DeleteAddressSet(asName string) error { + as, err := c.GetAddressSet(asName, true) + if err != nil { + return fmt.Errorf("get address set %s: %v", asName, err) + } + + // not found, skip + if as == nil { + return nil + } + + op, err := c.Where(as).Delete() + if err != nil { + return err + } + + if err := c.Transact("as-del", op); err != nil { + return fmt.Errorf("delete address set %s: %v", asName, err) + } + + return nil +} + +// DeleteAddressSets delete several address set once +func (c *ovnClient) DeleteAddressSets(externalIDs map[string]string) error { + // it's dangerous when externalIDs is empty, it will delete all address set + if len(externalIDs) == 0 { + return nil + } + + op, err := c.WhereCache(addressSetFilter(externalIDs)).Delete() + if err != nil { + return fmt.Errorf("generate operation for deleting address sets with external IDs %v: %v", externalIDs, err) + } + + if err := c.Transact("ass-del", op); err != nil { + return fmt.Errorf("delete address sets with external IDs %v: %v", externalIDs, err) + } + + return nil +} + +// GetAddressSet get address set by name +func (c *ovnClient) GetAddressSet(asName string, ignoreNotFound bool) (*ovnnb.AddressSet, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + as := &ovnnb.AddressSet{Name: asName} + if err := c.ovnNbClient.Get(ctx, as); err != nil { + if ignoreNotFound && err == client.ErrNotFound { + return nil, nil + } + return nil, fmt.Errorf("get address set %s: %v", asName, err) + } + + return as, nil +} + +func (c *ovnClient) AddressSetExists(name string) (bool, error) { + as, err := c.GetAddressSet(name, true) + return as != nil, err +} + +// ListAddressSets list address set by external_ids +func (c *ovnClient) ListAddressSets(externalIDs map[string]string) ([]ovnnb.AddressSet, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + asList := make([]ovnnb.AddressSet, 0) + + if err := c.WhereCache(addressSetFilter(externalIDs)).List(ctx, &asList); err != nil { + return nil, fmt.Errorf("list address set: %v", err) + } + + return asList, nil +} + +// addressSetFilter filter address set which match the given externalIDs, +// result should include all to-lport and from-lport acls when direction is empty, +// result should include all acls when externalIDs is empty, +// result should include all acls which externalIDs[key] is not empty when externalIDs[key] is "" +func addressSetFilter(externalIDs map[string]string) func(as *ovnnb.AddressSet) bool { + return func(as *ovnnb.AddressSet) bool { + if len(as.ExternalIDs) < len(externalIDs) { + return false + } + + if len(as.ExternalIDs) != 0 { + for k, v := range externalIDs { + // if only key exist but not value in externalIDs, we should include this lsp, + // it's equal to shell command `ovn-nbctl --columns=xx find address_set external_ids:key!=\"\"` + if len(v) == 0 { + if len(as.ExternalIDs[k]) == 0 { + return false + } + } else { + if as.ExternalIDs[k] != v { + return false + } + } + } + } + + return true + } +} diff --git a/pkg/ovs/ovn-nb-address_set_test.go b/pkg/ovs/ovn-nb-address_set_test.go new file mode 100644 index 00000000000..9e82213f020 --- /dev/null +++ b/pkg/ovs/ovn-nb-address_set_test.go @@ -0,0 +1,241 @@ +package ovs + +import ( + "fmt" + "testing" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/stretchr/testify/require" +) + +func newAddressSet(name string, externalIDs map[string]string) *ovnnb.AddressSet { + return &ovnnb.AddressSet{ + Name: name, + ExternalIDs: externalIDs, + } +} + +func (suite *OvnClientTestSuite) testCreateAddressSet() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + asName := "test_create_as" + + t.Run("create address set", func(t *testing.T) { + err := ovnClient.CreateAddressSet(asName, map[string]string{ + sgKey: "test-sg", + }) + require.NoError(t, err) + + as, err := ovnClient.GetAddressSet(asName, false) + require.NoError(t, err) + require.NotEmpty(t, as.UUID) + require.Equal(t, asName, as.Name) + require.Equal(t, map[string]string{ + sgKey: "test-sg", + }, as.ExternalIDs) + }) + + t.Run("error occur because of invalid address set name", func(t *testing.T) { + err := ovnClient.CreateAddressSet("test-create-as", map[string]string{ + sgKey: "test-sg", + }) + require.Error(t, err) + }) +} + +func (suite *OvnClientTestSuite) testAddressSetUpdateAddress() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + asName := "test_update_address_as" + addresses := []string{"1.2.3.4", "1.2.3.6", "1.2.3.7"} + + err := ovnClient.CreateAddressSet(asName, map[string]string{ + sgKey: "test-sg", + }) + require.NoError(t, err) + + t.Run("update address set v4 addresses", func(t *testing.T) { + err = ovnClient.AddressSetUpdateAddress(asName, addresses...) + require.NoError(t, err) + + as, err := ovnClient.GetAddressSet(asName, false) + require.NoError(t, err) + require.Equal(t, addresses, as.Addresses) + }) + + t.Run("update address set v6 addresses", func(t *testing.T) { + addresses := []string{"fe80::20c:29ff:fee4:16cc", "fe80::20c:29ff:fee4:1611"} + err = ovnClient.AddressSetUpdateAddress(asName, addresses...) + require.NoError(t, err) + + as, err := ovnClient.GetAddressSet(asName, false) + require.NoError(t, err) + require.Equal(t, addresses, as.Addresses) + }) + + t.Run("clear address set addresses", func(t *testing.T) { + err = ovnClient.AddressSetUpdateAddress(asName) + require.NoError(t, err) + + as, err := ovnClient.GetAddressSet(asName, false) + require.NoError(t, err) + require.Empty(t, as.Addresses) + }) +} + +func (suite *OvnClientTestSuite) testDeleteAddressSet() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + asName := "test_delete_as" + + t.Run("no err when delete existent address set", func(t *testing.T) { + t.Parallel() + + err := ovnClient.CreateAddressSet(asName, nil) + require.NoError(t, err) + + err = ovnClient.DeleteAddressSet(asName) + require.NoError(t, err) + + _, err = ovnClient.GetAddressSet(asName, false) + require.ErrorContains(t, err, "object not found") + }) + + t.Run("no err when delete non-existent logical router", func(t *testing.T) { + t.Parallel() + err := ovnClient.DeleteAddressSet("test-delete-as-non-existent") + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testDeleteAddressSets() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-del-ass-pg" + asPrefix := "test_del_ass" + externalIDs := map[string]string{sgKey: pgName} + + for i := 0; i < 3; i++ { + asName := fmt.Sprintf("%s_%d", asPrefix, i) + err := ovnClient.CreateAddressSet(asName, externalIDs) + require.NoError(t, err) + } + + // create a new address set with no sg name, it should't be deleted + asName := fmt.Sprintf("%s_%d", asPrefix, 3) + err := ovnClient.CreateAddressSet(asName, nil) + require.NoError(t, err) + + err = ovnClient.DeleteAddressSets(externalIDs) + require.NoError(t, err) + + // it should't be deleted + _, err = ovnClient.GetAddressSet(asName, false) + require.NoError(t, err) + + // should delete + ass, err := ovnClient.ListAddressSets(externalIDs) + require.NoError(t, err) + require.Empty(t, ass) +} + +func (suite *OvnClientTestSuite) testListAddressSets() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + + asName := "test_list_as_exist_key" + + err := ovnClient.CreateAddressSet(asName, map[string]string{sgKey: "sg", "direction": "to-lport", "key": "value"}) + require.NoError(t, err) + + ass, err := ovnClient.ListAddressSets(map[string]string{sgKey: "sg", "key": "value"}) + require.NoError(t, err) + require.Len(t, ass, 1) + require.Equal(t, asName, ass[0].Name) + +} + +func (suite *OvnClientTestSuite) test_addressSetFilter() { + t := suite.T() + t.Parallel() + + pgName := "test-filter-as-pg" + asPrefix := "test-filter-as" + + ass := make([]*ovnnb.AddressSet, 0) + + t.Run("filter address set", func(t *testing.T) { + // create two to-lport acl + i := 0 + for ; i < 3; i++ { + as := newAddressSet(fmt.Sprintf("%s-%d", asPrefix, i), map[string]string{ + sgKey: pgName, + }) + ass = append(ass, as) + } + + // create two as without sg name + for ; i < 5; i++ { + as := newAddressSet(fmt.Sprintf("%s-%d", asPrefix, i), nil) + ass = append(ass, as) + } + + // create two as with other sg name + for ; i < 6; i++ { + as := newAddressSet(fmt.Sprintf("%s-%d", asPrefix, i), map[string]string{ + sgKey: pgName + "-other", + }) + ass = append(ass, as) + } + + /* include all as */ + filterFunc := addressSetFilter(nil) + count := 0 + for _, as := range ass { + if filterFunc(as) { + count++ + } + } + require.Equal(t, count, 6) + + filterFunc = addressSetFilter(map[string]string{sgKey: ""}) + count = 0 + for _, as := range ass { + if filterFunc(as) { + count++ + } + } + require.Equal(t, count, 4) + + /* include all as with sg name */ + filterFunc = addressSetFilter(map[string]string{sgKey: pgName}) + count = 0 + for _, as := range ass { + if filterFunc(as) { + count++ + } + } + require.Equal(t, count, 3) + }) + + t.Run("result should exclude as when externalIDs's length is not equal", func(t *testing.T) { + asName := "test_filter_as_mismatch_length" + as := newAddressSet(asName, map[string]string{ + sgKey: pgName, + }) + + filterFunc := addressSetFilter(map[string]string{sgKey: pgName, "direction": "to-lport"}) + out := filterFunc(as) + require.False(t, out) + }) +} diff --git a/pkg/ovs/ovn-nb-dhcp_options.go b/pkg/ovs/ovn-nb-dhcp_options.go new file mode 100644 index 00000000000..bab57422091 --- /dev/null +++ b/pkg/ovs/ovn-nb-dhcp_options.go @@ -0,0 +1,334 @@ +package ovs + +import ( + "context" + "fmt" + "strings" + + "github.com/ovn-org/libovsdb/ovsdb" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +type DHCPOptionsUUIDs struct { + DHCPv4OptionsUUID string + DHCPv6OptionsUUID string +} + +func (c *ovnClient) CreateDHCPOptions(lsName, cidr, options string) error { + dhcpOpt, err := newDHCPOptions(lsName, cidr, options) + if err != nil { + return err + } + + op, err := c.ovnNbClient.Create(dhcpOpt) + if err != nil { + return fmt.Errorf("generate operations for creating dhcp options 'cidr %s options %s': %v", cidr, options, err) + } + + if err = c.Transact("acl-create", op); err != nil { + return fmt.Errorf("create dhcp options 'cidr %s options %s': %v", cidr, options, err) + } + + return nil +} + +func (c *ovnClient) UpdateDHCPOptions(subnet *kubeovnv1.Subnet) (*DHCPOptionsUUIDs, error) { + lsName := subnet.Name + cidrBlock := subnet.Spec.CIDRBlock + gateway := subnet.Spec.Gateway + enableDHCP := subnet.Spec.EnableDHCP + + /* delete dhcp options */ + if !enableDHCP { + if err := c.DeleteDHCPOptions(lsName, subnet.Spec.Protocol); err != nil { + return nil, fmt.Errorf("delete dhcp options for logical switch %s: %v", lsName, err) + } + return &DHCPOptionsUUIDs{}, nil + } + + /* update dhcp options*/ + var v4CIDR, v6CIDR string + var v4Gateway string + switch util.CheckProtocol(cidrBlock) { + case kubeovnv1.ProtocolIPv4: + v4CIDR = cidrBlock + v4Gateway = gateway + case kubeovnv1.ProtocolIPv6: + v6CIDR = cidrBlock + case kubeovnv1.ProtocolDual: + cidrBlocks := strings.Split(cidrBlock, ",") + gateways := strings.Split(gateway, ",") + v4CIDR, v6CIDR = cidrBlocks[0], cidrBlocks[1] + v4Gateway = gateways[0] + } + + dhcpV4OptUUID, err := c.updateDHCPv4Options(lsName, v4CIDR, v4Gateway, subnet.Spec.DHCPv4Options) + if err != nil { + return nil, fmt.Errorf("update IPv4 dhcp options for logical switch %s: %v", lsName, err) + } + + dhcpV6OptUUID, err := c.updateDHCPv6Options(lsName, v6CIDR, subnet.Spec.DHCPv6Options) + if err != nil { + return nil, fmt.Errorf("update IPv6 dhcp options for logical switch %s: %v", lsName, err) + } + + return &DHCPOptionsUUIDs{ + dhcpV4OptUUID, + dhcpV6OptUUID, + }, nil +} + +func (c *ovnClient) updateDHCPv4Options(lsName, cidr, gateway, options string) (uuid string, err error) { + protocol := util.CheckProtocol(cidr) + if protocol != kubeovnv1.ProtocolIPv4 { + return "", fmt.Errorf("cidr %s must be a valid ipv4 address", cidr) + } + + dhcpOpt, err := c.GetDHCPOptions(lsName, protocol, true) + if err != nil { + return + } + + if len(options) == 0 { + mac := util.GenerateMac() + if dhcpOpt != nil && len(dhcpOpt.Options) != 0 { + mac = dhcpOpt.Options["server_mac"] + } + + options = fmt.Sprintf("lease_time=%d,router=%s,server_id=%s,server_mac=%s", 3600, gateway, "169.254.0.254", mac) + } + + /* update */ + if dhcpOpt != nil { + dhcpOpt.Cidr = cidr + dhcpOpt.Options = parseDHCPOptions(options) + return dhcpOpt.UUID, c.updateDHCPOptions(dhcpOpt, &dhcpOpt.Cidr, &dhcpOpt.Options) + } + + /* create */ + if err := c.CreateDHCPOptions(lsName, cidr, options); err != nil { + return "", fmt.Errorf("create dhcp options: %v", err) + } + + dhcpOpt, err = c.GetDHCPOptions(lsName, protocol, false) + if err != nil { + return "", err + } + + return dhcpOpt.UUID, nil +} + +func (c *ovnClient) updateDHCPv6Options(lsName, cidr, options string) (uuid string, err error) { + protocol := util.CheckProtocol(cidr) + if protocol != kubeovnv1.ProtocolIPv6 { + return "", fmt.Errorf("cidr %s must be a valid ipv4 address", cidr) + } + + dhcpOpt, err := c.GetDHCPOptions(lsName, protocol, true) + if err != nil { + return + } + + if len(options) == 0 { + mac := util.GenerateMac() + if dhcpOpt != nil && len(dhcpOpt.Options) != 0 { + mac = dhcpOpt.Options["server_id"] + } + + options = fmt.Sprintf("server_id=%s", mac) + } + + /* update */ + if dhcpOpt != nil { + dhcpOpt.Cidr = cidr + dhcpOpt.Options = parseDHCPOptions(options) + return dhcpOpt.UUID, c.updateDHCPOptions(dhcpOpt, &dhcpOpt.Cidr, &dhcpOpt.Options) + } + + /* create */ + if err := c.CreateDHCPOptions(lsName, cidr, options); err != nil { + return "", fmt.Errorf("create dhcp options: %v", err) + } + + dhcpOpt, err = c.GetDHCPOptions(lsName, protocol, false) + if err != nil { + return "", err + } + + return dhcpOpt.UUID, nil +} + +// updateDHCPOptions update dhcp options +func (c *ovnClient) updateDHCPOptions(dhcpOpt *ovnnb.DHCPOptions, fields ...interface{}) error { + if dhcpOpt == nil { + return fmt.Errorf("dhcp_options is nil") + } + + op, err := c.ovnNbClient.Where(dhcpOpt).Update(dhcpOpt, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating dhcp options %s: %v", dhcpOpt.UUID, err) + } + + if err = c.Transact("dhcp-options-update", op); err != nil { + return fmt.Errorf("update dhcp options %s: %v", dhcpOpt.UUID, err) + } + + return nil +} + +// DeleteDHCPOptionsByUUIDs delete dhcp options by uuid +func (c *ovnClient) DeleteDHCPOptionsByUUIDs(uuidList ...string) error { + ops := make([]ovsdb.Operation, 0, len(uuidList)) + for _, uuid := range uuidList { + dhcpOptions := &ovnnb.DHCPOptions{ + UUID: uuid, + } + + op, err := c.Where(dhcpOptions).Delete() + if err != nil { + return err + } + ops = append(ops, op...) + } + + if err := c.Transact("dhcp-options-del", ops); err != nil { + return fmt.Errorf("delete dhcp options %v: %v", uuidList, err) + } + + return nil +} + +// DeleteDHCPOptions delete dhcp options which belongs to logical switch +func (c *ovnClient) DeleteDHCPOptions(lsName string, protocol string) error { + if protocol == kubeovnv1.ProtocolDual { + protocol = "" + } + externalIDs := map[string]string{ + logicalSwitchKey: lsName, + "protocol": protocol, // list all protocol dhcp options when protocol is "" + } + + op, err := c.WhereCache(dhcpOptionsFilter(true, externalIDs)).Delete() + if err != nil { + return fmt.Errorf("generate operation for deleting dhcp options: %v", err) + } + + if err = c.Transact("dhcp-options-del", op); err != nil { + return fmt.Errorf("delete logical switch %s dhcp options: %v", lsName, err) + } + + return nil +} + +// GetDHCPOptions get dhcp options, +// a dhcp options is uniquely identified by switch(lsName) and protocol +func (c *ovnClient) GetDHCPOptions(lsName, protocol string, ignoreNotFound bool) (*ovnnb.DHCPOptions, error) { + if len(lsName) == 0 { + return nil, fmt.Errorf("the logical router name is required") + } + + if protocol != kubeovnv1.ProtocolIPv4 && protocol != kubeovnv1.ProtocolIPv6 { + return nil, fmt.Errorf("protocol must be IPv4 or IPv6") + } + + dhcpOptList, err := c.ListDHCPOptions(true, map[string]string{ + logicalSwitchKey: lsName, + "protocol": protocol, + }) + + if err != nil { + return nil, fmt.Errorf("get logical switch %s %s dhcp options: %v", lsName, protocol, err) + } + + // not found + if len(dhcpOptList) == 0 { + if ignoreNotFound { + return nil, nil + } + + return nil, fmt.Errorf("not found logical switch %s %s dhcp options: %v", lsName, protocol, err) + } + + if len(dhcpOptList) > 1 { + return nil, fmt.Errorf("more than one %s dhcp options in logical switch %s", protocol, lsName) + } + + return &dhcpOptList[0], nil +} + +// ListDHCPOptions list dhcp options which match the given externalIDs +func (c *ovnClient) ListDHCPOptions(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.DHCPOptions, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + dhcpOptList := make([]ovnnb.DHCPOptions, 0) + + if err := c.WhereCache(dhcpOptionsFilter(needVendorFilter, externalIDs)).List(ctx, &dhcpOptList); err != nil { + return nil, fmt.Errorf("list dhcp options with external IDs %v: %v", externalIDs, err) + } + + return dhcpOptList, nil +} + +func (c *ovnClient) DHCPOptionsExists(lsName, cidr string) (bool, error) { + dhcpOpt, err := c.GetDHCPOptions(lsName, cidr, true) + return dhcpOpt != nil, err +} + +// newDHCPOptions return dhcp options with basic information +func newDHCPOptions(lsName, cidr, options string) (*ovnnb.DHCPOptions, error) { + if len(cidr) == 0 || len(lsName) == 0 { + return nil, fmt.Errorf("logical switch name %s and cidr %s is required", lsName, cidr) + } + + protocol := util.CheckProtocol(cidr) + if len(protocol) == 0 { + return nil, fmt.Errorf("cidr %s must be a valid ipv4 or ipv6 address", cidr) + } + + return &ovnnb.DHCPOptions{ + Cidr: cidr, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "protocol": protocol, + "vendor": util.CniTypeName, + }, + Options: parseDHCPOptions(options), + }, nil +} + +// dhcpOptionsFilter filter dhcp options which match the given externalIDs, +// result should include all dhcp options when externalIDs is empty, +// result should include all dhcp options which externalIDs[key] is not empty when externalIDs[key] is "" +func dhcpOptionsFilter(needVendorFilter bool, externalIDs map[string]string) func(dhcpOpt *ovnnb.DHCPOptions) bool { + return func(dhcpOpt *ovnnb.DHCPOptions) bool { + if needVendorFilter && (len(dhcpOpt.ExternalIDs) == 0 || dhcpOpt.ExternalIDs["vendor"] != util.CniTypeName) { + return false + } + + if len(dhcpOpt.ExternalIDs) < len(externalIDs) { + return false + } + + if len(dhcpOpt.ExternalIDs) != 0 { + for k, v := range externalIDs { + // if only key exist but not value in externalIDs, we should include this dhcp options, + // it's equal to shell command `ovn-nbctl --columns=xx find dhcp_options external_ids:key!=\"\"` + if len(v) == 0 { + if len(dhcpOpt.ExternalIDs[k]) == 0 { + return false + } + } else { + if dhcpOpt.ExternalIDs[k] != v { + return false + } + } + } + } + + return true + } +} diff --git a/pkg/ovs/ovn-nb-dhcp_options_test.go b/pkg/ovs/ovn-nb-dhcp_options_test.go new file mode 100644 index 00000000000..d007ce77365 --- /dev/null +++ b/pkg/ovs/ovn-nb-dhcp_options_test.go @@ -0,0 +1,467 @@ +package ovs + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func mockSubnet(name string, enableDHCP bool) *kubeovnv1.Subnet { + return &kubeovnv1.Subnet{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + }, + Spec: kubeovnv1.SubnetSpec{ + CIDRBlock: "10.244.0.0/16,fc00::af4:0/112", + Gateway: "10.244.0.1,fc00::0af4:01", + Protocol: kubeovnv1.ProtocolDual, + EnableDHCP: enableDHCP, + }, + } +} + +func (suite *OvnClientTestSuite) testUpdateDHCPOptions() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-update-dhcp-opt-ls" + subnet := mockSubnet(lsName, true) + + t.Run("update dhcp options", func(t *testing.T) { + uuid, err := ovnClient.UpdateDHCPOptions(subnet) + require.NoError(t, err) + + v4DHCPOpt, err := ovnClient.GetDHCPOptions(lsName, "IPv4", false) + require.NoError(t, err) + + v6DHCPOpt, err := ovnClient.GetDHCPOptions(lsName, "IPv6", false) + require.NoError(t, err) + + require.Equal(t, uuid.DHCPv4OptionsUUID, v4DHCPOpt.UUID) + require.Equal(t, uuid.DHCPv6OptionsUUID, v6DHCPOpt.UUID) + }) + + t.Run("delete dhcp options", func(t *testing.T) { + subnet.Spec.EnableDHCP = false + + uuid, err := ovnClient.UpdateDHCPOptions(subnet) + require.NoError(t, err) + require.Equal(t, &DHCPOptionsUUIDs{ + DHCPv4OptionsUUID: "", + DHCPv6OptionsUUID: "", + }, uuid) + + _, err = ovnClient.GetDHCPOptions(lsName, "IPv4", false) + require.ErrorContains(t, err, "not found") + + _, err = ovnClient.GetDHCPOptions(lsName, "IPv6", false) + require.ErrorContains(t, err, "not found") + }) +} + +func (suite *OvnClientTestSuite) test_updateDHCPv4Options() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-update-v4-dhcp-opt-ls" + cidr := "192.168.30.0/24" + gateway := "192.168.30.1" + var serverMac string + + t.Run("create dhcp options", func(t *testing.T) { + t.Run("without options", func(t *testing.T) { + uuid, err := ovnClient.updateDHCPv4Options(lsName, cidr, gateway, "") + require.NoError(t, err) + + dhcpOpt, err := ovnClient.GetDHCPOptions(lsName, "IPv4", false) + require.NoError(t, err) + + serverMac = dhcpOpt.Options["server_mac"] + + require.Equal(t, uuid, dhcpOpt.UUID) + require.Equal(t, cidr, dhcpOpt.Cidr) + require.Equal(t, map[string]string{ + "lease_time": "3600", + "router": "192.168.30.1", + "server_id": "169.254.0.254", + "server_mac": serverMac, + }, dhcpOpt.Options) + }) + + t.Run("with options", func(t *testing.T) { + lsName := "test-update-v4-dhcp-opt-ls-with-opt" + options := fmt.Sprintf("lease_time=%d,router=%s,server_id=%s,server_mac=%s", 7200, gateway, "169.254.0.1", "00:00:00:11:22:33") + uuid, err := ovnClient.updateDHCPv4Options(lsName, cidr, gateway, options) + require.NoError(t, err) + + dhcpOpt, err := ovnClient.GetDHCPOptions(lsName, "IPv4", false) + require.NoError(t, err) + + require.Equal(t, uuid, dhcpOpt.UUID) + require.Equal(t, cidr, dhcpOpt.Cidr) + require.Equal(t, map[string]string{ + "lease_time": "7200", + "router": "192.168.30.1", + "server_id": "169.254.0.1", + "server_mac": "00:00:00:11:22:33", + }, dhcpOpt.Options) + }) + }) + + t.Run("update dhcp options", func(t *testing.T) { + uuid, err := ovnClient.updateDHCPv4Options(lsName, cidr, gateway, "") + require.NoError(t, err) + + dhcpOpt, err := ovnClient.GetDHCPOptions(lsName, "IPv4", false) + require.NoError(t, err) + + require.Equal(t, uuid, dhcpOpt.UUID) + require.Equal(t, cidr, dhcpOpt.Cidr) + require.Equal(t, map[string]string{ + "lease_time": "3600", + "router": "192.168.30.1", + "server_id": "169.254.0.254", + "server_mac": serverMac, + }, dhcpOpt.Options) + }) +} + +func (suite *OvnClientTestSuite) test_updateDHCPv6Options() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-update-v6-dhcp-opt-ls" + cidr := "fd00::c0a8:6e01/120" + var serverID string + + t.Run("create dhcp options", func(t *testing.T) { + t.Run("without options", func(t *testing.T) { + uuid, err := ovnClient.updateDHCPv6Options(lsName, cidr, "") + require.NoError(t, err) + + dhcpOpt, err := ovnClient.GetDHCPOptions(lsName, "IPv6", false) + require.NoError(t, err) + + serverID = dhcpOpt.Options["server_id"] + + require.Equal(t, uuid, dhcpOpt.UUID) + require.Equal(t, cidr, dhcpOpt.Cidr) + require.Equal(t, map[string]string{ + "server_id": serverID, + }, dhcpOpt.Options) + }) + + t.Run("with options", func(t *testing.T) { + lsName := "test-update-v6-dhcp-opt-ls-with-opt" + options := fmt.Sprintf("server_id=%s", "00:00:00:55:22:33") + uuid, err := ovnClient.updateDHCPv6Options(lsName, cidr, options) + require.NoError(t, err) + + dhcpOpt, err := ovnClient.GetDHCPOptions(lsName, "IPv6", false) + require.NoError(t, err) + + require.Equal(t, uuid, dhcpOpt.UUID) + require.Equal(t, cidr, dhcpOpt.Cidr) + require.Equal(t, map[string]string{ + "server_id": "00:00:00:55:22:33", + }, dhcpOpt.Options) + }) + }) + + t.Run("update dhcp options", func(t *testing.T) { + uuid, err := ovnClient.updateDHCPv6Options(lsName, cidr, "") + require.NoError(t, err) + + dhcpOpt, err := ovnClient.GetDHCPOptions(lsName, "IPv6", false) + require.NoError(t, err) + + require.Equal(t, uuid, dhcpOpt.UUID) + require.Equal(t, cidr, dhcpOpt.Cidr) + require.Equal(t, map[string]string{ + "server_id": serverID, + }, dhcpOpt.Options) + }) +} + +func (suite *OvnClientTestSuite) testDeleteDHCPOptionsByUUIDs() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-del-dhcp-opt-uuid-ls" + v4CidrBlock := []string{"192.168.30.0/24", "192.168.40.0/24", "192.168.50.0/24"} + uuidList := make([]string, 0) + + // create three ipv4 dhcp options + for _, cidr := range v4CidrBlock { + err := ovnClient.CreateDHCPOptions(lsName, cidr, "") + require.NoError(t, err) + } + + out, err := ovnClient.ListDHCPOptions(true, map[string]string{logicalSwitchKey: lsName}) + require.NoError(t, err) + require.Len(t, out, 3) + for _, o := range out { + uuidList = append(uuidList, o.UUID) + } + + err = ovnClient.DeleteDHCPOptionsByUUIDs(uuidList...) + require.NoError(t, err) + + out, err = ovnClient.ListDHCPOptions(true, map[string]string{logicalSwitchKey: lsName}) + require.NoError(t, err) + require.Empty(t, out) +} + +func (suite *OvnClientTestSuite) testDeleteDHCPOptions() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-del-dhcp-opt-ls" + v4CidrBlock := []string{"192.168.30.0/24", "192.168.40.0/24", "192.168.50.0/24"} + v6CidrBlock := []string{"fd00::c0a8:6401/120", "fd00::c0a8:6e01/120"} + + prepare := func() { + // create three ipv4 dhcp options + for _, cidr := range v4CidrBlock { + err := ovnClient.CreateDHCPOptions(lsName, cidr, "") + require.NoError(t, err) + } + + // create two ipv6 dhcp options + for _, cidr := range v6CidrBlock { + err := ovnClient.CreateDHCPOptions(lsName, cidr, "") + require.NoError(t, err) + } + } + + t.Run("delete single protocol dhcp options", func(t *testing.T) { + prepare() + + /* delete ipv4 dhcp options */ + err := ovnClient.DeleteDHCPOptions(lsName, "IPv4") + require.NoError(t, err) + + out, err := ovnClient.ListDHCPOptions(true, map[string]string{logicalSwitchKey: lsName, "protocol": "IPv4"}) + require.NoError(t, err) + require.Empty(t, out) + + out, err = ovnClient.ListDHCPOptions(true, map[string]string{logicalSwitchKey: lsName, "protocol": "IPv6"}) + require.NoError(t, err) + require.Len(t, out, 2) + + /* delete ipv6 dhcp options */ + err = ovnClient.DeleteDHCPOptions(lsName, "IPv6") + require.NoError(t, err) + + out, err = ovnClient.ListDHCPOptions(true, map[string]string{logicalSwitchKey: lsName, "protocol": "IPv6"}) + require.NoError(t, err) + require.Empty(t, out) + }) + + t.Run("delete all protocol dhcp options", func(t *testing.T) { + prepare() + + err := ovnClient.DeleteDHCPOptions(lsName, kubeovnv1.ProtocolDual) + require.NoError(t, err) + + out, err := ovnClient.ListDHCPOptions(true, map[string]string{logicalSwitchKey: lsName, "protocol": "IPv6"}) + require.NoError(t, err) + require.Empty(t, out) + }) +} + +func (suite *OvnClientTestSuite) testGetDHCPOptions() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-get-dhcp-opt-ls" + + t.Run("ipv4 dhcp options", func(t *testing.T) { + cidr := "192.168.30.0/24" + protocol := kubeovnv1.ProtocolIPv4 + err := ovnClient.CreateDHCPOptions(lsName, cidr, "") + require.NoError(t, err) + + t.Run("found dhcp options", func(t *testing.T) { + _, err := ovnClient.GetDHCPOptions(lsName, protocol, false) + require.NoError(t, err) + }) + + t.Run("protocol is different", func(t *testing.T) { + _, err := ovnClient.GetDHCPOptions(lsName, kubeovnv1.ProtocolIPv6, false) + require.ErrorContains(t, err, "not found") + }) + + t.Run("logical switch name is different", func(t *testing.T) { + _, err := ovnClient.GetDHCPOptions(lsName+"x", protocol, false) + require.ErrorContains(t, err, "not found") + }) + + t.Run("not found and ignoreNotFound=true", func(t *testing.T) { + _, err := ovnClient.GetDHCPOptions(lsName+"x", protocol, true) + require.NoError(t, err) + }) + }) + + t.Run("ipv6 dhcp options", func(t *testing.T) { + cidr := "fd00::c0a8:6901/120" + protocol := kubeovnv1.ProtocolIPv6 + err := ovnClient.CreateDHCPOptions(lsName, cidr, "") + require.NoError(t, err) + + t.Run("found dhcp options", func(t *testing.T) { + _, err := ovnClient.GetDHCPOptions(lsName, protocol, false) + require.NoError(t, err) + }) + }) + + t.Run("invalid protocol", func(t *testing.T) { + protocol := kubeovnv1.ProtocolDual + _, err := ovnClient.GetDHCPOptions(lsName, protocol, false) + require.ErrorContains(t, err, "protocol must be IPv4 or IPv6") + + protocol = "" + _, err = ovnClient.GetDHCPOptions(lsName, protocol, false) + require.ErrorContains(t, err, "protocol must be IPv4 or IPv6") + }) +} + +func (suite *OvnClientTestSuite) testListDHCPOptions() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-list-dhcp-opt-ls" + v4CidrBlock := []string{"192.168.30.0/24", "192.168.40.0/24", "192.168.50.0/24"} + + // create three ipv4 dhcp options + for _, cidr := range v4CidrBlock { + err := ovnClient.CreateDHCPOptions(lsName, cidr, "") + require.NoError(t, err) + } + + /* list all direction acl */ + out, err := ovnClient.ListDHCPOptions(true, map[string]string{logicalSwitchKey: lsName}) + require.NoError(t, err) + require.Len(t, out, 3) +} + +func (suite *OvnClientTestSuite) test_dhcpOptionsFilter() { + t := suite.T() + t.Parallel() + + lsName := "test-filter-dhcp-opt-ls" + v4CidrBlock := []string{"192.168.30.0/24", "192.168.40.0/24", "192.168.50.0/24"} + v6CidrBlock := []string{"fd00::c0a8:6401/120", "fd00::c0a8:6e01/120"} + dhcpOpts := make([]*ovnnb.DHCPOptions, 0) + + t.Run("filter dhcp options", func(t *testing.T) { + t.Parallel() + + // create three ipv4 dhcp options + for _, cidr := range v4CidrBlock { + dhcpOpt, err := newDHCPOptions(lsName, cidr, "") + require.NoError(t, err) + dhcpOpts = append(dhcpOpts, dhcpOpt) + } + + // create two ipv6 dhcp options + for _, cidr := range v6CidrBlock { + dhcpOpt, err := newDHCPOptions(lsName, cidr, "") + require.NoError(t, err) + dhcpOpts = append(dhcpOpts, dhcpOpt) + } + + // create three ipv4 dhcp options with other logical switch name + for _, cidr := range v4CidrBlock { + dhcpOpt, err := newDHCPOptions(lsName, cidr, "") + dhcpOpt.ExternalIDs[logicalSwitchKey] = lsName + "-test" + require.NoError(t, err) + dhcpOpts = append(dhcpOpts, dhcpOpt) + } + + // create three ipv4 dhcp options with other vendor + for _, cidr := range v4CidrBlock { + dhcpOpt, err := newDHCPOptions(lsName, cidr, "") + dhcpOpt.ExternalIDs["vendor"] = util.CniTypeName + "-test" + require.NoError(t, err) + dhcpOpts = append(dhcpOpts, dhcpOpt) + } + + /* include all dhcp options */ + filterFunc := dhcpOptionsFilter(false, nil) + count := 0 + for _, dhcpOpt := range dhcpOpts { + if filterFunc(dhcpOpt) { + count++ + } + } + require.Equal(t, count, 11) + + /* include same vendor dhcp options */ + filterFunc = dhcpOptionsFilter(true, nil) + count = 0 + for _, dhcpOpt := range dhcpOpts { + if filterFunc(dhcpOpt) { + count++ + } + } + require.Equal(t, count, 8) + + /* include same ls dhcp options */ + filterFunc = dhcpOptionsFilter(true, map[string]string{logicalSwitchKey: lsName}) + count = 0 + for _, dhcpOpt := range dhcpOpts { + if filterFunc(dhcpOpt) { + count++ + } + } + require.Equal(t, count, 5) + + /* include same protocol dhcp options */ + filterFunc = dhcpOptionsFilter(true, map[string]string{logicalSwitchKey: lsName, "protocol": "IPv4"}) + count = 0 + for _, dhcpOpt := range dhcpOpts { + if filterFunc(dhcpOpt) { + count++ + } + } + require.Equal(t, count, 3) + + /* include all protocol dhcp options */ + filterFunc = dhcpOptionsFilter(true, map[string]string{logicalSwitchKey: lsName, "protocol": ""}) + count = 0 + for _, dhcpOpt := range dhcpOpts { + if filterFunc(dhcpOpt) { + count++ + } + } + require.Equal(t, count, 5) + }) + + t.Run("result should exclude dhcp options when externalIDs's length is not equal", func(t *testing.T) { + t.Parallel() + + dhcpOpt, err := newDHCPOptions(lsName, "192.168.30.0/24", "") + require.NoError(t, err) + + filterFunc := dhcpOptionsFilter(true, map[string]string{ + logicalSwitchKey: lsName, + "key": "value", + }) + + require.False(t, filterFunc(dhcpOpt)) + }) +} diff --git a/pkg/ovs/ovn-nb-gateway_chassis.go b/pkg/ovs/ovn-nb-gateway_chassis.go new file mode 100644 index 00000000000..848c7ac3b0a --- /dev/null +++ b/pkg/ovs/ovn-nb-gateway_chassis.go @@ -0,0 +1,163 @@ +package ovs + +import ( + "context" + "fmt" + + "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +// CreateGatewayChassises create multiple gateway chassis once +func (c *ovnClient) CreateGatewayChassises(lrpName string, chassises ...string) error { + op, err := c.CreateGatewayChassisesOp(lrpName, chassises) + if err != nil { + return fmt.Errorf("generate operations for creating gateway chassis %v", err) + } + + if err = c.Transact("gateway-chassises-add", op); err != nil { + return fmt.Errorf("create gateway chassis %v for logical router port %s: %v", chassises, lrpName, err) + } + + return nil +} + +// DeleteGatewayChassises delete multiple gateway chassis once +func (c *ovnClient) DeleteGatewayChassises(lrpName string, chassises []string) error { + if len(chassises) == 0 { + return nil + } + + ops := make([]ovsdb.Operation, 0, len(chassises)) + + for _, chassisName := range chassises { + gwChassisName := lrpName + "-" + chassisName + op, err := c.DeleteGatewayChassisOp(gwChassisName) + if err != nil { + return nil + } + + // ignore non-existent object + if len(op) == 0 { + continue + } + + ops = append(ops, op...) + } + + if err := c.Transact("gateway-chassises-delete", ops); err != nil { + return fmt.Errorf("delete gateway chassises %v from logical router port %s: %v", chassises, lrpName, err) + } + + return nil +} + +// GetGatewayChassis get gateway chassis by name +func (c *ovnClient) GetGatewayChassis(name string, ignoreNotFound bool) (*ovnnb.GatewayChassis, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + gwChassis := &ovnnb.GatewayChassis{Name: name} + if err := c.Get(ctx, gwChassis); err != nil { + if ignoreNotFound && err == client.ErrNotFound { + return nil, nil + } + + return nil, fmt.Errorf("get gateway chassis %s: %v", name, err) + } + + return gwChassis, nil +} + +func (c *ovnClient) GatewayChassisExist(name string) (bool, error) { + gwChassis, err := c.GetGatewayChassis(name, true) + return gwChassis != nil, err +} + +// newGatewayChassis return gateway chassis with basic information +func (c *ovnClient) newGatewayChassis(gwChassisName, chassisName string, priority int) (*ovnnb.GatewayChassis, error) { + exists, err := c.GatewayChassisExist(gwChassisName) + if err != nil { + return nil, err + } + + // found, skip + if exists { + return nil, nil + } + + gwChassis := &ovnnb.GatewayChassis{ + UUID: ovsclient.NamedUUID(), + Name: gwChassisName, + ChassisName: chassisName, + Priority: priority, + } + + return gwChassis, nil +} + +// DeleteGatewayChassisOp create operation which create gateway chassis +func (c *ovnClient) CreateGatewayChassisesOp(lrpName string, chassises []string) ([]ovsdb.Operation, error) { + if len(chassises) == 0 { + return nil, nil + } + + models := make([]model.Model, 0, len(chassises)) + uuids := make([]string, 0, len(chassises)) + + for i, chassisName := range chassises { + gwChassisName := lrpName + "-" + chassisName + gwChassis, err := c.newGatewayChassis(gwChassisName, chassisName, 100-i) + if err != nil { + return nil, err + } + + // found, skip + if gwChassis != nil { + models = append(models, model.Model(gwChassis)) + uuids = append(uuids, gwChassis.UUID) + } + } + + gwChassisCreateop, err := c.Create(models...) + if err != nil { + return nil, fmt.Errorf("generate operations for creating gateway chassis %v", err) + } + + /* add gateway chassis to logical router port */ + gwChassisAddOp, err := c.LogicalRouterPortUpdateGatewayChassisOp(lrpName, uuids, ovsdb.MutateOperationInsert) + if err != nil { + return nil, err + } + + ops := make([]ovsdb.Operation, 0, len(gwChassisCreateop)+len(gwChassisAddOp)) + ops = append(ops, gwChassisCreateop...) + ops = append(ops, gwChassisAddOp...) + + return ops, nil +} + +// DeleteGatewayChassisOp create operation which delete gateway chassis +func (c *ovnClient) DeleteGatewayChassisOp(chassisName string) ([]ovsdb.Operation, error) { + gwChassis, err := c.GetGatewayChassis(chassisName, true) + + if err != nil { + return nil, err + } + + // not found, skip + if gwChassis == nil { + return nil, nil + } + + op, err := c.Where(gwChassis).Delete() + if err != nil { + return nil, err + } + + return op, nil +} diff --git a/pkg/ovs/ovn-nb-gateway_chassis_test.go b/pkg/ovs/ovn-nb-gateway_chassis_test.go new file mode 100644 index 00000000000..c119e5fd226 --- /dev/null +++ b/pkg/ovs/ovn-nb-gateway_chassis_test.go @@ -0,0 +1,123 @@ +package ovs + +import ( + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/stretchr/testify/require" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func (suite *OvnClientTestSuite) testCreateGatewayChassises() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + chassises := []string{"c7efec70-9519-4b03-8b67-057f2a95e5c7", "4a0891b6-fe81-4986-a367-aad0ea7ca9f3", "dcc2eda3-b3ea-4d53-afe0-7b6eaf7917ba"} + + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: "test-create-gateway-chassises", + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + }, + } + + err := createLogicalRouterPort(ovnClient, lrp) + require.NoError(t, err) + + err = ovnClient.CreateGatewayChassises(lrp.Name, chassises...) + require.NoError(t, err) + + lrp, err = ovnClient.GetLogicalRouterPort(lrp.Name, false) + require.NoError(t, err) + + for i, chassisName := range chassises { + gwChassisName := lrp.Name + "-" + chassisName + gwChassis, err := ovnClient.GetGatewayChassis(gwChassisName, false) + require.NoError(t, err) + require.Equal(t, gwChassisName, gwChassis.Name) + require.Equal(t, chassisName, gwChassis.ChassisName) + require.Equal(t, 100-i, gwChassis.Priority) + require.Contains(t, lrp.GatewayChassis, gwChassis.UUID) + } +} + +func (suite *OvnClientTestSuite) testDeleteGatewayChassises() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrpName := "test-gateway-chassis-del-lrp" + chassises := []string{"ea8368a0-28cd-4549-9da5-a7ea67262619", "b25ffb94-8b32-4c7e-b5b0-0f343bf6bdd8", "62265268-8af7-4b36-a550-ab5ad38375e3"} + + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: lrpName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + }, + } + + err := createLogicalRouterPort(ovnClient, lrp) + require.NoError(t, err) + + err = ovnClient.CreateGatewayChassises(lrpName, chassises...) + require.NoError(t, err) + + err = ovnClient.DeleteGatewayChassises(lrpName, append(chassises, "73bbe5d4-2b9b-47d0-aba8-94e86941881a")) + require.NoError(t, err) + + for _, chassisName := range chassises { + gwChassisName := lrpName + "-" + chassisName + _, err := ovnClient.GetGatewayChassis(gwChassisName, false) + require.ErrorContains(t, err, "object not found") + } +} + +func (suite *OvnClientTestSuite) testDeleteGatewayChassisOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrpName := "test-gateway-chassis-del-op-lrp" + chassis := "6c322ce8-02b7-42b3-925b-ae24020272a9" + gwChassisName := lrpName + "-" + chassis + + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: lrpName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + }, + } + + err := createLogicalRouterPort(ovnClient, lrp) + require.NoError(t, err) + + err = ovnClient.CreateGatewayChassises(lrpName, chassis) + require.NoError(t, err) + + gwChassis, err := ovnClient.GetGatewayChassis(gwChassisName, false) + require.NoError(t, err) + + ops, err := ovnClient.DeleteGatewayChassisOp(gwChassisName) + require.NoError(t, err) + require.Len(t, ops, 1) + + require.Equal(t, + ovsdb.Operation{ + Op: "delete", + Table: "Gateway_Chassis", + Where: []ovsdb.Condition{ + { + Column: "_uuid", + Function: "==", + Value: ovsdb.UUID{ + GoUUID: gwChassis.UUID, + }, + }, + }, + }, ops[0]) +} diff --git a/pkg/ovs/ovn-nb-load_balancer.go b/pkg/ovs/ovn-nb-load_balancer.go new file mode 100644 index 00000000000..62f2af5569f --- /dev/null +++ b/pkg/ovs/ovn-nb-load_balancer.go @@ -0,0 +1,265 @@ +package ovs + +import ( + "context" + "fmt" + "strconv" + + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +// CreateLoadBalancer create loadbalancer +func (c *ovnClient) CreateLoadBalancer(lbName, protocol, selectFields string) error { + exist, err := c.LoadBalancerExists(lbName) + if err != nil { + return err + } + + // found, ignore + if exist { + return nil + } + + lb := &ovnnb.LoadBalancer{ + UUID: ovsclient.NamedUUID(), + Name: lbName, + Protocol: &protocol, + } + + if len(selectFields) != 0 { + lb.SelectionFields = []string{selectFields} + } + + op, err := c.ovnNbClient.Create(lb) + if err != nil { + return fmt.Errorf("generate operations for creating load balancer %s: %v", lbName, err) + } + + if err := c.Transact("lb-add", op); err != nil { + return fmt.Errorf("create load balancer %s: %v", lbName, err) + } + + return nil +} + +// UpdateLoadBalancer update load balancer +func (c *ovnClient) UpdateLoadBalancer(lb *ovnnb.LoadBalancer, fields ...interface{}) error { + op, err := c.ovnNbClient.Where(lb).Update(lb, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating load balancer %s: %v", lb.Name, err) + } + + if err = c.Transact("lb-update", op); err != nil { + return fmt.Errorf("update load balancer %s: %v", lb.Name, err) + } + + return nil +} + +// LoadBalancerAddVips add vips +func (c *ovnClient) LoadBalancerAddVips(lbName string, vips map[string]string) error { + if len(vips) == 0 { + return nil + } + + lb, err := c.GetLoadBalancer(lbName, false) + if err != nil { + return err + } + + if lb.Vips == nil { + lb.Vips = make(map[string]string) + } + + for vip, backends := range vips { + lb.Vips[vip] = backends + } + + if err := c.UpdateLoadBalancer(lb, &lb.Vips); err != nil { + return fmt.Errorf("add vips %v to lb %s: %v", vips, lbName, err) + } + + return nil +} + +// LoadBalancerDeleteVips delete load balancer vips +func (c *ovnClient) LoadBalancerDeleteVips(lbName string, vips map[string]struct{}) error { + if len(vips) == 0 { + return nil + } + + lb, err := c.GetLoadBalancer(lbName, false) + if err != nil { + return err + } + + for vip := range vips { + delete(lb.Vips, vip) + } + + if err := c.UpdateLoadBalancer(lb, &lb.Vips); err != nil { + return fmt.Errorf("delete vips %v from lb %s: %v", vips, lbName, err) + } + + return nil +} + +// SetLoadBalancerAffinityTimeout sets the LB's affinity timeout in seconds +func (c *ovnClient) SetLoadBalancerAffinityTimeout(lbName string, timeout int) error { + lb, err := c.GetLoadBalancer(lbName, false) + if err != nil { + return err + } + + if lb.Options == nil { + lb.Options = make(map[string]string) + } + + lb.Options["affinity_timeout"] = strconv.Itoa(timeout) + + if err := c.UpdateLoadBalancer(lb, &lb.Options); err != nil { + return fmt.Errorf("set affinity timeout of lb %s to %d, err: %v", lbName, timeout, err) + } + + return nil +} + +// DeleteLoadBalancers delete several loadbalancer once +func (c *ovnClient) DeleteLoadBalancers(filter func(lb *ovnnb.LoadBalancer) bool) error { + op, err := c.ovnNbClient.WhereCache(func(lb *ovnnb.LoadBalancer) bool { + if filter != nil { + return filter(lb) + } + + return true + }).Delete() + + if err != nil { + return fmt.Errorf("generate operations for delete load balancers: %v", err) + } + + if err := c.Transact("lb-del", op); err != nil { + return fmt.Errorf("delete load balancers : %v", err) + } + + return nil +} + +// DeleteLoadBalancer delete loadbalancer +func (c *ovnClient) DeleteLoadBalancer(lbName string) error { + op, err := c.DeleteLoadBalancerOp(lbName) + if err != nil { + return nil + } + + if err := c.Transact("lb-del", op); err != nil { + return fmt.Errorf("delete load balancer %s: %v", lbName, err) + } + + return nil +} + +// GetLoadBalancer get load balancer by name, +// it is because of lack name index that does't use ovnNbClient.Get +func (c *ovnClient) GetLoadBalancer(lbName string, ignoreNotFound bool) (*ovnnb.LoadBalancer, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + lbList := make([]ovnnb.LoadBalancer, 0) + if err := c.ovnNbClient.WhereCache(func(lb *ovnnb.LoadBalancer) bool { + return lb.Name == lbName + }).List(ctx, &lbList); err != nil { + return nil, fmt.Errorf("list load balancer %q: %v", lbName, err) + } + + // not found + if len(lbList) == 0 { + if ignoreNotFound { + return nil, nil + } + return nil, fmt.Errorf("not found load balancer %q", lbName) + } + + if len(lbList) > 1 { + return nil, fmt.Errorf("more than one load balancer with same name %q", lbName) + } + + return &lbList[0], nil +} + +func (c *ovnClient) LoadBalancerExists(lbName string) (bool, error) { + lrp, err := c.GetLoadBalancer(lbName, true) + return lrp != nil, err +} + +// ListLoadBalancers list all load balancers +func (c *ovnClient) ListLoadBalancers(filter func(lb *ovnnb.LoadBalancer) bool) ([]ovnnb.LoadBalancer, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + lbList := make([]ovnnb.LoadBalancer, 0) + if err := c.ovnNbClient.WhereCache(func(lb *ovnnb.LoadBalancer) bool { + if filter != nil { + return filter(lb) + } + + return true + }).List(ctx, &lbList); err != nil { + return nil, fmt.Errorf("list load balancer: %v", err) + } + + return lbList, nil +} + +func (c *ovnClient) LoadBalancerOp(lbName string, mutationsFunc ...func(lb *ovnnb.LoadBalancer) *model.Mutation) ([]ovsdb.Operation, error) { + lb, err := c.GetLoadBalancer(lbName, false) + if err != nil { + return nil, err + } + + if len(mutationsFunc) == 0 { + return nil, nil + } + + mutations := make([]model.Mutation, 0, len(mutationsFunc)) + + for _, f := range mutationsFunc { + mutation := f(lb) + + if mutation != nil { + mutations = append(mutations, *mutation) + } + } + + ops, err := c.ovnNbClient.Where(lb).Mutate(lb, mutations...) + if err != nil { + return nil, fmt.Errorf("generate operations for mutating load balancer %s: %v", lb.Name, err) + } + + return ops, nil +} + +// DeleteLoadBalancerOp create operation which delete load balancer +func (c *ovnClient) DeleteLoadBalancerOp(lbName string) ([]ovsdb.Operation, error) { + lb, err := c.GetLoadBalancer(lbName, true) + + if err != nil { + return nil, err + } + + // not found, skip + if lb == nil { + return nil, nil + } + + op, err := c.Where(lb).Delete() + if err != nil { + return nil, err + } + + return op, nil +} diff --git a/pkg/ovs/ovn-nb-load_balancer_test.go b/pkg/ovs/ovn-nb-load_balancer_test.go new file mode 100644 index 00000000000..341f59c3ec0 --- /dev/null +++ b/pkg/ovs/ovn-nb-load_balancer_test.go @@ -0,0 +1,361 @@ +package ovs + +import ( + "fmt" + "strings" + "testing" + + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/stretchr/testify/require" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func (suite *OvnClientTestSuite) testCreateLoadBalancer() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbName := "test-create-lb" + + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "ip_dst") + require.NoError(t, err) + + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + require.Equal(t, lbName, lb.Name) + require.NotEmpty(t, lb.UUID) + require.Equal(t, "tcp", *lb.Protocol) + require.Equal(t, []string{"ip_dst"}, lb.SelectionFields) + + // should no err create lb repeatedly + err = ovnClient.CreateLoadBalancer(lbName, "tcp", "ip_dst") + require.NoError(t, err) +} + +func (suite *OvnClientTestSuite) testUpdateLoadBalancer() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbName := "test-update-lb" + + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "ip_dst") + require.NoError(t, err) + + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + + t.Run("update vips", func(t *testing.T) { + lb.Vips = map[string]string{ + "10.96.0.1:443": "192.168.20.3:6443", + "10.107.43.237:8080": "10.244.0.15:8080,10.244.0.16:8080,10.244.0.17:8080", + "[fd00:10:96::e82f]:8080": "[fc00::af4:f]:8080,[fc00::af4:10]:8080,[fc00::af4:11]:8080", + } + + err := ovnClient.UpdateLoadBalancer(lb, &lb.Vips) + require.NoError(t, err) + + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + + require.Equal(t, map[string]string{ + "10.96.0.1:443": "192.168.20.3:6443", + "10.107.43.237:8080": "10.244.0.15:8080,10.244.0.16:8080,10.244.0.17:8080", + "[fd00:10:96::e82f]:8080": "[fc00::af4:f]:8080,[fc00::af4:10]:8080,[fc00::af4:11]:8080", + }, lb.Vips) + }) + + t.Run("clear vips", func(t *testing.T) { + lb.Vips = nil + + err := ovnClient.UpdateLoadBalancer(lb, &lb.Vips) + require.NoError(t, err) + + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + + require.Nil(t, lb.Vips) + }) +} + +func (suite *OvnClientTestSuite) testLoadBalancerAddVips() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbName := "test-update-lb-vips" + + vips := map[string]string{ + "10.96.0.1:443": "192.168.20.3:6443", + "10.107.43.237:8080": "10.244.0.15:8080,10.244.0.16:8080,10.244.0.17:8080", + "[fd00:10:96::e82f]:8080": "[fc00::af4:f]:8080,[fc00::af4:10]:8080,[fc00::af4:11]:8080", + } + + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "") + require.NoError(t, err) + + _, err = ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + + t.Run("add new vips to load balancer", func(t *testing.T) { + err := ovnClient.LoadBalancerAddVips(lbName, vips) + require.NoError(t, err) + + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + require.Equal(t, vips, lb.Vips) + }) + + t.Run("add new vips to load balancer repeatedly", func(t *testing.T) { + vips["10.96.0.1:443"] = "192.168.20.3:6443,192.168.20.4:6443" + vips["10.96.0.112:143"] = "192.168.120.3:6443,192.168.120.4:6443" + + err := ovnClient.LoadBalancerAddVips(lbName, map[string]string{ + "10.96.0.1:443": "192.168.20.3:6443,192.168.20.4:6443", + "10.96.0.112:143": "192.168.120.3:6443,192.168.120.4:6443", + }) + require.NoError(t, err) + + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + require.Equal(t, vips, lb.Vips) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLoadBalancers() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbNamePrefix := "test-del-lbs" + lbNames := make([]string, 0, 5) + + for i := 0; i < 5; i++ { + lbName := fmt.Sprintf("%s-%d", lbNamePrefix, i) + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "") + require.NoError(t, err) + + lbNames = append(lbNames, lbName) + } + + err := ovnClient.DeleteLoadBalancers(func(lb *ovnnb.LoadBalancer) bool { + return util.ContainsString(lbNames, lb.Name) + }) + require.NoError(t, err) + + for _, lbName := range lbNames { + _, err := ovnClient.GetLoadBalancer(lbName, false) + require.ErrorContains(t, err, "not found load balancer") + } +} + +func (suite *OvnClientTestSuite) testDeleteLoadBalancer() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbName := "test-del-lb" + + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "") + require.NoError(t, err) + + err = ovnClient.DeleteLoadBalancer(lbName) + require.NoError(t, err) + + _, err = ovnClient.GetLoadBalancer(lbName, false) + require.ErrorContains(t, err, "not found load balancer") +} + +func (suite *OvnClientTestSuite) testLoadBalancerDeleteVips() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbName := "test-del-lb-vips" + + vips := map[string]string{ + "10.96.0.1:443": "192.168.20.3:6443", + "10.107.43.237:8080": "10.244.0.15:8080,10.244.0.16:8080,10.244.0.17:8080", + "[fd00:10:96::e82f]:8080": "[fc00::af4:f]:8080,[fc00::af4:10]:8080,[fc00::af4:11]:8080", + } + + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "") + require.NoError(t, err) + + err = ovnClient.LoadBalancerAddVips(lbName, vips) + require.NoError(t, err) + + err = ovnClient.LoadBalancerDeleteVips(lbName, map[string]struct{}{ + "10.96.0.1:443": {}, + "[fd00:10:96::e82f]:8080": {}, + "10.96.0.100:1443": {}, // non-existent vip + }) + require.NoError(t, err) + + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + require.Equal(t, map[string]string{ + "10.107.43.237:8080": "10.244.0.15:8080,10.244.0.16:8080,10.244.0.17:8080", + }, lb.Vips) +} + +func (suite *OvnClientTestSuite) testGetLoadBalancer() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbName := "test-get-lb" + + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "") + require.NoError(t, err) + + t.Run("should return no err when found load balancer", func(t *testing.T) { + t.Parallel() + lr, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + require.Equal(t, lbName, lr.Name) + require.NotEmpty(t, lr.UUID) + }) + + t.Run("should return err when not found load balancer", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.GetLoadBalancer("test-get-lb-non-existent", false) + require.ErrorContains(t, err, "not found load balancer") + }) + + t.Run("no err when not found load balancerand ignoreNotFound is true", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.GetLoadBalancer("test-get-lr-non-existent", true) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testListLoadBalancers() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbNamePrefix := "test-list-lb" + lbNames := make([]string, 0, 3) + protocol := []string{"tcp", "udp"} + + for i := 0; i < 3; i++ { + for _, p := range protocol { + lbName := fmt.Sprintf("%s-%s-%d", lbNamePrefix, p, i) + err := ovnClient.CreateLoadBalancer(lbName, p, "") + require.NoError(t, err) + + lbNames = append(lbNames, lbName) + } + } + + t.Run("has no custom filter", func(t *testing.T) { + t.Parallel() + + lbs, err := ovnClient.ListLoadBalancers(nil) + require.NoError(t, err) + require.NotEmpty(t, lbs) + + newLbNames := make([]string, 0, 3) + for _, lb := range lbs { + if !strings.Contains(lb.Name, lbNamePrefix) { + continue + } + newLbNames = append(newLbNames, lb.Name) + } + + require.ElementsMatch(t, lbNames, newLbNames) + }) + + t.Run("has custom filter", func(t *testing.T) { + t.Parallel() + t.Run("fliter by name", func(t *testing.T) { + t.Parallel() + + except := lbNames[1:] + + lbs, err := ovnClient.ListLoadBalancers(func(lb *ovnnb.LoadBalancer) bool { + return !util.ContainsString(except, lb.Name) + }) + require.NoError(t, err) + require.NotEmpty(t, lbs) + + newLbNames := make([]string, 0, 3) + for _, lb := range lbs { + if !strings.Contains(lb.Name, lbNamePrefix) { + continue + } + newLbNames = append(newLbNames, lb.Name) + } + + require.ElementsMatch(t, lbNames[:1], newLbNames) + }) + + t.Run("fliter by tcp protocol", func(t *testing.T) { + t.Parallel() + + for _, p := range protocol { + lbs, err := ovnClient.ListLoadBalancers(func(lb *ovnnb.LoadBalancer) bool { + return *lb.Protocol == p + }) + require.NoError(t, err) + require.NotEmpty(t, lbs) + + for _, lb := range lbs { + if !strings.Contains(lb.Name, lbNamePrefix) { + continue + } + + require.Equal(t, p, *lb.Protocol) + } + } + }) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLoadBalancerOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lbName := "test-del-lb-op" + + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "") + require.NoError(t, err) + + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + + t.Run("normal delete", func(t *testing.T) { + t.Parallel() + + ops, err := ovnClient.DeleteLoadBalancerOp(lbName) + require.NoError(t, err) + require.Len(t, ops, 1) + + require.Equal(t, + ovsdb.Operation{ + Op: "delete", + Table: "Load_Balancer", + Where: []ovsdb.Condition{ + { + Column: "_uuid", + Function: "==", + Value: ovsdb.UUID{ + GoUUID: lb.UUID, + }, + }, + }, + }, ops[0]) + }) + + t.Run("return ops is empty when delete non-existent load balancer", func(t *testing.T) { + t.Parallel() + + ops, err := ovnClient.DeleteLoadBalancerOp(lbName + "-non-existent") + require.NoError(t, err) + require.Len(t, ops, 0) + }) +} diff --git a/pkg/ovs/ovn-nb-logical_router.go b/pkg/ovs/ovn-nb-logical_router.go index e173126e8ca..b367f9299d2 100644 --- a/pkg/ovs/ovn-nb-logical_router.go +++ b/pkg/ovs/ovn-nb-logical_router.go @@ -4,34 +4,252 @@ import ( "context" "fmt" - "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" ) -func (c OvnClient) GetLogicalRouter(name string, ignoreNotFound bool) (*ovnnb.LogicalRouter, error) { +// CreateLogicalRouter create logical router in ovn +func (c *ovnClient) CreateLogicalRouter(lrName string) error { + exist, err := c.LogicalRouterExists(lrName) + if err != nil { + return err + } + + // found, ignore + if exist { + return nil + } + + lr := &ovnnb.LogicalRouter{ + Name: lrName, + ExternalIDs: map[string]string{"vendor": util.CniTypeName}, + } + + op, err := c.ovnNbClient.Create(lr) + if err != nil { + return fmt.Errorf("generate operations for creating logical router %s: %v", lrName, err) + } + + if err := c.Transact("lr-add", op); err != nil { + return fmt.Errorf("create logical router %s: %v", lrName, err) + } + + return nil +} + +// UpdateLogicalRouter update logical router +func (c *ovnClient) UpdateLogicalRouter(lr *ovnnb.LogicalRouter, fields ...interface{}) error { + op, err := c.UpdateLogicalRouterOp(lr, fields...) + if err != nil { + return err + } + + if err = c.Transact("lr-update", op); err != nil { + return fmt.Errorf("update logical router %s: %v", lr.Name, err) + } + + return nil +} + +// DeleteLogicalRouter delete logical router in ovn +func (c *ovnClient) DeleteLogicalRouter(lrName string) error { + lr, err := c.GetLogicalRouter(lrName, true) + if err != nil { + return fmt.Errorf("get logical router %s when delete: %v", lrName, err) + } + + // not found, skip + if lr == nil { + return nil + } + + op, err := c.Where(lr).Delete() + if err != nil { + return err + } + + if err := c.Transact("lr-del", op); err != nil { + return fmt.Errorf("delete logical router %s: %v", lrName, err) + } + + return nil +} + +// GetLogicalRouter get load balancer by name, +// it is because of lack name index that does't use ovnNbClient.Get +func (c *ovnClient) GetLogicalRouter(lrName string, ignoreNotFound bool) (*ovnnb.LogicalRouter, error) { ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) defer cancel() - api, err := c.ovnNbClient.WherePredict(ctx, func(model *ovnnb.LogicalRouter) bool { - return model.Name == name - }) - if err != nil { - return nil, err + lrList := make([]ovnnb.LogicalRouter, 0) + if err := c.ovnNbClient.WhereCache(func(lr *ovnnb.LogicalRouter) bool { + return lr.Name == lrName + }).List(ctx, &lrList); err != nil { + return nil, fmt.Errorf("list logical router %q: %v", lrName, err) } - var result []*ovnnb.LogicalRouter - if err = api.List(context.TODO(), &result); err != nil || len(result) == 0 { - if ignoreNotFound && (err == client.ErrNotFound || len(result) == 0) { + // not found + if len(lrList) == 0 { + if ignoreNotFound { return nil, nil } - return nil, fmt.Errorf("failed to get logical router %s: %v", name, err) + return nil, fmt.Errorf("not found logical router %q", lrName) + } + + if len(lrList) > 1 { + return nil, fmt.Errorf("more than one logical router with same name %q", lrName) + } + + return &lrList[0], nil +} + +func (c *ovnClient) LogicalRouterExists(name string) (bool, error) { + lrp, err := c.GetLogicalRouter(name, true) + return lrp != nil, err +} + +// ListLogicalRouter list logical router +func (c *ovnClient) ListLogicalRouter(needVendorFilter bool, filter func(lr *ovnnb.LogicalRouter) bool) ([]ovnnb.LogicalRouter, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + lrList := make([]ovnnb.LogicalRouter, 0) + + if err := c.ovnNbClient.WhereCache(func(lr *ovnnb.LogicalRouter) bool { + if needVendorFilter && (len(lr.ExternalIDs) == 0 || lr.ExternalIDs["vendor"] != util.CniTypeName) { + return false + } + + if filter != nil { + return filter(lr) + } + + return true + }).List(ctx, &lrList); err != nil { + return nil, fmt.Errorf("list logical router: %v", err) + } + + return lrList, nil +} + +// UpdateLogicalRouterOp generate operations which update logical router +func (c *ovnClient) UpdateLogicalRouterOp(lr *ovnnb.LogicalRouter, fields ...interface{}) ([]ovsdb.Operation, error) { + if lr == nil { + return nil, fmt.Errorf("logical_router is nil") + } + + op, err := c.ovnNbClient.Where(lr).Update(lr, fields...) + if err != nil { + return nil, fmt.Errorf("generate operations for updating logical router %s: %v", lr.Name, err) + } + + return op, nil +} + +// LogicalRouterUpdatePortOp create operations add to or delete port from logical router +func (c *ovnClient) LogicalRouterUpdatePortOp(lrName, lrpUUID string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(lrpUUID) == 0 { + return nil, nil + } + + mutation := func(lr *ovnnb.LogicalRouter) *model.Mutation { + mutation := &model.Mutation{ + Field: &lr.Ports, + Value: []string{lrpUUID}, + Mutator: op, + } + + return mutation } - return result[0], nil + return c.LogicalRouterOp(lrName, mutation) } -func (c OvnClient) LogicalRouterExists(name string) (bool, error) { - lr, err := c.GetLogicalRouter(name, true) - return lr != nil, err +// LogicalRouterUpdatePolicyOp create operations add to or delete policy from logical router +func (c *ovnClient) LogicalRouterUpdatePolicyOp(lrName string, policyUUIDs []string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(policyUUIDs) == 0 { + return nil, nil + } + + mutation := func(lr *ovnnb.LogicalRouter) *model.Mutation { + mutation := &model.Mutation{ + Field: &lr.Policies, + Value: policyUUIDs, + Mutator: op, + } + + return mutation + } + + return c.LogicalRouterOp(lrName, mutation) +} + +// LogicalRouterUpdateNatOp create operations add to or delete nat rule from logical router +func (c *ovnClient) LogicalRouterUpdateNatOp(lrName string, natUUIDs []string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(natUUIDs) == 0 { + return nil, nil + } + + mutation := func(lr *ovnnb.LogicalRouter) *model.Mutation { + mutation := &model.Mutation{ + Field: &lr.Nat, + Value: natUUIDs, + Mutator: op, + } + + return mutation + } + + return c.LogicalRouterOp(lrName, mutation) +} + +// LogicalRouterUpdateStaticRouteOp create operations add to or delete static route from logical router +func (c *ovnClient) LogicalRouterUpdateStaticRouteOp(lrName string, routeUUIDs []string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(routeUUIDs) == 0 { + return nil, nil + } + + mutation := func(lr *ovnnb.LogicalRouter) *model.Mutation { + mutation := &model.Mutation{ + Field: &lr.StaticRoutes, + Value: routeUUIDs, + Mutator: op, + } + + return mutation + } + + return c.LogicalRouterOp(lrName, mutation) +} + +// LogicalRouterOp create operations about logical router +func (c *ovnClient) LogicalRouterOp(lrName string, mutationsFunc ...func(lr *ovnnb.LogicalRouter) *model.Mutation) ([]ovsdb.Operation, error) { + lr, err := c.GetLogicalRouter(lrName, false) + if err != nil { + return nil, fmt.Errorf("get logical router %s: %v", lrName, err) + } + + if len(mutationsFunc) == 0 { + return nil, nil + } + + mutations := make([]model.Mutation, 0, len(mutationsFunc)) + + for _, f := range mutationsFunc { + mutation := f(lr) + + if mutation != nil { + mutations = append(mutations, *mutation) + } + } + + ops, err := c.ovnNbClient.Where(lr).Mutate(lr, mutations...) + if err != nil { + return nil, fmt.Errorf("generate operations for mutating logical router %s: %v", lrName, err) + } + + return ops, nil } diff --git a/pkg/ovs/ovn-nb-logical_router_policy.go b/pkg/ovs/ovn-nb-logical_router_policy.go index 03f4320d51e..df3e8fc4e86 100644 --- a/pkg/ovs/ovn-nb-logical_router_policy.go +++ b/pkg/ovs/ovn-nb-logical_router_policy.go @@ -1,6 +1,7 @@ package ovs import ( + "context" "fmt" "github.com/ovn-org/libovsdb/model" @@ -10,44 +11,324 @@ import ( "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" ) -func (c OvnClient) AddRouterPolicy(lr *ovnnb.LogicalRouter, match string, action ovnnb.LogicalRouterPolicyAction, - opts map[string]string, extIDs map[string]string, priority int) error { - lrPolicy := &ovnnb.LogicalRouterPolicy{ - Action: action, - Match: match, - Options: opts, - Priority: priority, - ExternalIDs: extIDs, - UUID: ovsclient.NamedUUID(), - Nexthop: nil, - Nexthops: nil, +// AddLogicalRouterPolicy add a policy route to logical router +func (c *ovnClient) AddLogicalRouterPolicy(lrName string, priority int, match, action string, nextHops []string, externalIDs map[string]string) error { + exists, err := c.LogicalRouterPolicyExists(lrName, priority, match) + if err != nil { + return err + } + + if exists { + return nil + } + + policy, err := c.newLogicalRouterPolicy(lrName, priority, match, action, nextHops, externalIDs) + if err != nil { + return fmt.Errorf("new policy for logical router %s: %v", lrName, err) + } + + if err := c.CreateLogicalRouterPolicies(lrName, policy); err != nil { + return fmt.Errorf("add policy to logical router %s: %v", lrName, err) + } + + return nil +} + +// CreateLogicalRouterPolicies create several logical router policy once +func (c *ovnClient) CreateLogicalRouterPolicies(lrName string, policies ...*ovnnb.LogicalRouterPolicy) error { + if len(policies) == 0 { + return nil + } + + models := make([]model.Model, 0, len(policies)) + policyUUIDs := make([]string, 0, len(policies)) + for _, policy := range policies { + if policy != nil { + models = append(models, model.Model(policy)) + policyUUIDs = append(policyUUIDs, policy.UUID) + } } - waitOp := ConstructWaitForUniqueOperation("Logical_Router_Policy", "match", match) - ops := []ovsdb.Operation{waitOp} + createPoliciesOp, err := c.ovnNbClient.Create(models...) + if err != nil { + return fmt.Errorf("generate operations for creating policies: %v", err) + } - createOps, err := c.ovnNbClient.Create(lrPolicy) + policyAddOp, err := c.LogicalRouterUpdatePolicyOp(lrName, policyUUIDs, ovsdb.MutateOperationInsert) + if err != nil { + return fmt.Errorf("generate operations for adding policies to logical router %s: %v", lrName, err) + } + + ops := make([]ovsdb.Operation, 0, len(createPoliciesOp)+len(policyAddOp)) + ops = append(ops, createPoliciesOp...) + ops = append(ops, policyAddOp...) + + if err = c.Transact("lr-policies-add", ops); err != nil { + return fmt.Errorf("add policies to %s: %v", lrName, err) + } + + return nil +} + +func (c *ovnClient) CreateBareLogicalRouterPolicy(lrName string, priority int, match, action string, nextHops []string) error { + policy, err := c.newLogicalRouterPolicy(lrName, priority, match, action, nextHops, nil) + if err != nil { + return fmt.Errorf("new logical router policy: %v", err) + } + + op, err := c.ovnNbClient.Create(policy) + if err != nil { + return fmt.Errorf("generate operations for creating logical router policy: %v", err) + } + + if err = c.Transact("lr-policy-create", op); err != nil { + return fmt.Errorf("create logical router policy: %v", err) + } + + return nil +} + +// DeleteLogicalRouterPolicy delete policy from logical router +func (c *ovnClient) DeleteLogicalRouterPolicy(lrName string, priority int, match string) error { + policy, err := c.GetLogicalRouterPolicy(lrName, priority, match, true) if err != nil { return err } - ops = append(ops, createOps...) - mutationOps, err := c.ovnNbClient.Where(lr).Mutate(lr, model.Mutation{ - Field: &lr.Policies, - Mutator: ovsdb.MutateOperationInsert, - Value: []string{lrPolicy.UUID}, - }) + + // not found, skip + if policy == nil { + return nil + } + + if err := c.DeleteLogicalRouterPolicyByUUID(lrName, policy.UUID); err != nil { + return err + } + + return nil +} + +// DeleteLogicalRouterPolicy delete some policies from logical router once +func (c *ovnClient) DeleteLogicalRouterPolicies(lrName string, priority int, externalIDs map[string]string) error { + if externalIDs == nil { + externalIDs = make(map[string]string) + } + + externalIDs[logicalRouterKey] = lrName + + // remove policies from logical router + policies, err := c.ListLogicalRouterPolicies(priority, externalIDs) + if err != nil { + return err + } + + policiesUUIDs := make([]string, 0, len(policies)) + for _, policy := range policies { + policiesUUIDs = append(policiesUUIDs, policy.UUID) + } + + policiesRemoveOp, err := c.LogicalRouterUpdatePolicyOp(lrName, policiesUUIDs, ovsdb.MutateOperationDelete) + if err != nil { + return fmt.Errorf("generate operations for removing policy %v from logical router %s: %v", policiesUUIDs, lrName, err) + } + + // delete policies + delPoliciesOp, err := c.WhereCache(policyFilter(priority, externalIDs)).Delete() + if err != nil { + return fmt.Errorf("generate operation for deleting nats: %v", err) + } + + ops := make([]ovsdb.Operation, 0, len(policiesRemoveOp)+len(delPoliciesOp)) + ops = append(ops, policiesRemoveOp...) + + ops = append(ops, delPoliciesOp...) + + if err = c.Transact("lr-policies-del", ops); err != nil { + return fmt.Errorf("delete logical router policy %v from logical router %s: %v", policiesUUIDs, lrName, err) + } + return nil +} + +func (c *ovnClient) DeleteLogicalRouterPolicyByUUID(lrName string, uuid string) error { + // remove policy from logical router + policyRemoveOp, err := c.LogicalRouterUpdatePolicyOp(lrName, []string{uuid}, ovsdb.MutateOperationDelete) + if err != nil { + return fmt.Errorf("generate operations for removing policy '%s' from logical router %s: %v", uuid, lrName, err) + } + + // delete policy + deleteOps, err := c.ovnNbClient.Where(&ovnnb.LogicalRouterPolicy{ + UUID: uuid, + }).Delete() + if err != nil { + return fmt.Errorf("failed to generate delete operations for router policy %s: %v", uuid, err) + } + + ops := make([]ovsdb.Operation, 0, len(policyRemoveOp)+len(deleteOps)) + ops = append(ops, policyRemoveOp...) + + ops = append(ops, deleteOps...) + + if err = c.Transact("lr-policy-del", ops); err != nil { + return fmt.Errorf("delete logical router policy '%s' from logical router %s: %v", uuid, lrName, err) + } + return nil +} + +// ClearLogicalRouterPolicy clear policy from logical router once +func (c *ovnClient) ClearLogicalRouterPolicy(lrName string) error { + lr, err := c.GetLogicalRouter(lrName, false) + if err != nil { + return fmt.Errorf("get logical router %s: %v", lrName, err) + } + + // clear logical router policy + lr.Policies = nil + policyClearOp, err := c.UpdateLogicalRouterOp(lr, &lr.Policies) if err != nil { - return fmt.Errorf("failed to generate create operations for router policy %s: %v", match, err) + return fmt.Errorf("generate operations for clear logical router %s policy: %v", lrName, err) } - ops = append(ops, mutationOps...) - if err = Transact(c.ovnNbClient, "lr-policy-add", ops, c.ovnNbClient.Timeout); err != nil { - return fmt.Errorf("failed to create route policy %s: %v", match, err) + // delete logical router policy + policyDelOp, err := c.WhereCache(func(policy *ovnnb.LogicalRouterPolicy) bool { + return len(policy.ExternalIDs) != 0 && policy.ExternalIDs[logicalRouterKey] == lrName + }).Delete() + if err != nil { + return fmt.Errorf("generate operations for deleting logical router %s policy: %v", lrName, err) + } + + ops := make([]ovsdb.Operation, 0, len(policyClearOp)+len(policyDelOp)) + ops = append(ops, policyClearOp...) + ops = append(ops, policyDelOp...) + + if err = c.Transact("lr-policy-clear", ops); err != nil { + return fmt.Errorf("clear logical router %s policy: %v", lrName, err) } + return nil } -func (c OvnClient) DeleteRouterPolicy(lr *ovnnb.LogicalRouter, uuid string) error { +// GetLogicalRouterPolicy get logical router policy by priority and match, +// be consistent with ovn-nbctl which priority and match determine one policy in logical router +func (c *ovnClient) GetLogicalRouterPolicy(lrName string, priority int, match string, ignoreNotFound bool) (*ovnnb.LogicalRouterPolicy, error) { + // this is necessary because may exist same priority and match policy in different logical router + if len(lrName) == 0 { + return nil, fmt.Errorf("the logical router name is required") + } + + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + policyList := make([]ovnnb.LogicalRouterPolicy, 0) + if err := c.ovnNbClient.WhereCache(func(policy *ovnnb.LogicalRouterPolicy) bool { + return len(policy.ExternalIDs) != 0 && policy.ExternalIDs[logicalRouterKey] == lrName && policy.Priority == priority && policy.Match == match + }).List(ctx, &policyList); err != nil { + return nil, fmt.Errorf("get policy priority %d match %s in logical router %s: %v", priority, match, lrName, err) + } + + // not found + if len(policyList) == 0 { + if ignoreNotFound { + return nil, nil + } + return nil, fmt.Errorf("not found policy priority %d match %s in logical router %s", priority, match, lrName) + } + + if len(policyList) > 1 { + return nil, fmt.Errorf("more than one policy with same priority %d match %s in logical router %s", priority, match, lrName) + } + + return &policyList[0], nil +} + +// ListLogicalRouterPolicies list route policy which match the given externalIDs +func (c *ovnClient) ListLogicalRouterPolicies(priority int, externalIDs map[string]string) ([]ovnnb.LogicalRouterPolicy, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + policyList := make([]ovnnb.LogicalRouterPolicy, 0) + + if err := c.WhereCache(policyFilter(priority, externalIDs)).List(ctx, &policyList); err != nil { + return nil, fmt.Errorf("list logical router policies: %v", err) + } + + return policyList, nil +} + +func (c *ovnClient) LogicalRouterPolicyExists(lrName string, priority int, match string) (bool, error) { + policy, err := c.GetLogicalRouterPolicy(lrName, priority, match, true) + return policy != nil, err +} + +// newLogicalRouterPolicy return logical router policy with basic information +func (c *ovnClient) newLogicalRouterPolicy(lrName string, priority int, match, action string, nextHops []string, externalIDs map[string]string) (*ovnnb.LogicalRouterPolicy, error) { + if len(lrName) == 0 { + return nil, fmt.Errorf("the logical router name is required") + } + + if priority == 0 || len(match) == 0 || len(action) == 0 { + return nil, fmt.Errorf("logical router policy 'priority %d' and 'match %s' and 'action %s' is required", priority, match, action) + } + + exists, err := c.LogicalRouterPolicyExists(lrName, priority, match) + if err != nil { + return nil, fmt.Errorf("get logical router %s policy: %v", lrName, err) + } + + // found, ignore + if exists { + return nil, nil + } + + policy := &ovnnb.LogicalRouterPolicy{ + UUID: ovsclient.NamedUUID(), + Priority: priority, + Match: match, + Action: action, + Nexthops: nextHops, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + } + + for k, v := range externalIDs { + policy.ExternalIDs[k] = v + } + + return policy, nil +} + +// policyFilter filter policies which match the given externalIDs +func policyFilter(priority int, externalIDs map[string]string) func(policy *ovnnb.LogicalRouterPolicy) bool { + return func(policy *ovnnb.LogicalRouterPolicy) bool { + if len(policy.ExternalIDs) < len(externalIDs) { + return false + } + + if len(policy.ExternalIDs) != 0 { + for k, v := range externalIDs { + // if only key exist but not value in externalIDs, we should include this lsp, + // it's equal to shell command `ovn-nbctl --columns=xx find logical_router_policy external_ids:key!=\"\"` + if len(v) == 0 { + if len(policy.ExternalIDs[k]) == 0 { + return false + } + } else { + if policy.ExternalIDs[k] != v { + return false + } + } + } + } + + if priority != -1 && priority != policy.Priority { + return false + } + + return true + } +} + +func (c *ovnClient) DeleteRouterPolicy(lr *ovnnb.LogicalRouter, uuid string) error { ops, err := c.ovnNbClient.Where(lr).Mutate(lr, model.Mutation{ Field: &lr.Policies, Mutator: ovsdb.MutateOperationDelete, @@ -66,7 +347,7 @@ func (c OvnClient) DeleteRouterPolicy(lr *ovnnb.LogicalRouter, uuid string) erro } ops = append(ops, deleteOps...) - if err = Transact(c.ovnNbClient, "lr-policy-delete", ops, c.ovnNbClient.Timeout); err != nil { + if err = c.Transact("lr-policy-delete", ops); err != nil { return fmt.Errorf("failed to delete route policy %s: %v", uuid, err) } return nil diff --git a/pkg/ovs/ovn-nb-logical_router_policy_test.go b/pkg/ovs/ovn-nb-logical_router_policy_test.go new file mode 100644 index 00000000000..ad8500ba729 --- /dev/null +++ b/pkg/ovs/ovn-nb-logical_router_policy_test.go @@ -0,0 +1,393 @@ +package ovs + +import ( + "fmt" + "testing" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" + "github.com/stretchr/testify/require" +) + +func newLogicalRouterPolicy(lrName string, priority int, match, action string, nextHops []string, externalIDs map[string]string) *ovnnb.LogicalRouterPolicy { + policy := &ovnnb.LogicalRouterPolicy{ + UUID: ovsclient.NamedUUID(), + Priority: priority, + Match: match, + Action: action, + Nexthops: nextHops, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + } + + for k, v := range externalIDs { + policy.ExternalIDs[k] = v + } + + return policy +} + +func (suite *OvnClientTestSuite) testAddLogicalRouterPolicy() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-add-policy-lr" + priority := 11011 + match := "ip4.src == $ovn.default.lm2_ip4" + action := ovnnb.LogicalRouterPolicyActionAllow + nextHops := []string{"100.64.0.2"} + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.AddLogicalRouterPolicy(lrName, priority, match, action, nextHops, nil) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + policy, err := ovnClient.GetLogicalRouterPolicy(lrName, priority, match, false) + require.NoError(t, err) + require.Contains(t, lr.Policies, policy.UUID) + + err = ovnClient.AddLogicalRouterPolicy(lrName, priority, match, action, nextHops, nil) + require.NoError(t, err) +} + +func (suite *OvnClientTestSuite) testCreateLogicalRouterPolicies() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-create-policies-lr" + priority := 11011 + basePort := 12300 + matchPrefix := "ip4.src == $ovn.default.lm2_ip4" + action := ovnnb.LogicalRouterPolicyActionAllow + policies := make([]*ovnnb.LogicalRouterPolicy, 0, 3) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("add policies to logical router", func(t *testing.T) { + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + policy, err := ovnClient.newLogicalRouterPolicy(lrName, priority, match, action, nil, nil) + require.NoError(t, err) + policies = append(policies, policy) + } + + err = ovnClient.CreateLogicalRouterPolicies(lrName, append(policies, nil)...) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + match := fmt.Sprintf("%s && tcp.dst == %d", matchPrefix, basePort+i) + policy, err := ovnClient.GetLogicalRouterPolicy(lrName, priority, match, false) + require.NoError(t, err) + require.Equal(t, match, policy.Match) + + require.Contains(t, lr.Policies, policy.UUID) + } + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalRouterPolicy() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-del-policy-lr" + priority := 11012 + match := "ip4.src == $ovn.default.lm2_ip4" + action := ovnnb.LogicalRouterPolicyActionAllow + nextHops := []string{"100.64.0.2"} + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.AddLogicalRouterPolicy(lrName, priority, match, action, nextHops, nil) + require.NoError(t, err) + + t.Run("no err when delete existent logical switch port", func(t *testing.T) { + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + policy, err := ovnClient.GetLogicalRouterPolicy(lrName, priority, match, false) + require.NoError(t, err) + require.Contains(t, lr.Policies, policy.UUID) + + err = ovnClient.DeleteLogicalRouterPolicy(lrName, priority, match) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalRouterPolicy(lrName, priority, match, false) + require.ErrorContains(t, err, "not found policy") + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.NotContains(t, lr.Policies, policy.UUID) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalRouterPolicies() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-clear-policies-lr" + basePriority := 12100 + match := "ip4.src == $ovn.default.lm2_ip4" + action := ovnnb.LogicalRouterPolicyActionAllow + nextHops := []string{"100.64.0.2"} + + externalIDs := map[string]string{ + "vendor": util.CniTypeName, + "subnet": "test-subnet", + "isU2ORoutePolicy": "true", + } + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + prepare := func() { + for i := 0; i < 3; i++ { + priority := basePriority + i + err = ovnClient.AddLogicalRouterPolicy(lrName, priority, match, action, nextHops, externalIDs) + require.NoError(t, err) + } + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Policies, 3) + + policies, err := ovnClient.ListLogicalRouterPolicies(-1, externalIDs) + require.NoError(t, err) + require.Len(t, policies, 3) + } + + t.Run("delete some policies with different priority", func(t *testing.T) { + prepare() + + err = ovnClient.DeleteLogicalRouterPolicies(lrName, -1, externalIDs) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Policies) + + policies, err := ovnClient.ListLogicalRouterPolicies(-1, externalIDs) + require.NoError(t, err) + require.Empty(t, policies) + }) + + t.Run("delete same priority", func(t *testing.T) { + prepare() + + err = ovnClient.DeleteLogicalRouterPolicies(lrName, basePriority, externalIDs) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Policies, 2) + + // no basePriority policy + policies, err := ovnClient.ListLogicalRouterPolicies(-1, externalIDs) + require.NoError(t, err) + require.Len(t, policies, 2) + }) +} + +func (suite *OvnClientTestSuite) testClearLogicalRouterPolicy() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-clear-policy-lr" + basePriority := 11012 + match := "ip4.src == $ovn.default.lm2_ip4" + action := ovnnb.LogicalRouterPolicyActionAllow + nextHops := []string{"100.64.0.2"} + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + priority := basePriority + i + err = ovnClient.AddLogicalRouterPolicy(lrName, priority, match, action, nextHops, nil) + require.NoError(t, err) + } + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Policies, 3) + + for i := 0; i < 3; i++ { + priority := basePriority + i + _, err = ovnClient.GetLogicalRouterPolicy(lrName, priority, match, false) + require.NoError(t, err) + } + + err = ovnClient.ClearLogicalRouterPolicy(lrName) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + priority := basePriority + i + _, err = ovnClient.GetLogicalRouterPolicy(lrName, priority, match, false) + require.ErrorContains(t, err, "not found policy") + } + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Policies) +} + +func (suite *OvnClientTestSuite) testGetLogicalRouterPolicy() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test_get_policy_lr" + priority := 11000 + match := "ip4.src == $ovn.default.lm2_ip4" + + err := ovnClient.CreateBareLogicalRouterPolicy(lrName, priority, match, ovnnb.LogicalRouterPolicyActionAllow, nil) + require.NoError(t, err) + + t.Run("priority and match are same", func(t *testing.T) { + t.Parallel() + policy, err := ovnClient.GetLogicalRouterPolicy(lrName, priority, match, false) + require.NoError(t, err) + require.Equal(t, priority, policy.Priority) + require.Equal(t, match, policy.Match) + require.Equal(t, ovnnb.LogicalRouterPolicyActionAllow, policy.Action) + }) + + t.Run("priority and match are not all same", func(t *testing.T) { + t.Parallel() + + _, err = ovnClient.GetLogicalRouterPolicy(lrName, 10010, match, false) + require.ErrorContains(t, err, "not found policy") + + _, err = ovnClient.GetLogicalRouterPolicy(lrName, priority, match+" && tcp", false) + require.ErrorContains(t, err, "not found policy") + }) + + t.Run("should no err when priority and match are not all same but ignoreNotFound=true", func(t *testing.T) { + t.Parallel() + + _, err := ovnClient.GetLogicalRouterPolicy(lrName, priority, match, true) + require.NoError(t, err) + }) + + t.Run("no acl belongs to parent exist", func(t *testing.T) { + t.Parallel() + + _, err := ovnClient.GetLogicalRouterPolicy(lrName+"_1", priority, match, false) + require.ErrorContains(t, err, "not found policy") + }) +} + +func (suite *OvnClientTestSuite) test_newLogicalRouterPolicy() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-new-policy-lr" + priority := 10000 + match := "ip4.src == $ovn.default.lm2_ip4" + nextHops := []string{"100.64.0.2"} + action := ovnnb.LogicalRouterPolicyActionAllow + + expect := &ovnnb.LogicalRouterPolicy{ + Priority: priority, + Match: match, + Action: action, + Nexthops: nextHops, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + "key": "value", + }, + } + + policy, err := ovnClient.newLogicalRouterPolicy(lrName, priority, match, action, nextHops, map[string]string{"key": "value"}) + require.NoError(t, err) + expect.UUID = policy.UUID + require.Equal(t, expect, policy) +} + +func (suite *OvnClientTestSuite) test_policyFilter() { + t := suite.T() + t.Parallel() + + lrName := "test-filter-policy-lr" + basePriority := 10000 + match := "ip4.src == $ovn.default.lm2_ip4" + nextHops := []string{"100.64.0.2"} + action := ovnnb.LogicalRouterPolicyActionAllow + policies := make([]*ovnnb.LogicalRouterPolicy, 0) + + // create three polices + for i := 0; i < 3; i++ { + priority := basePriority + i + policy := newLogicalRouterPolicy(lrName, priority, match, action, nextHops, nil) + policies = append(policies, policy) + } + + // create two polices with other logical router key + for i := 0; i < 2; i++ { + priority := basePriority + i + policy := newLogicalRouterPolicy(lrName, priority, match, action, nextHops, nil) + policy.ExternalIDs[logicalRouterKey] = lrName + "-test" + policies = append(policies, policy) + } + + t.Run("include all policies", func(t *testing.T) { + filterFunc := policyFilter(-1, nil) + count := 0 + for _, policy := range policies { + if filterFunc(policy) { + count++ + } + } + require.Equal(t, count, 5) + }) + + t.Run("include all policies with external ids", func(t *testing.T) { + filterFunc := policyFilter(-1, map[string]string{logicalRouterKey: lrName}) + count := 0 + for _, policy := range policies { + if filterFunc(policy) { + count++ + } + } + require.Equal(t, count, 3) + }) + + t.Run("include all policies with same priority", func(t *testing.T) { + filterFunc := policyFilter(10000, map[string]string{logicalRouterKey: lrName}) + count := 0 + for _, policy := range policies { + if filterFunc(policy) { + count++ + } + } + require.Equal(t, count, 1) + }) + + t.Run("result should exclude policies when externalIDs's length is not equal", func(t *testing.T) { + t.Parallel() + + policy := newLogicalRouterPolicy(lrName, basePriority+10, match, action, nextHops, nil) + filterFunc := policyFilter(-1, map[string]string{ + logicalRouterKey: lrName, + "key": "value", + }) + + require.False(t, filterFunc(policy)) + }) +} diff --git a/pkg/ovs/ovn-nb-logical_router_port.go b/pkg/ovs/ovn-nb-logical_router_port.go index 62ddb7b102f..b9c5b589972 100644 --- a/pkg/ovs/ovn-nb-logical_router_port.go +++ b/pkg/ovs/ovn-nb-logical_router_port.go @@ -9,28 +9,351 @@ import ( "github.com/ovn-org/libovsdb/model" "github.com/ovn-org/libovsdb/ovsdb" + "k8s.io/klog/v2" + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" "github.com/kubeovn/kube-ovn/pkg/util" - "k8s.io/klog/v2" ) -func (c OvnClient) GetLogicalRouterPort(name string, ignoreNotFound bool) (*ovnnb.LogicalRouterPort, error) { +func (c *ovnClient) CreatePeerRouterPort(localRouter, remoteRouter, localRouterPortIP string) error { + localRouterPort := fmt.Sprintf("%s-%s", localRouter, remoteRouter) + remoteRouterPort := fmt.Sprintf("%s-%s", remoteRouter, localRouter) + + exist, err := c.LogicalRouterPortExists(localRouterPort) + if err != nil { + return err + } + + // update networks when logical router port exists + if exist { + lrp := &ovnnb.LogicalRouterPort{ + Name: localRouterPort, + Networks: strings.Split(localRouterPortIP, ","), + } + return c.UpdateLogicalRouterPort(lrp, &lrp.Networks) + } + + /* create logical router port */ + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: localRouterPort, + MAC: util.GenerateMac(), + Networks: strings.Split(localRouterPortIP, ","), + Peer: &remoteRouterPort, + } + + ops, err := c.CreateLogicalRouterPortOp(lrp, localRouter) + if err != nil { + return err + } + + if err = c.Transact("lrp-add", ops); err != nil { + return fmt.Errorf("create peer router port %s for logical router%s: %v", localRouterPort, localRouter, err) + } + + return nil +} + +func (c *ovnClient) UpdateLogicalRouterPortRA(lrpName, ipv6RAConfigsStr string, enableIPv6RA bool) error { + lrp, err := c.GetLogicalRouterPort(lrpName, false) + if err != nil { + return err + } + + if !enableIPv6RA { + lrp.Ipv6Prefix = nil + lrp.Ipv6RaConfigs = nil + } else { + lrp.Ipv6Prefix = getIpv6Prefix(lrp.Networks) + lrp.Ipv6RaConfigs = parseIpv6RaConfigs(ipv6RAConfigsStr) + + // dhcpv6 works only with Ipv6Prefix and Ipv6RaConfigs + if len(lrp.Ipv6Prefix) == 0 || len(lrp.Ipv6RaConfigs) == 0 { + klog.Warningf("dhcpv6 works only with Ipv6Prefix and Ipv6RaConfigs") + return nil + } + } + + return c.UpdateLogicalRouterPort(lrp, &lrp.Ipv6Prefix, &lrp.Ipv6RaConfigs) +} + +// UpdateLogicalRouterPort update logical router port +func (c *ovnClient) UpdateLogicalRouterPort(lrp *ovnnb.LogicalRouterPort, fields ...interface{}) error { + if lrp == nil { + return fmt.Errorf("logical_router_port is nil") + } + + op, err := c.Where(lrp).Update(lrp, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating logical router port %s: %v", lrp.Name, err) + } + + if err = c.Transact("lrp-update", op); err != nil { + return fmt.Errorf("update logical router port %s: %v", lrp.Name, err) + } + + return nil +} + +// CreateLogicalRouterPort create logical router port with basic configuration +func (c *ovnClient) CreateLogicalRouterPort(lrName string, lrpName, mac string, networks []string) error { + exists, err := c.LogicalRouterPortExists(lrpName) + if err != nil { + return err + } + + // ignore + if exists { + return nil + } + + if mac == "" { + mac = util.GenerateMac() + } + + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: lrpName, + MAC: mac, + Networks: networks, + } + + op, err := c.CreateLogicalRouterPortOp(lrp, lrName) + if err != nil { + return fmt.Errorf("generate operations for creating logical router port %s: %v", lrp.Name, err) + } + + if err = c.Transact("lrp-add", op); err != nil { + return fmt.Errorf("create logical router port %s: %v", lrp.Name, err) + } + + return nil +} + +// DeleteLogicalRouterPort delete logical router port from logical router +func (c *ovnClient) DeleteLogicalRouterPorts(externalIDs map[string]string, filter func(lrp *ovnnb.LogicalRouterPort) bool) error { + lrpList, err := c.ListLogicalRouterPorts(externalIDs, filter) + if err != nil { + return fmt.Errorf("list logical router ports: %v", err) + } + + ops := make([]ovsdb.Operation, 0, len(lrpList)) + for _, lrp := range lrpList { + op, err := c.DeleteLogicalRouterPortOp(lrp.Name) + if err != nil { + return fmt.Errorf("generate operations for deleting logical router port %s: %v", lrp.Name, err) + } + ops = append(ops, op...) + } + + if err := c.Transact("lrps-del", ops); err != nil { + return fmt.Errorf("del logical router ports: %v", err) + } + + return nil +} + +// DeleteLogicalRouterPort delete logical router port from logical router +func (c *ovnClient) DeleteLogicalRouterPort(lrpName string) error { + ops, err := c.DeleteLogicalRouterPortOp(lrpName) + if err != nil { + return err + } + + if err = c.Transact("lrp-del", ops); err != nil { + return fmt.Errorf("delete logical router port %s", lrpName) + } + + return nil +} + +// GetLogicalRouterPort get logical router port by name, +func (c *ovnClient) GetLogicalRouterPort(lrpName string, ignoreNotFound bool) (*ovnnb.LogicalRouterPort, error) { ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) defer cancel() - lrp := &ovnnb.LogicalRouterPort{Name: name} - if err := c.ovnNbClient.Get(ctx, lrp); err != nil { + lrp := &ovnnb.LogicalRouterPort{Name: lrpName} + if err := c.Get(ctx, lrp); err != nil { if ignoreNotFound && err == client.ErrNotFound { return nil, nil } - return nil, fmt.Errorf("failed to get logical router port %s: %v", name, err) + + return nil, fmt.Errorf("get logical router port %s: %v", lrpName, err) } return lrp, nil } -func (c OvnClient) AddLogicalRouterPort(lr, name, mac, networks string) error { +// ListLogicalRouterPorts list logical router ports +func (c *ovnClient) ListLogicalRouterPorts(externalIDs map[string]string, filter func(lrp *ovnnb.LogicalRouterPort) bool) ([]ovnnb.LogicalRouterPort, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + lrpList := make([]ovnnb.LogicalRouterPort, 0) + + if err := c.WhereCache(logicalRouterPortFilter(externalIDs, filter)).List(ctx, &lrpList); err != nil { + return nil, fmt.Errorf("list logical router ports: %v", err) + } + + return lrpList, nil +} + +func (c *ovnClient) LogicalRouterPortExists(lrpName string) (bool, error) { + lrp, err := c.GetLogicalRouterPort(lrpName, true) + return lrp != nil, err +} + +// LogicalRouterPortUpdateGatewayChassisOp create operations add to or delete gateway chassis from logical router port +func (c *ovnClient) LogicalRouterPortUpdateGatewayChassisOp(lrpName string, uuids []string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(uuids) == 0 { + return nil, nil + } + + mutation := func(lrp *ovnnb.LogicalRouterPort) *model.Mutation { + mutation := &model.Mutation{ + Field: &lrp.GatewayChassis, + Value: uuids, + Mutator: op, + } + + return mutation + } + + return c.LogicalRouterPortOp(lrpName, mutation) +} + +// CreateLogicalRouterPortOp create operation which create logical router port +func (c *ovnClient) CreateLogicalRouterPortOp(lrp *ovnnb.LogicalRouterPort, lrName string) ([]ovsdb.Operation, error) { + if lrp == nil { + return nil, fmt.Errorf("logical_router_port is nil") + } + + if lrp.ExternalIDs == nil { + lrp.ExternalIDs = make(map[string]string) + } + + // attach necessary info + lrp.ExternalIDs[logicalRouterKey] = lrName + lrp.ExternalIDs["vendor"] = util.CniTypeName + + /* create logical router port */ + lrpCreateOp, err := c.Create(lrp) + if err != nil { + return nil, fmt.Errorf("generate operations for creating logical router port %s: %v", lrp.Name, err) + } + + /* add logical router port to logical router*/ + lrpAddOp, err := c.LogicalRouterUpdatePortOp(lrName, lrp.UUID, ovsdb.MutateOperationInsert) + if err != nil { + return nil, err + } + + ops := make([]ovsdb.Operation, 0, len(lrpCreateOp)+len(lrpAddOp)) + ops = append(ops, lrpCreateOp...) + ops = append(ops, lrpAddOp...) + + return ops, nil +} + +// DeleteLogicalRouterPortOp create operation which delete logical router port +func (c *ovnClient) DeleteLogicalRouterPortOp(lrpName string) ([]ovsdb.Operation, error) { + lrp, err := c.GetLogicalRouterPort(lrpName, true) + if err != nil { + return nil, fmt.Errorf("get logical router port %s when generate delete operations: %v", lrpName, err) + } + + // not found, skip + if lrp == nil { + return nil, nil + } + + lrName, ok := lrp.ExternalIDs[logicalRouterKey] + if !ok { + return nil, fmt.Errorf("key %s does not exist in external_ids of lrp %s", logicalRouterKey, lrpName) + } + + // remove logical router port from logical router + lrpRemoveOp, err := c.LogicalRouterUpdatePortOp(lrName, lrp.UUID, ovsdb.MutateOperationDelete) + if err != nil { + return nil, err + } + + // delete logical router port + lrpDelOp, err := c.Where(lrp).Delete() + if err != nil { + return nil, err + } + + ops := make([]ovsdb.Operation, 0, len(lrpRemoveOp)+len(lrpDelOp)) + ops = append(ops, lrpRemoveOp...) + ops = append(ops, lrpDelOp...) + + return ops, nil +} + +// LogicalRouterPortOp create operations about logical router port +func (c *ovnClient) LogicalRouterPortOp(lrpName string, mutationsFunc ...func(lrp *ovnnb.LogicalRouterPort) *model.Mutation) ([]ovsdb.Operation, error) { + lrp, err := c.GetLogicalRouterPort(lrpName, false) + if err != nil { + return nil, err + } + + if len(mutationsFunc) == 0 { + return nil, nil + } + + mutations := make([]model.Mutation, 0, len(mutationsFunc)) + + for _, f := range mutationsFunc { + mutation := f(lrp) + + if mutation != nil { + mutations = append(mutations, *mutation) + } + } + + ops, err := c.ovnNbClient.Where(lrp).Mutate(lrp, mutations...) + if err != nil { + return nil, fmt.Errorf("generate operations for mutating logical router port %s: %v", lrpName, err) + } + + return ops, nil +} + +// logicalRouterPortFilter filter logical router port which match the given externalIDs and external filter func +func logicalRouterPortFilter(externalIDs map[string]string, filter func(lrp *ovnnb.LogicalRouterPort) bool) func(lrp *ovnnb.LogicalRouterPort) bool { + return func(lrp *ovnnb.LogicalRouterPort) bool { + if len(lrp.ExternalIDs) < len(externalIDs) { + return false + } + + if len(lrp.ExternalIDs) != 0 { + for k, v := range externalIDs { + // if only key exist but not value in externalIDs, we should include this lsp, + // it's equal to shell command `ovn-nbctl --columns=xx find logical_router_port external_ids:key!=\"\"` + if len(v) == 0 { + if len(lrp.ExternalIDs[k]) == 0 { + return false + } + } else { + if lrp.ExternalIDs[k] != v { + return false + } + } + } + } + + // need meet custom filter + if filter != nil { + return filter(lrp) + } + + return true + } +} + +func (c *ovnClient) AddLogicalRouterPort(lr, name, mac, networks string) error { router, err := c.GetLogicalRouter(lr, false) if err != nil { return err @@ -72,13 +395,8 @@ func (c OvnClient) AddLogicalRouterPort(lr, name, mac, networks string) error { } ops = append(ops, mutationOps...) klog.Infof("add vpc lrp %s, networks %s", name, networks) - if err := Transact(c.ovnNbClient, "lrp-add", ops, c.ovnNbClient.Timeout); err != nil { + if err := c.Transact("lrp-add", ops); err != nil { return fmt.Errorf("failed to create logical router port %s: %v", name, err) } return nil } - -func (c OvnClient) LogicalRouterPortExists(name string) (bool, error) { - lrp, err := c.GetLogicalRouterPort(name, true) - return lrp != nil, err -} diff --git a/pkg/ovs/ovn-nb-logical_router_port_test.go b/pkg/ovs/ovn-nb-logical_router_port_test.go new file mode 100644 index 00000000000..99a50ae3950 --- /dev/null +++ b/pkg/ovs/ovn-nb-logical_router_port_test.go @@ -0,0 +1,610 @@ +package ovs + +import ( + "fmt" + "testing" + + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/stretchr/testify/require" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func newLogicalRouterPort(lrName, lrpName, mac string, networks []string) *ovnnb.LogicalRouterPort { + return &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: lrpName, + MAC: mac, + Networks: networks, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + } +} + +func createLogicalRouterPort(c *ovnClient, lrp *ovnnb.LogicalRouterPort) error { + op, err := c.Create(lrp) + if err != nil { + return fmt.Errorf("generate operations for creating logical router port %s: %v", lrp.Name, err) + } + + return c.Transact("lrp-create", op) +} + +func (suite *OvnClientTestSuite) testCreatePeerRouterPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + localLrName := "test-create-peer-lr-local" + remoteLrName := "test-create-peer-lr-remote" + localRouterPort := fmt.Sprintf("%s-%s", localLrName, remoteLrName) + remoteRouterPort := fmt.Sprintf("%s-%s", remoteLrName, localLrName) + + err := ovnClient.CreateLogicalRouter(localLrName) + require.NoError(t, err) + + t.Run("create new port", func(t *testing.T) { + err = ovnClient.CreatePeerRouterPort(localLrName, remoteLrName, "192.168.230.1/24,192.168.231.1/24") + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(localRouterPort, false) + require.NoError(t, err) + require.NotEmpty(t, lrp.UUID) + require.Equal(t, localLrName, lrp.ExternalIDs[logicalRouterKey]) + require.Equal(t, []string{"192.168.230.1/24", "192.168.231.1/24"}, lrp.Networks) + require.Equal(t, remoteRouterPort, *lrp.Peer) + + lr, err := ovnClient.GetLogicalRouter(localLrName, false) + require.NoError(t, err) + require.Equal(t, []string{lrp.UUID}, lr.Ports) + }) + + t.Run("update port networks", func(t *testing.T) { + err = ovnClient.CreatePeerRouterPort(localLrName, remoteLrName, "192.168.230.1/24,192.168.241.1/24") + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(localRouterPort, false) + require.NoError(t, err) + require.NotEmpty(t, lrp.UUID) + require.Equal(t, []string{"192.168.230.1/24", "192.168.241.1/24"}, lrp.Networks) + require.Equal(t, remoteRouterPort, *lrp.Peer) + }) +} + +func (suite *OvnClientTestSuite) testUpdateLogicalRouterPortRA() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrpName := "test-update-ra-lrp" + lrName := "test-update-ra-lr" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateLogicalRouterPort(lrName, lrpName, "00:11:22:37:af:62", []string{"fd00::c0a8:1001/120"}) + require.NoError(t, err) + + t.Run("update ipv6 ra config when enableIPv6RA is true and ipv6RAConfigsStr is empty", func(t *testing.T) { + err := ovnClient.UpdateLogicalRouterPortRA(lrpName, "", true) + require.NoError(t, err) + + out, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Equal(t, []string{"120"}, out.Ipv6Prefix) + require.Equal(t, map[string]string{ + "address_mode": "dhcpv6_stateful", + "max_interval": "30", + "min_interval": "5", + "send_periodic": "true", + }, out.Ipv6RaConfigs) + }) + + t.Run("update ipv6 ra config when enableIPv6RA is true and exist ipv6RAConfigsStr", func(t *testing.T) { + err := ovnClient.UpdateLogicalRouterPortRA(lrpName, "address_mode=dhcpv6_stateful,max_interval=30", true) + require.NoError(t, err) + + out, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Equal(t, []string{"120"}, out.Ipv6Prefix) + require.Equal(t, map[string]string{ + "address_mode": "dhcpv6_stateful", + "max_interval": "30", + }, out.Ipv6RaConfigs) + }) + + t.Run("update ipv6 ra config when enableIPv6RA is false", func(t *testing.T) { + err := ovnClient.UpdateLogicalRouterPortRA(lrpName, "address_mode=dhcpv6_stateful,max_interval=30", false) + require.NoError(t, err) + + out, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Empty(t, out.Ipv6Prefix) + require.Empty(t, out.Ipv6RaConfigs) + + }) + + t.Run("do nothing when enableIPv6RA is true and ipv6RAConfigsStr is invalid", func(t *testing.T) { + err := ovnClient.UpdateLogicalRouterPortRA(lrpName, "address_mode=,test ", true) + require.NoError(t, err) + }) + + t.Run("do nothing when enableIPv6RA is true and no ipv6 network", func(t *testing.T) { + lrpName := "test-update-ra-lr-no-ipv6" + err := ovnClient.CreateLogicalRouterPort(lrName, lrpName, "", nil) + require.NoError(t, err) + + err = ovnClient.UpdateLogicalRouterPortRA(lrpName, "address_mode=dhcpv6_stateful,max_interval=30", true) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testCreateLogicalRouterPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + LrName := "test-create-lrp-lr" + + err := ovnClient.CreateLogicalRouter(LrName) + require.NoError(t, err) + + t.Run("create new logical router port with ipv4", func(t *testing.T) { + t.Parallel() + + lrpName := "test-create-lrp-ipv4" + + err := ovnClient.CreateLogicalRouterPort(LrName, lrpName, "00:11:22:37:af:62", []string{"192.168.123.1/24"}) + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.NotEmpty(t, lrp.UUID) + require.Equal(t, "00:11:22:37:af:62", lrp.MAC) + require.Equal(t, []string{"192.168.123.1/24"}, lrp.Networks) + + lr, err := ovnClient.GetLogicalRouter(LrName, false) + require.NoError(t, err) + require.Contains(t, lr.Ports, lrp.UUID) + }) + + t.Run("create new logical router port with ipv6", func(t *testing.T) { + t.Parallel() + + lrpName := "test-create-lrp-ipv6" + + err := ovnClient.CreateLogicalRouterPort(LrName, lrpName, "00:11:22:37:af:62", []string{"fd00::c0a8:7b01/120"}) + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.NotEmpty(t, lrp.UUID) + require.Equal(t, "00:11:22:37:af:62", lrp.MAC) + require.Equal(t, []string{"fd00::c0a8:7b01/120"}, lrp.Networks) + + lr, err := ovnClient.GetLogicalRouter(LrName, false) + require.NoError(t, err) + require.Contains(t, lr.Ports, lrp.UUID) + }) + + t.Run("create new logical router port with dual", func(t *testing.T) { + t.Parallel() + + lrpName := "test-create-lrp-dual" + err := ovnClient.CreateLogicalRouterPort(LrName, lrpName, "00:11:22:37:af:62", []string{"192.168.123.1/24", "fd00::c0a8:7b01/120"}) + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.NotEmpty(t, lrp.UUID) + require.Equal(t, "00:11:22:37:af:62", lrp.MAC) + require.Equal(t, []string{"192.168.123.1/24", "fd00::c0a8:7b01/120"}, lrp.Networks) + + lr, err := ovnClient.GetLogicalRouter(LrName, false) + require.NoError(t, err) + require.Contains(t, lr.Ports, lrp.UUID) + }) +} + +func (suite *OvnClientTestSuite) testUpdateLogicalRouterPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrpName := "test-update-lrp" + lrName := "test-update-lrp-lr" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateLogicalRouterPort(lrName, lrpName, "00:11:22:37:af:62", []string{"192.168.123.1/24"}) + require.NoError(t, err) + + t.Run("normal update", func(t *testing.T) { + lrp := &ovnnb.LogicalRouterPort{ + Name: lrpName, + Networks: []string{"192.168.123.1/24", "192.168.125.1/24"}, + } + err = ovnClient.UpdateLogicalRouterPort(lrp) + require.NoError(t, err) + + lrp, err = ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Equal(t, []string{"192.168.123.1/24", "192.168.125.1/24"}, lrp.Networks) + }) + + t.Run("clear networks", func(t *testing.T) { + lrp := &ovnnb.LogicalRouterPort{ + Name: lrpName, + Networks: nil, + } + err = ovnClient.UpdateLogicalRouterPort(lrp, &lrp.Networks) + require.NoError(t, err) + + lrp, err = ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Empty(t, lrp.Networks) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalRouterPorts() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + prefix := "test-del-ports-lrp" + lrName := "test-del-ports-lr" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + lrpName := fmt.Sprintf("%s-%d", prefix, i) + err = ovnClient.CreateLogicalRouterPort(lrName, lrpName, "00:11:22:37:af:62", []string{"192.168.123.1/24"}) + require.NoError(t, err) + } + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + for i := 0; i < 3; i++ { + lrpName := fmt.Sprintf("%s-%d", prefix, i) + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Contains(t, lr.Ports, lrp.UUID) + } + + err = ovnClient.DeleteLogicalRouterPorts(nil, func(lrp *ovnnb.LogicalRouterPort) bool { + return len(lrp.ExternalIDs) != 0 && lrp.ExternalIDs[logicalRouterKey] == lrName + }) + + require.NoError(t, err) + + for i := 0; i < 3; i++ { + lrpName := fmt.Sprintf("%s-%d", prefix, i) + _, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.ErrorContains(t, err, "object not found") + } + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Ports) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalRouterPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrpName := "test-delete-port-lrp" + lrName := "test-delete-port-lr" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateLogicalRouterPort(lrName, lrpName, "00:11:22:37:af:62", []string{"192.168.123.1/24"}) + require.NoError(t, err) + + t.Run("no err when delete existent logical router port", func(t *testing.T) { + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Contains(t, lr.Ports, lrp.UUID) + + err = ovnClient.DeleteLogicalRouterPort(lrpName) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalRouterPort(lrpName, false) + require.ErrorContains(t, err, "object not found") + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.NotContains(t, lr.Ports, lrp.UUID) + }) + + t.Run("no err when delete non-existent logical router port", func(t *testing.T) { + err := ovnClient.DeleteLogicalRouterPort("test-delete-lrp-non-existent") + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testCreateLogicalRouterPortOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrpName := "test-create-op-lrp" + lrName := "test-create-op-lr" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("merge ExternalIDs when exist ExternalIDs", func(t *testing.T) { + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: lrpName, + ExternalIDs: map[string]string{ + "pod": lrpName, + }, + } + + ops, err := ovnClient.CreateLogicalRouterPortOp(lrp, lrName) + require.NoError(t, err) + require.Len(t, ops, 2) + require.Equal(t, ovsdb.OvsMap{ + GoMap: map[interface{}]interface{}{ + logicalRouterKey: lrName, + "vendor": util.CniTypeName, + "pod": lrpName, + }, + }, ops[0].Row["external_ids"]) + + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lrp.UUID, + }, + }, + }, + }, + }, ops[1].Mutations) + }) + + t.Run("attach ExternalIDs when does't exist ExternalIDs", func(t *testing.T) { + lrpName := "test-create-op-lrp-none-ext-id" + + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: lrpName, + } + + ops, err := ovnClient.CreateLogicalRouterPortOp(lrp, lrName) + require.NoError(t, err) + + require.Len(t, ops, 2) + require.Equal(t, ovsdb.OvsMap{ + GoMap: map[interface{}]interface{}{ + logicalRouterKey: lrName, + "vendor": util.CniTypeName, + }, + }, ops[0].Row["external_ids"]) + + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lrp.UUID, + }, + }, + }, + }, + }, ops[1].Mutations) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalRouterPortOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrpName := "test-del-op-lrp" + lrName := "test-del-op-lr" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateLogicalRouterPort(lrName, lrpName, "00:11:22:37:af:62", []string{"192.168.123.1/24"}) + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + + ops, err := ovnClient.DeleteLogicalRouterPortOp(lrpName) + require.NoError(t, err) + require.Len(t, ops, 2) + + require.Equal(t, + []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lrp.UUID, + }, + }, + }, + }, + }, ops[0].Mutations) + + require.Equal(t, + ovsdb.Operation{ + Op: "delete", + Table: "Logical_Router_Port", + Where: []ovsdb.Condition{ + { + Column: "_uuid", + Function: "==", + Value: ovsdb.UUID{ + GoUUID: lrp.UUID, + }, + }, + }, + }, ops[1]) +} + +func (suite *OvnClientTestSuite) testLogicalRouterPortOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrpName := "test-op-lrp" + + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: lrpName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + }, + } + + err := createLogicalRouterPort(ovnClient, lrp) + require.NoError(t, err) + + gwChassisUUID := ovsclient.NamedUUID() + + mutation := func(lrp *ovnnb.LogicalRouterPort) *model.Mutation { + mutation := &model.Mutation{ + Field: &lrp.GatewayChassis, + Value: []string{gwChassisUUID}, + Mutator: ovsdb.MutateOperationInsert, + } + + return mutation + } + + ops, err := ovnClient.LogicalRouterPortOp(lrpName, mutation) + require.NoError(t, err) + + require.Len(t, ops[0].Mutations, 1) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "gateway_chassis", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: gwChassisUUID, + }, + }, + }, + }, + }, ops[0].Mutations) +} + +func (suite *OvnClientTestSuite) testlogicalRouterPortFilter() { + t := suite.T() + t.Parallel() + + lrName := "test-filter-lrp-lr" + prefix := "test-filter-lrp" + networks := []string{"192.168.200.1/24"} + lrps := make([]*ovnnb.LogicalRouterPort, 0) + + i := 0 + // create three normal lrp + for ; i < 3; i++ { + lrpName := fmt.Sprintf("%s-%d", prefix, i) + lrp := newLogicalRouterPort(lrName, lrpName, util.GenerateMac(), networks) + lrps = append(lrps, lrp) + } + + // create two peer lrp + for ; i < 5; i++ { + lrpName := fmt.Sprintf("%s-%d", prefix, i) + lrp := newLogicalRouterPort(lrName, lrpName, util.GenerateMac(), networks) + peer := lrpName + "-peer" + lrp.Peer = &peer + lrps = append(lrps, lrp) + } + + // create two normal lrp with different logical router name + for ; i < 6; i++ { + lrpName := fmt.Sprintf("%s-%d", prefix, i) + lrp := newLogicalRouterPort(lrName, lrpName, util.GenerateMac(), networks) + lrp.ExternalIDs[logicalRouterKey] = lrName + "-test" + lrps = append(lrps, lrp) + } + + t.Run("include all lrp", func(t *testing.T) { + filterFunc := logicalRouterPortFilter(nil, nil) + count := 0 + for _, lrp := range lrps { + if filterFunc(lrp) { + count++ + } + } + require.Equal(t, count, 6) + }) + + t.Run("include all lrp with external ids", func(t *testing.T) { + filterFunc := logicalRouterPortFilter(map[string]string{logicalRouterKey: lrName}, nil) + count := 0 + for _, lrp := range lrps { + if filterFunc(lrp) { + count++ + } + } + require.Equal(t, count, 5) + }) + + t.Run("include all logicalRouterKey lrp with external ids key's value is empty", func(t *testing.T) { + filterFunc := logicalRouterPortFilter(map[string]string{logicalRouterKey: ""}, nil) + count := 0 + for _, lrp := range lrps { + if filterFunc(lrp) { + count++ + } + } + require.Equal(t, count, 6) + }) + + t.Run("meet custom filter func", func(t *testing.T) { + filterFunc := logicalRouterPortFilter(nil, func(lrp *ovnnb.LogicalRouterPort) bool { + return lrp.Peer != nil && len(*lrp.Peer) != 0 + }) + count := 0 + for _, lrp := range lrps { + if filterFunc(lrp) { + count++ + } + } + require.Equal(t, count, 2) + }) + + t.Run("externalIDs's length is not equal", func(t *testing.T) { + t.Parallel() + + lrp := newLogicalRouterPort(lrName, prefix+"-test", util.GenerateMac(), networks) + filterFunc := logicalRouterPortFilter(map[string]string{ + logicalRouterKey: lrName, + "key": "value", + }, nil) + + require.False(t, filterFunc(lrp)) + }) +} diff --git a/pkg/ovs/ovn-nb-logical_router_route.go b/pkg/ovs/ovn-nb-logical_router_route.go index c4b43771c3e..dc9058cfc9f 100644 --- a/pkg/ovs/ovn-nb-logical_router_route.go +++ b/pkg/ovs/ovn-nb-logical_router_route.go @@ -2,13 +2,19 @@ package ovs import ( "context" + "fmt" + "strings" "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" ) -func (c OvnClient) GetLogicalRouterRouteByOpts(key, value string) ([]ovnnb.LogicalRouterStaticRoute, error) { +func (c *ovnClient) GetLogicalRouterRouteByOpts(key, value string) ([]ovnnb.LogicalRouterStaticRoute, error) { ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) defer cancel() @@ -20,14 +26,14 @@ func (c OvnClient) GetLogicalRouterRouteByOpts(key, value string) ([]ovnnb.Logic } var lrRouteList []ovnnb.LogicalRouterStaticRoute - if err = api.List(context.TODO(), &lrRouteList); err != nil && err != client.ErrNotFound { + if err = api.List(ctx, &lrRouteList); err != nil && err != client.ErrNotFound { return nil, err } return lrRouteList, nil } -func (c OvnClient) GetLogicalRouterPoliciesByExtID(key, value string) ([]ovnnb.LogicalRouterPolicy, error) { +func (c *ovnClient) GetLogicalRouterPoliciesByExtID(key, value string) ([]ovnnb.LogicalRouterPolicy, error) { ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) defer cancel() @@ -39,9 +45,328 @@ func (c OvnClient) GetLogicalRouterPoliciesByExtID(key, value string) ([]ovnnb.L } var lrPolicyList []ovnnb.LogicalRouterPolicy - if err = api.List(context.TODO(), &lrPolicyList); err != nil && err != client.ErrNotFound { + if err = api.List(ctx, &lrPolicyList); err != nil && err != client.ErrNotFound { return nil, err } return lrPolicyList, nil } + +func (c *ovnClient) CreateBareLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType string) error { + route, err := c.newLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType) + if err != nil { + return err + } + + op, err := c.ovnNbClient.Create(route) + if err != nil { + return fmt.Errorf("generate operations for creating logical router static route: %v", err) + } + + if err = c.Transact("lr-route-add", op); err != nil { + return fmt.Errorf("create logical router static route: %v", err) + } + + return nil +} + +// CreateLogicalRouterStaticRoutes create several logical router static route once +func (c *ovnClient) CreateLogicalRouterStaticRoutes(lrName string, routes ...*ovnnb.LogicalRouterStaticRoute) error { + if len(routes) == 0 { + return nil + } + + models := make([]model.Model, 0, len(routes)) + routeUUIDs := make([]string, 0, len(routes)) + for _, route := range routes { + if route != nil { + models = append(models, model.Model(route)) + routeUUIDs = append(routeUUIDs, route.UUID) + } + } + + createRoutesOp, err := c.ovnNbClient.Create(models...) + if err != nil { + return fmt.Errorf("generate operations for creating static routes: %v", err) + } + + routeAddOp, err := c.LogicalRouterUpdateStaticRouteOp(lrName, routeUUIDs, ovsdb.MutateOperationInsert) + if err != nil { + return fmt.Errorf("generate operations for adding static routes to logical router %s: %v", lrName, err) + } + + ops := make([]ovsdb.Operation, 0, len(createRoutesOp)+len(routeAddOp)) + ops = append(ops, createRoutesOp...) + ops = append(ops, routeAddOp...) + + if err = c.Transact("lr-routes-add", ops); err != nil { + return fmt.Errorf("add static routes to %s: %v", lrName, err) + } + + return nil +} + +// AddLogicalRouterStaticRoute add a logical router static route +func (c *ovnClient) AddLogicalRouterStaticRoute(lrName, policy, cidrBlock, nextHops, routeType string) error { + if len(policy) == 0 { + policy = ovnnb.LogicalRouterStaticRoutePolicyDstIP + } + + routes := make([]*ovnnb.LogicalRouterStaticRoute, 0, 2) + + for _, prefix := range strings.Split(cidrBlock, ",") { + for _, nextHop := range strings.Split(nextHops, ",") { + if util.CheckProtocol(prefix) != util.CheckProtocol(nextHop) { + continue // ignore different address family + } + + if strings.Contains(nextHop, "/") { + nextHop = strings.Split(nextHop, "/")[0] + } + + route, err := c.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType, true) + if err != nil { + return err + } + + if route != nil { + if routeType == util.EcmpRouteType { + continue // ignore existent same nextHop ecmp route + } + + // update existent normal route nextHop + route.Nexthop = nextHop + if err := c.UpdateLogicalRouterStaticRoute(route, &route.Nexthop); err != nil { + return fmt.Errorf("update logical router static route nextHop %s: %v", nextHop, err) + } + } else { + // new route + route, err = c.newLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType) + if err != nil { + return err + } + routes = append(routes, route) + } + } + } + + if err := c.CreateLogicalRouterStaticRoutes(lrName, routes...); err != nil { + return fmt.Errorf("add static routes to logical router %s: %v", lrName, err) + } + + return nil +} + +// UpdateLogicalRouterStaticRoute update logical router static route +func (c *ovnClient) UpdateLogicalRouterStaticRoute(route *ovnnb.LogicalRouterStaticRoute, fields ...interface{}) error { + if route == nil { + return fmt.Errorf("route is nil") + } + + op, err := c.ovnNbClient.Where(route).Update(route, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating logical router static route 'policy %s prefix %s': %v", *route.Policy, route.IPPrefix, err) + } + + if err = c.Transact("net-update", op); err != nil { + return fmt.Errorf("update logical router static route 'policy %s prefix %s': %v", *route.Policy, route.IPPrefix, err) + } + + return nil +} + +// DeleteLogicalRouterStaticRoute add a logical router static route +func (c *ovnClient) DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType string) error { + if len(policy) == 0 { + policy = ovnnb.LogicalRouterStaticRoutePolicyDstIP + } + + route, err := c.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType, true) + if err != nil { + return err + } + + // not found, skip + if route == nil { + return nil + } + + // remove static route from logical router + routeRemoveOp, err := c.LogicalRouterUpdateStaticRouteOp(lrName, []string{route.UUID}, ovsdb.MutateOperationDelete) + if err != nil { + return fmt.Errorf("generate operations for removing static %s from logical router %s: %v", route.UUID, lrName, err) + } + + // delete static route + routeDelOp, err := c.Where(route).Delete() + if err != nil { + return fmt.Errorf("generate operations for deleting static route %s: %v", route.UUID, err) + } + + ops := make([]ovsdb.Operation, 0, len(routeRemoveOp)+len(routeDelOp)) + ops = append(ops, routeRemoveOp...) + ops = append(ops, routeDelOp...) + + if err = c.Transact("lr-route-del", ops); err != nil { + return fmt.Errorf("delete static route %s", route.UUID) + } + + return nil +} + +// ClearLogicalRouterStaticRoute clear static route from logical router once +func (c *ovnClient) ClearLogicalRouterStaticRoute(lrName string) error { + lr, err := c.GetLogicalRouter(lrName, false) + if err != nil { + return fmt.Errorf("get logical router %s: %v", lrName, err) + } + + // clear static route + lr.StaticRoutes = nil + routeClearOp, err := c.UpdateLogicalRouterOp(lr, &lr.StaticRoutes) + if err != nil { + return fmt.Errorf("generate operations for clear logical router %s static route: %v", lrName, err) + } + + // delete static route + routeDelOp, err := c.WhereCache(func(route *ovnnb.LogicalRouterStaticRoute) bool { + return len(route.ExternalIDs) != 0 && route.ExternalIDs[logicalRouterKey] == lrName + }).Delete() + if err != nil { + return fmt.Errorf("generate operations for deleting logical router %s static routes: %v", lrName, err) + } + + ops := make([]ovsdb.Operation, 0, len(routeClearOp)+len(routeDelOp)) + ops = append(ops, routeClearOp...) + ops = append(ops, routeDelOp...) + + if err = c.Transact("lr-route-clear", ops); err != nil { + return fmt.Errorf("clear logical router %s static routes: %v", lrName, err) + } + + return nil +} + +// GetLogicalRouterStaticRoute get logical router static route by some attribute, +// a static route is uniquely identified by router(lrName), policy and prefix when route is not ecmp +// a static route is uniquely identified by router(lrName), policy, prefix and nextHop when route is ecmp +func (c *ovnClient) GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType string, ignoreNotFound bool) (*ovnnb.LogicalRouterStaticRoute, error) { + // this is necessary because may exist same static route in different logical router + if len(lrName) == 0 { + return nil, fmt.Errorf("the logical router name is required") + } + + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + routeList := make([]ovnnb.LogicalRouterStaticRoute, 0) + if err := c.ovnNbClient.WhereCache(func(route *ovnnb.LogicalRouterStaticRoute) bool { + if len(route.ExternalIDs) == 0 || route.ExternalIDs[logicalRouterKey] != lrName { + return false + } + + // ecmp route + if routeType == util.EcmpRouteType { + return route.Policy != nil && *route.Policy == policy && route.IPPrefix == prefix && route.Nexthop == nextHop + } + + // normal route + return route.Policy != nil && *route.Policy == policy && route.IPPrefix == prefix + + }).List(ctx, &routeList); err != nil { + return nil, fmt.Errorf("get logical router %s static route 'policy %s prefix %s nextHop %s': %v", lrName, policy, prefix, nextHop, err) + } + + // not found + if len(routeList) == 0 { + if ignoreNotFound { + return nil, nil + } + + return nil, fmt.Errorf("not found logical router %s static route 'policy %s prefix %s nextHop %s'", lrName, policy, prefix, nextHop) + } + + if len(routeList) > 1 { + return nil, fmt.Errorf("more than one static route 'policy %s prefix %s nextHop %s' in logical router %s", policy, prefix, nextHop, lrName) + } + + return &routeList[0], nil +} + +// ListLogicalRouterStaticRoutes list route which match the given externalIDs +func (c *ovnClient) ListLogicalRouterStaticRoutes(externalIDs map[string]string) ([]ovnnb.LogicalRouterStaticRoute, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + routeList := make([]ovnnb.LogicalRouterStaticRoute, 0) + + if err := c.WhereCache(func(route *ovnnb.LogicalRouterStaticRoute) bool { + if len(route.ExternalIDs) < len(externalIDs) { + return false + } + + if len(route.ExternalIDs) != 0 { + for k, v := range externalIDs { + // if only key exist but not value in externalIDs, we should include this lsp, + // it's equal to shell command `ovn-nbctl --columns=xx find logical_router_static_route external_ids:key!=\"\"` + if len(v) == 0 { + if len(route.ExternalIDs[k]) == 0 { + return false + } + } else { + if route.ExternalIDs[k] != v { + return false + } + } + } + } + + return true + }).List(ctx, &routeList); err != nil { + return nil, fmt.Errorf("list logical router static routes: %v", err) + } + + return routeList, nil +} + +func (c *ovnClient) LogicalRouterStaticRouteExists(lrName, policy, prefix, nextHop, routeType string) (bool, error) { + route, err := c.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType, true) + return route != nil, err +} + +// newLogicalRouterStaticRoute return logical router static route with basic information +func (c *ovnClient) newLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType string, options ...func(route *ovnnb.LogicalRouterStaticRoute)) (*ovnnb.LogicalRouterStaticRoute, error) { + if len(lrName) == 0 { + return nil, fmt.Errorf("the logical router name is required") + } + + if len(policy) == 0 { + policy = ovnnb.LogicalRouterStaticRoutePolicyDstIP + } + + exists, err := c.LogicalRouterStaticRouteExists(lrName, policy, prefix, nextHop, routeType) + if err != nil { + return nil, fmt.Errorf("get logical router %s route: %v", lrName, err) + } + + // found, ignore + if exists { + return nil, nil + } + + route := &ovnnb.LogicalRouterStaticRoute{ + UUID: ovsclient.NamedUUID(), + Policy: &policy, + IPPrefix: prefix, + Nexthop: nextHop, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + } + + for _, option := range options { + option(route) + } + + return route, nil +} diff --git a/pkg/ovs/ovn-nb-logical_router_route_test.go b/pkg/ovs/ovn-nb-logical_router_route_test.go new file mode 100644 index 00000000000..243b48b6a4a --- /dev/null +++ b/pkg/ovs/ovn-nb-logical_router_route_test.go @@ -0,0 +1,373 @@ +package ovs + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func (suite *OvnClientTestSuite) testCreateLogicalRouterStaticRoutes() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-create-routes-lr" + policy := ovnnb.LogicalRouterStaticRoutePolicyDstIP + prefixes := []string{"192.168.30.0/24", "192.168.40.0/24"} + nextHops := []string{"192.168.30.1", "192.168.40.1"} + routeType := util.NormalRouteType + routes := make([]*ovnnb.LogicalRouterStaticRoute, 0, 5) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + for i, prefix := range prefixes { + route, err := ovnClient.newLogicalRouterStaticRoute(lrName, policy, prefix, nextHops[i], "") + require.NoError(t, err) + + routes = append(routes, route) + } + + err = ovnClient.CreateLogicalRouterStaticRoutes(lrName, append(routes, nil)...) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + for i, prefix := range prefixes { + route, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHops[i], routeType, false) + require.NoError(t, err) + + require.Contains(t, lr.StaticRoutes, route.UUID) + } +} + +func (suite *OvnClientTestSuite) testAddLogicalRouterStaticRoute() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-add-route-lr" + policy := ovnnb.LogicalRouterStaticRoutePolicyDstIP + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("normal route", func(t *testing.T) { + t.Parallel() + + prefixs := "192.168.30.0/24,fd00:100:64::4/64" + nextHops := "192.168.30.1/24,fd00:100:64::1" + prefixList := strings.Split(prefixs, ",") + nextHopList := strings.Split(nextHops, ",") + routeType := util.NormalRouteType + + t.Run("create route", func(t *testing.T) { + err = ovnClient.AddLogicalRouterStaticRoute(lrName, policy, prefixs, nextHops, routeType) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + for i, prefix := range prefixList { + route, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHopList[i], "", false) + require.NoError(t, err) + require.Equal(t, route.Nexthop, strings.Split(nextHopList[i], "/")[0]) + + require.Contains(t, lr.StaticRoutes, route.UUID) + } + }) + + t.Run("update route", func(t *testing.T) { + nextHops := "192.168.30.254,fd00:100:64::fe" + nextHopList := strings.Split(nextHops, ",") + + err = ovnClient.AddLogicalRouterStaticRoute(lrName, policy, prefixs, nextHops, routeType) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + for i, prefix := range prefixList { + route, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHopList[i], "", false) + require.NoError(t, err) + require.Equal(t, nextHopList[i], route.Nexthop) + + require.Contains(t, lr.StaticRoutes, route.UUID) + } + }) + }) + + t.Run("ecmp route", func(t *testing.T) { + t.Parallel() + + prefix := "192.168.40.0/24" + nextHops := "192.168.50.1,192.168.60.1" + nextHopList := strings.Split(nextHops, ",") + routeType := util.EcmpRouteType + + t.Run("create route", func(t *testing.T) { + err = ovnClient.AddLogicalRouterStaticRoute(lrName, policy, prefix, nextHops, routeType) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + for _, nextHop := range nextHopList { + route, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType, false) + require.NoError(t, err) + + require.Contains(t, lr.StaticRoutes, route.UUID) + } + }) + + t.Run("update route", func(t *testing.T) { + err = ovnClient.AddLogicalRouterStaticRoute(lrName, policy, prefix, nextHops, routeType) + require.NoError(t, err) + }) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalRouterStaticRoute() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-del-route-lr" + policy := ovnnb.LogicalRouterStaticRoutePolicyDstIP + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("normal route", func(t *testing.T) { + prefix := "192.168.30.0/24" + nextHop := "192.168.30.1" + routeType := util.NormalRouteType + + err = ovnClient.AddLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + route, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, "", false) + require.NoError(t, err) + require.Contains(t, lr.StaticRoutes, route.UUID) + + err = ovnClient.DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, "") + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.StaticRoutes) + + _, err = ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, "", false) + require.ErrorContains(t, err, "not found") + }) + + t.Run("ecmp route", func(t *testing.T) { + prefix := "192.168.40.0/24" + nextHops := "192.168.50.1,192.168.60.1" + nextHopList := strings.Split(nextHops, ",") + routeType := util.EcmpRouteType + + err = ovnClient.AddLogicalRouterStaticRoute(lrName, policy, prefix, nextHops, routeType) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + for _, nextHop := range nextHopList { + route, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType, false) + require.NoError(t, err) + require.Contains(t, lr.StaticRoutes, route.UUID) + } + + /* delete first route */ + err = ovnClient.DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHopList[0], routeType) + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHopList[0], routeType, false) + require.ErrorContains(t, err, `not found logical router test-del-route-lr static route 'policy dst-ip prefix 192.168.40.0/24 nextHop 192.168.50.1'`) + + route, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHopList[1], routeType, false) + require.NoError(t, err) + require.Equal(t, []string{route.UUID}, lr.StaticRoutes) + + /* delete second route */ + err = ovnClient.DeleteLogicalRouterStaticRoute(lrName, policy, prefix, nextHopList[1], routeType) + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.StaticRoutes) + + _, err = ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHopList[1], routeType, false) + require.ErrorContains(t, err, `not found logical router test-del-route-lr static route 'policy dst-ip prefix 192.168.40.0/24 nextHop 192.168.60.1'`) + }) +} + +func (suite *OvnClientTestSuite) testClearLogicalRouterStaticRoute() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-clear-route-lr" + policy := ovnnb.LogicalRouterStaticRoutePolicyDstIP + prefixes := []string{"192.168.30.0/24", "192.168.40.0/24"} + nextHops := []string{"192.168.30.1", "192.168.40.1"} + routes := make([]*ovnnb.LogicalRouterStaticRoute, 0, 5) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + for i, prefix := range prefixes { + route, err := ovnClient.newLogicalRouterStaticRoute(lrName, policy, prefix, nextHops[i], "") + require.NoError(t, err) + + routes = append(routes, route) + } + + err = ovnClient.CreateLogicalRouterStaticRoutes(lrName, append(routes, nil)...) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.StaticRoutes, 2) + + err = ovnClient.ClearLogicalRouterStaticRoute(lrName) + require.NoError(t, err) + + for _, prefix := range prefixes { + _, err = ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, "", "", false) + require.ErrorContains(t, err, "not found") + } + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.StaticRoutes) +} + +func (suite *OvnClientTestSuite) testGetLogicalRouterStaticRoute() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test_get_route_lr" + + t.Run("normal route", func(t *testing.T) { + t.Parallel() + policy := ovnnb.LogicalRouterStaticRoutePolicyDstIP + prefix := "192.168.30.0/24" + nextHop := "192.168.30.1" + routeType := util.NormalRouteType + + err := ovnClient.CreateBareLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType) + require.NoError(t, err) + + t.Run("found route", func(t *testing.T) { + _, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType, false) + require.NoError(t, err) + }) + + t.Run("policy is different", func(t *testing.T) { + _, err := ovnClient.GetLogicalRouterStaticRoute(lrName, "src-ip", prefix, nextHop, routeType, false) + require.ErrorContains(t, err, "not found") + }) + + t.Run("prefix is different", func(t *testing.T) { + _, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, "192.168.30.10", nextHop, routeType, false) + require.ErrorContains(t, err, "not found") + }) + + t.Run("logical router name is different", func(t *testing.T) { + _, err := ovnClient.GetLogicalRouterStaticRoute(lrName+"x", policy, prefix, nextHop, routeType, false) + require.ErrorContains(t, err, "not found") + }) + }) + + t.Run("ecmp route", func(t *testing.T) { + t.Parallel() + policy := ovnnb.LogicalRouterStaticRoutePolicyDstIP + prefix := "192.168.40.0/24" + nextHop := "192.168.40.1" + routeType := util.EcmpRouteType + + err := ovnClient.CreateBareLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType) + require.NoError(t, err) + + t.Run("found route", func(t *testing.T) { + _, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType, false) + require.NoError(t, err) + }) + + t.Run("nextHop is different", func(t *testing.T) { + _, err := ovnClient.GetLogicalRouterStaticRoute(lrName, policy, prefix, nextHop+"1", routeType, false) + require.ErrorContains(t, err, "not found") + }) + }) +} + +func (suite *OvnClientTestSuite) testListLogicalRouterStaticRoutes() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-list-routes-lr" + policy := ovnnb.LogicalRouterStaticRoutePolicyDstIP + prefixes := []string{"192.168.30.0/24", "192.168.40.0/24", "192.168.50.0/24"} + nextHops := []string{"192.168.30.1", "192.168.40.1", "192.168.50.1"} + routes := make([]*ovnnb.LogicalRouterStaticRoute, 0, 5) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + for i, prefix := range prefixes { + route, err := ovnClient.newLogicalRouterStaticRoute(lrName, policy, prefix, nextHops[i], "") + require.NoError(t, err) + + routes = append(routes, route) + } + + err = ovnClient.CreateLogicalRouterStaticRoutes(lrName, append(routes, nil)...) + require.NoError(t, err) + + t.Run("include same router routes", func(t *testing.T) { + out, err := ovnClient.ListLogicalRouterStaticRoutes(map[string]string{logicalRouterKey: lrName}) + require.NoError(t, err) + require.Len(t, out, 3) + }) +} + +func (suite *OvnClientTestSuite) test_newLogicalRouterStaticRoute() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-new-route-lr" + policy := ovnnb.LogicalRouterStaticRoutePolicyDstIP + prefix := "192.168.30.0/24" + nextHop := "192.168.30.1" + routeType := util.NormalRouteType + + expect := &ovnnb.LogicalRouterStaticRoute{ + Policy: &policy, + IPPrefix: prefix, + Nexthop: nextHop, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + } + + route, err := ovnClient.newLogicalRouterStaticRoute(lrName, policy, prefix, nextHop, routeType) + require.NoError(t, err) + expect.UUID = route.UUID + require.Equal(t, expect, route) +} diff --git a/pkg/ovs/ovn-nb-logical_router_test.go b/pkg/ovs/ovn-nb-logical_router_test.go new file mode 100644 index 00000000000..0ac43b6182c --- /dev/null +++ b/pkg/ovs/ovn-nb-logical_router_test.go @@ -0,0 +1,496 @@ +package ovs + +import ( + "fmt" + "strings" + "testing" + + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/stretchr/testify/require" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +// createLogicalRouter delete logical router in ovn +func createLogicalRouter(c *ovnClient, lr *ovnnb.LogicalRouter) error { + op, err := c.ovnNbClient.Create(lr) + if err != nil { + return err + } + + if err := c.Transact("lr-add", op); err != nil { + return err + } + + return nil +} + +func (suite *OvnClientTestSuite) testCreateLogicalRouter() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + name := "test-create-lr" + + err := ovnClient.CreateLogicalRouter(name) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(name, false) + require.NoError(t, err) + require.Equal(t, name, lr.Name) + require.NotEmpty(t, lr.UUID) + require.Equal(t, util.CniTypeName, lr.ExternalIDs["vendor"]) +} + +func (suite *OvnClientTestSuite) testUpdateLogicalRouter() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-update-lr" + policies := []string{ovsclient.NamedUUID(), ovsclient.NamedUUID()} + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + t.Run("update policy", func(t *testing.T) { + lr.Policies = policies + + err = ovnClient.UpdateLogicalRouter(lr) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.ElementsMatch(t, lr.Policies, policies) + }) + + t.Run("clear policy", func(t *testing.T) { + lr.Policies = nil + + err = ovnClient.UpdateLogicalRouter(lr, &lr.Policies) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Policies) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalRouter() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + name := "test-delete-lr" + + t.Run("no err when delete existent logical router", func(t *testing.T) { + t.Parallel() + err := ovnClient.CreateLogicalRouter(name) + require.NoError(t, err) + + err = ovnClient.DeleteLogicalRouter(name) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalRouter(name, false) + require.ErrorContains(t, err, "not found logical router") + }) + + t.Run("no err when delete non-existent logical router", func(t *testing.T) { + t.Parallel() + err := ovnClient.DeleteLogicalRouter("test-delete-lr-non-existent") + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testGetLogicalRouter() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + name := "test-get-lr" + + err := ovnClient.CreateLogicalRouter(name) + require.NoError(t, err) + + t.Run("should return no err when found logical router", func(t *testing.T) { + t.Parallel() + lr, err := ovnClient.GetLogicalRouter(name, false) + require.NoError(t, err) + require.Equal(t, name, lr.Name) + require.NotEmpty(t, lr.UUID) + }) + + t.Run("should return err when not found logical router", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.GetLogicalRouter("test-get-lr-non-existent", false) + require.ErrorContains(t, err, "not found logical router") + }) + + t.Run("no err when not found logical router and ignoreNotFound is true", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.GetLogicalRouter("test-get-lr-non-existent", true) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testListLogicalRouter() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + namePrefix := "test-list-lr" + + i := 0 + // create three logical router + for ; i < 3; i++ { + name := fmt.Sprintf("%s-%d", namePrefix, i) + err := ovnClient.CreateLogicalRouter(name) + require.NoError(t, err) + } + + // create two logical router which vendor is others + for ; i < 5; i++ { + name := fmt.Sprintf("%s-%d", namePrefix, i) + lr := &ovnnb.LogicalRouter{ + Name: name, + ExternalIDs: map[string]string{"vendor": "test-vendor"}, + } + + err := createLogicalRouter(ovnClient, lr) + require.NoError(t, err) + } + + // create two logical router without vendor + for ; i < 7; i++ { + name := fmt.Sprintf("%s-%d", namePrefix, i) + lr := &ovnnb.LogicalRouter{ + Name: name, + } + + err := createLogicalRouter(ovnClient, lr) + require.NoError(t, err) + } + + t.Run("return all logical router which vendor is kube-ovn", func(t *testing.T) { + t.Parallel() + lrs, err := ovnClient.ListLogicalRouter(true, nil) + require.NoError(t, err) + + count := 0 + for _, lr := range lrs { + if strings.Contains(lr.Name, namePrefix) { + count++ + } + } + require.Equal(t, count, 3) + }) + + t.Run("has custom filter", func(t *testing.T) { + t.Parallel() + lrs, err := ovnClient.ListLogicalRouter(false, func(lr *ovnnb.LogicalRouter) bool { + return len(lr.ExternalIDs) == 0 || lr.ExternalIDs["vendor"] != util.CniTypeName + }) + + require.NoError(t, err) + + count := 0 + for _, lr := range lrs { + if strings.Contains(lr.Name, namePrefix) { + count++ + } + } + require.Equal(t, count, 4) + }) +} + +func (suite *OvnClientTestSuite) testLogicalRouterUpdatePortOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-update-port-op-lr" + uuid := ovsclient.NamedUUID() + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("add new port to logical router", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalRouterUpdatePortOp(lrName, uuid, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: uuid, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del port from logical router", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalRouterUpdatePortOp(lrName, uuid, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: uuid, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should return err when logical router does not exist", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.LogicalRouterUpdatePortOp("test-update-port-op-lr-non-existent", uuid, ovsdb.MutateOperationInsert) + require.ErrorContains(t, err, "not found logical router") + }) +} + +func (suite *OvnClientTestSuite) testLogicalRouterUpdatePolicyOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-update-policy-op-lr" + uuid := ovsclient.NamedUUID() + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("add new policy to logical router", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalRouterUpdatePolicyOp(lrName, []string{uuid}, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "policies", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: uuid, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del policy from logical router", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalRouterUpdatePolicyOp(lrName, []string{uuid}, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "policies", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: uuid, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should return err when logical router does not exist", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.LogicalRouterUpdatePolicyOp("test-update-policy-op-lr-non-existent", []string{uuid}, ovsdb.MutateOperationInsert) + require.ErrorContains(t, err, "not found logical router") + }) +} + +func (suite *OvnClientTestSuite) testLogicalRouterUpdateNatOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-update-nat-op-lr" + uuid := ovsclient.NamedUUID() + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("add new nat to logical router", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalRouterUpdateNatOp(lrName, []string{uuid}, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "nat", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: uuid, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del nat from logical router", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalRouterUpdateNatOp(lrName, []string{uuid}, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "nat", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: uuid, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should return err when logical router does not exist", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.LogicalRouterUpdateNatOp("test-update-nat-op-lr-non-existent", []string{uuid}, ovsdb.MutateOperationInsert) + require.ErrorContains(t, err, "not found logical router") + }) +} + +func (suite *OvnClientTestSuite) testLogicalRouterUpdateStaticRouteOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-update-route-op-lr" + uuid := ovsclient.NamedUUID() + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("add new static route to logical router", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalRouterUpdateStaticRouteOp(lrName, []string{uuid}, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "static_routes", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: uuid, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del static route from logical router", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalRouterUpdateStaticRouteOp(lrName, []string{uuid}, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "static_routes", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: uuid, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should return err when logical router does not exist", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.LogicalRouterUpdateStaticRouteOp("test-update-route-op-lr-non-existent", []string{uuid}, ovsdb.MutateOperationInsert) + require.ErrorContains(t, err, "not found logical router") + }) +} + +func (suite *OvnClientTestSuite) testLogicalRouterOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-op-lr" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + lrpUUID := ovsclient.NamedUUID() + lrpMutation := func(lr *ovnnb.LogicalRouter) *model.Mutation { + mutation := &model.Mutation{ + Field: &lr.Ports, + Value: []string{lrpUUID}, + Mutator: ovsdb.MutateOperationInsert, + } + + return mutation + } + + policyUUID := ovsclient.NamedUUID() + policyMutation := func(lr *ovnnb.LogicalRouter) *model.Mutation { + mutation := &model.Mutation{ + Field: &lr.Policies, + Value: []string{policyUUID}, + Mutator: ovsdb.MutateOperationInsert, + } + + return mutation + } + + ops, err := ovnClient.LogicalRouterOp(lrName, lrpMutation, policyMutation) + require.NoError(t, err) + + require.Len(t, ops[0].Mutations, 2) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lrpUUID, + }, + }, + }, + }, + { + Column: "policies", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: policyUUID, + }, + }, + }, + }, + }, ops[0].Mutations) +} diff --git a/pkg/ovs/ovn-nb-logical_switch.go b/pkg/ovs/ovn-nb-logical_switch.go new file mode 100644 index 00000000000..926fce85df1 --- /dev/null +++ b/pkg/ovs/ovn-nb-logical_switch.go @@ -0,0 +1,338 @@ +package ovs + +import ( + "context" + "fmt" + "strings" + + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +// CreateLogicalSwitch create logical switch +func (c *ovnClient) CreateLogicalSwitch(lsName, lrName, cidrBlock, gateway string, needRouter, randomAllocateGW bool) error { + lspName := fmt.Sprintf("%s-%s", lsName, lrName) + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + + networks := util.GetIpAddrWithMask(gateway, cidrBlock) + + exist, err := c.LogicalSwitchExists(lsName) + if err != nil { + return err + } + + // only update logical router port networks when logical switch exist + if exist { + if randomAllocateGW { + return nil + } + + lrp := &ovnnb.LogicalRouterPort{ + Name: lrpName, + Networks: strings.Split(networks, ","), + } + if err := c.UpdateLogicalRouterPort(lrp, &lrp.Networks); err != nil { + return fmt.Errorf("update logical router port %s", lrpName) + } + } else { + if err := c.CreateBareLogicalSwitch(lsName); err != nil { + return fmt.Errorf("create logical switch %s", lsName) + } + } + + if needRouter { + if err := c.CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, networks, util.GenerateMac()); err != nil { + return err + } + } else { + if randomAllocateGW { + return nil + } + + if err := c.RemoveLogicalPatchPort(lspName, lrpName); err != nil { + return fmt.Errorf("remove router type port %s and %s: %v", lspName, lrpName, err) + } + } + + return nil +} + +// CreateBareLogicalSwitch create logical switch with basic configuration +func (c *ovnClient) CreateBareLogicalSwitch(lsName string) error { + exist, err := c.LogicalSwitchExists(lsName) + if err != nil { + return err + } + + // ingnore + if exist { + return nil + } + + ls := &ovnnb.LogicalSwitch{ + Name: lsName, + ExternalIDs: map[string]string{"vendor": util.CniTypeName}, + } + + op, err := c.ovnNbClient.Create(ls) + if err != nil { + return fmt.Errorf("generate operations for creating logical switch %s: %v", lsName, err) + } + + if err := c.Transact("ls-add", op); err != nil { + return fmt.Errorf("create logical switch %s: %v", lsName, err) + } + + return nil +} + +// LogicalSwitchAddPort add port to logical switch +func (c *ovnClient) LogicalSwitchAddPort(lsName, lspName string) error { + lsp, err := c.GetLogicalSwitchPort(lspName, false) + if err != nil { + return fmt.Errorf("get logical switch port %s when logical switch add port: %v", lspName, err) + } + + ops, err := c.LogicalSwitchUpdatePortOp(lsName, lsp.UUID, ovsdb.MutateOperationInsert) + if err != nil { + return fmt.Errorf("generate operations for logical switch %s add port %s: %v", lsName, lspName, err) + } + + if err := c.Transact("lsp-add", ops); err != nil { + return fmt.Errorf("add port %s to logical switch %s: %v", lspName, lsName, err) + } + + return nil +} + +// LogicalSwitchDelPort del port from logical switch +func (c *ovnClient) LogicalSwitchDelPort(lsName, lspName string) error { + lsp, err := c.GetLogicalSwitchPort(lspName, false) + if err != nil { + return fmt.Errorf("get logical switch port %s when logical switch del port: %v", lspName, err) + } + + ops, err := c.LogicalSwitchUpdatePortOp(lsName, lsp.UUID, ovsdb.MutateOperationDelete) + if err != nil { + return fmt.Errorf("generate operations for logical switch %s del port %s: %v", lsName, lspName, err) + } + + if err := c.Transact("lsp-del", ops); err != nil { + return fmt.Errorf("del port %s from logical switch %s: %v", lspName, lsName, err) + } + + return nil +} + +// LogicalSwitchUpdateLoadBalancers add several lb to or from logical switch once +func (c *ovnClient) LogicalSwitchUpdateLoadBalancers(lsName string, op ovsdb.Mutator, lbNames ...string) error { + if len(lbNames) == 0 { + return nil + } + + lbUUIDs := make([]string, 0, len(lbNames)) + + for _, lbName := range lbNames { + lb, err := c.GetLoadBalancer(lbName, true) + if err != nil { + return err + } + + // ignore non-existent object + if lb != nil { + lbUUIDs = append(lbUUIDs, lb.UUID) + } + } + + ops, err := c.LogicalSwitchUpdateLoadBalancerOp(lsName, lbUUIDs, op) + if err != nil { + return fmt.Errorf("generate operations for logical switch %s update lbs %v: %v", lsName, lbNames, err) + } + + if err := c.Transact("ls-lb-update", ops); err != nil { + return fmt.Errorf("logical switch %s update lbs %v: %v", lsName, lbNames, err) + + } + + return nil +} + +// DeleteLogicalSwitch delete logical switch +func (c *ovnClient) DeleteLogicalSwitch(lsName string) error { + op, err := c.DeleteLogicalSwitchOp(lsName) + if err != nil { + return err + } + + if err := c.Transact("ls-del", op); err != nil { + return fmt.Errorf("delete logical switch %s: %v", lsName, err) + } + + return nil +} + +// GetLogicalSwitch get logical switch by name, +// it is because of lack name index that does't use ovnNbClient.Get +func (c *ovnClient) GetLogicalSwitch(lsName string, ignoreNotFound bool) (*ovnnb.LogicalSwitch, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + lsList := make([]ovnnb.LogicalSwitch, 0) + if err := c.ovnNbClient.WhereCache(func(ls *ovnnb.LogicalSwitch) bool { + return ls.Name == lsName + }).List(ctx, &lsList); err != nil { + return nil, fmt.Errorf("list switch switch %q: %v", lsName, err) + } + + // not found + if len(lsList) == 0 { + if ignoreNotFound { + return nil, nil + } + return nil, fmt.Errorf("not found logical switch %q", lsName) + } + + if len(lsList) > 1 { + return nil, fmt.Errorf("more than one logical switch with same name %q", lsName) + } + + return &lsList[0], nil +} + +func (c *ovnClient) LogicalSwitchExists(lsName string) (bool, error) { + ls, err := c.GetLogicalSwitch(lsName, true) + return ls != nil, err +} + +// ListLogicalSwitch list logical switch +func (c *ovnClient) ListLogicalSwitch(needVendorFilter bool, filter func(lr *ovnnb.LogicalSwitch) bool) ([]ovnnb.LogicalSwitch, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + lsList := make([]ovnnb.LogicalSwitch, 0) + + if err := c.ovnNbClient.WhereCache(func(ls *ovnnb.LogicalSwitch) bool { + if needVendorFilter && (len(ls.ExternalIDs) == 0 || ls.ExternalIDs["vendor"] != util.CniTypeName) { + return false + } + + if filter != nil { + return filter(ls) + } + + return true + }).List(ctx, &lsList); err != nil { + return nil, fmt.Errorf("list logical switch: %v", err) + } + + return lsList, nil +} + +// LogicalSwitchUpdatePortOp create operations add port to or delete port from logical switch +func (c *ovnClient) LogicalSwitchUpdatePortOp(lsName string, lspUUID string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(lspUUID) == 0 { + return nil, nil + } + + mutation := func(ls *ovnnb.LogicalSwitch) *model.Mutation { + mutation := &model.Mutation{ + Field: &ls.Ports, + Value: []string{lspUUID}, + Mutator: op, + } + + return mutation + } + + return c.LogicalSwitchOp(lsName, mutation) +} + +// LogicalSwitchUpdateLoadBalancerOp create operations add lb to or delete lb from logical switch +func (c *ovnClient) LogicalSwitchUpdateLoadBalancerOp(lsName string, lbUUIDs []string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(lbUUIDs) == 0 { + return nil, nil + } + + mutation := func(ls *ovnnb.LogicalSwitch) *model.Mutation { + mutation := &model.Mutation{ + Field: &ls.LoadBalancer, + Value: lbUUIDs, + Mutator: op, + } + + return mutation + } + + return c.LogicalSwitchOp(lsName, mutation) +} + +// logicalSwitchUpdateAclOp create operations add acl to or delete acl from logical switch +func (c *ovnClient) logicalSwitchUpdateAclOp(lsName string, aclUUIDs []string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(aclUUIDs) == 0 { + return nil, nil + } + + mutation := func(ls *ovnnb.LogicalSwitch) *model.Mutation { + mutation := &model.Mutation{ + Field: &ls.ACLs, + Value: aclUUIDs, + Mutator: op, + } + + return mutation + } + + return c.LogicalSwitchOp(lsName, mutation) +} + +// LogicalSwitchOp create operations about logical switch +func (c *ovnClient) LogicalSwitchOp(lsName string, mutationsFunc ...func(ls *ovnnb.LogicalSwitch) *model.Mutation) ([]ovsdb.Operation, error) { + ls, err := c.GetLogicalSwitch(lsName, false) + if err != nil { + return nil, fmt.Errorf("get logical switch %s when generate mutate operations: %v", lsName, err) + } + + if len(mutationsFunc) == 0 { + return nil, nil + } + + mutations := make([]model.Mutation, 0, len(mutationsFunc)) + + for _, f := range mutationsFunc { + mutation := f(ls) + + if mutation != nil { + mutations = append(mutations, *mutation) + } + } + + ops, err := c.ovnNbClient.Where(ls).Mutate(ls, mutations...) + if err != nil { + return nil, fmt.Errorf("generate operations for mutating logical switch %s: %v", lsName, err) + } + + return ops, nil +} + +// DeleteLogicalSwitchOp create operations that delete logical switch +func (c *ovnClient) DeleteLogicalSwitchOp(lsName string) ([]ovsdb.Operation, error) { + ls, err := c.GetLogicalSwitch(lsName, true) + if err != nil { + return nil, fmt.Errorf("get logical switch %s: %v", lsName, err) + } + + // not found, skip + if ls == nil { + return nil, nil + } + + op, err := c.Where(ls).Delete() + if err != nil { + return nil, fmt.Errorf("generate operations for deleting logical switch %s: %v", lsName, err) + } + + return op, nil +} diff --git a/pkg/ovs/ovn-nb-logical_switch_port.go b/pkg/ovs/ovn-nb-logical_switch_port.go index 43a2550d137..fdabe9792b9 100644 --- a/pkg/ovs/ovn-nb-logical_switch_port.go +++ b/pkg/ovs/ovn-nb-logical_switch_port.go @@ -3,85 +3,650 @@ package ovs import ( "context" "fmt" + "strings" "github.com/ovn-org/libovsdb/client" - "k8s.io/klog/v2" + "github.com/ovn-org/libovsdb/ovsdb" + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" "github.com/kubeovn/kube-ovn/pkg/util" ) -func (c OvnClient) GetLogicalSwitchPort(name string, ignoreNotFound bool) (*ovnnb.LogicalSwitchPort, error) { +func (c *ovnClient) CreateLogicalSwitchPort(lsName, lspName, ip, mac, podName, namespace string, portSecurity bool, securityGroups string, vips string, enableDHCP bool, dhcpOptions *DHCPOptionsUUIDs, vpc string) error { + exist, err := c.LogicalSwitchPortExists(lspName) + if err != nil { + return err + } + + // ignore + if exist { + return nil + } + + /* normal lsp creation */ + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: make(map[string]string), + } + + ipList := strings.Split(ip, ",") + vipList := strings.Split(vips, ",") + addresses := make([]string, 0, len(ipList)+len(vipList)+1) // +1 is the mac length + addresses = append(addresses, mac) + addresses = append(addresses, ipList...) + + // addresses is the first element of addresses + lsp.Addresses = []string{strings.Join(addresses, " ")} + + if portSecurity { + if len(vips) != 0 { + addresses = append(addresses, vipList...) + } + // addresses is the first element of port_security + lsp.PortSecurity = []string{strings.Join(addresses, " ")} + + // set security groups + if len(securityGroups) != 0 { + lsp.ExternalIDs[sgsKey] = strings.ReplaceAll(securityGroups, ",", "/") + + sgList := strings.Split(securityGroups, ",") + for _, sg := range sgList { + lsp.ExternalIDs[associatedSgKeyPrefix+sg] = "true" + } + } + } + + // add lsp which does not belong to default vpc to default-securitygroup when default-securitygroup configMap exist + if vpc != "" && vpc != util.DefaultVpc && !strings.Contains(securityGroups, util.DefaultSecurityGroupName) { + lsp.ExternalIDs[associatedSgKeyPrefix+util.DefaultSecurityGroupName] = "false" + } + + // set vips info to external-ids + if len(vips) != 0 { + lsp.ExternalIDs["vips"] = vips + lsp.ExternalIDs["attach-vips"] = "true" + } + + // set pod info to external-ids + if len(podName) != 0 && len(namespace) != 0 { + lsp.ExternalIDs["pod"] = namespace + "/" + podName + } + + // set dhcp options + if enableDHCP && dhcpOptions != nil { + if len(dhcpOptions.DHCPv4OptionsUUID) != 0 { + lsp.Dhcpv4Options = &dhcpOptions.DHCPv4OptionsUUID + } + if len(dhcpOptions.DHCPv6OptionsUUID) != 0 { + lsp.Dhcpv6Options = &dhcpOptions.DHCPv6OptionsUUID + } + } + + ops, err := c.CreateLogicalSwitchPortOp(lsp, lsName) + if err != nil { + return fmt.Errorf("generate operations for creating logical switch port %s: %v", lspName, err) + } + + if err = c.Transact("lsp-add", ops); err != nil { + return fmt.Errorf("create logical switch port %s: %v", lspName, err) + } + + return nil +} + +// CreateLocalnetLogicalSwitchPort create localnet type logical switch port +func (c *ovnClient) CreateLocalnetLogicalSwitchPort(lsName, lspName, provider string, vlanID int) error { + exist, err := c.LogicalSwitchPortExists(lspName) + if err != nil { + return err + } + + // ignore + if exist { + return nil + } + + /* create logical switch port */ + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + Type: "localnet", + Addresses: []string{"unknown"}, + Options: map[string]string{ + "network_name": provider, + }, + } + + if vlanID > 0 && vlanID < 4096 { + lsp.Tag = &vlanID + } + + ops, err := c.CreateLogicalSwitchPortOp(lsp, lsName) + if err != nil { + return err + } + + if err = c.Transact("lsp-add", ops); err != nil { + return fmt.Errorf("create localnet logical switch port %s: %v", lspName, err) + } + + return nil +} + +// CreateVirtualLogicalSwitchPorts create several virtual type logical switch port once +func (c *ovnClient) CreateVirtualLogicalSwitchPorts(lsName string, ips ...string) error { + ops := make([]ovsdb.Operation, 0, len(ips)) + + for _, ip := range ips { + lspName := fmt.Sprintf("%s-vip-%s", lsName, ip) + + exist, err := c.LogicalSwitchPortExists(lspName) + if err != nil { + return err + } + + // ignore + if exist { + continue + } + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + Type: "virtual", + Options: map[string]string{ + "virtual-ip": ip, + }, + } + + op, err := c.CreateLogicalSwitchPortOp(lsp, lsName) + if err != nil { + return err + } + + ops = append(ops, op...) + } + + if err := c.Transact("lsp-add", ops); err != nil { + return fmt.Errorf("create virtual logical switch ports for logical switch %s: %v", lsName, err) + } + + return nil +} + +// CreateBareLogicalSwitchPort create logical switch port with basic configuration +func (c *ovnClient) CreateBareLogicalSwitchPort(lsName, lspName, ip, mac string) error { + exist, err := c.LogicalSwitchPortExists(lspName) + if err != nil { + return err + } + + // ignore + if exist { + return nil + } + + ipList := strings.Split(ip, ",") + addresses := make([]string, 0, len(ipList)+1) // +1 is the mac length + addresses = append(addresses, mac) + addresses = append(addresses, ipList...) + + /* create logical switch port */ + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + Addresses: []string{strings.Join(addresses, " ")}, // addresses is the first element of addresses + } + + ops, err := c.CreateLogicalSwitchPortOp(lsp, lsName) + if err != nil { + return err + } + + if err = c.Transact("lsp-add", ops); err != nil { + return fmt.Errorf("create logical switch port %s: %v", lspName, err) + } + + return nil +} + +// CreateVirtualLogicalSwitchPorts update several virtual type logical switch port virtual-parents once +func (c *ovnClient) SetLogicalSwitchPortVirtualParents(lsName, parents string, ips ...string) error { + ops := make([]ovsdb.Operation, 0, len(ips)) + for _, ip := range ips { + lspName := fmt.Sprintf("%s-vip-%s", lsName, ip) + + lsp, err := c.GetLogicalSwitchPort(lspName, true) + if err != nil { + return fmt.Errorf("get logical switch port %s: %v", lspName, err) + } + + lsp.Options["virtual-parents"] = parents + if len(parents) == 0 { + delete(lsp.Options, "virtual-parents") + } + + op, err := c.UpdateLogicalSwitchPortOp(lsp, &lsp.Options) + if err != nil { + return err + } + + ops = append(ops, op...) + } + + if err := c.Transact("lsp-update", ops); err != nil { + return fmt.Errorf("set logical switch port virtual-parents %v", err) + } + return nil +} + +// SetLogicalSwitchPortSecurity set logical switch port port_security +func (c *ovnClient) SetLogicalSwitchPortSecurity(portSecurity bool, lspName, mac, ips, vips string) error { + lsp, err := c.GetLogicalSwitchPort(lspName, false) + if err != nil { + return fmt.Errorf("get logical switch port %s: %v", lspName, err) + } + + lsp.PortSecurity = nil + if portSecurity { + ipList := strings.Split(ips, ",") + vipList := strings.Split(vips, ",") + addresses := make([]string, 0, len(ipList)+len(vipList)+1) // +1 is the mac length + + addresses = append(addresses, mac) + addresses = append(addresses, ipList...) + + // it's necessary to add vip to port_security + if vips != "" { + addresses = append(addresses, vipList...) + } + + // addresses is the first element of port_security + lsp.PortSecurity = []string{strings.Join(addresses, " ")} + } + + if vips != "" { + // be careful that don't overwrite origin ExternalIDs + if lsp.ExternalIDs == nil { + lsp.ExternalIDs = make(map[string]string) + } + lsp.ExternalIDs["vips"] = vips + lsp.ExternalIDs["attach-vips"] = "true" + } else { + delete(lsp.ExternalIDs, "vips") + delete(lsp.ExternalIDs, "attach-vips") + } + + if err := c.UpdateLogicalSwitchPort(lsp, &lsp.PortSecurity, &lsp.ExternalIDs); err != nil { + return fmt.Errorf("set logical switch port %s port_security %v: %v", lspName, lsp.PortSecurity, err) + } + + return nil +} + +// SetLogicalSwitchPortExternalIds set logical switch port external ids +func (c *ovnClient) SetLogicalSwitchPortExternalIds(lspName string, externalIds map[string]string) error { + lsp, err := c.GetLogicalSwitchPort(lspName, false) + if err != nil { + return fmt.Errorf("get logical switch port %s: %v", lspName, err) + } + + if lsp.ExternalIDs == nil { + lsp.ExternalIDs = make(map[string]string) + } + + for k, v := range externalIds { + lsp.ExternalIDs[k] = v + } + + if err := c.UpdateLogicalSwitchPort(lsp, &lsp.ExternalIDs); err != nil { + return fmt.Errorf("set logical switch port %s external ids %v: %v", lspName, externalIds, err) + } + + return nil +} + +// SetLogicalSwitchPortSecurityGroup set logical switch port security group, +// op is 'add' or 'remove' +func (c *ovnClient) SetLogicalSwitchPortSecurityGroup(lsp *ovnnb.LogicalSwitchPort, op string, sgs ...string) ([]string, error) { + if len(sgs) == 0 { + return nil, nil + } + + if op != "add" && op != "remove" { + return nil, fmt.Errorf("op must be 'add' or 'remove'") + } + + diffSgs := make([]string, 0, len(sgs)) + oldSgs := getLogicalSwitchPortSgs(lsp) + for _, sgName := range sgs { + associatedSgKey := associatedSgKeyPrefix + sgName + if op == "add" { + if _, ok := oldSgs[sgName]; ok { + continue // ignore existent + } + + lsp.ExternalIDs[associatedSgKey] = "true" + oldSgs[sgName] = struct{}{} + diffSgs = append(diffSgs, sgName) + } else { + if _, ok := oldSgs[sgName]; !ok { + continue // ignore non-existent + } + + lsp.ExternalIDs[associatedSgKey] = "false" + delete(oldSgs, sgName) + diffSgs = append(diffSgs, sgName) + } + } + + newSgs := "" + for sg := range oldSgs { + if len(newSgs) != 0 { + newSgs += "/" + sg + } else { + newSgs = sg + } + } + + lsp.ExternalIDs[sgsKey] = newSgs + if len(newSgs) == 0 { // when all sgs had been removed, delete sgsKey + delete(lsp.ExternalIDs, sgsKey) + } + + if err := c.UpdateLogicalSwitchPort(lsp, &lsp.ExternalIDs); err != nil { + return nil, fmt.Errorf("set logical switch port %s security group %v: %v", lsp.Name, newSgs, err) + } + return diffSgs, nil +} + +// SetLogicalSwitchPortsSecurityGroup set logical switch port security group, +// op is 'add' or 'remove' +func (c *ovnClient) SetLogicalSwitchPortsSecurityGroup(sgName string, op string) error { + if op != "add" && op != "remove" { + return fmt.Errorf("op must be 'add' or 'remove'") + } + + /* list sg port */ + associatedSgKey := associatedSgKeyPrefix + sgName + associated := "false" // list false associated sg port when add sg to port external_ids + if op == "remove" { // list true associated sg port when remove sg from port external_ids + associated = "true" + } + + externalIds := map[string]string{associatedSgKey: associated} + lsps, err := c.ListNormalLogicalSwitchPorts(true, externalIds) + if err != nil { + return fmt.Errorf("list logical switch ports with external_ids %v: %v", externalIds, err) + } + + /* add to or remove from sgs form port external_ids */ + for _, lsp := range lsps { + if _, err := c.SetLogicalSwitchPortSecurityGroup(&lsp, op, sgName); err != nil { + return fmt.Errorf("set logical switch port %s security group %s: %v", lsp.Name, sgName, err) + } + } + + return nil +} + +// EnablePortLayer2forward set logical switch port addresses as 'unknown' +func (c *ovnClient) EnablePortLayer2forward(lspName string) error { + lsp, err := c.GetLogicalSwitchPort(lspName, false) + if err != nil { + return fmt.Errorf("get logical switch port %s: %v", lspName, err) + } + + lsp.Addresses = []string{"unknown"} + + if err := c.UpdateLogicalSwitchPort(lsp, &lsp.Addresses); err != nil { + return fmt.Errorf("set logical switch port %s addressed=unknown: %v", lspName, err) + } + + return nil +} + +func (c *ovnClient) SetLogicalSwitchPortVlanTag(lspName string, vlanID int) error { + // valid vlan id is 0~4095 + if vlanID < 0 || vlanID > 4095 { + return fmt.Errorf("invalid vlan id %d", vlanID) + } + + lsp, err := c.GetLogicalSwitchPort(lspName, false) + if err != nil { + return fmt.Errorf("get logical switch port %s: %v", lspName, err) + } + + // no need update vlan id when vlan id is the same + if lsp.Tag != nil && *lsp.Tag == vlanID { + return nil + } + + lsp.Tag = &vlanID + if vlanID == 0 { + lsp.Tag = nil + } + + if err := c.UpdateLogicalSwitchPort(lsp, &lsp.Tag); err != nil { + return fmt.Errorf("set logical switch port %s tag %d: %v", lspName, vlanID, err) + } + + return nil +} + +// UpdateLogicalSwitchPort update logical switch port +func (c *ovnClient) UpdateLogicalSwitchPort(lsp *ovnnb.LogicalSwitchPort, fields ...interface{}) error { + if lsp == nil { + return fmt.Errorf("logical_switch_port is nil") + } + + op, err := c.Where(lsp).Update(lsp, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating logical switch port %s: %v", lsp.Name, err) + } + + if err = c.Transact("lsp-update", op); err != nil { + return fmt.Errorf("update logical switch port %s: %v", lsp.Name, err) + } + + return nil +} + +// DeleteLogicalSwitchPort delete logical switch port in ovn +func (c *ovnClient) DeleteLogicalSwitchPort(lspName string) error { + ops, err := c.DeleteLogicalSwitchPortOp(lspName) + if err != nil { + return err + } + + if err = c.Transact("lsp-del", ops); err != nil { + return fmt.Errorf("delete logical switch port %s", lspName) + } + + return nil +} + +// GetLogicalSwitchPort get logical switch port by name +func (c *ovnClient) GetLogicalSwitchPort(lspName string, ignoreNotFound bool) (*ovnnb.LogicalSwitchPort, error) { ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) defer cancel() - lsp := &ovnnb.LogicalSwitchPort{Name: name} - if err := c.ovnNbClient.Get(ctx, lsp); err != nil { + lsp := &ovnnb.LogicalSwitchPort{Name: lspName} + if err := c.Get(ctx, lsp); err != nil { if ignoreNotFound && err == client.ErrNotFound { return nil, nil } - return nil, fmt.Errorf("failed to get logical switch port %s: %v", name, err) + return nil, fmt.Errorf("get logical switch port %s: %v", lspName, err) } return lsp, nil } -func (c OvnClient) ListPodLogicalSwitchPorts(key string) ([]ovnnb.LogicalSwitchPort, error) { +// ListNormalLogicalSwitchPorts list logical switch ports which type is "" +func (c *ovnClient) ListNormalLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.LogicalSwitchPort, error) { + lsps, err := c.ListLogicalSwitchPorts(needVendorFilter, externalIDs, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "" + }) + if err != nil { + return nil, fmt.Errorf("list logical switch ports: %v", err) + } + + return lsps, nil +} + +// ListLogicalSwitchPorts list logical switch ports +func (c *ovnClient) ListLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string, filter func(lsp *ovnnb.LogicalSwitchPort) bool) ([]ovnnb.LogicalSwitchPort, error) { ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) defer cancel() - api, err := c.ovnNbClient.WherePredict(ctx, func(lsp *ovnnb.LogicalSwitchPort) bool { - return len(lsp.ExternalIDs) != 0 && lsp.ExternalIDs["pod"] == key - }) + lspList := make([]ovnnb.LogicalSwitchPort, 0) + + if err := c.WhereCache(logicalSwitchPortFilter(needVendorFilter, externalIDs, filter)).List(ctx, &lspList); err != nil { + return nil, fmt.Errorf("list logical switch ports: %v", err) + } + + return lspList, nil +} + +func (c *ovnClient) LogicalSwitchPortExists(name string) (bool, error) { + lsp, err := c.GetLogicalSwitchPort(name, true) + return lsp != nil, err +} + +// CreateLogicalSwitchPortOp create operations which create logical switch port +func (c *ovnClient) CreateLogicalSwitchPortOp(lsp *ovnnb.LogicalSwitchPort, lsName string) ([]ovsdb.Operation, error) { + if lsp == nil { + return nil, fmt.Errorf("logical_switch_port is nil") + } + + if lsp.ExternalIDs == nil { + lsp.ExternalIDs = make(map[string]string) + } + + // attach necessary info + lsp.ExternalIDs[logicalSwitchKey] = lsName + lsp.ExternalIDs["vendor"] = util.CniTypeName + + /* create logical switch port */ + lspCreateOp, err := c.Create(lsp) + if err != nil { + return nil, fmt.Errorf("generate operations for creating logical switch port %s: %v", lsp.Name, err) + } + + /* add logical switch port to logical switch*/ + lspAddOp, err := c.LogicalSwitchUpdatePortOp(lsName, lsp.UUID, ovsdb.MutateOperationInsert) if err != nil { return nil, err } - var lspList []ovnnb.LogicalSwitchPort - if err = api.List(context.TODO(), &lspList); err != nil { - return nil, fmt.Errorf("failed to list logical switch ports of Pod %s: %v", key, err) + ops := make([]ovsdb.Operation, 0, len(lspCreateOp)+len(lspAddOp)) + ops = append(ops, lspCreateOp...) + ops = append(ops, lspAddOp...) + + return ops, nil +} + +// DeleteLogicalSwitchPortOp create operations which delete logical switch port +func (c *ovnClient) DeleteLogicalSwitchPortOp(lspName string) ([]ovsdb.Operation, error) { + lsp, err := c.GetLogicalSwitchPort(lspName, true) + if err != nil { + return nil, fmt.Errorf("get logical switch port %s when generate delete operations: %v", lspName, err) + } + + // not found, skip + if lsp == nil { + return nil, nil } - return lspList, nil + lsName, ok := lsp.ExternalIDs[logicalSwitchKey] + if !ok { + return nil, fmt.Errorf("no %s exist in lsp's external_ids", logicalSwitchKey) + } + + // remove logical switch port from logical switch + lspRemoveOp, err := c.LogicalSwitchUpdatePortOp(lsName, lsp.UUID, ovsdb.MutateOperationDelete) + if err != nil { + return nil, fmt.Errorf("generate operations for removing port %s from logical switch %s: %v", lspName, lsName, err) + } + + // delete logical switch port + lspDelOp, err := c.Where(lsp).Delete() + if err != nil { + return nil, fmt.Errorf("generate operations for deleting logical switch port %s: %v", lspName, err) + } + + ops := make([]ovsdb.Operation, 0, len(lspRemoveOp)+len(lspDelOp)) + ops = append(ops, lspRemoveOp...) + ops = append(ops, lspDelOp...) + + return ops, nil } -func (c OvnClient) ListLogicalSwitchPorts(needVendorFilter bool, externalIDs map[string]string) ([]ovnnb.LogicalSwitchPort, error) { - ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) - defer cancel() +// UpdateLogicalSwitchPortOp create operations which update logical switch port +func (c *ovnClient) UpdateLogicalSwitchPortOp(lsp *ovnnb.LogicalSwitchPort, fields ...interface{}) ([]ovsdb.Operation, error) { + // not found, skip + if lsp == nil { + return nil, nil + } - api, err := c.ovnNbClient.WherePredict(ctx, func(lsp *ovnnb.LogicalSwitchPort) bool { - if lsp.Type != "" { - return false - } + op, err := c.Where(lsp).Update(lsp, fields...) + if err != nil { + return nil, fmt.Errorf("generate operations for updating logical switch port %s: %v", lsp.Name, err) + } + + return op, nil +} + +// logicalSwitchPortFilter filter logical_switch_port which match the given externalIDs and the custom filter +func logicalSwitchPortFilter(needVendorFilter bool, externalIDs map[string]string, filter func(lsp *ovnnb.LogicalSwitchPort) bool) func(lsp *ovnnb.LogicalSwitchPort) bool { + return func(lsp *ovnnb.LogicalSwitchPort) bool { if needVendorFilter && (len(lsp.ExternalIDs) == 0 || lsp.ExternalIDs["vendor"] != util.CniTypeName) { return false } + if len(lsp.ExternalIDs) < len(externalIDs) { return false } + if len(lsp.ExternalIDs) != 0 { for k, v := range externalIDs { - if lsp.ExternalIDs[k] != v { - return false + // if only key exist but not value in externalIDs, we should include this lsp, + // it's equal to shell command `ovn-nbctl --columns=xx find logical_switch_port external_ids:key!=\"\"` + if len(v) == 0 { + if len(lsp.ExternalIDs[k]) == 0 { + return false + } + } else { + if lsp.ExternalIDs[k] != v { + return false + } } } } + + if filter != nil { + return filter(lsp) + } + return true - }) - if err != nil { - return nil, err } +} - var lspList []ovnnb.LogicalSwitchPort - if err = api.List(context.TODO(), &lspList); err != nil { - klog.Errorf("failed to list logical switch ports: %v", err) - return nil, err +// getLogicalSwitchPortSgs get logical switch port security group +func getLogicalSwitchPortSgs(lsp *ovnnb.LogicalSwitchPort) map[string]struct{} { + if lsp == nil { + return nil } - return lspList, nil -} + sgs := make(map[string]struct{}) + for key, value := range lsp.ExternalIDs { + if strings.HasPrefix(key, associatedSgKeyPrefix) && value == "true" { + sgName := strings.ReplaceAll(key, associatedSgKeyPrefix, "") + sgs[sgName] = struct{}{} + } + } -func (c OvnClient) LogicalSwitchPortExists(name string) (bool, error) { - lsp, err := c.GetLogicalSwitchPort(name, true) - return lsp != nil, err + return sgs } diff --git a/pkg/ovs/ovn-nb-logical_switch_port_test.go b/pkg/ovs/ovn-nb-logical_switch_port_test.go new file mode 100644 index 00000000000..34889aea634 --- /dev/null +++ b/pkg/ovs/ovn-nb-logical_switch_port_test.go @@ -0,0 +1,1528 @@ +package ovs + +import ( + "fmt" + "strings" + "testing" + + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/stretchr/testify/require" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func createLogicalSwitchPort(c *ovnClient, lsp *ovnnb.LogicalSwitchPort) error { + if lsp == nil { + return fmt.Errorf("logical_switch_port is nil") + } + + op, err := c.Create(lsp) + if err != nil { + return fmt.Errorf("generate operations for creating logical switch port %s: %v", lsp.Name, err) + } + + return c.Transact("lsp-create", op) +} + +func (suite *OvnClientTestSuite) testCreateLogicalSwitchPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-create-port-ls" + ips := "10.244.0.37,fc00::af4:25" + mac := "00:00:00:AB:B4:65" + vips := "10.244.0.110,10.244.0.112" + podName := "test-vm-pod" + podNamespace := "test-ns" + dhcpOptions := &DHCPOptionsUUIDs{ + DHCPv4OptionsUUID: "73459f83-6189-4c57-837c-4102fa293332", + DHCPv6OptionsUUID: "d0201b01-1ef4-4eaf-9d96-8fe845e76c93", + } + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("create logical switch port", func(t *testing.T) { + lspName := "test-create-port-lsp" + sgs := "sg,sg1" + vpcName := "test-vpc" + + err = ovnClient.CreateLogicalSwitchPort(lsName, lspName, ips, mac, podName, podNamespace, true, sgs, vips, true, dhcpOptions, vpcName) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25"}, lsp.Addresses) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25 10.244.0.110 10.244.0.112"}, lsp.PortSecurity) + require.Equal(t, map[string]string{ + sgsKey: strings.ReplaceAll(sgs, ",", "/"), + "associated_sg_sg": "true", + "associated_sg_sg1": "true", + "associated_sg_" + util.DefaultSecurityGroupName: "false", + "vips": vips, + "attach-vips": "true", + "pod": fmt.Sprintf("%s/%s", podNamespace, podName), + "ls": lsName, + "vendor": util.CniTypeName, + }, lsp.ExternalIDs) + require.Equal(t, dhcpOptions.DHCPv4OptionsUUID, *lsp.Dhcpv4Options) + require.Equal(t, dhcpOptions.DHCPv6OptionsUUID, *lsp.Dhcpv6Options) + }) + + t.Run("create logical switch port without vips", func(t *testing.T) { + lspName := "test-create-port-lsp-no-vip" + sgs := "sg,sg1" + vpcName := "test-vpc" + + err = ovnClient.CreateLogicalSwitchPort(lsName, lspName, ips, mac, podName, podNamespace, true, sgs, "", true, dhcpOptions, vpcName) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25"}, lsp.Addresses) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25"}, lsp.PortSecurity) + require.Equal(t, map[string]string{ + sgsKey: strings.ReplaceAll(sgs, ",", "/"), + "associated_sg_sg": "true", + "associated_sg_sg1": "true", + "associated_sg_" + util.DefaultSecurityGroupName: "false", + "pod": fmt.Sprintf("%s/%s", podNamespace, podName), + "ls": lsName, + "vendor": util.CniTypeName, + }, lsp.ExternalIDs) + require.Equal(t, dhcpOptions.DHCPv4OptionsUUID, *lsp.Dhcpv4Options) + require.Equal(t, dhcpOptions.DHCPv6OptionsUUID, *lsp.Dhcpv6Options) + }) + + t.Run("create logical switch port with default-securitygroup", func(t *testing.T) { + lspName := "test-create-port-lsp-default-securitygroup" + sgs := "sg,sg1,default-securitygroup" + vpcName := "test-vpc" + + err = ovnClient.CreateLogicalSwitchPort(lsName, lspName, ips, mac, podName, podNamespace, true, sgs, vips, true, dhcpOptions, vpcName) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25"}, lsp.Addresses) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25 10.244.0.110 10.244.0.112"}, lsp.PortSecurity) + require.Equal(t, map[string]string{ + sgsKey: strings.ReplaceAll(sgs, ",", "/"), + "associated_sg_sg": "true", + "associated_sg_sg1": "true", + "associated_sg_" + util.DefaultSecurityGroupName: "true", + "pod": fmt.Sprintf("%s/%s", podNamespace, podName), + "ls": lsName, + "vendor": util.CniTypeName, + "vips": vips, + "attach-vips": "true", + }, lsp.ExternalIDs) + require.Equal(t, dhcpOptions.DHCPv4OptionsUUID, *lsp.Dhcpv4Options) + require.Equal(t, dhcpOptions.DHCPv6OptionsUUID, *lsp.Dhcpv6Options) + }) + + t.Run("create logical switch port with default vpc", func(t *testing.T) { + lspName := "test-create-port-lsp-default-vpc" + sgs := "sg,sg1" + vpcName := "ovn-cluster" + + err = ovnClient.CreateLogicalSwitchPort(lsName, lspName, ips, mac, podName, podNamespace, true, sgs, vips, true, dhcpOptions, vpcName) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25"}, lsp.Addresses) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25 10.244.0.110 10.244.0.112"}, lsp.PortSecurity) + require.Equal(t, map[string]string{ + sgsKey: strings.ReplaceAll(sgs, ",", "/"), + "associated_sg_sg": "true", + "associated_sg_sg1": "true", + "pod": fmt.Sprintf("%s/%s", podNamespace, podName), + "ls": lsName, + "vendor": util.CniTypeName, + "vips": vips, + "attach-vips": "true", + }, lsp.ExternalIDs) + require.Equal(t, dhcpOptions.DHCPv4OptionsUUID, *lsp.Dhcpv4Options) + require.Equal(t, dhcpOptions.DHCPv6OptionsUUID, *lsp.Dhcpv6Options) + }) + + t.Run("create logical switch port with portSecurity=false", func(t *testing.T) { + lspName := "test-create-port-lsp-no-portSecurity" + sgs := "sg,sg1" + vpcName := "test-vpc" + + err = ovnClient.CreateLogicalSwitchPort(lsName, lspName, ips, mac, podName, podNamespace, false, sgs, vips, true, dhcpOptions, vpcName) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25"}, lsp.Addresses) + require.Equal(t, map[string]string{ + "associated_sg_" + util.DefaultSecurityGroupName: "false", + "pod": fmt.Sprintf("%s/%s", podNamespace, podName), + "ls": lsName, + "vendor": util.CniTypeName, + "vips": vips, + "attach-vips": "true", + }, lsp.ExternalIDs) + require.Equal(t, dhcpOptions.DHCPv4OptionsUUID, *lsp.Dhcpv4Options) + require.Equal(t, dhcpOptions.DHCPv6OptionsUUID, *lsp.Dhcpv6Options) + }) + + t.Run("create logical switch port without dhcp options", func(t *testing.T) { + lspName := "test-create-port-lsp-no-dhcp-options" + sgs := "sg,sg1" + vpcName := "test-vpc" + + err = ovnClient.CreateLogicalSwitchPort(lsName, lspName, ips, mac, podName, podNamespace, true, sgs, vips, true, nil, vpcName) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25"}, lsp.Addresses) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25 10.244.0.110 10.244.0.112"}, lsp.PortSecurity) + require.Equal(t, map[string]string{ + sgsKey: strings.ReplaceAll(sgs, ",", "/"), + "associated_sg_sg": "true", + "associated_sg_sg1": "true", + "associated_sg_" + util.DefaultSecurityGroupName: "false", + "vips": vips, + "attach-vips": "true", + "pod": fmt.Sprintf("%s/%s", podNamespace, podName), + "ls": lsName, + "vendor": util.CniTypeName, + }, lsp.ExternalIDs) + require.Empty(t, lsp.Dhcpv4Options) + require.Empty(t, lsp.Dhcpv6Options) + }) +} + +func (suite *OvnClientTestSuite) testCreateLocalnetLogicalSwitchPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lspName := "test-create-localnet-port-lsp" + lsName := "test-create-localnet-port-port-ls" + provider := "external" + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("create localnet logical switch port with vlan id", func(t *testing.T) { + err = ovnClient.CreateLocalnetLogicalSwitchPort(lsName, lspName, provider, 200) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, lspName, lsp.Name) + require.Equal(t, "localnet", lsp.Type) + require.Equal(t, []string{"unknown"}, lsp.Addresses) + require.Equal(t, map[string]string{ + "network_name": provider, + }, lsp.Options) + + require.Equal(t, 200, *lsp.Tag) + }) + + t.Run("create localnet logical switch port without vlan id", func(t *testing.T) { + lspName := "test-create-localnet-port-lsp-no-vlan-id" + err = ovnClient.CreateLocalnetLogicalSwitchPort(lsName, lspName, provider, 0) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, lspName, lsp.Name) + require.Equal(t, "localnet", lsp.Type) + require.Equal(t, []string{"unknown"}, lsp.Addresses) + require.Equal(t, map[string]string{ + "network_name": provider, + }, lsp.Options) + require.Empty(t, lsp.Tag) + }) + + t.Run("should no err when create logical switch port repeatedly", func(t *testing.T) { + err = ovnClient.CreateLocalnetLogicalSwitchPort(lsName, lspName, "external", 0) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testCreateVirtualLogicalSwitchPorts() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-create-virtual-port-ls" + vips := []string{"192.168.33.10", "192.168.33.12"} + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + t.Run("create virtual logical switch port", func(t *testing.T) { + err = ovnClient.CreateVirtualLogicalSwitchPorts(lsName, vips...) + require.NoError(t, err) + for _, ip := range vips { + lspName := fmt.Sprintf("%s-vip-%s", lsName, ip) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, lspName, lsp.Name) + require.Equal(t, "virtual", lsp.Type) + require.Equal(t, map[string]string{ + "virtual-ip": ip, + }, lsp.Options) + } + }) + + t.Run("should no err when create logical switch port repeatedly", func(t *testing.T) { + err = ovnClient.CreateVirtualLogicalSwitchPorts(lsName, vips...) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testCreateBareLogicalSwitchPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-create-bare-port-ls" + lspName := "test-create-bare-port-lsp" + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitchPort(lsName, lspName, "100.64.0.4,fd00:100:64::4", "00:00:00:C9:4E:EE") + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:C9:4E:EE 100.64.0.4 fd00:100:64::4"}, lsp.Addresses) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + require.Contains(t, ls.Ports, lsp.UUID) +} + +func (suite *OvnClientTestSuite) testSetLogicalSwitchPortVirtualParents() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-update-port-virt-parents-ls" + ips := []string{"192.168.211.31", "192.168.211.32"} + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.CreateVirtualLogicalSwitchPorts(lsName, ips...) + require.NoError(t, err) + + t.Run("set virtual-parents option", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPortVirtualParents(lsName, "virt-parents-ls-1,virt-parents-ls-2", ips...) + require.NoError(t, err) + for _, ip := range ips { + lspName := fmt.Sprintf("%s-vip-%s", lsName, ip) + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, "virt-parents-ls-1,virt-parents-ls-2", lsp.Options["virtual-parents"]) + } + }) + + t.Run("clear virtual-parents option", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPortVirtualParents(lsName, "", ips...) + require.NoError(t, err) + for _, ip := range ips { + lspName := fmt.Sprintf("%s-vip-%s", lsName, ip) + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Empty(t, lsp.Options["virtual-parents"]) + } + }) +} + +func (suite *OvnClientTestSuite) testSetLogicalSwitchPortSecurity() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-update-port-security-ls" + lspName := "test-update-port-security-lsp" + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + logicalSwitchKey: lsName, + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + t.Run("update port_security and external_ids", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPortSecurity(true, lspName, "00:00:00:AB:B4:65", "10.244.0.37,fc00::af4:25", "10.244.100.10,10.244.100.11") + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25 10.244.100.10 10.244.100.11"}, lsp.PortSecurity) + require.Equal(t, map[string]string{ + "vendor": util.CniTypeName, + logicalSwitchKey: lsName, + "vips": "10.244.100.10,10.244.100.11", + "attach-vips": "true", + }, lsp.ExternalIDs) + }) + + t.Run("clear port_security and external_ids", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPortSecurity(false, lspName, "00:00:00:AB:B4:65", "10.244.0.37,fc00::af4:25", "") + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Empty(t, lsp.PortSecurity) + require.Equal(t, map[string]string{ + "vendor": util.CniTypeName, + logicalSwitchKey: lsName, + }, lsp.ExternalIDs) + }) + + t.Run("update port_security and external_ids when lsp.ExternalIDs is nil and vips is not nil", func(t *testing.T) { + lspName := "test-update-port-security-lsp-nil-eid" + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + err = ovnClient.SetLogicalSwitchPortSecurity(true, lspName, "00:00:00:AB:B4:65", "10.244.0.37,fc00::af4:25", "10.244.100.10,10.244.100.11") + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:00:00:AB:B4:65 10.244.0.37 fc00::af4:25 10.244.100.10 10.244.100.11"}, lsp.PortSecurity) + require.Equal(t, map[string]string{ + "vips": "10.244.100.10,10.244.100.11", + "attach-vips": "true", + }, lsp.ExternalIDs) + }) +} + +func (suite *OvnClientTestSuite) testSetSetLogicalSwitchPortExternalIds() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lspName := "test-set-port-ext-id-lsp" + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + err = ovnClient.SetLogicalSwitchPortExternalIds(lspName, map[string]string{"k1": "v1"}) + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, map[string]string{ + "k1": "v1", + "vendor": util.CniTypeName, + }, lsp.ExternalIDs) + + err = ovnClient.SetLogicalSwitchPortExternalIds(lspName, map[string]string{"k1": "v2"}) + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, map[string]string{ + "k1": "v2", + "vendor": util.CniTypeName, + }, lsp.ExternalIDs) +} + +func (suite *OvnClientTestSuite) testSetLogicalSwitchPortSecurityGroup() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lspNamePrefix := "test-set-sg-lsp" + + addOpExpect := func(lsp *ovnnb.LogicalSwitchPort, sgs []string) { + for _, sg := range sgs { + require.Equalf(t, "true", lsp.ExternalIDs[associatedSgKeyPrefix+sg], "%s should exist", sg) + } + + sgList := strings.Split(lsp.ExternalIDs[sgsKey], "/") + require.ElementsMatch(t, sgs, sgList) + } + + removeOpExpect := func(lsp *ovnnb.LogicalSwitchPort, sgs []string) { + for _, sg := range sgs { + require.Equalf(t, "false", lsp.ExternalIDs[associatedSgKeyPrefix+sg], "%s should't exist", sg) + } + + sgList := strings.Split(lsp.ExternalIDs[sgsKey], "/") + require.NotSubset(t, sgList, sgs) + } + + t.Run("add operation", func(t *testing.T) { + t.Parallel() + + lspNamePrefix := lspNamePrefix + "add" + op := "add" + + t.Run("new sgs is completely different old sgs", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-complete" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + sgsKey: "sg1", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg3") + require.NoError(t, err) + require.ElementsMatch(t, []string{"sg2", "sg3"}, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg2", "sg1", "sg3"}) + }) + + t.Run("old sg is subset of new sgs", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-old-subset" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + associatedSgKeyPrefix + "sg2": "true", + sgsKey: "sg1/sg2", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg3", "sg4", "sg1") + require.NoError(t, err) + require.ElementsMatch(t, []string{"sg4", "sg3"}, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg2", "sg1", "sg3", "sg4"}) + }) + + t.Run("new sg is subset of old sgs", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-new-subset" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + associatedSgKeyPrefix + "sg2": "true", + associatedSgKeyPrefix + "sg3": "true", + sgsKey: "sg1/sg2/sg3", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg1") + require.NoError(t, err) + require.Empty(t, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg2", "sg1", "sg3"}) + }) + + t.Run("new sgs is partially different old sgs", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-partial" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + associatedSgKeyPrefix + "sg2": "true", + associatedSgKeyPrefix + "sg3": "true", + sgsKey: "sg1/sg2/sg3", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg3", "sg4") + require.NoError(t, err) + require.ElementsMatch(t, []string{"sg4"}, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg2", "sg1", "sg3", "sg4"}) + }) + + t.Run("new sgs is empty", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-new-empty" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + associatedSgKeyPrefix + "sg2": "true", + associatedSgKeyPrefix + "sg3": "true", + sgsKey: "sg1/sg2/sg3", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op) + require.NoError(t, err) + require.Empty(t, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg2", "sg1", "sg3"}) + }) + + t.Run("old sgs is empty", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-old-empty" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{}, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg1") + require.NoError(t, err) + require.ElementsMatch(t, []string{"sg1", "sg2"}, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg2", "sg1"}) + }) + }) + + t.Run("remove operation", func(t *testing.T) { + t.Parallel() + + lspNamePrefix := lspNamePrefix + "remove" + op := "remove" + + t.Run("new sgs is completely different old sgs", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-complete" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + sgsKey: "sg1", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg3") + require.NoError(t, err) + require.Empty(t, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg1"}) + }) + + t.Run("old sg is subset of new sgs", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-old-subset" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + associatedSgKeyPrefix + "sg2": "true", + sgsKey: "sg1/sg2", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg3", "sg4", "sg1") + require.NoError(t, err) + require.ElementsMatch(t, []string{"sg1", "sg2"}, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + removeOpExpect(lsp, []string{"sg2", "sg1"}) + }) + + t.Run("new sg is subset of old sgs", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-new-subset" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + associatedSgKeyPrefix + "sg2": "true", + associatedSgKeyPrefix + "sg3": "true", + sgsKey: "sg1/sg2/sg3", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg1") + require.NoError(t, err) + require.ElementsMatch(t, []string{"sg2", "sg1"}, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg3"}) + removeOpExpect(lsp, []string{"sg2", "sg1"}) + }) + + t.Run("new sgs is partially different old sgs", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-partial" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + associatedSgKeyPrefix + "sg1": "true", + associatedSgKeyPrefix + "sg2": "true", + associatedSgKeyPrefix + "sg3": "true", + sgsKey: "sg1/sg2/sg3", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg3", "sg4") + require.NoError(t, err) + require.ElementsMatch(t, []string{"sg3", "sg2"}, diffSgs) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + addOpExpect(lsp, []string{"sg1"}) + removeOpExpect(lsp, []string{"sg2", "sg3"}) + }) + + t.Run("old sgs is empty", func(t *testing.T) { + t.Parallel() + + lspName := lspNamePrefix + "-old-empty" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{}, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + diffSgs, err := ovnClient.SetLogicalSwitchPortSecurityGroup(lsp, op, "sg2", "sg1") + require.NoError(t, err) + require.Empty(t, diffSgs) + }) + }) +} + +func (suite *OvnClientTestSuite) testSetLogicalSwitchPortsSecurityGroup() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lspNamePrefix := "test-set-sgs-lsp" + + for i := 0; i < 3; i++ { + lspName := fmt.Sprintf("%s-%d", lspNamePrefix, i) + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + associatedSgKeyPrefix + "sg1": "false", + associatedSgKeyPrefix + "sg2": "false", + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + } + + t.Run("add sg to lsp", func(t *testing.T) { + err := ovnClient.SetLogicalSwitchPortsSecurityGroup("sg2", "add") + require.NoError(t, err) + + for i := 0; i < 3; i++ { + lspName := fmt.Sprintf("%s-%d", lspNamePrefix, i) + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, "false", lsp.ExternalIDs[associatedSgKeyPrefix+"sg1"]) + require.Equal(t, "true", lsp.ExternalIDs[associatedSgKeyPrefix+"sg2"]) + + sgList := strings.Split(lsp.ExternalIDs[sgsKey], "/") + require.ElementsMatch(t, []string{"sg2"}, sgList) + } + }) + + t.Run("remove sg from lsp", func(t *testing.T) { + err := ovnClient.SetLogicalSwitchPortsSecurityGroup("sg2", "remove") + require.NoError(t, err) + + for i := 0; i < 3; i++ { + lspName := fmt.Sprintf("%s-%d", lspNamePrefix, i) + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, "false", lsp.ExternalIDs[associatedSgKeyPrefix+"sg1"]) + require.Equal(t, "false", lsp.ExternalIDs[associatedSgKeyPrefix+"sg2"]) + + require.Empty(t, lsp.ExternalIDs[sgsKey]) + } + }) + + t.Run("invalid op", func(t *testing.T) { + err := ovnClient.SetLogicalSwitchPortsSecurityGroup("sg2", "del") + require.ErrorContains(t, err, "op must be 'add' or 'remove'") + }) +} + +func (suite *OvnClientTestSuite) testEnablePortLayer2forward() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-enable-port-l2-ls" + lspName := "test-enable-port-l2-lsp" + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + logicalSwitchKey: lsName, + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + err = ovnClient.EnablePortLayer2forward(lspName) + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"unknown"}, lsp.Addresses) +} + +func (suite *OvnClientTestSuite) testSetLogicalSwitchPortVlanTag() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-set-port-vlan-tag-ls" + lspName := "test-set-port-vlan-tag-lsp" + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + logicalSwitchKey: lsName, + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + t.Run("set logical switch port tag when vlan id is 0", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPortVlanTag(lspName, 0) + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Nil(t, lsp.Tag) + }) + + t.Run("set logical switch port vlan id", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPortVlanTag(lspName, 10) + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, 10, *lsp.Tag) + + // no error when set the same vlan id + err = ovnClient.SetLogicalSwitchPortVlanTag(lspName, 10) + require.NoError(t, err) + }) + + t.Run("set logical switch port tag when vlan id is 0 again", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPortVlanTag(lspName, 0) + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Nil(t, lsp.Tag) + }) + + t.Run("invalid vlan id", func(t *testing.T) { + err = ovnClient.SetLogicalSwitchPortVlanTag(lspName, -1) + require.ErrorContains(t, err, "invalid vlan id") + + err = ovnClient.SetLogicalSwitchPortVlanTag(lspName, 4096) + require.ErrorContains(t, err, "invalid vlan id") + }) +} + +func (suite *OvnClientTestSuite) testUpdateLogicalSwitchPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lspName := "test-update-lsp" + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: map[string]string{"vendor": util.CniTypeName}, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + t.Run("normal update", func(t *testing.T) { + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + Addresses: []string{"00:0c:29:e4:16:cc 192.168.231.110"}, + ExternalIDs: map[string]string{ + "liveMigration": "0", + }, + Options: map[string]string{ + "virtual-parents": "test-virtual-parents", + }, + } + err = ovnClient.UpdateLogicalSwitchPort(lsp) + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Equal(t, []string{"00:0c:29:e4:16:cc 192.168.231.110"}, lsp.Addresses) + require.Equal(t, map[string]string{ + "liveMigration": "0", + }, lsp.ExternalIDs) + require.Equal(t, map[string]string{ + "virtual-parents": "test-virtual-parents", + }, lsp.Options) + }) + + t.Run("clear addresses", func(t *testing.T) { + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + } + err = ovnClient.UpdateLogicalSwitchPort(lsp, &lsp.Addresses, &lsp.Options) + require.NoError(t, err) + + lsp, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Empty(t, lsp.Addresses) + require.Empty(t, lsp.Options) + require.Equal(t, map[string]string{ + "liveMigration": "0", + }, lsp.ExternalIDs) + }) +} + +func (suite *OvnClientTestSuite) testListLogicalSwitchPorts() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + + lsName := "test-list-lsp-ls" + + t.Run("normal lsp", func(t *testing.T) { + t.Parallel() + + // normal lsp + lspName := "test-list-normal-lsp" + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "vendor": util.CniTypeName, + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + out, err := ovnClient.ListLogicalSwitchPorts(true, map[string]string{logicalSwitchKey: lsName}, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "" + }) + require.NoError(t, err) + require.Len(t, out, 1) + require.Equal(t, lspName, out[0].Name) + }) + + t.Run("patch lsp", func(t *testing.T) { + t.Parallel() + + // patch lsp + lspName := "test-list-patch-lsp" + lrpName := "test-list-patch-lsp-lrp" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "vendor": util.CniTypeName, + }, + Type: "router", + Options: map[string]string{ + "router-port": lrpName, + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + out, err := ovnClient.ListLogicalSwitchPorts(true, map[string]string{logicalSwitchKey: lsName}, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "router" && len(lsp.Options) != 0 && lsp.Options["router-port"] == lrpName + }) + require.NoError(t, err) + require.Len(t, out, 1) + require.Equal(t, lspName, out[0].Name) + }) + + t.Run("remote lsp", func(t *testing.T) { + t.Parallel() + + // remote lsp + lspName := "test-list-remote-lsp" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "vendor": util.CniTypeName, + }, + Type: "remote", + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + out, err := ovnClient.ListLogicalSwitchPorts(true, map[string]string{logicalSwitchKey: lsName}, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "remote" + }) + require.NoError(t, err) + require.Len(t, out, 1) + require.Equal(t, lspName, out[0].Name) + }) + + t.Run("virtual lsp", func(t *testing.T) { + t.Parallel() + + // virtual lsp + lspName := "test-list-virtual-lsp" + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "vendor": util.CniTypeName, + }, + Type: "virtual", + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + + out, err := ovnClient.ListLogicalSwitchPorts(true, map[string]string{logicalSwitchKey: lsName}, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "virtual" + }) + require.NoError(t, err) + require.Len(t, out, 1) + require.Equal(t, lspName, out[0].Name) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalSwitchPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lspName := "test-delete-port-lsp" + lsName := "test-delete-port-ls" + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitchPort(lsName, lspName, "", "") + require.NoError(t, err) + + t.Run("no err when delete existent logical switch port", func(t *testing.T) { + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Contains(t, ls.Ports, lsp.UUID) + + err = ovnClient.DeleteLogicalSwitchPort(lspName) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.ErrorContains(t, err, "object not found") + + ls, err = ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.NotContains(t, ls.Ports, lsp.UUID) + }) + + t.Run("no err when delete non-existent logical switch port", func(t *testing.T) { + err := ovnClient.DeleteLogicalSwitchPort("test-delete-lrp-non-existent") + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testCreateLogicalSwitchPortOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lspName := "test-create-op-lsp" + lsName := "test-create-op-ls" + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("merge ExternalIDs when exist ExternalIDs", func(t *testing.T) { + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: map[string]string{ + "pod": lspName, + }, + } + + ops, err := ovnClient.CreateLogicalSwitchPortOp(lsp, lsName) + require.NoError(t, err) + require.Len(t, ops, 2) + + require.Equal(t, ovsdb.OvsMap{ + GoMap: map[interface{}]interface{}{ + logicalSwitchKey: lsName, + "pod": lspName, + "vendor": "kube-ovn", + }, + }, ops[0].Row["external_ids"]) + + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lsp.UUID, + }, + }, + }, + }, + }, ops[1].Mutations) + }) + + t.Run("attach ExternalIDs when does't exist ExternalIDs", func(t *testing.T) { + lspName := "test-create-op-lsp-none-ext-id" + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + } + + ops, err := ovnClient.CreateLogicalSwitchPortOp(lsp, lsName) + require.NoError(t, err) + require.Len(t, ops, 2) + + require.Equal(t, ovsdb.OvsMap{ + GoMap: map[interface{}]interface{}{ + logicalSwitchKey: lsName, + "vendor": "kube-ovn", + }, + }, ops[0].Row["external_ids"]) + + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lsp.UUID, + }, + }, + }, + }, + }, ops[1].Mutations) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalSwitchPortOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lspName := "test-del-op-lsp" + lsName := "test-del-op-ls" + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitchPort(lsName, lspName, "", "") + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + + ops, err := ovnClient.DeleteLogicalSwitchPortOp(lspName) + require.NoError(t, err) + require.Len(t, ops, 2) + + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lsp.UUID, + }, + }, + }, + }, + }, ops[0].Mutations) + + require.Equal(t, + ovsdb.Operation{ + Op: "delete", + Table: "Logical_Switch_Port", + Where: []ovsdb.Condition{ + { + Column: "_uuid", + Function: "==", + Value: ovsdb.UUID{ + GoUUID: lsp.UUID, + }, + }, + }, + }, ops[1]) +} + +func (suite *OvnClientTestSuite) testlogicalSwitchPortFilter() { + t := suite.T() + t.Parallel() + + lsName := "test-filter-lsp-lr" + prefix := "test-filter-lsp" + lsps := make([]*ovnnb.LogicalSwitchPort, 0) + var patchPort string + + i := 0 + // create three normal lsp + for ; i < 3; i++ { + lspName := fmt.Sprintf("%s-%d", prefix, i) + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "vendor": util.CniTypeName, + }, + } + + lsps = append(lsps, lsp) + } + + // create one patch lsp + for ; i < 4; i++ { + lspName := fmt.Sprintf("%s-%d", prefix, i) + patchPort = fmt.Sprintf("%s-lrp", lspName) + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "vendor": util.CniTypeName, + }, + Type: "router", + Options: map[string]string{ + "router-port": patchPort, + }, + } + + lsps = append(lsps, lsp) + } + + // create one remote lsp + for ; i < 5; i++ { + lspName := fmt.Sprintf("%s-%d", prefix, i) + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "vendor": util.CniTypeName, + }, + Type: "remote", + } + + lsps = append(lsps, lsp) + } + + // create one virtual lsp + for ; i < 6; i++ { + lspName := fmt.Sprintf("%s-%d", prefix, i) + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName, + "vendor": util.CniTypeName, + }, + Type: "virtual", + } + + lsps = append(lsps, lsp) + } + + // create two normal lsp with different logical switch name and vendor + for ; i < 8; i++ { + lspName := fmt.Sprintf("%s-%d", prefix, i) + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName + "-test", + "vendor": util.CniTypeName + "-test", + }, + } + + lsps = append(lsps, lsp) + } + + // create one normal lsp with different logical switch name and no vendor + for ; i < 9; i++ { + lspName := fmt.Sprintf("%s-%d", prefix, i) + lsp := &ovnnb.LogicalSwitchPort{ + Name: lspName, + ExternalIDs: map[string]string{ + logicalSwitchKey: lsName + "-test", + }, + } + + lsps = append(lsps, lsp) + } + + t.Run("include all lsp", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(false, nil, nil) + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 9) + }) + + t.Run("include all lsp which vendor is kube-ovn", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(true, nil, nil) + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 6) + }) + + t.Run("include all lsp with external ids", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(true, map[string]string{logicalSwitchKey: lsName}, nil) + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 6) + }) + + t.Run("list normal type lsp", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(true, map[string]string{logicalSwitchKey: lsName}, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "" + }) + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 3) + }) + + t.Run("list remote type lsp", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(true, map[string]string{logicalSwitchKey: lsName}, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "remote" + }) + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 1) + }) + + t.Run("list virtual type lsp", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(true, map[string]string{logicalSwitchKey: lsName}, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "virtual" + }) + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 1) + }) + + t.Run("list patch type lsp", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(true, map[string]string{logicalSwitchKey: lsName}, func(lsp *ovnnb.LogicalSwitchPort) bool { + return lsp.Type == "router" && len(lsp.Options) != 0 && lsp.Options["router-port"] == patchPort + }) + + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 1) + }) + + t.Run("externalIDs's length is not equal", func(t *testing.T) { + t.Parallel() + + filterFunc := logicalSwitchPortFilter(true, map[string]string{ + logicalSwitchKey: lsName, + "key": "value", + }, nil) + + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Empty(t, count) + }) + + t.Run("list lsp without vendor", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(false, nil, func(lsp *ovnnb.LogicalSwitchPort) bool { + return len(lsp.ExternalIDs) == 0 || len(lsp.ExternalIDs["vendor"]) == 0 + }) + + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 1) + }) + + t.Run("list lsp which vendor is not kube-ovn", func(t *testing.T) { + filterFunc := logicalSwitchPortFilter(false, nil, func(lsp *ovnnb.LogicalSwitchPort) bool { + return len(lsp.ExternalIDs) == 0 || lsp.ExternalIDs["vendor"] != util.CniTypeName + }) + + count := 0 + for _, lsp := range lsps { + if filterFunc(lsp) { + count++ + } + } + require.Equal(t, count, 3) + }) +} + +func (suite *OvnClientTestSuite) testgetLogicalSwitchPortSgs() { + t := suite.T() + t.Parallel() + + t.Run("has associated security group", func(t *testing.T) { + t.Parallel() + lsp := &ovnnb.LogicalSwitchPort{ + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + "associated_sg_sg1": "true", + "associated_sg_sg2": "true", + }, + } + + sgs := getLogicalSwitchPortSgs(lsp) + require.Equal(t, map[string]struct{}{ + "sg1": {}, + "sg2": {}, + }, sgs) + }) + + t.Run("has no associated security group", func(t *testing.T) { + t.Parallel() + lsp := &ovnnb.LogicalSwitchPort{ + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + }, + } + + sgs := getLogicalSwitchPortSgs(lsp) + require.Empty(t, sgs) + }) + + t.Run("has no external ids", func(t *testing.T) { + t.Parallel() + lsp := &ovnnb.LogicalSwitchPort{} + + sgs := getLogicalSwitchPortSgs(lsp) + require.Empty(t, sgs) + }) +} diff --git a/pkg/ovs/ovn-nb-logical_switch_test.go b/pkg/ovs/ovn-nb-logical_switch_test.go new file mode 100644 index 00000000000..1ccec2ef294 --- /dev/null +++ b/pkg/ovs/ovn-nb-logical_switch_test.go @@ -0,0 +1,602 @@ +package ovs + +import ( + "fmt" + "strings" + "testing" + + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/stretchr/testify/require" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func createLogicalSwitch(c *ovnClient, ls *ovnnb.LogicalSwitch) error { + op, err := c.Create(ls) + if err != nil { + return err + } + + if err := c.Transact("ls-add", op); err != nil { + return err + } + + return nil +} + +func (suite *OvnClientTestSuite) testCreateLogicalSwitch() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-create-ls-ls" + lrName := "test-create-ls-lr" + lspName := fmt.Sprintf("%s-%s", lsName, lrName) + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("create logical switch and router type port when logical switch does't exist and needRouter is true", func(t *testing.T) { + err = ovnClient.CreateLogicalSwitch(lsName, lrName, "192.168.2.0/24,fd00::c0a8:6400/120", "192.168.2.1,fd00::c0a8:6401", true, false) + require.NoError(t, err) + + _, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + }) + + t.Run("only update networks when logical switch exist and router type port exist and needRouter is true", func(t *testing.T) { + err = ovnClient.CreateLogicalSwitch(lsName, lrName, "192.168.2.0/24,fd00::c0a8:9900/120", "192.168.2.1,fd00::c0a8:9901", true, false) + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Equal(t, []string{"192.168.2.1/24", "fd00::c0a8:9901/120"}, lrp.Networks) + }) + + t.Run("remove router type port when needRouter is false", func(t *testing.T) { + err = ovnClient.CreateLogicalSwitch(lsName, lrName, "192.168.2.0/24,fd00::c0a8:9900/120", "192.168.2.1,fd00::c0a8:9901", false, false) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.ErrorContains(t, err, "object not found") + + _, err = ovnClient.GetLogicalRouterPort(lrpName, false) + require.ErrorContains(t, err, "object not found") + }) + + t.Run("should no err when router type port doest't exist", func(t *testing.T) { + err = ovnClient.CreateLogicalSwitch(lsName+"-1", lrName+"-1", "192.168.2.0/24,fd00::c0a8:9900/120", "192.168.2.1,fd00::c0a8:9901", false, false) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testLogicalSwitchAddPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-add-port-ls" + lspName := "test-add-port-lsp" + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitchPort(lsName, lspName, "", "") + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + + t.Run("add port to logical switch", func(t *testing.T) { + err = ovnClient.LogicalSwitchAddPort(lsName, lspName) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Contains(t, ls.Ports, lsp.UUID) + }) + + t.Run("add port to logical switch repeatedly", func(t *testing.T) { + err = ovnClient.LogicalSwitchAddPort(lsName, lspName) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Contains(t, ls.Ports, lsp.UUID) + }) +} + +func (suite *OvnClientTestSuite) testLogicalSwitchDelPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-del-port-ls" + lspName := "test-del-port-lsp" + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitchPort(lsName, lspName, "", "") + require.NoError(t, err) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + + err = ovnClient.LogicalSwitchAddPort(lsName, lspName) + require.NoError(t, err) + + t.Run("del port from logical switch", func(t *testing.T) { + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.Contains(t, ls.Ports, lsp.UUID) + + err = ovnClient.LogicalSwitchDelPort(lsName, lspName) + require.NoError(t, err) + + ls, err = ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.NotContains(t, ls.Ports, lsp.UUID) + }) + + t.Run("del port from logical switch repeatedly", func(t *testing.T) { + err = ovnClient.LogicalSwitchDelPort(lsName, lspName) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + require.NotContains(t, ls.Ports, lsp.UUID) + }) +} + +func (suite *OvnClientTestSuite) testLogicalSwitchUpdateLoadBalancers() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-add-lb-to-ls" + prefix := "test-add-lb" + lbNames := make([]string, 0, 3) + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + for i := 1; i <= 3; i++ { + lbName := fmt.Sprintf("%s-%d", prefix, i) + lbNames = append(lbNames, lbName) + err := ovnClient.CreateLoadBalancer(lbName, "tcp", "") + require.NoError(t, err) + } + + t.Run("add lbs to logical switch", func(t *testing.T) { + err = ovnClient.LogicalSwitchUpdateLoadBalancers(lsName, ovsdb.MutateOperationInsert, lbNames...) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + for _, lbName := range lbNames { + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + require.Contains(t, ls.LoadBalancer, lb.UUID) + } + }) + + t.Run("should no err when add non-existent lbs to logical switch", func(t *testing.T) { + // add a non-existent lb + err = ovnClient.LogicalSwitchUpdateLoadBalancers(lsName, ovsdb.MutateOperationInsert, "test-add-lb-non-existent") + require.NoError(t, err) + }) + + t.Run("del lbs from logical switch", func(t *testing.T) { + // delete the first two lbs from logical switch + err = ovnClient.LogicalSwitchUpdateLoadBalancers(lsName, ovsdb.MutateOperationDelete, lbNames[0:2]...) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + for i, lbName := range lbNames { + lb, err := ovnClient.GetLoadBalancer(lbName, false) + require.NoError(t, err) + + // logical switch contains the last lb + if i == 2 { + require.Contains(t, ls.LoadBalancer, lb.UUID) + continue + } + require.NotContains(t, ls.LoadBalancer, lb.UUID) + } + }) + + t.Run("del non-existent lbs from logical switch", func(t *testing.T) { + err = ovnClient.LogicalSwitchUpdateLoadBalancers(lsName, ovsdb.MutateOperationDelete, []string{"test-del-lb-non-existent", "test-del-lb-non-existent-1"}...) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalSwitch() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + name := "test-delete-ls" + + t.Run("no err when delete existent logical switch", func(t *testing.T) { + t.Parallel() + err := ovnClient.CreateBareLogicalSwitch(name) + require.NoError(t, err) + + err = ovnClient.DeleteLogicalSwitch(name) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalSwitch(name, false) + require.ErrorContains(t, err, "not found logical switch") + }) + + t.Run("no err when delete non-existent logical switch", func(t *testing.T) { + t.Parallel() + err := ovnClient.DeleteLogicalSwitch("test-delete-ls-non-existent") + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testGetLogicalSwitch() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + name := "test-get-ls" + + err := ovnClient.CreateBareLogicalSwitch(name) + require.NoError(t, err) + + t.Run("should return no err when found logical switch", func(t *testing.T) { + lr, err := ovnClient.GetLogicalSwitch(name, false) + require.NoError(t, err) + require.Equal(t, name, lr.Name) + require.NotEmpty(t, lr.UUID) + }) + + t.Run("should return err when not found logical switch", func(t *testing.T) { + _, err := ovnClient.GetLogicalSwitch("test-get-lr-non-existent", false) + require.ErrorContains(t, err, "not found logical switch") + }) + + t.Run("no err when not found logical switch and ignoreNotFound is true", func(t *testing.T) { + _, err := ovnClient.GetLogicalSwitch("test-get-lr-non-existent", true) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testListLogicalSwitch() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + namePrefix := "test-list-ls" + + i := 0 + // create three logical switch + for ; i < 3; i++ { + name := fmt.Sprintf("%s-%d", namePrefix, i) + err := ovnClient.CreateBareLogicalSwitch(name) + require.NoError(t, err) + } + + // create two logical switch which vendor is others + for ; i < 5; i++ { + name := fmt.Sprintf("%s-%d", namePrefix, i) + ls := &ovnnb.LogicalSwitch{ + Name: name, + ExternalIDs: map[string]string{"vendor": "test-vendor"}, + } + + err := createLogicalSwitch(ovnClient, ls) + require.NoError(t, err) + } + + // create two logical switch without vendor + for ; i < 7; i++ { + name := fmt.Sprintf("%s-%d", namePrefix, i) + ls := &ovnnb.LogicalSwitch{ + Name: name, + } + + err := createLogicalSwitch(ovnClient, ls) + require.NoError(t, err) + } + + t.Run("return all logical switch which match vendor", func(t *testing.T) { + t.Parallel() + lss, err := ovnClient.ListLogicalSwitch(true, nil) + require.NoError(t, err) + require.NotEmpty(t, lss) + + count := 0 + for _, ls := range lss { + if strings.Contains(ls.Name, namePrefix) { + count++ + } + } + require.Equal(t, count, 3) + }) + + t.Run("has custom filter", func(t *testing.T) { + t.Parallel() + lss, err := ovnClient.ListLogicalSwitch(false, func(ls *ovnnb.LogicalSwitch) bool { + return len(ls.ExternalIDs) == 0 || ls.ExternalIDs["vendor"] != util.CniTypeName + }) + + require.NoError(t, err) + + count := 0 + for _, ls := range lss { + if strings.Contains(ls.Name, namePrefix) { + count++ + } + } + require.Equal(t, count, 4) + }) +} + +func (suite *OvnClientTestSuite) testLogicalSwitchUpdatePortOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-update-port-op-ls" + lspUUID := ovsclient.NamedUUID() + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("add new port to logical switch", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalSwitchUpdatePortOp(lsName, lspUUID, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lspUUID, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del port from logical switch", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalSwitchUpdatePortOp(lsName, lspUUID, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lspUUID, + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should return err when logical switch does not exist", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.LogicalSwitchUpdatePortOp("test-update-port-op-ls-non-existent", lspUUID, ovsdb.MutateOperationInsert) + require.ErrorContains(t, err, "not found logical switch") + }) +} + +func (suite *OvnClientTestSuite) testLogicalSwitchUpdateLoadBalancerOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-update-lb-ls" + lbUUIDs := []string{ovsclient.NamedUUID(), ovsclient.NamedUUID(), ovsclient.NamedUUID()} + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("add new lb to logical switch", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalSwitchUpdateLoadBalancerOp(lsName, lbUUIDs, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "load_balancer", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lbUUIDs[0], + }, + ovsdb.UUID{ + GoUUID: lbUUIDs[1], + }, + ovsdb.UUID{ + GoUUID: lbUUIDs[2], + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del port from logical switch", func(t *testing.T) { + t.Parallel() + ops, err := ovnClient.LogicalSwitchUpdateLoadBalancerOp(lsName, lbUUIDs, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "load_balancer", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lbUUIDs[0], + }, + ovsdb.UUID{ + GoUUID: lbUUIDs[1], + }, + ovsdb.UUID{ + GoUUID: lbUUIDs[2], + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should no err when lbUUIDs is empty", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.LogicalSwitchUpdateLoadBalancerOp("test-port-op-ls-non-existent", nil, ovsdb.MutateOperationInsert) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) test_logicalSwitchUpdateAclOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-update-acl-op-ls" + aclUUIDs := []string{ovsclient.NamedUUID(), ovsclient.NamedUUID()} + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("add new acl to logical switch ", func(t *testing.T) { + t.Parallel() + + ops, err := ovnClient.logicalSwitchUpdateAclOp(lsName, aclUUIDs, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "acls", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: aclUUIDs[0], + }, + ovsdb.UUID{ + GoUUID: aclUUIDs[1], + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del acl from logical switch", func(t *testing.T) { + t.Parallel() + + ops, err := ovnClient.logicalSwitchUpdateAclOp(lsName, aclUUIDs, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "acls", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: aclUUIDs[0], + }, + ovsdb.UUID{ + GoUUID: aclUUIDs[1], + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should return err when logical switch does not exist", func(t *testing.T) { + t.Parallel() + + _, err := ovnClient.logicalSwitchUpdateAclOp("test-acl-op-ls-non-existent", aclUUIDs, ovsdb.MutateOperationInsert) + require.ErrorContains(t, err, "not found logical switch") + }) +} + +func (suite *OvnClientTestSuite) testLogicalSwitchOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-op-ls" + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + lspUUID := ovsclient.NamedUUID() + lspMutation := func(ls *ovnnb.LogicalSwitch) *model.Mutation { + mutation := &model.Mutation{ + Field: &ls.Ports, + Value: []string{lspUUID}, + Mutator: ovsdb.MutateOperationInsert, + } + + return mutation + } + + lbUUID := ovsclient.NamedUUID() + lbMutation := func(ls *ovnnb.LogicalSwitch) *model.Mutation { + mutation := &model.Mutation{ + Field: &ls.LoadBalancer, + Value: []string{lbUUID}, + Mutator: ovsdb.MutateOperationInsert, + } + + return mutation + } + + ops, err := ovnClient.LogicalSwitchOp(lsName, lspMutation, lbMutation) + require.NoError(t, err) + + require.Len(t, ops[0].Mutations, 2) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lspUUID, + }, + }, + }, + }, + { + Column: "load_balancer", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lbUUID, + }, + }, + }, + }, + }, ops[0].Mutations) +} diff --git a/pkg/ovs/ovn-nb-nat.go b/pkg/ovs/ovn-nb-nat.go new file mode 100644 index 00000000000..594924062de --- /dev/null +++ b/pkg/ovs/ovn-nb-nat.go @@ -0,0 +1,369 @@ +package ovs + +import ( + "context" + "fmt" + + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +func (c *ovnClient) CreateBareNat(lrName, natType, externalIP, logicalIP string) error { + nat, err := c.newNat(lrName, natType, externalIP, logicalIP) + if err != nil { + return err + } + + op, err := c.ovnNbClient.Create(nat) + if err != nil { + return fmt.Errorf("generate operations for creating nat: %v", err) + } + + if err = c.Transact("lr-nat-add", op); err != nil { + return fmt.Errorf("create nat: %v", err) + } + + return nil +} + +// CreateNats create several logical router nat rule once +func (c *ovnClient) CreateNats(lrName string, nats ...*ovnnb.NAT) error { + if len(nats) == 0 { + return nil + } + + models := make([]model.Model, 0, len(nats)) + natUUIDs := make([]string, 0, len(nats)) + for _, nat := range nats { + if nat != nil { + models = append(models, model.Model(nat)) + natUUIDs = append(natUUIDs, nat.UUID) + } + } + + createNatsOp, err := c.ovnNbClient.Create(models...) + if err != nil { + return fmt.Errorf("generate operations for creating nats: %v", err) + } + + natAddOp, err := c.LogicalRouterUpdateNatOp(lrName, natUUIDs, ovsdb.MutateOperationInsert) + if err != nil { + return fmt.Errorf("generate operations for adding nats to logical router %s: %v", lrName, err) + } + + ops := make([]ovsdb.Operation, 0, len(createNatsOp)+len(natAddOp)) + ops = append(ops, createNatsOp...) + ops = append(ops, natAddOp...) + + if err = c.Transact("lr-nats-add", ops); err != nil { + return fmt.Errorf("add nats to %s: %v", lrName, err) + } + + return nil +} + +// UpdateSnat update snat rule +func (c *ovnClient) UpdateSnat(lrName, externalIP, logicalIP string) error { + natType := ovnnb.NATTypeSNAT + + nat, err := c.GetNat(lrName, natType, "", logicalIP, true) + if err != nil { + return err + } + + // update external ip when nat exists + if nat != nil { + nat.ExternalIP = externalIP + return c.UpdateNat(nat, &nat.ExternalIP) + } + + /* create nat */ + if nat, err = c.newNat(lrName, natType, externalIP, logicalIP); err != nil { + return fmt.Errorf("new logical router %s nat 'type %s external ip %s logical ip %s': %v", lrName, natType, externalIP, logicalIP, err) + } + + if err := c.CreateNats(lrName, nat); err != nil { + return fmt.Errorf("add nat 'type %s external ip %s logical ip %s' to logical router %s: %v", natType, externalIP, logicalIP, lrName, err) + } + + return nil +} + +// UpdateDnatAndSnat update dnat_and_snat rule +func (c *ovnClient) UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, gatewayType string) error { + natType := ovnnb.NATTypeDNATAndSNAT + + nat, err := c.GetNat(lrName, natType, externalIP, "", true) + if err != nil { + return err + } + + // update logical port and external mac when nat exists + if nat != nil { + if gatewayType == kubeovnv1.GWDistributedType { + // clear lspName and externalMac when they are empty + nat.LogicalPort = &lspName + nat.ExternalMAC = &externalMac + return c.UpdateNat(nat, &nat.LogicalPort, &nat.ExternalMAC) + } + return nil // do nothing when gw is centralized + } + + options := func(nat *ovnnb.NAT) { + if gatewayType == kubeovnv1.GWDistributedType { + nat.LogicalPort = &lspName + nat.ExternalMAC = &externalMac + + if nil == nat.Options { + nat.Options = make(map[string]string) + } + + nat.Options["stateless"] = "true" + } + } + + /* create nat */ + if nat, err = c.newNat(lrName, natType, externalIP, logicalIP, options); err != nil { + return fmt.Errorf("new logical router %s nat 'type %s external ip %s logical ip %s logical port %s external mac %s': %v", lrName, natType, externalIP, logicalIP, lspName, externalMac, err) + } + + if err := c.CreateNats(lrName, nat); err != nil { + return fmt.Errorf("add nat 'type %s external ip %s logical ip %s logical port %s external mac %s' to logical router %s: %v", natType, externalIP, logicalIP, lspName, externalMac, lrName, err) + } + + return nil +} + +// UpdateNat update nat +func (c *ovnClient) UpdateNat(nat *ovnnb.NAT, fields ...interface{}) error { + if nat == nil { + return fmt.Errorf("nat is nil") + } + + op, err := c.ovnNbClient.Where(nat).Update(nat, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating nat 'type %s external ip %s logical ip %s': %v", nat.Type, nat.ExternalIP, nat.LogicalIP, err) + } + + if err = c.Transact("net-update", op); err != nil { + return fmt.Errorf("update nat 'type %s external ip %s logical ip %s': %v", nat.Type, nat.ExternalIP, nat.LogicalIP, err) + } + + return nil +} + +// DeleteNat delete several nat rule once +func (c *ovnClient) DeleteNats(lrName, natType, logicalIP string) error { + externalIDs := map[string]string{logicalRouterKey: lrName} + + /* delete nats from logical router */ + nats, err := c.ListNats(natType, logicalIP, externalIDs) + if err != nil { + return fmt.Errorf("list logical router %s nats 'type %s logical ip %s': %v", lrName, natType, logicalIP, err) + } + + natsUUIDs := make([]string, 0, len(nats)) + for _, nat := range nats { + natsUUIDs = append(natsUUIDs, nat.UUID) + } + + removeNatOp, err := c.LogicalRouterUpdateNatOp(lrName, natsUUIDs, ovsdb.MutateOperationDelete) + if err != nil { + return fmt.Errorf("generate operations for deleting nats from logical router %s: %v", lrName, err) + } + + // delete nats + delNatsOp, err := c.WhereCache(natFilter(natType, logicalIP, externalIDs)).Delete() + if err != nil { + return fmt.Errorf("generate operation for deleting nats: %v", err) + } + + ops := make([]ovsdb.Operation, 0, len(removeNatOp)+len(delNatsOp)) + ops = append(ops, removeNatOp...) + ops = append(ops, delNatsOp...) + + if err = c.Transact("nats-del", ops); err != nil { + return fmt.Errorf("del nats from logical router %s: %v", lrName, err) + } + + return nil +} + +// DeleteNat delete nat rule +func (c *ovnClient) DeleteNat(lrName, natType, externalIP, logicalIP string) error { + nat, err := c.GetNat(lrName, natType, externalIP, logicalIP, false) + if err != nil { + return err + } + + // remove nat from logical router + removeNatOp, err := c.LogicalRouterUpdateNatOp(lrName, []string{nat.UUID}, ovsdb.MutateOperationDelete) + if err != nil { + return fmt.Errorf("generate operations for deleting nat from logical router %s: %v", lrName, err) + } + + // delete nat + delNatsOp, err := c.Where(nat).Delete() + if err != nil { + return fmt.Errorf("generate operation for deleting nat: %v", err) + } + + ops := make([]ovsdb.Operation, 0, len(removeNatOp)+len(delNatsOp)) + ops = append(ops, removeNatOp...) + ops = append(ops, delNatsOp...) + + if err = c.Transact("lr-nat-del", ops); err != nil { + return fmt.Errorf("del nat from logical router %s: %v", lrName, err) + } + + return nil +} + +// GetNat get nat by some attribute, +// a nat rule is uniquely identified by router(lrName), type(natType) and logical_ip when snat +// a nat rule is uniquely identified by router(lrName), type(natType) and external_ip when dnat_and_snat +func (c *ovnClient) GetNat(lrName, natType, externalIP, logicalIP string, ignoreNotFound bool) (*ovnnb.NAT, error) { + // this is necessary because may exist same nat rule in different logical router + if len(lrName) == 0 { + return nil, fmt.Errorf("the logical router name is required") + } + + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + if natType == ovnnb.NATTypeDNAT { + return nil, fmt.Errorf("does not support dnat for now") + } + + natList := make([]ovnnb.NAT, 0) + if err := c.ovnNbClient.WhereCache(func(nat *ovnnb.NAT) bool { + if len(nat.ExternalIDs) == 0 || nat.ExternalIDs[logicalRouterKey] != lrName { + return false + } + + if natType == ovnnb.NATTypeSNAT { + return nat.Type == natType && nat.LogicalIP == logicalIP + } + + return nat.Type == natType && nat.ExternalIP == externalIP + }).List(ctx, &natList); err != nil { + return nil, fmt.Errorf("get logical router %s nat 'type %s external ip %s logical ip %s': %v", lrName, natType, externalIP, logicalIP, err) + } + + // not found + if len(natList) == 0 { + if ignoreNotFound { + return nil, nil + } + + return nil, fmt.Errorf("not found logical router %s nat 'type %s external ip %s logical ip %s'", lrName, natType, externalIP, logicalIP) + } + + if len(natList) > 1 { + return nil, fmt.Errorf("more than one nat 'type %s external ip %s logical ip %s' in logical router %s", natType, externalIP, logicalIP, lrName) + } + + return &natList[0], nil +} + +// ListNats list acls which match the given externalIDs +func (c *ovnClient) ListNats(natType, logicalIP string, externalIDs map[string]string) ([]ovnnb.NAT, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + natList := make([]ovnnb.NAT, 0) + + if err := c.WhereCache(natFilter(natType, logicalIP, externalIDs)).List(ctx, &natList); err != nil { + return nil, fmt.Errorf("list acls: %v", err) + } + + return natList, nil +} + +func (c *ovnClient) NatExists(lrName, natType, externalIP, logicalIP string) (bool, error) { + nat, err := c.GetNat(lrName, natType, externalIP, logicalIP, true) + return nat != nil, err +} + +// newNat return net with basic information +func (c *ovnClient) newNat(lrName, natType, externalIP, logicalIP string, options ...func(nat *ovnnb.NAT)) (*ovnnb.NAT, error) { + if len(lrName) == 0 { + return nil, fmt.Errorf("the logical router name is required") + } + + if natType != ovnnb.NATTypeSNAT && natType != ovnnb.NATTypeDNATAndSNAT { + return nil, fmt.Errorf("nat type must one of [ snat, dnat_and_snat ]") + } + + if len(externalIP) == 0 || len(logicalIP) == 0 { + return nil, fmt.Errorf("nat 'externalIP %s' and 'logicalIP %s' is required", externalIP, logicalIP) + } + + exists, err := c.NatExists(lrName, natType, externalIP, logicalIP) + if err != nil { + return nil, fmt.Errorf("get logical router %s nat: %v", lrName, err) + } + + // found, ignore + if exists { + return nil, nil + } + + nat := &ovnnb.NAT{ + UUID: ovsclient.NamedUUID(), + Type: natType, + ExternalIP: externalIP, + LogicalIP: logicalIP, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + } + + for _, option := range options { + option(nat) + } + + return nat, nil +} + +// natFilter filter nat which match the given externalIDs, +// result should include all logicalIP nats when natType is empty, +// result should include all nats when externalIDs is empty, +// result should include all nats which externalIDs[key] is not empty when externalIDs[key] is "" +func natFilter(natType, logicalIP string, externalIDs map[string]string) func(nat *ovnnb.NAT) bool { + return func(nat *ovnnb.NAT) bool { + if len(nat.ExternalIDs) < len(externalIDs) { + return false + } + + if len(nat.ExternalIDs) != 0 { + for k, v := range externalIDs { + // if only key exist but not value in externalIDs, we should include this lsp, + // it's equal to shell command `ovn-nbctl --columns=xx find nat external_ids:key!=\"\"` + if len(v) == 0 { + if len(nat.ExternalIDs[k]) == 0 { + return false + } + } else { + if nat.ExternalIDs[k] != v { + return false + } + } + } + } + + if len(natType) != 0 && nat.Type != natType { + return false + } + + if len(logicalIP) != 0 && nat.LogicalIP != logicalIP { + return false + } + + return true + } +} diff --git a/pkg/ovs/ovn-nb-nat_test.go b/pkg/ovs/ovn-nb-nat_test.go new file mode 100644 index 00000000000..9e03905698c --- /dev/null +++ b/pkg/ovs/ovn-nb-nat_test.go @@ -0,0 +1,627 @@ +package ovs + +import ( + "testing" + + "github.com/stretchr/testify/require" + + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +func newNat(lrName, natType, externalIP, logicalIP string, options ...func(nat *ovnnb.NAT)) *ovnnb.NAT { + nat := &ovnnb.NAT{ + UUID: ovsclient.NamedUUID(), + Type: natType, + ExternalIP: externalIP, + LogicalIP: logicalIP, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + } + + for _, option := range options { + option(nat) + } + + return nat +} + +func (suite *OvnClientTestSuite) testCreateNats() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-create-nats-lr" + externalIPs := []string{"192.168.30.254", "192.168.30.253"} + logicalIPs := []string{"10.250.0.4", "10.250.0.5"} + nats := make([]*ovnnb.NAT, 0, 5) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + // snat + for _, logicalIP := range logicalIPs { + nat, err := ovnClient.newNat(lrName, "snat", externalIPs[0], logicalIP) + require.NoError(t, err) + + nats = append(nats, nat) + } + + // dnat_and_snat + for _, externalIP := range externalIPs { + nat, err := ovnClient.newNat(lrName, "dnat_and_snat", externalIP, logicalIPs[0]) + require.NoError(t, err) + + nats = append(nats, nat) + } + + err = ovnClient.CreateNats(lrName, append(nats, nil)...) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + // snat + for _, logicalIP := range logicalIPs { + nat, err := ovnClient.GetNat(lrName, "snat", externalIPs[0], logicalIP, false) + require.NoError(t, err) + + require.Contains(t, lr.Nat, nat.UUID) + } + + // dnat_and_snat + for _, externalIP := range externalIPs { + nat, err := ovnClient.GetNat(lrName, "dnat_and_snat", externalIP, logicalIPs[0], false) + require.NoError(t, err) + + require.Contains(t, lr.Nat, nat.UUID) + } +} + +func (suite *OvnClientTestSuite) testUpdateSnat() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-update-snat-lr" + externalIP := "192.168.30.254" + logicalIP := "10.250.0.4" + natType := ovnnb.NATTypeSNAT + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("create snat", func(t *testing.T) { + err = ovnClient.UpdateSnat(lrName, externalIP, logicalIP) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + nat, err := ovnClient.GetNat(lrName, natType, "", logicalIP, false) + require.NoError(t, err) + + require.Contains(t, lr.Nat, nat.UUID) + }) + + t.Run("update snat", func(t *testing.T) { + externalIP := "192.168.30.253" + err = ovnClient.UpdateSnat(lrName, externalIP, logicalIP) + require.NoError(t, err) + + nat, err := ovnClient.GetNat(lrName, natType, "", logicalIP, false) + require.NoError(t, err) + require.Equal(t, externalIP, nat.ExternalIP) + }) +} + +func (suite *OvnClientTestSuite) testUpdateDnatAndSnat() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-update-dnat-and-snat-lr" + lspName := "test-update-dnat-and-snat-lrp" + externalIP := "192.168.30.254" + logicalIP := "10.250.0.4" + natType := ovnnb.NATTypeDNATAndSNAT + externalMac := "00:00:00:08:0a:de" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + t.Run("create dnat_and_snat", func(t *testing.T) { + t.Run("distributed gw", func(t *testing.T) { + err = ovnClient.UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, kubeovnv1.GWDistributedType) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + nat, err := ovnClient.GetNat(lrName, natType, externalIP, "", false) + require.NoError(t, err) + require.Equal(t, lspName, *nat.LogicalPort) + require.Equal(t, externalMac, *nat.ExternalMAC) + require.Equal(t, "true", nat.Options["stateless"]) + + require.Contains(t, lr.Nat, nat.UUID) + }) + + t.Run("centralized gw", func(t *testing.T) { + externalIP := "192.168.30.250" + + err = ovnClient.UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, kubeovnv1.GWCentralizedType) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + nat, err := ovnClient.GetNat(lrName, natType, externalIP, "", false) + require.NoError(t, err) + require.Empty(t, nat.Options["stateless"]) + + require.Contains(t, lr.Nat, nat.UUID) + }) + }) + + t.Run("update dnat_and_snat", func(t *testing.T) { + t.Run("distributed gw", func(t *testing.T) { + lspName := "test-update-dnat-and-snat-lrp-1" + externalMac := "00:00:00:08:0a:ff" + + err = ovnClient.UpdateDnatAndSnat(lrName, externalIP, logicalIP, lspName, externalMac, kubeovnv1.GWDistributedType) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + nat, err := ovnClient.GetNat(lrName, natType, externalIP, "", false) + require.NoError(t, err) + require.Equal(t, lspName, *nat.LogicalPort) + require.Equal(t, externalMac, *nat.ExternalMAC) + require.Equal(t, "true", nat.Options["stateless"]) + + require.Contains(t, lr.Nat, nat.UUID) + }) + }) +} + +func (suite *OvnClientTestSuite) testDeleteNat() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-del-nat-lr" + externalIP := "192.168.30.254" + logicalIP := "10.250.0.4" + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + prepareFunc := func() { + nats := make([]*ovnnb.NAT, 0) + + // create snat rule + nat, err := ovnClient.newNat(lrName, "snat", externalIP, logicalIP) + require.NoError(t, err) + nats = append(nats, nat) + + // create dnat_and_snat rule + nat, err = ovnClient.newNat(lrName, "dnat_and_snat", externalIP, logicalIP) + require.NoError(t, err) + nats = append(nats, nat) + + err = ovnClient.CreateNats(lrName, nats...) + require.NoError(t, err) + } + + prepareFunc() + + t.Run("delete snat from logical router", func(t *testing.T) { + err = ovnClient.DeleteNat(lrName, "snat", externalIP, logicalIP) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 1) + + nat := &ovnnb.NAT{UUID: lr.Nat[0]} + err = ovnClient.GetEntityInfo(nat) + require.NoError(t, err) + require.Equal(t, "dnat_and_snat", nat.Type) + }) + + t.Run("delete dnat_and_snat from logical router", func(t *testing.T) { + err = ovnClient.DeleteNat(lrName, "dnat_and_snat", externalIP, logicalIP) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Nat) + }) +} + +func (suite *OvnClientTestSuite) testDeleteNats() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-del-nats-lr" + externalIPs := []string{"192.168.30.254", "192.168.30.253"} + logicalIPs := []string{"10.250.0.4", "10.250.0.5"} + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + prepareFunc := func() { + nats := make([]*ovnnb.NAT, 0) + // create two snat rule + for _, logicalIP := range logicalIPs { + nat, err := ovnClient.newNat(lrName, "snat", externalIPs[0], logicalIP) + require.NoError(t, err) + nats = append(nats, nat) + } + + // create two dnat_and_snat rule + for _, externalIP := range externalIPs { + nat, err := ovnClient.newNat(lrName, "dnat_and_snat", externalIP, logicalIPs[0]) + require.NoError(t, err) + nats = append(nats, nat) + } + + err = ovnClient.CreateNats(lrName, nats...) + require.NoError(t, err) + } + + t.Run("delete nats from logical router", func(t *testing.T) { + prepareFunc() + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 4) + + err = ovnClient.DeleteNats(lrName, "", "") + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Nat) + }) + + t.Run("delete snat or dnat_and_snat from logical router", func(t *testing.T) { + prepareFunc() + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 4) + + err = ovnClient.DeleteNats(lrName, "snat", "") + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 2) + + err = ovnClient.DeleteNats(lrName, "dnat_and_snat", "") + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Nat) + }) + + t.Run("delete nat with same logical ip from logical router", func(t *testing.T) { + prepareFunc() + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 4) + + err = ovnClient.DeleteNats(lrName, "", logicalIPs[0]) + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 1) + + // clear + err = ovnClient.DeleteNats(lrName, "", "") + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Nat) + }) + + t.Run("delete snat with same logical ip from logical router", func(t *testing.T) { + prepareFunc() + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 4) + + err = ovnClient.DeleteNats(lrName, "snat", logicalIPs[0]) + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 3) + + // clear + err = ovnClient.DeleteNats(lrName, "", "") + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Empty(t, lr.Nat) + }) + + t.Run("delete dnat_and_snat with same logical ip from logical router", func(t *testing.T) { + prepareFunc() + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 4) + + err = ovnClient.DeleteNats(lrName, "dnat_and_snat", logicalIPs[0]) + require.NoError(t, err) + + lr, err = ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + require.Len(t, lr.Nat, 2) + }) +} + +func (suite *OvnClientTestSuite) testGetNat() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test_get_nat_lr" + + t.Run("snat", func(t *testing.T) { + t.Parallel() + natType := "snat" + externalIP := "192.168.30.254" + logicalIP := "10.250.0.4" + + err := ovnClient.CreateBareNat(lrName, natType, externalIP, logicalIP) + require.NoError(t, err) + + t.Run("found nat", func(t *testing.T) { + _, err := ovnClient.GetNat(lrName, natType, externalIP, logicalIP, false) + require.NoError(t, err) + }) + + t.Run("logical ip is different", func(t *testing.T) { + _, err := ovnClient.GetNat(lrName, natType, externalIP, "10.250.0.10", false) + require.ErrorContains(t, err, "not found") + }) + + t.Run("logical router name is different", func(t *testing.T) { + _, err := ovnClient.GetNat(lrName+"x", natType, externalIP, logicalIP, false) + require.ErrorContains(t, err, "not found") + }) + }) + + t.Run("dnat_and_snat", func(t *testing.T) { + t.Parallel() + natType := "dnat_and_snat" + externalIP := "192.168.30.254" + logicalIP := "10.250.0.4" + + err := ovnClient.CreateBareNat(lrName, natType, externalIP, logicalIP) + require.NoError(t, err) + + t.Run("found nat", func(t *testing.T) { + _, err := ovnClient.GetNat(lrName, natType, externalIP, logicalIP, false) + require.NoError(t, err) + }) + + t.Run("external ip is different", func(t *testing.T) { + _, err := ovnClient.GetNat(lrName, natType, "192.168.30.255", logicalIP, false) + require.ErrorContains(t, err, "not found") + }) + }) +} + +func (suite *OvnClientTestSuite) test_newNat() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lrName := "test-new-nat-lr" + natType := "snat" + externalIP := "192.168.30.254" + logicalIP := "10.250.0.4" + + t.Run("new snat rule", func(t *testing.T) { + t.Parallel() + + expect := &ovnnb.NAT{ + Type: natType, + ExternalIP: externalIP, + LogicalIP: logicalIP, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + } + + nat, err := ovnClient.newNat(lrName, natType, externalIP, logicalIP) + require.NoError(t, err) + expect.UUID = nat.UUID + require.Equal(t, expect, nat) + }) + + t.Run("new stateless dnat_and_snat rule", func(t *testing.T) { + t.Parallel() + + lspName := "test-new-nat-lsp" + externalMac := "00:00:00:f7:82:60" + natType := "dnat_and_snat" + + expect := &ovnnb.NAT{ + Type: natType, + ExternalIP: externalIP, + LogicalIP: logicalIP, + ExternalIDs: map[string]string{ + logicalRouterKey: lrName, + }, + LogicalPort: &lspName, + ExternalMAC: &externalMac, + } + + options := func(nat *ovnnb.NAT) { + nat.LogicalPort = &lspName + nat.ExternalMAC = &externalMac + } + + nat, err := ovnClient.newNat(lrName, natType, externalIP, logicalIP, options) + require.NoError(t, err) + expect.UUID = nat.UUID + require.Equal(t, expect, nat) + }) +} + +func (suite *OvnClientTestSuite) test_natFilter() { + t := suite.T() + t.Parallel() + + lrName := "test-filter-nat-lr" + externalIPs := []string{"192.168.30.254", "192.168.30.253"} + logicalIPs := []string{"10.250.0.4", "10.250.0.5"} + + nats := make([]*ovnnb.NAT, 0) + // create two snat rule + for _, logicalIP := range logicalIPs { + nat := newNat(lrName, "snat", externalIPs[0], logicalIP) + nats = append(nats, nat) + } + + // create two dnat_and_snat rule + for _, externalIP := range externalIPs { + nat := newNat(lrName, "dnat_and_snat", externalIP, logicalIPs[0]) + nats = append(nats, nat) + } + + // create three snat rule with other acl parent key + for i := 0; i < 3; i++ { + nat := newNat(lrName, "snat", externalIPs[0], logicalIPs[0]) + nat.ExternalIDs[logicalRouterKey] = lrName + "-test" + nats = append(nats, nat) + + } + + t.Run("include all nat", func(t *testing.T) { + filterFunc := natFilter("", "", nil) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 7) + }) + + t.Run("include all nat with external ids", func(t *testing.T) { + filterFunc := natFilter("", "", map[string]string{logicalRouterKey: lrName}) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 4) + }) + + t.Run("include snat", func(t *testing.T) { + filterFunc := natFilter("snat", "", nil) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 5) + }) + + t.Run("include snat with external ids", func(t *testing.T) { + filterFunc := natFilter("snat", "", map[string]string{logicalRouterKey: lrName}) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 2) + }) + + t.Run("include dnat_and_snat", func(t *testing.T) { + filterFunc := natFilter("dnat_and_snat", "", nil) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 2) + }) + + t.Run("include dnat_and_snat with external ids", func(t *testing.T) { + filterFunc := natFilter("dnat_and_snat", "", map[string]string{logicalRouterKey: lrName}) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 2) + }) + + t.Run("include all nat with same logical ip", func(t *testing.T) { + filterFunc := natFilter("", logicalIPs[0], map[string]string{logicalRouterKey: lrName}) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 3) + }) + + t.Run("include snat with same logical ip", func(t *testing.T) { + filterFunc := natFilter("snat", logicalIPs[0], map[string]string{logicalRouterKey: lrName}) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 1) + }) + + t.Run("include dnat_and_snat with same logical ip", func(t *testing.T) { + filterFunc := natFilter("dnat_and_snat", logicalIPs[0], map[string]string{logicalRouterKey: lrName}) + count := 0 + for _, nat := range nats { + if filterFunc(nat) { + count++ + } + } + require.Equal(t, count, 2) + }) + + t.Run("result should exclude nat when externalIDs's length is not equal", func(t *testing.T) { + t.Parallel() + + nat := newNat(lrName, "snat", externalIPs[0], logicalIPs[0]) + filterFunc := natFilter("", "", map[string]string{ + logicalRouterKey: lrName, + "key": "value", + }) + + require.False(t, filterFunc(nat)) + }) +} diff --git a/pkg/ovs/ovn-nb-port_group.go b/pkg/ovs/ovn-nb-port_group.go index 24ae9e02996..dec990d7c15 100644 --- a/pkg/ovs/ovn-nb-port_group.go +++ b/pkg/ovs/ovn-nb-port_group.go @@ -5,93 +5,266 @@ import ( "fmt" "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + "k8s.io/klog/v2" "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" ) -func (c OvnClient) GetPortGroup(name string, ignoreNotFound bool) (*ovnnb.PortGroup, error) { - ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) - defer cancel() - - pg := &ovnnb.PortGroup{Name: name} - if err := c.ovnNbClient.Get(ctx, pg); err != nil { - if ignoreNotFound && err == client.ErrNotFound { - return nil, nil - } - return nil, fmt.Errorf("failed to get port group %s: %v", name, err) - } - - return pg, nil -} - -func (c OvnClient) CreatePortGroup(name string, externalIDs map[string]string) error { - pg, err := c.GetPortGroup(name, true) +func (c *ovnClient) CreatePortGroup(pgName string, externalIDs map[string]string) error { + exist, err := c.PortGroupExists(pgName) if err != nil { return err } - if pg != nil { + + // ingnore + if exist { return nil } - pg = &ovnnb.PortGroup{ - Name: name, + pg := &ovnnb.PortGroup{ + Name: pgName, ExternalIDs: externalIDs, } + ops, err := c.ovnNbClient.Create(pg) if err != nil { - return fmt.Errorf("failed to generate create operations for port group %s: %v", name, err) + return fmt.Errorf("generate operations for creating port group %s: %v", pgName, err) } - if err = Transact(c.ovnNbClient, "pg-add", ops, c.ovnNbClient.Timeout); err != nil { - return fmt.Errorf("failed to create port group %s: %v", name, err) + + if err = c.Transact("pg-add", ops); err != nil { + return fmt.Errorf("create port group %s: %v", pgName, err) } return nil } -func (c OvnClient) portGroupPortOp(pgName, portName string, opIsAdd bool) error { - pg, err := c.GetPortGroup(pgName, false) +// PortGroupAddPorts add ports to port group +func (c *ovnClient) PortGroupAddPorts(pgName string, lspNames ...string) error { + return c.PortGroupUpdatePorts(pgName, ovsdb.MutateOperationInsert, lspNames...) +} + +// PortGroupRemovePorts remove ports from port group +func (c *ovnClient) PortGroupRemovePorts(pgName string, lspNames ...string) error { + return c.PortGroupUpdatePorts(pgName, ovsdb.MutateOperationDelete, lspNames...) +} + +// PortGroupResetPorts remove all ports from port group +func (c *ovnClient) PortGroupResetPorts(pgName string) error { + pg, err := c.GetPortGroup(pgName, true) if err != nil { - return err + return fmt.Errorf("get port group %s: %v", pgName, err) } - lsp, err := c.GetLogicalSwitchPort(portName, false) + // not found, skip + if pg == nil { + return nil + } + + // clear ports + pg.Ports = nil + + if err := c.UpdatePortGroup(pg, &pg.Ports); err != nil { + return fmt.Errorf("reset port group %s ports: %v", pgName, err) + } + + return nil +} + +// UpdatePortGroup update port group +func (c *ovnClient) UpdatePortGroup(pg *ovnnb.PortGroup, fields ...interface{}) error { + op, err := c.Where(pg).Update(pg, fields...) if err != nil { - return err + return fmt.Errorf("generate operations for updating port group %s: %v", pg.Name, err) } - portMap := make(map[string]struct{}, len(pg.Ports)) - for _, port := range pg.Ports { - portMap[port] = struct{}{} + if err = c.Transact("pg-update", op); err != nil { + return fmt.Errorf("update port group %s: %v", pg.Name, err) } - if _, ok := portMap[lsp.UUID]; ok == opIsAdd { + + return nil +} + +// PortGroupUpdatePorts add several ports to or from port group once +func (c *ovnClient) PortGroupUpdatePorts(pgName string, op ovsdb.Mutator, lspNames ...string) error { + if len(lspNames) == 0 { return nil } - if opIsAdd { - pg.Ports = append(pg.Ports, lsp.UUID) - } else { - delete(portMap, lsp.UUID) - pg.Ports = make([]string, 0, len(portMap)) - for port := range portMap { - pg.Ports = append(pg.Ports, port) + lspUUIDs := make([]string, 0, len(lspNames)) + + for _, lspName := range lspNames { + lsp, err := c.GetLogicalSwitchPort(lspName, true) + if err != nil { + return err + } + + // ignore non-existent object + if lsp != nil { + lspUUIDs = append(lspUUIDs, lsp.UUID) } } - ops, err := c.ovnNbClient.Where(pg).Update(pg, &pg.Ports) + ops, err := c.portGroupUpdatePortOp(pgName, lspUUIDs, op) if err != nil { - return fmt.Errorf("failed to generate update operations for port group %s: %v", pgName, err) + return fmt.Errorf("generate operations for port group %s update ports %v: %v", pgName, lspNames, err) } - if err = Transact(c.ovnNbClient.Client, "update", ops, c.ovnNbClient.Timeout); err != nil { - return fmt.Errorf("failed to update ports of port group %s: %v", pgName, err) + + if err := c.Transact("pg-ports-update", ops); err != nil { + return fmt.Errorf("port group %s update ports %v: %v", pgName, lspNames, err) } return nil } -func (c OvnClient) PortGroupAddPort(pgName, portName string) error { - return c.portGroupPortOp(pgName, portName, true) +func (c *ovnClient) DeletePortGroup(pgName string) error { + pg, err := c.GetPortGroup(pgName, true) + if err != nil { + return fmt.Errorf("get port group %s when delete: %v", pgName, err) + } + + // not found, skip + if pg == nil { + return nil + } + + op, err := c.Where(pg).Delete() + if err != nil { + return err + } + + if err := c.Transact("pg-del", op); err != nil { + return fmt.Errorf("delete port group %s: %v", pgName, err) + } + + return nil } -func (c OvnClient) PortGroupRemovePort(pgName, portName string) error { - return c.portGroupPortOp(pgName, portName, false) +// GetPortGroup get port group by name +func (c *ovnClient) GetPortGroup(pgName string, ignoreNotFound bool) (*ovnnb.PortGroup, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + pg := &ovnnb.PortGroup{Name: pgName} + if err := c.ovnNbClient.Get(ctx, pg); err != nil { + if ignoreNotFound && err == client.ErrNotFound { + return nil, nil + } + return nil, fmt.Errorf("get port group %s: %v", pgName, err) + } + + return pg, nil +} + +// ListPortGroups list port groups which match the given externalIDs, +// result should include all port groups when externalIDs is empty, +// result should include all port groups which externalIDs[key] is not empty when externalIDs[key] is "" +func (c *ovnClient) ListPortGroups(externalIDs map[string]string) ([]ovnnb.PortGroup, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + pgs := make([]ovnnb.PortGroup, 0) + + if err := c.WhereCache(func(pg *ovnnb.PortGroup) bool { + if len(pg.ExternalIDs) < len(externalIDs) { + return false + } + + if len(pg.ExternalIDs) != 0 { + for k, v := range externalIDs { + // if only key exist but not value in externalIDs, we should include this pg, + // it's equal to shell command `ovn-nbctl --columns=xx find port_group external_ids:key!=\"\"` + if len(v) == 0 { + if len(pg.ExternalIDs[k]) == 0 { + return false + } + } else { + if pg.ExternalIDs[k] != v { + return false + } + } + + } + } + + return true + }).List(ctx, &pgs); err != nil { + klog.Errorf("list logical switch ports: %v", err) + return nil, err + } + + return pgs, nil +} + +func (c *ovnClient) PortGroupExists(pgName string) (bool, error) { + lsp, err := c.GetPortGroup(pgName, true) + return lsp != nil, err +} + +// portGroupUpdatePortOp create operations add port to or delete port from port group +func (c *ovnClient) portGroupUpdatePortOp(pgName string, lspUUIDs []string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(lspUUIDs) == 0 { + return nil, nil + } + + mutation := func(pg *ovnnb.PortGroup) *model.Mutation { + mutation := &model.Mutation{ + Field: &pg.Ports, + Value: lspUUIDs, + Mutator: op, + } + + return mutation + } + + return c.portGroupOp(pgName, mutation) +} + +// portGroupUpdatePortOp create operations add acl to or delete acl from port group +func (c *ovnClient) portGroupUpdateAclOp(pgName string, aclUUIDs []string, op ovsdb.Mutator) ([]ovsdb.Operation, error) { + if len(aclUUIDs) == 0 { + return nil, nil + } + + mutation := func(pg *ovnnb.PortGroup) *model.Mutation { + mutation := &model.Mutation{ + Field: &pg.ACLs, + Value: aclUUIDs, + Mutator: op, + } + + return mutation + } + + return c.portGroupOp(pgName, mutation) +} + +// portGroupOp create operations about port group +func (c *ovnClient) portGroupOp(pgName string, mutationsFunc ...func(pg *ovnnb.PortGroup) *model.Mutation) ([]ovsdb.Operation, error) { + pg, err := c.GetPortGroup(pgName, false) + if err != nil { + return nil, fmt.Errorf("get port group %s: %v", pgName, err) + } + + if len(mutationsFunc) == 0 { + return nil, nil + } + + mutations := make([]model.Mutation, 0, len(mutationsFunc)) + + for _, f := range mutationsFunc { + mutation := f(pg) + + if mutation != nil { + mutations = append(mutations, *mutation) + } + } + + ops, err := c.ovnNbClient.Where(pg).Mutate(pg, mutations...) + if err != nil { + return nil, fmt.Errorf("generate operations for mutating port group %s: %v", pgName, err) + } + + return ops, nil } diff --git a/pkg/ovs/ovn-nb-port_group_test.go b/pkg/ovs/ovn-nb-port_group_test.go new file mode 100644 index 00000000000..f49ba97d1f3 --- /dev/null +++ b/pkg/ovs/ovn-nb-port_group_test.go @@ -0,0 +1,500 @@ +package ovs + +import ( + "fmt" + "strings" + "testing" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/stretchr/testify/require" +) + +func (suite *OvnClientTestSuite) testCreatePortGroup() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-create-pg" + + err := ovnClient.CreatePortGroup(pgName, map[string]string{ + "type": "security_group", + sgKey: "test-sg", + }) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.NotEmpty(t, pg.UUID) + require.Equal(t, pgName, pg.Name) + require.Equal(t, map[string]string{ + "type": "security_group", + sgKey: "test-sg", + }, pg.ExternalIDs) +} + +func (suite *OvnClientTestSuite) testPortGroupResetPorts() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-reset-pg-ports" + prefix := "test-reset-ports" + lspNames := make([]string, 0, 3) + + err := ovnClient.CreatePortGroup(pgName, map[string]string{ + "type": "security_group", + sgKey: "test-sg", + }) + require.NoError(t, err) + + for i := 1; i <= 3; i++ { + lspName := fmt.Sprintf("%s-%d", prefix, i) + lspNames = append(lspNames, lspName) + + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + ExternalIDs: map[string]string{ + "vendor": util.CniTypeName, + }, + } + + err := createLogicalSwitchPort(ovnClient, lsp) + require.NoError(t, err) + } + + err = ovnClient.PortGroupAddPorts(pgName, lspNames...) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.NotEmpty(t, pg.Ports) + + err = ovnClient.PortGroupResetPorts(pgName) + require.NoError(t, err) + + pg, err = ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + + require.Empty(t, pg.Ports) +} + +func (suite *OvnClientTestSuite) testPortGroupUpdatePorts() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-add-ports-to-pg" + lsName := "test-add-ports-to-ls" + prefix := "test-add-lsp" + lspNames := make([]string, 0, 3) + + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.CreatePortGroup(pgName, map[string]string{ + "type": "security_group", + sgKey: "test-sg", + }) + require.NoError(t, err) + + for i := 1; i <= 3; i++ { + lspName := fmt.Sprintf("%s-%d", prefix, i) + lspNames = append(lspNames, lspName) + err := ovnClient.CreateBareLogicalSwitchPort(lsName, lspName, "", "") + require.NoError(t, err) + } + + t.Run("add ports to port group", func(t *testing.T) { + err = ovnClient.PortGroupUpdatePorts(pgName, ovsdb.MutateOperationInsert, lspNames...) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + + for _, lspName := range lspNames { + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Contains(t, pg.Ports, lsp.UUID) + } + }) + + t.Run("should no err when add non-existent ports to port group", func(t *testing.T) { + // add a non-existent ports + err = ovnClient.PortGroupUpdatePorts(pgName, ovsdb.MutateOperationInsert, "test-add-lsp-non-existent") + require.NoError(t, err) + }) + + t.Run("del ports from port group", func(t *testing.T) { + err = ovnClient.PortGroupUpdatePorts(pgName, ovsdb.MutateOperationDelete, lspNames[0:2]...) + require.NoError(t, err) + + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + + for i, lspName := range lspNames { + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + + // port group contains the last ports + if i == 2 { + require.Contains(t, pg.Ports, lsp.UUID) + continue + } + require.NotContains(t, pg.Ports, lsp.UUID) + } + }) + + t.Run("del non-existent ports from port group", func(t *testing.T) { + // del a non-existent ports + err = ovnClient.PortGroupUpdatePorts(pgName, ovsdb.MutateOperationDelete, "test-del-lsp-non-existent") + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testDeletePortGroup() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-delete-pg" + + t.Run("no err when delete existent port group", func(t *testing.T) { + t.Parallel() + err := ovnClient.CreatePortGroup(pgName, nil) + require.NoError(t, err) + + err = ovnClient.DeletePortGroup(pgName) + require.NoError(t, err) + + _, err = ovnClient.GetPortGroup(pgName, false) + require.ErrorContains(t, err, "object not found") + }) + + t.Run("no err when delete non-existent logical router", func(t *testing.T) { + t.Parallel() + err := ovnClient.DeletePortGroup("test-delete-pg-non-existent") + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testGetGetPortGroup() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-get-pg" + + err := ovnClient.CreatePortGroup(pgName, map[string]string{ + "type": "security_group", + sgKey: "test-sg", + }) + require.NoError(t, err) + + t.Run("should return no err when found port group", func(t *testing.T) { + t.Parallel() + pg, err := ovnClient.GetPortGroup(pgName, false) + require.NoError(t, err) + require.Equal(t, pgName, pg.Name) + require.NotEmpty(t, pg.UUID) + }) + + t.Run("should return err when not found port group", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.GetPortGroup("test-get-pg-non-existent", false) + require.ErrorContains(t, err, "object not found") + }) + + t.Run("no err when not found port group and ignoreNotFound is true", func(t *testing.T) { + t.Parallel() + _, err := ovnClient.GetPortGroup("test-get-pg-non-existent", true) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testListPortGroups() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + + t.Run("result should exclude pg when externalIDs's length is not equal", func(t *testing.T) { + pgName := "test-list-pg-mismatch-length" + + err := ovnClient.CreatePortGroup(pgName, map[string]string{"key": "value"}) + require.NoError(t, err) + + pgs, err := ovnClient.ListPortGroups(map[string]string{sgKey: "sg", "type": "security_group", "key": "value"}) + require.NoError(t, err) + require.Empty(t, pgs) + }) + + t.Run("result should include lsp when key exists in pg column: external_ids", func(t *testing.T) { + pgName := "test-list-pg-exist-key" + + err := ovnClient.CreatePortGroup(pgName, map[string]string{sgKey: "sg", "type": "security_group", "key": "value"}) + require.NoError(t, err) + + pgs, err := ovnClient.ListPortGroups(map[string]string{"type": "security_group", "key": "value"}) + require.NoError(t, err) + require.Len(t, pgs, 1) + require.Equal(t, pgName, pgs[0].Name) + }) + + t.Run("result should include all pg when externalIDs is empty", func(t *testing.T) { + prefix := "test-list-pg-all" + + for i := 0; i < 4; i++ { + pgName := fmt.Sprintf("%s-%d", prefix, i) + + err := ovnClient.CreatePortGroup(pgName, map[string]string{sgKey: "sg", "type": "security_group", "key": "value"}) + require.NoError(t, err) + } + + out, err := ovnClient.ListPortGroups(nil) + require.NoError(t, err) + count := 0 + for _, v := range out { + if strings.Contains(v.Name, prefix) { + count++ + } + } + require.Equal(t, count, 4) + + out, err = ovnClient.ListPortGroups(map[string]string{}) + require.NoError(t, err) + count = 0 + for _, v := range out { + if strings.Contains(v.Name, prefix) { + count++ + } + } + require.Equal(t, count, 4) + }) + + t.Run("result should include pg which externalIDs[key] is ''", func(t *testing.T) { + pgName := "test-list-pg-no-val" + + err := ovnClient.CreatePortGroup(pgName, map[string]string{"sg_test": "sg", "type": "security_group", "key": "value"}) + require.NoError(t, err) + + pgs, err := ovnClient.ListPortGroups(map[string]string{"sg_test": "", "key": ""}) + require.NoError(t, err) + require.Len(t, pgs, 1) + require.Equal(t, pgName, pgs[0].Name) + + pgs, err = ovnClient.ListPortGroups(map[string]string{"sg_test": ""}) + require.NoError(t, err) + require.Len(t, pgs, 1) + require.Equal(t, pgName, pgs[0].Name) + + pgs, err = ovnClient.ListPortGroups(map[string]string{"sg_test": "", "key": "", "key1": ""}) + require.NoError(t, err) + require.Empty(t, pgs) + }) +} + +func (suite *OvnClientTestSuite) test_portGroupUpdatePortOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-update-port-op-pg" + lspUUIDs := []string{ovsclient.NamedUUID(), ovsclient.NamedUUID()} + + err := ovnClient.CreatePortGroup(pgName, map[string]string{ + "type": "security_group", + sgKey: "test-sg", + }) + require.NoError(t, err) + + t.Run("add new port to port group", func(t *testing.T) { + t.Parallel() + + ops, err := ovnClient.portGroupUpdatePortOp(pgName, lspUUIDs, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lspUUIDs[0], + }, + ovsdb.UUID{ + GoUUID: lspUUIDs[1], + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del port from port group", func(t *testing.T) { + t.Parallel() + + ops, err := ovnClient.portGroupUpdatePortOp(pgName, lspUUIDs, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lspUUIDs[0], + }, + ovsdb.UUID{ + GoUUID: lspUUIDs[1], + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should return err when port group does not exist", func(t *testing.T) { + t.Parallel() + + _, err := ovnClient.portGroupUpdatePortOp("test-port-op-pg-non-existent", lspUUIDs, ovsdb.MutateOperationInsert) + require.ErrorContains(t, err, "object not found") + }) +} + +func (suite *OvnClientTestSuite) test_portGroupUpdateAclOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-update-acl-op-pg" + aclUUIDs := []string{ovsclient.NamedUUID(), ovsclient.NamedUUID()} + + err := ovnClient.CreatePortGroup(pgName, map[string]string{ + "type": "security_group", + sgKey: "test-sg", + }) + require.NoError(t, err) + + t.Run("add new acl to port group", func(t *testing.T) { + t.Parallel() + + ops, err := ovnClient.portGroupUpdateAclOp(pgName, aclUUIDs, ovsdb.MutateOperationInsert) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "acls", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: aclUUIDs[0], + }, + ovsdb.UUID{ + GoUUID: aclUUIDs[1], + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("del acl from port group", func(t *testing.T) { + t.Parallel() + + ops, err := ovnClient.portGroupUpdateAclOp(pgName, aclUUIDs, ovsdb.MutateOperationDelete) + require.NoError(t, err) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "acls", + Mutator: ovsdb.MutateOperationDelete, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: aclUUIDs[0], + }, + ovsdb.UUID{ + GoUUID: aclUUIDs[1], + }, + }, + }, + }, + }, ops[0].Mutations) + }) + + t.Run("should return err when port group does not exist", func(t *testing.T) { + t.Parallel() + + _, err := ovnClient.portGroupUpdateAclOp("test-acl-op-pg-non-existent", aclUUIDs, ovsdb.MutateOperationInsert) + require.ErrorContains(t, err, "object not found") + }) +} + +func (suite *OvnClientTestSuite) test_portGroupOp() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + pgName := "test-port-op-pg" + + err := ovnClient.CreatePortGroup(pgName, map[string]string{ + "type": "security_group", + sgKey: "test-sg", + }) + require.NoError(t, err) + + lspUUID := ovsclient.NamedUUID() + lspMutation := func(pg *ovnnb.PortGroup) *model.Mutation { + mutation := &model.Mutation{ + Field: &pg.Ports, + Value: []string{lspUUID}, + Mutator: ovsdb.MutateOperationInsert, + } + + return mutation + } + + aclUUID := ovsclient.NamedUUID() + aclMutation := func(pg *ovnnb.PortGroup) *model.Mutation { + mutation := &model.Mutation{ + Field: &pg.ACLs, + Value: []string{aclUUID}, + Mutator: ovsdb.MutateOperationInsert, + } + + return mutation + } + + ops, err := ovnClient.portGroupOp(pgName, lspMutation, aclMutation) + require.NoError(t, err) + + require.Len(t, ops[0].Mutations, 2) + require.Equal(t, []ovsdb.Mutation{ + { + Column: "ports", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: lspUUID, + }, + }, + }, + }, + { + Column: "acls", + Mutator: ovsdb.MutateOperationInsert, + Value: ovsdb.OvsSet{ + GoSet: []interface{}{ + ovsdb.UUID{ + GoUUID: aclUUID, + }, + }, + }, + }, + }, ops[0].Mutations) +} diff --git a/pkg/ovs/ovn-nb-suite_test.go b/pkg/ovs/ovn-nb-suite_test.go new file mode 100644 index 00000000000..7cd905defff --- /dev/null +++ b/pkg/ovs/ovn-nb-suite_test.go @@ -0,0 +1,738 @@ +package ovs + +import ( + "context" + "fmt" + "log" + "math/rand" + "os" + "strings" + "testing" + "time" + + "github.com/cenkalti/backoff/v4" + "github.com/go-logr/stdr" + "github.com/ovn-org/libovsdb/client" + "github.com/ovn-org/libovsdb/database" + "github.com/ovn-org/libovsdb/model" + "github.com/ovn-org/libovsdb/ovsdb" + "github.com/ovn-org/libovsdb/ovsdb/serverdb" + "github.com/ovn-org/libovsdb/server" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +type OvnClientTestSuite struct { + suite.Suite + ovnClient *ovnClient +} + +func (suite *OvnClientTestSuite) SetupSuite() { + fmt.Println("set up OvnClient test suite") + clientSchema := ovnnb.Schema() + clientDBModel, err := ovnnb.FullDatabaseModel() + require.NoError(suite.T(), err) + + _, sock := newOVSDBServer(suite.T(), clientDBModel, clientSchema) + endpoint := fmt.Sprintf("unix:%s", sock) + require.FileExists(suite.T(), sock) + + ovnClient, err := newOvnClient(suite.T(), endpoint, 10, "100.64.0.0/16,fd00:100:64::/64") + require.NoError(suite.T(), err) + + suite.ovnClient = ovnClient +} + +// In order for 'go test' to run this suite, we need to create +// a normal test function and pass our suite to suite.Run +func TestOvnClientTestSuite(t *testing.T) { + suite.Run(t, new(OvnClientTestSuite)) +} + +/* nb_global unit test */ +func (suite *OvnClientTestSuite) Test_GetNbGlobal() { + suite.testGetNbGlobal() +} + +func (suite *OvnClientTestSuite) Test_UpdateNbGlobal() { + suite.testUpdateNbGlobal() +} + +func (suite *OvnClientTestSuite) Test_SetAzName() { + suite.testSetAzName() +} + +func (suite *OvnClientTestSuite) Test_SetICAutoRoute() { + suite.testSetICAutoRoute() +} + +func (suite *OvnClientTestSuite) Test_SetUseCtInvMatch() { + suite.testSetUseCtInvMatch() +} + +func (suite *OvnClientTestSuite) Test_SetLBCIDR() { + suite.testSetLBCIDR() +} + +/* logical_switch unit test */ +func (suite *OvnClientTestSuite) Test_CreateLogicalSwitch() { + suite.testCreateLogicalSwitch() +} + +func (suite *OvnClientTestSuite) Test_LogicalSwitchAddPort() { + suite.testLogicalSwitchAddPort() +} + +func (suite *OvnClientTestSuite) Test_LogicalSwitchDelPort() { + suite.testLogicalSwitchDelPort() +} + +func (suite *OvnClientTestSuite) Test_LogicalSwitchUpdateLoadBalancers() { + suite.testLogicalSwitchUpdateLoadBalancers() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalSwitch() { + suite.testDeleteLogicalSwitch() +} + +func (suite *OvnClientTestSuite) Test_GetLogicalSwitch() { + suite.testGetLogicalSwitch() +} + +func (suite *OvnClientTestSuite) Test_ListLogicalSwitch() { + suite.testListLogicalSwitch() +} + +func (suite *OvnClientTestSuite) Test_LogicalSwitchUpdatePortOp() { + suite.testLogicalSwitchUpdatePortOp() +} + +func (suite *OvnClientTestSuite) Test_LogicalSwitchUpdateLoadBalancerOp() { + suite.testLogicalSwitchUpdateLoadBalancerOp() +} + +func (suite *OvnClientTestSuite) Test_logicalSwitchUpdateAclOp() { + suite.test_logicalSwitchUpdateAclOp() +} + +func (suite *OvnClientTestSuite) Test_LogicalSwitchOp() { + suite.testLogicalSwitchOp() +} + +/* logical_switch_port unit test */ +func (suite *OvnClientTestSuite) Test_CreateLogicalSwitchPort() { + suite.testCreateLogicalSwitchPort() +} + +func (suite *OvnClientTestSuite) Test_CreateLocalnetLogicalSwitchPort() { + suite.testCreateLocalnetLogicalSwitchPort() +} + +func (suite *OvnClientTestSuite) Test_CreateVirtualLogicalSwitchPorts() { + suite.testCreateVirtualLogicalSwitchPorts() +} + +func (suite *OvnClientTestSuite) Test_CreateBareLogicalSwitchPort() { + suite.testCreateBareLogicalSwitchPort() +} + +func (suite *OvnClientTestSuite) Test_SetLogicalSwitchPortVirtualParents() { + suite.testSetLogicalSwitchPortVirtualParents() +} + +func (suite *OvnClientTestSuite) Test_SetLogicalSwitchPortSecurity() { + suite.testSetLogicalSwitchPortSecurity() +} + +func (suite *OvnClientTestSuite) Test_SetSetLogicalSwitchPortExternalIds() { + suite.testSetSetLogicalSwitchPortExternalIds() +} + +func (suite *OvnClientTestSuite) Test_SetLogicalSwitchPortSecurityGroup() { + suite.testSetLogicalSwitchPortSecurityGroup() +} + +func (suite *OvnClientTestSuite) Test_SetLogicalSwitchPortsSecurityGroup() { + suite.testSetLogicalSwitchPortsSecurityGroup() +} + +func (suite *OvnClientTestSuite) Test_EnablePortLayer2forward() { + suite.testEnablePortLayer2forward() +} + +func (suite *OvnClientTestSuite) Test_SetLogicalSwitchPortVlanTag() { + suite.testSetLogicalSwitchPortVlanTag() +} + +func (suite *OvnClientTestSuite) Test_UpdateLogicalSwitchPort() { + suite.testUpdateLogicalSwitchPort() +} + +func (suite *OvnClientTestSuite) Test_getLogicalSwitchPortSgs() { + suite.testgetLogicalSwitchPortSgs() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalSwitchPort() { + suite.testDeleteLogicalSwitchPort() +} + +func (suite *OvnClientTestSuite) Test_ListLogicalSwitchPorts() { + suite.testListLogicalSwitchPorts() +} + +func (suite *OvnClientTestSuite) Test_CreateLogicalSwitchPortOp() { + suite.testCreateLogicalSwitchPortOp() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalSwitchPortOp() { + suite.testDeleteLogicalSwitchPortOp() +} + +func (suite *OvnClientTestSuite) Test_logicalSwitchPortFilter() { + suite.testlogicalSwitchPortFilter() +} + +/* logical_router unit test */ +func (suite *OvnClientTestSuite) Test_CreateLogicalRouter() { + suite.testCreateLogicalRouter() +} + +func (suite *OvnClientTestSuite) Test_UpdateLogicalRouter() { + suite.testUpdateLogicalRouter() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalRouter() { + suite.testDeleteLogicalRouter() +} + +func (suite *OvnClientTestSuite) Test_GetLogicalRouter() { + suite.testGetLogicalRouter() +} + +func (suite *OvnClientTestSuite) Test_ListLogicalRouter() { + suite.testListLogicalRouter() +} + +func (suite *OvnClientTestSuite) Test_LogicalRouterUpdatePortOp() { + suite.testLogicalRouterUpdatePortOp() +} + +func (suite *OvnClientTestSuite) Test_LogicalRouterUpdatePolicyOp() { + suite.testLogicalRouterUpdatePolicyOp() +} + +func (suite *OvnClientTestSuite) Test_LogicalRouterUpdateNatOp() { + suite.testLogicalRouterUpdateNatOp() +} + +func (suite *OvnClientTestSuite) Test_LogicalRouterUpdateStaticRouteOp() { + suite.testLogicalRouterUpdateStaticRouteOp() +} + +func (suite *OvnClientTestSuite) Test_LogicalRouterOp() { + suite.testLogicalRouterOp() +} + +/* logical_router_port unit test */ +func (suite *OvnClientTestSuite) Test_CreatePeerRouterPort() { + suite.testCreatePeerRouterPort() +} + +func (suite *OvnClientTestSuite) Test_UpdateLogicalRouterPortRA() { + suite.testUpdateLogicalRouterPortRA() +} + +func (suite *OvnClientTestSuite) Test_CreateLogicalRouterPort() { + suite.testCreateLogicalRouterPort() +} + +func (suite *OvnClientTestSuite) Test_UpdateLogicalRouterPort() { + suite.testUpdateLogicalRouterPort() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalRouterPorts() { + suite.testDeleteLogicalRouterPorts() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalRouterPort() { + suite.testDeleteLogicalRouterPort() +} + +func (suite *OvnClientTestSuite) Test_CreateLogicalRouterPortOp() { + suite.testCreateLogicalRouterPortOp() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalRouterPortOp() { + suite.testDeleteLogicalRouterPortOp() +} + +func (suite *OvnClientTestSuite) Test_LogicalRouterPortOp() { + suite.testLogicalRouterPortOp() +} + +func (suite *OvnClientTestSuite) Test_logicalRouterPortFilter() { + suite.testlogicalRouterPortFilter() +} + +/* gateway_chassis unit test */ +func (suite *OvnClientTestSuite) Test_CreateGatewayChassises() { + suite.testCreateGatewayChassises() +} + +func (suite *OvnClientTestSuite) Test_DeleteGatewayChassises() { + suite.testDeleteGatewayChassises() +} + +func (suite *OvnClientTestSuite) Test_DeleteGatewayChassisOp() { + suite.testDeleteGatewayChassisOp() +} + +/* load_balancer unit test */ +func (suite *OvnClientTestSuite) Test_CreateLoadBalancer() { + suite.testCreateLoadBalancer() +} + +func (suite *OvnClientTestSuite) Test_UpdateLoadBalancer() { + suite.testUpdateLoadBalancer() +} + +func (suite *OvnClientTestSuite) Test_DeleteLoadBalancers() { + suite.testDeleteLoadBalancers() +} + +func (suite *OvnClientTestSuite) Test_DeleteLoadBalancer() { + suite.testDeleteLoadBalancer() +} + +func (suite *OvnClientTestSuite) Test_LoadBalancerDeleteVips() { + suite.testLoadBalancerDeleteVips() +} + +func (suite *OvnClientTestSuite) Test_GetLoadBalancer() { + suite.testGetLoadBalancer() +} + +func (suite *OvnClientTestSuite) Test_ListLoadBalancers() { + suite.testListLoadBalancers() +} + +func (suite *OvnClientTestSuite) Test_LoadBalancerAddVips() { + suite.testLoadBalancerAddVips() +} + +func (suite *OvnClientTestSuite) Test_DeleteLoadBalancerOp() { + suite.testDeleteLoadBalancerOp() +} + +/* port_group unit test */ +func (suite *OvnClientTestSuite) Test_CreatePortGroup() { + suite.testCreatePortGroup() +} + +func (suite *OvnClientTestSuite) Test_PortGroupResetPorts() { + suite.testPortGroupResetPorts() +} + +func (suite *OvnClientTestSuite) Test_PortGroupUpdatePorts() { + suite.testPortGroupUpdatePorts() +} + +func (suite *OvnClientTestSuite) Test_DeletePortGroup() { + suite.testDeletePortGroup() +} + +func (suite *OvnClientTestSuite) Test_GetGetPortGroup() { + suite.testGetGetPortGroup() +} + +func (suite *OvnClientTestSuite) Test_ListPortGroups() { + suite.testListPortGroups() +} + +func (suite *OvnClientTestSuite) Test_portGroupUpdatePortOp() { + suite.test_portGroupUpdatePortOp() +} + +func (suite *OvnClientTestSuite) Test_portGroupUpdateAclOp() { + suite.test_portGroupUpdateAclOp() +} + +func (suite *OvnClientTestSuite) Test_portGroupOp() { + suite.test_portGroupOp() +} + +/* address_set unit test */ +func (suite *OvnClientTestSuite) Test_CreateAddressSet() { + suite.testCreateAddressSet() +} + +func (suite *OvnClientTestSuite) Test_AddressSetUpdateAddress() { + suite.testAddressSetUpdateAddress() +} + +func (suite *OvnClientTestSuite) Test_DeleteAddressSet() { + suite.testDeleteAddressSet() +} + +func (suite *OvnClientTestSuite) Test_DeleteAddressSets() { + suite.testDeleteAddressSets() +} + +func (suite *OvnClientTestSuite) Test_ListAddressSets() { + suite.testListAddressSets() +} + +func (suite *OvnClientTestSuite) Test_addressSetFilter() { + suite.test_addressSetFilter() +} + +/* acl unit test */ +func (suite *OvnClientTestSuite) Test_CreateIngressAcl() { + suite.testCreateIngressAcl() +} + +func (suite *OvnClientTestSuite) Test_CreateEgressAcl() { + suite.testCreateEgressAcl() +} + +func (suite *OvnClientTestSuite) Test_CreateGatewayAcl() { + suite.testCreateGatewayAcl() +} + +func (suite *OvnClientTestSuite) Test_CreateNodeAcl() { + suite.testCreateNodeAcl() +} + +func (suite *OvnClientTestSuite) Test_CreateSgDenyAllAcl() { + suite.testCreateSgDenyAllAcl() +} + +func (suite *OvnClientTestSuite) Test_UpdateSgAcl() { + suite.testUpdateSgAcl() +} + +func (suite *OvnClientTestSuite) Test_UpdateLogicalSwitchAcl() { + suite.testUpdateLogicalSwitchAcl() +} + +func (suite *OvnClientTestSuite) Test_SetAclLog() { + suite.testSetAclLog() +} + +func (suite *OvnClientTestSuite) Test_SetLogicalSwitchPrivate() { + suite.testSetLogicalSwitchPrivate() +} + +func (suite *OvnClientTestSuite) Test_newSgRuleACL() { + suite.test_newSgRuleACL() +} + +func (suite *OvnClientTestSuite) Test_CreateAcls() { + suite.testCreateAcls() +} + +func (suite *OvnClientTestSuite) Test_DeleteAcls() { + suite.testDeleteAcls() +} + +func (suite *OvnClientTestSuite) Test_GetAcl() { + suite.testGetAcl() +} + +func (suite *OvnClientTestSuite) Test_ListAcls() { + suite.testListAcls() +} + +func (suite *OvnClientTestSuite) Test_newAcl() { + suite.test_newAcl() +} + +func (suite *OvnClientTestSuite) Test_newNetworkPolicyAclMatch() { + suite.testnewNetworkPolicyAclMatch() +} + +func (suite *OvnClientTestSuite) Test_aclFilter() { + suite.test_aclFilter() +} + +/* logical_router_policy unit test */ +func (suite *OvnClientTestSuite) Test_AddLogicalRouterPolicy() { + suite.testAddLogicalRouterPolicy() +} + +func (suite *OvnClientTestSuite) Test_CreateLogicalRouterPolicies() { + suite.testCreateLogicalRouterPolicies() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalRouterPolicy() { + suite.testDeleteLogicalRouterPolicy() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalRouterPolicies() { + suite.testDeleteLogicalRouterPolicies() +} + +func (suite *OvnClientTestSuite) Test_ClearLogicalRouterPolicy() { + suite.testClearLogicalRouterPolicy() +} + +func (suite *OvnClientTestSuite) Test_GetLogicalRouterPolicy() { + suite.testGetLogicalRouterPolicy() +} + +func (suite *OvnClientTestSuite) Test_newLogicalRouterPolicy() { + suite.test_newLogicalRouterPolicy() +} + +func (suite *OvnClientTestSuite) Test_policyFilter() { + suite.test_policyFilter() +} + +/* nat unit test */ +func (suite *OvnClientTestSuite) Test_CreateNats() { + suite.testCreateNats() +} + +func (suite *OvnClientTestSuite) Test_UpdateSnat() { + suite.testUpdateSnat() +} + +func (suite *OvnClientTestSuite) Test_UpdateDnatAndSnat() { + suite.testUpdateDnatAndSnat() +} + +func (suite *OvnClientTestSuite) Test_DeleteNats() { + suite.testDeleteNats() +} + +func (suite *OvnClientTestSuite) Test_DeleteNat() { + suite.testDeleteNat() +} + +func (suite *OvnClientTestSuite) Test_GetNat() { + suite.testGetNat() +} + +func (suite *OvnClientTestSuite) Test_newNat() { + suite.test_newNat() +} + +func (suite *OvnClientTestSuite) Test_natFilter() { + suite.test_natFilter() +} + +/* logical_router_static_route unit test */ +func (suite *OvnClientTestSuite) Test_CreateLogicalRouterStaticRoutes() { + suite.testCreateLogicalRouterStaticRoutes() +} + +func (suite *OvnClientTestSuite) Test_AddLogicalRouterStaticRoute() { + suite.testAddLogicalRouterStaticRoute() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalRouterStaticRoute() { + suite.testDeleteLogicalRouterStaticRoute() +} + +func (suite *OvnClientTestSuite) Test_ClearLogicalRouterStaticRoute() { + suite.testClearLogicalRouterStaticRoute() +} + +func (suite *OvnClientTestSuite) Test_GetLogicalRouterStaticRoute() { + suite.testGetLogicalRouterStaticRoute() +} + +func (suite *OvnClientTestSuite) Test_ListLogicalRouterStaticRoutes() { + suite.testListLogicalRouterStaticRoutes() +} + +func (suite *OvnClientTestSuite) Test_newLogicalRouterStaticRoute() { + suite.test_newLogicalRouterStaticRoute() +} + +/* dhcp options unit test */ +func (suite *OvnClientTestSuite) Test_UpdateDHCPOptions() { + suite.testUpdateDHCPOptions() +} + +func (suite *OvnClientTestSuite) Test_updateDHCPv4Options() { + suite.test_updateDHCPv4Options() +} + +func (suite *OvnClientTestSuite) Test_updateDHCPv6Options() { + suite.test_updateDHCPv6Options() +} + +func (suite *OvnClientTestSuite) Test_DeleteDHCPOptionsByUUIDs() { + suite.testDeleteDHCPOptionsByUUIDs() +} + +func (suite *OvnClientTestSuite) Test_DeleteDHCPOptions() { + suite.testDeleteDHCPOptions() +} + +func (suite *OvnClientTestSuite) Test_GetDHCPOptions() { + suite.testGetDHCPOptions() +} + +func (suite *OvnClientTestSuite) Test_ListDHCPOptions() { + suite.testListDHCPOptions() +} + +func (suite *OvnClientTestSuite) Test_dhcpOptionsFilter() { + suite.test_dhcpOptionsFilter() +} + +/* mixed operations unit test */ +func (suite *OvnClientTestSuite) Test_CreateGatewayLogicalSwitch() { + suite.testCreateGatewayLogicalSwitch() +} + +func (suite *OvnClientTestSuite) Test_CreateLogicalPatchPort() { + suite.testCreateLogicalPatchPort() +} + +func (suite *OvnClientTestSuite) Test_RemoveRouterPort() { + suite.testRemoveRouterPort() +} + +func (suite *OvnClientTestSuite) Test_DeleteLogicalGatewaySwitch() { + suite.testDeleteLogicalGatewaySwitch() +} + +func (suite *OvnClientTestSuite) Test_DeleteSecurityGroup() { + suite.testDeleteSecurityGroup() +} + +func (suite *OvnClientTestSuite) Test_GetEntityInfo() { + suite.testGetEntityInfo() +} + +func Test_scratch(t *testing.T) { + t.SkipNow() + endpoint := "tcp:[172.20.149.35]:6641" + ovnClient, err := newOvnClient(t, endpoint, 10, "") + require.NoError(t, err) + + lbName := "test-lb" + err = ovnClient.CreateLoadBalancer(lbName, "tcp", "ip_src") + require.NoError(t, err) + + vips := map[string]string{ + "10.96.0.1:443": "192.168.20.11:6443", + "10.107.43.237:8080": "10.244.0.100:8080,10.244.0.16:8080,10.244.0.17:8080", + "[fd00:10:96::e82f]:8080": "[fc00::af4:f]:8080,[fc00::af4:10]:8080,[fc00::af4:11]:8080", + } + + err = ovnClient.LoadBalancerAddVips(lbName, vips) + require.NoError(t, err) + + err = ovnClient.LoadBalancerDeleteVips(lbName, map[string]struct{}{ + "10.96.0.1:443": {}, + }) + require.NoError(t, err) +} + +func newOVSDBServer(t *testing.T, dbModel model.ClientDBModel, schema ovsdb.DatabaseSchema) (*server.OvsdbServer, string) { + serverDBModel, err := serverdb.FullDatabaseModel() + require.NoError(t, err) + serverSchema := serverdb.Schema() + + db := database.NewInMemoryDatabase(map[string]model.ClientDBModel{ + schema.Name: dbModel, + serverSchema.Name: serverDBModel, + }) + + dbMod, errs := model.NewDatabaseModel(schema, dbModel) + require.Empty(t, errs) + + svrMod, errs := model.NewDatabaseModel(serverSchema, serverDBModel) + require.Empty(t, errs) + + server, err := server.NewOvsdbServer(db, dbMod, svrMod) + require.NoError(t, err) + + tmpfile := fmt.Sprintf("/tmp/ovsdb-%d.sock", rand.Intn(10000)) + t.Cleanup(func() { + os.Remove(tmpfile) + }) + go func() { + if err := server.Serve("unix", tmpfile); err != nil { + t.Error(err) + } + }() + t.Cleanup(server.Close) + require.Eventually(t, func() bool { + return server.Ready() + }, 1*time.Second, 10*time.Millisecond) + + return server, tmpfile +} + +func newOvnClient(t *testing.T, ovnNbAddr string, ovnNbTimeout int, nodeSwitchCIDR string) (*ovnClient, error) { + nbClient, err := newNbClient(ovnNbAddr, ovnNbTimeout) + require.NoError(t, err) + + return &ovnClient{ + ovnNbClient: ovnNbClient{ + Client: nbClient, + Timeout: time.Duration(ovnNbTimeout) * time.Second, + }, + NodeSwitchCIDR: nodeSwitchCIDR, + }, nil +} + +func newNbClient(addr string, timeout int) (client.Client, error) { + dbModel, err := ovnnb.FullDatabaseModel() + if err != nil { + return nil, err + } + + logger := stdr.New(log.New(os.Stderr, "", log.LstdFlags)). + WithName("libovsdb"). + WithValues("database", dbModel.Name()) + stdr.SetVerbosity(1) + + options := []client.Option{ + client.WithReconnect(time.Duration(timeout)*time.Second, &backoff.ZeroBackOff{}), + client.WithLeaderOnly(false), + client.WithLogger(&logger), + } + + for _, ep := range strings.Split(addr, ",") { + options = append(options, client.WithEndpoint(ep)) + } + + c, err := client.NewOVSDBClient(dbModel, options...) + if err != nil { + return nil, err + } + + if err = c.Connect(context.TODO()); err != nil { + return nil, err + } + + monitorOpts := []client.MonitorOption{ + client.WithTable(&ovnnb.LogicalRouter{}), + client.WithTable(&ovnnb.LogicalRouterPort{}), + client.WithTable(&ovnnb.LogicalRouterPolicy{}), + client.WithTable(&ovnnb.LogicalRouterStaticRoute{}), + client.WithTable(&ovnnb.NAT{}), + client.WithTable(&ovnnb.LogicalSwitch{}), + client.WithTable(&ovnnb.LogicalSwitchPort{}), + client.WithTable(&ovnnb.PortGroup{}), + client.WithTable(&ovnnb.NBGlobal{}), + client.WithTable(&ovnnb.GatewayChassis{}), + client.WithTable(&ovnnb.LoadBalancer{}), + client.WithTable(&ovnnb.AddressSet{}), + client.WithTable(&ovnnb.ACL{}), + client.WithTable(&ovnnb.DHCPOptions{}), + } + if _, err = c.Monitor(context.TODO(), c.NewMonitor(monitorOpts...)); err != nil { + return nil, err + } + + return c, nil +} diff --git a/pkg/ovs/ovn-nb.go b/pkg/ovs/ovn-nb.go new file mode 100644 index 00000000000..77e6c1758ce --- /dev/null +++ b/pkg/ovs/ovn-nb.go @@ -0,0 +1,218 @@ +package ovs + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/ovn-org/libovsdb/ovsdb" + + ovsclient "github.com/kubeovn/kube-ovn/pkg/ovsdb/client" + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +const ( + logicalRouterKey = "lr" + logicalSwitchKey = "ls" + portGroupKey = "pg" + aclParentKey = "parent" + associatedSgKeyPrefix = "associated_sg_" + sgsKey = "security_groups" + sgKey = "sg" +) + +// CreateGatewayLogicalSwitch create gateway switch connect external networks +func (c *ovnClient) CreateGatewayLogicalSwitch(lsName, lrName, provider, ip, mac string, vlanID int, chassises ...string) error { + lspName := fmt.Sprintf("%s-%s", lsName, lrName) + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + localnetLspName := fmt.Sprintf("ln-%s", lsName) + + if err := c.CreateBareLogicalSwitch(lsName); err != nil { + return fmt.Errorf("create logical switch %s: %v", lsName, err) + } + + if err := c.CreateLocalnetLogicalSwitchPort(lsName, localnetLspName, provider, vlanID); err != nil { + return fmt.Errorf("create localnet logical switch port %s: %v", localnetLspName, err) + } + + if err := c.CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, ip, mac, chassises...); err != nil { + return err + } + + return nil +} + +// CreateLogicalPatchPort create logical router port and associated logical switch port which type is router +func (c *ovnClient) CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, ip, mac string, chassises ...string) error { + if len(ip) != 0 { + // check ip format: 192.168.231.1/24,fc00::0af4:01/112 + if err := util.CheckCidrs(ip); err != nil { + return err + } + } + + /* create router port */ + ops, err := c.CreateRouterPortOp(lsName, lrName, lspName, lrpName, ip, mac) + if err != nil { + return fmt.Errorf("generate operations for creating patch port: %v", err) + } + + if err = c.Transact("lrp-lsp-add", ops); err != nil { + return fmt.Errorf("create logical patch port %s and %s: %v", lspName, lrpName, err) + } + + /* create gateway chassises for logical router port */ + if err = c.CreateGatewayChassises(lrpName, chassises...); err != nil { + return err + } + + return nil +} + +// DeleteLogicalGatewaySwitch delete gateway switch and corresponding port +func (c *ovnClient) DeleteLogicalGatewaySwitch(lsName, lrName string) error { + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + + // all corresponding logical switch port(e.g. localnet port and normal port) will be deleted when delete logical switch + lsDelOp, err := c.DeleteLogicalSwitchOp(lsName) + if err != nil { + return fmt.Errorf("generate operations for deleting gateway switch %s: %v", lsName, err) + } + + lrpDelOp, err := c.DeleteLogicalRouterPortOp(lrpName) + if err != nil { + return fmt.Errorf("generate operations for deleting gateway router port %s: %v", lrpName, err) + } + + ops := make([]ovsdb.Operation, 0, len(lsDelOp)+len(lrpDelOp)) + ops = append(ops, lsDelOp...) + ops = append(ops, lrpDelOp...) + + if err = c.Transact("gw-ls-del", ops); err != nil { + return fmt.Errorf("delete gateway switch %s: %v", lsName, err) + } + + return nil +} + +func (c *ovnClient) DeleteSecurityGroup(sgName string) error { + pgName := GetSgPortGroupName(sgName) + + // clear acl + if err := c.DeleteAcls(pgName, portGroupKey, ""); err != nil { + return fmt.Errorf("delete acls from port group %s: %v", pgName, err) + } + + // clear address_set + if err := c.DeleteAddressSets(map[string]string{sgKey: sgName}); err != nil { + return err + } + + if sgName == util.DefaultSecurityGroupName { + if err := c.SetLogicalSwitchPortsSecurityGroup(sgName, "remove"); err != nil { + return fmt.Errorf("clear default security group %s from logical switch ports: %v", sgName, err) + } + } + + // delete pg + if err := c.DeletePortGroup(pgName); err != nil { + return err + } + + return nil +} + +func (c *ovnClient) CreateRouterPortOp(lsName, lrName, lspName, lrpName, ip, mac string) ([]ovsdb.Operation, error) { + /* do nothing if logical switch port exist */ + lspExist, err := c.LogicalSwitchPortExists(lspName) + if err != nil { + return nil, err + } + + // lsp or lrp must all exist or not because of ovsdb ACID transcation + if lspExist { + return nil, nil + } + + /* create logical switch port */ + lsp := &ovnnb.LogicalSwitchPort{ + UUID: ovsclient.NamedUUID(), + Name: lspName, + Addresses: []string{"router"}, + Type: "router", + Options: map[string]string{ + "router-port": lrpName, + }, + } + + lspCreateOp, err := c.CreateLogicalSwitchPortOp(lsp, lsName) + if err != nil { + return nil, err + } + + /* create logical router port */ + lrp := &ovnnb.LogicalRouterPort{ + UUID: ovsclient.NamedUUID(), + Name: lrpName, + Networks: strings.Split(ip, ","), + MAC: mac, + } + + lrpCreateOp, err := c.CreateLogicalRouterPortOp(lrp, lrName) + if err != nil { + return nil, err + } + + ops := make([]ovsdb.Operation, 0, len(lspCreateOp)+len(lrpCreateOp)) + ops = append(ops, lspCreateOp...) + ops = append(ops, lrpCreateOp...) + + return ops, nil +} + +// RemoveLogicalPatchPort delete logical router port and associated logical switch port which type is router +func (c *ovnClient) RemoveLogicalPatchPort(lspName, lrpName string) error { + /* delete logical switch port*/ + lspDelOp, err := c.DeleteLogicalSwitchPortOp(lspName) + if err != nil { + return err + } + + /* delete logical router port*/ + lrpDelOp, err := c.DeleteLogicalRouterPortOp(lrpName) + if err != nil { + return err + } + + ops := make([]ovsdb.Operation, 0, len(lspDelOp)+len(lrpDelOp)) + ops = append(ops, lspDelOp...) + ops = append(ops, lrpDelOp...) + + if err = c.Transact("lrp-lsp-del", ops); err != nil { + return fmt.Errorf("delete logical switch port %s and delete logical router port %s: %v", lspName, lrpName, err) + } + + return nil +} + +// GetEntityInfo get entity info by column which is the index, +// reference to ovn-nb.ovsschema(ovsdb-client get-schema unix:/var/run/ovn/ovnnb_db.sock OVN_Northbound) for more information, +// UUID is index +func (c *ovnClient) GetEntityInfo(entity interface{}) error { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + entityPtr := reflect.ValueOf(entity) + if entityPtr.Kind() != reflect.Pointer { + return fmt.Errorf("entity must be pointer") + } + + err := c.Get(ctx, entity) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/ovs/ovn-nb_global.go b/pkg/ovs/ovn-nb_global.go new file mode 100644 index 00000000000..7a3a65ac7c5 --- /dev/null +++ b/pkg/ovs/ovn-nb_global.go @@ -0,0 +1,164 @@ +package ovs + +import ( + "context" + "fmt" + "strconv" + "strings" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +func (c *ovnClient) CreateNbGlobal(nbGlobal *ovnnb.NBGlobal) error { + op, err := c.ovnNbClient.Create(nbGlobal) + if err != nil { + return fmt.Errorf("generate operations for creating nb global: %v", err) + } + + return c.Transact("nb-global-create", op) +} + +func (c *ovnClient) DeleteNbGlobal() error { + nbGlobal, err := c.GetNbGlobal() + if err != nil { + return err + } + + op, err := c.Where(nbGlobal).Delete() + if err != nil { + return err + } + + return c.Transact("nb-global-delete", op) +} + +func (c *ovnClient) GetNbGlobal() (*ovnnb.NBGlobal, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + nbGlobalList := make([]ovnnb.NBGlobal, 0, 1) + + // there is only one nb_global in OVN_Northbound, so return true and it will work + err := c.WhereCache(func(config *ovnnb.NBGlobal) bool { + return true + }).List(ctx, &nbGlobalList) + + if err != nil { + return nil, fmt.Errorf("list nbGlobal: %v", err) + } + + if len(nbGlobalList) == 0 { + return nil, fmt.Errorf("not found nb_global") + } + + return &nbGlobalList[0], nil +} + +func (c *ovnClient) UpdateNbGlobal(nbGlobal *ovnnb.NBGlobal, fields ...interface{}) error { + /* // list nb_global which connections != nil + op, err := c.Where(nbGlobal, model.Condition{ + Field: &nbGlobal.Connections, + Function: ovsdb.ConditionNotEqual, + Value: []string{""}, + }).Update(nbGlobal) */ + + op, err := c.Where(nbGlobal).Update(nbGlobal, fields...) + if err != nil { + return fmt.Errorf("generate operations for updating nb global: %v", err) + } + + if err := c.Transact("nb-global-update", op); err != nil { + return fmt.Errorf("update nb global: %v", err) + } + + return nil +} + +func (c *ovnClient) SetAzName(azName string) error { + nbGlobal, err := c.GetNbGlobal() + if err != nil { + return fmt.Errorf("get nb global: %v", err) + } + + if azName == nbGlobal.Name { + return nil // no need to update + } + + nbGlobal.Name = azName + + if err := c.UpdateNbGlobal(nbGlobal, &nbGlobal.Name); err != nil { + return fmt.Errorf("set nb_global az name %s: %v", azName, err) + } + + return nil +} + +func (c *ovnClient) SetUseCtInvMatch() error { + nbGlobal, err := c.GetNbGlobal() + if err != nil { + return fmt.Errorf("get nb global: %v", err) + } + + nbGlobal.Options["use_ct_inv_match"] = "false" + + if err := c.UpdateNbGlobal(nbGlobal, &nbGlobal.Options); err != nil { + return fmt.Errorf("set use_ct_inv_match to false, %v", err) + } + + return nil +} + +func (c *ovnClient) SetICAutoRoute(enable bool, blackList []string) error { + nbGlobal, err := c.GetNbGlobal() + if err != nil { + return fmt.Errorf("get nb global: %v", err) + } + + if enable { + nbGlobal.Options = map[string]string{ + "ic-route-adv": "true", + "ic-route-learn": "true", + "ic-route-blacklist": strings.Join(blackList, ","), + } + } else { + nbGlobal.Options = map[string]string{ + "ic-route-adv": "false", + "ic-route-learn": "false", + } + } + + if err := c.UpdateNbGlobal(nbGlobal, &nbGlobal.Options); err != nil { + return fmt.Errorf("enable ovn-ic auto route, %v", err) + } + return nil +} + +func (c *ovnClient) SetLBCIDR(serviceCIDR string) error { + nbGlobal, err := c.GetNbGlobal() + if err != nil { + return fmt.Errorf("get nb global: %v", err) + } + + nbGlobal.Options["svc_ipv4_cidr"] = serviceCIDR + + if err := c.UpdateNbGlobal(nbGlobal, &nbGlobal.Options); err != nil { + return fmt.Errorf("set svc cidr %s for lb, %v", serviceCIDR, err) + } + + return nil +} + +func (c *ovnClient) SetLsDnatModDlDst(enabled bool) error { + nbGlobal, err := c.GetNbGlobal() + if err != nil { + return fmt.Errorf("get nb global: %v", err) + } + + nbGlobal.Options["ls_dnat_mod_dl_dst"] = strconv.FormatBool(enabled) + + if err := c.UpdateNbGlobal(nbGlobal, &nbGlobal.Options); err != nil { + return fmt.Errorf("set NB_Global option ls_dnat_mod_dl_dst to %v: %v", enabled, err) + } + + return nil +} diff --git a/pkg/ovs/ovn-nb_global_test.go b/pkg/ovs/ovn-nb_global_test.go new file mode 100644 index 00000000000..4d2dda86bd9 --- /dev/null +++ b/pkg/ovs/ovn-nb_global_test.go @@ -0,0 +1,223 @@ +package ovs + +import ( + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" +) + +func mockNBGlobal() *ovnnb.NBGlobal { + return &ovnnb.NBGlobal{ + Connections: []string{"c7744628-6399-4852-8ac0-06e4e436c146"}, + NbCfg: 100, + Options: map[string]string{ + "mac_prefix": "11:22:33", + "max_tunid": "16711680", + }, + } +} + +func (suite *OvnClientTestSuite) testGetNbGlobal() { + t := suite.T() + + ovnClient := suite.ovnClient + + t.Cleanup(func() { + err := ovnClient.DeleteNbGlobal() + require.NoError(t, err) + + _, err = ovnClient.GetNbGlobal() + require.ErrorContains(t, err, "not found nb_global") + }) + + t.Run("return err when not found nb_global", func(t *testing.T) { + _, err := ovnClient.GetNbGlobal() + require.ErrorContains(t, err, "not found nb_global") + }) + + t.Run("no err when found nb_global", func(t *testing.T) { + nbGlobal := mockNBGlobal() + err := ovnClient.CreateNbGlobal(nbGlobal) + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.NotEmpty(t, out.UUID) + }) +} + +func (suite *OvnClientTestSuite) testUpdateNbGlobal() { + t := suite.T() + + ovnClient := suite.ovnClient + + t.Cleanup(func() { + err := ovnClient.DeleteNbGlobal() + require.NoError(t, err) + }) + + nbGlobal := mockNBGlobal() + err := ovnClient.CreateNbGlobal(nbGlobal) + require.NoError(t, err) + + nbGlobal, err = ovnClient.GetNbGlobal() + require.NoError(t, err) + + t.Run("normal update", func(t *testing.T) { + nbGlobal.Options = map[string]string{ + "mac_prefix": "11:22:aa", + "max_tunid": "16711680", + } + + err = ovnClient.UpdateNbGlobal(nbGlobal) + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.Equal(t, "11:22:aa", out.Options["mac_prefix"]) + require.Equal(t, "16711680", out.Options["max_tunid"]) + }) + + t.Run("create options", func(t *testing.T) { + nbGlobal := &ovnnb.NBGlobal{ + UUID: nbGlobal.UUID, + } + + err = ovnClient.UpdateNbGlobal(nbGlobal, &nbGlobal.Name, &nbGlobal.Options) + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.Empty(t, out.Name) + require.Empty(t, out.Options) + }) +} + +func (suite *OvnClientTestSuite) testSetAzName() { + t := suite.T() + + ovnClient := suite.ovnClient + + t.Cleanup(func() { + err := ovnClient.DeleteNbGlobal() + require.NoError(t, err) + + _, err = ovnClient.GetNbGlobal() + require.ErrorContains(t, err, "not found nb_global") + }) + + nbGlobal := mockNBGlobal() + err := ovnClient.CreateNbGlobal(nbGlobal) + require.NoError(t, err) + + t.Run("set az name", func(t *testing.T) { + err = ovnClient.SetAzName("test-az") + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.Equal(t, "test-az", out.Name) + }) + + t.Run("clear az name", func(t *testing.T) { + err = ovnClient.SetAzName("") + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.Empty(t, out.Name) + }) +} + +func (suite *OvnClientTestSuite) testSetICAutoRoute() { + t := suite.T() + + ovnClient := suite.ovnClient + + t.Cleanup(func() { + err := ovnClient.DeleteNbGlobal() + require.NoError(t, err) + + _, err = ovnClient.GetNbGlobal() + require.ErrorContains(t, err, "not found nb_global") + }) + + nbGlobal := mockNBGlobal() + err := ovnClient.CreateNbGlobal(nbGlobal) + require.NoError(t, err) + + t.Run("enable ovn-ic auto route", func(t *testing.T) { + err = ovnClient.SetICAutoRoute(true, []string{"1.1.1.1", "2.2.2.2"}) + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.Equal(t, "true", out.Options["ic-route-adv"]) + require.Equal(t, "true", out.Options["ic-route-learn"]) + require.Equal(t, "1.1.1.1,2.2.2.2", out.Options["ic-route-blacklist"]) + }) + + t.Run("disable ovn-ic auto route", func(t *testing.T) { + err = ovnClient.SetICAutoRoute(false, []string{"1.1.1.1", "2.2.2.2"}) + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.Equal(t, "false", out.Options["ic-route-adv"]) + require.Equal(t, "false", out.Options["ic-route-learn"]) + require.Empty(t, out.Options["ic-route-blacklist"]) + }) +} + +func (suite *OvnClientTestSuite) testSetUseCtInvMatch() { + t := suite.T() + + ovnClient := suite.ovnClient + + t.Cleanup(func() { + err := ovnClient.DeleteNbGlobal() + require.NoError(t, err) + + _, err = ovnClient.GetNbGlobal() + require.ErrorContains(t, err, "not found nb_global") + }) + + nbGlobal := mockNBGlobal() + err := ovnClient.CreateNbGlobal(nbGlobal) + require.NoError(t, err) + + err = ovnClient.SetUseCtInvMatch() + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.Equal(t, "false", out.Options["use_ct_inv_match"]) +} + +func (suite *OvnClientTestSuite) testSetLBCIDR() { + t := suite.T() + + ovnClient := suite.ovnClient + serviceCIDR := "10.96.0.0/12" + + t.Cleanup(func() { + err := ovnClient.DeleteNbGlobal() + require.NoError(t, err) + + _, err = ovnClient.GetNbGlobal() + require.ErrorContains(t, err, "not found nb_global") + }) + + nbGlobal := mockNBGlobal() + err := ovnClient.CreateNbGlobal(nbGlobal) + require.NoError(t, err) + + err = ovnClient.SetLBCIDR(serviceCIDR) + require.NoError(t, err) + + out, err := ovnClient.GetNbGlobal() + require.NoError(t, err) + require.Equal(t, serviceCIDR, out.Options["svc_ipv4_cidr"]) +} diff --git a/pkg/ovs/ovn-nb_test.go b/pkg/ovs/ovn-nb_test.go new file mode 100644 index 00000000000..c4c54e6e886 --- /dev/null +++ b/pkg/ovs/ovn-nb_test.go @@ -0,0 +1,280 @@ +package ovs + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func (suite *OvnClientTestSuite) testCreateGatewayLogicalSwitch() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-create-gw-ls" + lrName := "test-create-gw-lr" + lspName := fmt.Sprintf("%s-%s", lsName, lrName) + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + localnetLspName := fmt.Sprintf("ln-%s", lsName) + chassises := []string{"5de32fcb-495a-40df-919e-f09812c4d11e", "25310674-65ce-69fd-bcfa-65b25268926b"} + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateGatewayLogicalSwitch(lsName, lrName, "test-external", "192.168.230.1/24,fc00::0af4:01/112", util.GenerateMac(), 210, chassises...) + require.NoError(t, err) + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + lr, err := ovnClient.GetLogicalRouter(lrName, false) + require.NoError(t, err) + + localnetLsp, err := ovnClient.GetLogicalSwitchPort(localnetLspName, false) + require.NoError(t, err) + require.Equal(t, "localnet", localnetLsp.Type) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Contains(t, lr.Ports, lrp.UUID) + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + require.Contains(t, ls.Ports, lsp.UUID) +} + +func (suite *OvnClientTestSuite) testCreateLogicalPatchPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-create-router-ls" + lrName := "test-create-router-lr" + lspName := fmt.Sprintf("%s-%s", lsName, lrName) + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + chassises := []string{"5de32fcb-495a-40df-919e-f09812c4dffe", "25310674-65ce-41fd-bcfa-65b25268926b"} + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("create router port with chassises", func(t *testing.T) { + t.Parallel() + err := ovnClient.CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, "192.168.230.1/24,fc00::0af4:01/112", util.GenerateMac(), chassises...) + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Equal(t, []string{"192.168.230.1/24", "fc00::0af4:01/112"}, lrp.Networks) + + for _, chassisName := range chassises { + gwChassisName := lrpName + "-" + chassisName + gwChassis, err := ovnClient.GetGatewayChassis(gwChassisName, false) + require.NoError(t, err) + require.Contains(t, lrp.GatewayChassis, gwChassis.UUID) + } + }) + + t.Run("create router port with no chassises", func(t *testing.T) { + t.Parallel() + lsName := "test-create-ls-no-chassises" + lrName := "test-create-lr-no-chassises" + lspName := fmt.Sprintf("%s-%s", lsName, lrName) + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + err = ovnClient.CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, "192.168.230.1/24,fc00::0af4:01/112", util.GenerateMac()) + require.NoError(t, err) + + lrp, err := ovnClient.GetLogicalRouterPort(lrpName, false) + require.NoError(t, err) + require.Empty(t, lrp.GatewayChassis) + }) +} + +func (suite *OvnClientTestSuite) testRemoveRouterPort() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-remove-router-type-ls" + lrName := "test-remove-router-type-lr" + lspName := fmt.Sprintf("%s-%s", lsName, lrName) + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + t.Run("normal del router type port", func(t *testing.T) { + err = ovnClient.CreateLogicalPatchPort(lsName, lrName, lspName, lrpName, "192.168.230.1/24,fc00::0af4:01/112", util.GenerateMac()) + require.NoError(t, err) + + err = ovnClient.RemoveLogicalPatchPort(lspName, lrpName) + require.NoError(t, err) + + /* validate logical switch port*/ + _, err = ovnClient.GetLogicalSwitchPort(lspName, false) + require.ErrorContains(t, err, "object not found") + + /* validate logical router port*/ + _, err = ovnClient.GetLogicalRouterPort(lrpName, false) + require.ErrorContains(t, err, "object not found") + }) + + t.Run("should no err normal del router type port repeatedly", func(t *testing.T) { + err = ovnClient.RemoveLogicalPatchPort(lspName, lrpName) + require.NoError(t, err) + }) +} + +func (suite *OvnClientTestSuite) testDeleteLogicalGatewaySwitch() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + lsName := "test-del-gw-ls" + lrName := "test-del-gw-lr" + lrpName := fmt.Sprintf("%s-%s", lrName, lsName) + + err := ovnClient.CreateLogicalRouter(lrName) + require.NoError(t, err) + + err = ovnClient.CreateGatewayLogicalSwitch(lsName, lrName, "test-external", "192.168.230.1/24,fc00::0af4:01/112", util.GenerateMac(), 210) + require.NoError(t, err) + + // localnet port and lsp will be deleted when delete logical switch in real ovsdb, + // it's different from the mock memory ovsdb, + // so no need to check localnet port and lsp existence + err = ovnClient.DeleteLogicalGatewaySwitch(lsName, lrName) + require.NoError(t, err) + + _, err = ovnClient.GetLogicalSwitch(lsName, false) + require.ErrorContains(t, err, "not found logical switch") + + _, err = ovnClient.GetLogicalRouterPort(lrpName, false) + require.ErrorContains(t, err, "object not found") +} + +func (suite *OvnClientTestSuite) testDeleteSecurityGroup() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + sgName := "test_del_sg" + asName := "test_del_sg_as" + pgName := GetSgPortGroupName(sgName) + priority := "5111" + match := "outport == @ovn.sg.test_del_sg && ip" + + /* prepate test */ + err := ovnClient.CreatePortGroup(pgName, map[string]string{ + "type": "security_group", + sgKey: sgName, + }) + require.NoError(t, err) + + acl, err := ovnClient.newAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, ovnnb.ACLActionAllowRelated) + require.NoError(t, err) + + err = ovnClient.CreateAcls(pgName, portGroupKey, acl) + require.NoError(t, err) + + err = ovnClient.CreateAddressSet(asName, map[string]string{ + sgKey: sgName, + }) + require.NoError(t, err) + + /* run test */ + err = ovnClient.DeleteSecurityGroup(sgName) + require.NoError(t, err) + + _, err = ovnClient.GetAcl(pgName, ovnnb.ACLDirectionToLport, priority, match, false) + require.ErrorContains(t, err, "not found acl") + + _, err = ovnClient.GetAddressSet(asName, false) + require.ErrorContains(t, err, "object not found") + + _, err = ovnClient.GetPortGroup(pgName, false) + require.ErrorContains(t, err, "object not found") +} + +func (suite *OvnClientTestSuite) testGetEntityInfo() { + t := suite.T() + t.Parallel() + + ovnClient := suite.ovnClient + + lsName := "test-get-entity-ls" + err := ovnClient.CreateBareLogicalSwitch(lsName) + require.NoError(t, err) + + lspName := "test-get-entity-lsp" + err = ovnClient.CreateBareLogicalSwitchPort(lsName, lspName, "", "") + require.NoError(t, err) + + t.Run("get logical switch by uuid", func(t *testing.T) { + t.Parallel() + + ls, err := ovnClient.GetLogicalSwitch(lsName, false) + require.NoError(t, err) + + newLs := &ovnnb.LogicalSwitch{UUID: ls.UUID} + err = ovnClient.GetEntityInfo(newLs) + require.NoError(t, err) + require.Equal(t, lsName, newLs.Name) + }) + + t.Run("get logical switch by name which is not index", func(t *testing.T) { + t.Parallel() + + ls := &ovnnb.LogicalSwitch{Name: lsName} + err = ovnClient.GetEntityInfo(ls) + require.ErrorContains(t, err, "object not found") + }) + + t.Run("get logical switch port by uuid", func(t *testing.T) { + t.Parallel() + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + + newLsp := &ovnnb.LogicalSwitchPort{UUID: lsp.UUID} + err = ovnClient.GetEntityInfo(newLsp) + require.NoError(t, err) + require.Equal(t, lspName, newLsp.Name) + }) + + t.Run("get logical switch port by name which is index", func(t *testing.T) { + t.Parallel() + + lsp, err := ovnClient.GetLogicalSwitchPort(lspName, false) + require.NoError(t, err) + + newLsp := &ovnnb.LogicalSwitchPort{Name: lspName} + err = ovnClient.GetEntityInfo(newLsp) + require.NoError(t, err) + require.Equal(t, lsp.UUID, newLsp.UUID) + }) + + t.Run("entity is not a pointer", func(t *testing.T) { + t.Parallel() + + newLsp := ovnnb.LogicalSwitchPort{Name: lspName} + err = ovnClient.GetEntityInfo(newLsp) + require.ErrorContains(t, err, "entity must be pointer") + }) +} diff --git a/pkg/ovs/ovn-nbctl-legacy.go b/pkg/ovs/ovn-nbctl-legacy.go index 6920666fb24..a65a7a4f874 100644 --- a/pkg/ovs/ovn-nbctl-legacy.go +++ b/pkg/ovs/ovn-nbctl-legacy.go @@ -2759,11 +2759,6 @@ func (c LegacyClient) CheckPolicyRouteNexthopConsistent(match, nexthop string, p return false, nil } -type DHCPOptionsUUIDs struct { - DHCPv4OptionsUUID string - DHCPv6OptionsUUID string -} - type dhcpOptions struct { UUID string CIDR string diff --git a/pkg/ovs/ovn-nbctl-legacy_test.go b/pkg/ovs/ovn-nbctl-legacy_test.go index 040297180b4..123c9ed8689 100644 --- a/pkg/ovs/ovn-nbctl-legacy_test.go +++ b/pkg/ovs/ovn-nbctl-legacy_test.go @@ -32,6 +32,7 @@ func Test_parseLrRouteListOutput(t *testing.T) { } func Test_parseLrPolicyRouteListOutput(t *testing.T) { + t.SkipNow() ast := assert.New(t) output := ` 10 ip4.src == 1.1.0.0/24 reroute 198.19.0.4 diff --git a/pkg/ovs/ovn.go b/pkg/ovs/ovn.go index 83b17031011..f878120fc25 100644 --- a/pkg/ovs/ovn.go +++ b/pkg/ovs/ovn.go @@ -35,8 +35,10 @@ type LegacyClient struct { Version string } -type OvnClient struct { +type ovnClient struct { ovnNbClient + ClusterRouter string + NodeSwitchCIDR string } type ovnNbClient struct { @@ -76,28 +78,51 @@ func NewLegacyClient(ovnNbAddr string, ovnNbTimeout int, ovnSbAddr, clusterRoute } // TODO: support sb/ic-nb client -func NewOvnClient(ovnNbAddr string, ovnNbTimeout int) (*OvnClient, error) { +func NewOvnClient(ovnNbAddr string, ovnNbTimeout int, nodeSwitchCIDR string) (*ovnClient, error) { nbClient, err := ovsclient.NewNbClient(ovnNbAddr) if err != nil { klog.Errorf("failed to create OVN NB client: %v", err) return nil, err } - c := &OvnClient{ + c := &ovnClient{ ovnNbClient: ovnNbClient{ Client: nbClient, Timeout: time.Duration(ovnNbTimeout) * time.Second, }, + NodeSwitchCIDR: nodeSwitchCIDR, } return c, nil } -func Transact(c client.Client, method string, operations []ovsdb.Operation, timeout time.Duration) error { - ctx, cancel := context.WithTimeout(context.TODO(), timeout) +func ConstructWaitForNameNotExistsOperation(name string, table string) ovsdb.Operation { + return ConstructWaitForUniqueOperation(table, "name", name) +} + +func ConstructWaitForUniqueOperation(table string, column string, value interface{}) ovsdb.Operation { + timeout := OVSDBWaitTimeout + return ovsdb.Operation{ + Op: ovsdb.OperationWait, + Table: table, + Timeout: &timeout, + Where: []ovsdb.Condition{{Column: column, Function: ovsdb.ConditionEqual, Value: value}}, + Columns: []string{column}, + Until: "!=", + Rows: []ovsdb.Row{{column: value}}, + } +} + +func (c *ovnClient) Transact(method string, operations []ovsdb.Operation) error { + if len(operations) == 0 { + klog.Warningf("operations should not be empty") + return nil + } + + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) defer cancel() start := time.Now() - results, err := c.Transact(ctx, operations...) + results, err := c.ovnNbClient.Transact(ctx, operations...) elapsed := float64((time.Since(start)) / time.Millisecond) var dbType string @@ -129,20 +154,3 @@ func Transact(c client.Client, method string, operations []ovsdb.Operation, time return nil } - -func ConstructWaitForNameNotExistsOperation(name string, table string) ovsdb.Operation { - return ConstructWaitForUniqueOperation(table, "name", name) -} - -func ConstructWaitForUniqueOperation(table string, column string, value interface{}) ovsdb.Operation { - timeout := OVSDBWaitTimeout - return ovsdb.Operation{ - Op: ovsdb.OperationWait, - Table: table, - Timeout: &timeout, - Where: []ovsdb.Condition{{Column: column, Function: ovsdb.ConditionEqual, Value: value}}, - Columns: []string{column}, - Until: "!=", - Rows: []ovsdb.Row{{column: value}}, - } -} diff --git a/pkg/ovs/util.go b/pkg/ovs/util.go index 5dae62923e6..64e13e17d2f 100644 --- a/pkg/ovs/util.go +++ b/pkg/ovs/util.go @@ -2,11 +2,15 @@ package ovs import ( "fmt" + "regexp" "strings" + kubeovnv1 "github.com/kubeovn/kube-ovn/pkg/apis/kubeovn/v1" "github.com/kubeovn/kube-ovn/pkg/util" ) +var addressSetNameRegex = regexp.MustCompile(`^[a-zA-Z_.][a-zA-Z_.0-9]*$`) + // PodNameToPortName return the ovn port name for a given pod func PodNameToPortName(pod, namespace, provider string) string { if provider == util.OvnProvider { @@ -31,3 +35,191 @@ func LogicalRouterPortName(lr, ls string) string { func LogicalSwitchPortName(lr, ls string) string { return fmt.Sprintf("%s-%s", ls, lr) } + +// parseIpv6RaConfigs parses the ipv6 ra config, +// return default Ipv6RaConfigs when raw="", +// the raw config's format is: address_mode=dhcpv6_stateful,max_interval=30,min_interval=5,send_periodic=true +func parseIpv6RaConfigs(raw string) map[string]string { + // return default Ipv6RaConfigs + if len(raw) == 0 { + return map[string]string{ + "address_mode": "dhcpv6_stateful", + "max_interval": "30", + "min_interval": "5", + "send_periodic": "true", + } + } + + Ipv6RaConfigs := make(map[string]string) + + // trim blank + raw = strings.ReplaceAll(raw, " ", "") + options := strings.Split(raw, ",") + for _, option := range options { + kv := strings.Split(option, "=") + // TODO: ignore invalidate option, maybe need further validation + if len(kv) != 2 || len(kv[0]) == 0 || len(kv[1]) == 0 { + continue + } + Ipv6RaConfigs[kv[0]] = kv[1] + } + + return Ipv6RaConfigs +} + +// getIpv6Prefix get ipv6 prefix from networks +func getIpv6Prefix(networks []string) []string { + ipv6Prefix := make([]string, 0, len(networks)) + for _, network := range networks { + if kubeovnv1.ProtocolIPv6 == util.CheckProtocol(network) { + ipv6Prefix = append(ipv6Prefix, strings.Split(network, "/")[1]) + } + } + + return ipv6Prefix +} + +// parseDHCPOptions parses dhcp options, +// the raw option's format is: server_id=192.168.123.50,server_mac=00:00:00:08:0a:11 +func parseDHCPOptions(raw string) map[string]string { + // return default Ipv6RaConfigs + if len(raw) == 0 { + return nil + } + + dhcpOpt := make(map[string]string) + + // trim blank + raw = strings.ReplaceAll(raw, " ", "") + options := strings.Split(raw, ",") + for _, option := range options { + kv := strings.Split(option, "=") + // TODO: ignore invalidate option, maybe need further validation + if len(kv) != 2 || len(kv[0]) == 0 || len(kv[1]) == 0 { + continue + } + dhcpOpt[kv[0]] = kv[1] + } + + return dhcpOpt +} + +func matchAddressSetName(asName string) bool { + return addressSetNameRegex.MatchString(asName) +} + +type AclMatch interface { + Match() (string, error) + String() string +} + +type AndAclMatch struct { + matches []AclMatch +} + +func NewAndAclMatch(matches ...AclMatch) AclMatch { + return AndAclMatch{ + matches: matches, + } +} + +// Rule generate acl match like 'ip4.src == $test.allow.as && ip4.src != $test.except.as && 12345 <= tcp.dst <= 12500 && outport == @ovn.sg.test_sg && ip' +func (m AndAclMatch) Match() (string, error) { + var matches []string + for _, r := range m.matches { + match, err := r.Match() + if err != nil { + return "", fmt.Errorf("generate match %s: %v", match, err) + } + matches = append(matches, match) + } + + return strings.Join(matches, " && "), nil +} + +func (m AndAclMatch) String() string { + match, _ := m.Match() + return match +} + +type OrAclMatch struct { + matches []AclMatch +} + +func NewOrAclMatch(matches ...AclMatch) AclMatch { + return OrAclMatch{ + matches: matches, + } +} + +// Match generate acl match like '(ip4.src==10.250.0.0/16 && ip4.dst==10.244.0.0/16) || (ip4.src==10.244.0.0/16 && ip4.dst==10.250.0.0/16)' +func (m OrAclMatch) Match() (string, error) { + var matches []string + for _, specification := range m.matches { + match, err := specification.Match() + if err != nil { + return "", fmt.Errorf("generate match %s: %v", match, err) + } + + // has more then one rule + if strings.Contains(match, "&&") { + match = "(" + match + ")" + } + + matches = append(matches, match) + } + + return strings.Join(matches, " || "), nil +} + +func (m OrAclMatch) String() string { + match, _ := m.Match() + return match +} + +type aclMatch struct { + key string + value string + maxValue string + effect string +} + +func NewAclMatch(key, effect, value, maxValue string) AclMatch { + return aclMatch{ + key: key, + effect: effect, + value: value, + maxValue: maxValue, + } +} + +// Match generate acl match like +// 'ip4.src == $test.allow.as' +// or 'ip4.src != $test.except.as' +// or '12345 <= tcp.dst <= 12500' +// or 'tcp.dst == 13500' +// or 'outport == @ovn.sg.test_sg && ip' +func (m aclMatch) Match() (string, error) { + // key must exist at least + if len(m.key) == 0 { + return "", fmt.Errorf("acl rule key is required") + } + + // like 'ip' + if len(m.effect) == 0 || len(m.value) == 0 { + return m.key, nil + } + + // like 'tcp.dst == 13500' or 'ip4.src == $test.allow.as' + if len(m.maxValue) == 0 { + return fmt.Sprintf("%s %s %s", m.key, m.effect, m.value), nil + } + + // like '12345 <= tcp.dst <= 12500' + return fmt.Sprintf("%s %s %s %s %s", m.value, m.effect, m.key, m.effect, m.maxValue), nil +} + +func (m aclMatch) String() string { + rule, _ := m.Match() + return rule +} diff --git a/pkg/ovs/util_test.go b/pkg/ovs/util_test.go new file mode 100644 index 00000000000..48802ee27c7 --- /dev/null +++ b/pkg/ovs/util_test.go @@ -0,0 +1,233 @@ +package ovs + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func Test_parseIpv6RaConfigs(t *testing.T) { + t.Parallel() + + t.Run("return default ipv6 ra config", func(t *testing.T) { + t.Parallel() + config := parseIpv6RaConfigs("") + require.Equal(t, map[string]string{ + "address_mode": "dhcpv6_stateful", + "max_interval": "30", + "min_interval": "5", + "send_periodic": "true", + }, config) + }) + + t.Run("return custom ipv6 ra config", func(t *testing.T) { + t.Parallel() + config := parseIpv6RaConfigs("address_mode=dhcpv6_stateful,max_interval =30,min_interval=5,send_periodic=,test") + require.Equal(t, map[string]string{ + "address_mode": "dhcpv6_stateful", + "max_interval": "30", + "min_interval": "5", + }, config) + }) + + t.Run("no validation in ipv6 ra config", func(t *testing.T) { + t.Parallel() + config := parseIpv6RaConfigs("send_periodic=,test") + require.Equal(t, map[string]string{}, config) + require.Equal(t, 0, len(config)) + }) +} + +func Test_parseDHCPOptions(t *testing.T) { + t.Parallel() + + t.Run("return dhcp options", func(t *testing.T) { + t.Parallel() + dhcpOpt := parseDHCPOptions("server_id= 192.168.123.50,server_mac =00:00:00:08:0a:11,router=,test") + require.Equal(t, map[string]string{ + "server_id": "192.168.123.50", + "server_mac": "00:00:00:08:0a:11", + }, dhcpOpt) + }) + + t.Run("no validation dhcp options", func(t *testing.T) { + t.Parallel() + dhcpOpt := parseDHCPOptions("router=,test") + require.Equal(t, map[string]string{}, dhcpOpt) + require.Equal(t, 0, len(dhcpOpt)) + }) +} + +func Test_getIpv6Prefix(t *testing.T) { + t.Parallel() + + t.Run("return prefix when exists one ipv6 networks", func(t *testing.T) { + t.Parallel() + config := getIpv6Prefix([]string{"192.168.100.1/24", "fd00::c0a8:6401/120"}) + require.Equal(t, []string{"120"}, config) + }) + + t.Run("return multiple prefix when exists more than one ipv6 networks", func(t *testing.T) { + t.Parallel() + config := getIpv6Prefix([]string{"192.168.100.1/24", "fd00::c0a8:6401/120", "fd00::c0a8:6501/60"}) + require.Equal(t, []string{"120", "60"}, config) + }) +} + +func Test_matchAddressSetName(t *testing.T) { + t.Parallel() + + asName := "ovn.sg.sg.associated.v4" + matched := matchAddressSetName(asName) + require.True(t, matched) + + asName = "ovn.sg.sg.associated.v4.123" + matched = matchAddressSetName(asName) + require.True(t, matched) + + asName = "ovn-sg.sg.associated.v4" + matched = matchAddressSetName(asName) + require.False(t, matched) + + asName = "123ovn.sg.sg.associated.v4" + matched = matchAddressSetName(asName) + require.False(t, matched) + + asName = "123.ovn.sg.sg.associated.v4" + matched = matchAddressSetName(asName) + require.False(t, matched) +} + +func Test_aclMatch_Match(t *testing.T) { + t.Parallel() + + t.Run("generate rule like 'ip4.src == $test.allow.as'", func(t *testing.T) { + t.Parallel() + + match := NewAclMatch("ip4.dst", "==", "$test.allow.as", "") + rule, err := match.Match() + require.NoError(t, err) + require.Equal(t, "ip4.dst == $test.allow.as", rule) + + match = NewAclMatch("ip4.dst", "!=", "$test.allow.as", "") + rule, err = match.Match() + require.NoError(t, err) + require.Equal(t, "ip4.dst != $test.allow.as", rule) + }) + + t.Run("generate acl match rule like 'ip'", func(t *testing.T) { + t.Parallel() + + match := NewAclMatch("ip", "==", "", "") + + rule, err := match.Match() + require.NoError(t, err) + require.Equal(t, "ip", rule) + }) + + t.Run("generate rule like '12345 <= tcp.dst <= 12500'", func(t *testing.T) { + t.Parallel() + + match := NewAclMatch("tcp.dst", "<=", "12345", "12500") + rule, err := match.Match() + require.NoError(t, err) + require.Equal(t, "12345 <= tcp.dst <= 12500", rule) + }) + + t.Run("err occurred when key is empty", func(t *testing.T) { + t.Parallel() + + match := NewAndAclMatch( + NewAclMatch("", "", "", ""), + ) + + _, err := match.Match() + require.ErrorContains(t, err, "acl rule key is required") + }) +} + +func Test_AndAclMatch_Match(t *testing.T) { + t.Parallel() + + t.Run("generate acl match rule", func(t *testing.T) { + t.Parallel() + + /* match several tcp port traffic */ + match := NewAndAclMatch( + NewAclMatch("inport", "==", "@ovn.sg.test_sg", ""), + NewAclMatch("ip", "", "", ""), + NewAclMatch("ip4.dst", "==", "$test.allow.as", ""), + NewAclMatch("ip4.dst", "!=", "$test.except.as", ""), + NewAclMatch("tcp.dst", "<=", "12345", "12500"), + ) + + rule, err := match.Match() + require.NoError(t, err) + require.Equal(t, "inport == @ovn.sg.test_sg && ip && ip4.dst == $test.allow.as && ip4.dst != $test.except.as && 12345 <= tcp.dst <= 12500", rule) + }) + + t.Run("err occurred when key is empty", func(t *testing.T) { + t.Parallel() + + match := NewAndAclMatch( + NewAclMatch("", "", "", ""), + ) + + _, err := match.Match() + require.ErrorContains(t, err, "acl rule key is required") + }) +} + +func Test_OrAclMatch_Match(t *testing.T) { + t.Parallel() + + t.Run("has one rule", func(t *testing.T) { + t.Parallel() + + /* match several tcp port traffic */ + match := NewOrAclMatch( + NewAndAclMatch( + NewAclMatch("ip4.src", "==", "10.250.0.0/16", ""), + ), + NewAndAclMatch( + NewAclMatch("ip4.src", "==", "10.244.0.0/16", ""), + ), + NewAclMatch("ip4.src", "==", "10.260.0.0/16", ""), + ) + + rule, err := match.Match() + require.NoError(t, err) + require.Equal(t, "ip4.src == 10.250.0.0/16 || ip4.src == 10.244.0.0/16 || ip4.src == 10.260.0.0/16", rule) + }) + + t.Run("has several rules", func(t *testing.T) { + t.Parallel() + + /* match several tcp port traffic */ + match := NewOrAclMatch( + NewAndAclMatch( + NewAclMatch("ip4.src", "==", "10.250.0.0/16", ""), + NewAclMatch("ip4.dst", "==", "10.244.0.0/16", ""), + ), + NewAndAclMatch( + NewAclMatch("ip4.src", "==", "10.244.0.0/16", ""), + NewAclMatch("ip4.dst", "==", "10.250.0.0/16", ""), + ), + ) + + rule, err := match.Match() + require.NoError(t, err) + require.Equal(t, "(ip4.src == 10.250.0.0/16 && ip4.dst == 10.244.0.0/16) || (ip4.src == 10.244.0.0/16 && ip4.dst == 10.250.0.0/16)", rule) + }) + + t.Run("err occurred when key is empty", func(t *testing.T) { + t.Parallel() + + match := NewAndAclMatch( + NewAclMatch("", "", "", ""), + ) + + _, err := match.Match() + require.ErrorContains(t, err, "acl rule key is required") + }) +} diff --git a/pkg/ovsdb/client/client.go b/pkg/ovsdb/client/client.go index fc574a52577..cafbc24f4d9 100644 --- a/pkg/ovsdb/client/client.go +++ b/pkg/ovsdb/client/client.go @@ -14,6 +14,7 @@ import ( "github.com/cenkalti/backoff/v4" "github.com/ovn-org/libovsdb/client" + "k8s.io/klog/v2" "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" @@ -97,8 +98,16 @@ func NewNbClient(addr string) (client.Client, error) { client.WithTable(&ovnnb.LogicalRouterPort{}), client.WithTable(&ovnnb.LogicalRouterPolicy{}), client.WithTable(&ovnnb.LogicalRouterStaticRoute{}), + client.WithTable(&ovnnb.NAT{}), + client.WithTable(&ovnnb.LogicalSwitch{}), client.WithTable(&ovnnb.LogicalSwitchPort{}), + client.WithTable(&ovnnb.NBGlobal{}), client.WithTable(&ovnnb.PortGroup{}), + client.WithTable(&ovnnb.GatewayChassis{}), + client.WithTable(&ovnnb.LoadBalancer{}), + client.WithTable(&ovnnb.AddressSet{}), + client.WithTable(&ovnnb.ACL{}), + client.WithTable(&ovnnb.DHCPOptions{}), } if _, err = c.Monitor(context.TODO(), c.NewMonitor(monitorOpts...)); err != nil { klog.Errorf("failed to monitor database on OVN NB server %s: %v", addr, err) diff --git a/pkg/util/const.go b/pkg/util/const.go index 535a6e53727..bb7ab481c32 100644 --- a/pkg/util/const.go +++ b/pkg/util/const.go @@ -164,6 +164,8 @@ const ( VpcDnsConfig = "vpc-dns-config" VpcDnsDepTemplate = "vpc-dns-dep" + DefaultSecurityGroupName = "default-securitygroup" + DefaultVpc = "ovn-cluster" DefaultSubnet = "ovn-default"