Repository: openshift/assisted-service (language: python)

Issue:
[release-4.15] OCPBUGS-32972: Deploy dual stack with IPv6 on top of bond/vlan fails
This is an automated cherry-pick of #6149

/assign bfournie


Discussion / hints:

Fix patch:
diff --git a/internal/network/connectivity_groups.go b/internal/network/connectivity_groups.go
index 7c6afead847..73cde7233a0 100644
--- a/internal/network/connectivity_groups.go
+++ b/internal/network/connectivity_groups.go
@@ -376,6 +376,11 @@ type l2Query struct {
 	connectivityReport models.ConnectivityReport
 }
 
+func (l *l2Query) isIPv6() bool {
+	_, bits := l.parsedCidr.Mask.Size()
+	return bits == net.IPv6len*8
+}
+
 func (l *l2Query) next() strfmt.UUID {
 	for l.current != len(l.connectivityReport.RemoteHosts) {
 		rh := l.connectivityReport.RemoteHosts[l.current]
@@ -386,6 +391,17 @@ func (l *l2Query) next() strfmt.UUID {
 				return rh.HostID
 			}
 		}
+		if l.isIPv6() {
+			// nmap uses NDP (Network Discovery Protocol) for IPv6 L2 discovery.  There are cases that NDP responses do not
+			// arrive and parsed by nmap, but still there is connectivity between the hosts.  In this case we fallback to L3
+			// check and verify that the remote address is in the required subnet.
+			for _, l3 := range rh.L3Connectivity {
+				ip := net.ParseIP(l3.RemoteIPAddress)
+				if ip != nil && l.parsedCidr.Contains(ip) && l3.Successful {
+					return rh.HostID
+				}
+			}
+		}
 	}
 	return ""
 }


Test patch:
diff --git a/internal/network/connectivity_groups_test.go b/internal/network/connectivity_groups_test.go
index 6204c4a386a..5625e1ef2dd 100644
--- a/internal/network/connectivity_groups_test.go
+++ b/internal/network/connectivity_groups_test.go
@@ -515,6 +515,55 @@ func GenerateL2ConnectivityGroupTests(ipV4 bool, net1CIDR, net2CIDR string) {
 	})
 }
 
+var _ = Describe("L2 Ipv6 with L3 fallback", func() {
+	var (
+		nodes    []*node
+		net1CIDR string
+		hosts    []*models.Host
+	)
+
+	BeforeEach(func() {
+		net1CIDR = "2001:db8::/120"
+		nodes = generateIPv6Nodes(7, "2001:db8::/120", "fe80:5054::/120")
+		hosts = []*models.Host{
+			{
+				ID: nodes[0].id,
+				Connectivity: createConnectivityReport(
+					createL2Remote(nodes[1], l2LinkNet1),
+					createL2Remote(nodes[2], l2LinkNet1)),
+			},
+			{
+				ID: nodes[1].id,
+				Connectivity: createConnectivityReport(
+					createL2Remote(nodes[0], l2LinkNet1),
+					createL2Remote(nodes[2], l2LinkNet1)),
+			},
+			{
+				ID: nodes[2].id,
+				Connectivity: createConnectivityReport(
+					createL2Remote(nodes[0], l2LinkNet1)),
+			},
+		}
+	})
+	It("Missing L2 connectivity", func() {
+		ret, err := CreateL2MajorityGroup(net1CIDR, hosts)
+		Expect(err).ToNot(HaveOccurred())
+		Expect(ret).To(HaveLen(0))
+	})
+
+	It("Missing L2 connectivity. Add L3 connectivity", func() {
+		hosts[2].Connectivity = createConnectivityReport(
+			createL2Remote(nodes[0], l2LinkNet1),
+			createL3Remote(nodes[1], l3LinkNet1))
+		ret, err := CreateL2MajorityGroup(net1CIDR, hosts)
+		Expect(err).ToNot(HaveOccurred())
+		Expect(ret).To(HaveLen(3))
+		Expect(ret).To(ContainElement(*nodes[0].id))
+		Expect(ret).To(ContainElement(*nodes[1].id))
+		Expect(ret).To(ContainElement(*nodes[2].id))
+	})
+})
+
 var _ = Describe("L3 connectivity groups all", func() {
 	GenerateL3ConnectivityGroupTests(true, "1.2.3.0/24", "2.2.3.0/24")
 	GenerateL3ConnectivityGroupTests(false, "2001:db8::/120", "fe80:5054::/120")