Skip to content

Commit edf5e71

Browse files
Merge pull request #1133 from gcs278/ibm-dns-createOrUpdateDNSRecord-bug
OCPBUGS-32776: Fix IBM Public Cloud DNS Provider Update Logic
2 parents ce5dace + d2160e6 commit edf5e71

File tree

7 files changed

+736
-299
lines changed

7 files changed

+736
-299
lines changed

pkg/dns/ibm/private/client/fake_client.go

Lines changed: 37 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -5,33 +5,37 @@ import (
55

66
"github.com/IBM/go-sdk-core/v5/core"
77
"github.com/IBM/networking-go-sdk/dnssvcsv1"
8-
iov1 "github.com/openshift/api/operatoringress/v1"
98
)
109

1110
type FakeDnsClient struct {
1211
CallHistory map[string]string
1312
DeleteDnsRecordInputOutput DeleteDnsRecordInputOutput
1413
ListAllDnsRecordsInputOutput ListAllDnsRecordsInputOutput
1514
UpdateDnsRecordInputOutput UpdateDnsRecordInputOutput
15+
CreateDnsRecordInputOutput CreateDnsRecordInputOutput
1616
}
1717

1818
type DeleteDnsRecordInputOutput struct {
19-
InputId string
20-
OutputError error
21-
OutputStatusCode int
19+
InputId string
20+
OutputError error
21+
OutputResponse *core.DetailedResponse
2222
}
2323

2424
type UpdateDnsRecordInputOutput struct {
25-
InputId string
26-
OutputError error
27-
OutputStatusCode int
25+
InputId string
26+
OutputError error
27+
OutputResponse *core.DetailedResponse
28+
}
29+
type CreateDnsRecordInputOutput struct {
30+
InputId string
31+
OutputError error
32+
OutputResponse *core.DetailedResponse
2833
}
2934

3035
type ListAllDnsRecordsInputOutput struct {
31-
RecordName string
32-
RecordTarget string
33-
OutputError error
34-
OutputStatusCode int
36+
OutputResult *dnssvcsv1.ListResourceRecords
37+
OutputError error
38+
OutputResponse *core.DetailedResponse
3539
}
3640

