Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Initial IPv6 support #2309

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion endpoint/endpoint.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
const (
// RecordTypeA is a RecordType enum value
RecordTypeA = "A"
// RecordTypeAAAA is a RecordType enum value
RecordTypeAAAA = "AAAA"
// RecordTypeCNAME is a RecordType enum value
RecordTypeCNAME = "CNAME"
// RecordTypeTXT is a RecordType enum value
Expand Down Expand Up @@ -128,7 +130,7 @@ type Endpoint struct {
DNSName string `json:"dnsName,omitempty"`
// The targets the DNS record points to
Targets Targets `json:"targets,omitempty"`
// RecordType type of record, e.g. CNAME, A, SRV, TXT etc
// RecordType type of record, e.g. CNAME, A, AAAA, SRV, TXT etc
RecordType string `json:"recordType,omitempty"`
// Identifier to distinguish multiple records with the same name and type (e.g. Route53 records with routing policies other than 'simple')
SetIdentifier string `json:"setIdentifier,omitempty"`
Expand Down
16 changes: 16 additions & 0 deletions endpoint/endpoint_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,22 @@ func TestNewEndpoint(t *testing.T) {
}
}

func TestNewEndpointWithIPv6(t *testing.T) {
e := NewEndpoint("example.org", "AAAA", "foo.com")
if e.DNSName != "example.com" || e.Targets[0] != "foo.com" || e.RecordType != "AAAA" {
t.Error("Endpoint is not initialized correctly")
}

if e.Labels == nil {
t.Error("Labels is not initialized")
}

w := NewEndpoint("example.org", "", "load-balancer.com.")
if w.DNSName != "example.org" || e.Targets[0] != "load-balancer.com" || w.RecordType != "" {
t.Error("Endpoint is not initialized correctly")
}
}

func TestTargetsSame(t *testing.T) {
tests := []Targets{
{""},
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/externaldns/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ var defaultConfig = &Config{
TransIPAccountName: "",
TransIPPrivateKeyFile: "",
DigitalOceanAPIPageSize: 50,
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
GoDaddyAPIKey: "",
GoDaddySecretKey: "",
GoDaddyTTL: 600,
Expand Down Expand Up @@ -379,7 +379,7 @@ func (cfg *Config) ParseFlags(args []string) error {
app.Flag("crd-source-apiversion", "API version of the CRD for crd source, e.g. `externaldns.k8s.io/v1alpha1`, valid only when using crd source").Default(defaultConfig.CRDSourceAPIVersion).StringVar(&cfg.CRDSourceAPIVersion)
app.Flag("crd-source-kind", "Kind of the CRD for the crd source in API group and version specified by crd-source-apiversion").Default(defaultConfig.CRDSourceKind).StringVar(&cfg.CRDSourceKind)
app.Flag("service-type-filter", "The service types to take care about (default: all, expected: ClusterIP, NodePort, LoadBalancer or ExternalName)").StringsVar(&cfg.ServiceTypeFilter)
app.Flag("managed-record-types", "Comma separated list of record types to manage (default: A, CNAME) (supported records: CNAME, A, NS").Default("A", "CNAME").StringsVar(&cfg.ManagedDNSRecordTypes)
app.Flag("managed-record-types", "Comma separated list of record types to manage (default: A, AAAA, CNAME) (supported records: CNAME, A, AAAA, NS").Default("A", "AAAA", "CNAME").StringsVar(&cfg.ManagedDNSRecordTypes)
app.Flag("default-targets", "Set globally default IP address that will apply as a target instead of source addresses. Specify multiple times for multiple targets (optional)").StringsVar(&cfg.DefaultTargets)

// Flags related to providers
Expand Down
4 changes: 2 additions & 2 deletions pkg/apis/externaldns/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ var (
TransIPAccountName: "",
TransIPPrivateKeyFile: "",
DigitalOceanAPIPageSize: 50,
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
RFC2136BatchChangeSize: 50,
}

Expand Down Expand Up @@ -208,7 +208,7 @@ var (
TransIPAccountName: "transip",
TransIPPrivateKeyFile: "/path/to/transip.key",
DigitalOceanAPIPageSize: 100,
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
ManagedDNSRecordTypes: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
RFC2136BatchChangeSize: 100,
}
)
Expand Down
58 changes: 33 additions & 25 deletions plan/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ bar.com | | [->191.1.1.1, ->190.1.1.1] | = create (bar.com -> 1
"=", i.e. result of calculation relies on supplied ConflictResolver
*/
type planTable struct {
rows map[string]map[string]*planTableRow
rows map[string]map[string]map[string]*planTableRow
resolver ConflictResolver
}

func newPlanTable() planTable { //TODO: make resolver configurable
return planTable{map[string]map[string]*planTableRow{}, PerResource{}}
return planTable{map[string]map[string]map[string]*planTableRow{}, PerResource{}}
}

// planTableRow
Expand All @@ -99,23 +99,29 @@ func (t planTableRow) String() string {
func (t planTable) addCurrent(e *endpoint.Endpoint) {
dnsName := normalizeDNSName(e.DNSName)
if _, ok := t.rows[dnsName]; !ok {
t.rows[dnsName] = make(map[string]*planTableRow)
t.rows[dnsName] = make(map[string]map[string]*planTableRow)
}
if _, ok := t.rows[dnsName][e.SetIdentifier]; !ok {
t.rows[dnsName][e.SetIdentifier] = &planTableRow{}
t.rows[dnsName][e.SetIdentifier] = make(map[string]*planTableRow)
}
t.rows[dnsName][e.SetIdentifier].current = e
if _, ok := t.rows[e.SetIdentifier][e.RecordType]; !ok {
t.rows[dnsName][e.SetIdentifier][e.RecordType] = &planTableRow{}
}
t.rows[dnsName][e.SetIdentifier][e.RecordType].current = e
}

func (t planTable) addCandidate(e *endpoint.Endpoint) {
dnsName := normalizeDNSName(e.DNSName)
if _, ok := t.rows[dnsName]; !ok {
t.rows[dnsName] = make(map[string]*planTableRow)
t.rows[dnsName] = make(map[string]map[string]*planTableRow)
}
if _, ok := t.rows[dnsName][e.SetIdentifier]; !ok {
t.rows[dnsName][e.SetIdentifier] = &planTableRow{}
t.rows[dnsName][e.SetIdentifier] = make(map[string]*planTableRow)
}
if _, ok := t.rows[e.SetIdentifier][e.RecordType]; !ok {
t.rows[dnsName][e.SetIdentifier][e.RecordType] = &planTableRow{}
}
t.rows[dnsName][e.SetIdentifier].candidates = append(t.rows[dnsName][e.SetIdentifier].candidates, e)
t.rows[dnsName][e.SetIdentifier][e.RecordType].candidates = append(t.rows[dnsName][e.SetIdentifier][e.RecordType].candidates, e)
}

func (c *Changes) HasChanges() bool {
Expand Down Expand Up @@ -145,24 +151,26 @@ func (p *Plan) Calculate() *Plan {
changes := &Changes{}

for _, topRow := range t.rows {
for _, row := range topRow {
if row.current == nil { //dns name not taken
changes.Create = append(changes.Create, t.resolver.ResolveCreate(row.candidates))
}
if row.current != nil && len(row.candidates) == 0 {
changes.Delete = append(changes.Delete, row.current)
}
for _, midRow := range topRow {
for _, row := range midRow {
if row.current == nil { //dns name not taken
changes.Create = append(changes.Create, t.resolver.ResolveCreate(row.candidates))
}
if row.current != nil && len(row.candidates) == 0 {
changes.Delete = append(changes.Delete, row.current)
}

// TODO: allows record type change, which might not be supported by all dns providers
if row.current != nil && len(row.candidates) > 0 { //dns name is taken
update := t.resolver.ResolveUpdate(row.current, row.candidates)
// compare "update" to "current" to figure out if actual update is required
if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || p.shouldUpdateProviderSpecific(update, row.current) {
inheritOwner(row.current, update)
changes.UpdateNew = append(changes.UpdateNew, update)
changes.UpdateOld = append(changes.UpdateOld, row.current)
// TODO: allows record type change, which might not be supported by all dns providers
if row.current != nil && len(row.candidates) > 0 { //dns name is taken
update := t.resolver.ResolveUpdate(row.current, row.candidates)
// compare "update" to "current" to figure out if actual update is required
if shouldUpdateTTL(update, row.current) || targetChanged(update, row.current) || p.shouldUpdateProviderSpecific(update, row.current) {
inheritOwner(row.current, update)
changes.UpdateNew = append(changes.UpdateNew, update)
changes.UpdateOld = append(changes.UpdateOld, row.current)
}
continue
}
continue
}
}
}
Expand All @@ -174,7 +182,7 @@ func (p *Plan) Calculate() *Plan {
Current: p.Current,
Desired: p.Desired,
Changes: changes,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
}

return plan
Expand Down
107 changes: 60 additions & 47 deletions plan/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ type PlanTestSuite struct {
fooV2CnameNoLabel *endpoint.Endpoint
fooV3CnameSameResource *endpoint.Endpoint
fooA5 *endpoint.Endpoint
fooAAAA *endpoint.Endpoint
dsA *endpoint.Endpoint
dsAAAA *endpoint.Endpoint
bar127A *endpoint.Endpoint
bar127AWithTTL *endpoint.Endpoint
bar127AWithProviderSpecificTrue *endpoint.Endpoint
Expand Down Expand Up @@ -103,6 +106,30 @@ func (suite *PlanTestSuite) SetupTest() {
endpoint.ResourceLabelKey: "ingress/default/foo-5",
},
}
suite.fooAAAA = &endpoint.Endpoint{
DNSName: "foo",
Targets: endpoint.Targets{"2001:DB8::1"},
RecordType: "AAAA",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/foo-AAAA",
},
}
samip5 marked this conversation as resolved.
Show resolved Hide resolved
suite.dsA = &endpoint.Endpoint{
DNSName: "ds",
Targets: endpoint.Targets{"1.1.1.1"},
RecordType: "A",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/ds",
},
}
suite.dsAAAA = &endpoint.Endpoint{
DNSName: "ds",
Targets: endpoint.Targets{"1.1.1.1"},
RecordType: "AAAA",
Labels: map[string]string{
endpoint.ResourceLabelKey: "ingress/default/ds-AAAAA",
},
}
suite.bar127A = &endpoint.Endpoint{
DNSName: "bar",
Targets: endpoint.Targets{"127.0.0.1"},
Expand Down Expand Up @@ -526,53 +553,6 @@ func (suite *PlanTestSuite) TestRemoveEndpointWithUpsert() {
validateEntries(suite.T(), changes.Delete, expectedDelete)
}

//TODO: remove once multiple-target per endpoint is supported
func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceReplace() {
current := []*endpoint.Endpoint{suite.fooV3CnameSameResource, suite.bar192A}
desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV3CnameSameResource}
expectedCreate := []*endpoint.Endpoint{}
expectedUpdateOld := []*endpoint.Endpoint{suite.fooV3CnameSameResource}
expectedUpdateNew := []*endpoint.Endpoint{suite.fooV1Cname}
expectedDelete := []*endpoint.Endpoint{suite.bar192A}

p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
}

changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
validateEntries(suite.T(), changes.Delete, expectedDelete)
}

//TODO: remove once multiple-target per endpoint is supported
func (suite *PlanTestSuite) TestDuplicatedEndpointsForSameResourceRetain() {

current := []*endpoint.Endpoint{suite.fooV1Cname, suite.bar192A}
desired := []*endpoint.Endpoint{suite.fooV1Cname, suite.fooV3CnameSameResource}
expectedCreate := []*endpoint.Endpoint{}
expectedUpdateOld := []*endpoint.Endpoint{}
expectedUpdateNew := []*endpoint.Endpoint{}
expectedDelete := []*endpoint.Endpoint{suite.bar192A}

p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeCNAME},
}

changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
validateEntries(suite.T(), changes.UpdateNew, expectedUpdateNew)
validateEntries(suite.T(), changes.UpdateOld, expectedUpdateOld)
validateEntries(suite.T(), changes.Delete, expectedDelete)
}

func (suite *PlanTestSuite) TestMultipleRecordsSameNameDifferentSetIdentifier() {

current := []*endpoint.Endpoint{suite.multiple1}
Expand Down Expand Up @@ -667,6 +647,39 @@ func (suite *PlanTestSuite) TestDomainFiltersUpdate() {
validateEntries(suite.T(), changes.Delete, expectedDelete)
}

func (suite *PlanTestSuite) TestAAAARecords() {

current := []*endpoint.Endpoint{}
desired := []*endpoint.Endpoint{suite.fooAAAA}
expectedCreate := []*endpoint.Endpoint{suite.fooAAAA}

p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
}

changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
}

func (suite *PlanTestSuite) TestDualStackRecords() {
current := []*endpoint.Endpoint{}
desired := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}
expectedCreate := []*endpoint.Endpoint{suite.dsA, suite.dsAAAA}

p := &Plan{
Policies: []Policy{&SyncPolicy{}},
Current: current,
Desired: desired,
ManagedRecords: []string{endpoint.RecordTypeA, endpoint.RecordTypeAAAA, endpoint.RecordTypeCNAME},
}

changes := p.Calculate().Changes
validateEntries(suite.T(), changes.Create, expectedCreate)
}

func TestPlan(t *testing.T) {
suite.Run(t, new(PlanTestSuite))
}
Expand Down
1 change: 0 additions & 1 deletion provider/pdns/pdns.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,6 @@ func (p *PDNSProvider) ConvertEndpointsToZones(eps []*endpoint.Endpoint, changet
if ep.RecordType == "CNAME" {
t = provider.EnsureTrailingDot(t)
}

records = append(records, pgo.Record{Content: t})
}
rrset := pgo.RrSet{
Expand Down
14 changes: 14 additions & 0 deletions source/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,14 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, pro
DNSName: hostname,
}

eaAAAA := &endpoint.Endpoint{
RecordTTL: ttl,
RecordType: endpoint.RecordTypeAAAA,
Labels: endpoint.NewLabels(),
Targets: make(endpoint.Targets, 0, defaultTargetsCapacity),
DNSName: hostname,
}
samip5 marked this conversation as resolved.
Show resolved Hide resolved

epCNAME := &endpoint.Endpoint{
RecordTTL: ttl,
RecordType: endpoint.RecordTypeCNAME,
Expand Down Expand Up @@ -483,6 +491,9 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, pro
if suitableType(t) == endpoint.RecordTypeA {
epA.Targets = append(epA.Targets, t)
}
if suitableType(t) == endpoint.RecordTypeAAAA {
eaAAAA.Targets = append(eaAAAA.Targets, t)
}
if suitableType(t) == endpoint.RecordTypeCNAME {
epCNAME.Targets = append(epCNAME.Targets, t)
}
Expand All @@ -491,6 +502,9 @@ func (sc *serviceSource) generateEndpoints(svc *v1.Service, hostname string, pro
if len(epA.Targets) > 0 {
endpoints = append(endpoints, epA)
}
if len(eaAAAA.Targets) > 0 {
endpoints = append(endpoints, eaAAAA)
}
if len(epCNAME.Targets) > 0 {
endpoints = append(endpoints, epCNAME)
}
Expand Down
Loading