@@ -1754,6 +1754,301 @@ var _ = Describe("DNSRecordReconciler", func() {
17541754 g .Expect (err ).To (MatchError (ContainSubstring ("not found" )))
17551755 }, TestTimeoutMedium , time .Second , ctx ).Should (Succeed ())
17561756 })
1757+
1758+ // Test that NS records from multiple primaries are correctly merged into authoritative record
1759+ It ("should merge NS records from multiple primaries into authoritative record" , Labels {"primary" , "multi-primary" , "ns-records" }, func (ctx SpecContext ) {
1760+ var primary1AuthRecord , primary2AuthRecord * v1alpha1.DNSRecord
1761+
1762+ // Create delegating NS records on both primaries
1763+ primary1NSRecord := & v1alpha1.DNSRecord {
1764+ ObjectMeta : metav1.ObjectMeta {
1765+ Name : "ns-" + testHostname ,
1766+ Namespace : testNamespace ,
1767+ },
1768+ Spec : v1alpha1.DNSRecordSpec {
1769+ RootHost : testHostname ,
1770+ Endpoints : []* externaldnsendpoint.Endpoint {
1771+ {
1772+ DNSName : testHostname ,
1773+ Targets : []string {"ns1.primary1.example.com" },
1774+ RecordType : "NS" ,
1775+ RecordTTL : 300 ,
1776+ Labels : nil ,
1777+ ProviderSpecific : nil ,
1778+ },
1779+ },
1780+ Delegate : true ,
1781+ },
1782+ }
1783+
1784+ primary2NSRecord := & v1alpha1.DNSRecord {
1785+ ObjectMeta : metav1.ObjectMeta {
1786+ Name : "ns-" + testHostname ,
1787+ Namespace : testNamespace ,
1788+ },
1789+ Spec : v1alpha1.DNSRecordSpec {
1790+ RootHost : testHostname ,
1791+ Endpoints : []* externaldnsendpoint.Endpoint {
1792+ {
1793+ DNSName : testHostname ,
1794+ Targets : []string {"ns1.primary2.example.com" },
1795+ RecordType : "NS" ,
1796+ RecordTTL : 300 ,
1797+ Labels : nil ,
1798+ ProviderSpecific : nil ,
1799+ },
1800+ },
1801+ Delegate : true ,
1802+ },
1803+ }
1804+
1805+ By ("creating delegating NS record on primary-1" )
1806+ Expect (primaryK8sClient .Create (ctx , primary1NSRecord )).To (Succeed ())
1807+
1808+ By ("creating delegating NS record on primary-2" )
1809+ Expect (primary2K8sClient .Create (ctx , primary2NSRecord )).To (Succeed ())
1810+
1811+ By ("verifying the status of primary-1 and primary-2 NS records" )
1812+ Eventually (func (g Gomega ) {
1813+ // Find the primary-1 NS record
1814+ g .Expect (primaryK8sClient .Get (ctx , client .ObjectKeyFromObject (primary1NSRecord ), primary1NSRecord )).To (Succeed ())
1815+ // Find the primary-2 NS record
1816+ g .Expect (primary2K8sClient .Get (ctx , client .ObjectKeyFromObject (primary2NSRecord ), primary2NSRecord )).To (Succeed ())
1817+
1818+ // Verify the expected state of the primary records
1819+ g .Expect (primary1NSRecord .Status .Conditions ).To (
1820+ ContainElement (MatchFields (IgnoreExtras , Fields {
1821+ "Type" : Equal (string (v1alpha1 .ConditionTypeReady )),
1822+ "Status" : Equal (metav1 .ConditionTrue ),
1823+ "Reason" : Equal ("ProviderSuccess" ),
1824+ "Message" : Equal ("Provider ensured the dns record" ),
1825+ "ObservedGeneration" : Equal (primary1NSRecord .Generation ),
1826+ })),
1827+ )
1828+ g .Expect (primary1NSRecord .IsDelegating ()).To (BeTrue ())
1829+ g .Expect (primary1NSRecord .Status .DomainOwners ).To (ConsistOf (primary1NSRecord .GetUIDHash (), primary2NSRecord .GetUIDHash ()))
1830+
1831+ g .Expect (primary2NSRecord .Status .Conditions ).To (
1832+ ContainElement (MatchFields (IgnoreExtras , Fields {
1833+ "Type" : Equal (string (v1alpha1 .ConditionTypeReady )),
1834+ "Status" : Equal (metav1 .ConditionTrue ),
1835+ "Reason" : Equal ("ProviderSuccess" ),
1836+ "Message" : Equal ("Provider ensured the dns record" ),
1837+ "ObservedGeneration" : Equal (primary2NSRecord .Generation ),
1838+ })),
1839+ )
1840+ g .Expect (primary2NSRecord .IsDelegating ()).To (BeTrue ())
1841+ g .Expect (primary2NSRecord .Status .DomainOwners ).To (ConsistOf (primary1NSRecord .GetUIDHash (), primary2NSRecord .GetUIDHash ()))
1842+ }, TestTimeoutLong , time .Second ).Should (Succeed ())
1843+
1844+ By ("verifying an authoritative record exists for the test host on both primary-1 and primary-2" )
1845+ Eventually (func (g Gomega ) {
1846+ // Find the authoritative record on primary-1
1847+ authRecords := & v1alpha1.DNSRecordList {}
1848+ g .Expect (primaryK8sClient .List (ctx , authRecords , client .InNamespace (testNamespace ), client.MatchingLabels {v1alpha1 .AuthoritativeRecordLabel : "true" , v1alpha1 .AuthoritativeRecordHashLabel : common .HashRootHost (testHostname )})).To (Succeed ())
1849+ g .Expect (authRecords .Items ).To (HaveLen (1 ))
1850+ primary1AuthRecord = & authRecords .Items [0 ]
1851+
1852+ // Find the authoritative record on primary-2
1853+ g .Expect (primary2K8sClient .List (ctx , authRecords , client .InNamespace (testNamespace ), client.MatchingLabels {v1alpha1 .AuthoritativeRecordLabel : "true" , v1alpha1 .AuthoritativeRecordHashLabel : common .HashRootHost (testHostname )})).To (Succeed ())
1854+ g .Expect (authRecords .Items ).To (HaveLen (1 ))
1855+ primary2AuthRecord = & authRecords .Items [0 ]
1856+ }, TestTimeoutMedium , time .Second ).Should (Succeed ())
1857+
1858+ By (fmt .Sprintf ("setting the inmemory dns provider as the default in the '%s' test namespace on primary-1" , testNamespace ))
1859+ // Set the default-provider label on the provider secret for primary-1
1860+ labels := primary1DNSProviderSecret .GetLabels ()
1861+ if labels == nil {
1862+ labels = map [string ]string {}
1863+ }
1864+ labels [v1alpha1 .DefaultProviderSecretLabel ] = "true"
1865+ primary1DNSProviderSecret .SetLabels (labels )
1866+ Expect (primaryK8sClient .Update (ctx , primary1DNSProviderSecret )).To (Succeed ())
1867+
1868+ By (fmt .Sprintf ("setting the inmemory dns provider as the default in the '%s' test namespace on primary-2" , testNamespace ))
1869+ // Set the default-provider label on the provider secret for primary-2
1870+ labels = primary2DNSProviderSecret .GetLabels ()
1871+ if labels == nil {
1872+ labels = map [string ]string {}
1873+ }
1874+ labels [v1alpha1 .DefaultProviderSecretLabel ] = "true"
1875+ primary2DNSProviderSecret .SetLabels (labels )
1876+ Expect (primary2K8sClient .Update (ctx , primary2DNSProviderSecret )).To (Succeed ())
1877+
1878+ By ("verifying the authoritative records have the correct merged NS endpoints" )
1879+ Eventually (func (g Gomega ) {
1880+ // Get the authoritative record on primary-1
1881+ g .Expect (primaryK8sClient .Get (ctx , client .ObjectKeyFromObject (primary1AuthRecord ), primary1AuthRecord )).To (Succeed ())
1882+
1883+ // Verify the expected state of the authoritative record
1884+ g .Expect (primary1AuthRecord .Name ).To (Equal (fmt .Sprintf ("authoritative-record-%s" , common .HashRootHost (testHostname ))))
1885+ g .Expect (primary1AuthRecord .IsDelegating ()).To (BeFalse ())
1886+ g .Expect (primary1AuthRecord .Spec .RootHost ).To (Equal (testHostname ))
1887+ g .Expect (primary1AuthRecord .Labels ).Should (HaveKeyWithValue ("kuadrant.io/dns-provider-name" , "inmemory" ))
1888+ g .Expect (primary1AuthRecord .Status .Conditions ).To (
1889+ ContainElement (MatchFields (IgnoreExtras , Fields {
1890+ "Type" : Equal (string (v1alpha1 .ConditionTypeReady )),
1891+ "Status" : Equal (metav1 .ConditionTrue ),
1892+ "Reason" : Equal ("ProviderSuccess" ),
1893+ "Message" : Equal ("Provider ensured the dns record" ),
1894+ })),
1895+ )
1896+
1897+ // Authoritative record should contain the merged NS endpoints from both primaries
1898+ g .Expect (primary1AuthRecord .Spec .Endpoints ).To (HaveLen (3 ))
1899+ g .Expect (primary1AuthRecord .Spec .Endpoints ).To (ContainElements (
1900+ PointTo (MatchFields (IgnoreExtras , Fields {
1901+ "DNSName" : Equal (testHostname ),
1902+ "Targets" : ConsistOf ("ns1.primary1.example.com" , "ns1.primary2.example.com" ),
1903+ "RecordType" : Equal ("NS" ),
1904+ "RecordTTL" : Equal (externaldnsendpoint .TTL (300 )),
1905+ })),
1906+ PointTo (MatchFields (IgnoreExtras , Fields {
1907+ "DNSName" : HaveSuffix (testHostname ),
1908+ "Targets" : ConsistOf ("\" heritage=external-dns,external-dns/owner=" + primary1NSRecord .Status .OwnerID + ",external-dns/version=1\" " ),
1909+ "RecordType" : Equal ("TXT" ),
1910+ "RecordTTL" : Equal (externaldnsendpoint .TTL (0 )),
1911+ })),
1912+ PointTo (MatchFields (IgnoreExtras , Fields {
1913+ "DNSName" : HaveSuffix (testHostname ),
1914+ "Targets" : ConsistOf ("\" heritage=external-dns,external-dns/owner=" + primary2NSRecord .Status .OwnerID + ",external-dns/version=1\" " ),
1915+ "RecordType" : Equal ("TXT" ),
1916+ "RecordTTL" : Equal (externaldnsendpoint .TTL (0 )),
1917+ })),
1918+ ))
1919+
1920+ // Get the authoritative record on primary-2
1921+ g .Expect (primary2K8sClient .Get (ctx , client .ObjectKeyFromObject (primary2AuthRecord ), primary2AuthRecord )).To (Succeed ())
1922+
1923+ // Verify the expected state of the authoritative record on primary-2
1924+ g .Expect (primary2AuthRecord .Name ).To (Equal (fmt .Sprintf ("authoritative-record-%s" , common .HashRootHost (testHostname ))))
1925+ g .Expect (primary2AuthRecord .IsDelegating ()).To (BeFalse ())
1926+ g .Expect (primary2AuthRecord .Spec .RootHost ).To (Equal (testHostname ))
1927+ g .Expect (primary2AuthRecord .Labels ).Should (HaveKeyWithValue ("kuadrant.io/dns-provider-name" , "inmemory" ))
1928+ g .Expect (primary2AuthRecord .Status .Conditions ).To (
1929+ ContainElement (MatchFields (IgnoreExtras , Fields {
1930+ "Type" : Equal (string (v1alpha1 .ConditionTypeReady )),
1931+ "Status" : Equal (metav1 .ConditionTrue ),
1932+ "Reason" : Equal ("ProviderSuccess" ),
1933+ "Message" : Equal ("Provider ensured the dns record" ),
1934+ })),
1935+ )
1936+
1937+ // Authoritative record on primary-2 should also contain the merged NS endpoints
1938+ g .Expect (primary2AuthRecord .Spec .Endpoints ).To (HaveLen (3 ))
1939+ g .Expect (primary2AuthRecord .Spec .Endpoints ).To (ContainElements (
1940+ PointTo (MatchFields (IgnoreExtras , Fields {
1941+ "DNSName" : Equal (testHostname ),
1942+ "Targets" : ConsistOf ("ns1.primary1.example.com" , "ns1.primary2.example.com" ),
1943+ "RecordType" : Equal ("NS" ),
1944+ "RecordTTL" : Equal (externaldnsendpoint .TTL (300 )),
1945+ })),
1946+ PointTo (MatchFields (IgnoreExtras , Fields {
1947+ "DNSName" : HaveSuffix (testHostname ),
1948+ "Targets" : ConsistOf ("\" heritage=external-dns,external-dns/owner=" + primary1NSRecord .Status .OwnerID + ",external-dns/version=1\" " ),
1949+ "RecordType" : Equal ("TXT" ),
1950+ "RecordTTL" : Equal (externaldnsendpoint .TTL (0 )),
1951+ })),
1952+ PointTo (MatchFields (IgnoreExtras , Fields {
1953+ "DNSName" : HaveSuffix (testHostname ),
1954+ "Targets" : ConsistOf ("\" heritage=external-dns,external-dns/owner=" + primary2NSRecord .Status .OwnerID + ",external-dns/version=1\" " ),
1955+ "RecordType" : Equal ("TXT" ),
1956+ "RecordTTL" : Equal (externaldnsendpoint .TTL (0 )),
1957+ })),
1958+ ))
1959+ }, TestTimeoutMedium , time .Second ).Should (Succeed ())
1960+
1961+ By ("updating NS record on primary-1 to add an additional nameserver" )
1962+ Eventually (func (g Gomega ) {
1963+ g .Expect (primaryK8sClient .Get (ctx , client .ObjectKeyFromObject (primary1NSRecord ), primary1NSRecord )).To (Succeed ())
1964+ primary1NSRecord .Spec .Endpoints = []* externaldnsendpoint.Endpoint {
1965+ {
1966+ DNSName : testHostname ,
1967+ Targets : []string {"ns1.primary1.example.com" , "ns2.primary1.example.com" },
1968+ RecordType : "NS" ,
1969+ RecordTTL : 300 ,
1970+ Labels : nil ,
1971+ ProviderSpecific : nil ,
1972+ },
1973+ }
1974+ g .Expect (primaryK8sClient .Update (ctx , primary1NSRecord )).To (Succeed ())
1975+ }, TestTimeoutShort , time .Second ).Should (Succeed ())
1976+
1977+ By ("verifying the authoritative record is updated with the new nameserver" )
1978+ Eventually (func (g Gomega ) {
1979+ // Get the authoritative record on primary-1
1980+ g .Expect (primaryK8sClient .Get (ctx , client .ObjectKeyFromObject (primary1AuthRecord ), primary1AuthRecord )).To (Succeed ())
1981+
1982+ // Authoritative record should now contain all three nameservers
1983+ g .Expect (primary1AuthRecord .Spec .Endpoints ).To (ContainElement (
1984+ PointTo (MatchFields (IgnoreExtras , Fields {
1985+ "DNSName" : Equal (testHostname ),
1986+ "Targets" : ConsistOf ("ns1.primary1.example.com" , "ns2.primary1.example.com" , "ns1.primary2.example.com" ),
1987+ "RecordType" : Equal ("NS" ),
1988+ "RecordTTL" : Equal (externaldnsendpoint .TTL (300 )),
1989+ })),
1990+ ))
1991+
1992+ // Get the authoritative record on primary-2
1993+ g .Expect (primary2K8sClient .Get (ctx , client .ObjectKeyFromObject (primary2AuthRecord ), primary2AuthRecord )).To (Succeed ())
1994+
1995+ // Authoritative record on primary-2 should also be updated
1996+ g .Expect (primary2AuthRecord .Spec .Endpoints ).To (ContainElement (
1997+ PointTo (MatchFields (IgnoreExtras , Fields {
1998+ "DNSName" : Equal (testHostname ),
1999+ "Targets" : ConsistOf ("ns1.primary1.example.com" , "ns2.primary1.example.com" , "ns1.primary2.example.com" ),
2000+ "RecordType" : Equal ("NS" ),
2001+ "RecordTTL" : Equal (externaldnsendpoint .TTL (300 )),
2002+ })),
2003+ ))
2004+ }, TestTimeoutMedium , time .Second ).Should (Succeed ())
2005+
2006+ By ("deleting NS record on primary-1" )
2007+ Expect (primaryK8sClient .Delete (ctx , primary1NSRecord )).To (Succeed ())
2008+
2009+ By ("verifying the authoritative record is updated to remove primary-1 nameservers" )
2010+ Eventually (func (g Gomega ) {
2011+ // Get the authoritative record on primary-1
2012+ g .Expect (primaryK8sClient .Get (ctx , client .ObjectKeyFromObject (primary1AuthRecord ), primary1AuthRecord )).To (Succeed ())
2013+
2014+ // Authoritative record should only contain primary-2's nameserver
2015+ g .Expect (primary1AuthRecord .Spec .Endpoints ).To (HaveLen (2 ))
2016+ g .Expect (primary1AuthRecord .Spec .Endpoints ).To (ContainElements (
2017+ PointTo (MatchFields (IgnoreExtras , Fields {
2018+ "DNSName" : Equal (testHostname ),
2019+ "Targets" : ConsistOf ("ns1.primary2.example.com" ),
2020+ "RecordType" : Equal ("NS" ),
2021+ "RecordTTL" : Equal (externaldnsendpoint .TTL (300 )),
2022+ })),
2023+ PointTo (MatchFields (IgnoreExtras , Fields {
2024+ "DNSName" : HaveSuffix (testHostname ),
2025+ "Targets" : ConsistOf ("\" heritage=external-dns,external-dns/owner=" + primary2NSRecord .Status .OwnerID + ",external-dns/version=1\" " ),
2026+ "RecordType" : Equal ("TXT" ),
2027+ "RecordTTL" : Equal (externaldnsendpoint .TTL (0 )),
2028+ })),
2029+ ))
2030+
2031+ // Get the authoritative record on primary-2
2032+ g .Expect (primary2K8sClient .Get (ctx , client .ObjectKeyFromObject (primary2AuthRecord ), primary2AuthRecord )).To (Succeed ())
2033+
2034+ // Authoritative record on primary-2 should also be updated
2035+ g .Expect (primary2AuthRecord .Spec .Endpoints ).To (HaveLen (2 ))
2036+ g .Expect (primary2AuthRecord .Spec .Endpoints ).To (ContainElements (
2037+ PointTo (MatchFields (IgnoreExtras , Fields {
2038+ "DNSName" : Equal (testHostname ),
2039+ "Targets" : ConsistOf ("ns1.primary2.example.com" ),
2040+ "RecordType" : Equal ("NS" ),
2041+ "RecordTTL" : Equal (externaldnsendpoint .TTL (300 )),
2042+ })),
2043+ PointTo (MatchFields (IgnoreExtras , Fields {
2044+ "DNSName" : HaveSuffix (testHostname ),
2045+ "Targets" : ConsistOf ("\" heritage=external-dns,external-dns/owner=" + primary2NSRecord .Status .OwnerID + ",external-dns/version=1\" " ),
2046+ "RecordType" : Equal ("TXT" ),
2047+ "RecordTTL" : Equal (externaldnsendpoint .TTL (0 )),
2048+ })),
2049+ ))
2050+ }, TestTimeoutMedium , time .Second ).Should (Succeed ())
2051+ })
17572052 })
17582053 })
17592054
0 commit comments