3741
func NewFake() (*FakeDnsClient, error) {
@@ -43,42 +47,29 @@ func (c *FakeDnsClient) RecordedCall(zoneID string) (string, bool) {
4347
return call, ok
4448
}
4549

50+
func (c *FakeDnsClient) ClearCallHistory() {
51+
c.CallHistory = map[string]string{}
52+
}
53+
4654
func (FakeDnsClient) NewListResourceRecordsOptions(instanceID string, dnszoneID string) *dnssvcsv1.ListResourceRecordsOptions {
4755
return &dnssvcsv1.ListResourceRecordsOptions{}
4856
}
4957
func (fdc FakeDnsClient) ListResourceRecords(listResourceRecordsOptions *dnssvcsv1.ListResourceRecordsOptions) (result *dnssvcsv1.ListResourceRecords, response *core.DetailedResponse, err error) {
50-
fakeListDnsrecordsResp := &dnssvcsv1.ListResourceRecords{}
51-
recordType := string(iov1.ARecordType)
52-
rData := map[string]interface{}{"ip": fdc.ListAllDnsRecordsInputOutput.RecordTarget}
53-
54-
fakeListDnsrecordsResp.ResourceRecords = append(fakeListDnsrecordsResp.ResourceRecords, dnssvcsv1.ResourceRecord{ID: &fdc.ListAllDnsRecordsInputOutput.RecordName, Name: &fdc.ListAllDnsRecordsInputOutput.RecordName, Type: &recordType, Rdata: rData})
55-
56-
resp := &core.DetailedResponse{
57-
StatusCode: fdc.ListAllDnsRecordsInputOutput.OutputStatusCode,
58-
Headers: map[string][]string{},
59-
Result: result,
60-
RawResult: []byte{},
61-
}
62-
63-
return fakeListDnsrecordsResp, resp, fdc.ListAllDnsRecordsInputOutput.OutputError
58+
// Return the fields in ListAllDnsRecordsInputOutput which will be populated in each of the unit test cases.
59+
return fdc.ListAllDnsRecordsInputOutput.OutputResult, fdc.ListAllDnsRecordsInputOutput.OutputResponse, fdc.ListAllDnsRecordsInputOutput.OutputError
6460
}
6561
func (FakeDnsClient) NewDeleteResourceRecordOptions(instanceID string, dnszoneID string, recordID string) *dnssvcsv1.DeleteResourceRecordOptions {
6662
return &dnssvcsv1.DeleteResourceRecordOptions{InstanceID: &instanceID, DnszoneID: &dnszoneID, RecordID: &recordID}
6763
}
6864
func (fdc FakeDnsClient) DeleteResourceRecord(deleteResourceRecordOptions *dnssvcsv1.DeleteResourceRecordOptions) (response *core.DetailedResponse, err error) {
65+
// Check InputID against the incoming deleteResourceRecordOptions to ensure the
66+
// Delete method is using the correct recordID in deleteResourceRecordOptions.
6967
if fdc.DeleteDnsRecordInputOutput.InputId != *deleteResourceRecordOptions.RecordID {
7068
return nil, errors.New("deleteDnsRecord: inputs don't match")
7169
}
7270

73-
resp := &core.DetailedResponse{
74-
StatusCode: fdc.DeleteDnsRecordInputOutput.OutputStatusCode,
75-
Headers: map[string][]string{},
76-
Result: response,
77-
RawResult: []byte{},
78-
}
79-
8071
fdc.CallHistory[*deleteResourceRecordOptions.RecordID] = "DELETE"
81-
return resp, fdc.DeleteDnsRecordInputOutput.OutputError
72+
return fdc.DeleteDnsRecordInputOutput.OutputResponse, fdc.DeleteDnsRecordInputOutput.OutputError
8273
}
8374
func (FakeDnsClient) NewUpdateResourceRecordOptions(instanceID string, dnszoneID string, recordID string) *dnssvcsv1.UpdateResourceRecordOptions {
8475
return &dnssvcsv1.UpdateResourceRecordOptions{InstanceID: &instanceID, DnszoneID: &dnszoneID, RecordID: &recordID}
@@ -90,31 +81,33 @@ func (FakeDnsClient) NewResourceRecordUpdateInputRdataRdataARecord(ip string) (_
9081
return &dnssvcsv1.ResourceRecordUpdateInputRdataRdataARecord{Ip: &ip}, nil
9182
}
9283
func (fdc FakeDnsClient) UpdateResourceRecord(updateResourceRecordOptions *dnssvcsv1.UpdateResourceRecordOptions) (result *dnssvcsv1.ResourceRecord, response *core.DetailedResponse, err error) {
84+
// Check InputID against the incoming updateResourceRecordOptions to ensure the
85+
// createOrUpdateDNSRecord method is using the correct recordID in updateResourceRecordOptions.
9386
if fdc.UpdateDnsRecordInputOutput.InputId != *updateResourceRecordOptions.RecordID {
9487
return nil, nil, errors.New("updateDnsRecord: inputs don't match")
9588
}
9689

97-
resp := &core.DetailedResponse{
98-
StatusCode: fdc.UpdateDnsRecordInputOutput.OutputStatusCode,
99-
Headers: map[string][]string{},
100-
Result: response,
101-
RawResult: []byte{},
102-
}
103-
10490
fdc.CallHistory[*updateResourceRecordOptions.RecordID] = "PUT"
105-
return nil, resp, fdc.UpdateDnsRecordInputOutput.OutputError
91+
return nil, fdc.UpdateDnsRecordInputOutput.OutputResponse, fdc.UpdateDnsRecordInputOutput.OutputError
10692
}
10793
func (FakeDnsClient) NewCreateResourceRecordOptions(instanceID string, dnszoneID string) *dnssvcsv1.CreateResourceRecordOptions {
108-
return nil
94+
return &dnssvcsv1.CreateResourceRecordOptions{}
10995
}
11096
func (FakeDnsClient) NewResourceRecordInputRdataRdataCnameRecord(cname string) (_model *dnssvcsv1.ResourceRecordInputRdataRdataCnameRecord, err error) {
11197
return nil, nil
11298
}
11399
func (FakeDnsClient) NewResourceRecordInputRdataRdataARecord(ip string) (_model *dnssvcsv1.ResourceRecordInputRdataRdataARecord, err error) {
114100
return nil, nil
115101
}
116-
func (FakeDnsClient) CreateResourceRecord(createResourceRecordOptions *dnssvcsv1.CreateResourceRecordOptions) (result *dnssvcsv1.ResourceRecord, response *core.DetailedResponse, err error) {
117-
return nil, nil, nil
102+
func (fdc FakeDnsClient) CreateResourceRecord(createResourceRecordOptions *dnssvcsv1.CreateResourceRecordOptions) (result *dnssvcsv1.ResourceRecord, response *core.DetailedResponse, err error) {
103+
// Check InputID against the incoming createResourceRecordOptions to ensure the
104+
// createOrUpdateDNSRecord method is using the correct record name in createResourceRecordOptions.
105+
if fdc.CreateDnsRecordInputOutput.InputId != *createResourceRecordOptions.Name {
106+
return nil, nil, errors.New("createResourceRecord: inputs don't match")
107+
}
108+
109+
fdc.CallHistory[*createResourceRecordOptions.Name] = "CREATE"
110+
return nil, fdc.CreateDnsRecordInputOutput.OutputResponse, fdc.CreateDnsRecordInputOutput.OutputError
118111
}
119112
func (FakeDnsClient) NewGetDnszoneOptions(instanceID string, dnszoneID string) *dnssvcsv1.GetDnszoneOptions {
120113
return nil

pkg/dns/ibm/private/dnssvcs_provider.go

Lines changed: 65 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,9 @@ func (p *Provider) Delete(record *iov1.DNSRecord, zone configv1.DNSZone) error {
103103

104104
for _, resourceRecord := range result.ResourceRecords {
105105
var resourceRecordTarget string
106+
if resourceRecord.ID == nil {
107+
return fmt.Errorf("delete: record id is nil")
108+
}
106109
rData, ok := resourceRecord.Rdata.(map[string]interface{})
107110
if !ok {
108111
return fmt.Errorf("delete: failed to get resource data: %v", resourceRecord.Rdata)
@@ -126,7 +129,8 @@ func (p *Provider) Delete(record *iov1.DNSRecord, zone configv1.DNSZone) error {
126129
default:
127130
return fmt.Errorf("delete: resource data has record with unknown type: %v", *resourceRecord.Type)
128131
}
129-
132+
// While creating DNS records with multiple targets is unsupported, we still
133+
// iterate through all targets during deletion to be extra cautious.
130134
for _, target := range record.Spec.Targets {
131135
if *resourceRecord.Name == dnsName {
132136
if resourceRecordTarget != target {
@@ -190,9 +194,14 @@ func (p *Provider) createOrUpdateDNSRecord(record *iov1.DNSRecord, zone configv1
190194
log.Info("Warning: TTL must be one of [1 60 120 300 600 900 1800 3600 7200 18000 43200]. RecordTTL set to default", "default DSNSVCS record TTL", defaultDNSSVCSRecordTTL)
191195
record.Spec.RecordTTL = defaultDNSSVCSRecordTTL
192196
}
197+
if len(record.Spec.Targets) > 1 {
198+
return fmt.Errorf("createOrUpdateDNSRecord: only one target is supported, but found %d: targets=%v", len(record.Spec.Targets), record.Spec.Targets)
199+
}
193200

194201
listResult, response, err := p.dnsService.ListResourceRecords(listOpt)
195202
if err != nil {
203+
// Avoid continuing with an invalid list response, as we can't determine
204+
// whether to create or update the DNS record, which may lead to further issues.
196205
if response == nil || response.StatusCode != http.StatusNotFound {
197206
return fmt.Errorf("createOrUpdateDNSRecord: failed to list the dns record: %w", err)
198207
}
@@ -201,72 +210,74 @@ func (p *Provider) createOrUpdateDNSRecord(record *iov1.DNSRecord, zone configv1
201210
return fmt.Errorf("createOrUpdateDNSRecord: ListResourceRecords returned nil as result")
202211
}
203212

204-
for _, target := range record.Spec.Targets {
205-
updated := false
206-
for _, resourceRecord := range listResult.ResourceRecords {
207-
if *resourceRecord.Name == dnsName {
208-
updateOpt := p.dnsService.NewUpdateResourceRecordOptions(p.config.InstanceID, zone.ID, *resourceRecord.ID)
209-
updateOpt.SetName(dnsName)
210-
211-
if resourceRecord.Type == nil {
212-
return fmt.Errorf("createOrUpdateDNSRecord: failed to get resource type, resourceRecord.Type is nil")
213-
}
213+
target := record.Spec.Targets[0]
214+
updated := false
215+
for _, resourceRecord := range listResult.ResourceRecords {
216+
if *resourceRecord.Name == dnsName {
217+
if resourceRecord.ID == nil {
218+
return fmt.Errorf("createOrUpdateDNSRecord: record id is nil")
219+
}
220+
updateOpt := p.dnsService.NewUpdateResourceRecordOptions(p.config.InstanceID, zone.ID, *resourceRecord.ID)
221+
updateOpt.SetName(dnsName)
214222

215-
// TODO DNS record update should handle the case where we have an A record and want a CNAME record or vice versa
216-
switch *resourceRecord.Type {
217-
case string(iov1.CNAMERecordType):
218-
inputRData, err := p.dnsService.NewResourceRecordUpdateInputRdataRdataCnameRecord(target)
219-
if err != nil {
220-
return fmt.Errorf("createOrUpdateDNSRecord: failed to create CNAME inputRData for the dns record: %w", err)
221-
}
222-
updateOpt.SetRdata(inputRData)
223-
case string(iov1.ARecordType):
224-
inputRData, err := p.dnsService.NewResourceRecordUpdateInputRdataRdataARecord(target)
225-
if err != nil {
226-
return fmt.Errorf("createOrUpdateDNSRecord: failed to create A inputRData for the dns record: %w", err)
227-
}
228-
updateOpt.SetRdata(inputRData)
229-
default:
230-
return fmt.Errorf("createOrUpdateDNSRecord: resource data has record with unknown type: %v", *resourceRecord.Type)
231-
}
232-
updateOpt.SetTTL(record.Spec.RecordTTL)
233-
_, _, err := p.dnsService.UpdateResourceRecord(updateOpt)
234-
if err != nil {
235-
return fmt.Errorf("createOrUpdateDNSRecord: failed to update the dns record: %w", err)
236-
}
237-
updated = true
238-
log.Info("updated DNS record", "record", record.Spec, "zone", zone, "target", target)
223+
if resourceRecord.Type == nil {
224+
return fmt.Errorf("createOrUpdateDNSRecord: failed to get resource type, resourceRecord.Type is nil")
239225
}
240-
}
241-
if !updated {
242-
createOpt := p.dnsService.NewCreateResourceRecordOptions(p.config.InstanceID, zone.ID)
243-
createOpt.SetName(dnsName)
244-
createOpt.SetType(string(record.Spec.RecordType))
245-
246-
switch record.Spec.RecordType {
247-
case iov1.CNAMERecordType:
248-
inputRData, err := p.dnsService.NewResourceRecordInputRdataRdataCnameRecord(target)
226+
227+
// TODO DNS record update should handle the case where we have an A record and want a CNAME record or vice versa
228+
switch *resourceRecord.Type {
229+
case string(iov1.CNAMERecordType):
230+
inputRData, err := p.dnsService.NewResourceRecordUpdateInputRdataRdataCnameRecord(target)
249231
if err != nil {
250232
return fmt.Errorf("createOrUpdateDNSRecord: failed to create CNAME inputRData for the dns record: %w", err)
251233
}
252-
createOpt.SetRdata(inputRData)
253-
case iov1.ARecordType:
254-
inputRData, err := p.dnsService.NewResourceRecordInputRdataRdataARecord(target)
234+
updateOpt.SetRdata(inputRData)
235+
case string(iov1.ARecordType):
236+
inputRData, err := p.dnsService.NewResourceRecordUpdateInputRdataRdataARecord(target)
255237
if err != nil {
256238
return fmt.Errorf("createOrUpdateDNSRecord: failed to create A inputRData for the dns record: %w", err)
257239
}
258-
createOpt.SetRdata(inputRData)
240+
updateOpt.SetRdata(inputRData)
259241
default:
260-
return fmt.Errorf("createOrUpdateDNSRecord: resource data has record with unknown type: %v", record.Spec.RecordType)
261-
242+
return fmt.Errorf("createOrUpdateDNSRecord: resource data has record with unknown type: %v", *resourceRecord.Type)
243+
}
244+
updateOpt.SetTTL(record.Spec.RecordTTL)
245+
_, _, err := p.dnsService.UpdateResourceRecord(updateOpt)
246+
if err != nil {
247+
return fmt.Errorf("createOrUpdateDNSRecord: failed to update the dns record: %w", err)
248+
}
249+
updated = true
250+
log.Info("updated DNS record", "record", record.Spec, "zone", zone, "target", target)
251+
}
252+
}
253+
if !updated {
254+
createOpt := p.dnsService.NewCreateResourceRecordOptions(p.config.InstanceID, zone.ID)
255+
createOpt.SetName(dnsName)
256+
createOpt.SetType(string(record.Spec.RecordType))
257+
258+
switch record.Spec.RecordType {
259+
case iov1.CNAMERecordType:
260+
inputRData, err := p.dnsService.NewResourceRecordInputRdataRdataCnameRecord(target)
261+
if err != nil {
262+
return fmt.Errorf("createOrUpdateDNSRecord: failed to create CNAME inputRData for the dns record: %w", err)
262263
}
263-
createOpt.SetTTL(record.Spec.RecordTTL)
264-
_, _, err := p.dnsService.CreateResourceRecord(createOpt)
264+
createOpt.SetRdata(inputRData)
265+
case iov1.ARecordType:
266+
inputRData, err := p.dnsService.NewResourceRecordInputRdataRdataARecord(target)
265267
if err != nil {
266-
return fmt.Errorf("createOrUpdateDNSRecord: failed to create the dns record: %w", err)
268+
return fmt.Errorf("createOrUpdateDNSRecord: failed to create A inputRData for the dns record: %w", err)
267269
}
268-
log.Info("created DNS record", "record", record.Spec, "zone", zone, "target", target)
270+
createOpt.SetRdata(inputRData)
271+
default:
272+
return fmt.Errorf("createOrUpdateDNSRecord: resource data has record with unknown type: %v", record.Spec.RecordType)
273+
274+
}
275+
createOpt.SetTTL(record.Spec.RecordTTL)
276+
_, _, err := p.dnsService.CreateResourceRecord(createOpt)
277+
if err != nil {
278+
return fmt.Errorf("createOrUpdateDNSRecord: failed to create the dns record: %w", err)
269279
}
280+
log.Info("created DNS record", "record", record.Spec, "zone", zone, "target", target)
270281
}
271282
return nil
272283
}

0 commit comments

Comments
 (0)