diff --git a/charts/kube-ovn/templates/kube-ovn-crd.yaml b/charts/kube-ovn/templates/kube-ovn-crd.yaml index 1c858734813..14f0093662e 100644 --- a/charts/kube-ovn/templates/kube-ovn-crd.yaml +++ b/charts/kube-ovn/templates/kube-ovn-crd.yaml @@ -1665,6 +1665,50 @@ spec: type: string type: object type: array + bfdPort: + properties: + enabled: + type: boolean + ip: + type: string + anyOf: + - pattern: ^$ + - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ + - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5]),((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:))),(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ + nodeSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + enum: + - In + - NotIn + - Exists + - DoesNotExist + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + x-kubernetes-validations: + - rule: "self.enabled == false || self.ip != ''" + message: 'Port IP must be set when BFD Port is enabled' type: object status: properties: @@ -1721,6 +1765,17 @@ spec: type: string sctpSessionLoadBalancer: type: string + bfdPort: + type: object + properties: + enabled: + type: boolean + ip: + type: string + nodes: + type: array + items: + type: string type: object type: object served: true diff --git a/dist/images/Dockerfile.base b/dist/images/Dockerfile.base index c72bb38ffd6..ffdce9feb90 100644 --- a/dist/images/Dockerfile.base +++ b/dist/images/Dockerfile.base @@ -59,7 +59,9 @@ RUN cd /usr/src/ && git clone -b branch-24.03 --depth=1 https://github.com/ovn-o # northd: skip arp/nd request for lrp addresses from localnet ports curl -s https://github.com/kubeovn/ovn/commit/283930b627ffa843ebf0e7c3fa0cc70edacfdd12.patch | git apply && \ # ovn-controller: make activation strategy work for single chassis - curl -s https://github.com/kubeovn/ovn/commit/1160d956e49e8f3f1b19535dbf1b9a624a090717.patch | git apply + curl -s https://github.com/kubeovn/ovn/commit/1160d956e49e8f3f1b19535dbf1b9a624a090717.patch | git apply && \ + # support dedicated BFD LRP + curl -s https://github.com/kubeovn/ovn/commit/40345aa35d03c93cde877ccfa8111346291ebc7c.patch | git apply RUN apt install -y build-essential fakeroot \ autoconf automake bzip2 debhelper-compat dh-exec dh-python dh-sequence-python3 dh-sequence-sphinxdoc \ @@ -87,10 +89,12 @@ RUN mkdir -p /usr/src/openbfdd && \ tar -xz -C /usr/src/openbfdd --strip-components=1 ADD OpenBFDD-compile.patch /usr/src/ +ADD OpenBFDD-allow-ttl-254.patch /usr/src/ RUN cd /usr/src/openbfdd && \ rm -vf missing && \ git apply --no-apply /usr/src/OpenBFDD-compile.patch && \ + git apply --no-apply /usr/src/OpenBFDD-allow-ttl-254.patch && \ autoupdate && \ ./autogen.sh && \ ./configure --enable-silent-rules && \ diff --git a/dist/images/OpenBFDD-allow-ttl-254.patch b/dist/images/OpenBFDD-allow-ttl-254.patch new file mode 100644 index 00000000000..7172eae2895 --- /dev/null +++ b/dist/images/OpenBFDD-allow-ttl-254.patch @@ -0,0 +1,26 @@ +From edc60ecd05185acf317ac3ca67c54eb50f9e99a8 Mon Sep 17 00:00:00 2001 +From: zhangzujian +Date: Thu, 31 Oct 2024 02:56:09 +0000 +Subject: [PATCH] allow ttl 254 + +Signed-off-by: zhangzujian +--- + Beacon.cpp | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/Beacon.cpp b/Beacon.cpp +index d1b0658..1d4c3da 100755 +--- a/Beacon.cpp ++++ b/Beacon.cpp +@@ -481,7 +481,7 @@ void Beacon::handleListenSocket(Socket &socket) + } + + // TTL assumes that all control packets are from neighbors. +- if (ttl != 255) ++ if (ttl < 254) + { + gLog.Optional(Log::Discard, "Discard packet: bad ttl/hops %hhu", ttl); + return; +-- +2.43.0 + diff --git a/dist/images/install.sh b/dist/images/install.sh index b4683bb793b..d2d0e23f408 100755 --- a/dist/images/install.sh +++ b/dist/images/install.sh @@ -1912,6 +1912,50 @@ spec: type: string type: object type: array + bfdPort: + properties: + enabled: + type: boolean + ip: + type: string + anyOf: + - pattern: ^$ + - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ + - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + - pattern: ^(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5]),((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:)))$ + - pattern: ^((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|:))),(?:(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])\.){3}(?:[01]?\d{1,2}|2[0-4]\d|25[0-5])$ + nodeSelector: + properties: + matchExpressions: + items: + properties: + key: + type: string + operator: + type: string + enum: + - In + - NotIn + - Exists + - DoesNotExist + values: + items: + type: string + type: array + required: + - key + - operator + type: object + type: array + matchLabels: + additionalProperties: + type: string + type: object + type: object + type: object + x-kubernetes-validations: + - rule: "self.enabled == false || self.ip != ''" + message: 'Port IP must be set when BFD Port is enabled' type: object status: properties: @@ -1968,6 +2012,17 @@ spec: type: string sctpSessionLoadBalancer: type: string + bfdPort: + type: object + properties: + enabled: + type: boolean + ip: + type: string + nodes: + type: array + items: + type: string type: object type: object served: true diff --git a/dist/images/kubectl-ko b/dist/images/kubectl-ko index d0666d40c47..9b34a32e24d 100755 --- a/dist/images/kubectl-ko +++ b/dist/images/kubectl-ko @@ -420,11 +420,11 @@ trace(){ case $type in icmp) set -x - kubectl exec "$OVN_SB_POD" -n $KUBE_OVN_NS -c ovn-central -- ovn-trace "$ls" "inport == \"$lsp\" && ip.ttl == 64 && icmp && eth.src == $mac && ip$af.src == $srcIP && eth.dst == $dstMac && ip$af.dst == $dst && ct.new" + kubectl exec "$OVN_SB_POD" -n $KUBE_OVN_NS -c ovn-central -- ovn-trace "$ls" "inport == \"$lsp\" && ip.ttl == 255 && icmp && eth.src == $mac && ip$af.src == $srcIP && eth.dst == $dstMac && ip$af.dst == $dst && ct.new" ;; tcp|udp) set -x - kubectl exec "$OVN_SB_POD" -n $KUBE_OVN_NS -c ovn-central -- ovn-trace "$ls" "inport == \"$lsp\" && ip.ttl == 64 && eth.src == $mac && ip$af.src == $srcIP && eth.dst == $dstMac && ip$af.dst == $dst && $type.src == 10000 && $type.dst == $4 && ct.new" + kubectl exec "$OVN_SB_POD" -n $KUBE_OVN_NS -c ovn-central -- ovn-trace "$ls" "inport == \"$lsp\" && ip.ttl == 255 && eth.src == $mac && ip$af.src == $srcIP && eth.dst == $dstMac && ip$af.dst == $dst && $type.src == 10000 && $type.dst == $4 && ct.new" ;; arp) case "$4" in diff --git a/mocks/pkg/ovs/interface.go b/mocks/pkg/ovs/interface.go index e9c472beeb2..f5bcdfeef3a 100644 --- a/mocks/pkg/ovs/interface.go +++ b/mocks/pkg/ovs/interface.go @@ -469,6 +469,34 @@ func (mr *MockLogicalRouterPortMockRecorder) LogicalRouterPortExists(lrpName any return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LogicalRouterPortExists", reflect.TypeOf((*MockLogicalRouterPort)(nil).LogicalRouterPortExists), lrpName) } +// SetLogicalRouterPortHAChassisGroup mocks base method. +func (m *MockLogicalRouterPort) SetLogicalRouterPortHAChassisGroup(lrpName, haChassisGroupName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalRouterPortHAChassisGroup", lrpName, haChassisGroupName) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalRouterPortHAChassisGroup indicates an expected call of SetLogicalRouterPortHAChassisGroup. +func (mr *MockLogicalRouterPortMockRecorder) SetLogicalRouterPortHAChassisGroup(lrpName, haChassisGroupName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalRouterPortHAChassisGroup", reflect.TypeOf((*MockLogicalRouterPort)(nil).SetLogicalRouterPortHAChassisGroup), lrpName, haChassisGroupName) +} + +// UpdateLogicalRouterPortNetworks mocks base method. +func (m *MockLogicalRouterPort) UpdateLogicalRouterPortNetworks(lrpName string, networks []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLogicalRouterPortNetworks", lrpName, networks) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateLogicalRouterPortNetworks indicates an expected call of UpdateLogicalRouterPortNetworks. +func (mr *MockLogicalRouterPortMockRecorder) UpdateLogicalRouterPortNetworks(lrpName, networks any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLogicalRouterPortNetworks", reflect.TypeOf((*MockLogicalRouterPort)(nil).UpdateLogicalRouterPortNetworks), lrpName, networks) +} + // UpdateLogicalRouterPortOptions mocks base method. func (m *MockLogicalRouterPort) UpdateLogicalRouterPortOptions(lrpName string, options map[string]string) error { m.ctrl.T.Helper() @@ -497,6 +525,73 @@ func (mr *MockLogicalRouterPortMockRecorder) UpdateLogicalRouterPortRA(lrpName, return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLogicalRouterPortRA", reflect.TypeOf((*MockLogicalRouterPort)(nil).UpdateLogicalRouterPortRA), lrpName, ipv6RAConfigsStr, enableIPv6RA) } +// MockHAChassisGroup is a mock of HAChassisGroup interface. +type MockHAChassisGroup struct { + ctrl *gomock.Controller + recorder *MockHAChassisGroupMockRecorder + isgomock struct{} +} + +// MockHAChassisGroupMockRecorder is the mock recorder for MockHAChassisGroup. +type MockHAChassisGroupMockRecorder struct { + mock *MockHAChassisGroup +} + +// NewMockHAChassisGroup creates a new mock instance. +func NewMockHAChassisGroup(ctrl *gomock.Controller) *MockHAChassisGroup { + mock := &MockHAChassisGroup{ctrl: ctrl} + mock.recorder = &MockHAChassisGroupMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockHAChassisGroup) EXPECT() *MockHAChassisGroupMockRecorder { + return m.recorder +} + +// CreateHAChassisGroup mocks base method. +func (m *MockHAChassisGroup) CreateHAChassisGroup(name string, chassises []string, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateHAChassisGroup", name, chassises, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateHAChassisGroup indicates an expected call of CreateHAChassisGroup. +func (mr *MockHAChassisGroupMockRecorder) CreateHAChassisGroup(name, chassises, externalIDs any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHAChassisGroup", reflect.TypeOf((*MockHAChassisGroup)(nil).CreateHAChassisGroup), name, chassises, externalIDs) +} + +// DeleteHAChassisGroup mocks base method. +func (m *MockHAChassisGroup) DeleteHAChassisGroup(name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteHAChassisGroup", name) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteHAChassisGroup indicates an expected call of DeleteHAChassisGroup. +func (mr *MockHAChassisGroupMockRecorder) DeleteHAChassisGroup(name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteHAChassisGroup", reflect.TypeOf((*MockHAChassisGroup)(nil).DeleteHAChassisGroup), name) +} + +// GetHAChassisGroup mocks base method. +func (m *MockHAChassisGroup) GetHAChassisGroup(name string, ignoreNotFound bool) (*ovnnb.HAChassisGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHAChassisGroup", name, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.HAChassisGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHAChassisGroup indicates an expected call of GetHAChassisGroup. +func (mr *MockHAChassisGroupMockRecorder) GetHAChassisGroup(name, ignoreNotFound any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHAChassisGroup", reflect.TypeOf((*MockHAChassisGroup)(nil).GetHAChassisGroup), name, ignoreNotFound) +} + // MockGatewayChassis is a mock of GatewayChassis interface. type MockGatewayChassis struct { ctrl *gomock.Controller @@ -2725,6 +2820,20 @@ func (mr *MockNbClientMockRecorder) CreateGatewayLogicalSwitch(lsName, lrName, p return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateGatewayLogicalSwitch", reflect.TypeOf((*MockNbClient)(nil).CreateGatewayLogicalSwitch), varargs...) } +// CreateHAChassisGroup mocks base method. +func (m *MockNbClient) CreateHAChassisGroup(name string, chassises []string, externalIDs map[string]string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CreateHAChassisGroup", name, chassises, externalIDs) + ret0, _ := ret[0].(error) + return ret0 +} + +// CreateHAChassisGroup indicates an expected call of CreateHAChassisGroup. +func (mr *MockNbClientMockRecorder) CreateHAChassisGroup(name, chassises, externalIDs any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CreateHAChassisGroup", reflect.TypeOf((*MockNbClient)(nil).CreateHAChassisGroup), name, chassises, externalIDs) +} + // CreateLoadBalancer mocks base method. func (m *MockNbClient) CreateLoadBalancer(lbName, protocol, selectFields string) error { m.ctrl.T.Helper() @@ -3052,6 +3161,20 @@ func (mr *MockNbClientMockRecorder) DeleteDHCPOptionsByUUIDs(uuidList ...any) *g return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteDHCPOptionsByUUIDs", reflect.TypeOf((*MockNbClient)(nil).DeleteDHCPOptionsByUUIDs), uuidList...) } +// DeleteHAChassisGroup mocks base method. +func (m *MockNbClient) DeleteHAChassisGroup(name string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "DeleteHAChassisGroup", name) + ret0, _ := ret[0].(error) + return ret0 +} + +// DeleteHAChassisGroup indicates an expected call of DeleteHAChassisGroup. +func (mr *MockNbClientMockRecorder) DeleteHAChassisGroup(name any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteHAChassisGroup", reflect.TypeOf((*MockNbClient)(nil).DeleteHAChassisGroup), name) +} + // DeleteLoadBalancerHealthCheck mocks base method. func (m *MockNbClient) DeleteLoadBalancerHealthCheck(lbName, vip string) error { m.ctrl.T.Helper() @@ -3350,6 +3473,21 @@ func (mr *MockNbClientMockRecorder) GetEntityInfo(entity any) *gomock.Call { return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetEntityInfo", reflect.TypeOf((*MockNbClient)(nil).GetEntityInfo), entity) } +// GetHAChassisGroup mocks base method. +func (m *MockNbClient) GetHAChassisGroup(name string, ignoreNotFound bool) (*ovnnb.HAChassisGroup, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetHAChassisGroup", name, ignoreNotFound) + ret0, _ := ret[0].(*ovnnb.HAChassisGroup) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// GetHAChassisGroup indicates an expected call of GetHAChassisGroup. +func (mr *MockNbClientMockRecorder) GetHAChassisGroup(name, ignoreNotFound any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetHAChassisGroup", reflect.TypeOf((*MockNbClient)(nil).GetHAChassisGroup), name, ignoreNotFound) +} + // GetLoadBalancer mocks base method. func (m *MockNbClient) GetLoadBalancer(lbName string, ignoreNotFound bool) (*ovnnb.LoadBalancer, error) { m.ctrl.T.Helper() @@ -4254,6 +4392,20 @@ func (mr *MockNbClientMockRecorder) SetLoadBalancerAffinityTimeout(lbName, timeo return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLoadBalancerAffinityTimeout", reflect.TypeOf((*MockNbClient)(nil).SetLoadBalancerAffinityTimeout), lbName, timeout) } +// SetLogicalRouterPortHAChassisGroup mocks base method. +func (m *MockNbClient) SetLogicalRouterPortHAChassisGroup(lrpName, haChassisGroupName string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "SetLogicalRouterPortHAChassisGroup", lrpName, haChassisGroupName) + ret0, _ := ret[0].(error) + return ret0 +} + +// SetLogicalRouterPortHAChassisGroup indicates an expected call of SetLogicalRouterPortHAChassisGroup. +func (mr *MockNbClientMockRecorder) SetLogicalRouterPortHAChassisGroup(lrpName, haChassisGroupName any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetLogicalRouterPortHAChassisGroup", reflect.TypeOf((*MockNbClient)(nil).SetLogicalRouterPortHAChassisGroup), lrpName, haChassisGroupName) +} + // SetLogicalSwitchPortActivationStrategy mocks base method. func (m *MockNbClient) SetLogicalSwitchPortActivationStrategy(lspName, chassis string) error { m.ctrl.T.Helper() @@ -4614,6 +4766,20 @@ func (mr *MockNbClientMockRecorder) UpdateLogicalRouter(lr any, fields ...any) * return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLogicalRouter", reflect.TypeOf((*MockNbClient)(nil).UpdateLogicalRouter), varargs...) } +// UpdateLogicalRouterPortNetworks mocks base method. +func (m *MockNbClient) UpdateLogicalRouterPortNetworks(lrpName string, networks []string) error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "UpdateLogicalRouterPortNetworks", lrpName, networks) + ret0, _ := ret[0].(error) + return ret0 +} + +// UpdateLogicalRouterPortNetworks indicates an expected call of UpdateLogicalRouterPortNetworks. +func (mr *MockNbClientMockRecorder) UpdateLogicalRouterPortNetworks(lrpName, networks any) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateLogicalRouterPortNetworks", reflect.TypeOf((*MockNbClient)(nil).UpdateLogicalRouterPortNetworks), lrpName, networks) +} + // UpdateLogicalRouterPortOptions mocks base method. func (m *MockNbClient) UpdateLogicalRouterPortOptions(lrpName string, options map[string]string) error { m.ctrl.T.Helper() diff --git a/pkg/apis/kubeovn/v1/types.go b/pkg/apis/kubeovn/v1/types.go index 19badbfe2f1..1e9521dd523 100644 --- a/pkg/apis/kubeovn/v1/types.go +++ b/pkg/apis/kubeovn/v1/types.go @@ -429,6 +429,14 @@ type VpcSpec struct { EnableExternal bool `json:"enableExternal,omitempty"` ExtraExternalSubnets []string `json:"extraExternalSubnets,omitempty"` EnableBfd bool `json:"enableBfd,omitempty"` + BFDPort *BFDPort `json:"bfdPort"` +} + +type BFDPort struct { + Enabled bool `json:"enabled"` + IP string `json:"ip,omitempty"` + + NodeSelector *metav1.LabelSelector `json:"nodeSelector,omitempty"` } type VpcPeering struct { @@ -491,6 +499,14 @@ type VpcStatus struct { EnableExternal bool `json:"enableExternal"` ExtraExternalSubnets []string `json:"extraExternalSubnets"` EnableBfd bool `json:"enableBfd"` + + BFDPort BFDPortStatus `json:"bfdPort"` +} + +type BFDPortStatus struct { + Enabled bool `json:"enabled"` + IP string `json:"ip,omitempty"` + Nodes []string `json:"nodes,omitempty"` } // VpcCondition describes the state of an object at a certain point. diff --git a/pkg/apis/kubeovn/v1/zz_generated.deepcopy.go b/pkg/apis/kubeovn/v1/zz_generated.deepcopy.go index 14f37f4fa3d..abe89febd62 100644 --- a/pkg/apis/kubeovn/v1/zz_generated.deepcopy.go +++ b/pkg/apis/kubeovn/v1/zz_generated.deepcopy.go @@ -43,6 +43,48 @@ func (in *ACL) DeepCopy() *ACL { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BFDPort) DeepCopyInto(out *BFDPort) { + *out = *in + if in.NodeSelector != nil { + in, out := &in.NodeSelector, &out.NodeSelector + *out = new(metav1.LabelSelector) + (*in).DeepCopyInto(*out) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BFDPort. +func (in *BFDPort) DeepCopy() *BFDPort { + if in == nil { + return nil + } + out := new(BFDPort) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BFDPortStatus) DeepCopyInto(out *BFDPortStatus) { + *out = *in + if in.Nodes != nil { + in, out := &in.Nodes, &out.Nodes + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BFDPortStatus. +func (in *BFDPortStatus) DeepCopy() *BFDPortStatus { + if in == nil { + return nil + } + out := new(BFDPortStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Condition) DeepCopyInto(out *Condition) { *out = *in @@ -2769,6 +2811,11 @@ func (in *VpcSpec) DeepCopyInto(out *VpcSpec) { *out = make([]string, len(*in)) copy(*out, *in) } + if in.BFDPort != nil { + in, out := &in.BFDPort, &out.BFDPort + *out = new(BFDPort) + (*in).DeepCopyInto(*out) + } return } @@ -2807,6 +2854,7 @@ func (in *VpcStatus) DeepCopyInto(out *VpcStatus) { *out = make([]string, len(*in)) copy(*out, *in) } + in.BFDPort.DeepCopyInto(&out.BFDPort) return } diff --git a/pkg/controller/vpc.go b/pkg/controller/vpc.go index a0dc0d2c59b..5b36a231f82 100644 --- a/pkg/controller/vpc.go +++ b/pkg/controller/vpc.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "math" "net" "reflect" "slices" @@ -43,6 +44,16 @@ func (c *Controller) enqueueAddVpc(obj interface{}) { } } +func vpcBFDPortChanged(oldObj, newObj *kubeovnv1.BFDPort) bool { + if oldObj == nil && newObj == nil { + return false + } + if oldObj == nil || newObj == nil { + return true + } + return oldObj.Enabled != newObj.Enabled || oldObj.IP != newObj.IP || !reflect.DeepEqual(oldObj.NodeSelector, newObj.NodeSelector) +} + func (c *Controller) enqueueUpdateVpc(oldObj, newObj interface{}) { oldVpc := oldObj.(*kubeovnv1.Vpc) newVpc := newObj.(*kubeovnv1.Vpc) @@ -56,14 +67,11 @@ func (c *Controller) enqueueUpdateVpc(oldObj, newObj interface{}) { !reflect.DeepEqual(oldVpc.Spec.ExtraExternalSubnets, newVpc.Spec.ExtraExternalSubnets) || oldVpc.Spec.EnableExternal != newVpc.Spec.EnableExternal || oldVpc.Spec.EnableBfd != newVpc.Spec.EnableBfd || + vpcBFDPortChanged(oldVpc.Spec.BFDPort, newVpc.Spec.BFDPort) || oldVpc.Labels[util.VpcExternalLabel] != newVpc.Labels[util.VpcExternalLabel] { // TODO:// label VpcExternalLabel replace with spec enable external - var ( - key string - err error - ) - - if key, err = cache.MetaNamespaceKeyFunc(newObj); err != nil { + key, err := cache.MetaNamespaceKeyFunc(newObj) + if err != nil { utilruntime.HandleError(err) return } @@ -249,13 +257,7 @@ func (c *Controller) handleAddOrUpdateVpc(key string) error { defer func() { _ = c.vpcKeyMutex.UnlockKey(key) }() klog.Infof("handle add/update vpc %s", key) - // get latest vpc info - var ( - vpc, cachedVpc *kubeovnv1.Vpc - err error - ) - - cachedVpc, err = c.vpcsLister.Get(key) + cachedVpc, err := c.vpcsLister.Get(key) if err != nil { if k8serrors.IsNotFound(err) { return nil @@ -263,9 +265,9 @@ func (c *Controller) handleAddOrUpdateVpc(key string) error { klog.Error(err) return err } - vpc = cachedVpc.DeepCopy() - if vpc, err = c.formatVpc(vpc); err != nil { + vpc, err := c.formatVpc(cachedVpc.DeepCopy()) + if err != nil { klog.Errorf("failed to format vpc %s: %v", key, err) return err } @@ -651,9 +653,108 @@ func (c *Controller) handleAddOrUpdateVpc(key string) error { } } + bfdPortNodes, err := c.reconcileVpcBfdLRP(vpc) + if err != nil { + klog.Error(err) + return err + } + if vpc.Spec.BFDPort == nil || !vpc.Spec.BFDPort.Enabled { + vpc.Status.BFDPort = kubeovnv1.BFDPortStatus{Enabled: false} + } else { + vpc.Status.BFDPort = kubeovnv1.BFDPortStatus{ + Enabled: true, + IP: vpc.Spec.BFDPort.IP, + Nodes: bfdPortNodes, + } + } + if _, err = c.config.KubeOvnClient.KubeovnV1().Vpcs(). + UpdateStatus(context.Background(), vpc, metav1.UpdateOptions{}); err != nil { + klog.Error(err) + return err + } + return nil } +func (c *Controller) reconcileVpcBfdLRP(vpc *kubeovnv1.Vpc) ([]string, error) { + portName := "bfd@" + vpc.Name + if vpc.Spec.BFDPort == nil || !vpc.Spec.BFDPort.Enabled { + if err := c.OVNNbClient.DeleteLogicalRouterPort(portName); err != nil { + err = fmt.Errorf("failed to delete BFD LRP %s: %w", portName, err) + klog.Error(err) + return nil, err + } + if err := c.OVNNbClient.DeleteHAChassisGroup(portName); err != nil { + err = fmt.Errorf("failed to delete HA chassis group %s: %w", portName, err) + klog.Error(err) + return nil, err + } + return nil, nil + } + + var err error + chassisCount := 3 + selector := labels.Everything() + if vpc.Spec.BFDPort.NodeSelector != nil { + chassisCount = math.MaxInt + if selector, err = metav1.LabelSelectorAsSelector(vpc.Spec.BFDPort.NodeSelector); err != nil { + err = fmt.Errorf("failed to parse node selector %q: %w", vpc.Spec.BFDPort.NodeSelector.String(), err) + klog.Error(err) + return nil, err + } + } + + nodes, err := c.nodesLister.List(selector) + if err != nil { + err = fmt.Errorf("failed to list nodes with selector %q: %w", vpc.Spec.BFDPort.NodeSelector, err) + klog.Error(err) + return nil, err + } + if len(nodes) == 0 { + err = fmt.Errorf("no nodes found by selector %q", selector.String()) + klog.Error(err) + return nil, err + } + + nodeNames := make([]string, 0, len(nodes)) + chassisCount = min(chassisCount, len(nodes)) + chassisNames := make([]string, 0, chassisCount) + for _, nodes := range nodes[:chassisCount] { + chassis, err := c.OVNSbClient.GetChassisByHost(nodes.Name) + if err != nil { + err = fmt.Errorf("failed to get chassis of node %s: %w", nodes.Name, err) + klog.Error(err) + return nil, err + } + chassisNames = append(chassisNames, chassis.Name) + nodeNames = append(nodeNames, nodes.Name) + } + + networks := strings.Split(vpc.Spec.BFDPort.IP, ",") + if err = c.OVNNbClient.CreateLogicalRouterPort(vpc.Name, portName, "", networks); err != nil { + klog.Error(err) + return nil, err + } + if err = c.OVNNbClient.UpdateLogicalRouterPortNetworks(portName, networks); err != nil { + klog.Error(err) + return nil, err + } + if err = c.OVNNbClient.UpdateLogicalRouterPortOptions(portName, map[string]string{"bfd-only": "true"}); err != nil { + klog.Error(err) + return nil, err + } + if err = c.OVNNbClient.CreateHAChassisGroup(portName, chassisNames, map[string]string{"lrp": portName}); err != nil { + klog.Error(err) + return nil, err + } + if err = c.OVNNbClient.SetLogicalRouterPortHAChassisGroup(portName, portName); err != nil { + klog.Error(err) + return nil, err + } + + return nodeNames, nil +} + func (c *Controller) addPolicyRouteToVpc(vpcName string, policy *kubeovnv1.PolicyRoute, externalIDs map[string]string) error { var ( nextHops []string @@ -856,19 +957,19 @@ func (c *Controller) formatVpc(vpc *kubeovnv1.Vpc) (*kubeovnv1.Vpc, error) { changed = true } if item.Policy != kubeovnv1.PolicyDst && item.Policy != kubeovnv1.PolicySrc { - return nil, fmt.Errorf("unknown policy type: %s", item.Policy) + return nil, fmt.Errorf("unknown policy type: %q", item.Policy) } // check cidr if strings.Contains(item.CIDR, "/") { if _, _, err := net.ParseCIDR(item.CIDR); err != nil { - return nil, fmt.Errorf("invalid cidr %s: %w", item.CIDR, err) + return nil, fmt.Errorf("invalid cidr %q: %w", item.CIDR, err) } } else if ip := net.ParseIP(item.CIDR); ip == nil { - return nil, fmt.Errorf("invalid ip %s", item.CIDR) + return nil, fmt.Errorf("invalid ip %q", item.CIDR) } // check next hop ip if ip := net.ParseIP(item.NextHopIP); ip == nil { - return nil, fmt.Errorf("invalid next hop ip %s", item.NextHopIP) + return nil, fmt.Errorf("invalid next hop ip %q", item.NextHopIP) } } @@ -896,7 +997,7 @@ func (c *Controller) formatVpc(vpc *kubeovnv1.Vpc) (*kubeovnv1.Vpc, error) { klog.Errorf("failed to update vpc %s: %v", vpc.Name, err) return nil, err } - return newVpc, err + return newVpc, nil } return vpc, nil diff --git a/pkg/ovs/interface.go b/pkg/ovs/interface.go index df6907c4d29..dc3d786235d 100644 --- a/pkg/ovs/interface.go +++ b/pkg/ovs/interface.go @@ -39,7 +39,9 @@ type LogicalRouterPort interface { CreatePeerRouterPort(localRouter, remoteRouter, localRouterPortIP string) error CreateLogicalRouterPort(lrName, lrpName, mac string, networks []string) error UpdateLogicalRouterPortRA(lrpName, ipv6RAConfigsStr string, enableIPv6RA bool) error + UpdateLogicalRouterPortNetworks(lrpName string, networks []string) error UpdateLogicalRouterPortOptions(lrpName string, options map[string]string) error + SetLogicalRouterPortHAChassisGroup(lrpName, haChassisGroupName string) 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) @@ -49,6 +51,12 @@ type LogicalRouterPort interface { LogicalRouterPortExists(lrpName string) (bool, error) } +type HAChassisGroup interface { + CreateHAChassisGroup(name string, chassises []string, externalIDs map[string]string) error + GetHAChassisGroup(name string, ignoreNotFound bool) (*ovnnb.HAChassisGroup, error) + DeleteHAChassisGroup(name string) error +} + type GatewayChassis interface { UpdateGatewayChassis(gwChassis *ovnnb.GatewayChassis, fields ...interface{}) error } @@ -210,6 +218,7 @@ type NbClient interface { BFD DHCPOptions GatewayChassis + HAChassisGroup LoadBalancer LoadBalancerHealthCheck LogicalRouterPolicy diff --git a/pkg/ovs/ovn-nb-ha_chassis_group.go b/pkg/ovs/ovn-nb-ha_chassis_group.go new file mode 100644 index 00000000000..44ac9eca98c --- /dev/null +++ b/pkg/ovs/ovn-nb-ha_chassis_group.go @@ -0,0 +1,177 @@ +package ovs + +import ( + "context" + "errors" + "fmt" + "maps" + "slices" + + "github.com/ovn-org/libovsdb/client" + "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" +) + +// CreateHAChassisGroup adds or updates the ha chassis group +func (c *OVNNbClient) CreateHAChassisGroup(name string, chassises []string, externalIDs map[string]string) error { + group, err := c.GetHAChassisGroup(name, true) + if err != nil { + klog.Error(err) + return err + } + + var ops []ovsdb.Operation + if group == nil { + group = &ovnnb.HAChassisGroup{ + UUID: ovsclient.NamedUUID(), + Name: name, + ExternalIDs: map[string]string{"vendor": util.CniTypeName}, + } + maps.Insert(group.ExternalIDs, maps.All(externalIDs)) + createOps, err := c.Create(group) + if err != nil { + klog.Error(err) + return err + } + ops = append(ops, createOps...) + } else { + group.ExternalIDs = map[string]string{"vendor": util.CniTypeName} + maps.Insert(group.ExternalIDs, maps.All(externalIDs)) + updateOps, err := c.Where(group).Update(group, &group.ExternalIDs) + if err != nil { + klog.Error(err) + return err + } + ops = append(ops, updateOps...) + } + + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + haChassises := make([]*ovnnb.HAChassis, 0, max(len(group.HaChassis), len(chassises))) + if len(group.HaChassis) != 0 { + if err = c.ovsDbClient.WhereCache(func(c *ovnnb.HAChassis) bool { + return slices.Contains(group.HaChassis, c.UUID) + }).List(ctx, &haChassises); err != nil { + klog.Error(err) + return err + } + } + + priorityMap := make(map[string]int, len(chassises)) + for i, chassis := range chassises { + priorityMap[chassis] = 100 - i + } + + uuids := make([]string, 0, len(group.HaChassis)) + for _, chassis := range haChassises { + if priority, ok := priorityMap[chassis.ChassisName]; ok { + delete(priorityMap, chassis.ChassisName) + if chassis.Priority != priority { + // update ha chassis priority + chassis.Priority = priority + updateOps, err := c.Where(chassis).Update(chassis, &chassis.Priority) + if err != nil { + klog.Error(err) + return err + } + ops = append(ops, updateOps...) + } + } else { + uuids = append(uuids, chassis.UUID) + } + } + if len(uuids) != 0 { + // delete ha chassis from the group + deleteOps, err := c.Where(group).Mutate(group, model.Mutation{ + Field: &group.HaChassis, + Value: uuids, + Mutator: ovsdb.MutateOperationDelete, + }) + if err != nil { + klog.Error(err) + return err + } + ops = append(ops, deleteOps...) + } + + // add new ha chassis to the group + for chassis, priority := range priorityMap { + haChassis := &ovnnb.HAChassis{ + UUID: ovsclient.NamedUUID(), + ChassisName: chassis, + Priority: priority, + ExternalIDs: map[string]string{"group": name, "vendor": util.CniTypeName}, + } + createOps, err := c.Create(haChassis) + if err != nil { + klog.Error(err) + return err + } + insertOps, err := c.Where(group).Mutate(group, model.Mutation{ + Field: &group.HaChassis, + Value: []string{haChassis.UUID}, + Mutator: ovsdb.MutateOperationInsert, + }) + if err != nil { + klog.Error(err) + return err + } + ops = append(ops, createOps...) + ops = append(ops, insertOps...) + } + + if err = c.Transact("ha-chassis-group-add", ops); err != nil { + err := fmt.Errorf("failed to add/update HA chassis group %s: %w", name, err) + klog.Error(err) + return err + } + return nil +} + +// GetHAChassisGroup gets the ha chassis group +func (c *OVNNbClient) GetHAChassisGroup(name string, ignoreNotFound bool) (*ovnnb.HAChassisGroup, error) { + ctx, cancel := context.WithTimeout(context.Background(), c.Timeout) + defer cancel() + + group := &ovnnb.HAChassisGroup{Name: name} + if err := c.Get(ctx, group); err != nil { + if ignoreNotFound && errors.Is(err, client.ErrNotFound) { + return nil, nil + } + + return nil, fmt.Errorf("failed to get HA chassis group %q: %w", name, err) + } + + return group, nil +} + +// DeleteHAChassisGroup deletes the ha chassis group +func (c *OVNNbClient) DeleteHAChassisGroup(name string) error { + group, err := c.GetHAChassisGroup(name, true) + if err != nil { + klog.Error(err) + return err + } + if group == nil { + return nil + } + + ops, err := c.Where(group).Delete() + if err != nil { + klog.Error(err) + return err + } + + if err = c.Transact("ha-chassis-group-del", ops); err != nil { + klog.Error(err) + return fmt.Errorf("failed to delete HA chassis group %q: %w", name, err) + } + + return nil +} diff --git a/pkg/ovs/ovn-nb-ha_chassis_group_test.go b/pkg/ovs/ovn-nb-ha_chassis_group_test.go new file mode 100644 index 00000000000..2f5fa07c201 --- /dev/null +++ b/pkg/ovs/ovn-nb-ha_chassis_group_test.go @@ -0,0 +1,96 @@ +package ovs + +import ( + "context" + "slices" + + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/util/uuid" + + "github.com/kubeovn/kube-ovn/pkg/ovsdb/ovnnb" + "github.com/kubeovn/kube-ovn/pkg/util" +) + +func (suite *OvnClientTestSuite) testCreateHAChassisGroup() { + t := suite.T() + t.Parallel() + + nbClient := suite.ovnNBClient + name := "test-create-ha-chassis-group" + chassises := []string{string(uuid.NewUUID()), string(uuid.NewUUID()), string(uuid.NewUUID())} + + err := nbClient.CreateHAChassisGroup(name, chassises, map[string]string{"k1": "v1"}) + require.NoError(t, err) + + group, err := nbClient.GetHAChassisGroup(name, false) + require.NoError(t, err) + require.NotNil(t, group) + require.Equal(t, group.ExternalIDs, map[string]string{"vendor": util.CniTypeName, "k1": "v1"}) + require.Len(t, group.HaChassis, len(chassises)) + for _, uuid := range group.HaChassis { + chassis := &ovnnb.HAChassis{UUID: uuid} + err = nbClient.Get(context.Background(), chassis) + require.NoError(t, err) + require.Contains(t, chassises, chassis.ChassisName) + require.Equal(t, chassis.Priority, 100-slices.Index(chassises, chassis.ChassisName)) + require.Equal(t, chassis.ExternalIDs, map[string]string{"group": name, "vendor": util.CniTypeName}) + } + + // update the ha chassis group + chassises[1], chassises[2] = chassises[2], string(uuid.NewUUID()) + err = nbClient.CreateHAChassisGroup(name, chassises, map[string]string{"k2": "v2"}) + require.NoError(t, err) + + group, err = nbClient.GetHAChassisGroup(name, false) + require.NoError(t, err) + require.NotNil(t, group) + require.Equal(t, group.ExternalIDs, map[string]string{"vendor": util.CniTypeName, "k2": "v2"}) + require.Len(t, group.HaChassis, len(chassises)) + for _, uuid := range group.HaChassis { + chassis := &ovnnb.HAChassis{UUID: uuid} + err = nbClient.Get(context.Background(), chassis) + require.NoError(t, err) + require.Contains(t, chassises, chassis.ChassisName) + require.Equal(t, chassis.Priority, 100-slices.Index(chassises, chassis.ChassisName)) + require.Equal(t, chassis.ExternalIDs, map[string]string{"group": name, "vendor": util.CniTypeName}) + } +} + +func (suite *OvnClientTestSuite) testGetHAChassisGroup() { + t := suite.T() + t.Parallel() + + nbClient := suite.ovnNBClient + name := "non-existent-ha-chassis-group" + + group, err := nbClient.GetHAChassisGroup(name, true) + require.NoError(t, err) + require.Nil(t, group) + + group, err = nbClient.GetHAChassisGroup(name, false) + require.Error(t, err) + require.Nil(t, group) +} + +func (suite *OvnClientTestSuite) testDeleteHAChassisGroup() { + t := suite.T() + t.Parallel() + + nbClient := suite.ovnNBClient + name := "test-delete-ha-chassis-group" + chassises := []string{string(uuid.NewUUID()), string(uuid.NewUUID()), string(uuid.NewUUID())} + + err := nbClient.CreateHAChassisGroup(name, chassises, nil) + require.NoError(t, err) + + group, err := nbClient.GetHAChassisGroup(name, false) + require.NoError(t, err) + require.NotNil(t, group) + + err = nbClient.DeleteHAChassisGroup(name) + require.NoError(t, err) + + group, err = nbClient.GetHAChassisGroup(name, true) + require.NoError(t, err) + require.Nil(t, group) +} diff --git a/pkg/ovs/ovn-nb-logical_router_port.go b/pkg/ovs/ovn-nb-logical_router_port.go index 47778b3de61..bd2e2a217a9 100644 --- a/pkg/ovs/ovn-nb-logical_router_port.go +++ b/pkg/ovs/ovn-nb-logical_router_port.go @@ -4,6 +4,8 @@ import ( "context" "errors" "fmt" + "maps" + "slices" "strings" "github.com/ovn-org/libovsdb/client" @@ -83,6 +85,20 @@ func (c *OVNNbClient) UpdateLogicalRouterPortRA(lrpName, ipv6RAConfigsStr string return c.UpdateLogicalRouterPort(lrp, &lrp.Ipv6Prefix, &lrp.Ipv6RaConfigs) } +func (c *OVNNbClient) UpdateLogicalRouterPortNetworks(lrpName string, networks []string) error { + lrp, err := c.GetLogicalRouterPort(lrpName, false) + if err != nil { + klog.Error(err) + return err + } + if slices.Equal(networks, lrp.Networks) { + return nil + } + + lrp.Networks = slices.Clone(networks) + return c.UpdateLogicalRouterPort(lrp, &lrp.Networks) +} + func (c *OVNNbClient) UpdateLogicalRouterPortOptions(lrpName string, options map[string]string) error { if len(options) == 0 { return nil @@ -94,20 +110,41 @@ func (c *OVNNbClient) UpdateLogicalRouterPortOptions(lrpName string, options map return err } + newOptions := maps.Clone(lrp.Options) for k, v := range options { if len(v) == 0 { - delete(lrp.Options, k) + delete(newOptions, k) } else { - if len(lrp.Options) == 0 { - lrp.Options = make(map[string]string) + if len(newOptions) == 0 { + newOptions = make(map[string]string) } - lrp.Options[k] = v + newOptions[k] = v } } + if maps.Equal(newOptions, lrp.Options) { + return nil + } + lrp.Options = newOptions return c.UpdateLogicalRouterPort(lrp, &lrp.Options) } +func (c *OVNNbClient) SetLogicalRouterPortHAChassisGroup(lrpName, haChassisGroupName string) error { + lrp, err := c.GetLogicalRouterPort(lrpName, false) + if err != nil { + klog.Error(err) + return err + } + group, err := c.GetHAChassisGroup(haChassisGroupName, false) + if err != nil { + klog.Error(err) + return err + } + + lrp.HaChassisGroup = &group.UUID + return c.UpdateLogicalRouterPort(lrp, &lrp.HaChassisGroup) +} + // UpdateLogicalRouterPort update logical router port func (c *OVNNbClient) UpdateLogicalRouterPort(lrp *ovnnb.LogicalRouterPort, fields ...interface{}) error { if lrp == nil { diff --git a/pkg/ovs/ovn-nb-suite_test.go b/pkg/ovs/ovn-nb-suite_test.go index ad134e8e0c4..cdf0dd9223b 100644 --- a/pkg/ovs/ovn-nb-suite_test.go +++ b/pkg/ovs/ovn-nb-suite_test.go @@ -491,6 +491,19 @@ func (suite *OvnClientTestSuite) Test_NewGatewayChassis() { suite.testNewGatewayChassis() } +/* ha_chassis_group unit test */ +func (suite *OvnClientTestSuite) Test_CreateHAChassisGroup() { + suite.testCreateHAChassisGroup() +} + +func (suite *OvnClientTestSuite) Test_GetHAChassisGroup() { + suite.testGetHAChassisGroup() +} + +func (suite *OvnClientTestSuite) Test_DeleteHAChassisGroup() { + suite.testDeleteHAChassisGroup() +} + /* load_balancer unit test */ func (suite *OvnClientTestSuite) Test_CreateLoadBalancer() { suite.testCreateLoadBalancer() @@ -1332,6 +1345,8 @@ func newNbClient(addr string, timeout int) (client.Client, error) { client.WithTable(&ovnnb.BFD{}), client.WithTable(&ovnnb.DHCPOptions{}), client.WithTable(&ovnnb.GatewayChassis{}), + client.WithTable(&ovnnb.HAChassis{}), + client.WithTable(&ovnnb.HAChassisGroup{}), client.WithTable(&ovnnb.LoadBalancer{}), client.WithTable(&ovnnb.LoadBalancerHealthCheck{}), client.WithTable(&ovnnb.LogicalRouterPolicy{}), diff --git a/pkg/ovs/ovn.go b/pkg/ovs/ovn.go index c1baeba2a03..534621ebb6a 100644 --- a/pkg/ovs/ovn.go +++ b/pkg/ovs/ovn.go @@ -66,6 +66,8 @@ func NewOvnNbClient(ovnNbAddr string, ovnNbTimeout, ovsDbConTimeout, ovsDbInacti client.WithTable(&ovnnb.BFD{}), client.WithTable(&ovnnb.DHCPOptions{}), client.WithTable(&ovnnb.GatewayChassis{}), + client.WithTable(&ovnnb.HAChassis{}), + client.WithTable(&ovnnb.HAChassisGroup{}), client.WithTable(&ovnnb.LoadBalancer{}), client.WithTable(&ovnnb.LoadBalancerHealthCheck{}), client.WithTable(&ovnnb.LogicalRouterPolicy{}), diff --git a/test/anp/anp_test.go b/test/anp/anp_test.go index d159d17197e..7dd727a8fe7 100644 --- a/test/anp/anp_test.go +++ b/test/anp/anp_test.go @@ -82,7 +82,7 @@ func TestAdminNetworkPolicyConformance(t *testing.T) { if err != nil { t.Fatalf("error marshalling conformance profile report: %v", err) } - err = os.WriteFile("../../"+reportFileName, rawReport, 0600) + err = os.WriteFile("../../"+reportFileName, rawReport, 0o600) if err != nil { t.Fatalf("error writing conformance profile report: %v", err) }